#include "haloo3d/haloo3d.h" #include "haloo3d/haloo3dex_console.h" #include "haloo3d/haloo3dex_easy.h" #include "haloo3d/haloo3dex_gen.h" // #include "haloo3d/haloo3dex_img.h" #include "haloo3d/haloo3dex_obj.h" #include "haloo3d/haloo3dex_print.h" #include "unigi/main.h" #include "ecs2.h" #include "keys.h" #include "maze_ecstypes.h" // For higher compatibility with devices with poor filesystem // access, we simply store the resources directly in the program. // They're quite small... #include "resources/mazeendsprite.h" #include "resources/mousesprite.h" #include "resources/specwall.h" #include "resources/tetrahedron.h" #include // INteresting flags for debugging #define FASTFILL #define NUMMICE 1 #define MOUSELOGGING // #define NOWALLS #define WIDTH 480 #define HEIGHT 300 #define ASPECT ((float)WIDTH / HEIGHT) #define SCREENSCALE 2 #define SWIDTH (WIDTH * SCREENSCALE) #define SHEIGHT (HEIGHT * SCREENSCALE) #define NEARCLIP 0.01 #define FARCLIP 100.0 #define LIGHTANG -MPI / 4.0 #define AVGWEIGHT 0.85 // Try 0.5 and 3.5 or something #define DITHERSTART 10000 #define DITHEREND 10000 // Game options #define MAZESIZE 15 #define MAZEENDGAP (MAZESIZE / 5) #define HSCALE 1.5 #define MOUSESCALE 0.25 #define MOUSESPEED 0.45 // Player speed is 0.5. Lower is faster #define PAINTINGODDS 20 // Some arbitrarily large number, up to you #define MAZEHRAND 100 // The pool of numbers to choose from when checking if a mouse // will turn left or right randomly. Higher = less chance to turn #define MOUSETURNRAND 4 // The horizontal choice will be this out of MAZEHRAND. // So, if MAZEHRAND is 100, setting this to 80 will mean // an 8 / 10 chance to go horizontal #define MAZEHBIAS 60 #define PRINTMAZE // Max amount of flip polys to generate in maze. actual amount can be lower, // but there will always at least be 1 #define MAXFLIPPOLYS 5 // Min space between flip polys #define FLIPPOLYBUFFER 3 // Maze grows in the positive direction #define MAZENORTH 1 #define MAZEEAST 2 #define MAZEVISIT 4 #define MAZEFLIP 8 #define MAZETYPEFLIP 1 // When you rightshift these values, you "turn right". // NOTE: north in this case is "towards the screen" because it moves in the // positive direction. In this case, it's actually wound in the opposite // direction of what you'd expect #define DIRNORTH 8 #define DIRWEST 4 #define DIRSOUTH 2 #define DIREAST 1 #define TURNRIGHT(d) (d == 1 ? 8 : (d >> 1)) #define TURNLEFT(d) (d == 8 ? 1 : (d << 1)) #define STACKPUSH(s, t, v) s[t++] = v; #define PAINTINGTEXTURE "resources/specwall.ppm" #define PAINTINGNAME "painting" #define NUMPOLYS 1 const char POLYNAMES[NUMPOLYS][20] = {"tetrahedron"}; // Store all the values users can change at the beginning float fov = 90.0; float minlight = 0.15; float speed = 1.0; int fps = 30; uint16_t sky = 0xF000; struct vec2i dirtovec(uint8_t dir) { struct vec2i result; switch (dir) { case DIREAST: vec2i(result.v, 1, 0); break; case DIRWEST: vec2i(result.v, -1, 0); break; case DIRNORTH: vec2i(result.v, 0, 1); break; case DIRSOUTH: vec2i(result.v, 0, -1); break; default: vec2i(result.v, 0, 0); } return result; } mfloat_t dirtoyaw(uint8_t dir) { switch (dir) { case DIREAST: return MPI_2; case DIRWEST: return -MPI_2; case DIRNORTH: return MPI; case DIRSOUTH: return 0; default: return 0; } } int maze_visited(uint8_t *maze, int x, int y, int size) { return (maze[x + y * size] & MAZEVISIT) > 0; } int maze_connected(uint8_t *maze, int x, int y, int size, uint8_t move) { switch (move) { case DIREAST: return (maze[x + y * size] & MAZEEAST) == 0; case DIRWEST: return (x > 0) && ((maze[x - 1 + y * size] & MAZEEAST) == 0); case DIRNORTH: return (maze[x + y * size] & MAZENORTH) == 0; case DIRSOUTH: return (y > 0) && ((maze[x + (y - 1) * size] & MAZENORTH) == 0); default: return 0; } } void maze_to_pos(struct vec2i *maze, mfloat_t *dest, mfloat_t cellsize) { dest[0] = cellsize * (maze->x + 0.5); dest[2] = cellsize * (maze->y + 0.5); } // Calculate which direction from the given position would have you // facing down the longest hallway uint8_t maze_longesthallway(uint8_t *maze, int size, int x, int y) { uint8_t face = DIRNORTH; uint8_t maxface = face; uint16_t maxdist = 0; while (face) { uint16_t dist = 0; int dx = x, dy = y; struct vec2i move = dirtovec(face); while (maze_connected(maze, dx, dy, size, face)) { dx += move.x; dy += move.y; dist++; } if (dist > maxdist) { maxface = face; maxdist = dist; } face >>= 1; } eprintf("MAXFACE: %d\n", maxface); return maxface; } // Generate a (square) maze. Utilize one bit of the maze (#2) to // indicate whether it is visited void maze_generate(uint8_t *maze, int size, struct vec2i *start, struct vec2i *end) { const int mazesquare = (size) * (size); for (int i = 0; i < mazesquare; i++) { maze[i] = MAZENORTH | MAZEEAST; } start->x = rand() % size; start->y = rand() % size; // Extremely simple sidewinder algorithm. Because the maze grows northeast, we // actually have to start from the end and work backwards for (int y = size - 1; y >= 0; y--) { if (y == size - 1) { for (int x = 0; x < size - 1; x++) { maze[x + y * size] &= ~MAZEEAST; } continue; } int xs = 0; for (int x = 0; x < size; x++) { // If we decide to stop (or it's the end), find a place in our // current span to carve north if ((x == size - 1) || ((rand() % MAZEHRAND) > MAZEHBIAS)) { maze[xs + (rand() % (x - xs + 1)) + y * size] &= ~MAZENORTH; xs = x + 1; } else { maze[x + y * size] &= ~MAZEEAST; } } } do { end->x = rand() % size; end->y = rand() % size; } while (abs(end->x - start->x) < MAZEENDGAP && abs(end->y - start->y) < MAZEENDGAP); #ifdef PRINTMAZE char line[MAZESIZE * 4]; // IDK, just in case // -1 because we draw the top line for (int y = -1; y < size; y++) { for (int x = size - 1; x >= 0; x--) { line[x * 2] = (y == -1 || maze[x + y * size] & MAZENORTH) ? '_' : ' '; line[x * 2 + 1] = (y == -1 || (maze[x + y * size] & MAZEEAST) == 0) ? ' ' : '|'; } line[size * 2] = 0; eprintf("|%s\n", line); } #endif eprintf("Maze generate: %d,%d -> %d,%d\n", start->x, start->y, end->x, end->y); } void create_painting(struct vec2i mazepos, uint8_t dir, haloo3d_easyinstancer *ins, mecs *ecs, ecs_world *world) { // Create the painting render instance, set up the ecs instance, etc. haloo3d_obj_instance *painting = haloo3d_easyinstantiate(ins, PAINTINGNAME, H3D_EASYOBJSTATE_NOTRANS); ecs_eid id = mecs_newentity(ecs, 0); ECS_SETCOMPONENT(ecs, id, ecs_syncgrow){.obj = painting, .scale = &world->scaleto, .basescale = 1, .timer = &world->scaletimer}; ECS_SETCOMPONENT(ecs, id, ecs_dieoninit){.obj = painting, .render = ins->render, .ws = world->state, .diefunc = NULL}; // Fix up some things based on dir. switch (dir) { case DIRNORTH: mazepos.y++; break; case DIREAST: // East also needs the rotation from west mazepos.x++; // fall through case DIRWEST: vec3(painting->lookvec.v, 1, 0, 0); break; } // OK now that it's setup, we need to put it in the right spot and scale it vec3(painting->pos.v, mazepos.x * world->state->cellsize, 0, mazepos.y * world->state->cellsize); vec3(painting->scale.v, HSCALE, world->scaleto, HSCALE); // rotation is around the left edge. That's both where we put it AND // where the painting rotates around. } void kill_flippoly(haloo3d_obj_instance *obj) { free(obj->lighting); } void create_flippoly(struct vec2i mazepos, haloo3d_easyinstancer *ins, mecs *ecs, ecs_world *world) { // Create the render instance, set up the ecs instance, etc. haloo3d_obj_instance *poly = haloo3d_easyinstantiate(ins, POLYNAMES[0], H3D_EASYOBJSTATE_NOTRANS); vec3(poly->scale.v, 0.15, world->scaleto, 0.15); vec3(poly->pos.v, (mazepos.x + 0.5) * world->state->cellsize, 0.35, (mazepos.y + 0.5) * world->state->cellsize); ecs_eid id = mecs_newentity(ecs, 0); mallocordie(poly->lighting, sizeof(struct vec3)); // vec3(poly->lighting->v, 0, 0, -1); //-MPI / 4, -1); vec3(poly->lighting->v, 0, -MPI / 4, -1); ECS_SETCOMPONENT(ecs, id, ecs_placement){ .pos = {.x = (mazepos.x + 0.5) * world->state->cellsize, .y = 0.35, .z = (mazepos.y + 0.5) * world->state->cellsize}, .rot = {.x = 0, .y = MPI * 0.3}}; ECS_SETCOMPONENT(ecs, id, ecs_syncgrow){.obj = poly, .scale = &world->scaleto, .basescale = poly->scale.x, .timer = &world->scaletimer}; ECS_SETCOMPONENT(ecs, id, ecs_dieoninit){.obj = poly, .render = ins->render, .ws = world->state, .diefunc = kill_flippoly}; ECS_SETCOMPONENT(ecs, id, ecs_autorotate){ .dest = {.x = MPI * 60 * 60 * 24, .y = 0}, .timer = fps * 60 * 60 * 24}; ECS_SETCOMPONENT(ecs, id, ecs_object) poly; ECS_SETCOMPONENT(ecs, id, ecs_mazeentity){.type = MAZETYPEFLIP, .mpos = mazepos}; } // Generate walls AND create paintings. Kind of doing too much void maze_wall_generate(uint8_t *maze, int size, haloo3d_obj *obj, haloo3d_easyinstancer *ins, mecs *ecs, ecs_world *world) { // Reset ALL walls obj->numfaces = 0; // Simple: for each cell, we check if north or east is a wall. If so, // generate it. Also, generate walls for the south and west global wall for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { struct vec2i mazepos = {.x = x, .y = y}; if (!maze_connected(maze, x, y, size, DIREAST)) { haloo3d_gen_grid_quad(obj, x, y, dirtovec(DIREAST)); if ((rand() % PAINTINGODDS) == 0) { create_painting(mazepos, DIREAST, ins, ecs, world); } } if (!maze_connected(maze, x, y, size, DIRNORTH)) { haloo3d_gen_grid_quad(obj, x, y, dirtovec(DIRNORTH)); if ((rand() % PAINTINGODDS) == 0) { create_painting(mazepos, DIRNORTH, ins, ecs, world); } } } } for (int i = 0; i < size; i++) { haloo3d_gen_grid_quad(obj, i, 0, dirtovec(DIRSOUTH)); haloo3d_gen_grid_quad(obj, 0, i, dirtovec(DIRWEST)); } } enum { WSTATE_INIT = 0, WSTATE_SPINUP = 1, WSTATE_GAMEPLAY = 2, WSTATE_GAMEOVER = 3, WSTATE_SPINDOWN = 4 }; void sys_billboard(ecs_billboard *bb) { // In our current system, the lookvec is the direction it wants to face, not a // "lookat" point. To lookat something, you simply get the vector pointing // towards it. Since we want them to never be looking up or down, y is luckily // 0. // NOTE: since we look towards the -z dir, we invert z. IDK if that's right // but things spawn looking in the same direction as the camera, so turning it // to face the player would make the texture backwards. bb->obj->lookvec.x = -(bb->lookat->x - bb->obj->pos.x); bb->obj->lookvec.y = 0; bb->obj->lookvec.z = -(bb->lookat->z - bb->obj->pos.z); } void sys_syncgrow(ecs_syncgrow *sg) { // Only perform logic when a timer is running if (*sg->timer) { mfloat_t scale = sg->basescale * *sg->scale; if (*sg->timer == 1) { // Just jump right to it on the last frame. We don't run on frame 0 sg->obj->scale.y = scale; } else { mfloat_t scaleleft = scale - sg->obj->scale.y; sg->obj->scale.y += scaleleft / *sg->timer; } } } void sys_autonav(ecs_autonav *nav, ecs_placement *p) { // Only perform logic when the timer is running if (nav->timer) { // On the last frame, just set it outright, nothing else to do if (nav->timer == 1) { p->pos = nav->dest; } else { mfloat_t xdiff = nav->dest.x - p->pos.x; mfloat_t ydiff = nav->dest.y - p->pos.y; mfloat_t zdiff = nav->dest.z - p->pos.z; p->pos.x += xdiff / nav->timer; p->pos.y += ydiff / nav->timer; p->pos.z += zdiff / nav->timer; } nav->timer--; } } void sys_autorotate(ecs_autorotate *arot, ecs_placement *p) { // Only perform logic when the timer is running if (arot->timer) { // On the last frame, set it outright if (arot->timer == 1) { p->rot = arot->dest; } else { mfloat_t xdiff = arot->dest.x - p->rot.x; mfloat_t ydiff = arot->dest.y - p->rot.y; p->rot.x += xdiff / arot->timer; p->rot.y += ydiff / arot->timer; } arot->timer--; } } // update camera with placement value on entity void sys_camera(ecs_camera *cam, ecs_placement *p) { (*cam)->pos = p->pos; (*cam)->yaw = p->rot.x; (*cam)->pitch = p->rot.y; } void sys_object(ecs_object *obj, ecs_placement *p) { (*obj)->pos = p->pos; YAWP2VEC(p->rot.x, p->rot.y, (*obj)->lookvec.v); // eprintf("OBJ: %f %f %f (%f %f %f)\n", (*obj)->pos.x, (*obj)->pos.y, // (*obj)->pos.z, (*obj)->lookvec.x, (*obj)->lookvec.y, // (*obj)->lookvec.z); } void sys_dieoninit(ecs_dieoninit *die, mecs **ecs) { if (die->ws->state == WSTATE_INIT) { ecs_eid id = mecs_eid(ecs); eprintf("DELETING SELF: %d\n", id); if (die->diefunc) { die->diefunc(die->obj); } // Trivially delete the entity from the renderer haloo3d_easyrender_deleteinstance(die->render, die->obj); // Delete ourselves from existence mecs_deleteentity(*ecs, id); } } void sys_world(ecs_world *w, mecs **ecs) { const int spinspeed = fps * 4.0 / (5 * speed); switch (w->state->state) { case WSTATE_INIT: // We don't need to free anything created from previous runs; they delete // themselves maze_generate(w->state->maze, w->state->size, &w->state->start, &w->state->end); maze_wall_generate(w->state->maze, w->state->size, w->wallmodel, w->instancer, *ecs, w); // SUPER simple flip poly generation for (int i = 0; i < MAXFLIPPOLYS; i++) { struct vec2i m = {.x = rand() % w->state->size, .y = rand() % w->state->size}; if (m.x == w->state->start.x && m.y == w->state->start.y) { continue; } for (int yc = m.y - FLIPPOLYBUFFER; yc <= m.y + FLIPPOLYBUFFER; yc++) { if (yc < 0 || yc >= w->state->size) continue; for (int xc = m.x - FLIPPOLYBUFFER; xc <= m.x + FLIPPOLYBUFFER; xc++) { if (xc < 0 || xc >= w->state->size) continue; if (w->state->maze[m.x + m.y * w->state->size] & MAZEFLIP) { goto SKIPFLIPPOLYADD; } } } eprintf("ADDING FLIPPOLY TO %d,%d\n", m.x, m.y); create_flippoly(m, w->instancer, *ecs, w); w->state->maze[m.x + m.y * w->state->size] |= MAZEFLIP; SKIPFLIPPOLYADD:; } haloo3d_easyrender_calctotals(w->instancer->render); eprintf("INIT MAZE COMPLETE, spinning up walls\n"); maze_to_pos(&w->state->end, w->endobj->pos.v, w->state->cellsize); w->state->state = WSTATE_SPINUP; w->scaletimer = spinspeed; w->scaleto = 1; break; case WSTATE_SPINUP: w->scaletimer--; if (w->scaletimer == 0) { eprintf("SPINUP COMPLETE, starting gameplay\n"); // w->scaletimer = 1; w->state->state = WSTATE_GAMEPLAY; } break; case WSTATE_GAMEOVER: w->scaletimer = spinspeed; w->scaleto = 0; w->state->state = WSTATE_SPINDOWN; eprintf("GAME OVER, spinning down\n"); break; case WSTATE_SPINDOWN: // Bring walls down, timer down w->scaletimer--; if (w->scaletimer == 0) { eprintf("SPINDOWN COMPLETE, reinitializing\n"); // Start gameplay. We won't know when it's done w->state->state = WSTATE_INIT; } break; } } enum { SAI_INIT, SAI_READY, SAI_GAMEPLAY, SAI_FLIP, SAI_HOLD }; int smartai_mazeend(ecs_smartai *smartai) { if (smartai->mpos.x == smartai->ws->end.x && smartai->mpos.y == smartai->ws->end.y) { eprintf("YOU WIN\n"); smartai->ws->state = WSTATE_GAMEOVER; smartai->state = SAI_INIT; return 1; } return 0; } void sys_smartai(ecs_smartai *smartai, ecs_placement *p, ecs_autonav *anav, ecs_autorotate *arot, mecs **ecs, ecs_camera *cam) { const int actiontime = fps / (2 * speed); const int rotdelaytime = actiontime / 2; // 2 * actiontime / 5; switch (smartai->state) { case SAI_INIT: // Here, we wait until the world state is spinup, in which // case we can spawn and face if (smartai->ws->state == WSTATE_SPINUP) { // Reset up vector (even though I like starting upside down, it's a // bit jarring) vec3((*cam)->up.v, 0, 1, 0); smartai->mpos = smartai->ws->start; smartai->dir = maze_longesthallway(smartai->ws->maze, smartai->ws->size, smartai->ws->start.x, smartai->ws->start.y); maze_to_pos(&smartai->mpos, p->pos.v, smartai->ws->cellsize); p->rot.x = dirtoyaw(smartai->dir); // Move startmarker to in front of player. struct vec2i lookdir = dirtovec(smartai->dir); smartai->startmarker->pos.x = p->pos.x + lookdir.x; smartai->startmarker->pos.z = p->pos.z + lookdir.y; // Reset autonav + autorotate anav->dest = p->pos; anav->timer = 0; arot->dest = p->rot; arot->timer = 0; smartai->state = SAI_READY; eprintf("PLAYER READY: %f %f (%f), waiting for spinup\n", anav->dest.x, anav->dest.z, arot->dest.x); } break; case SAI_READY: if (smartai->ws->state == WSTATE_GAMEPLAY) { smartai->state = SAI_GAMEPLAY; eprintf("PLAYER STARTING GAMEPLAY\n"); } break; case SAI_GAMEPLAY: // Normal gameplay: move through the maze, etc. // Some states are triggered based on the timer if (smartai->timer > 0) { smartai->timer--; } // The rotation is delayed to make it feel a bit more like the original // maze, which I think determined rotation and direction upon entering // a tile. I instead calculate that in the middle of the tile. It doesn't // really line up like it does on the windows screensaver but it's // close enough for me. if (smartai->timer == 0 && smartai->rotchange) { eprintf("TURNING BY %f\n", smartai->rotchange); arot->dest.x += smartai->rotchange; smartai->rotchange = 0; arot->timer = actiontime; } // Only decide to do things if you're not moving anymore. Movement is the // most important thing if (anav->timer == 0) { eprintf("SMARTAI: %f TIMER: %d DIR: %d POS: (%f, %f)\n", smartai->rotchange, smartai->timer, smartai->dir, p->pos.x, p->pos.z); if (smartai_mazeend(smartai)) { return; } int mazeind = smartai->mpos.x + smartai->mpos.y * smartai->ws->size; // I'm being SUPER lazy and we don't do proper collision detection with // ecs, we just detect the tile and assign objects to tiles // clang-format off if (smartai->ws->maze[mazeind] & MAZEFLIP) { ecs_eid eids[ECS_MAXENTITIES]; int count = mecs_query(*ecs, ecs_mazeentity_fl | ecs_placement_fl, eids); eprintf("CHECKING MAZEFLIP (%d entities)\n", count); for(int i = 0; i < count; i++) { ecs_mazeentity * mze = &ECS_GETCOMPONENT(*ecs, eids[i], ecs_mazeentity); if(mze->mpos.x == smartai->mpos.x && mze->mpos.y == smartai->mpos.y) { eprintf("FLIPPING WORLD\n"); smartai->state = SAI_FLIP; smartai->timer = 0; // To be SUPER lazy, we just remove the flag and move the object // somewhere else ecs_placement * pl = &ECS_GETCOMPONENT(*ecs, eids[i], ecs_placement); pl->pos.y = -10000; smartai->ws->maze[mazeind] &= ~MAZEFLIP; smartai->upsidedown = !smartai->upsidedown; return; } } } // clang-format on // Player can only move forward if there's nothing in front of them if (maze_connected(smartai->ws->maze, smartai->mpos.x, smartai->mpos.y, smartai->ws->size, smartai->dir)) { struct vec2i movement = dirtovec(smartai->dir); smartai->mpos.x += movement.x; smartai->mpos.y += movement.y; anav->timer = actiontime; anav->dest.x = p->pos.x + smartai->ws->cellsize * movement.x; anav->dest.z = p->pos.z + smartai->ws->cellsize * movement.y; } // Figure out if a rotation should be scheduled if (!(smartai->mpos.x == smartai->ws->end.x && smartai->mpos.y == smartai->ws->end.y)) { // Ok we might be moving, we might not be. Let's go ahead and // calculate rotation based on the FUTURE direction we want to turn. uint8_t rightdir = TURNRIGHT(smartai->dir); uint8_t leftdir = TURNLEFT(smartai->dir); if (maze_connected(smartai->ws->maze, smartai->mpos.x, smartai->mpos.y, smartai->ws->size, rightdir)) { // Always choose right over left smartai->rotchange += MPI_2; smartai->timer = rotdelaytime; smartai->dir = TURNRIGHT(smartai->dir); eprintf("WILL TURN RIGHT TO: %d\n", rightdir); } else { // This while loop lets us turn around if necessary, so reaching a // dead end isn't super painful waiting for two rotations while (!maze_connected(smartai->ws->maze, smartai->mpos.x, smartai->mpos.y, smartai->ws->size, smartai->dir)) { // We seem to have reached a wall. Do we need to turn ALL the way // around? We only move left if the player can't move forward or // right smartai->rotchange -= MPI_2; smartai->timer = rotdelaytime; smartai->dir = TURNLEFT(smartai->dir); eprintf("WILL TURN LEFT (stuck) TO: %d\n", leftdir); } } } } break; case SAI_FLIP: smartai->timer++; mfloat_t towards = smartai->upsidedown ? -1 : 1; if (smartai->timer >= actiontime) { eprintf("FLIP COMPLETE\n"); smartai->timer = actiontime / 8; smartai->state = SAI_HOLD; vec3((*cam)->up.v, 0, towards, 0); } else { mfloat_t angle = MPI * smartai->timer / actiontime; mfloat_t y = towards * -cos(angle); mfloat_t x = towards * -sin(angle); switch (smartai->dir) { case DIRNORTH: vec3((*cam)->up.v, -x, y, 0); break; case DIRSOUTH: vec3((*cam)->up.v, x, y, 0); break; case DIREAST: vec3((*cam)->up.v, 0, y, x); break; case DIRWEST: vec3((*cam)->up.v, 0, y, -x); break; } } break; case SAI_HOLD: smartai->timer--; if (smartai->timer <= 0) { eprintf("HOLD COMPLETE\n"); smartai->state = SAI_GAMEPLAY; } break; } } void sys_mouseai(ecs_mouseai *mouseai, ecs_placement *p, ecs_autonav *anav) { const int actiontime = fps * MOUSESPEED / speed; switch (mouseai->state) { case SAI_INIT: // Just reuse smartai state // Here, we wait until the world state is spinup, in which // case we can spawn and face if (mouseai->ws->state == WSTATE_SPINUP) { mouseai->mpos = (struct vec2i){.x = rand() % mouseai->ws->size, .y = rand() % mouseai->ws->size}; mouseai->dir = 1; maze_to_pos(&mouseai->mpos, p->pos.v, mouseai->ws->cellsize); // Reset autonav + autorotate anav->dest = p->pos; anav->timer = 0; mouseai->state = SAI_READY; #ifdef MOUSELOGGING eprintf("MOUSE READY: %f %f, waiting for spinup\n", anav->dest.x, anav->dest.z); #endif } break; case SAI_READY: if (mouseai->ws->state == WSTATE_GAMEPLAY) { mouseai->state = SAI_GAMEPLAY; #ifdef MOUSELOGGING eprintf("MOUSE STARTING GAMEPLAY\n"); #endif } break; case SAI_GAMEPLAY: // Normal gameplay: move through the maze, etc. if (mouseai->ws->state != WSTATE_GAMEPLAY) { #ifdef MOUSELOGGING eprintf("GAMEPLAY OVER, MOUSE RESETTING\n"); #endif mouseai->state = SAI_INIT; anav->timer = 0; // Stop moving return; } // Only decide to do things if you're not moving anymore. Movement is the // most important thing if (anav->timer == 0) { #ifdef MOUSELOGGING eprintf("MOUSEAI DIR: %d POS: (%f, %f)\n", mouseai->dir, p->pos.x, p->pos.z); #endif // Look left or right randomly. You can affect how "straight" the mouse // moves by increasing the MOUSETURNRAND uint8_t ndir; switch (rand() % MOUSETURNRAND) { case 0: ndir = TURNRIGHT(mouseai->dir); break; case 1: ndir = TURNLEFT(mouseai->dir); break; default: ndir = mouseai->dir; } // If the direction is valid, switch to it. This means that if the mouse // is faced with just one new direction while walking down a hallway, // there's only a CHANCE it will move that way. This ALSO allows the mouse // to handle hitting a wall, as they will eventually choose a valid // direction if (maze_connected(mouseai->ws->maze, mouseai->mpos.x, mouseai->mpos.y, mouseai->ws->size, ndir)) { mouseai->dir = ndir; } // Now, regardless of what happened above, make sure the current direction // we're moving is valid before we step that direction if (maze_connected(mouseai->ws->maze, mouseai->mpos.x, mouseai->mpos.y, mouseai->ws->size, mouseai->dir)) { struct vec2i movement = dirtovec(mouseai->dir); mouseai->mpos.x += movement.x; mouseai->mpos.y += movement.y; anav->timer = actiontime; anav->dest.x = p->pos.x + mouseai->ws->cellsize * movement.x; anav->dest.z = p->pos.z + mouseai->ws->cellsize * movement.y; } else { // Choose a direction at random if we can't move mouseai->dir = (1 << (rand() % 4)); } } break; } } ECS_SYSTEM2(mecs, sys_world, ecs_world, mecs); ECS_SYSTEM2(mecs, sys_dieoninit, ecs_dieoninit, mecs); ECS_SYSTEM1(mecs, sys_syncgrow, ecs_syncgrow); ECS_SYSTEM1(mecs, sys_billboard, ecs_billboard); ECS_SYSTEM2(mecs, sys_autonav, ecs_autonav, ecs_placement); ECS_SYSTEM2(mecs, sys_autorotate, ecs_autorotate, ecs_placement); ECS_SYSTEM2(mecs, sys_camera, ecs_camera, ecs_placement); ECS_SYSTEM2(mecs, sys_object, ecs_object, ecs_placement); ECS_SYSTEM6(mecs, sys_smartai, ecs_smartai, ecs_placement, ecs_autonav, ecs_autorotate, mecs, ecs_camera); ECS_SYSTEM3(mecs, sys_mouseai, ecs_mouseai, ecs_placement, ecs_autonav); void init_floortexture(haloo3d_fb *floort) { uint16_t cols[1] = {0xFD93}; haloo3d_fb_init_tex(floort, 64, 64); haloo3d_apply_alternating(floort, cols, 1); haloo3d_apply_noise(floort, NULL, 1.0 / 6); } void init_ceilingtexture(haloo3d_fb *ceilt) { uint16_t cols[1] = {0xFFFF}; haloo3d_fb_init_tex(ceilt, 64, 64); haloo3d_apply_alternating(ceilt, cols, 1); haloo3d_apply_noise(ceilt, NULL, 1.0 / 4); haloo3d_apply_brick(ceilt, 16, 8, 0xFAAA); } void init_walltexture(haloo3d_fb *wallt) { haloo3d_fb_init_tex(wallt, 64, 64); uint16_t wallcols[] = {0xFA22}; haloo3d_apply_alternating(wallt, wallcols, 1); haloo3d_apply_noise(wallt, NULL, 1.0 / 8); haloo3d_apply_brick(wallt, 18, 13, 0xFEEE); // haloo3d_apply_brick(wallt, 14, 8, 0xFD94); haloo3d_apply_noise(wallt, NULL, 1.0 / 8); } void init_starttexture(haloo3d_fb *startt) { haloo3d_fb_init_tex(startt, 64, 128); // Fill buffer with 0 memset(startt->buffer, 0, startt->width * startt->height * sizeof(uint16_t)); haloo3d_recti rect = {.x1 = 4, .x2 = 60, .y1 = 58, .y2 = 71}; // Create a temporary printing system just to print to this. IDK, maybe I'll // make this betterin the future haloo3d_print_tracker pt; const uint16_t bgcol = 0xF888; const uint16_t fgcol = 0xF222; char buf[64]; uint8_t dither[8]; haloo3d_print_initdefault(&pt, buf, sizeof(buf)); pt.bcolor = 0; // Full transparency pt.fb = startt; // haloo3d_getdither4x4(1.0, dither); // haloo3d_apply_fillrect(startt, &rect, 0xFAAA, dither); int pbx = rect.x2 - 42; int pby = rect.y1 + 2; for (int i = 0; i < 9; i++) { int x = -1 + (i % 3); int y = -1 + (i / 3); pt.fcolor = 0xFAAA; pt.x = pbx + x; pt.y = pby + y; haloo3d_print(&pt, "START"); } pt.fcolor = fgcol; pt.x = pbx; pt.y = pby; haloo3d_print(&pt, "START"); uint16_t binbows[4] = {0xF6C5, 0xFF63, 0xFFC0, 0xF48D}; haloo3d_getdither4x4(1, dither); for (int i = 0; i < 4; i++) { int x = rect.x1 + 2 + 5 * (i % 2); int y = rect.y1 + 2 + 5 * (i / 2); haloo3d_recti wrect = {.x1 = x, .x2 = x + 4, .y1 = y, .y2 = y + 4}; haloo3d_apply_fillrect(startt, wrect, binbows[i], dither); } haloo3d_apply_rect(startt, rect, bgcol, 1); rect.x1--; rect.x2++; rect.y1--; rect.y2++; haloo3d_apply_rect(startt, rect, fgcol, 1); } void init_endtexture(haloo3d_fb *endt) { endt->width = mazeendsprite_width; endt->height = mazeendsprite_height; // We "promise" we won't modify the sprite... endt->buffer = (uint16_t *)mazeendsprite_data; // haloo3d_img_loadppmfile(endt, ENDTEXTURE); // Assume the corner of the image is the "transparent" color // haloo3d_img_totransparent(endt, haloo3d_fb_get(endt, 0, 0)); } void init_paintingtexture(haloo3d_fb *endt) { endt->width = specwall_width; endt->height = specwall_height; // We "promise" we won't modify the sprite... endt->buffer = (uint16_t *)specwall_data; // haloo3d_img_loadppmfile(endt, PAINTINGTEXTURE); } void init_mousetexture(haloo3d_fb *mouset) { mouset->width = mousesprite_width; mouset->height = mousesprite_height; // We "promise" we won't modify mouse sprite... mouset->buffer = (uint16_t *)mousesprite_data; // haloo3d_img_loadppmfile(mouset, MOUSETEXTURE); // Assume the corner of the image is the "transparent" color // haloo3d_img_totransparent(mouset, haloo3d_fb_get(mouset, 0, 0)); } void init_billboard(haloo3d_obj_instance *bb, mfloat_t scale) { // Haven't actually generated the object yet, oops. We don't let billboards // all share the same model, as they might require slightly different // requirements. struct vec3 center = {.x = 0, .y = 0.5, .z = 0}; haloo3d_gen_quad(bb->model, bb->texture, center); bb->cullbackface = 0; // This only works if bb is not centered at 0,0. We do this so that the // billboards "grow up" like the walls do, instead of just expanding out of // nothingness bb->pos.y = 0.5 * (1 - scale); vec3(bb->scale.v, scale, 0, scale); } void init_mazeinstances(haloo3d_obj_instance *floori, haloo3d_obj_instance *ceili, haloo3d_obj_instance *walli) { floori->cullbackface = 0; ceili->cullbackface = 0; walli->cullbackface = 0; vec3(floori->scale.v, HSCALE, 1, HSCALE); vec3(ceili->scale.v, HSCALE, 1, HSCALE); vec3(walli->scale.v, HSCALE, 0, HSCALE); floori->pos.x += MAZESIZE / 2.0 * HSCALE; floori->pos.z += MAZESIZE / 2.0 * HSCALE; ceili->pos.x += MAZESIZE / 2.0 * HSCALE; ceili->pos.z += MAZESIZE / 2.0 * HSCALE; walli->pos.x += MAZESIZE / 2.0 * HSCALE; walli->pos.z += MAZESIZE / 2.0 * HSCALE; ceili->pos.y = 1; } // Given a pointer into an obj, fill it with everything required to make // the painting that clips with the wall (it's a cube) void create_paintingobj(haloo3d_obj *obj) { // 2 for each face; we have 4 faces (top and bottom don't need anything) haloo3d_gen_obj_prealloc(obj, 8, 8, 8); // ppm is 128x128 but 1 pixel along the top is the wall side const mfloat_t ptextop = 1.0 / 128.0; const mfloat_t thickness = 1.0 / 64.0; // box will be aligned along x/y axis, so it will be "facing" the negative z // dir like most other models. Box is topleft, topright, bottomleft, // bottomright, then same on other side. Box is not centered around 0, // intstead it is the same as a wall where iti starts from y=0 and goes to // y=1 vec4(obj->vertices[0].v, 0, 1, thickness, 1); vec4(obj->vertices[1].v, 1, 1, thickness, 1); vec4(obj->vertices[2].v, 0, 0, thickness, 1); vec4(obj->vertices[3].v, 1, 0, thickness, 1); vec4(obj->vertices[4].v, 0, 1, -thickness, 1); vec4(obj->vertices[5].v, 1, 1, -thickness, 1); vec4(obj->vertices[6].v, 0, 0, -thickness, 1); vec4(obj->vertices[7].v, 1, 0, -thickness, 1); // Now, the vtexture points. Some might be dupes, I don't care. First 4 are // the painting texture, next 4 are the wall texture vec3(obj->vtexture[0].v, 0.001, 0.999 - ptextop, 0); vec3(obj->vtexture[1].v, 0.999, 0.999 - ptextop, 0); vec3(obj->vtexture[2].v, 0.001, 0.001, 0); vec3(obj->vtexture[3].v, 0.999, 0.001, 0); vec3(obj->vtexture[4].v, 0.001, 0.999, 0); vec3(obj->vtexture[5].v, 0.999, 0.999, 0); vec3(obj->vtexture[6].v, 0.001, 1.0 - ptextop, 0); vec3(obj->vtexture[7].v, 0.999, 1.0 - ptextop, 0); // Preconstruct the simplified format of the face vertices and vtexture. // First 3 are vertices, next are vtexture // clang-format: off const uint8_t f[8][6] = { {0, 2, 3, 0, 2, 3}, // front face {0, 3, 1, 0, 3, 1}, {5, 7, 6, 0, 2, 3}, // back face {5, 6, 4, 0, 3, 1}, {1, 3, 7, 4, 6, 7}, // front side {1, 7, 5, 4, 7, 5}, {4, 6, 2, 4, 6, 7}, // back side {4, 2, 0, 4, 7, 5}, }; // clang-format: on for (int fi = 0; fi < obj->numfaces; fi++) { for (int v = 0; v < 3; v++) { obj->faces[fi][v].posi = f[fi][v]; obj->faces[fi][v].texi = f[fi][v + 3]; } } } int main() { // int argc, char **argv) { srand(clock()); haloo3d_easystore storage; haloo3d_easystore_init(&storage); haloo3d_fb screen; haloo3d_fb_init(&screen, SWIDTH, SHEIGHT); haloo3d_easyrender render; haloo3d_easyrender_init(&render, WIDTH, HEIGHT); render.camera.pos.y = 0.5; render.tprint.fb = &screen; render.autolightfix = 1; render.rendersettings.ditherclose = DITHERSTART; render.rendersettings.ditherfar = DITHEREND; // render.rendersettings.flags &= ~(H3DR_LIGHTING); render.rendersettings.pctminsize = 100; eprintf("Initialized renderer\n"); haloo3d_easyinstancer instancer = {.storage = &storage, .render = &render}; haloo3d_easytimer frametimer, sdltimer, filltimer, logictimer; haloo3d_easytimer_init(&frametimer, AVGWEIGHT); haloo3d_easytimer_init(&sdltimer, AVGWEIGHT); haloo3d_easytimer_init(&filltimer, AVGWEIGHT); haloo3d_easytimer_init(&logictimer, AVGWEIGHT); // Load the junk + generate stuff. The floor and ceiling use the SAME model haloo3d_obj *planeo = haloo3d_easystore_addobj(&storage, "plane"); haloo3d_obj *wallo = haloo3d_easystore_addobj(&storage, "walls"); haloo3d_obj *starto = haloo3d_easystore_addobj(&storage, "start"); haloo3d_obj *endo = haloo3d_easystore_addobj(&storage, "end"); haloo3d_obj *mouseo = haloo3d_easystore_addobj(&storage, "mouse"); haloo3d_obj *paintingo = haloo3d_easystore_addobj(&storage, PAINTINGNAME); haloo3d_fb *floort = haloo3d_easystore_addtex(&storage, "floor"); haloo3d_fb *ceilt = haloo3d_easystore_addtex(&storage, "ceiling"); haloo3d_fb *wallt = haloo3d_easystore_addtex(&storage, "walls"); haloo3d_fb *startt = haloo3d_easystore_addtex(&storage, "start"); haloo3d_fb *endt = haloo3d_easystore_addtex(&storage, "end"); haloo3d_fb *mouset = haloo3d_easystore_addtex(&storage, "mouse"); haloo3d_fb *paintingt = haloo3d_easystore_addtex(&storage, PAINTINGNAME); for (int i = 0; i < NUMPOLYS; i++) { haloo3d_fb *polyt = haloo3d_easystore_addtex(&storage, POLYNAMES[i]); haloo3d_obj *polyo = haloo3d_easystore_addobj(&storage, "tetrahedron"); switch (i) { default: haloo3d_obj_loadstring(polyo, tetrahedron_string); break; } haloo3d_gen_solidtex(polyt, 0xFDDD); } haloo3d_gen_plane(planeo, MAZESIZE); haloo3d_gen_grid(wallo, MAZESIZE, 0); create_paintingobj(paintingo); init_floortexture(floort); init_ceilingtexture(ceilt); init_walltexture(wallt); init_starttexture(startt); init_endtexture(endt); init_mousetexture(mouset); init_paintingtexture(paintingt); eprintf("Initialized models and textures\n"); worldstate wstate; memset(&wstate, 0, sizeof(worldstate)); // The maze won't change size (for now), so we can set it here to some // constant array uint8_t maze[MAZESIZE * MAZESIZE]; wstate.size = MAZESIZE; wstate.maze = maze; wstate.state = WSTATE_INIT; wstate.cellsize = HSCALE; // Lighting. Note that for performance, the lighting is always calculated // against the base model, and is thus not realistic if the object rotates // in the world. This can be fixed easily, since each object gets its own // lighting vector, which can easily be rotated in the opposite direction of // the model struct vec3 light; vec3(light.v, 0, -MCOS(LIGHTANG), MSIN(LIGHTANG)); // WARN: the order you draw these things can matter greatly! haloo3d_obj_instance *walli = haloo3d_easyrender_addinstance( &render, wallo, wallt, H3D_EASYOBJSTATE_NOTRANS); haloo3d_obj_instance *floori = haloo3d_easyrender_addinstance( &render, planeo, floort, H3D_EASYOBJSTATE_NOTRANS); haloo3d_obj_instance *ceili = haloo3d_easyrender_addinstance( &render, planeo, ceilt, H3D_EASYOBJSTATE_NOTRANS); haloo3d_obj_instance *starti = haloo3d_easyrender_addinstance(&render, starto, startt, 0); haloo3d_obj_instance *endi = haloo3d_easyrender_addinstance(&render, endo, endt, 0); init_mazeinstances(floori, ceili, walli); init_billboard(starti, 1.0); init_billboard(endi, 0.25); eprintf("Setup all static object instances\n"); unigi_type_event event; unigi_type_resolution res; res.width = SWIDTH; res.height = SHEIGHT; res.depth = 0; int totaldrawn = 0; eprintf("Scene has %d tris, %d verts\n", render.totalfaces, render.totalverts); // Init unigi system unigi_graphics_init(); unigi_window_create(res, "maze.exe"); // render.printbuf); // render.camera.pos.y = 4; // 5; // render.camera.pitch = MPI - 0.1; // 2.2; // ceili->pos.y = -10; haloo3d_debugconsole dc; haloo3d_debugconsole_init(&dc); haloo3d_debugconsole_set(&dc, "game/speed.f", &speed); haloo3d_debugconsole_set(&dc, "render/fps.i", &fps); haloo3d_debugconsole_set(&dc, "render/fov.f", &fov); haloo3d_debugconsole_set(&dc, "render/ditherstart.f", &render.rendersettings.ditherclose); haloo3d_debugconsole_set(&dc, "render/ditherend.f", &render.rendersettings.ditherfar); haloo3d_debugconsole_set(&dc, "render/sky.u16x", &sky); haloo3d_debugconsole_set(&dc, "camera/pos_y.f", &render.camera.pos.y); haloo3d_debugconsole_set(&dc, "camera/pitch.f", &render.camera.pitch); haloo3d_debugconsole_set(&dc, "obj/ceil/pos_y.f", &ceili->pos.y); // Set up ECS entities. For this game, we mostly have global entities. mecs ecs; mecs_init(&ecs); eprintf("ECS sys size: %zu\n", sizeof(mecs)); ecs_eid worldid = mecs_newentity(&ecs, 0); eprintf("World eid: %d\n", worldid); ECS_SETCOMPONENT(&ecs, worldid, ecs_world){.state = &wstate, .wallmodel = wallo, .endobj = endi, .scaletimer = 0, .instancer = &instancer}; ecs_world *eworld = ecs.c_ecs_world + worldid; // Setup some dynamic objects ecs_eid wallid = mecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, wallid, ecs_syncgrow){ .obj = walli, .scale = &eworld->scaleto, #ifdef NOWALLS .basescale = 0, // can't set to scale because hscale #else .basescale = 1, #endif .timer = &eworld->scaletimer}; ecs_eid startid = mecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, startid, ecs_syncgrow){.obj = starti, .scale = &eworld->scaleto, .basescale = starti->scale.x, .timer = &eworld->scaletimer}; ECS_SETCOMPONENT(&ecs, startid, ecs_billboard){.obj = starti, .lookat = &render.camera.pos}; ecs_eid endid = mecs_newentity(&ecs, 0); ECS_SETCOMPONENT(&ecs, endid, ecs_syncgrow){.obj = endi, .scale = &eworld->scaleto, .basescale = endi->scale.x, .timer = &eworld->scaletimer}; ECS_SETCOMPONENT(&ecs, endid, ecs_billboard){.obj = endi, .lookat = &render.camera.pos}; // Player is ofc most complicated ecs_eid playerid = mecs_newentity(&ecs, 0); eprintf("Player eid: %d\n", playerid); ECS_SETCOMPONENT(&ecs, playerid, ecs_placement){ .pos = render.camera.pos, .rot = {.x = render.camera.yaw, .y = render.camera.pitch}}; ECS_SETCOMPONENT(&ecs, playerid, ecs_camera) & render.camera; ECS_SETCOMPONENT(&ecs, playerid, ecs_autonav){.timer = 0}; ECS_SETCOMPONENT(&ecs, playerid, ecs_autorotate){.timer = 0}; ECS_SETCOMPONENT(&ecs, playerid, ecs_smartai){.state = SAI_INIT, .ws = &wstate, .rotchange = 0, .timer = 0, .upsidedown = 0, .startmarker = starti}; for (int i = 0; i < NUMMICE; i++) { haloo3d_obj_instance *mousei = haloo3d_easyrender_addinstance(&render, mouseo, mouset, 0); init_billboard(mousei, MOUSESCALE); // Mouse should be near the floor mousei->pos.y = MOUSESCALE; ecs_eid mouseid = mecs_newentity(&ecs, 0); eprintf("Mouse eid: %d\n", mouseid); ECS_SETCOMPONENT(&ecs, mouseid, ecs_placement){.pos = mousei->pos, .rot = {.x = 0, .y = 0}}; ECS_SETCOMPONENT(&ecs, mouseid, ecs_autonav){.timer = 0}; ECS_SETCOMPONENT(&ecs, mouseid, ecs_object) mousei; ECS_SETCOMPONENT(&ecs, mouseid, ecs_mouseai){.state = SAI_INIT, .ws = &wstate}; ECS_SETCOMPONENT(&ecs, mouseid, ecs_billboard){ .obj = mousei, .lookat = &render.camera.pos}; ECS_SETCOMPONENT(&ecs, mouseid, ecs_syncgrow){.obj = mousei, .scale = &eworld->scaleto, .basescale = mousei->scale.x, .timer = &eworld->scaletimer}; } // ----------------------------------- // Actual rendering // ----------------------------------- // ceili->texture = &render.window; while (1) { haloo3d_easytimer_start(&frametimer); // render.camera.yaw += 0.008; haloo3d_perspective(render.perspective, fov, ASPECT, NEARCLIP, FARCLIP); haloo3d_easyrender_beginframe(&render); haloo3d_fb_clear(&render.window, sky); // walli->scale.y = fabs(sin(3 * render.camera.yaw)); // render.camera.up.x = sin(render.camera.yaw); // render.camera.up.y = cos(render.camera.yaw); // walli->up.x = sin(3 * render.camera.yaw); // walli->up.y = cos(4 * render.camera.yaw); do { unigi_event_get(&event); switch (event.type) { case unigi_enum_event_input_keyboard: if (event.data.input_keyboard.down) { switch (event.data.input_keyboard.button) { case KEY_SPACE: haloo3d_debugconsole_beginprompt(&dc); break; default: exit(0); } } break; } } while (event.type != unigi_enum_event_none); // --------------------------- // Game logic? // --------------------------- haloo3d_easytimer_start(&logictimer); for (int i = 0; i < ECS_MAXENTITIES; i++) { sys_world_run(&ecs, i); sys_smartai_run(&ecs, i); sys_mouseai_run(&ecs, i); sys_syncgrow_run(&ecs, i); sys_autonav_run(&ecs, i); sys_autorotate_run(&ecs, i); sys_camera_run(&ecs, i); // NOTE: must run object BEFORE billboard so that billboard // overrides the rotation from object (we want this) sys_object_run(&ecs, i); sys_billboard_run(&ecs, i); sys_dieoninit_run(&ecs, i); } haloo3d_easytimer_end(&logictimer); totaldrawn = 0; haloo3d_obj_instance *object = NULL; // Iterate over objects while ((object = haloo3d_easyrender_nextinstance(&render, object)) != NULL) { // Setup final model matrix and the precalced vertices haloo3d_easyrender_beginmodel(&render, object); // Iterate over object faces for (int fi = 0; fi < object->model->numfaces; fi++) { totaldrawn += haloo3d_easyrender_renderface(&render, object, fi, minlight); } } haloo3d_easytimer_start(&filltimer); #ifdef FASTFILL haloo3d_fb_fill(&screen, &render.window); #else haloo3d_recti texrect = {.x1 = 0, .y1 = 0, .x2 = WIDTH, .y2 = HEIGHT}; haloo3d_recti screenrect = {.x1 = 0, .y1 = 0, .x2 = SWIDTH, .y2 = SHEIGHT}; haloo3d_sprite(&screen, &render.window, texrect, screenrect); #endif haloo3d_easytimer_end(&filltimer); haloo3d_print(&render.tprint, "Pframe: %05.2f (%05.2f)\nPSDLFl: %05.2f " "(%05.2f)\nFill: %05.2f " "(%05.2f)\nLogic: %05.2f (%05.2f)\nTris: %d / %d\nVerts: " "%d\nWState: %d", frametimer.last * 1000, frametimer.sum * 1000, sdltimer.last * 1000, sdltimer.sum * 1000, filltimer.last * 1000, filltimer.sum * 1000, logictimer.last * 1000, logictimer.sum * 1000, totaldrawn, render.totalfaces, render.totalverts, wstate.state); haloo3d_easytimer_start(&sdltimer); unigi_graphics_blit(0, (unigi_type_color *)screen.buffer, res.width * res.height); unigi_graphics_flush(); haloo3d_easytimer_end(&sdltimer); haloo3d_easytimer_end(&frametimer); float waittime = (1.0 / fps) - frametimer.last; if (waittime > 0) { unigi_time_sleep(waittime * unigi_time_clocks_per_s); } } // Just to get the compiler to STOP COMPLAINING about unused mecs_deleteentity(&ecs, worldid); // NOTE:the tex delete PROBABLY should throw some segmentation fault or // something, since I'm trying to free memory that wasn't malloc'd (all // textures are global consts) haloo3d_easystore_deleteallobj(&storage, haloo3d_obj_free); haloo3d_easystore_deletealltex(&storage, haloo3d_fb_free); }