k4/src/main.c

963 lines
22 KiB
C
Raw Normal View History

2025-01-19 17:29:52 +02:00
#include"k4.h"
#ifdef _WIN32
#include<winsock2.h>
#endif
#define ENET_IMPLEMENTATION
#define GLAD_GL_IMPLEMENTATION
#include"gl.h"
#pragma GCC push_options
#pragma GCC optimize("Ofast")
#define STB_IMAGE_IMPLEMENTATION
#include"stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include"stb_image_resize.h"
#pragma GCC pop_options
#include<GLFW/glfw3.h>
#include<cglm/cam.h>
#include<cglm/affine.h>
#include"k3.h"
#include"k3water.h"
#include"k3mix.h"
#include"k3font.h"
#include"k3menu.h"
#include"k3bloom.h"
#include"resman.h"
#include"game.h"
#include"luaapi.h"
#include"net_server.h"
#include"net_client.h"
#include<ctype.h>
struct NetWrap NetWrap;
GLFWwindow *GameWnd;
static int TextureResolutionReduction = 0;
static int IrregularShadows = 0;
static float CamPitch, CamYaw;
static vec3 CamPos;
static int ThirdPerson = 1;
struct k3MScreen *UiActive;
static double CurrentTime;
static double LastTime;
#include"loaders.inc"
void set_ui_mode(int yes) {
glfwSetInputMode(GameWnd, GLFW_CURSOR, yes ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
}
static int is_ui_mode() {
return glfwGetInputMode(GameWnd, GLFW_CURSOR) == GLFW_CURSOR_NORMAL;
}
static double xposold, yposold;
static void buttoncallback(GLFWwindow *GameWnd, int button, int action, int mods) {
if(button == GLFW_MOUSE_BUTTON_LEFT) {
if(UiActive) {
int ww, wh;
glfwGetWindowSize(GameWnd, &ww, &wh);
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = action == GLFW_PRESS ? k3M_EVENT_MOUSE_PRESS : k3M_EVENT_MOUSE_RELEASE,
.mouse = {
.button = k3M_MOUSE_BUTTON_0,
.x = xposold * UiActive->w / ww,
.y = (wh - yposold - 1) * UiActive->h / wh,
},
};
k3MEventSend(&ev);
}
}
if(!UiActive) {
luaapi_ctrlev(button, action == GLFW_PRESS ? 0 : (action == GLFW_RELEASE ? 2 : 1));
}
}
static void motioncallback(GLFWwindow *GameWnd, double xpos, double ypos) {
static int first = 1;
if(!first && !is_ui_mode()) {
CamPitch -= (ypos - yposold) / 500.f;
CamYaw -= (xpos - xposold) / 500.f;
if(CamPitch > 1.5393) {
CamPitch = 1.5393;
} else if(CamPitch < -1.5393) {
CamPitch = -1.5393;
}
}
xposold = xpos;
yposold = ypos;
first = 0;
if(UiActive) {
int ww, wh;
glfwGetWindowSize(GameWnd, &ww, &wh);
uint16_t uix = xposold * UiActive->w / ww;
uint16_t uiy = (wh - yposold - 1) * UiActive->h / wh;
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = k3M_EVENT_MOUSE_MOTION,
.mouse = {
.x = uix,
.y = uiy,
},
};
k3MEventSend(&ev);
}
}
static void keycallback(GLFWwindow *GameWnd, int key, int scancode, int action, int mods) {
if(action == GLFW_RELEASE && key == GLFW_KEY_ESCAPE) {
luaapi_escape();
set_ui_mode(!!UiActive);
} else {
if(UiActive) {
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = action == GLFW_PRESS ? k3M_EVENT_KEY_PRESS : k3M_EVENT_KEY_RELEASE,
.key = {
.num = key,
},
};
k3MEventSend(&ev);
}
}
luaapi_ctrlev(key, action == GLFW_PRESS ? 0 : (action == GLFW_RELEASE ? 2 : 1));
}
static void charcallback(GLFWwindow *window, unsigned int codepoint) {
if(UiActive) {
struct k3MEvent ev = {
.original = (void*) UiActive,
.target = (void*) UiActive,
.code = k3M_EVENT_INPUT,
.input = {
.code = codepoint
},
};
k3MEventSend(&ev);
}
}
static void resizecallback(GLFWwindow *window, int width, int height) {
k3Resize(width, height);
}
static int argc;
static char **argv;
const char *k4_get_arg(const char *name) {
for(int i = 1; i < argc; i++) {
if(strstr(argv[i], name) == argv[i] && strlen(argv[i]) > strlen(name) && argv[i][strlen(name)] == '=') {
return argv[i] + strlen(name) + 1;
}
}
return NULL;
}
static void netwrap_step() {
if(NetWrap.stage && CurrentTime >= NetWrap.timeout) {
if(NetWrap.stage == 1) {
if(stoon_req(&NetWrap.stoon)) {
if(stoon_listen(&NetWrap.stoon)) {
uint8_t conndata[STOON_CONN_INFO_SIZE] = {};
stoon_serialize(&NetWrap.stoon, conndata);
char str[STOON_CONN_INFO_SIZE * 2 + 1] = {};
for(int i = 0; i < sizeof(conndata); i++) {
snprintf(str + i * 2, 3, "%02x", conndata[i]);
}
luaapi_peercode_found(str);
//glfwSetClipboardString(NULL, str);
//screenMain.lblpeerdata->invisible = 0;
//screenMain.btnconnect->invisible = 0;
NetWrap.stage = 2;
} else {
k3Log(k3_INFO, "Stoon listen timeout.");
NetWrap.timeout = CurrentTime + 0.5;
}
} else {
k3Log(k3_INFO, "Stoon request failed.");
NetWrap.timeout = CurrentTime + 0.5;
}
} else if(NetWrap.stage == 2) {
stoon_keepalive(&NetWrap.stoon);
NetWrap.timeout = CurrentTime + 1;
} else if(NetWrap.stage == 3) {
int status = stoon_poonch(&NetWrap.stoon, NetWrap.otherpeer);
//screenMain.lblpeerdata->txt = strdup("Trying to connect...\nMay not work if both are\nbehind symmetric NATs.");
if(status == STOON_POONCH_INCOMPATIBLE_NETFAMS) {
NetWrap.stage = 0;
stoon_kill(&NetWrap.stoon);
//screenMain.lblpeerdata->txt = strdup("Connection cannot be established:\nIncompatible netfams.");
} else if(status == STOON_POONCH_NO_KNOCK && NetWrap.stoon.poonchstage == POONCH_STAGE_ACK) {
if(NetWrap.gotten++ > 5) {
NetWrap.stage = 0;
stoon_kill(&NetWrap.stoon);
if(NetWrap.isHost) {
net_server_init();
} else {
char ip[64];
int port;
if(*(uint16_t*) (NetWrap.otherpeer + 22) == 0) {
inet_ntop(AF_INET, NetWrap.otherpeer, ip, sizeof(ip));
port = ntohs(*(uint16_t*) (NetWrap.otherpeer + 4));
} else {
inet_ntop(AF_INET6, NetWrap.otherpeer + 6, ip, sizeof(ip));
port = ntohs(*(uint16_t*) (NetWrap.otherpeer + 22));
}
printf("Trying [%s]:%u\n", ip, port);
net_client_init();
net_client_connect(ip, port);
}
//screenMain.lblpeerdata->txt = strdup("Connection successful.");
}
} else NetWrap.gotten = 0;
NetWrap.timeout = CurrentTime + 1;
}
}
}
#include<signal.h>
void k4k3LogCallback(enum k3LogLevel lvl, const char *str, size_t len) {
static const char *prefixes[] = {
[k3_DEBUG] = "[\x1B[95mDEBUG\x1B[0m]",
[k3_INFO] = "[\x1B[97mINFO\x1B[0m] ",
[k3_WARN] = "[\x1B[93mWARN\x1B[0m] ",
[k3_ERR] = "[\x1B[91mERROR\x1B[0m]",
};
//if(lvl == k3_ERR) raise(SIGINT);
fprintf(stderr, "%s : %s\n", prefixes[lvl], str);
}
struct k4Control {
const char *name;
int btn;
} Controls[K4_MAX_CONTROLS];
static int eng_init() {
glfwInit();
glfwWindowHint(GLFW_SRGB_CAPABLE, GL_TRUE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GL_TRUE);
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
#define START_WIDTH 960
#define START_HEIGHT 540
#define START_TITLE "k4"
// Prefer core
if(!k4_get_arg("core") || strcmp(k4_get_arg("core"), "0")) {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GameWnd = glfwCreateWindow(START_WIDTH, START_HEIGHT, START_TITLE, NULL, NULL);
}
if(!GameWnd) {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
GameWnd = glfwCreateWindow(START_WIDTH, START_HEIGHT, START_TITLE, NULL, NULL);
}
k3MixInit(44100, 2);
k3MixPortAudio();
if(glfwRawMouseMotionSupported()) {
glfwSetInputMode(GameWnd, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
}
glfwSetMouseButtonCallback(GameWnd, buttoncallback);
glfwSetCursorPosCallback(GameWnd, motioncallback);
glfwSetKeyCallback(GameWnd, keycallback);
glfwSetCharCallback(GameWnd, charcallback);
glfwSetFramebufferSizeCallback(GameWnd, resizecallback);
glfwMakeContextCurrent(GameWnd);
gladLoadGL(glfwGetProcAddress);
if(k4_get_arg("shading") && strtol(k4_get_arg("shading"), NULL, 0) == 0) {
GLAD_GL_ARB_shading_language_100 = 0;
GLAD_GL_ARB_vertex_program = 0;
GLAD_GL_ARB_fragment_program = 0;
GLAD_GL_ARB_shader_objects = 0;
}
if(k4_get_arg("srgb") && strtol(k4_get_arg("srgb"), NULL, 0) == 0) {
// Just forget sRGB if the driver doesn't bother with GL_FRAMEBUFFER_SRGB
GLAD_GL_EXT_framebuffer_sRGB = 0;
GLAD_GL_EXT_texture_sRGB = 0;
}
if(k4_get_arg("fbo") && strtol(k4_get_arg("fbo"), NULL, 0) == 0) {
GLAD_GL_ARB_framebuffer_sRGB = 0;
GLAD_GL_EXT_framebuffer_object = 0;
}
glfwSwapInterval(0);
printf("GL version: %s\n", glGetString(GL_VERSION));
printf("GL renderer: %s\n", glGetString(GL_RENDERER));
printf("GL vendor: %s\n", glGetString(GL_VENDOR));
k3SetLogCallback(k4k3LogCallback);
k3Init();
k3BatchInit();
resizecallback(GameWnd, START_WIDTH, START_HEIGHT);
enet_initialize();
init_res_handlers();
luaapi_init();
}
static int gr_lowres(int w, int h);
static int ResolPercentage = 100;
static void fix_resol() {
int w, h;
glfwGetWindowSize(GameWnd, &w, &h);
gr_lowres(roundf(w * ResolPercentage / 100.f / 4) * 4, roundf(h * ResolPercentage / 100.f / 4) * 4);
}
static bool onqualitypress(struct k3MEvent *ev, uint8_t *ud) {
k3GraphicalReduction = (k3GraphicalReduction + 1) % 3;
return true;
}
static bool onresolpress(struct k3MEvent *ev, uint8_t *ud) {
struct k3MTextButton *btn = (void*) ev->target;
ResolPercentage -= 10;
if(ResolPercentage <= 0) {
ResolPercentage = 100;
}
const char *txt = NULL;
switch(ResolPercentage) {
case 10:
txt = "10%";
break;
case 20:
txt = "20%";
break;
case 30:
txt = "30%";
break;
case 40:
txt = "40%";
break;
case 50:
txt = "50%";
break;
case 60:
txt = "60%";
break;
case 70:
txt = "70%";
break;
case 80:
txt = "80%";
break;
case 90:
txt = "90%";
break;
case 100:
txt = "Full Resolution";
break;
}
btn->txt = strdup(txt);
fix_resol();
return true;
}
static bool ontexrespress(struct k3MEvent *ev, uint8_t *ud) {
TextureResolutionReduction = (TextureResolutionReduction + 1) % 3;
refresh_textures();
return true;
}
static bool onresumepress(struct k3MEvent *ev, uint8_t *ud) {
UiActive = NULL;
set_ui_mode(0);
return true;
}
#ifdef LOCALHOST_ONLY
static bool onhostpress(struct k3MEvent *ev, uint8_t *ud) {
net_server_init();
return true;
}
static bool onjoinpress(struct k3MEvent *ev, uint8_t *ud) {
net_client_init();
net_client_connect("127.0.0.1", 26656);
return true;
}
static bool onconnectpress(struct k3MEvent *ev, uint8_t *ud) {
return true;
}
#else
static bool onhostpress(struct k3MEvent *ev, uint8_t *ud) {
screenMain.btnhost->disabled = 1;
screenMain.btnjoin->disabled = 1;
NetWrap.stage = 1;
NetWrap.timeout = glfwGetTime() + 0;
NetWrap.isHost = 1;
NetWrap.stoon = stoon_init("stun.easybell.de", 3478, 26656);
return true;
}
static bool onjoinpress(struct k3MEvent *ev, uint8_t *ud) {
screenMain.btnhost->disabled = 1;
screenMain.btnjoin->disabled = 1;
NetWrap.stage = 1;
NetWrap.timeout = glfwGetTime() + 0;
NetWrap.isHost = 0;
NetWrap.stoon = stoon_init("stun.easybell.de", 3478, 26656);
return true;
}
static bool onconnectpress(struct k3MEvent *ev, uint8_t *ud) {
const char *name = glfwGetClipboardString(NULL);
while(isspace(*name)) name++;
if(strlen(name) < STOON_CONN_INFO_SIZE * 2) {
goto bad;
}
for(int i = 0; i < STOON_CONN_INFO_SIZE; i++) {
int b = 0;
if(name[i * 2 + 0] >= '0' && name[i * 2 + 0] <= '9') {
b |= (name[i * 2 + 0] - '0') << 4;
} else if(name[i * 2 + 0] >= 'a' && name[i * 2 + 0] <= 'f') {
b |= (name[i * 2 + 0] - 'a' + 10) << 4;
} else goto bad;
if(name[i * 2 + 1] >= '0' && name[i * 2 + 1] <= '9') {
b |= (name[i * 2 + 1] - '0') << 0;
} else if(name[i * 2 + 1] >= 'a' && name[i * 2 + 1] <= 'f') {
b |= (name[i * 2 + 1] - 'a' + 10) << 0;
} else goto bad;
NetWrap.otherpeer[i] = b;
}
NetWrap.stage = 3;
screenMain.btnconnect->disabled = 1;
return true;
bad:
screenMain.lblpeerdata->txt = strdup("Incorrect peer conndata.");
return true;
}
#endif
static bool onconsoleenter(struct k3MEvent *ev, uint8_t *ud) {
return true;
}
static void eng_ui_init() {
set_ui_mode(1);
}
static void fps_counter_step(double newDT) {
static size_t lastDTidx = 0;
static double lastDTs[30] = {};
lastDTs[lastDTidx] = newDT;
lastDTidx = (lastDTidx + 1) % (sizeof(lastDTs) / sizeof(*lastDTs));
if(fmodf(CurrentTime, 1) < fmodf(LastTime, 1)) {
double dtSum = 0;
for(int i = 0; i < sizeof(lastDTs) / sizeof(*lastDTs); i++) {
dtSum += lastDTs[i];
}
k3Log(k3_DEBUG, "fps=%05.03f", 1 / (dtSum / (sizeof(lastDTs) / sizeof(*lastDTs))));
}
}
static struct k3Tex *shadowmap;
static struct k3Offscreen *shadowmapOffscreen;
static struct k3ARBFP *shadowmapDebugThing;
static int gr_shadowmap_init() {
shadowmap = k3TexCreate(k3_DEPTH);
k3TexUpdate(shadowmap, k3_DEPTH, 0, k3TexSzMax() / 2, k3TexSzMax() / 2, NULL);
shadowmapOffscreen = k3OffscreenCreate(NULL, shadowmap);
if(!shadowmapOffscreen) {
return 0;
}
shadowmapDebugThing = k3ProgramARBFP(
"!!ARBfp1.0\n"
"TEMP col;\n"
"TEX col, fragment.texcoord, texture[0], 2D;\n"
"MAD col.xyz, -col, 29.9, 30.1;\n"
"RCP col.x, col.x;\n"
"RCP col.y, col.y;\n"
"RCP col.z, col.z;\n"
"MUL col.xyz, col, 0.2;\n"
"MOV result.color, col;\n"
"END\n"
);
return 1;
}
static struct k3Tex *bloomTex;
static struct k3Offscreen *bloomOffscr;
static int gr_bloom_init() {
if(!k3BloomInit()) {
return 0;
}
bloomTex = k3TexCreate(k3_RAWCOLOR);
k3TexUpdate(bloomTex, k3_RAWCOLOR, 0, 1024, 512, NULL);
bloomOffscr = k3OffscreenCreate(bloomTex, NULL);
return !!bloomOffscr;
}
static struct k3Tex *lowres, *lowresDepth;
static struct k3Offscreen *lowresOffscreen;
static int gr_lowres(int newW, int newH) {
if(lowresOffscreen) {
k3OffscreenDestroy(lowresOffscreen);
lowresOffscreen = NULL;
}
if(newW && newH) {
if(!lowres) {
lowres = k3TexCreate(k3_RAWCOLOR);
}
k3TexUpdate(lowres, k3_RAWCOLOR, 0, newW, newH, NULL);
if(!lowresDepth) {
lowresDepth = k3TexCreate(k3_DEPTH);
}
k3TexUpdate(lowresDepth, k3_DEPTH, 0, newW, newH, NULL);
lowresOffscreen = k3OffscreenCreate(lowres, lowresDepth);
}
return !!lowresOffscreen;
}
#define MAX_RAYS 64
static struct LocalRay RaysToCast[MAX_RAYS];
static size_t RaysToCastCount = 0;
struct LocalRay *request_ray(struct LocalRay *lr) {
if(RaysToCastCount == MAX_RAYS) {
return NULL;
}
memcpy(RaysToCast + RaysToCastCount, lr, sizeof(*lr));
return &RaysToCast[RaysToCastCount++];
}
int main(int argc_, char **argv_) {
argc = argc_;
argv = argv_;
eng_init();
eng_ui_init();
gr_shadowmap_init();
gr_bloom_init();
if(k4_get_arg("res%")) {
ResolPercentage = strtod(k4_get_arg("res%"), NULL);
fix_resol();
}
IrregularShadows = 0;
if(k4_get_arg("irsh") && strcmp(k4_get_arg("irsh"), "0")) {
IrregularShadows = 1;
}
// INIT LEVEL
game_init();
luaapi_load(k4_get_arg("script") ? k4_get_arg("script") : "lvl1");
//
LuaapiStartTime = glfwGetTime();
LastTime = glfwGetTime();
double accumulator = 0;
while(!glfwWindowShouldClose(GameWnd)) {
CurrentTime = glfwGetTime();
double dt = CurrentTime - LastTime;
accumulator += dt;
fps_counter_step(dt);
LastTime = CurrentTime;
glfwPollEvents();
netwrap_step();
float alpha = fmodf(accumulator * GAME_TPS, 1.f);
vec3 cameraForwardDir = {0, 0, -1};
glm_vec3_rotate(cameraForwardDir, CamPitch, (vec3) {1, 0, 0});
glm_vec3_rotate(cameraForwardDir, CamYaw, (vec3) {0, 1, 0});
/* Control translation from keys to game logic */
struct CPlayerCtrl *cc = Game.controlled == ENT_ID_INVALID ? NULL : game_ensurecomponent(Game.controlled, playerctrl);
if(cc && !is_ui_mode()) {
cc->yaw = CamYaw;
cc->pitch = CamPitch;
cc->keys = 0;
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
struct k4Control *k = &Controls[i];
if(k->name) {
if((k->btn <= GLFW_MOUSE_BUTTON_LAST ? glfwGetMouseButton(GameWnd, k->btn) : glfwGetKey(GameWnd, k->btn)) == GLFW_PRESS) {
cc->keys |= 1 << i;
}
}
}
// This is necessary so the host player would send its own pointing to clients
game_ensurecomponent(Game.controlled, movement)->pointing = cc->yaw;
}
if(Game.isMultiplayer) {
if(Game.isAuthority) {
net_server_receive();
} else {
net_client_receive();
}
}
game_raycast(RaysToCast, RaysToCastCount);
RaysToCastCount = 0;
while(accumulator >= 1. / GAME_TPS) {
for(size_t i = 0; i < Game.entities.renderCount; i++) {
glm_vec4_copy(Game.entities.render[i].pos, Game.entities.render[i].posLast);
glm_quat_copy(Game.entities.render[i].rot, Game.entities.render[i].rotLast);
}
if(Game.isMultiplayer && !Game.isAuthority) {
net_client_dejitter();
net_client_update();
}
accumulator -= 1. / GAME_TPS;
game_update();
luaapi_update();
if(Game.isMultiplayer && Game.isAuthority) {
net_server_update();
}
}
int width, height;
glfwGetFramebufferSize(GameWnd, &width, &height);
mat4 proj;
glm_perspective(glm_rad(LuaapiFov), (float) width / height, 0.01f, 100.f, proj);
/* Third-person camera movement */
struct LocalRay camray;
if(Game.spectated != ENT_ID_INVALID) {
struct CRender *c = game_getcomponent(Game.spectated, render);
if(c) {
if(!LuaapiFirstPerson) {
glm_vec3_lerp(c->posLast, c->pos, alpha, camray.pos);
camray.pos[1] += 1.5;
glm_vec3_negate_to(cameraForwardDir, camray.dir);
camray.ignore = Game.spectated;
camray.maxlen = 3;
camray.depth = 0;
game_raycast(&camray, 1);
}
}
}
mat4 cam;
if(LuaapiFirstPerson) {
struct CRender *c = game_getcomponent(Game.spectated, render);
if(c) {
vec3 p;
glm_vec3_lerp(c->posLast, c->pos, alpha, p);
mat4 view;
glm_look(p, cameraForwardDir, (vec3) {0, 1, 0}, view);
glm_mat4_inv(view, cam);
}
} else if(camray.depth || LuaapiCamFocus) {
glm_mat4_identity(cam);
vec3 almostThere;
if(LuaapiCamFocus) {
glm_vec3_copy(LuaapiCamFocusPos, almostThere);
glm_vec3_lerp(CamPos, almostThere, 0.1, CamPos);
glm_look(CamPos, LuaapiCamFocusDir, (vec3) {0, 1, 0}, cam);
} else {
vec3 dirneg;
glm_vec3_negate_to(camray.dir, dirneg);
glm_vec3_lerp(camray.pos, camray.out, 0.9, almostThere);
glm_vec3_lerp(CamPos, almostThere, 0.2, CamPos);
glm_look(CamPos, dirneg, (vec3) {0, 1, 0}, cam);
}
glm_mat4_inv(cam, cam);
}
glm_mat4_copy(cam, LuaapiCamMatrix);
k3SetTime(glfwGetTime());
size_t lightCount;
struct k3Light *lights = luaapi_getlights(&lightCount);
k3SetLights(lightCount, lights);
luaapi_render(dt, alpha);
for(size_t i = 0; i < Game.entities.renderCount; i++) {
struct CRender *c = &Game.entities.render[i];
versor interrot;
glm_quat_nlerp(c->rotLast, c->rot, alpha, interrot);
vec4 interpos;
glm_vec4_lerp(c->posLast, c->pos, alpha, interpos);
mat4 transform = {};
glm_quat_mat4(interrot, transform);
glm_vec4_copy(interpos, (float*) transform[3]);
if(c->cache) {
struct CBoned *b = game_getcomponent(c->entity, boned);
k3Batch(c->cache, transform, b ? b->bones : NULL);
}
}
if(!IrregularShadows) {
k3PassShadowmap(proj, cam, shadowmapOffscreen);
}
if(lowresOffscreen) {
k3BeginOffscreen(lowresOffscreen);
}
k3Clear();
if(LuaapiSkybox) {
mat4 rotation;
glm_quat_mat4(LuaapiSkyboxRotation, rotation);
mat4 rotated;
glm_mat4_mul(rotation, cam, rotated);
k3CubemapTraditional(LuaapiSkybox, proj, rotated);
}
k3PassDepthOnly(proj, cam, true, true);// TRY COMMENTING THIS LINE
if(IrregularShadows) {
k3PassIrregular(lowresOffscreen, proj, cam);
glEnable(GL_SCISSOR_TEST);
//glScissor(256, 0, 10000, 10000);
}
k3PassForward(proj, cam);
glDisable(GL_SCISSOR_TEST);
k3BatchClear();
if(lowresOffscreen) {
k3EndOffscreen(lowresOffscreen);
k3BlitToScreenEffect(lowresOffscreen, false, k3_GLSL, k3ToneMapper(), NULL);
if(bloomOffscr) {
k3Bloom(lowresOffscreen, bloomOffscr);
}
}
glClear(GL_DEPTH_BUFFER_BIT);
if(!k3IsCore) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 3600, 0, 2025, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
luaapi_render2d();
if(UiActive) {
struct k3MEvent ev = {
.original = UiActive,
.target = UiActive,
.code = k3M_EVENT_DRAW,
};
k3MEventSend(&ev);
}
k3BatchFlush();
// Shadowmap debugging
if(glfwGetKey(GameWnd, GLFW_KEY_F5) == GLFW_PRESS) {
k3BlitToScreen(shadowmapOffscreen, 0);
}
glfwSwapBuffers(GameWnd);
}
}
int k4_control_set(const char *name, int btn) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(!Controls[i].name) {
Controls[i].name = strdup(name);
Controls[i].btn = btn;
return i;
}
}
return -1;
}
int k4_control_get_id(int btn) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && btn == Controls[i].btn) {
return i;
}
}
return -1;
}
int k4_control_get_by_name(const char *name) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && !strcmp(Controls[i].name, name)) {
return Controls[i].btn;
}
}
return -1;
}
int k4_control_get_id_by_name(const char *name) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && !strcmp(Controls[i].name, name)) {
return i;
}
}
return -1;
}
const char *k4_control_get_name_by_ctrl(int btn) {
for(int i = 0; i < K4_MAX_CONTROLS; i++) {
if(Controls[i].name && Controls[i].btn == btn) {
return Controls[i].name;
}
}
return NULL;
}
void k4_set_reduction(float f) {
ResolPercentage = f * 100;
fix_resol();
}
void k4_set_texture_reduction(int i) {
TextureResolutionReduction = i;
refresh_textures();
}
void k4_set_clipboard_text(const char *str) {
glfwSetClipboardString(GameWnd, str);
}
const char *k4_get_clipboard_text() {
return glfwGetClipboardString(GameWnd);
}