#include "render.h" #include "game.h" #include "anim.h" #include //--- // Camera management //--- void camera_init(camera_t *c, map_t const *m) { c->zoom = 1; c->limits.x_min = fix(-CAMERA_BORDER); c->limits.x_max = fix(m->width + CAMERA_BORDER); c->limits.y_min = fix(-CAMERA_BORDER); c->limits.y_max = fix(m->height + CAMERA_BORDER); /* Fullscreen */ c->viewport.x_min = 0; c->viewport.x_max = DWIDTH; c->viewport.y_min = 0; c->viewport.y_max = DHEIGHT; c->width = fix(c->viewport.x_max - c->viewport.x_min) / TILE_WIDTH; c->height = fix(c->viewport.y_max - c->viewport.y_min) / TILE_HEIGHT; c->x = (c->limits.x_min + c->limits.x_max) / 2; c->y = (c->limits.y_min + c->limits.y_max) / 2; /* Slightly off-center to add space for the HUD */ c->y -= fix(0.5); } ipoint_t camera_map2screen(camera_t const *c, fpoint_t p) { return (ipoint_t){ .x = DWIDTH / 2 + fround((p.x - c->x) * TILE_WIDTH * c->zoom), .y = DHEIGHT / 2 + fround((p.y - c->y) * TILE_HEIGHT * c->zoom), }; } /* Translate screen coordinates to map coordinates */ fpoint_t camera_screen2map(camera_t const *c, ipoint_t p) { return (fpoint_t){ .x = c->x + fix(p.x - DWIDTH / 2) / TILE_WIDTH / c->zoom, .y = c->y + fix(p.y - DHEIGHT / 2) / TILE_HEIGHT / c->zoom, }; } /* Lock the camera at the center if set by settings. */ static bool camera_lock(camera_t *c) { if(c->zoom == 1 && CAMERA_LOCK_AT_x1) { c->x = (c->limits.x_min + c->limits.x_max) / 2; c->y = (c->limits.y_min + c->limits.y_max) / 2; return true; } return false; } /* Bound the camera to the map limits */ static void camera_bound(camera_t *c) { /* Project top left and bottom-right corners of viewport onto the map */ ipoint_t v_tl = { c->viewport.x_min, c->viewport.y_min }; ipoint_t v_br = { c->viewport.x_max, c->viewport.y_max }; fpoint_t m_tl = camera_screen2map(c, v_tl); fpoint_t m_br = camera_screen2map(c, v_br); /* Bound viewport to map limits */ if(m_tl.x < c->limits.x_min) c->x += (c->limits.x_min - m_tl.x); else if(m_br.x > c->limits.x_max) c->x += (c->limits.x_max - m_br.x); if(m_tl.y < c->limits.y_min) c->y += (c->limits.y_min - m_tl.y); else if(m_br.y > c->limits.y_max) c->y += (c->limits.y_max - m_br.y); } void camera_move(camera_t *c, map_coord_t dx, map_coord_t dy) { if(camera_lock(c)) return; c->x += dx; c->y += dy; camera_bound(c); } void camera_zoom(camera_t *c, int zoom) { if(zoom < ZOOM_MIN) zoom = ZOOM_MIN; if(zoom > ZOOM_MAX) zoom = ZOOM_MAX; c->zoom = zoom; if(camera_lock(c)) return; camera_bound(c); } fixed_t camera_ppu(camera_t const *c) { /* Since this assumes isotropic space we can use TILE_WIDTH or TILE_HEIGHT indifferently (they're assumed equal) */ return fix(1) * c->zoom * TILE_WIDTH; } //--- // Rendering //--- void render_map(map_t const *m, camera_t const *c) { extern bopti_image_t img_tileset_base; extern bopti_image_t img_tileset_decor; /* Render floor and walls */ for(int row = -2; row < m->height + 1; row++) for(int col = -1; col < m->width + 1; col++) { struct tile *t = map_tile(m, col, row); fpoint_t tile_pos = { fix(col), fix(row) }; ipoint_t p = camera_map2screen(c, tile_pos); if(!t) { drect(p.x, p.y, p.x+15, p.y+15, C_BLACK); continue; } /* Floor/wall layer */ dsubimage(p.x, p.y, &img_tileset_base, TILE_WIDTH * (t->base % 16), TILE_HEIGHT * (t->base / 16), TILE_WIDTH, TILE_HEIGHT, DIMAGE_NONE); /* Decoration layer */ if(t->decor) dsubimage(p.x, p.y, &img_tileset_decor, TILE_WIDTH * (t->decor % 16), TILE_HEIGHT * (t->decor / 16), TILE_WIDTH, TILE_HEIGHT, DIMAGE_NONE); } } static void render_effect_area(effect_area_t const *ea, camera_t const *c, bool show_hitboxes) { ipoint_t anchor = camera_map2screen(c, ea->anchor); if(ea->anim.frame) { fpoint_t top_left = { ea->anchor.x + ea->sprite.l, ea->anchor.y + ea->sprite.t }; ipoint_t p = camera_map2screen(c, top_left); anim_frame_render(p.x, p.y, ea->anim.frame); } if(!ea->anim.frame || show_hitboxes) { frect_t r = ea->sprite; r = rect_scale(r, camera_ppu(c)); r = rect_translate(r, point_i2f(anchor)); rect_draw(r, C_RGB(31, 31, 0)); } } void render_game(game_t const *g, bool show_hitboxes) { camera_t const *c = &g->camera; render_map(&g->map, &g->camera); /* Render background particles */ for(int i = 0; i < g->particle_count; i++) { particle_t *p = g->particles[i]; if(!particle_is_background(p)) continue; ipoint_t center = camera_map2screen(c, p->pos); particle_render(center.x, center.y, p); } /* Render background effect areas */ for(int i = 0; i < g->effect_area_count; i++) { if(!effect_area_is_background(g->effect_areas[i])) continue; render_effect_area(g->effect_areas[i], c, show_hitboxes); } /* Render entities */ for(int i = 0; i < g->entity_count; i++) { entity_t *e = g->entities[i]; ipoint_t p = camera_map2screen(c, entity_pos(e)); /* Show entity sprite */ if(e->anim.frame) { anim_frame_render(p.x, p.y, e->anim.frame); } /* Show entity hitbox in the map coordinate system */ if(!e->anim.frame || show_hitboxes) { frect_t r = e->hitbox; r = rect_scale(r, camera_ppu(c)); r = rect_translate(r, point_i2f(p)); rect_draw(r, C_BLUE); r = e->sprite; r = rect_scale(r, camera_ppu(c)); r = rect_translate(r, point_i2f(p)); rect_draw(r, C_RGB(12, 12, 12)); /* Show entity center */ dline(p.x-1, p.y, p.x+1, p.y, C_BLUE); dline(p.x, p.y-1, p.x, p.y+1, C_BLUE); } } /* Render background effect areas */ for(int i = 0; i < g->effect_area_count; i++) { if(effect_area_is_background(g->effect_areas[i])) continue; render_effect_area(g->effect_areas[i], c, show_hitboxes); } /* Render foreground particles */ for(int i = 0; i < g->particle_count; i++) { particle_t *p = g->particles[i]; if(particle_is_background(p)) continue; ipoint_t center = camera_map2screen(c, p->pos); particle_render(center.x, center.y, p); } extern font_t font_rogue; font_t const *old_font = dfont(&font_rogue); /* Render wave progress bar */ int enemies_left = 0; for(int i = 0; i < g->entity_count; i++) enemies_left += (g->entities[i]->identity != 0); extern bopti_image_t img_hud_wave, img_hud_wave2; dimage((DWIDTH-img_hud_wave.width) / 2, 17, &img_hud_wave); dsubimage((DWIDTH-img_hud_wave2.width) / 2, 17, &img_hud_wave2, 0, 0, (img_hud_wave2.width*enemies_left) / g->level->waves[g->wave-1].enemy_count, img_hud_wave2.height, DIMAGE_NONE); if(g->wave_spawned < game_current_wave(g)->enemy_count) dprint_opt(DWIDTH / 2, 2, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP, "Wave %d: %d enemies spawning", g->wave, game_current_wave(g)->enemy_count - g->wave_spawned); else if(enemies_left > 0) dprint_opt(DWIDTH / 2, 2, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP, "Wave %d: %d enemies left", g->wave, enemies_left); else dprint_opt(DWIDTH / 2, 2, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP, "Wave %d: Victory!", g->wave); entity_t const *player = g->player; dprint(2, 2, C_WHITE, "HP: %d", player->HP); dprint_opt(DWIDTH - 2, 2, C_WHITE, C_NONE, DTEXT_RIGHT, DTEXT_TOP, "ATK:%d DEF:%d", player->ATK, player->DEF); /* Render skill icons */ extern bopti_image_t img_skillicons; for(int i = 0; i < 6; i++) { /* Activity and cooldown */ fixed_t cooldown_total=0, cooldown_remaining=0; if(i == 0 && player->movement.dash < 0) { cooldown_total = player->movement_params->dash_cooldown; cooldown_remaining = -player->movement.dash; } else if(i > 0) { cooldown_remaining=fix(1); } int x=15+68*i, y=DHEIGHT-28, bg=(cooldown_remaining!=0); dsubimage(x, y, &img_skillicons, 23*bg, 0, 23, 23, DIMAGE_NONE); dsubimage(x, y, &img_skillicons, 23*(i+2), 0, 23, 23, DIMAGE_NONE); /* Darken the area representing remaining cooldown */ if(cooldown_total != 0) { int height = (cooldown_remaining * 23) / cooldown_total; for(int y1 = y+23-height; y1 < y + 23; y1++) { for(int x1 = x; x1 < x+23; x1++) { int i = DWIDTH * y1 + x1; gint_vram[i] = ~((~gint_vram[i] & 0xf7de) >> 1); } } } } dfont(old_font); } void render_pfg_all2one(pfg_all2one_t const *paths, camera_t const *c) { for(int row = 0; row < paths->map->height; row++) for(int col = 0; col < paths->map->width; col++) { struct tile *tile = map_tile(paths->map, col, row); if(!tile || tile->solid) continue; fpoint_t fp = point_i2f_center((ipoint_t){ col, row }); ipoint_t p = camera_map2screen(c, fp); int dir = paths->direction[row * paths->map->width + col]; if(dir == -1) { drect(p.x-1, p.y-1, p.x+1, p.y+1, C_RGB(31, 0, 31)); } else { ipoint_t v = point_f2i(fdir(dir)); int x2 = p.x + 8*v.x; int y2 = p.y + 8*v.y; int dx = 3*v.x, dy = 3*v.y; dline(p.x, p.y, x2, y2, C_RGB(31, 0, 31)); dline(x2, y2, x2-dx-dy, y2-dy-dx, C_RGB(31, 0, 31)); dline(x2, y2, x2-dx+dy, y2-dy+dx, C_RGB(31, 0, 31)); } } }