- Changes some source code, especially in data.c - Added xmake.lua for building game using xmake
1015 lines
30 KiB
C
1015 lines
30 KiB
C
#include <limits.h>
|
|
#include "gameloop.h"
|
|
#include "textures.h"
|
|
#include "utility.h"
|
|
#include "options.h"
|
|
#include "blocks.h"
|
|
#include "menus.h"
|
|
#include "data.h"
|
|
#include "gui.h"
|
|
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
* WARNING!!! *
|
|
* *
|
|
* This file is where all the decompiled nonsense is. Things are so heavily *
|
|
* nested and spaghetti-like that its the only place with an indent size of *
|
|
* two spaces (predominantly). *
|
|
* *
|
|
* Looking at this code for extended periods of time can cause adverse *
|
|
* psychological effects. *
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
|
|
World world = {0};
|
|
Player *player = &world.player;
|
|
|
|
int gameState = STATE_TITLE;
|
|
int gamePopup;
|
|
|
|
static int guiOn;
|
|
static int debugOn;
|
|
|
|
static SDL_Rect backgroundRect;
|
|
static char *errorMessage = NULL;
|
|
static long l;
|
|
|
|
static void gameLoop_gameplay(SDL_Renderer *, Inputs *);
|
|
static void gameLoop_drawPopup(SDL_Renderer *, Inputs *);
|
|
static void gameLoop_processMovement(Inputs *, int);
|
|
|
|
static uint32_t
|
|
fps_lastmil = 0,
|
|
fps_count = 0,
|
|
fps_now = 0;
|
|
|
|
|
|
/* gameLoop_resetGame
|
|
* Resets elements of the game such as time and the player position. This will
|
|
* also reset the world.
|
|
*/
|
|
void gameLoop_resetGame()
|
|
{
|
|
l = SDL_GetTicks();
|
|
|
|
gamePopup = 0;
|
|
|
|
guiOn = 1;
|
|
debugOn = 0;
|
|
|
|
backgroundRect.x = 0;
|
|
backgroundRect.y = 0;
|
|
backgroundRect.w = BUFFER_W;
|
|
backgroundRect.h = BUFFER_H;
|
|
|
|
chatAdd("Game started");
|
|
}
|
|
|
|
|
|
/* gameLoop
|
|
* Does all the raycasting stuff, moves the player around, etc.
|
|
* If by chance the game ends, it returns false - which should
|
|
* terminate the main while loop and end the program.
|
|
*/
|
|
int gameLoop(
|
|
Inputs *inputs,
|
|
SDL_Renderer *renderer)
|
|
{
|
|
// If there is an error, show it and stop
|
|
if (errorMessage)
|
|
{
|
|
if (state_err(renderer, inputs, errorMessage))
|
|
{
|
|
errorMessage = NULL;
|
|
// TODO: add capability to recover from error:
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
switch (gameState)
|
|
{
|
|
case STATE_TITLE:
|
|
// A main menu
|
|
if (state_title(renderer, inputs, &gameState))
|
|
return 0;
|
|
break;
|
|
|
|
case STATE_SELECT_WORLD:
|
|
// World creation menu
|
|
state_selectWorld(renderer, inputs, &gameState, &world);
|
|
break;
|
|
|
|
case STATE_NEW_WORLD:
|
|
// World creation menu
|
|
state_newWorld(renderer, inputs, &gameState, &world);
|
|
break;
|
|
|
|
case STATE_LOADING:
|
|
// Generate a world and present a loading screen
|
|
if (state_loading(renderer, &world, world.seed, player->pos))
|
|
{
|
|
gameLoop_resetGame();
|
|
gameState = 5;
|
|
};
|
|
break;
|
|
|
|
case STATE_GAMEPLAY:
|
|
// The actual gameplay
|
|
gameLoop_gameplay(renderer, inputs);
|
|
break;
|
|
|
|
case STATE_OPTIONS:
|
|
state_options(renderer, inputs, &gameState);
|
|
break;
|
|
|
|
default:
|
|
state_egg(renderer, inputs, &gameState);
|
|
break;
|
|
}
|
|
|
|
if (gameState != STATE_GAMEPLAY || gamePopup != POPUP_HUD)
|
|
{
|
|
inputs->mouse.left = 0;
|
|
inputs->mouse.right = 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* gameLoop_gameplay
|
|
* Runs game logic and rendering. This is where most of the obfuscated code is.
|
|
*/
|
|
static void gameLoop_gameplay(SDL_Renderer *renderer, Inputs *inputs)
|
|
{
|
|
static double
|
|
f21,
|
|
f22,
|
|
f23,
|
|
f24,
|
|
f25,
|
|
f26,
|
|
f27,
|
|
f28,
|
|
f29,
|
|
f30,
|
|
f31,
|
|
f32,
|
|
f33,
|
|
f34,
|
|
f35,
|
|
f36;
|
|
|
|
static int
|
|
blockSelected = 0,
|
|
selectedPass;
|
|
|
|
static IntCoords blockSelect = {0};
|
|
static IntCoords blockSelectOffset = {0};
|
|
static IntCoords coordPass = {0};
|
|
static IntCoords blockRayPosition = {0};
|
|
|
|
static Chunk *chunk;
|
|
|
|
/* Look to see if there are chunks that need to be
|
|
loaded in*/
|
|
|
|
// static int chunkLoadNum = 0;
|
|
// if(chunkLoadNum < CHUNKARR_SIZE) {
|
|
// static IntCoords chunkLoadCoords = { 0 };
|
|
// chunkLoadCoords.x =
|
|
// ((chunkLoadNum % CHUNKARR_DIAM) -
|
|
// CHUNKARR_RAD) * 64 + player.pos.x;
|
|
// chunkLoadCoords.y =
|
|
// (((chunkLoadNum / CHUNKARR_DIAM) % CHUNKARR_DIAM) -
|
|
// CHUNKARR_RAD) * 64 + player.pos.y;
|
|
// chunkLoadCoords.z =
|
|
// ((chunkLoadNum / (CHUNKARR_DIAM * CHUNKARR_DIAM)) -
|
|
// CHUNKARR_RAD) * 64 + player.pos.z;
|
|
// chunkLoadNum++;
|
|
//
|
|
// genChunk (
|
|
// &world, world.seed,
|
|
// chunkLoadCoords.x,
|
|
// chunkLoadCoords.y,
|
|
// chunkLoadCoords.z, world->type, 0,
|
|
// player.pos
|
|
// );
|
|
// } else {
|
|
// chunkLoadNum = 0;
|
|
// }
|
|
|
|
;
|
|
int headInWater = World_getBlock(&world,
|
|
player->pos.x,
|
|
player->pos.y,
|
|
player->pos.z) == BLOCK_WATER;
|
|
|
|
int feetInWater = World_getBlock(&world,
|
|
player->pos.x,
|
|
player->pos.y + 1,
|
|
player->pos.z) == BLOCK_WATER;
|
|
|
|
int effectDrawDistance = options.drawDistance;
|
|
// Restrict view distance while in water
|
|
if (headInWater)
|
|
{
|
|
effectDrawDistance = 10;
|
|
}
|
|
|
|
// Update directional vectors
|
|
player->vectorH.x = sin(player->hRot);
|
|
player->vectorH.y = cos(player->hRot);
|
|
player->vectorV.x = sin(player->vRot);
|
|
player->vectorV.y = cos(player->vRot);
|
|
|
|
// Skybox, basically
|
|
double timeCoef;
|
|
switch (world.dayNightMode)
|
|
{
|
|
case 0:
|
|
timeCoef = (double)(world.time % 102944) / 16384;
|
|
timeCoef = sin(timeCoef);
|
|
timeCoef /= sqrt(timeCoef * timeCoef + (1.0 / 128.0));
|
|
timeCoef = (timeCoef + 1) / 2;
|
|
break;
|
|
case 1:
|
|
timeCoef = 1;
|
|
break;
|
|
case 2:
|
|
timeCoef = 0;
|
|
break;
|
|
}
|
|
|
|
// Change ambient color depending on if we are in the water or the air
|
|
if (headInWater)
|
|
{
|
|
SDL_SetRenderDrawColor(
|
|
renderer,
|
|
48 * timeCoef,
|
|
96 * timeCoef,
|
|
200 * timeCoef,
|
|
255);
|
|
}
|
|
else
|
|
{
|
|
SDL_SetRenderDrawColor(
|
|
renderer,
|
|
153 * timeCoef,
|
|
204 * timeCoef,
|
|
255 * timeCoef,
|
|
255);
|
|
}
|
|
|
|
SDL_RenderClear(renderer);
|
|
|
|
if (inputs->keyboard.esc)
|
|
{
|
|
gamePopup = gamePopup ? 0 : 1;
|
|
inputs->keyboard.esc = 0;
|
|
}
|
|
|
|
fps_count++;
|
|
if (fps_lastmil < SDL_GetTicks() - 1000)
|
|
{
|
|
fps_lastmil = SDL_GetTicks();
|
|
fps_now = fps_count;
|
|
fps_count = 0;
|
|
}
|
|
|
|
/* Things that should run at a constant speed, regardless
|
|
of CPU power. If the rendering takes a long time, this
|
|
will fire more times to compensate. */
|
|
while (SDL_GetTicks() - l > 10L)
|
|
{
|
|
world.time++;
|
|
l += 10L;
|
|
gameLoop_processMovement(inputs, feetInWater);
|
|
}
|
|
|
|
if (gamePopup == POPUP_HUD)
|
|
{
|
|
if (blockSelected)
|
|
{
|
|
InvSlot *activeSlot = &player->inventory.hotbar[player->inventory.hotbarSelect];
|
|
|
|
// Breaking blocks
|
|
if (inputs->mouse.left > 0)
|
|
{
|
|
Block blockid = World_getBlock(
|
|
&world,
|
|
blockSelect.x,
|
|
blockSelect.y,
|
|
blockSelect.z);
|
|
|
|
// Can't break other players
|
|
if (blockid != BLOCK_PLAYER_BODY && blockid != BLOCK_PLAYER_HEAD)
|
|
{
|
|
InvSlot pickedUp = {
|
|
.blockid = blockid,
|
|
.amount = 1,
|
|
.durability = 1};
|
|
|
|
Inventory_transferIn(&player->inventory, &pickedUp);
|
|
|
|
World_setBlock(
|
|
&world,
|
|
blockSelect.x,
|
|
blockSelect.y,
|
|
blockSelect.z, 0, 1);
|
|
}
|
|
}
|
|
|
|
blockSelectOffset.x += blockSelect.x;
|
|
blockSelectOffset.y += blockSelect.y;
|
|
blockSelectOffset.z += blockSelect.z;
|
|
|
|
// Placing blocks
|
|
if (inputs->mouse.right > 0)
|
|
{
|
|
if (
|
|
// Player cannot be obstructing the block
|
|
(
|
|
fabs(player->pos.x - 0.5 - blockSelectOffset.x) >= 0.8 ||
|
|
fabs(player->pos.y - blockSelectOffset.y) >= 1.45 ||
|
|
fabs(player->pos.z - 0.5 - blockSelectOffset.z) >= 0.8) &&
|
|
// Player must have enough of that block
|
|
activeSlot->amount > 0)
|
|
{
|
|
int blockSet = World_setBlock(
|
|
&world,
|
|
blockSelectOffset.x,
|
|
blockSelectOffset.y,
|
|
blockSelectOffset.z,
|
|
activeSlot->blockid, 1);
|
|
|
|
if (blockSet)
|
|
{
|
|
activeSlot->amount--;
|
|
if (activeSlot->amount == 0)
|
|
activeSlot->blockid = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
inputs->mouse.left = 0;
|
|
inputs->mouse.right = 0;
|
|
|
|
// Toggle GUI
|
|
if (inputs->keyboard.f1)
|
|
{
|
|
inputs->keyboard.f1 = 0;
|
|
guiOn ^= 1;
|
|
}
|
|
|
|
// Toggle debug mode
|
|
if (inputs->keyboard.f3)
|
|
{
|
|
inputs->keyboard.f3 = 0;
|
|
debugOn = !debugOn;
|
|
}
|
|
|
|
// Toggle advanced debug menu
|
|
#ifndef small
|
|
if (inputs->keyboard.f4)
|
|
{
|
|
inputs->keyboard.f4 = 0;
|
|
gamePopup = (gamePopup == POPUP_ADVANCED_DEBUG) ? 0 : 4;
|
|
}
|
|
#endif
|
|
|
|
// Enter chat
|
|
if (inputs->keyboard.t)
|
|
{
|
|
// reset text input
|
|
inputs->keyboard.t = 0;
|
|
inputs->keyTyped = 0;
|
|
gamePopup = POPUP_CHAT;
|
|
}
|
|
|
|
// Enter inventory
|
|
if (inputs->keyboard.e)
|
|
{
|
|
inputs->keyboard.e = 0;
|
|
gamePopup = POPUP_INVENTORY;
|
|
}
|
|
|
|
// Swap hotbar selection with offhand
|
|
if (inputs->keyboard.f)
|
|
{
|
|
inputs->keyboard.f = 0;
|
|
InvSlot_swap(
|
|
&player->inventory.hotbar[player->inventory.hotbarSelect],
|
|
&player->inventory.offhand);
|
|
}
|
|
|
|
// Select hotbar slots with scroll wheel
|
|
if (inputs->mouse.wheel != 0)
|
|
{
|
|
player->inventory.hotbarSelect -= inputs->mouse.wheel;
|
|
player->inventory.hotbarSelect = nmod(
|
|
player->inventory.hotbarSelect, 9);
|
|
inputs->mouse.wheel = 0;
|
|
}
|
|
|
|
// Select hotbar slots with number keys
|
|
if (inputs->keyboard.num1)
|
|
{
|
|
player->inventory.hotbarSelect = 0;
|
|
}
|
|
if (inputs->keyboard.num2)
|
|
{
|
|
player->inventory.hotbarSelect = 1;
|
|
}
|
|
if (inputs->keyboard.num3)
|
|
{
|
|
player->inventory.hotbarSelect = 2;
|
|
}
|
|
if (inputs->keyboard.num4)
|
|
{
|
|
player->inventory.hotbarSelect = 3;
|
|
}
|
|
if (inputs->keyboard.num5)
|
|
{
|
|
player->inventory.hotbarSelect = 4;
|
|
}
|
|
if (inputs->keyboard.num6)
|
|
{
|
|
player->inventory.hotbarSelect = 5;
|
|
}
|
|
if (inputs->keyboard.num7)
|
|
{
|
|
player->inventory.hotbarSelect = 6;
|
|
}
|
|
if (inputs->keyboard.num8)
|
|
{
|
|
player->inventory.hotbarSelect = 7;
|
|
}
|
|
if (inputs->keyboard.num9)
|
|
{
|
|
player->inventory.hotbarSelect = 8;
|
|
}
|
|
}
|
|
|
|
/* Cast rays. selectedPass passes wether or not a block is
|
|
selected to the blockSelected variable */
|
|
|
|
// Decrease fov when in water
|
|
double effectFov = options.fov;
|
|
if (headInWater)
|
|
{
|
|
effectFov += 20;
|
|
}
|
|
|
|
selectedPass = 0;
|
|
for (int pixelX = 0; pixelX < BUFFER_W; pixelX++)
|
|
{
|
|
double rayOffsetX = (pixelX - BUFFER_HALF_W) / effectFov;
|
|
for (int pixelY = 0; pixelY < BUFFER_H; pixelY++)
|
|
{
|
|
int finalPixelColor = 0;
|
|
int pixelMist = 255;
|
|
int pixelShade;
|
|
|
|
double rayOffsetY = (pixelY - BUFFER_HALF_H) / effectFov;
|
|
|
|
// Ray offset Z?
|
|
f21 = 1.0;
|
|
|
|
f22 = f21 * player->vectorV.y + rayOffsetY * player->vectorV.x;
|
|
f23 = rayOffsetY * player->vectorV.y - f21 * player->vectorV.x;
|
|
f24 = rayOffsetX * player->vectorH.y + f22 * player->vectorH.x;
|
|
f25 = f22 * player->vectorH.y - rayOffsetX * player->vectorH.x;
|
|
|
|
double rayDistanceLimit = effectDrawDistance;
|
|
|
|
f26 = 5.0;
|
|
for (int blockFace = 0; blockFace < 3; blockFace++)
|
|
{
|
|
f27 = f24;
|
|
if (blockFace == 1)
|
|
f27 = f23;
|
|
if (blockFace == 2)
|
|
f27 = f25;
|
|
f28 = 1.0 / ((f27 < 0.0) ? (-1 * f27) : f27);
|
|
f29 = f24 * f28;
|
|
f30 = f23 * f28;
|
|
f31 = f25 * f28;
|
|
f32 = player->pos.x - floor(player->pos.x);
|
|
if (blockFace == 1)
|
|
f32 = player->pos.y - floor(player->pos.y);
|
|
if (blockFace == 2)
|
|
f32 = player->pos.z - floor(player->pos.z);
|
|
if (f27 > 0.0)
|
|
f32 = 1.0 - f32;
|
|
f33 = f28 * f32;
|
|
f34 = player->pos.x + f29 * f32;
|
|
f35 = player->pos.y + f30 * f32;
|
|
f36 = player->pos.z + f31 * f32;
|
|
if (f27 < 0.0)
|
|
{
|
|
if (blockFace == 0)
|
|
f34--;
|
|
if (blockFace == 1)
|
|
f35--;
|
|
if (blockFace == 2)
|
|
f36--;
|
|
}
|
|
|
|
/* Whatever's in this loop needs to run *extremely*
|
|
fast */
|
|
while (f33 < rayDistanceLimit)
|
|
{
|
|
blockRayPosition.x = floor(f34);
|
|
blockRayPosition.y = floor(f35);
|
|
blockRayPosition.z = floor(f36);
|
|
|
|
/* Imitate getBlock so we don't have to launch
|
|
into a function then another function a zillion
|
|
times per second. This MUST BE STATIC because
|
|
this information needs to carry over between
|
|
iterations of gameLoop. */
|
|
// TODO: make this an inline function
|
|
static IntCoords lookup_ago = {
|
|
100000000,
|
|
100000000,
|
|
100000000},
|
|
lookup_now;
|
|
|
|
lookup_now.x = blockRayPosition.x >> 6;
|
|
lookup_now.y = blockRayPosition.y >> 6;
|
|
lookup_now.z = blockRayPosition.z >> 6;
|
|
|
|
if (
|
|
lookup_now.x != lookup_ago.x ||
|
|
lookup_now.y != lookup_ago.y ||
|
|
lookup_now.z != lookup_ago.z)
|
|
{
|
|
lookup_ago = lookup_now;
|
|
|
|
// hash coordinates
|
|
lookup_now.x &= 0x3FF;
|
|
lookup_now.y &= 0x3FF;
|
|
lookup_now.z &= 0x3FF;
|
|
|
|
lookup_now.y <<= 10;
|
|
lookup_now.z <<= 20;
|
|
|
|
uint32_t lookup_hash = lookup_now.x | lookup_now.y | lookup_now.z;
|
|
lookup_hash++;
|
|
|
|
int lookup_first = 0,
|
|
lookup_last = CHUNKARR_SIZE - 1,
|
|
lookup_middle = (CHUNKARR_SIZE - 1) / 2;
|
|
|
|
// Perform binary search
|
|
while (lookup_first <= lookup_last)
|
|
{
|
|
if (world.chunk[lookup_middle].coordHash > lookup_hash)
|
|
{
|
|
lookup_first = lookup_middle + 1;
|
|
}
|
|
else if (world.chunk[lookup_middle].coordHash == lookup_hash)
|
|
{
|
|
chunk = &world.chunk[lookup_middle];
|
|
goto foundChunk;
|
|
}
|
|
else
|
|
{
|
|
lookup_last = lookup_middle - 1;
|
|
}
|
|
|
|
lookup_middle = (lookup_first + lookup_last) / 2;
|
|
}
|
|
chunk = NULL;
|
|
}
|
|
|
|
Block intersectedBlock;
|
|
foundChunk:
|
|
if (chunk)
|
|
{
|
|
intersectedBlock = chunk->blocks[nmod(blockRayPosition.x, 64) +
|
|
(nmod(blockRayPosition.y, 64) << 6) +
|
|
(nmod(blockRayPosition.z, 64) << 12)];
|
|
}
|
|
else
|
|
{
|
|
intersectedBlock = 0;
|
|
goto chunkNull;
|
|
}
|
|
|
|
if (
|
|
intersectedBlock != BLOCK_AIR &&
|
|
!(headInWater && intersectedBlock == BLOCK_WATER))
|
|
{
|
|
// Determine what texel the ray hit
|
|
int textureX = (int)floor((f34 + f36) * 16.0) & 0xF;
|
|
int textureY = ((int)floor(f35 * 16.0) & 0xF) + 16;
|
|
if (blockFace == 1)
|
|
{
|
|
textureX = (int)floor(f34 * 16.0) & 0xF;
|
|
textureY = (int)floor(f36 * 16.0) & 0xF;
|
|
if (f30 < 0.0)
|
|
textureY += 32;
|
|
}
|
|
|
|
// Block outline color
|
|
int pixelColor = 0xFFFFFF;
|
|
if (
|
|
(
|
|
!blockSelected ||
|
|
blockRayPosition.x != blockSelect.x ||
|
|
blockRayPosition.y != blockSelect.y ||
|
|
blockRayPosition.z != blockSelect.z) ||
|
|
(textureX > 0 && textureY % 16 > 0 && textureX < 15 && textureY % 16 < 15) || !guiOn || gamePopup)
|
|
{
|
|
if (intersectedBlock >= NUMBER_OF_BLOCKS)
|
|
{
|
|
pixelColor = 0xFF0000;
|
|
}
|
|
else
|
|
{
|
|
pixelColor = textures[textureX + (textureY * 16) + intersectedBlock * 256 * 3];
|
|
}
|
|
}
|
|
|
|
/* See if the block is selected. There must be a
|
|
better way to do this check... */
|
|
if (
|
|
f33 < f26 &&
|
|
(
|
|
(!options.trapMouse && pixelX == inputs->mouse.x / BUFFER_SCALE && pixelY == inputs->mouse.y / BUFFER_SCALE) ||
|
|
(options.trapMouse && pixelX == BUFFER_HALF_W && pixelY == BUFFER_HALF_H)
|
|
)
|
|
)
|
|
{
|
|
selectedPass = 1;
|
|
coordPass = blockRayPosition;
|
|
|
|
blockSelectOffset = (const IntCoords){0};
|
|
switch (blockFace)
|
|
{
|
|
case 0:
|
|
blockSelectOffset.x = 1 - 2 * (f27 > 0.0);
|
|
break;
|
|
case 1:
|
|
blockSelectOffset.y = 1 - 2 * (f27 > 0.0);
|
|
break;
|
|
case 2:
|
|
blockSelectOffset.z = 1 - 2 * (f27 > 0.0);
|
|
break;
|
|
}
|
|
|
|
f26 = f33;
|
|
}
|
|
|
|
if (pixelColor > 0)
|
|
{
|
|
finalPixelColor = pixelColor;
|
|
pixelMist = 255 - (int)(f33 / (double)effectDrawDistance * 255.0F);
|
|
pixelShade = 255 - (blockFace + 2) % 3 * 50;
|
|
rayDistanceLimit = f33;
|
|
}
|
|
}
|
|
chunkNull:
|
|
f34 += f29;
|
|
f35 += f30;
|
|
f36 += f31;
|
|
f33 += f28;
|
|
} // This concludes our warpspeed rampage
|
|
}
|
|
|
|
// Draw inverted color crosshair
|
|
if (options.trapMouse && ((pixelX == BUFFER_HALF_W && abs(BUFFER_HALF_H - pixelY) < 4) ||
|
|
(pixelY == BUFFER_HALF_H && abs(BUFFER_HALF_W - pixelX) < 4)))
|
|
{
|
|
finalPixelColor = 0x1000000 - finalPixelColor;
|
|
}
|
|
|
|
if (finalPixelColor > 0)
|
|
{
|
|
SDL_SetRenderDrawColor(
|
|
renderer,
|
|
((finalPixelColor >> 16 & 0xFF) * pixelShade) >> 8,
|
|
((finalPixelColor >> 8 & 0xFF) * pixelShade) >> 8,
|
|
((finalPixelColor & 0xFF) * pixelShade) >> 8,
|
|
options.fogType ? sqrt(pixelMist) * 16 : pixelMist);
|
|
|
|
SDL_RenderDrawPoint(renderer, pixelX, pixelY);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make camera blue if in water
|
|
if (headInWater)
|
|
{
|
|
SDL_SetRenderDrawColor(renderer, 16, 32, 255, 128);
|
|
SDL_RenderFillRect(renderer, &backgroundRect);
|
|
}
|
|
|
|
// Pass info about selected block on
|
|
blockSelected = selectedPass;
|
|
blockSelect = coordPass;
|
|
|
|
inputs->mouse.x /= BUFFER_SCALE;
|
|
inputs->mouse.y /= BUFFER_SCALE;
|
|
|
|
// Draw current popup
|
|
gameLoop_drawPopup(renderer, inputs);
|
|
|
|
// If we need to take a screenshot, do so
|
|
if (inputs->keyboard.f2)
|
|
{
|
|
inputs->keyboard.f2 = 0;
|
|
char path[PATH_MAX];
|
|
int err = data_getScreenshotPath(path);
|
|
gameLoop_screenshot(renderer, err ? NULL : path);
|
|
}
|
|
}
|
|
|
|
|
|
void gameLoop_drawPopup(SDL_Renderer *renderer, Inputs *inputs)
|
|
{
|
|
// In-game menus
|
|
if (gamePopup != 0)
|
|
{
|
|
SDL_SetRelativeMouseMode(0);
|
|
}
|
|
|
|
switch (gamePopup)
|
|
{
|
|
case POPUP_HUD:
|
|
// HUD
|
|
if (options.trapMouse)
|
|
SDL_SetRelativeMouseMode(1);
|
|
if (guiOn)
|
|
popup_hud(
|
|
renderer, inputs, &world,
|
|
&debugOn, &fps_now, player);
|
|
break;
|
|
|
|
case POPUP_PAUSE:
|
|
// Pause menu
|
|
tblack(renderer);
|
|
SDL_RenderFillRect(renderer, &backgroundRect);
|
|
popup_pause(renderer, inputs, &gamePopup, &gameState, &world);
|
|
break;
|
|
|
|
case POPUP_OPTIONS:
|
|
// Options
|
|
tblack(renderer);
|
|
SDL_RenderFillRect(renderer, &backgroundRect);
|
|
popup_options(renderer, inputs, &gamePopup);
|
|
break;
|
|
|
|
case POPUP_INVENTORY:
|
|
// Inventory
|
|
popup_inventory(renderer, inputs, player, &gamePopup);
|
|
break;
|
|
|
|
#ifndef small
|
|
case POPUP_ADVANCED_DEBUG:
|
|
// Advanced debug menu
|
|
tblack(renderer);
|
|
SDL_RenderFillRect(renderer, &backgroundRect);
|
|
popup_debugTools(renderer, inputs, &gamePopup);
|
|
break;
|
|
|
|
case POPUP_CHUNK_PEEK:
|
|
// Chunk peek
|
|
tblack(renderer);
|
|
SDL_RenderFillRect(renderer, &backgroundRect);
|
|
popup_chunkPeek(renderer, inputs, &world, &gamePopup, player);
|
|
break;
|
|
|
|
case POPUP_ROLL_CALL:
|
|
// Chunk info viewer
|
|
tblack(renderer);
|
|
SDL_RenderFillRect(renderer, &backgroundRect);
|
|
popup_rollCall(renderer, inputs, &world, &gamePopup);
|
|
break;
|
|
|
|
case POPUP_OVERVIEW:
|
|
// View all chunks
|
|
tblack(renderer);
|
|
SDL_RenderFillRect(renderer, &backgroundRect);
|
|
popup_overview(renderer, inputs, &world, &gamePopup);
|
|
break;
|
|
#endif
|
|
|
|
case POPUP_CHAT:
|
|
// Chat
|
|
popup_chat(renderer, inputs, world.time);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void gameLoop_processMovement(Inputs *inputs, int inWater)
|
|
{
|
|
// Run at half speed if in water
|
|
static int flipFlop = 0;
|
|
flipFlop = !flipFlop;
|
|
int doPhysics = !inWater || flipFlop;
|
|
|
|
// Only process movement controls if there are no active popup
|
|
if (gamePopup == 0)
|
|
{
|
|
// Looking around
|
|
if (options.trapMouse)
|
|
{
|
|
player->hRot += (double)inputs->mouse.x / 64;
|
|
player->vRot -= (double)inputs->mouse.y / 64;
|
|
}
|
|
else
|
|
{
|
|
double cameraMoveX =
|
|
(inputs->mouse.x - BUFFER_W * 2) /
|
|
(double)BUFFER_W * 2.0;
|
|
double cameraMoveY =
|
|
(inputs->mouse.y - BUFFER_H * 2) /
|
|
(double)BUFFER_H * 2.0;
|
|
|
|
double cameraMoveDistance = sqrt(
|
|
cameraMoveX * cameraMoveX +
|
|
cameraMoveY * cameraMoveY) -
|
|
1.2;
|
|
|
|
if (cameraMoveDistance < 0.0)
|
|
{
|
|
cameraMoveDistance = 0.0;
|
|
}
|
|
if (cameraMoveDistance > 0.0)
|
|
{
|
|
player->hRot += cameraMoveX *
|
|
cameraMoveDistance / 400.0;
|
|
player->vRot -= cameraMoveY *
|
|
cameraMoveDistance / 400.0;
|
|
}
|
|
}
|
|
|
|
// Restrict camera vertical position
|
|
if (player->vRot < -1.57)
|
|
player->vRot = -1.57;
|
|
if (player->vRot > 1.57)
|
|
player->vRot = 1.57;
|
|
|
|
double speed = 0.02;
|
|
|
|
if (doPhysics)
|
|
{
|
|
player->FBVelocity =
|
|
(inputs->keyboard.w - inputs->keyboard.s) *
|
|
speed;
|
|
player->LRVelocity =
|
|
(inputs->keyboard.d - inputs->keyboard.a) *
|
|
speed;
|
|
}
|
|
}
|
|
|
|
static Coords playerMovement = {0.0, 0.0, 0.0};
|
|
|
|
// Moving around
|
|
if (doPhysics)
|
|
{
|
|
playerMovement.x *= 0.5;
|
|
playerMovement.y *= 0.99;
|
|
playerMovement.z *= 0.5;
|
|
|
|
playerMovement.x +=
|
|
player->vectorH.x * player->FBVelocity +
|
|
player->vectorH.y * player->LRVelocity;
|
|
playerMovement.z +=
|
|
player->vectorH.y * player->FBVelocity -
|
|
player->vectorH.x * player->LRVelocity;
|
|
playerMovement.y += 0.003;
|
|
}
|
|
|
|
// Detect collisions and jump
|
|
for (int axis = 0; axis < 3; axis++)
|
|
{
|
|
if (!doPhysics)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Coords playerPosTry = {
|
|
player->pos.x + playerMovement.x * (double)((axis + 2) % 3 / 2),
|
|
player->pos.y + playerMovement.y * (double)((axis + 1) % 3 / 2),
|
|
player->pos.z + playerMovement.z * (double)((axis + 3) % 3 / 2),
|
|
};
|
|
|
|
for (int step = 0; step < 12; step++)
|
|
{
|
|
int blockX = floor(playerPosTry.x +
|
|
((step >> 0) & 1) * 0.6 - 0.3);
|
|
int blockY = floor(playerPosTry.y +
|
|
((step >> 2) - 1) * 0.8 + 0.65);
|
|
int blockZ = floor(playerPosTry.z +
|
|
((step >> 1) & 1) * 0.6 - 0.3);
|
|
|
|
// Very ad-hoc. TODO: look into a deeper fix than this.
|
|
// blockX -= (blockX < 0);
|
|
// blockY -= (blockY < 0);
|
|
// blockZ -= (blockZ < 0);
|
|
// ---
|
|
|
|
Block block = World_getBlock(&world,
|
|
blockX,
|
|
blockY,
|
|
blockZ);
|
|
|
|
int shouldCollide = 1;
|
|
// Blocks that have collision disabled
|
|
shouldCollide &= block != BLOCK_AIR;
|
|
shouldCollide &= block != BLOCK_WATER;
|
|
shouldCollide &= block != BLOCK_TALL_GRASS;
|
|
// Uncomment this line to be able to enter chunks that
|
|
// don't exist in memory
|
|
// shouldCollide &= block != BLOCK_NIL;
|
|
|
|
if (shouldCollide)
|
|
{
|
|
if (axis != 1)
|
|
{
|
|
goto stopCheck;
|
|
}
|
|
|
|
if (
|
|
inputs->keyboard.space > 0 &&
|
|
(playerMovement.y > 0.0) &&
|
|
!gamePopup)
|
|
{
|
|
inputs->keyboard.space = 0;
|
|
playerMovement.y = -0.1;
|
|
goto stopCheck;
|
|
}
|
|
playerMovement.y = 0.0;
|
|
goto stopCheck;
|
|
}
|
|
}
|
|
|
|
player->pos.x = playerPosTry.x;
|
|
player->pos.y = playerPosTry.y;
|
|
player->pos.z = playerPosTry.z;
|
|
|
|
stopCheck:;
|
|
}
|
|
|
|
// Swim in water
|
|
if (inWater && doPhysics)
|
|
{
|
|
if (
|
|
inputs->keyboard.space > 0 &&
|
|
(playerMovement.y > -0.05) &&
|
|
!gamePopup)
|
|
{
|
|
inputs->keyboard.space = 0;
|
|
playerMovement.y = -0.1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int gameLoop_screenshot(SDL_Renderer *renderer, const char *path)
|
|
{
|
|
SDL_Surface *grab = SDL_CreateRGBSurfaceWithFormat(
|
|
0, BUFFER_W * BUFFER_SCALE, BUFFER_H * BUFFER_SCALE,
|
|
32, SDL_PIXELFORMAT_ARGB8888);
|
|
|
|
SDL_RenderReadPixels(
|
|
renderer, NULL, SDL_PIXELFORMAT_ARGB8888,
|
|
grab->pixels, grab->pitch);
|
|
|
|
if (path == NULL)
|
|
{
|
|
chatAdd("Couldn't save screenshot");
|
|
return 1;
|
|
}
|
|
|
|
int saved = SDL_SaveBMP(grab, path);
|
|
SDL_FreeSurface(grab);
|
|
|
|
if (saved == 0)
|
|
{
|
|
chatAdd("Saved screenshot");
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
chatAdd("Couldn't save screenshot");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
void gameLoop_error(char *message)
|
|
{
|
|
errorMessage = message;
|
|
}
|