diff --git a/game/main.c b/game/main.c index 3ac22ab..9117bde 100644 --- a/game/main.c +++ b/game/main.c @@ -126,23 +126,13 @@ const engineconf_t game_conf = { .memsize = sizeof(gamestate_t), }; -static const unsigned impassable[] = { +static const unsigned impassable_tiles[] = { 284, 485, 486, 525, 527, 566, 567, 731, 768, 770, 771, 804, 805, 806, 808, 845, }; -static inline double mag(dvec_t v) -{ - return sqrt(v.x * v.x + v.y * v.y); -} - -static inline double dot(dvec_t v, dvec_t u) -{ - return v.x * u.x + v.y * u.y; -} - static inline unsigned -tileat(const map_t map, int layeridx, double x, double y) +map_tile(const map_t map, int layeridx, double x, double y) { const unsigned row = (unsigned)(floor(x / TILESIZE) + MAPSHIFTX); const unsigned col = (unsigned)(floor(y / TILESIZE) + MAPSHIFTY); @@ -152,18 +142,6 @@ tileat(const map_t map, int layeridx, double x, double y) return map[layeridx][row][col]; } -static inline bool tilepassable(const map_t map, int x, int y) -{ - for (unsigned l = 0; l < MAPNLAYERS; ++l) { - const unsigned id = tileat(map, l, x, y); - for (unsigned i = 0; i < NELEMS(impassable); ++i) { - if (impassable[i] == id) - return false; - } - } - return true; -} - static inline int propint(xmlNodePtr node, const char *prop) { xmlChar *str = xmlGetProp(node, (const xmlChar *)prop); @@ -173,7 +151,7 @@ static inline int propint(xmlNodePtr node, const char *prop) return val; } -static void maploadlayer(map_t map, xmlNodePtr layernode, int layeridx) +static void load_layer(map_t map, xmlNodePtr layernode, int layeridx) { xmlNodePtr node = layernode->xmlChildrenNode; while (NULL != node @@ -230,7 +208,7 @@ static void maploadlayer(map_t map, xmlNodePtr layernode, int layeridx) } static unsigned -objtypeload(gamestate_t *state, const char *templ, SDL_Renderer *renderer) +load_objtype(gamestate_t *state, const char *templ, SDL_Renderer *renderer) { static char buf[MAX_PATH_LEN]; assert(strlen(state->assetdir) + strlen(templ) + 1 < MAX_PATH_LEN); @@ -294,7 +272,7 @@ objtypeload(gamestate_t *state, const char *templ, SDL_Renderer *renderer) } static void -maploadobjects(gamestate_t *state, xmlNodePtr node, SDL_Renderer *renderer) +load_objects(gamestate_t *state, xmlNodePtr node, SDL_Renderer *renderer) { state->objcol.n = 0; state->objcol.cap = INITOBJCOLCAP; @@ -319,7 +297,7 @@ maploadobjects(gamestate_t *state, xmlNodePtr node, SDL_Renderer *renderer) // Load object type xmlChar *templ = xmlGetProp(node, (const xmlChar *)"template"); assert(templ); - o->type = objtypeload(state, (const char *)templ, renderer); + o->type = load_objtype(state, (const char *)templ, renderer); xmlFree(templ); // Load object location @@ -329,7 +307,7 @@ maploadobjects(gamestate_t *state, xmlNodePtr node, SDL_Renderer *renderer) } static void -mapload(gamestate_t *state, const char *path, SDL_Renderer *renderer) +load_map(gamestate_t *state, const char *path, SDL_Renderer *renderer) { xmlDocPtr doc = xmlParseFile(path); assert(NULL != doc); @@ -340,132 +318,13 @@ mapload(gamestate_t *state, const char *path, SDL_Renderer *renderer) assert(NULL != node); do { if (xmlStrcmp(node->name, (const xmlChar *)"layer") == 0) - maploadlayer(state->map, node, layer++); + load_layer(state->map, node, layer++); else if (xmlStrcmp(node->name, (const xmlChar *)"objectgroup") == 0) - maploadobjects(state, node, renderer); + load_objects(state, node, renderer); } while ((node = node->next) != NULL); xmlFreeDoc(doc); } -static void mapdraw(const gamestate_t *state, SDL_Renderer *renderer) -{ - const int startx = TILESIZE * floor(state->vpos.x / TILESIZE); - const int starty = TILESIZE * floor(state->vpos.y / TILESIZE); - const int endx = ceil(state->vpos.x + VIEWWIDTH); - const int endy = ceil(state->vpos.y + VIEWHEIGHT); - for (int l = 0; l < MAPNLAYERS; ++l) { - for (int y = starty; y < endy; y += TILESIZE) { - for (int x = startx; x < endx; x += TILESIZE) { - const unsigned tileid = tileat(state->map, l, x, y); - if (0 == tileid) - continue; - const SDL_Rect src = { - .x = TILESIZE * ((tileid - 1) % TSCOLS), - .y = TILESIZE * ((tileid - 1) / TSCOLS), - .w = TILESIZE, - .h = TILESIZE, - }; - const SDL_Rect dest = { - .x = (int)rint(SCALE * (x - state->vpos.x)), - .y = (int)rint(SCALE * (y - state->vpos.y)), - .w = SCALE * TILESIZE, - .h = SCALE * TILESIZE, - }; - SDL_RenderCopy(renderer, state->tstex, &src, &dest); - } - } - } -} - -static void entityupdate(gamestate_t *state, dynentity_t *e, double dt) -{ - if (0 == e->speed) { - // Round position to nearest integer to align with pixel grid. - e->pos.x = rint(e->pos.x); - e->pos.y = rint(e->pos.y); - return; - } - - // Update sprite variant - if (e->dir.y >= 0) { - if (e->dir.x > 0) - e->svar = SPRITE_DIR_RIGHT_DOWN; - else if (e->dir.x < 0) - e->svar = SPRITE_DIR_LEFT_DOWN; - else - e->svar = SPRITE_DIR_DOWN; - } else if (e->dir.y < 0) { - if (e->dir.x > 0) - e->svar = SPRITE_DIR_RIGHT_UP; - else if (e->dir.x < 0) - e->svar = SPRITE_DIR_LEFT_UP; - else - e->svar = SPRITE_DIR_UP; - } - - // Apply velocity (handling map collisions) - const double nextx = e->pos.x + dt * e->speed * e->dir.x; - const double nexty = e->pos.y + dt * e->speed * e->dir.y; - const dvec_t pfb - = { .x = nextx + e->fbox.off.x, .y = nexty + e->fbox.off.y }; - bool valid = true; - valid &= tilepassable(state->map, (int)rint(pfb.x), (int)rint(pfb.y)); - valid &= tilepassable( - state->map, (int)rint(pfb.x + e->fbox.ext.x), (int)rint(pfb.y)); - valid &= tilepassable( - state->map, (int)rint(pfb.x), (int)rint(pfb.y + e->fbox.ext.y)); - valid &= tilepassable( - state->map, (int)rint(pfb.x + e->fbox.ext.x), - (int)rint(pfb.y + e->fbox.ext.y)); - if (valid) { - e->pos.x = nextx; - e->pos.y = nexty; - } -} - -static void entitydraw( - const gamestate_t *state, SDL_Renderer *renderer, const dynentity_t *e, - uint64_t t) -{ - const unsigned frame = (t / BASEANIMPERIOD) % e->animlen; - const SDL_Rect src = { - .x = e->animstep.x * frame + e->svarstep.x * e->svar, - .y = e->animstep.y * frame + e->svarstep.y * e->svar, - .w = e->ext.x, - .h = e->ext.y, - }; - const SDL_Rect dest = { - .x = (int)rint(SCALE * (e->pos.x - e->ext.x / 2 - state->vpos.x)), - .y = (int)rint(SCALE * (e->pos.y - e->ext.y / 2 - state->vpos.y)), - .w = SCALE * e->ext.x, - .h = SCALE * e->ext.y, - }; - SDL_RenderCopy(renderer, e->tex, &src, &dest); -} - -static void -objsdraw(const gamestate_t *state, SDL_Renderer *renderer, uint64_t t) -{ - for (unsigned i = 0; i < state->objcol.n; ++i) { - const objentity_t *obj = &state->objcol.buf[i]; - assert(obj->type < MAXOBJTYPES); - const objtype_t *type = &state->objtypes[obj->type]; - assert(NULL != type->tex); - SDL_Rect src = type->src; - src.x += type->src.w * ((t / BASEANIMPERIOD) % type->animframes); - const int x = rint(SCALE * (obj->pos.x - state->vpos.x)); - const int y - = rint(SCALE * (obj->pos.y - state->vpos.y - type->src.h)); - SDL_Rect dest = { - .x = x, - .y = y, - .w = SCALE * type->src.w, - .h = SCALE * type->src.h, - }; - SDL_RenderCopy(renderer, type->tex, &src, &dest); - } -} - void game_init(int argc, char *argv[], void *mem, SDL_Renderer *renderer) { char path[MAX_PATH_LEN]; @@ -478,7 +337,7 @@ void game_init(int argc, char *argv[], void *mem, SDL_Renderer *renderer) assert(strlen(state->assetdir) + strlen(MAP_ASSET) < MAX_PATH_LEN); strcpy(path, state->assetdir); strcat(path, MAP_ASSET); - mapload(state, path, renderer); + load_map(state, path, renderer); // Load tileset assert(strlen(state->assetdir) + strlen(TSASSET) < MAX_PATH_LEN); @@ -586,6 +445,74 @@ gamestatus_t game_evt(void *mem, const SDL_Event *evt) return GAMESTATUS_OK; } +static inline double mag(dvec_t v) +{ + return sqrt(v.x * v.x + v.y * v.y); +} + +static inline double dot(dvec_t v, dvec_t u) +{ + return v.x * u.x + v.y * u.y; +} + +static inline bool tile_passable(const map_t map, int x, int y) +{ + for (unsigned l = 0; l < MAPNLAYERS; ++l) { + const unsigned id = map_tile(map, l, x, y); + for (unsigned i = 0; i < NELEMS(impassable_tiles); ++i) { + if (impassable_tiles[i] == id) + return false; + } + } + return true; +} + +static void update_dynentity(gamestate_t *state, dynentity_t *e, double dt) +{ + if (0 == e->speed) { + // Round position to nearest integer to align with pixel grid. + e->pos.x = rint(e->pos.x); + e->pos.y = rint(e->pos.y); + return; + } + + // Update sprite variant + if (e->dir.y >= 0) { + if (e->dir.x > 0) + e->svar = SPRITE_DIR_RIGHT_DOWN; + else if (e->dir.x < 0) + e->svar = SPRITE_DIR_LEFT_DOWN; + else + e->svar = SPRITE_DIR_DOWN; + } else if (e->dir.y < 0) { + if (e->dir.x > 0) + e->svar = SPRITE_DIR_RIGHT_UP; + else if (e->dir.x < 0) + e->svar = SPRITE_DIR_LEFT_UP; + else + e->svar = SPRITE_DIR_UP; + } + + // Apply velocity (handling map collisions) + const double nextx = e->pos.x + dt * e->speed * e->dir.x; + const double nexty = e->pos.y + dt * e->speed * e->dir.y; + const dvec_t pfb + = { .x = nextx + e->fbox.off.x, .y = nexty + e->fbox.off.y }; + bool valid = true; + valid &= tile_passable(state->map, (int)rint(pfb.x), (int)rint(pfb.y)); + valid &= tile_passable( + state->map, (int)rint(pfb.x + e->fbox.ext.x), (int)rint(pfb.y)); + valid &= tile_passable( + state->map, (int)rint(pfb.x), (int)rint(pfb.y + e->fbox.ext.y)); + valid &= tile_passable( + state->map, (int)rint(pfb.x + e->fbox.ext.x), + (int)rint(pfb.y + e->fbox.ext.y)); + if (valid) { + e->pos.x = nextx; + e->pos.y = nexty; + } +} + gamestatus_t game_update(void *mem, double dt) { gamestate_t *state = (gamestate_t *)mem; @@ -610,7 +537,7 @@ gamestatus_t game_update(void *mem, double dt) state->vpos.x = rint(state->vpos.x); state->vpos.y = rint(state->vpos.y); } - entityupdate(state, &state->p, dt); + update_dynentity(state, &state->p, dt); // Update view const dvec_t pvdisp = { @@ -633,10 +560,81 @@ gamestatus_t game_update(void *mem, double dt) return GAMESTATUS_OK; } +static void render_map(const gamestate_t *state, SDL_Renderer *renderer) +{ + const int startx = TILESIZE * floor(state->vpos.x / TILESIZE); + const int starty = TILESIZE * floor(state->vpos.y / TILESIZE); + const int endx = ceil(state->vpos.x + VIEWWIDTH); + const int endy = ceil(state->vpos.y + VIEWHEIGHT); + for (int l = 0; l < MAPNLAYERS; ++l) { + for (int y = starty; y < endy; y += TILESIZE) { + for (int x = startx; x < endx; x += TILESIZE) { + const unsigned tileid = map_tile(state->map, l, x, y); + if (0 == tileid) + continue; + const SDL_Rect src = { + .x = TILESIZE * ((tileid - 1) % TSCOLS), + .y = TILESIZE * ((tileid - 1) / TSCOLS), + .w = TILESIZE, + .h = TILESIZE, + }; + const SDL_Rect dest = { + .x = (int)rint(SCALE * (x - state->vpos.x)), + .y = (int)rint(SCALE * (y - state->vpos.y)), + .w = SCALE * TILESIZE, + .h = SCALE * TILESIZE, + }; + SDL_RenderCopy(renderer, state->tstex, &src, &dest); + } + } + } +} + +static void render_dynentity( + const gamestate_t *state, SDL_Renderer *renderer, const dynentity_t *e, + uint64_t t) +{ + const unsigned frame = (t / BASEANIMPERIOD) % e->animlen; + const SDL_Rect src = { + .x = e->animstep.x * frame + e->svarstep.x * e->svar, + .y = e->animstep.y * frame + e->svarstep.y * e->svar, + .w = e->ext.x, + .h = e->ext.y, + }; + const SDL_Rect dest = { + .x = (int)rint(SCALE * (e->pos.x - e->ext.x / 2 - state->vpos.x)), + .y = (int)rint(SCALE * (e->pos.y - e->ext.y / 2 - state->vpos.y)), + .w = SCALE * e->ext.x, + .h = SCALE * e->ext.y, + }; + SDL_RenderCopy(renderer, e->tex, &src, &dest); +} + +static void render_objentity( + const gamestate_t *state, SDL_Renderer *renderer, uint64_t t, + const objentity_t *obj) +{ + assert(obj->type < MAXOBJTYPES); + const objtype_t *type = &state->objtypes[obj->type]; + assert(NULL != type->tex); + SDL_Rect src = type->src; + src.x += type->src.w * ((t / BASEANIMPERIOD) % type->animframes); + const int x = rint(SCALE * (obj->pos.x - state->vpos.x)); + const int y = rint(SCALE * (obj->pos.y - state->vpos.y - type->src.h)); + SDL_Rect dest = { + .x = x, + .y = y, + .w = SCALE * type->src.w, + .h = SCALE * type->src.h, + }; + SDL_RenderCopy(renderer, type->tex, &src, &dest); +} + void game_render(const void *mem, SDL_Renderer *renderer, long unsigned t) { const gamestate_t *state = (const gamestate_t *)mem; - mapdraw(state, renderer); - objsdraw(state, renderer, t); - entitydraw(state, renderer, &state->p, t); + render_map(state, renderer); + for (unsigned i = 0; i < state->objcol.n; ++i) + render_objentity(state, renderer, t, &state->objcol.buf[i]); + render_dynentity(state, renderer, &state->p, t); }