2021-12-25 11:47:22 +01:00
|
|
|
#include "comp/entity.h"
|
|
|
|
#include "comp/physical.h"
|
|
|
|
#include "comp/visible.h"
|
|
|
|
#include "comp/mechanical.h"
|
|
|
|
#include "comp/fighter.h"
|
2021-12-27 21:54:55 +01:00
|
|
|
#include "comp/particle.h"
|
2022-03-16 20:00:22 +01:00
|
|
|
#include "anim.h"
|
2021-12-29 12:02:51 +01:00
|
|
|
#include "enemies.h"
|
2022-03-16 20:00:22 +01:00
|
|
|
#include "game.h"
|
|
|
|
#include "item.h"
|
2021-05-30 21:59:09 +02:00
|
|
|
#include "render.h"
|
2022-02-05 14:38:26 +01:00
|
|
|
#include "skills.h"
|
2021-06-04 15:14:12 +02:00
|
|
|
|
2021-05-30 21:59:09 +02:00
|
|
|
#include <gint/display.h>
|
2021-12-25 11:47:22 +01:00
|
|
|
#include <gint/defs/util.h>
|
2021-12-28 22:23:09 +01:00
|
|
|
#include <stdio.h>
|
2022-03-18 23:36:32 +01:00
|
|
|
#include <string.h>
|
2022-02-08 22:48:08 +01:00
|
|
|
#include <libprof.h>
|
2021-05-30 21:59:09 +02:00
|
|
|
|
|
|
|
//---
|
|
|
|
// Camera management
|
|
|
|
//---
|
|
|
|
|
2021-06-04 15:14:12 +02:00
|
|
|
void camera_init(camera_t *c, map_t const *m)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
|
|
|
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;
|
2021-08-15 18:19:48 +02:00
|
|
|
|
2021-10-23 15:10:20 +02:00
|
|
|
/* Vertical adjustment to add space for the HUD */
|
2022-06-05 23:11:06 +02:00
|
|
|
c->y -= fix(2)/16;
|
2021-05-30 21:59:09 +02:00
|
|
|
}
|
|
|
|
|
2021-10-24 21:54:37 +02:00
|
|
|
ivec2 camera_map2screen(camera_t const *c, vec2 p)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
2021-10-24 21:54:37 +02:00
|
|
|
return (ivec2){
|
2021-06-01 21:30:05 +02:00
|
|
|
.x = DWIDTH / 2 + fround((p.x - c->x) * TILE_WIDTH * c->zoom),
|
|
|
|
.y = DHEIGHT / 2 + fround((p.y - c->y) * TILE_HEIGHT * c->zoom),
|
|
|
|
};
|
2021-05-30 21:59:09 +02:00
|
|
|
}
|
|
|
|
/* Translate screen coordinates to map coordinates */
|
2021-10-24 21:54:37 +02:00
|
|
|
vec2 camera_screen2map(camera_t const *c, ivec2 p)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
2021-10-24 21:54:37 +02:00
|
|
|
return (vec2){
|
2021-06-01 21:30:05 +02:00
|
|
|
.x = c->x + fix(p.x - DWIDTH / 2) / TILE_WIDTH / c->zoom,
|
|
|
|
.y = c->y + fix(p.y - DHEIGHT / 2) / TILE_HEIGHT / c->zoom,
|
|
|
|
};
|
2021-05-30 21:59:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Lock the camera at the center if set by settings. */
|
2021-06-04 15:14:12 +02:00
|
|
|
static bool camera_lock(camera_t *c)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
|
|
|
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 */
|
2021-06-04 15:14:12 +02:00
|
|
|
static void camera_bound(camera_t *c)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
2021-06-01 21:30:05 +02:00
|
|
|
/* Project top left and bottom-right corners of viewport onto the map */
|
2021-10-24 21:54:37 +02:00
|
|
|
ivec2 v_tl = { c->viewport.x_min, c->viewport.y_min };
|
|
|
|
ivec2 v_br = { c->viewport.x_max, c->viewport.y_max };
|
2021-06-01 21:30:05 +02:00
|
|
|
|
2021-10-24 21:54:37 +02:00
|
|
|
vec2 m_tl = camera_screen2map(c, v_tl);
|
|
|
|
vec2 m_br = camera_screen2map(c, v_br);
|
2021-05-30 21:59:09 +02:00
|
|
|
|
|
|
|
/* Bound viewport to map limits */
|
2021-06-01 21:30:05 +02:00
|
|
|
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);
|
2021-05-30 21:59:09 +02:00
|
|
|
}
|
|
|
|
|
2021-06-04 15:14:12 +02:00
|
|
|
void camera_move(camera_t *c, map_coord_t dx, map_coord_t dy)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
|
|
|
if(camera_lock(c)) return;
|
|
|
|
c->x += dx;
|
|
|
|
c->y += dy;
|
|
|
|
camera_bound(c);
|
|
|
|
}
|
|
|
|
|
2021-06-04 15:14:12 +02:00
|
|
|
void camera_zoom(camera_t *c, int zoom)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-06-04 15:14:12 +02:00
|
|
|
fixed_t camera_ppu(camera_t const *c)
|
2021-06-01 17:49:29 +02:00
|
|
|
{
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2021-05-30 21:59:09 +02:00
|
|
|
//---
|
|
|
|
// Rendering
|
|
|
|
//---
|
|
|
|
|
2022-02-11 20:42:20 +01:00
|
|
|
static inline void render_tile(int x, int y, tileset_t const *tileset,
|
2022-02-13 10:03:14 +01:00
|
|
|
int tile_id, int time_ms, int flags)
|
2022-02-11 20:42:20 +01:00
|
|
|
{
|
|
|
|
/* If the tile is animated, find the position in the cycle */
|
|
|
|
if(tileset->tiles[tile_id].anim_length > 0) {
|
|
|
|
int start = tileset->tiles[tile_id].anim_start;
|
|
|
|
tile_animation_frame_t *frames = &tileset->anim[start];
|
|
|
|
|
|
|
|
int cycle_duration_ms = 0;
|
|
|
|
for(int i = 0; i < tileset->tiles[tile_id].anim_length; i++)
|
|
|
|
cycle_duration_ms += frames[i].duration_ms;
|
|
|
|
|
|
|
|
time_ms %= cycle_duration_ms;
|
|
|
|
int i = 0;
|
|
|
|
while(time_ms >= 0) {
|
|
|
|
time_ms -= frames[i].duration_ms;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
tile_id = frames[i-1].tile_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
dsubimage(x, y, tileset->sheet,
|
|
|
|
TILE_WIDTH * (tile_id % tileset->width),
|
|
|
|
TILE_HEIGHT * (tile_id / tileset->width),
|
2022-02-13 10:03:14 +01:00
|
|
|
TILE_WIDTH, TILE_HEIGHT, flags);
|
2022-02-11 20:42:20 +01:00
|
|
|
}
|
|
|
|
|
2022-02-13 10:03:14 +01:00
|
|
|
void render_map_layer(map_t const *map, camera_t const *c, int ss_x, int ss_y,
|
|
|
|
int layer, uint16_t *map_anim, int flags)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
2021-06-01 17:49:29 +02:00
|
|
|
/* Render floor and walls */
|
2022-02-11 20:42:20 +01:00
|
|
|
for(int row = -2; row < map->height + 2; row++)
|
|
|
|
for(int col = -1; col < map->width + 1; col++) {
|
|
|
|
map_cell_t *cell = map_cell(map, col, row);
|
2021-10-24 21:54:37 +02:00
|
|
|
vec2 tile_pos = { fix(col), fix(row) };
|
|
|
|
ivec2 p = camera_map2screen(c, tile_pos);
|
2022-02-02 10:16:59 +01:00
|
|
|
p.x += ss_x;
|
|
|
|
p.y += ss_y;
|
2021-05-30 21:59:09 +02:00
|
|
|
|
2022-02-11 20:42:20 +01:00
|
|
|
if(!cell) {
|
|
|
|
if(layer == CEILING)
|
|
|
|
drect(p.x, p.y, p.x+15, p.y+15, C_BLACK);
|
2021-08-30 17:48:44 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-02-13 10:03:14 +01:00
|
|
|
int time_ms = map_anim ? map_anim[map->width * row + col] : 0;
|
2022-02-11 20:42:20 +01:00
|
|
|
|
|
|
|
if(map->tileset->tiles[cell->base].plane == layer)
|
2022-02-13 10:03:14 +01:00
|
|
|
render_tile(p.x, p.y, map->tileset, cell->base, time_ms, flags);
|
2022-02-11 20:42:20 +01:00
|
|
|
if(cell->decor && map->tileset->tiles[cell->decor].plane == layer)
|
2022-02-13 10:03:14 +01:00
|
|
|
render_tile(p.x, p.y, map->tileset, cell->decor, time_ms, flags);
|
2021-05-30 21:59:09 +02:00
|
|
|
}
|
2021-06-04 15:14:12 +02:00
|
|
|
}
|
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
static int depth_measure(entity_t const *e, int direction)
|
2021-08-30 17:48:44 +02:00
|
|
|
{
|
2021-12-27 21:54:55 +01:00
|
|
|
particle_t *p = getcomp(e, particle);
|
2023-01-15 17:17:45 +01:00
|
|
|
if(p) {
|
|
|
|
if(p->plane != direction)
|
|
|
|
return -1;
|
|
|
|
if(!p->bound_to_entity)
|
|
|
|
return p->y;
|
|
|
|
|
|
|
|
physical_t *bound_p = getcomp(p->bound_entity, physical);
|
|
|
|
return bound_p ? bound_p->y : -1;
|
|
|
|
}
|
2021-12-27 21:54:55 +01:00
|
|
|
if(p) {
|
|
|
|
return (p->plane != direction) ? -1 : p->y;
|
|
|
|
}
|
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
visible_t *v = getcomp(e, visible);
|
2021-12-27 21:54:55 +01:00
|
|
|
if(v) {
|
|
|
|
return (v->sprite_plane != direction) ? -1 : getcomp(e, physical)->y;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
2021-12-25 11:47:22 +01:00
|
|
|
}
|
|
|
|
static int floor_depth_measure(entity_t const *e)
|
|
|
|
{
|
|
|
|
return depth_measure(e, HORIZONTAL);
|
|
|
|
}
|
|
|
|
static int wall_depth_measure(entity_t const *e)
|
|
|
|
{
|
|
|
|
return depth_measure(e, VERTICAL);
|
2021-08-30 17:48:44 +02:00
|
|
|
}
|
2021-12-27 21:54:55 +01:00
|
|
|
static int ceiling_depth_measure(entity_t const *e)
|
|
|
|
{
|
|
|
|
return depth_measure(e, CEILING);
|
|
|
|
}
|
2021-08-30 17:48:44 +02:00
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
static void render_shadow(int cx, int cy, int shadow_size)
|
2021-06-04 15:14:12 +02:00
|
|
|
{
|
2021-12-25 11:47:22 +01:00
|
|
|
if(shadow_size < 1 || shadow_size > 4)
|
|
|
|
return;
|
|
|
|
|
|
|
|
static const int hw_array[] = { 2, 3, 5, 7 };
|
|
|
|
static const int hh_array[] = { 1, 1, 2, 3 };
|
|
|
|
|
|
|
|
/* TODO: render_shadow(): Encode shadow shapes properly x) */
|
|
|
|
static const uint8_t profile_1[] = { 1, 0, 1 };
|
|
|
|
static const uint8_t profile_2[] = { 1, 0, 1 };
|
|
|
|
static const uint8_t profile_3[] = { 3, 1, 0, 1, 3 };
|
|
|
|
static const uint8_t profile_4[] = { 4, 2, 1, 0, 1, 2, 4 };
|
|
|
|
static const uint8_t *profile_array[] = {
|
|
|
|
profile_1, profile_2, profile_3, profile_4,
|
|
|
|
};
|
2021-06-04 15:14:12 +02:00
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
int hw = hw_array[shadow_size - 1];
|
|
|
|
int hh = hh_array[shadow_size - 1];
|
|
|
|
uint8_t const *profile = profile_array[shadow_size - 1] + hh;
|
2021-06-01 17:49:29 +02:00
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
int xmin = max(-hw, -cx);
|
|
|
|
int xmax = min(hw, DWIDTH-1 - cx);
|
|
|
|
int ymin = max(-hh, -cy);
|
|
|
|
int ymax = min(hh, DHEIGHT-1 - cy);
|
2021-08-15 11:05:04 +02:00
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
for(int y = ymin; y <= ymax; y++)
|
|
|
|
for(int x = xmin; x <= xmax; x++) {
|
|
|
|
if(hw - abs(x) < profile[y])
|
|
|
|
continue;
|
|
|
|
int i = DWIDTH * (cy+y) + (cx+x);
|
|
|
|
gint_vram[i] = (gint_vram[i] & 0xf7de) >> 1;
|
2021-08-30 17:48:44 +02:00
|
|
|
}
|
2021-12-25 11:47:22 +01:00
|
|
|
}
|
2021-08-30 17:48:44 +02:00
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
static void render_entities(game_t const *g, camera_t const *camera,
|
2022-02-02 10:16:59 +01:00
|
|
|
entity_measure_t *measure, int ss_x, int ss_y, bool show_hitboxes)
|
2021-12-25 11:47:22 +01:00
|
|
|
{
|
|
|
|
uint16_t *rendering_order;
|
|
|
|
int count = game_sort_entities(g, measure, &rendering_order);
|
2021-06-01 17:49:29 +02:00
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
for(int i = 0; i < count; i++) {
|
|
|
|
entity_t *e = g->entities[rendering_order[i]];
|
|
|
|
physical_t *p = getcomp(e, physical);
|
|
|
|
visible_t *v = getcomp(e, visible);
|
2021-12-27 21:54:55 +01:00
|
|
|
particle_t *pt = getcomp(e, particle);
|
2021-12-25 11:47:22 +01:00
|
|
|
|
2023-01-15 17:17:45 +01:00
|
|
|
vec2 xy = { 0, 0 };
|
|
|
|
fixed_t z = 0;
|
|
|
|
if(pt && pt->bound_to_entity) {
|
|
|
|
physical_t *bound_p = getcomp(pt->bound_entity, physical);
|
|
|
|
if(bound_p)
|
|
|
|
xy = (vec2) { bound_p->x, bound_p->y };
|
|
|
|
z = pt->z;
|
|
|
|
}
|
|
|
|
else if(pt) {
|
2021-12-27 21:54:55 +01:00
|
|
|
xy = (vec2) { pt->x, pt->y };
|
|
|
|
z = pt->z;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
xy = physical_pos(e);
|
|
|
|
z = v->z;
|
|
|
|
}
|
|
|
|
|
|
|
|
ivec2 scr = camera_map2screen(camera, xy);
|
2022-02-02 10:16:59 +01:00
|
|
|
scr.x += ss_x;
|
|
|
|
scr.y += ss_y;
|
2021-12-27 21:54:55 +01:00
|
|
|
int elevated_y = scr.y - fround(16 * z);
|
2021-12-25 11:47:22 +01:00
|
|
|
|
|
|
|
/* Show shadow */
|
2021-12-27 21:54:55 +01:00
|
|
|
if(v && v->shadow_size) {
|
2021-12-25 11:47:22 +01:00
|
|
|
render_shadow(scr.x, scr.y, v->shadow_size);
|
|
|
|
}
|
2021-06-01 21:30:05 +02:00
|
|
|
|
2021-06-09 20:47:39 +02:00
|
|
|
/* Show entity sprite */
|
2021-12-27 21:54:55 +01:00
|
|
|
if(v && v->anim.frame) {
|
2021-12-25 11:47:22 +01:00
|
|
|
anim_frame_render(scr.x, elevated_y, v->anim.frame);
|
2021-06-09 20:47:39 +02:00
|
|
|
}
|
|
|
|
/* Show entity hitbox in the map coordinate system */
|
2022-05-21 21:48:36 +02:00
|
|
|
if(v && show_hitboxes) {
|
2021-12-25 11:47:22 +01:00
|
|
|
rect r = p->hitbox;
|
|
|
|
r = rect_scale(r, camera_ppu(camera));
|
|
|
|
r = rect_translate(r, vec_i2f(scr));
|
2021-06-25 11:44:29 +02:00
|
|
|
rect_draw(r, C_BLUE);
|
2021-06-10 22:48:27 +02:00
|
|
|
|
2021-06-09 20:47:39 +02:00
|
|
|
/* Show entity center */
|
2021-12-25 11:47:22 +01:00
|
|
|
dline(scr.x-1, scr.y, scr.x+1, scr.y, C_BLUE);
|
|
|
|
dline(scr.x, scr.y-1, scr.x, scr.y+1, C_BLUE);
|
2021-06-10 22:48:27 +02:00
|
|
|
}
|
2021-12-27 21:54:55 +01:00
|
|
|
|
|
|
|
/* Show particle */
|
|
|
|
if(pt) {
|
2023-01-15 17:17:45 +01:00
|
|
|
particle_render(scr.x, elevated_y, pt);
|
2021-12-27 21:54:55 +01:00
|
|
|
}
|
2021-06-10 22:48:27 +02:00
|
|
|
}
|
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
free(rendering_order);
|
|
|
|
}
|
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
static int render_info_delay(int x, int y, level_event_t const *event)
|
2021-12-28 22:23:09 +01:00
|
|
|
{
|
2022-03-16 20:00:22 +01:00
|
|
|
(void)event;
|
2022-03-15 21:44:58 +01:00
|
|
|
extern bopti_image_t img_hud_delay;
|
|
|
|
dimage(x, y, &img_hud_delay);
|
|
|
|
return img_hud_delay.width;
|
|
|
|
}
|
2021-12-28 22:23:09 +01:00
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
static int render_info_wave(int x, int y, level_event_t const *event,
|
|
|
|
uint8_t *wave_left)
|
|
|
|
{
|
|
|
|
level_wave_t const *wave = event->wave;
|
|
|
|
int x0 = x;
|
|
|
|
y += 4;
|
2021-12-28 22:23:09 +01:00
|
|
|
|
2022-03-14 22:50:24 +01:00
|
|
|
for(int i = 0; i < wave->entry_count; i++) {
|
|
|
|
enemy_t const *enemy = enemy_data(wave->entries[i].identity);
|
2022-03-15 21:44:58 +01:00
|
|
|
int amount_total = wave->entries[i].amount;
|
|
|
|
int amount = wave_left ? wave_left[i] : amount_total;
|
2021-12-28 22:23:09 +01:00
|
|
|
|
|
|
|
int text_w;
|
2022-03-15 21:44:58 +01:00
|
|
|
font_damage_size(amount_total, &text_w, NULL);
|
2021-12-28 22:23:09 +01:00
|
|
|
|
|
|
|
anim_frame_t *frame = enemy->anim_idle->start[0];
|
2022-03-14 22:50:24 +01:00
|
|
|
dsubimage(x, y - frame->h / 2, frame->sheet,
|
2021-12-28 22:23:09 +01:00
|
|
|
frame->x, frame->y, frame->w, frame->h, DIMAGE_NONE);
|
2022-03-14 22:50:24 +01:00
|
|
|
font_damage_print(x + frame->w - 4, y + frame->h / 2 - 4, C_WHITE,
|
2021-12-28 22:23:09 +01:00
|
|
|
DTEXT_LEFT, DTEXT_TOP, amount);
|
|
|
|
|
2022-03-14 22:50:24 +01:00
|
|
|
x += frame->w - 6 + text_w;
|
2021-12-28 22:23:09 +01:00
|
|
|
}
|
2022-03-15 21:44:58 +01:00
|
|
|
|
|
|
|
return x-x0;
|
2022-03-14 22:50:24 +01:00
|
|
|
}
|
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
static int render_info_item(int x, int y, level_event_t const *event)
|
2022-03-14 22:50:24 +01:00
|
|
|
{
|
2022-03-16 20:00:22 +01:00
|
|
|
anim_t const *anim = item_anim(event->item);
|
2022-03-15 21:44:58 +01:00
|
|
|
|
2022-03-16 20:00:22 +01:00
|
|
|
if(anim) {
|
|
|
|
anim_frame_render(x+5, y+5, anim->start[0]);
|
|
|
|
return anim->start[0]->w;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
int w;
|
|
|
|
dsize("Item", NULL, &w, NULL);
|
|
|
|
dprint(x, y, C_WHITE, "Item");
|
|
|
|
return w;
|
|
|
|
}
|
2022-03-14 22:50:24 +01:00
|
|
|
}
|
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
static void render_info(int x, int y, game_t const *g)
|
2022-03-14 22:50:24 +01:00
|
|
|
{
|
2022-03-15 21:44:58 +01:00
|
|
|
if(!g->level)
|
|
|
|
return;
|
2021-12-28 22:23:09 +01:00
|
|
|
|
2023-01-23 21:56:55 +01:00
|
|
|
/* Determine maximum length of message that can be rendered */
|
|
|
|
int intro_w, frozen_w;
|
2022-03-15 21:44:58 +01:00
|
|
|
char str[32];
|
|
|
|
snprintf(str, 32, "Wave %d", level_wave_count(g->level));
|
|
|
|
dsize(str, NULL, &intro_w, NULL);
|
2023-01-23 21:56:55 +01:00
|
|
|
dsize("Frozen", NULL, &frozen_w, NULL);
|
|
|
|
intro_w = max(intro_w, frozen_w);
|
|
|
|
|
|
|
|
int overlay_color = -1;
|
|
|
|
|
|
|
|
if(g->freeze_time > 0) {
|
|
|
|
strcpy(str, "Frozen");
|
|
|
|
overlay_color = 0x5555;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
snprintf(str, 32, "Wave %d", g->wave_number);
|
|
|
|
if(g->hud_wave_number_timer)
|
|
|
|
overlay_color = C_RGB(fround(g->hud_wave_number_timer * 31), 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(overlay_color >= 0) {
|
|
|
|
dtext(x+8, y-2, overlay_color, str);
|
|
|
|
dtext(x+8, y, overlay_color, str);
|
|
|
|
dtext(x+7, y-1, overlay_color, str);
|
|
|
|
dtext(x+9, y-1, overlay_color, str);
|
2022-12-31 18:26:57 +01:00
|
|
|
}
|
2022-03-15 21:44:58 +01:00
|
|
|
dtext(x+8, y-1, C_WHITE, str);
|
|
|
|
x += intro_w + 20;
|
2022-03-14 22:50:24 +01:00
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
int sep_w;
|
|
|
|
dsize(">", NULL, &sep_w, NULL);
|
2022-03-14 22:50:24 +01:00
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
for(int i = g->event; i <= g->level->event_count && x <= DWIDTH; i++) {
|
|
|
|
if(i == g->level->event_count) {
|
|
|
|
extern bopti_image_t img_hud_flag;
|
|
|
|
dimage(x+1, y, &img_hud_flag);
|
2022-03-14 22:50:24 +01:00
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
// TODO: If victory reached, write "Victory!"
|
|
|
|
break;
|
|
|
|
}
|
2022-03-14 22:50:24 +01:00
|
|
|
|
|
|
|
level_event_t const *event = &g->level->events[i];
|
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
if(event->type == LEVEL_EVENT_DELAY)
|
|
|
|
x += render_info_delay(x, y, event);
|
|
|
|
if(event->type == LEVEL_EVENT_WAVE) {
|
|
|
|
uint8_t *wave_left = (i == g->event) ? g->wave_left : NULL;
|
|
|
|
x += render_info_wave(x, y, event, wave_left);
|
2022-03-14 22:50:24 +01:00
|
|
|
}
|
2022-03-15 21:44:58 +01:00
|
|
|
if(event->type == LEVEL_EVENT_ITEM)
|
|
|
|
x += render_info_item(x, y, event);
|
2022-03-14 22:50:24 +01:00
|
|
|
|
2022-03-15 21:44:58 +01:00
|
|
|
x += 12;
|
|
|
|
dprint(x, y, C_WHITE, ">");
|
|
|
|
x += sep_w + 12;
|
2022-03-14 22:50:24 +01:00
|
|
|
}
|
2021-12-28 22:23:09 +01:00
|
|
|
}
|
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
static void render_panel(int x, int y, bool hflip)
|
2022-01-18 09:45:23 +01:00
|
|
|
{
|
2022-03-18 20:36:29 +01:00
|
|
|
extern bopti_image_t img_hud_panel;
|
2022-05-31 21:42:43 +02:00
|
|
|
int w = 130;
|
|
|
|
int sx = hflip ? 0 : img_hud_panel.width - w;
|
2022-03-18 20:36:29 +01:00
|
|
|
int sy = 0;
|
|
|
|
|
|
|
|
dsubimage(x, y, &img_hud_panel, sx, sy, w, 30, DIMAGE_NONE);
|
|
|
|
y += 30;
|
|
|
|
sy += 30;
|
2022-05-31 21:42:43 +02:00
|
|
|
for(int i = 0; i < 10; i++) {
|
2022-03-18 20:36:29 +01:00
|
|
|
dsubimage(x, y, &img_hud_panel, sx, sy, w, 10, DIMAGE_NONE);
|
|
|
|
y += 10;
|
2022-01-18 09:45:23 +01:00
|
|
|
}
|
2022-03-18 20:36:29 +01:00
|
|
|
sy += 10;
|
|
|
|
dsubimage(x, y, &img_hud_panel, sx, sy, w, 30, DIMAGE_NONE);
|
2022-01-18 09:45:23 +01:00
|
|
|
}
|
|
|
|
|
2023-01-02 11:50:25 +01:00
|
|
|
void render_full_panel(int x, int y, int w, int h)
|
|
|
|
{
|
|
|
|
extern bopti_image_t img_hud_panel;
|
|
|
|
w = max(w, 100);
|
|
|
|
h = max(h, 60);
|
|
|
|
|
|
|
|
/* Top row */
|
|
|
|
dsubimage(x, y, &img_hud_panel, 0, 0, 50, 30, DIMAGE_NONE);
|
|
|
|
for(int sx = 50; sx < w-50;) {
|
|
|
|
int frame_w = min(w-50-sx, 44);
|
|
|
|
dsubimage(x+sx, y, &img_hud_panel, 50, 0, frame_w, 30, DIMAGE_NONE);
|
|
|
|
sx += frame_w;
|
|
|
|
}
|
|
|
|
dsubimage(x+w-50, y, &img_hud_panel, 94, 0, 50, 30, DIMAGE_NONE);
|
|
|
|
|
|
|
|
/* Middle row */
|
|
|
|
for(int sy = 30; sy < h-30;) {
|
|
|
|
int frame_h = min(h-30-sy, 10);
|
|
|
|
dsubimage(x, y+sy, &img_hud_panel, 0, 30, 50, 10, DIMAGE_NONE);
|
|
|
|
dsubimage(x+w-50, y+sy, &img_hud_panel, 94, 30, 50, 10, DIMAGE_NONE);
|
|
|
|
sy += frame_h;
|
|
|
|
}
|
|
|
|
if(w > 100 && h > 60)
|
|
|
|
drect(x+50, y+30, x+w-51, y+h-31, RGB24(0x202828));
|
|
|
|
|
|
|
|
/* Bottom row */
|
|
|
|
dsubimage(x, y+h-30, &img_hud_panel, 0, 40, 50, 30, DIMAGE_NONE);
|
|
|
|
for(int sx = 50; sx < w-50;) {
|
|
|
|
int frame_w = min(w-50-sx, 44);
|
|
|
|
dsubimage(x+sx, y+h-30, &img_hud_panel, 50, 40, frame_w, 30,
|
|
|
|
DIMAGE_NONE);
|
|
|
|
sx += frame_w;
|
|
|
|
}
|
|
|
|
dsubimage(x+w-50, y+h-30, &img_hud_panel, 94, 40, 50, 30, DIMAGE_NONE);
|
|
|
|
}
|
|
|
|
|
2022-02-08 22:48:08 +01:00
|
|
|
uint32_t time_render_map = 0;
|
|
|
|
uint32_t time_render_hud = 0;
|
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
static void subimage_outline(int x, int y, image_t const *img, int left,
|
|
|
|
int top, size_t w, size_t h, int color)
|
|
|
|
{
|
|
|
|
int alpha = image_alpha(img->format);
|
|
|
|
|
|
|
|
for(int iy = -1; iy < (int)h+1; iy++)
|
|
|
|
for(int ix = -1; ix < (int)w+1; ix++) {
|
|
|
|
bool inb = ((size_t)ix < w && (size_t)iy < h);
|
|
|
|
int pixel = inb ? image_get_pixel(img, left+ix, top+iy) : alpha;
|
|
|
|
|
|
|
|
if(pixel != alpha) {
|
|
|
|
dpixel(x+ix, y+iy, image_decode_pixel(img, pixel));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool linked = false;
|
|
|
|
for(int i = 0; i < 4; i++) {
|
|
|
|
int dy = (i == 0) - (i == 1);
|
|
|
|
int dx = (i == 2) - (i == 3);
|
|
|
|
if((size_t)(ix+dx) < w && (size_t)(iy+dy) < h)
|
|
|
|
linked |= image_get_pixel(img, left+ix+dx, top+iy+dy) != alpha;
|
|
|
|
}
|
|
|
|
if(linked)
|
|
|
|
dpixel(x+ix, y+iy, color);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void anim_frame_render_outline(int x, int y, anim_frame_t const *frame,
|
|
|
|
int color)
|
|
|
|
{
|
|
|
|
if(!frame) return;
|
|
|
|
subimage_outline(x - frame->cx, y - frame->cy, frame->sheet,
|
|
|
|
frame->x, frame->y, frame->w, frame->h, color);
|
|
|
|
}
|
|
|
|
|
2022-12-23 18:42:05 +01:00
|
|
|
int render_small_text(int x, int y, int color, char const *text, int size)
|
2022-05-31 21:42:43 +02:00
|
|
|
{
|
|
|
|
extern bopti_image_t img_hud_small;
|
2022-12-23 18:42:05 +01:00
|
|
|
(void)color;
|
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
if(size < 0)
|
|
|
|
size = strlen(text);
|
|
|
|
|
2022-12-23 18:42:05 +01:00
|
|
|
static char const *texts[] = {
|
|
|
|
"SHIFT", "HP", "ATK", "MAG", "DEF", "PLAY", "HIGH SCORE", "OTHER"
|
|
|
|
};
|
|
|
|
static int const widths[] = {
|
|
|
|
27, 12, 17, 19, 18, 22, 51, 28
|
|
|
|
};
|
|
|
|
|
|
|
|
for(size_t i = 0; i < sizeof texts / sizeof texts[0]; i++) {
|
|
|
|
if(!strncmp(text, texts[i], size)) {
|
|
|
|
dsubimage(x-1, y+3, &img_hud_small, 0, 8*i, widths[i], 8,
|
|
|
|
DIMAGE_NONE);
|
|
|
|
return widths[i] - 1;
|
|
|
|
}
|
2022-05-31 21:42:43 +02:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-12-31 18:26:57 +01:00
|
|
|
static int render_arcade_char_size(int c, int font_size)
|
2022-12-28 23:48:32 +01:00
|
|
|
{
|
|
|
|
if(c < '0' || c > '9')
|
|
|
|
return 0;
|
2022-12-31 18:26:57 +01:00
|
|
|
if(font_size == 1)
|
|
|
|
return 7 - (c == '1' || c == '7') + (c == '4');
|
|
|
|
if(font_size == 2)
|
|
|
|
return 8 + (c == '0' || c == '4');
|
|
|
|
return 0;
|
2022-12-28 23:48:32 +01:00
|
|
|
}
|
2022-12-31 18:26:57 +01:00
|
|
|
int render_arcade_dsize(int value, int font_size)
|
2022-12-28 23:48:32 +01:00
|
|
|
{
|
|
|
|
char str[16];
|
|
|
|
sprintf(str, "%d", value);
|
|
|
|
|
|
|
|
int pixels = 0;
|
|
|
|
for(int i = 0; str[i]; i++)
|
2022-12-31 18:26:57 +01:00
|
|
|
pixels += render_arcade_char_size(str[i], font_size);
|
2022-12-28 23:48:32 +01:00
|
|
|
return pixels;
|
|
|
|
}
|
|
|
|
|
2022-12-31 18:26:57 +01:00
|
|
|
void render_arcade(int x, int y, int halign, int value, int color_style,
|
|
|
|
int font_size)
|
2022-12-28 23:48:32 +01:00
|
|
|
{
|
|
|
|
extern bopti_image_t img_hud_arcade_font;
|
2022-12-31 18:26:57 +01:00
|
|
|
extern bopti_image_t img_hud_arcade_font2;
|
|
|
|
|
|
|
|
int w = render_arcade_dsize(value, font_size);
|
2022-12-28 23:48:32 +01:00
|
|
|
if(halign == DTEXT_RIGHT)
|
|
|
|
x -= w;
|
|
|
|
else if(halign == DTEXT_CENTER)
|
|
|
|
x -= (w >> 1);
|
|
|
|
|
|
|
|
/* Auto style based on the number of digits */
|
|
|
|
if(color_style < 0)
|
2022-12-30 15:59:13 +01:00
|
|
|
color_style = (value >= 10) + (value >= 30);
|
2022-12-28 23:48:32 +01:00
|
|
|
|
|
|
|
char str[16];
|
|
|
|
sprintf(str, "%d", value);
|
|
|
|
|
|
|
|
for(int i = 0; str[i]; i++) {
|
2022-12-31 18:26:57 +01:00
|
|
|
int w = render_arcade_char_size(str[i], font_size);
|
|
|
|
|
|
|
|
if(font_size == 1)
|
|
|
|
dsubimage(x, y, &img_hud_arcade_font, 1 + 9 * (str[i] - '0'),
|
|
|
|
1 + 9 * color_style, w, 8, DIMAGE_NONE);
|
|
|
|
if(font_size == 2)
|
|
|
|
dsubimage(x, y, &img_hud_arcade_font2, 1 + 10 * (str[i] - '0'),
|
|
|
|
1 + 12 * color_style, w, 12, DIMAGE_NONE);
|
2022-12-28 23:48:32 +01:00
|
|
|
x += w;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 23:36:32 +01:00
|
|
|
static void print_stat_opt(int x, int y, int stat, int reference,
|
|
|
|
char const *format, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
char str[32];
|
|
|
|
vsnprintf(str, 32, format, args);
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
int color = C_WHITE;
|
|
|
|
if(stat < reference) color = RGB24(0xc05458);
|
|
|
|
if(stat > reference) color = RGB24(0x21c24f);
|
|
|
|
dtext(x, y, color, str);
|
|
|
|
}
|
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
static void print_stat(int x, int y, int current, int g_vis, int g_base)
|
2022-03-18 23:36:32 +01:00
|
|
|
{
|
2022-05-31 21:42:43 +02:00
|
|
|
dprint(x, y, C_WHITE, "%d", current);
|
|
|
|
|
|
|
|
int color = C_RGB(10, 10, 10);
|
|
|
|
if(g_vis < g_base) color = RGB24(0xc05458);
|
|
|
|
if(g_vis > g_base) color = RGB24(0x21c24f);
|
|
|
|
dprint(x+30, y, color, "+%d/Lv", g_vis);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dtext_multi(int x0, int y, int color, char const *str)
|
|
|
|
{
|
|
|
|
int x = x0;
|
|
|
|
|
|
|
|
while(*str) {
|
|
|
|
if(!strncmp(str, "HP", 2)) {
|
2022-12-23 18:42:05 +01:00
|
|
|
x += render_small_text(x, y, color, str, 2) + 1;
|
2022-05-31 21:42:43 +02:00
|
|
|
str += 2;
|
|
|
|
}
|
|
|
|
else if(!strncmp(str, "ATK", 3) || !strncmp(str, "MAG", 3) ||
|
|
|
|
!strncmp(str, "DEF", 3)) {
|
2022-12-23 18:42:05 +01:00
|
|
|
x += render_small_text(x, y, color, str, 3) + 1;
|
2022-05-31 21:42:43 +02:00
|
|
|
str += 3;
|
|
|
|
}
|
|
|
|
else if(*str == '\n') {
|
|
|
|
str++;
|
|
|
|
y += 13;
|
|
|
|
x = x0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
int w;
|
|
|
|
dnsize(str, 1, NULL, &w, NULL);
|
|
|
|
dtext_opt(x, y, color, C_NONE, DTEXT_LEFT, DTEXT_TOP, str, 1);
|
|
|
|
x += w + 1;
|
|
|
|
str++;
|
|
|
|
}
|
|
|
|
}
|
2022-03-18 23:36:32 +01:00
|
|
|
}
|
|
|
|
|
2022-12-23 22:34:49 +01:00
|
|
|
static int skill_x(int i)
|
|
|
|
{
|
|
|
|
return 23 + 44*i + 103*(i>=3);
|
|
|
|
}
|
|
|
|
|
2021-12-25 11:47:22 +01:00
|
|
|
void render_game(game_t const *g, bool show_hitboxes)
|
|
|
|
{
|
|
|
|
camera_t const *camera = &g->camera;
|
|
|
|
|
2022-02-02 10:16:59 +01:00
|
|
|
/* Screenshake displacement */
|
|
|
|
int ss_x=0, ss_y=0;
|
|
|
|
if(g->screenshake_duration > 0) {
|
|
|
|
int amp = g->screenshake_amplitude;
|
|
|
|
ss_x = rand() % amp - (amp/2);
|
|
|
|
ss_y = rand() % amp - (amp/2);
|
|
|
|
}
|
|
|
|
|
2022-02-08 22:48:08 +01:00
|
|
|
prof_t ctx = prof_make();
|
|
|
|
prof_enter(ctx);
|
|
|
|
|
2021-12-28 20:28:17 +01:00
|
|
|
/* Render map floor and floor entities */
|
2022-02-13 10:03:14 +01:00
|
|
|
render_map_layer(g->map, camera, ss_x, ss_y, HORIZONTAL, g->map_anim,
|
|
|
|
DIMAGE_NOCLIP);
|
2022-02-02 10:16:59 +01:00
|
|
|
render_entities(g, camera, floor_depth_measure, ss_x, ss_y, show_hitboxes);
|
2021-12-25 11:47:22 +01:00
|
|
|
|
2021-12-28 20:28:17 +01:00
|
|
|
/* Render map walls and vertical entities
|
|
|
|
TODO ECS: Sort walls and wall entities together for proper ordering!*/
|
2022-02-13 10:03:14 +01:00
|
|
|
render_map_layer(g->map, camera, ss_x, ss_y, VERTICAL, g->map_anim,
|
|
|
|
DIMAGE_NOCLIP);
|
2022-02-02 10:16:59 +01:00
|
|
|
render_entities(g, camera, wall_depth_measure, ss_x, ss_y, show_hitboxes);
|
2021-12-25 11:47:22 +01:00
|
|
|
|
2021-12-28 20:28:17 +01:00
|
|
|
/* Render ceiling tiles (including out of bounds) and ceiling entities */
|
2022-02-13 10:03:14 +01:00
|
|
|
render_map_layer(g->map, camera, ss_x, ss_y, CEILING, g->map_anim,
|
|
|
|
DIMAGE_NOCLIP);
|
2022-02-02 10:16:59 +01:00
|
|
|
render_entities(g, camera, ceiling_depth_measure, ss_x,ss_y,show_hitboxes);
|
2021-07-16 11:06:28 +02:00
|
|
|
|
2022-02-08 22:48:08 +01:00
|
|
|
prof_leave(ctx);
|
|
|
|
time_render_map = prof_time(ctx);
|
|
|
|
|
2021-07-16 11:06:28 +02:00
|
|
|
extern font_t font_rogue;
|
|
|
|
font_t const *old_font = dfont(&font_rogue);
|
|
|
|
|
2022-02-08 22:48:08 +01:00
|
|
|
ctx = prof_make();
|
|
|
|
prof_enter(ctx);
|
|
|
|
|
2022-02-16 14:54:26 +01:00
|
|
|
/* GUI positioning variables used during level entry */
|
|
|
|
fixed_t MAX_GUI_TIME = fix(0.75);
|
|
|
|
fixed_t gui_time = MAX_GUI_TIME;
|
|
|
|
if(g->time_total < MAX_GUI_TIME)
|
|
|
|
gui_time = g->time_total;
|
|
|
|
int HUD_Y = cubic(DHEIGHT+30, DHEIGHT, gui_time, MAX_GUI_TIME);
|
2022-03-14 22:50:24 +01:00
|
|
|
int HEADER_Y = cubic(-15, 2, gui_time, MAX_GUI_TIME);
|
2022-02-16 14:54:26 +01:00
|
|
|
|
2022-12-31 18:26:57 +01:00
|
|
|
/* Render score box */
|
|
|
|
extern bopti_image_t img_hud_top;
|
|
|
|
dimage(6, HEADER_Y - 2, &img_hud_top);
|
|
|
|
int score = game_compute_score(g);
|
|
|
|
render_arcade(42, HEADER_Y, DTEXT_LEFT, score,
|
|
|
|
0 + (score >= 100) + (score >= 1000), 2);
|
|
|
|
|
2021-12-28 22:23:09 +01:00
|
|
|
/* Render wave information */
|
2022-12-31 18:26:57 +01:00
|
|
|
render_info(90, HEADER_Y, g);
|
2021-07-16 15:51:32 +02:00
|
|
|
|
2022-06-05 23:11:06 +02:00
|
|
|
/* Render current message */
|
2022-12-31 18:26:57 +01:00
|
|
|
if(g->message) {
|
|
|
|
dtext_opt(DWIDTH - 8, HEADER_Y + 16, RGB24(0x15171a), C_NONE,
|
|
|
|
DTEXT_RIGHT, DTEXT_TOP, g->message, -1);
|
|
|
|
dtext_opt(DWIDTH - 8, HEADER_Y + 15, RGB24(0xabb1ba), C_NONE,
|
|
|
|
DTEXT_RIGHT, DTEXT_TOP, g->message, -1);
|
|
|
|
}
|
2022-06-05 23:11:06 +02:00
|
|
|
|
2021-10-23 15:10:20 +02:00
|
|
|
/* Render HUD */
|
|
|
|
extern bopti_image_t img_hud;
|
2022-02-16 14:54:26 +01:00
|
|
|
dimage(0, HUD_Y - img_hud.height, &img_hud);
|
2021-08-30 19:07:22 +02:00
|
|
|
|
2022-02-16 17:42:05 +01:00
|
|
|
fighter_t *player_f = getcomp(g->player, fighter);
|
|
|
|
|
2022-01-17 18:29:05 +01:00
|
|
|
extern font_t font_hud;
|
2022-02-16 17:42:05 +01:00
|
|
|
player_data_t *player_data = player_f->player;
|
2022-01-17 18:29:05 +01:00
|
|
|
dfont(&font_hud);
|
2022-12-28 23:48:32 +01:00
|
|
|
dprint(167, HUD_Y - 5, RGB24(0x15171a), "%d", player_data->xp_level);
|
|
|
|
dprint(167, HUD_Y - 6, RGB24(0xabb1ba), "%d", player_data->xp_level);
|
2022-01-17 18:29:05 +01:00
|
|
|
dfont(&font_rogue);
|
|
|
|
|
2021-10-23 15:10:20 +02:00
|
|
|
/* Render life bar */
|
|
|
|
extern bopti_image_t img_hud_life;
|
2021-12-25 11:47:22 +01:00
|
|
|
int fill_height = (img_hud_life.height * player_f->HP) / player_f->HP_max;
|
2022-02-16 14:54:26 +01:00
|
|
|
dsubimage(184, HUD_Y - 5 - fill_height, &img_hud_life,
|
2021-10-23 15:10:20 +02:00
|
|
|
0, img_hud_life.height - fill_height, img_hud_life.width, fill_height,
|
2021-08-30 19:07:22 +02:00
|
|
|
DIMAGE_NONE);
|
2021-07-16 11:06:28 +02:00
|
|
|
|
2022-02-07 09:10:47 +01:00
|
|
|
/* Render XP bar. The following values indicate the geometry of the XP bar
|
|
|
|
so that we can show a partially-filled version of it */
|
|
|
|
if(anim_in(g->hud_xp_anim.frame, &anims_hud_xp_Explode, 0)) {
|
2022-12-23 21:51:17 +01:00
|
|
|
anim_frame_render(158, HUD_Y-32, g->hud_xp_anim.frame);
|
2022-02-07 09:10:47 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
static int const XP_FULL=22;
|
2022-02-16 17:42:05 +01:00
|
|
|
int xp_current = player_data->xp_current;
|
|
|
|
int xp_total = max(player_data->xp_to_next_level, 1);
|
2022-02-07 09:10:47 +01:00
|
|
|
int fill_height = XP_FULL * xp_current / xp_total;
|
2022-12-23 21:51:17 +01:00
|
|
|
anim_frame_subrender(158, HUD_Y-32, g->hud_xp_anim.frame,
|
2022-02-07 09:10:47 +01:00
|
|
|
0, XP_FULL - fill_height, -1, fill_height);
|
|
|
|
}
|
2022-01-17 18:29:05 +01:00
|
|
|
|
2021-08-15 18:19:48 +02:00
|
|
|
/* Render skill icons */
|
2021-12-28 21:01:20 +01:00
|
|
|
static const int skill_box_size = 27;
|
|
|
|
|
2022-03-18 20:36:29 +01:00
|
|
|
for(int i = 0; i < 5; i++) {
|
2021-08-15 18:19:48 +02:00
|
|
|
/* Activity and cooldown */
|
2022-02-05 14:38:26 +01:00
|
|
|
fixed_t cooldown_total = skill_cooldown(player_f->skills[i+1]);
|
2022-02-04 10:12:00 +01:00
|
|
|
fixed_t cooldown_remaining = player_f->actions_cooldown[i+1];
|
2022-03-19 18:14:00 +01:00
|
|
|
int skill = player_f->skills[i+1];
|
2021-08-15 18:19:48 +02:00
|
|
|
|
2022-12-23 22:34:49 +01:00
|
|
|
int x = skill_x(i);
|
2022-02-16 14:54:26 +01:00
|
|
|
int y = HUD_Y - 33;
|
2022-03-19 18:14:00 +01:00
|
|
|
int bg = (cooldown_remaining != 0) ? 2 : 1;
|
|
|
|
skill_render(x+2, y+2, skill, bg, C_WHITE);
|
2021-08-15 18:19:48 +02:00
|
|
|
|
2022-03-19 18:14:00 +01:00
|
|
|
/* Whiten the area representing remaining cooldown */
|
2021-08-15 18:19:48 +02:00
|
|
|
if(cooldown_total != 0) {
|
2021-12-28 21:01:20 +01:00
|
|
|
int height = (cooldown_remaining*skill_box_size) / cooldown_total;
|
|
|
|
int ymin = y + skill_box_size - height;
|
|
|
|
int ymax = y + skill_box_size;
|
2022-02-16 14:54:26 +01:00
|
|
|
if(ymin >= DHEIGHT) ymin = DHEIGHT;
|
|
|
|
if(ymax >= DHEIGHT) ymax = DHEIGHT;
|
2021-12-28 21:01:20 +01:00
|
|
|
for(int y1 = ymin; y1 < ymax; y1++) {
|
|
|
|
for(int x1 = x; x1 < x + skill_box_size; x1++) {
|
2021-08-15 18:19:48 +02:00
|
|
|
int i = DWIDTH * y1 + x1;
|
|
|
|
gint_vram[i] = ~((~gint_vram[i] & 0xf7de) >> 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 22:34:49 +01:00
|
|
|
/* Render backpack icon */
|
|
|
|
anim_frame_render(skill_x(5)+2, HUD_Y-33+2, g->hud_backpack_anim.frame);
|
|
|
|
|
2022-12-28 23:48:32 +01:00
|
|
|
/* Render combo score */
|
|
|
|
extern bopti_image_t img_hud_combo;
|
|
|
|
fill_height = fround(img_hud_combo.height * g->combo_health);
|
|
|
|
dsubimage(224, HUD_Y - 9 - fill_height, &img_hud_combo,
|
|
|
|
0, img_hud_combo.height - fill_height, img_hud_combo.width,
|
|
|
|
fill_height, DIMAGE_NONE);
|
2022-12-31 18:26:57 +01:00
|
|
|
render_arcade(234, HUD_Y-25, DTEXT_CENTER, g->combo, -1, 1);
|
2022-12-28 23:48:32 +01:00
|
|
|
|
2022-03-18 20:36:29 +01:00
|
|
|
if(g->menu_time > 0) {
|
2022-05-31 21:42:43 +02:00
|
|
|
int x1 = cubic(-130, 0, g->menu_time, fix(1));
|
|
|
|
int x2 = DWIDTH - 130 - x1;
|
2022-03-18 20:36:29 +01:00
|
|
|
fighter_t *player_f = getcomp(g->player, fighter);
|
|
|
|
player_data_t *player_data = player_f->player;
|
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
render_panel(x1, 22, false);
|
|
|
|
render_panel(x2, 22, true);
|
2022-03-18 20:36:29 +01:00
|
|
|
|
|
|
|
extern font_t font_rogue;
|
|
|
|
font_t const *old_font = dfont(&font_rogue);
|
2022-05-31 21:42:43 +02:00
|
|
|
dprint_opt(x1+61, 33, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP,
|
2022-03-18 20:36:29 +01:00
|
|
|
"Inventory");
|
2022-05-31 21:42:43 +02:00
|
|
|
dprint_opt(x2+72, 33, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP,
|
2022-03-18 20:36:29 +01:00
|
|
|
"Status");
|
2022-05-31 21:42:43 +02:00
|
|
|
dprint_opt(x2+72, 44, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_TOP,
|
2022-03-18 20:36:29 +01:00
|
|
|
"Lv.%d (%d/%d)", player_data->xp_level, player_data->xp_current,
|
|
|
|
player_data->xp_to_next_level);
|
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
int selected_item = player_data->inventory[g->menu_cursor];
|
2022-03-18 22:21:30 +01:00
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
extern bopti_image_t img_hud_itemslots;
|
|
|
|
for(int y = 0; y < 2; y++)
|
|
|
|
for(int x = 0; x < 4; x++) {
|
|
|
|
int sx = g->menu_cursor == 4*y + x ? 21 : 0;
|
|
|
|
int x2 = x1 + 15 + 24*x;
|
|
|
|
int y2 = 48 + 24*y;
|
|
|
|
dsubimage(x2, y2, &img_hud_itemslots, sx, 0, 21, 21, DIMAGE_NONE);
|
|
|
|
|
|
|
|
int item = player_data->inventory[4*y + x];
|
|
|
|
int slot = item_equipment_slot(item);
|
2022-03-18 22:21:30 +01:00
|
|
|
if(item > 0) {
|
|
|
|
anim_t const *anim = item_anim(item);
|
2022-05-31 21:42:43 +02:00
|
|
|
if(anim) {
|
|
|
|
if(slot >= 0 && player_data->equipment[slot] == 4*y + x)
|
|
|
|
anim_frame_render_outline(x2+10, y2+10, anim->start[0],
|
|
|
|
RGB24(0x979515));
|
|
|
|
else
|
|
|
|
anim_frame_render(x2+10, y2+10, anim->start[0]);
|
|
|
|
}
|
2022-03-18 22:21:30 +01:00
|
|
|
}
|
2022-03-18 20:36:29 +01:00
|
|
|
}
|
2022-05-31 21:42:43 +02:00
|
|
|
if(selected_item >= 0) {
|
|
|
|
int w, h;
|
|
|
|
dsize(item_name(selected_item), NULL, &w, &h);
|
2022-05-31 21:45:48 +02:00
|
|
|
drect(x1+5, 104, x1+w+8, 104+h+3, RGB24(0x3d5050));
|
|
|
|
dtext(x1+7, 105, C_WHITE, item_name(selected_item));
|
2022-05-31 21:42:43 +02:00
|
|
|
|
|
|
|
char const *desc = item_description(selected_item);
|
|
|
|
if(desc)
|
2022-05-31 21:45:48 +02:00
|
|
|
dtext_multi(x1+7, 123, C_WHITE, desc);
|
2022-05-31 21:42:43 +02:00
|
|
|
|
2022-12-23 18:42:05 +01:00
|
|
|
int dx = render_small_text(x1+7, 156, -1, "SHIFT", -1);
|
2022-05-31 21:42:43 +02:00
|
|
|
char const *use_str = "Use";
|
|
|
|
if(item_is_equip(selected_item)) {
|
|
|
|
use_str = "Equip";
|
|
|
|
int slot = item_equipment_slot(selected_item);
|
|
|
|
if(slot >= 0 && player_data->equipment[slot] == g->menu_cursor)
|
|
|
|
use_str = "Unequip";
|
2022-03-18 22:21:30 +01:00
|
|
|
}
|
2022-05-31 21:45:48 +02:00
|
|
|
dtext(x1+7+dx+4, 156, C_WHITE, use_str);
|
2022-03-18 20:36:29 +01:00
|
|
|
}
|
|
|
|
|
2022-05-31 21:42:43 +02:00
|
|
|
/* What the growth is with the current equips */
|
|
|
|
fighter_stats_t growth_base;
|
|
|
|
growth_base = player_compute_growth(g->player, player_data->equipment);
|
|
|
|
|
2022-03-18 23:36:32 +01:00
|
|
|
/* What the stats would be if the selected item is equipped */
|
2022-05-31 21:42:43 +02:00
|
|
|
fighter_stats_t growth_equip;
|
2022-03-18 23:36:32 +01:00
|
|
|
int switched_equipment[3];
|
|
|
|
memcpy(switched_equipment, player_data->equipment, 3*sizeof(int));
|
|
|
|
int selected_slot = item_equipment_slot(selected_item);
|
|
|
|
if(selected_item >= 0 && selected_slot >= 0)
|
2022-05-31 21:42:43 +02:00
|
|
|
switched_equipment[selected_slot] = g->menu_cursor;
|
|
|
|
growth_equip = player_compute_growth(g->player, switched_equipment);
|
|
|
|
|
2022-12-23 18:42:05 +01:00
|
|
|
render_small_text(x2+14, 100, C_WHITE, "HP", -1);
|
2022-05-31 21:42:43 +02:00
|
|
|
print_stat_opt(x2+44, 100, growth_equip.HP, growth_base.HP,
|
|
|
|
"%d/%d", player_f->HP, player_f->HP_max);
|
2022-12-23 18:42:05 +01:00
|
|
|
render_small_text(x2+14, 114, C_WHITE, "ATK", -1);
|
2022-05-31 21:42:43 +02:00
|
|
|
print_stat(x2+44, 114, player_f->ATK, growth_equip.ATK,
|
|
|
|
growth_base.ATK);
|
2022-12-23 18:42:05 +01:00
|
|
|
render_small_text(x2+14, 128, C_WHITE, "MAG", -1);
|
2022-05-31 21:42:43 +02:00
|
|
|
print_stat(x2+44, 128, player_f->MAG, growth_equip.MAG,
|
|
|
|
growth_base.MAG);
|
2022-12-23 18:42:05 +01:00
|
|
|
render_small_text(x2+14, 142, C_WHITE, "DEF", -1);
|
2022-05-31 21:42:43 +02:00
|
|
|
print_stat(x2+44, 142, player_f->DEF, growth_equip.DEF,
|
|
|
|
growth_base.DEF);
|
2022-03-18 20:36:29 +01:00
|
|
|
dfont(old_font);
|
2022-03-19 18:14:00 +01:00
|
|
|
|
|
|
|
/* What the skills would be if the selected item is equipped */
|
|
|
|
int switched_skills[6] = { -1, -1, -1, -1, -1, -1 };
|
|
|
|
player_compute_skills(g->player, switched_equipment, switched_skills);
|
|
|
|
|
|
|
|
for(int i = 0; i < 5; i++) {
|
|
|
|
int s1 = player_f->skills[i+1], s2 = switched_skills[i+1];
|
|
|
|
if(s1 == s2) continue;
|
2022-12-23 22:34:49 +01:00
|
|
|
int x = skill_x(i);
|
2022-03-19 18:14:00 +01:00
|
|
|
int y = HUD_Y - 33;
|
2022-05-31 21:45:48 +02:00
|
|
|
skill_render(x+2, y+2, s2, 3, C_WHITE);
|
2022-03-19 18:14:00 +01:00
|
|
|
}
|
2022-03-18 20:36:29 +01:00
|
|
|
}
|
|
|
|
|
2023-01-02 11:50:25 +01:00
|
|
|
/* Render final panel */
|
|
|
|
if(g->final_screen_time >= 0 && g->victory) {
|
|
|
|
int PANEL_Y = cubic(-(DHEIGHT-30), 30, g->final_screen_time, fix(2.0));
|
|
|
|
render_full_panel(30, PANEL_Y, DWIDTH-30*2, DHEIGHT-30-40);
|
|
|
|
|
|
|
|
dtext_opt(DWIDTH/2, PANEL_Y+12, C_WHITE, C_NONE, DTEXT_CENTER,
|
|
|
|
DTEXT_TOP, "Victory!", -1);
|
|
|
|
|
|
|
|
dprint(45, PANEL_Y+30, 0x5555, "Score");
|
|
|
|
dprint(300, PANEL_Y+30, 0x5555, "%d", game_compute_score(g));
|
|
|
|
|
|
|
|
dprint(45, PANEL_Y+45, C_WHITE, "Time survived");
|
|
|
|
dprint(300, PANEL_Y+45, C_WHITE, "%d",
|
|
|
|
g->score.waves_survived);
|
|
|
|
|
|
|
|
dprint(45, PANEL_Y+60, C_WHITE, "Kills (%d one-shot)",
|
|
|
|
g->score.one_shot_kills);
|
|
|
|
dprint(300, PANEL_Y+60, C_WHITE, "%d",
|
|
|
|
g->score.kill_number);
|
|
|
|
|
|
|
|
dprint(45, PANEL_Y+75, C_WHITE, "Combos (longest: %d)",
|
|
|
|
g->score.longest_combo_chain);
|
|
|
|
dprint(300, PANEL_Y+75, C_WHITE, "%d",
|
|
|
|
g->score.combo_chains);
|
|
|
|
|
|
|
|
dprint(45, PANEL_Y+90, C_WHITE, "Simult. kills (largest: %d)",
|
|
|
|
g->score.largest_simult_kill);
|
|
|
|
dprint(300, PANEL_Y+90, C_WHITE, "%d",
|
|
|
|
g->score.simult_kills);
|
|
|
|
|
|
|
|
dtext_opt(DWIDTH/2, PANEL_Y+120, C_WHITE, C_NONE, DTEXT_CENTER,
|
|
|
|
DTEXT_TOP, "EXIT: Back to menu", -1);
|
|
|
|
}
|
|
|
|
if(g->final_screen_time >= 0 && !g->victory) {
|
|
|
|
int PANEL_Y = cubic(-60, DHEIGHT/2-30, g->final_screen_time, fix(2.0));
|
|
|
|
render_full_panel(DWIDTH/2-90, PANEL_Y, 180, 60);
|
|
|
|
|
|
|
|
dtext_opt(DWIDTH/2, PANEL_Y+15, C_RED, C_NONE, DTEXT_CENTER,
|
|
|
|
DTEXT_TOP, "Defeat!", -1);
|
|
|
|
dtext_opt(DWIDTH/2, PANEL_Y+30, C_WHITE, C_NONE, DTEXT_CENTER,
|
|
|
|
DTEXT_TOP, "EXIT: Back to menu", -1);
|
|
|
|
}
|
|
|
|
|
2022-02-08 22:48:08 +01:00
|
|
|
prof_leave(ctx);
|
|
|
|
time_render_hud = prof_time(ctx);
|
2021-07-16 11:06:28 +02:00
|
|
|
dfont(old_font);
|
2021-06-25 11:44:29 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 09:32:54 +01:00
|
|
|
void render_pfg_all2one(pfg_all2one_t const *paths, camera_t const *c,
|
|
|
|
uint8_t *occupation)
|
2021-06-25 11:44:29 +02:00
|
|
|
{
|
|
|
|
for(int row = 0; row < paths->map->height; row++)
|
|
|
|
for(int col = 0; col < paths->map->width; col++) {
|
2022-02-11 20:42:20 +01:00
|
|
|
map_cell_t *cell = map_cell(paths->map, col, row);
|
|
|
|
if(!cell)
|
|
|
|
continue;
|
|
|
|
if(paths->map->tileset->tiles[cell->base].solid)
|
|
|
|
continue;
|
|
|
|
if(cell->decor && paths->map->tileset->tiles[cell->decor].solid)
|
|
|
|
continue;
|
2021-06-25 11:44:29 +02:00
|
|
|
|
2021-10-24 21:54:37 +02:00
|
|
|
vec2 fp = vec_i2f_center((ivec2){ col, row });
|
|
|
|
ivec2 p = camera_map2screen(c, fp);
|
2021-06-25 11:44:29 +02:00
|
|
|
|
|
|
|
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 {
|
2021-10-24 21:54:37 +02:00
|
|
|
ivec2 v = vec_f2i(fdir(dir));
|
2021-06-25 11:44:29 +02:00
|
|
|
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));
|
|
|
|
}
|
2022-02-06 09:32:54 +01:00
|
|
|
|
|
|
|
int occ = occupation ? occupation[paths->map->width * row + col] : -1;
|
|
|
|
if(occ > 0) {
|
|
|
|
dprint_opt(p.x, p.y, C_WHITE, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE,
|
|
|
|
"%d", occ);
|
|
|
|
}
|
2021-06-25 11:44:29 +02:00
|
|
|
}
|
2021-05-30 21:59:09 +02:00
|
|
|
}
|