essence-os/apps/fly.cpp

943 lines
26 KiB
C++

// TODO Icon.
// TODO Music.
// TODO Draw() with rotation.
// TODO Saving with new file IO.
#include <essence.h>
EsInstance *instance;
uint32_t backgroundColor;
int musicIndex;
bool keysPressedSpace, keysPressedEscape, keysPressedX;
uint32_t previousControllerButtons[ES_GAME_CONTROLLER_MAX_COUNT];
uint32_t *targetBits;
size_t targetWidth, targetHeight, targetStride;
double updateTimeAccumulator;
float gameScale;
uint32_t gameOffsetX, gameOffsetY, gameWidth, gameHeight;
#define GAME_SIZE (380)
#define BRAND "fly"
struct Texture {
uint32_t width, height;
uint32_t *bits;
};
void Transform(float *destination, float *left, float *right) {
float d[6];
d[0] = left[0] * right[0] + left[1] * right[3];
d[1] = left[0] * right[1] + left[1] * right[4];
d[2] = left[0] * right[2] + left[1] * right[5] + left[2];
d[3] = left[3] * right[0] + left[4] * right[3];
d[4] = left[3] * right[1] + left[4] * right[4];
d[5] = left[3] * right[2] + left[4] * right[5] + left[5];
destination[0] = d[0];
destination[1] = d[1];
destination[2] = d[2];
destination[3] = d[3];
destination[4] = d[4];
destination[5] = d[5];
}
__attribute__((optimize("-O2")))
void Draw(Texture *texture,
float x, float y, float w = -1, float h = -1,
float sx = 0, float sy = 0, float sw = -1, float sh = -1,
float _a = 1, float _r = 1, float _g = 1, float _b = 1,
float rot = 0) {
(void) rot;
if (sw == -1 && sh == -1) sw = texture->width, sh = texture->height;
if (w == -1 && h == -1) w = sw, h = sh;
if (_a <= 0) return;
if (x + w < 0 || y + h < 0 || x > GAME_SIZE || y > GAME_SIZE) return;
x *= gameScale, y *= gameScale, w *= gameScale, h *= gameScale;
float m[6] = {};
float m1[6] = { sw / w, 0, 0, 0, sh / h, 0 };
float m2[6] = { 1, 0, -x * (sw / w), 0, 1, -y * (sh / h) };
Transform(m, m2, m1);
intptr_t yStart = y - 1, yEnd = y + h + 1, xStart = x - 1, xEnd = x + w + 1;
// if (rot) yStart -= h * 0.45f, yEnd += h * 0.45f, xStart -= w * 0.45f, xEnd += w * 0.45f;
if (yStart < 0) yStart = 0;
if (yStart > gameHeight) yStart = gameHeight;
if (yEnd < 0) yEnd = 0;
if (yEnd > gameHeight) yEnd = gameHeight;
if (xStart < 0) xStart = 0;
if (xStart > gameWidth) xStart = gameWidth;
if (xEnd < 0) xEnd = 0;
if (xEnd > gameWidth) xEnd = gameWidth;
m[2] += m[0] * xStart, m[5] += m[3] * xStart;
uint32_t *scanlineStart = targetBits + (gameOffsetY + yStart) * targetStride / 4 + (gameOffsetX + xStart);
uint32_t r = _r * 255, g = _g * 255, b = _b * 255, a = _a * 255;
for (intptr_t y = yStart; y < yEnd; y++, scanlineStart += targetStride / 4) {
uint32_t *output = scanlineStart;
float tx = m[1] * y + m[2];
float ty = m[4] * y + m[5];
for (intptr_t x = xStart; x < xEnd; x++, output++, tx += m[0], ty += m[3]) {
if (tx + sx < 0 || ty + sy < 0) continue;
uintptr_t txi = tx + sx, tyi = ty + sy;
if (txi < sx || tyi < sy || txi >= sx + sw || tyi >= sy + sh) continue;
uint32_t modified = texture->bits[tyi * texture->width + txi];
if (!(modified & 0xFF000000)) continue;
uint32_t original = *output;
uint32_t alpha1 = (((modified & 0xFF000000) >> 24) * a) >> 8;
uint32_t alpha2 = (255 - alpha1) << 8;
uint32_t r2 = alpha2 * ((original & 0x00FF0000) >> 16);
uint32_t g2 = alpha2 * ((original & 0x0000FF00) >> 8);
uint32_t b2 = alpha2 * ((original & 0x000000FF) >> 0);
uint32_t r1 = r * alpha1 * ((modified & 0x00FF0000) >> 16);
uint32_t g1 = g * alpha1 * ((modified & 0x0000FF00) >> 8);
uint32_t b1 = b * alpha1 * ((modified & 0x000000FF) >> 0);
*output = 0xFF000000
| (0x00FF0000 & ((r1 + r2) << 0))
| (0x0000FF00 & ((g1 + g2) >> 8))
| (0x000000FF & ((b1 + b2) >> 16));
}
}
}
void CreateTexture(Texture *texture, const char *cName) {
size_t dataBytes;
const void *data = EsEmbeddedFileGet(cName, -1, &dataBytes);
texture->bits = (uint32_t *) EsImageLoad(data, dataBytes, &texture->width, &texture->height, 4);
EsAssert(texture->bits);
}
void ExitGame() {
EsInstanceDestroy(instance);
}
///////////////////////////////////////////////////////////
#define TAG_ALL (-1)
#define TAG_SOLID (-2)
#define TAG_KILL (-3)
#define TAG_WORLD (-4)
#define TILE_SIZE (16)
struct Entity {
uint8_t tag;
uint8_t layer;
bool solid, kill, hide;
char symbol;
int8_t frameCount, stepsPerFrame;
Texture *texture;
float texOffX, texOffY, w, h;
int uid;
void (*create)(Entity *);
void (*destroy)(Entity *);
void (*stepAlways)(Entity *); // Called even if level editing.
void (*step)(Entity *);
void (*draw)(Entity *);
void (*drawAfter)(Entity *);
float x, y, px, py;
int stepIndex;
bool isUsed, isDestroyed;
union {
struct {
float cx, cy;
float th, dth;
float dths;
int respawn, respawnGrow, dash;
} player;
struct {
int random;
} block;
struct {
float vel;
} moveBlock;
struct {
float th, cx, cy;
} spin;
struct {
float vx, vy, th, dth;
int life;
uint32_t col;
} star;
};
void Destroy() {
if (isDestroyed) return;
isDestroyed = true;
if (destroy) destroy(this);
}
};
Texture textureDashMessage, textureKeyMessage, textureKey, textureControlsMessage, textureWhite;
Entity typePlayer, typeBlock, typeCheck, typeMoveH, typeStar, typeMoveV, typePowerup, typeShowMessage, typeKey, typeLock, typeSpin, typeEnd;
// All the entities that can be loaded from the rooms.
Entity *entityTypes[] = {
&typeBlock, &typeCheck, &typeMoveH, &typeMoveV, &typePowerup, &typeKey, &typeLock, &typeSpin, &typeEnd,
nullptr,
};
int levelTick;
bool justLoaded = true;
#define MAX_ENTITIES (1000)
struct SaveState {
float checkX, checkY;
int roomX, roomY;
bool hasDash, hasKey;
int deathCount;
uint32_t check;
};
struct GameState : SaveState {
Entity entities[MAX_ENTITIES];
int world;
Entity *player;
};
GameState state;
Entity *AddEntity(Entity *templateEntity, float x, float y, int uid = 0) {
for (int i = 0; i < MAX_ENTITIES; i++) {
if (!state.entities[i].isUsed) {
EsMemoryCopy(state.entities + i, templateEntity, sizeof(Entity));
state.entities[i].isUsed = true;
if (!state.entities[i].frameCount) state.entities[i].frameCount = 1;
if (!state.entities[i].stepsPerFrame) state.entities[i].stepsPerFrame = 1;
state.entities[i].x = state.entities[i].px = x;
state.entities[i].y = state.entities[i].py = y;
if (!state.entities[i].w) state.entities[i].w = state.entities[i].texture->width - 1;
if (!state.entities[i].h) state.entities[i].h = state.entities[i].texture->height / state.entities[i].frameCount - 1;
state.entities[i].uid = uid;
if (state.entities[i].create) state.entities[i].create(state.entities + i);
return state.entities + i;
}
}
static Entity fake = {};
return &fake;
}
Entity *FindEntity(float x, float y, float w, float h, int tag, Entity *exclude) {
for (int i = 0; i < MAX_ENTITIES; i++) {
if (state.entities[i].isUsed && !state.entities[i].isDestroyed
&& (state.entities[i].tag == tag
|| tag == TAG_ALL
|| (tag == TAG_SOLID && state.entities[i].solid)
|| (tag == TAG_KILL && state.entities[i].kill)
)
&& state.entities + i != exclude) {
if (x <= state.entities[i].x + state.entities[i].w && state.entities[i].x <= x + w
&& y <= state.entities[i].y + state.entities[i].h && state.entities[i].y <= y + h) {
return state.entities + i;
}
}
}
return nullptr;
}
char roomName[16];
void UpdateRoomName() {
roomName[0] = 'R';
roomName[1] = (state.roomX / 10) + '0';
roomName[2] = (state.roomX % 10) + '0';
roomName[3] = '_';
roomName[4] = (state.roomY / 10) + '0';
roomName[5] = (state.roomY % 10) + '0';
roomName[6] = '.';
roomName[7] = 'D';
roomName[8] = 'A';
roomName[9] = 'T';
roomName[10] = 0;
}
#define ROOM_ID ((state.roomX - 7) + (state.roomY - 9) * 6)
void LoadRoom() {
state.world = 0;
UpdateRoomName();
roomName[6] = '_';
const uint8_t *buffer = (const uint8_t *) EsEmbeddedFileGet(roomName, -1);
for (int i = 0; i < MAX_ENTITIES; i++) {
if (!state.entities[i].isUsed || state.entities[i].isDestroyed) continue;
if (state.entities[i].tag != typePlayer.tag) {
state.entities[i].Destroy();
} else {
state.entities[i].px = state.entities[i].x;
state.entities[i].py = state.entities[i].y;
}
}
int p = 0;
int iir = 0;
while (true) {
uint8_t tag = buffer[p++];
if (!tag) break;
float x = *(float *) (buffer + p); p += 4;
float y = *(float *) (buffer + p); p += 4;
if (tag == (uint8_t) TAG_WORLD) {
state.world = x;
}
for (int i = 0; entityTypes[i]; i++) {
if (entityTypes[i]->tag == tag) {
AddEntity(entityTypes[i], x, y, (state.roomX << 24) | (state.roomY << 16) | iir);
iir++;
}
}
}
musicIndex = state.world;
}
void CalculateCheck() {
state.check = 0;
uint8_t *buffer = (uint8_t *) &state;
uint32_t check = 0x1D471D47;
for (uintptr_t i = 0; i < sizeof(SaveState); i++) {
check ^= ((uint32_t) buffer[i] + 10) * (i + 100);
}
state.check = check;
}
float FadeInOut(float t) {
if (t < 0.3f) return t / 0.3f;
else if (t < 0.7f) return 1;
else return 1 - (t - 0.7f) / 0.3f;
}
void InitialiseGame() {
state.roomX = 10;
state.roomY = 10;
CreateTexture(&textureWhite, "white_png");
CreateTexture(&textureKey, "key_png");
CreateTexture(&textureDashMessage, "dash_msg_png");
CreateTexture(&textureKeyMessage, "key_msg_png");
CreateTexture(&textureControlsMessage, "controls_png");
int tag = 1;
{
static Texture texture;
CreateTexture(&texture, "player_png");
typePlayer.tag = tag++;
typePlayer.texture = &texture;
typePlayer.frameCount = 6;
typePlayer.stepsPerFrame = 5;
typePlayer.layer = 1;
typePlayer.step = [] (Entity *entity) {
if (entity->player.respawn) {
if (entity->stepIndex > entity->player.respawn) {
entity->player.respawn = 0;
entity->hide = false;
entity->player.cx = state.checkX;
entity->player.cy = state.checkY;
entity->player.respawnGrow = entity->stepIndex + 20;
entity->player.dash = 0;
} else {
return;
}
}
if (!entity->player.respawnGrow && !entity->player.dash) {
if (keysPressedSpace) {
entity->player.cx += (entity->x - entity->player.cx) * 2;
entity->player.cy += (entity->y - entity->player.cy) * 2;
entity->player.th += 3.15f;
entity->player.dth = -entity->player.dth;
entity->player.dths = 5;
} else if (keysPressedX && state.hasDash) {
entity->player.dash = 10;
}
}
float rd = 40;
if (entity->player.respawnGrow) {
if (entity->stepIndex > entity->player.respawnGrow) {
entity->player.respawnGrow = 0;
} else {
rd *= 1 - (entity->player.respawnGrow - entity->stepIndex) / 20.0f;
}
}
entity->x = rd * EsCRTcosf(entity->player.th) + entity->player.cx;
entity->y = rd * EsCRTsinf(entity->player.th) + entity->player.cy + 5 * EsCRTsinf(4.71f + entity->stepIndex * 0.1f);
if (entity->player.dash) {
float pt = entity->player.th - entity->player.dth;
float px = rd * EsCRTcosf(pt) + entity->player.cx;
float py = rd * EsCRTsinf(pt) + entity->player.cy + 5 * EsCRTsinf(4.71f + (entity->stepIndex - 1) * 0.1f);
float dx = entity->x - px;
float dy = entity->y - py;
entity->player.cx += dx * 3.2f;
entity->player.cy += dy * 3.2f;
entity->player.dash--;
AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFF;
} else {
entity->player.th += entity->player.dth;
}
if (entity->player.respawnGrow) {
entity->px = entity->x;
entity->py = entity->y;
}
if (entity->player.dths) {
entity->player.th += entity->player.dth;
entity->player.dths--;
entity->stepIndex = 5 * 3;
}
if (FindEntity(entity->x + 5, entity->y + 5, entity->w - 10, entity->h - 10, TAG_KILL, 0)) {
for (int i = 0; i < 20; i++) {
AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFF0000;
}
entity->hide = true;
entity->player.respawn = entity->stepIndex + 20;
state.deathCount++;
}
if (entity->x > GAME_SIZE - 8) {
entity->x -= GAME_SIZE;
entity->player.cx -= GAME_SIZE;
state.checkX -= GAME_SIZE;
state.roomX++;
LoadRoom();
} else if (entity->x < -8) {
entity->x += GAME_SIZE;
entity->player.cx += GAME_SIZE;
state.checkX += GAME_SIZE;
state.roomX--;
LoadRoom();
}
if (entity->y > GAME_SIZE - 8) {
entity->y -= GAME_SIZE;
entity->player.cy -= GAME_SIZE;
state.checkY -= GAME_SIZE;
state.roomY++;
LoadRoom();
} else if (entity->y < -8) {
entity->y += GAME_SIZE;
entity->player.cy += GAME_SIZE;
state.checkY += GAME_SIZE;
state.roomY--;
LoadRoom();
}
};
typePlayer.create = [] (Entity *entity) {
state.player = entity;
entity->player.cx = entity->x;
entity->player.cy = entity->y;
entity->player.dth = 0.06f;
};
}
{
static Texture texture;
CreateTexture(&texture, "block_png");
static Texture block2;
CreateTexture(&block2, "block2_png");
typeBlock.tag = tag++;
typeBlock.texture = &texture;
typeBlock.kill = true;
typeBlock.hide = true;
typeBlock.layer = 2;
typeBlock.create = [] (Entity *entity) {
uint8_t r = EsRandomU8();
if (r < 20) {
entity->block.random = 1;
} else if (r < 50) {
entity->block.random = 2;
} else {
entity->block.random = 0;
}
};
typeBlock.stepAlways = [] (Entity *entity) {
if (FindEntity(entity->x + 1, entity->y + 1, entity->w - 2, entity->h - 2, entity->tag, entity)) {
entity->Destroy();
}
};
typeBlock.drawAfter = [] (Entity *entity) {
if (!FindEntity(entity->x - 16, entity->y, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x - 16, entity->y, 16, 16, 0, entity->block.random * 16, 16, 16, 1);
}
if (!FindEntity(entity->x + 16, entity->y, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x + 16, entity->y, 16, 16, 32, entity->block.random * 16, 16, 16, 1);
}
if (!FindEntity(entity->x, entity->y - 16, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x, entity->y - 16, 16, 16, 16, entity->block.random * 16, 16, 16, 1);
}
if (!FindEntity(entity->x, entity->y + 16, 1, 1, entity->tag, entity)) {
Draw(&block2, entity->x, entity->y + 16, 16, 16, 48, entity->block.random * 16, 16, 16, 1);
}
};
}
{
static Texture check1, check2;
CreateTexture(&check1, "check1_png");
CreateTexture(&check2, "check2_png");
typeCheck.tag = tag++;
typeCheck.texture = &check1;
typeCheck.step = [] (Entity *entity) {
if (state.checkX == entity->x && state.checkY == entity->y) {
entity->texture = &check2;
} else {
entity->texture = &check1;
}
if (FindEntity(entity->x - 4, entity->y - 4, entity->w + 8, entity->h + 8, typePlayer.tag, 0)) {
if (state.checkX != entity->x || state.checkY != entity->y) {
for (int i = 0; i < 10; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFFFFFF;
}
state.checkX = entity->x;
state.checkY = entity->y;
CalculateCheck();
EsFileWriteAll("|Settings:/Save.dat", -1, &state, sizeof(SaveState));
}
};
}
{
static Texture texture;
CreateTexture(&texture, "moveblock_png");
typeMoveH.tag = tag++;
typeMoveH.texture = &texture;
typeMoveH.kill = true;
typeMoveH.w = 16;
typeMoveH.h = 16;
typeMoveH.texOffX = -4;
typeMoveH.texOffY = -4;
typeMoveH.create = [] (Entity *entity) {
entity->moveBlock.vel = -4;
};
typeMoveH.step = [] (Entity *entity) {
entity->x += entity->moveBlock.vel;
if (FindEntity(entity->x, entity->y, entity->w, entity->h, typeBlock.tag, 0)) {
entity->moveBlock.vel = -entity->moveBlock.vel;
}
};
}
{
// Removed entity.
tag++;
}
{
static Texture texture;
CreateTexture(&texture, "star_png");
typeStar.texture = &texture;
typeStar.tag = tag++;
typeStar.hide = true; // Draw manually.
typeStar.layer = 2;
typeStar.create = [] (Entity *entity) {
float th = EsRandomU8() / 255.0 * 6.24;
float sp = EsRandomU8() / 255.0 * 0.5 + 0.5;
entity->star.vx = sp * EsCRTcosf(th);
entity->star.vy = sp * EsCRTsinf(th);
entity->star.life = EsRandomU8();
entity->star.dth = (EsRandomU8() / 255.0f - 0.5f) * 0.2f;
};
typeStar.step = [] (Entity *entity) {
entity->x += entity->star.vx;
entity->y += entity->star.vy;
entity->star.th += entity->star.dth;
if (entity->star.life < entity->stepIndex) {
entity->Destroy();
}
};
typeStar.drawAfter = [] (Entity *entity) {
Draw(entity->texture, entity->x - 4, entity->y - 4, -1, -1, 0, 0, -1, -1,
1.0 - (float) entity->stepIndex / entity->star.life,
((entity->star.col >> 16) & 0xFF) / 255.0f,
((entity->star.col >> 8) & 0xFF) / 255.0f,
((entity->star.col >> 0) & 0xFF) / 255.0f,
entity->star.th);
};
}
{
static Texture texture;
CreateTexture(&texture, "moveblock_png");
typeMoveV.tag = tag++;
typeMoveV.texture = &texture;
typeMoveV.kill = true;
typeMoveV.w = 16;
typeMoveV.h = 16;
typeMoveV.texOffX = -4;
typeMoveV.texOffY = -4;
typeMoveV.create = [] (Entity *entity) {
entity->moveBlock.vel = -4;
};
typeMoveV.step = [] (Entity *entity) {
entity->y += entity->moveBlock.vel;
if (FindEntity(entity->x, entity->y, entity->w, entity->h, typeBlock.tag, 0)) {
entity->moveBlock.vel = -entity->moveBlock.vel;
}
};
}
{
static Texture texture;
CreateTexture(&texture, "powerup_png");
typePowerup.tag = tag++;
typePowerup.texture = &texture;
typePowerup.step = [] (Entity *entity) {
if (state.hasDash) {
entity->Destroy();
return;
}
if (FindEntity(entity->x, entity->y, entity->w, entity->h, typePlayer.tag, 0)) {
state.hasDash = true;
AddEntity(&typeShowMessage, 0, 0)->texture = &textureDashMessage;
entity->Destroy();
}
};
}
{
typeShowMessage.texture = &textureKeyMessage;
typeShowMessage.tag = tag++;
typeShowMessage.hide = true;
typeShowMessage.draw = [] (Entity *entity) {
Draw(entity->texture, entity->x, entity->y, -1, -1, 0, 0, -1, -1, FadeInOut(entity->stepIndex / 180.0));
if (entity->stepIndex > 180) entity->Destroy();
};
}
{
typeKey.tag = tag++;
typeKey.texture = &textureKey;
typeKey.step = [] (Entity *entity) {
if (state.hasKey) {
entity->Destroy();
} else if (FindEntity(entity->x, entity->y, entity->w, entity->h, typePlayer.tag, 0)) {
state.hasKey = true;
AddEntity(&typeShowMessage, 0, 0)->texture = &textureKeyMessage;
entity->Destroy();
for (int i = 0; i < 10; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0xFFFFFF;
}
};
}
{
static Texture texture;
CreateTexture(&texture, "lock_png");
typeLock.tag = tag++;
typeLock.texture = &texture;
typeLock.kill = true;
typeLock.step = [] (Entity *entity) {
if (state.hasKey) {
for (int i = 0; i < 1; i++) AddEntity(&typeStar, entity->x + 8, entity->y + 8)->star.col = 0x000000;
entity->Destroy();
}
};
}
{
static Texture texture;
CreateTexture(&texture, "moveblock_png");
typeSpin.tag = tag++;
typeSpin.texture = &texture;
typeSpin.kill = true;
typeSpin.w = 16;
typeSpin.h = 16;
typeSpin.texOffX = -4;
typeSpin.texOffY = -4;
typeSpin.create = [] (Entity *entity) {
entity->spin.cx = entity->x;
entity->spin.cy = entity->y;
};
typeSpin.step = [] (Entity *entity) {
entity->x = 60 * EsCRTcosf(entity->spin.th) + entity->spin.cx;
entity->y = 60 * EsCRTsinf(entity->spin.th) + entity->spin.cy;
entity->spin.th += 0.04f;
};
}
{
static Texture msg1, msg2, msg3, num;
CreateTexture(&msg1, "end1_png");
CreateTexture(&msg2, "end2_png");
CreateTexture(&msg3, "end3_png");
CreateTexture(&num, "numbers_png");
typeEnd.tag = tag++;
typeEnd.texture = &textureKey;
typeEnd.hide = true;
typeEnd.create = [] (Entity *) {
state.player->Destroy();
};
typeEnd.draw = [] (Entity *entity) {
float t = entity->stepIndex / 180.0f;
if (t < 1) {
Draw(&msg1, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t));
} else if (t < 2) {
Draw(&msg2, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t - 1));
} else if (t < 3) {
Draw(&msg3, 40, 150, -1, -1, 0, 0, -1, -1, FadeInOut(t - 2));
int p = state.deathCount;
char digits[10];
int dc = 0;
if (p == 0) {
digits[dc++] = 0;
} else {
while (p) {
digits[dc++] = p % 10;
p /= 10;
}
}
int w = dc * 16;
for (int i = dc - 1; i >= 0; i--) {
Draw(&num, 40 + 150 - w / 2 + (dc - 1 - i) * 16,
150 + 33, 16, 30, 16 * digits[i], 0, 16, 30, FadeInOut(t - 2));
}
} else {
ExitGame();
}
};
}
state.checkX = GAME_SIZE / 2;
state.checkY = GAME_SIZE / 2 - 20;
size_t loadedStateBytes;
SaveState *loadedState = (SaveState *) EsFileReadAll("|Settings:/Save.dat", -1, &loadedStateBytes);
bool noSave = true;
if (loadedStateBytes == sizeof(SaveState)) {
EsMemoryCopy(&state, loadedState, loadedStateBytes);
uint32_t oldCheck = state.check;
CalculateCheck();
EsAssert(oldCheck == state.check);
noSave = false;
}
LoadRoom();
AddEntity(&typePlayer, state.checkX, state.checkY);
if (noSave) {
AddEntity(&typeShowMessage, 0, GAME_SIZE - 65)->texture = &textureControlsMessage;
}
}
void UpdateGame() {
if (keysPressedEscape) {
ExitGame();
}
for (int i = 0; i < MAX_ENTITIES; i++) {
if (state.entities[i].isUsed) {
state.entities[i].stepIndex++;
state.entities[i].px += (state.entities[i].x - state.entities[i].px) * 0.5f;
state.entities[i].py += (state.entities[i].y - state.entities[i].py) * 0.5f;
}
}
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].stepAlways) state.entities[i].stepAlways(state.entities + i);
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].step) {
state.entities[i].step(state.entities + i);
}
for (int i = 0; i < MAX_ENTITIES; i++) {
if (state.entities[i].isUsed && state.entities[i].isDestroyed) state.entities[i].isUsed = false;
}
levelTick++;
state.world %= 3;
if (state.world == 0) {
backgroundColor = 0xbef1b1;
} else if (state.world == 1) {
backgroundColor = 0xcee5f1;
} else if (state.world == 2) {
backgroundColor = 0xf3bdf6;
}
}
void RenderGame() {
for (int layer = -1; layer <= 3; layer++) {
for (int i = 0; i < MAX_ENTITIES; i++) {
Entity *entity = state.entities + i;
if (!entity->isUsed) continue;
if (entity->layer != layer) continue;
if (!entity->texture) continue;
if (entity->hide) continue;
int frame = entity->stepsPerFrame >= 0 ? ((entity->stepIndex / entity->stepsPerFrame) % entity->frameCount) : (-entity->stepsPerFrame - 1);
Draw(entity->texture, (int) (entity->px + entity->texOffX + 0.5f),
(int) (entity->py + entity->texOffY + 0.5f),
entity->texture->width, entity->texture->height / entity->frameCount,
0, entity->texture->height / entity->frameCount * frame,
entity->texture->width, entity->texture->height / entity->frameCount);
}
}
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].draw ) state.entities[i].draw (state.entities + i);
for (int i = 0; i < MAX_ENTITIES; i++) if (state.entities[i].isUsed && state.entities[i].drawAfter ) state.entities[i].drawAfter (state.entities + i);
if (state.hasKey) {
Draw(&textureKey, GAME_SIZE - 20, 4);
}
}
///////////////////////////////////////////////////////////
int ProcessCanvasMessage(EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_ANIMATE) {
message->animate.complete = false;
updateTimeAccumulator += message->animate.deltaMs / 1000.0;
while (updateTimeAccumulator > 1 / 60.0) {
{
EsGameControllerState state[ES_GAME_CONTROLLER_MAX_COUNT];
size_t count = EsGameControllerStatePoll(state);
for (uintptr_t i = 0; i < count; i++) {
if (state[i].buttons & (1 << 0) && (~previousControllerButtons[i] & (1 << 0))) {
keysPressedSpace = true;
} else if (state[i].buttons & (1 << 1) && (~previousControllerButtons[i] & (1 << 1))) {
keysPressedX = true;
}
previousControllerButtons[i] = state[i].buttons;
}
}
UpdateGame();
updateTimeAccumulator -= 1 / 60.0;
keysPressedSpace = keysPressedEscape = keysPressedX = false;
}
EsElementRepaint(element);
} else if (message->type == ES_MSG_KEY_DOWN && !message->keyboard.repeat) {
if (message->keyboard.scancode == ES_SCANCODE_SPACE || message->keyboard.scancode == ES_SCANCODE_Z) {
keysPressedSpace = true;
} else if (message->keyboard.scancode == ES_SCANCODE_ESCAPE) {
keysPressedEscape = true;
} else if (message->keyboard.scancode == ES_SCANCODE_X) {
keysPressedX = true;
}
} else if (message->type == ES_MSG_PAINT) {
EsPainter *painter = message->painter;
EsPaintTargetStartDirectAccess(painter->target, &targetBits, nullptr, nullptr, &targetStride);
targetBits = (uint32_t *) ((uint8_t *) targetBits + targetStride * painter->offsetY + 4 * painter->offsetX);
targetWidth = painter->width, targetHeight = painter->height;
gameScale = (float) painter->width / GAME_SIZE;
if (gameScale * GAME_SIZE > painter->height) gameScale = (float) painter->height / GAME_SIZE;
if (gameScale > 1) gameScale = EsCRTfloorf(gameScale);
gameWidth = GAME_SIZE * gameScale, gameHeight = GAME_SIZE * gameScale;
gameOffsetX = painter->width / 2 - gameWidth / 2;
gameOffsetY = painter->height / 2 - gameHeight / 2;
// TODO Clear margins.
Draw(&textureWhite, 0, 0, GAME_SIZE, GAME_SIZE, 0, 0, 1, 1, 1,
((backgroundColor >> 16) & 0xFF) / 255.0f,
((backgroundColor >> 8) & 0xFF) / 255.0f,
((backgroundColor >> 0) & 0xFF) / 255.0f);
RenderGame();
EsPaintTargetEndDirectAccess(painter->target);
}
return 0;
}
void _start() {
_init();
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
instance = EsInstanceCreate(message, BRAND);
EsWindow *window = instance->window;
EsPanel *container = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER);
EsElement *canvas = EsCustomElementCreate(container, ES_CELL_FILL | ES_ELEMENT_FOCUSABLE, {});
canvas->messageUser = ProcessCanvasMessage;
EsElementStartAnimating(canvas);
EsElementFocus(canvas);
InitialiseGame();
}
}
}