1134 lines
56 KiB
C++

/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui_doom.hpp"
#include "ui.hpp"
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define RENDER_HEIGHT 280
#define HALF_WIDTH (SCREEN_WIDTH / 2)
#define HALF_HEIGHT (RENDER_HEIGHT / 2)
#define LEVEL_WIDTH_BASE 6
#define LEVEL_WIDTH (1 << LEVEL_WIDTH_BASE)
#define LEVEL_HEIGHT 57
#define LEVEL_SIZE (LEVEL_WIDTH / 2 * LEVEL_HEIGHT)
#define MAX_RENDER_DEPTH 12
#define MIN_ENTITIES 15
#define MAX_ENTITIES 25
#define RES_DIVIDER 2
#define DISTANCE_MULTIPLIER 20
#define ROT_SPEED 0.25
#define MOV_SPEED 1.0
#define JOGGING_SPEED 1.2
#define MAX_ENTITY_DISTANCE 200
#define ITEM_COLLIDER_DIST 6
#define PI 3.14159265358979323846
#define GUN_WIDTH 30
#define GUN_HEIGHT 40
#define GUN_TARGET_POS 24
#define GUN_SHOT_POS (GUN_TARGET_POS + 6)
#define ENEMY_SIZE 16
#define ENEMY_SPEED 0.03
#define ENEMY_MELEE_DIST 20
#define ENEMY_MELEE_DAMAGE 2
#define GUN_MAX_DAMAGE 15
#define ACTIVATION_RADIUS 120
#define STATE_INACTIVE 6
#define COLOR_BACKGROUND Black
#define COLOR_WALL_LIGHT Green
#define COLOR_WALL_DARK Maroon
#define COLOR_FLOOR_DARK Black
struct Coords {
double x, y;
};
struct Player {
Coords pos;
Coords dir;
Coords plane;
double velocity;
uint8_t health;
uint8_t keys;
uint8_t ammo;
};
struct Entity {
uint16_t uid;
Coords pos;
Coords death_pos;
uint8_t state;
uint8_t health;
uint8_t distance;
uint8_t timer;
};
// Stole this level map from Flipper Zero Doom, but he stole it from a guy who stole it and that guy probably stole it too. Argh!
static const uint8_t level[LEVEL_SIZE] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x20, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x90, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF2, 0x00, 0x00, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x4F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x20, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF0, 0x00, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF0, 0x00, 0x05, 0x00, 0x00, 0x90, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0xFF,
0xF0, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF0, 0x00, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x08, 0x00, 0xFF,
0xF0, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF2, 0x02, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0x40, 0x80, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF,
0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xF0, 0x00, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x40, 0x00, 0x02, 0x00, 0x90, 0xFF, 0xFF,
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
0xF0, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x20, 0x00, 0x7F, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF,
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
namespace ui::external_app::doom {
static Ticker game_timer;
static Player player;
static Entity entities[MAX_ENTITIES];
static uint8_t num_entities = 0;
static uint16_t kills = 0;
static uint8_t scene = 0;
static bool up, down, left, right, fired;
static double jogging, view_height;
static bool needs_redraw = false;
static bool needs_gun_redraw = false;
static uint8_t gun_pos = GUN_TARGET_POS;
static bool gun_fired = false;
static int prev_gun_x = 0;
static int prev_gun_y = 0;
Coords create_coords(double x, double y) {
Coords c;
c.x = x;
c.y = y;
return c;
}
Player create_player(double x, double y) {
Player p;
p.pos = create_coords(x + 0.5, y + 0.5);
p.dir = create_coords(1, 0);
p.plane = create_coords(0, -0.66);
p.velocity = 0;
p.health = 100;
p.keys = 0;
p.ammo = 99;
return p;
}
Entity create_entity(uint8_t type, uint8_t x, uint8_t y, uint8_t state, uint8_t health) {
Entity e;
e.uid = ((y << 6) | x) << 4 | type;
e.pos = create_coords(x + 0.5, y + 0.5);
e.death_pos = create_coords(x + 0.5, y + 0.5);
e.state = state;
e.health = health;
e.distance = 0;
e.timer = 0;
return e;
}
uint8_t get_block_at(uint8_t x, uint8_t y) {
if (x >= LEVEL_WIDTH || y >= LEVEL_HEIGHT) return 0xF;
return level[((LEVEL_HEIGHT - 1 - y) * LEVEL_WIDTH + x) / 2] >> (!(x % 2) * 4) & 0b1111;
}
Coords translate_into_view(Coords pos) {
double sprite_x = pos.x - player.pos.x;
double sprite_y = pos.y - player.pos.y;
double inv_det = 1.0 / (player.plane.x * player.dir.y - player.dir.x * player.plane.y);
double transform_x = inv_det * (player.dir.y * sprite_x - player.dir.x * sprite_y);
double transform_y = inv_det * (-player.plane.y * sprite_x + player.plane.x * sprite_y);
return create_coords(transform_x, transform_y);
}
void spawn_entity(uint8_t type, uint8_t x, uint8_t y) {
if (num_entities >= MAX_ENTITIES) return;
entities[num_entities] = create_entity(type, x, y, STATE_INACTIVE, 50);
num_entities++;
}
void spawn_random_entity(uint8_t type) {
if (num_entities >= MAX_ENTITIES) return;
std::srand(LPC_RTC->CTIME0);
uint8_t spawn_x, spawn_y;
do {
spawn_x = std::rand() % LEVEL_WIDTH;
spawn_y = std::rand() % LEVEL_HEIGHT;
} while (get_block_at(spawn_x, spawn_y) == 0xF ||
(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y));
entities[num_entities] = create_entity(type, spawn_x, spawn_y, STATE_INACTIVE, 50);
num_entities++;
}
void remove_entity(uint8_t index) {
for (uint8_t i = index; i < num_entities - 1; i++) {
entities[i] = entities[i + 1];
}
num_entities--;
if (num_entities < MIN_ENTITIES) {
uint8_t attempts = 0;
bool spawned = false;
while (!spawned && attempts < 50) {
std::srand(LPC_RTC->CTIME0 + attempts);
uint8_t spawn_x = std::rand() % LEVEL_WIDTH;
uint8_t spawn_y = std::rand() % LEVEL_HEIGHT;
int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x;
int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y;
uint16_t dist_squared = dx * dx + dy * dy;
if (dist_squared >= 64 &&
get_block_at(spawn_x, spawn_y) != 0xF &&
!(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) {
entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50);
entities[num_entities].pos.x = (double)spawn_x + 0.5;
entities[num_entities].pos.y = (double)spawn_y + 0.5;
entities[num_entities].death_pos.x = (double)spawn_x + 0.5;
entities[num_entities].death_pos.y = (double)spawn_y + 0.5;
if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) != 0xF) {
num_entities++;
spawned = true;
}
}
attempts++;
}
}
}
void initialize_level() {
for (uint8_t y = LEVEL_HEIGHT - 1; y > 0; y--) {
for (uint8_t x = 0; x < LEVEL_WIDTH; x++) {
uint8_t block = get_block_at(x, y);
if (block == 0x1) {
player = create_player(x, y);
break;
}
}
}
num_entities = 0;
uint8_t initial_enemies = 0;
uint8_t attempts = 0;
while (initial_enemies < 2 && num_entities < MAX_ENTITIES && attempts < 50) {
std::srand(LPC_RTC->CTIME0 + attempts);
int8_t offset_x = (std::rand() % 11) - 5;
int8_t offset_y = (std::rand() % 11) - 5;
if (abs(offset_x) < 3 && abs(offset_y) < 3) {
attempts++;
continue;
}
int16_t temp_x = (int16_t)player.pos.x + offset_x;
int16_t temp_y = (int16_t)player.pos.y + offset_y;
uint8_t spawn_x = (temp_x < 0) ? 0 : (temp_x >= LEVEL_WIDTH) ? LEVEL_WIDTH - 1
: temp_x;
uint8_t spawn_y = (temp_y < 0) ? 0 : (temp_y >= LEVEL_HEIGHT) ? LEVEL_HEIGHT - 1
: temp_y;
if (get_block_at(spawn_x, spawn_y) != 0xF &&
!(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) {
entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50);
entities[num_entities].pos.x = (double)spawn_x + 0.5;
entities[num_entities].pos.y = (double)spawn_y + 0.5;
entities[num_entities].death_pos.x = (double)spawn_x + 0.5;
entities[num_entities].death_pos.y = (double)spawn_y + 0.5;
if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) == 0xF) {
attempts++;
continue;
}
num_entities++;
initial_enemies++;
}
attempts++;
}
attempts = 0;
while (num_entities < MIN_ENTITIES && attempts < 100) {
std::srand(LPC_RTC->CTIME0 + attempts + 100);
uint8_t spawn_x = std::rand() % LEVEL_WIDTH;
uint8_t spawn_y = std::rand() % LEVEL_HEIGHT;
int16_t dx = (int16_t)spawn_x - (int16_t)player.pos.x;
int16_t dy = (int16_t)spawn_y - (int16_t)player.pos.y;
uint16_t dist_squared = dx * dx + dy * dy;
if (dist_squared >= 25 &&
get_block_at(spawn_x, spawn_y) != 0xF &&
!(spawn_x == (uint8_t)player.pos.x && spawn_y == (uint8_t)player.pos.y)) {
entities[num_entities] = create_entity(0x2, spawn_x, spawn_y, STATE_INACTIVE, 50);
entities[num_entities].pos.x = (double)spawn_x + 0.5;
entities[num_entities].pos.y = (double)spawn_y + 0.5;
entities[num_entities].death_pos.x = (double)spawn_x + 0.5;
entities[num_entities].death_pos.y = (double)spawn_y + 0.5;
if (get_block_at((uint8_t)entities[num_entities].pos.x, (uint8_t)entities[num_entities].pos.y) == 0xF) {
attempts++;
continue;
}
num_entities++;
}
attempts++;
}
}
void fire() {
if (player.ammo > 0) {
uint8_t closest_i = 255;
double min_dist = 1000.0;
for (uint8_t i = 0; i < num_entities; i++) {
if (entities[i].state == 5) continue;
double dx = entities[i].pos.x - player.pos.x;
double dy = entities[i].pos.y - player.pos.y;
double dist = sqrt(dx * dx + dy * dy) * DISTANCE_MULTIPLIER;
double dot = dx * player.dir.x + dy * player.dir.y;
double angle = acos(dot / (dist / DISTANCE_MULTIPLIER));
if (dist < 100 && angle < 0.2 && dot > 0 && dist < min_dist) {
closest_i = i;
min_dist = dist;
}
}
if (closest_i != 255) {
uint8_t damage = fmin(GUN_MAX_DAMAGE, GUN_MAX_DAMAGE * (100 - min_dist) / 100);
if (damage > 0) {
entities[closest_i].health = fmax(0, entities[closest_i].health - damage);
entities[closest_i].state = 4;
entities[closest_i].timer = 4;
needs_redraw = true;
}
}
player.ammo--;
}
}
void update_entities() {
uint8_t i = 0;
while (i < num_entities) {
entities[i].distance = sqrt(pow(player.pos.x - entities[i].pos.x, 2) + pow(player.pos.y - entities[i].pos.y, 2)) * DISTANCE_MULTIPLIER;
if (entities[i].state != 5) {
if (entities[i].distance > ACTIVATION_RADIUS && entities[i].state != STATE_INACTIVE) {
entities[i].state = STATE_INACTIVE;
needs_redraw = true;
} else if (entities[i].distance <= ACTIVATION_RADIUS && entities[i].state == STATE_INACTIVE) {
entities[i].state = 0;
needs_redraw = true;
}
}
if (entities[i].timer > 0) entities[i].timer--;
if (entities[i].health == 0 && entities[i].state == 5 && entities[i].timer == 0) {
kills++;
remove_entity(i);
continue;
}
if (entities[i].health == 0 && entities[i].state != 5) {
entities[i].state = 5;
entities[i].timer = 6;
entities[i].death_pos = entities[i].pos;
needs_redraw = true;
}
if (entities[i].state == 4 && entities[i].timer == 0) {
entities[i].state = 0;
needs_redraw = true;
}
if (entities[i].state != STATE_INACTIVE && entities[i].state != 4 && entities[i].state != 5) {
double dx = player.pos.x - entities[i].pos.x;
double dy = player.pos.y - entities[i].pos.y;
double dist = sqrt(dx * dx + dy * dy);
if (dist <= (ENEMY_MELEE_DIST / DISTANCE_MULTIPLIER) && entities[i].timer == 0) {
entities[i].state = 3;
player.health = fmax(0, player.health - ENEMY_MELEE_DAMAGE);
entities[i].timer = 30;
needs_redraw = true;
}
double move_dist = fmin(ENEMY_SPEED, dist - 0.2);
bool moved = false;
double new_x = entities[i].pos.x + (dx / dist) * move_dist;
double new_y = entities[i].pos.y + (dy / dist) * move_dist;
if (get_block_at((uint8_t)new_x, (uint8_t)entities[i].pos.y) != 0xF) {
entities[i].pos.x = new_x;
moved = true;
}
if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)new_y) != 0xF) {
entities[i].pos.y = new_y;
moved = true;
}
if (!moved) {
bool wall_x_pos = get_block_at((uint8_t)entities[i].pos.x + 1, (uint8_t)entities[i].pos.y) == 0xF;
bool wall_x_neg = get_block_at((uint8_t)entities[i].pos.x - 1, (uint8_t)entities[i].pos.y) == 0xF;
bool wall_y_pos = get_block_at((uint8_t)entities[i].pos.x, (uint8_t)entities[i].pos.y + 1) == 0xF;
bool wall_y_neg = get_block_at((uint8_t)entities[i].pos.x, (uint8_t)entities[i].pos.y - 1) == 0xF;
uint8_t dir_choice = (entities[i].uid + LPC_RTC->CTIME0) % 4;
if (wall_x_pos || wall_x_neg) {
double try_y = entities[i].pos.y + (dir_choice < 2 ? 1 : -1) * move_dist;
if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)try_y) != 0xF) {
entities[i].pos.y = try_y;
moved = true;
}
} else if (wall_y_pos || wall_y_neg) {
double try_x = entities[i].pos.x + (dir_choice < 2 ? 1 : -1) * move_dist;
if (get_block_at((uint8_t)try_x, (uint8_t)entities[i].pos.y) != 0xF) {
entities[i].pos.x = try_x;
moved = true;
}
}
if (!moved) {
double angle = (entities[i].uid * 17 + LPC_RTC->CTIME0) % 628 / 100.0;
double try_x = entities[i].pos.x + cos(angle) * move_dist;
double try_y = entities[i].pos.y + sin(angle) * move_dist;
if (get_block_at((uint8_t)try_x, (uint8_t)entities[i].pos.y) != 0xF) {
entities[i].pos.x = try_x;
moved = true;
}
if (get_block_at((uint8_t)entities[i].pos.x, (uint8_t)try_y) != 0xF) {
entities[i].pos.y = try_y;
moved = true;
}
}
}
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if (dx == 0 && dy == 0) continue;
if (get_block_at((uint8_t)(entities[i].pos.x + dx * 0.3), (uint8_t)(entities[i].pos.y + dy * 0.3)) == 0xF) {
entities[i].pos.x -= dx * 0.05;
entities[i].pos.y -= dy * 0.05;
}
}
}
if (dist > (ENEMY_MELEE_DIST / DISTANCE_MULTIPLIER)) entities[i].state = 0;
}
i++;
}
}
void update_game() {
bool state_changed = false;
bool gun_only_change = false;
if (scene == 1) {
if (player.health > 0) {
if (up) {
player.velocity += (MOV_SPEED - player.velocity) * 0.4;
jogging = fabs(player.velocity) * 5;
up = false;
state_changed = true;
} else if (down) {
player.velocity += (-MOV_SPEED - player.velocity) * 0.4;
jogging = fabs(player.velocity) * 5;
down = false;
state_changed = true;
} else {
double old_velocity = player.velocity;
player.velocity *= 0.5;
jogging = fabs(player.velocity) * 5;
if (fabs(player.velocity) < 0.001) player.velocity = 0;
if (old_velocity != player.velocity) state_changed = true;
}
if (right) {
double old_dir_x = player.dir.x;
player.dir.x = player.dir.x * cos(-ROT_SPEED) - player.dir.y * sin(-ROT_SPEED);
player.dir.y = old_dir_x * sin(-ROT_SPEED) + player.dir.y * cos(-ROT_SPEED);
double old_plane_x = player.plane.x;
player.plane.x = player.plane.x * cos(-ROT_SPEED) - player.plane.y * sin(-ROT_SPEED);
player.plane.y = old_plane_x * sin(-ROT_SPEED) + player.plane.y * cos(-ROT_SPEED);
right = false;
state_changed = true;
} else if (left) {
double old_dir_x = player.dir.x;
player.dir.x = player.dir.x * cos(ROT_SPEED) - player.dir.y * sin(ROT_SPEED);
player.dir.y = old_dir_x * sin(ROT_SPEED) + player.dir.y * cos(ROT_SPEED);
double old_plane_x = player.plane.x;
player.plane.x = player.plane.x * cos(ROT_SPEED) - player.plane.y * sin(ROT_SPEED);
player.plane.y = old_plane_x * sin(ROT_SPEED) + player.plane.y * cos(ROT_SPEED);
left = false;
state_changed = true;
}
if (player.velocity != 0) {
view_height = fabs(sin(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 6 * jogging;
} else {
view_height = 0;
}
if (fabs(player.velocity) > 0.003) {
double new_x = player.pos.x + player.dir.x * player.velocity;
double new_y = player.pos.y + player.dir.y * player.velocity;
bool can_move_x = get_block_at((uint8_t)new_x, (uint8_t)player.pos.y) != 0xF;
bool can_move_y = get_block_at((uint8_t)player.pos.x, (uint8_t)new_y) != 0xF;
for (uint8_t i = 0; i < num_entities; i++) {
if (entities[i].state != 5 && entities[i].state != STATE_INACTIVE) {
double dx = new_x - entities[i].pos.x;
double dy = player.pos.y - entities[i].pos.y;
if (sqrt(dx * dx + dy * dy) < 0.5) {
can_move_x = false;
}
dx = player.pos.x - entities[i].pos.x;
dy = new_y - entities[i].pos.y;
if (sqrt(dx * dx + dy * dy) < 0.5) {
can_move_y = false;
}
}
}
if (can_move_x) {
player.pos.x = new_x;
state_changed = true;
}
if (can_move_y) {
player.pos.y = new_y;
state_changed = true;
}
} else if (player.velocity != 0) {
player.velocity = 0;
state_changed = true;
}
if (gun_pos > GUN_TARGET_POS) {
gun_pos -= 1;
gun_only_change = true;
} else if (gun_pos < GUN_TARGET_POS) {
gun_pos += 2;
gun_only_change = true;
} else if (fired) {
gun_pos = GUN_SHOT_POS;
fire();
needs_gun_redraw = true;
gun_only_change = true;
fired = false;
}
} else {
if (view_height > -10) {
view_height--;
state_changed = true;
}
if (gun_pos > 1) {
gun_pos -= 2;
gun_only_change = true;
}
}
update_entities();
}
if (state_changed && !gun_only_change)
needs_redraw = true;
else if (gun_only_change)
needs_gun_redraw = true;
}
static void game_timer_check() {
if (scene == 1) update_game();
}
void render_gun(Painter& painter, uint8_t gun_pos, double jogging) {
int x = HALF_WIDTH - GUN_WIDTH / 2 + sin(LPC_RTC->CTIME0 * JOGGING_SPEED) * 10 * jogging;
int y = RENDER_HEIGHT - gun_pos - 29 + fabs(cos(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 8 * jogging;
prev_gun_x = x;
prev_gun_y = y;
int recoil = gun_pos > GUN_TARGET_POS ? (gun_pos - GUN_TARGET_POS) / 2 : 0;
// Gun body - shotgun style with more detail
painter.fill_rectangle({x + 8, y + 8, 18, 12}, pp_colors[Green]);
painter.fill_rectangle({x + 6, y + 10, 2, 8}, pp_colors[Black]);
painter.fill_rectangle({x + 26, y + 10, 2, 8}, pp_colors[Black]);
// Barrel
painter.fill_rectangle({x + 12, y - 4, 8, 12}, pp_colors[Green]);
painter.fill_rectangle({x + 14, y - 6, 4, 2}, pp_colors[Green]);
// Barrel shading
painter.fill_rectangle({x + 12, y - 4, 2, 12}, pp_colors[White]);
painter.fill_rectangle({x + 18, y - 4, 2, 12}, pp_colors[Black]);
// Handle
painter.fill_rectangle({x + 12, y + 20, 8, 20}, pp_colors[Maroon]);
painter.fill_rectangle({x + 10, y + 20, 2, 18}, pp_colors[White]);
painter.fill_rectangle({x + 20, y + 20, 2, 18}, pp_colors[Black]);
// Trigger guard
painter.fill_rectangle({x + 10, y + 20, 12, 2}, pp_colors[Green]);
painter.fill_rectangle({x + 10, y + 20, 2, 6}, pp_colors[Green]);
painter.fill_rectangle({x + 20, y + 20, 2, 6}, pp_colors[Green]);
painter.fill_rectangle({x + 10, y + 26, 12, 2}, pp_colors[Green]);
// Trigger
painter.fill_rectangle({x + 14, y + 22 + recoil / 2, 4, 4}, pp_colors[Black]);
// Pump action
painter.fill_rectangle({x + 8, y + 4, 16, 4}, pp_colors[Maroon]);
painter.fill_rectangle({x + 7, y + 4, 1, 4}, pp_colors[White]);
painter.fill_rectangle({x + 24, y + 4, 1, 4}, pp_colors[Black]);
// Action slide rails
painter.fill_rectangle({x + 10, y + 8, 1, 8 + recoil}, pp_colors[White]);
painter.fill_rectangle({x + 21, y + 8, 1, 8 + recoil}, pp_colors[Black]);
// Ammo chamber
painter.fill_rectangle({x + 24, y + 12, 4, 4}, pp_colors[Black]);
// Muzzle flash
if (gun_pos > GUN_TARGET_POS) {
int flash_size = 10 + (gun_pos - GUN_TARGET_POS) * 2;
int flash_center_x = x + 16;
int flash_center_y = y - 6;
// Core of flash
painter.fill_rectangle({flash_center_x - flash_size / 4, flash_center_y - flash_size / 2,
flash_size / 2, flash_size},
pp_colors[White]);
// Outer yellow glow
painter.fill_rectangle({flash_center_x - flash_size / 2, flash_center_y - flash_size / 4,
flash_size, flash_size / 2},
pp_colors[Yellow]);
// Left spike
painter.fill_rectangle({flash_center_x - flash_size, flash_center_y - 2,
flash_size / 2, 4},
pp_colors[Yellow]);
// Right spike
painter.fill_rectangle({flash_center_x + flash_size / 2, flash_center_y - 2,
flash_size / 2, 4},
pp_colors[Yellow]);
// Top spike
painter.fill_rectangle({flash_center_x - 2, flash_center_y - flash_size,
4, flash_size / 2},
pp_colors[Yellow]);
// Small random sparks
if (LPC_RTC->CTIME0 % 2) {
painter.fill_rectangle({flash_center_x - flash_size / 2 - 2, flash_center_y - flash_size / 2 - 2,
2, 2},
pp_colors[Yellow]);
painter.fill_rectangle({flash_center_x + flash_size / 3, flash_center_y - flash_size / 3,
2, 2},
pp_colors[Yellow]);
} else {
painter.fill_rectangle({flash_center_x + flash_size / 3, flash_center_y - flash_size / 2,
2, 2},
pp_colors[Yellow]);
painter.fill_rectangle({flash_center_x - flash_size / 4, flash_center_y + flash_size / 4,
2, 2},
pp_colors[Yellow]);
}
// Smoke effect
if (gun_pos > GUN_TARGET_POS + 2) {
int smoke_y = flash_center_y - flash_size - 4;
painter.fill_rectangle({flash_center_x - 6, smoke_y, 12, 4}, pp_colors[White]);
painter.fill_rectangle({flash_center_x - 8, smoke_y - 4, 16, 4}, pp_colors[White]);
painter.fill_rectangle({flash_center_x - 6, smoke_y - 8, 12, 4}, pp_colors[White]);
}
}
// Shell ejection
if (gun_pos > GUN_TARGET_POS && gun_pos < GUN_TARGET_POS + 6) {
int shell_x = x + 24 + (gun_pos - GUN_TARGET_POS) * 2;
int shell_y = y + 10 - (gun_pos - GUN_TARGET_POS);
painter.fill_rectangle({shell_x, shell_y, 4, 2}, pp_colors[Yellow]);
painter.fill_rectangle({shell_x + 1, shell_y - 1, 2, 4}, pp_colors[Yellow]);
}
}
void render_entities(Painter& painter) {
for (uint8_t i = 0; i < num_entities; i++) {
if (entities[i].state == STATE_INACTIVE) continue;
Coords transform = translate_into_view(entities[i].state == 5 ? entities[i].death_pos : entities[i].pos);
if (transform.y <= 0.1 || transform.y > MAX_RENDER_DEPTH) continue;
int16_t sprite_x = HALF_WIDTH * (1.0 + transform.x / transform.y);
int16_t sprite_y = HALF_HEIGHT + view_height / transform.y;
uint8_t base_size = ENEMY_SIZE / fmax(0.1, transform.y);
if (sprite_x < -base_size || sprite_x > SCREEN_WIDTH + base_size || sprite_y < 0 || sprite_y > RENDER_HEIGHT) continue;
uint8_t size = base_size * 2;
int16_t half_size = size / 2;
int16_t x_start = sprite_x - half_size;
int16_t y_start = sprite_y - half_size;
Color body_color = Color(180, 40, 20);
Color spine_color = Color(120, 20, 10);
Color eye_color = Color(255, 165, 0);
Color pupil_color = Color(255, 0, 0);
Color claw_color = Color(60, 60, 60);
Color teeth_color = Color(220, 220, 220);
Color blood_color = Color(150, 0, 0);
Color horn_color = Color(90, 30, 10);
if (entities[i].state == 5) {
// Death animation - gibbed/flattened demon
uint8_t death_phase = entities[i].timer / 5;
if (death_phase > 3) death_phase = 3;
if (death_phase == 0) {
// Initial death - falling apart
painter.fill_rectangle({x_start + size / 5, sprite_y, size * 3 / 5, size / 3}, body_color);
painter.fill_rectangle({x_start + size * 2 / 5, sprite_y - size / 6, size / 5, size / 4}, horn_color);
painter.fill_rectangle({x_start + size / 3, sprite_y + size / 8, size / 8, size / 6}, blood_color);
painter.fill_rectangle({x_start + size / 2, sprite_y + size / 6, size / 6, size / 8}, blood_color);
painter.fill_rectangle({x_start + size / 4, sprite_y + size / 4, size / 5, size / 10}, claw_color);
painter.fill_rectangle({x_start + size * 3 / 5, sprite_y + size / 5, size / 5, size / 10}, claw_color);
} else if (death_phase == 1) {
// More splattered
painter.fill_rectangle({x_start + size / 6, sprite_y, size * 2 / 3, size / 4}, body_color);
painter.fill_rectangle({x_start + size / 4, sprite_y + size / 8, size / 6, size / 6}, blood_color);
painter.fill_rectangle({x_start + size / 2, sprite_y + size / 10, size / 6, size / 8}, blood_color);
painter.fill_rectangle({x_start + size * 2 / 3, sprite_y + size / 12, size / 8, size / 8}, blood_color);
} else {
// Final gore puddle
painter.fill_rectangle({x_start + size / 8, sprite_y, size * 3 / 4, size / 5}, body_color);
painter.fill_rectangle({x_start + size / 6, sprite_y - size / 20, size * 2 / 3, size / 10}, blood_color);
painter.fill_rectangle({x_start + size / 4, sprite_y + size / 10, size / 5, size / 12}, blood_color);
painter.fill_rectangle({x_start + size / 2, sprite_y + size / 12, size / 4, size / 10}, blood_color);
}
} else {
// Body
painter.fill_rectangle({x_start + size / 3, y_start + size / 4, size / 3, size * 3 / 4}, body_color);
// Spine/backbone
painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 6, size / 6, size / 2}, spine_color);
// Head
painter.fill_rectangle({x_start + size * 3 / 8, y_start, size * 5 / 16, size / 3}, body_color);
// Horns
painter.fill_rectangle({x_start + size / 4, y_start - size / 6, size / 8, size / 5}, horn_color);
painter.fill_rectangle({x_start + size * 5 / 8, y_start - size / 6, size / 8, size / 5}, horn_color);
// Eyes
if (entities[i].state != 4) {
uint8_t eye_anim = (LPC_RTC->CTIME0 % 8 == 0) ? 1 : 0;
painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 12, size / 6, size / 6 - eye_anim}, eye_color);
painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 8, size / 20, size / 20}, pupil_color);
painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 12, size / 6, size / 6 - eye_anim}, eye_color);
painter.fill_rectangle({x_start + size * 17 / 32, y_start + size / 8, size / 20, size / 20}, pupil_color);
} else {
// Damaged eye
painter.fill_rectangle({x_start + size * 5 / 12, y_start + size / 12, size / 6, size / 6}, eye_color);
painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 8, size / 20, size / 20}, pupil_color);
// Blood from damaged eye
painter.fill_rectangle({x_start + size * 7 / 16, y_start + size / 6, size / 10, size / 8}, blood_color);
painter.fill_rectangle({x_start + size / 2, y_start + size / 3, size / 12, size / 12}, blood_color);
}
// Teeth/mouth
painter.fill_rectangle({x_start + size * 7 / 16, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color);
painter.fill_rectangle({x_start + size / 2, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color);
painter.fill_rectangle({x_start + size * 9 / 16, y_start + size * 5 / 16, size / 20, size / 16}, teeth_color);
// Attacking state - claws extended
if (entities[i].state == 3) {
uint8_t claw_anim = (LPC_RTC->CTIME0 % 4) / 2;
if (claw_anim == 0) {
// Claws forward
painter.fill_rectangle({x_start - size / 8, y_start + size * 5 / 12, size / 3, size / 6}, claw_color);
painter.fill_rectangle({x_start - size / 12, y_start + size / 2, size / 5, size / 4}, claw_color);
painter.fill_rectangle({x_start + size * 3 / 4, y_start + size * 5 / 12, size / 3, size / 6}, claw_color);
painter.fill_rectangle({x_start + size * 3 / 4, y_start + size / 2, size / 5, size / 4}, claw_color);
} else {
// Claws slashing
painter.fill_rectangle({x_start, y_start + size * 4 / 12, size / 4, size / 5}, claw_color);
painter.fill_rectangle({x_start + size / 12, y_start + size * 6 / 12, size / 5, size / 3}, claw_color);
painter.fill_rectangle({x_start + size * 3 / 4, y_start + size * 4 / 12, size / 4, size / 5}, claw_color);
painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 6 / 12, size / 5, size / 3}, claw_color);
}
} else {
// Normal claws
painter.fill_rectangle({x_start + size / 6, y_start + size / 2, size / 5, size / 5}, claw_color);
painter.fill_rectangle({x_start + size * 3 / 5, y_start + size / 2, size / 5, size / 5}, claw_color);
}
// Legs animation
uint8_t leg_anim = (LPC_RTC->CTIME0 % 4) / 2;
if (entities[i].state == 0 && leg_anim == 0) {
// Walking animation frame 1
painter.fill_rectangle({x_start + size / 4, y_start + size * 5 / 6, size / 6, size / 5}, body_color);
painter.fill_rectangle({x_start + size / 3, y_start + size * 11 / 12, size / 10, size / 10}, spine_color);
painter.fill_rectangle({x_start + size * 7 / 12, y_start + size * 3 / 4, size / 6, size / 4}, body_color);
painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 5 / 6, size / 10, size / 5}, spine_color);
} else {
// Walking animation frame 2
painter.fill_rectangle({x_start + size / 4, y_start + size * 3 / 4, size / 6, size / 4}, body_color);
painter.fill_rectangle({x_start + size / 3, y_start + size * 5 / 6, size / 10, size / 5}, spine_color);
painter.fill_rectangle({x_start + size * 7 / 12, y_start + size * 5 / 6, size / 6, size / 5}, body_color);
painter.fill_rectangle({x_start + size * 2 / 3, y_start + size * 11 / 12, size / 10, size / 10}, spine_color);
}
// Injury effects for damaged state
if (entities[i].state == 4) {
painter.fill_rectangle({x_start + size / 2, y_start + size / 3, size / 10, size / 8}, blood_color);
painter.fill_rectangle({x_start + size * 5 / 12, y_start + size * 7 / 12, size / 8, size / 8}, blood_color);
painter.fill_rectangle({x_start + size * 3 / 5, y_start + size / 2, size / 10, size / 10}, blood_color);
}
}
}
}
void render_map(Painter& painter, bool full_clear, int16_t x_start = 0, int16_t x_end = SCREEN_WIDTH) {
if (full_clear) {
painter.fill_rectangle({0, 0, SCREEN_WIDTH, RENDER_HEIGHT / 2}, Color(64, 64, 128));
painter.fill_rectangle({0, RENDER_HEIGHT / 2, SCREEN_WIDTH, RENDER_HEIGHT / 2}, Color(32, 32, 32));
}
for (uint8_t x = x_start; x < x_end; x += RES_DIVIDER) {
double camera_x = 2 * (double)x / SCREEN_WIDTH - 1;
double ray_x = player.dir.x + player.plane.x * camera_x;
double ray_y = player.dir.y + player.plane.y * camera_x;
if (fabs(ray_x) < 0.00001) ray_x = 0.00001;
if (fabs(ray_y) < 0.00001) ray_y = 0.00001;
uint8_t map_x = (uint8_t)player.pos.x;
uint8_t map_y = (uint8_t)player.pos.y;
double delta_x = fabs(1 / ray_x);
double delta_y = fabs(1 / ray_y);
int8_t step_x, step_y;
double side_x, side_y;
if (ray_x < 0) {
step_x = -1;
side_x = (player.pos.x - map_x) * delta_x;
} else {
step_x = 1;
side_x = (map_x + 1.0 - player.pos.x) * delta_x;
}
if (ray_y < 0) {
step_y = -1;
side_y = (player.pos.y - map_y) * delta_y;
} else {
step_y = 1;
side_y = (map_y + 1.0 - player.pos.y) * delta_y;
}
uint8_t depth = 0;
bool hit = false;
bool side = false;
double perpWallDist = 0.0;
bool close_wall_handled = false;
uint8_t current_block = get_block_at(map_x, map_y);
if (current_block == 0xF) {
hit = true;
perpWallDist = 0.1;
close_wall_handled = true;
} else {
uint8_t check_blocks[4][3] = {
{(uint8_t)(map_x + 1), map_y, 0},
{(uint8_t)(map_x - 1), map_y, 0},
{map_x, (uint8_t)(map_y + 1), 1},
{map_x, (uint8_t)(map_y - 1), 1}};
for (int i = 0; i < 4; i++) {
if (get_block_at(check_blocks[i][0], check_blocks[i][1]) == 0xF) {
double dist_to_wall;
if (check_blocks[i][2] == 0) {
dist_to_wall = (check_blocks[i][0] > map_x) ? check_blocks[i][0] - player.pos.x : player.pos.x - check_blocks[i][0];
} else {
dist_to_wall = (check_blocks[i][1] > map_y) ? check_blocks[i][1] - player.pos.y : player.pos.y - check_blocks[i][1];
}
if (dist_to_wall < 0.2) {
double dot_product;
if (check_blocks[i][2] == 0) {
double wall_normal_x = (check_blocks[i][0] > map_x) ? -1.0 : 1.0;
dot_product = ray_x * wall_normal_x;
} else {
double wall_normal_y = (check_blocks[i][1] > map_y) ? -1.0 : 1.0;
dot_product = ray_y * wall_normal_y;
}
if (dot_product < 0) {
hit = true;
side = (check_blocks[i][2] == 1);
perpWallDist = fmax(0.1, dist_to_wall);
close_wall_handled = true;
break;
}
}
}
}
}
if (!close_wall_handled) {
while (!hit && depth < MAX_RENDER_DEPTH) {
if (side_x < side_y) {
side_x += delta_x;
map_x += step_x;
side = false;
} else {
side_y += delta_y;
map_y += step_y;
side = true;
}
uint8_t block = get_block_at(map_x, map_y);
if (block == 0xF) {
hit = true;
if (side == false) {
perpWallDist = (map_x - player.pos.x + (1 - step_x) / 2) / ray_x;
} else {
perpWallDist = (map_y - player.pos.y + (1 - step_y) / 2) / ray_y;
}
}
depth++;
}
}
if (perpWallDist <= 0) perpWallDist = 0.1;
int start_y = 0;
int end_y = RENDER_HEIGHT;
if (hit) {
uint8_t line_height = fmin(255, RENDER_HEIGHT / perpWallDist);
start_y = view_height / perpWallDist - line_height / 2 + RENDER_HEIGHT / 2;
end_y = view_height / perpWallDist + line_height / 2 + RENDER_HEIGHT / 2;
if (start_y < 0) start_y = 0;
if (end_y > RENDER_HEIGHT) end_y = RENDER_HEIGHT;
uint8_t brightness = fmax(64, 255 - (perpWallDist * 20));
uint8_t noise = ((map_x * 17 + map_y * 23) & 0x0F);
Color wall_color;
if (!side) {
wall_color = Color(brightness / 4, brightness - noise, brightness / 4);
} else {
wall_color = Color(brightness / 5, (brightness - noise) * 0.8, brightness / 5);
}
painter.fill_rectangle({x, start_y, RES_DIVIDER, end_y - start_y}, wall_color);
}
if (!full_clear && hit) {
if (start_y > 0) {
painter.fill_rectangle({x, 0, RES_DIVIDER, start_y}, Color(64, 64, 128));
}
if (end_y < RENDER_HEIGHT) {
painter.fill_rectangle({x, end_y, RES_DIVIDER, RENDER_HEIGHT - end_y}, Color(32, 32, 32));
}
}
}
}
DoomView::DoomView(NavigationView& nav)
: nav_{nav} {
add_children({&dummy});
game_timer.attach(&game_timer_check, 1.0 / 60.0);
}
void DoomView::on_show() {
dummy.focus();
}
void DoomView::paint(Painter& painter) {
if (!initialized) {
initialized = true;
std::srand(LPC_RTC->CTIME0);
scene = 0;
up = down = left = right = fired = false;
jogging = view_height = 0;
gun_pos = GUN_TARGET_POS;
gun_fired = false;
num_entities = 0;
kills = 0;
prev_gun_x = 0;
prev_gun_y = 0;
painter.fill_rectangle({0, 0, SCREEN_WIDTH, SCREEN_HEIGHT}, pp_colors[COLOR_BACKGROUND]);
needs_redraw = true;
needs_gun_redraw = false;
}
if (scene == 0) {
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
painter.draw_string({50, 40}, style_yellow, "* * * DOOM * * *");
auto style_green = *ui::Theme::getInstance()->fg_green;
painter.draw_string({15, 240}, style_green, "** PRESS SELECT TO START **");
} else if (scene == 1) {
if (needs_redraw || (needs_gun_redraw && jogging > 0)) {
bool full_clear = (player.velocity == 0 || !prev_velocity_moving);
render_map(painter, full_clear);
render_entities(painter);
render_gun(painter, gun_pos, jogging);
painter.fill_rectangle({0, RENDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - RENDER_HEIGHT}, pp_colors[COLOR_BACKGROUND]);
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_red = *ui::Theme::getInstance()->fg_red;
auto style_blue = *ui::Theme::getInstance()->fg_blue;
painter.draw_string({5, RENDER_HEIGHT + 5}, style_yellow, "Health: " + std::to_string(player.health));
painter.draw_string({100, RENDER_HEIGHT + 5}, style_red, "Kills: " + std::to_string(kills));
painter.draw_string({170, RENDER_HEIGHT + 5}, style_blue, "Ammo: " + std::to_string(player.ammo));
prev_velocity_moving = (player.velocity != 0);
needs_redraw = false;
needs_gun_redraw = false;
} else if (needs_gun_redraw) {
int current_x = HALF_WIDTH - GUN_WIDTH / 2 + sin(LPC_RTC->CTIME0 * JOGGING_SPEED) * 10 * jogging;
int current_y = RENDER_HEIGHT - gun_pos - 29 + fabs(cos(LPC_RTC->CTIME0 * JOGGING_SPEED)) * 8 * jogging;
int flash_height = (gun_pos > GUN_TARGET_POS) ? (gun_pos - GUN_TARGET_POS) * 2 + 10 : 0;
int flash_width = (gun_pos > GUN_TARGET_POS) ? GUN_WIDTH + (gun_pos - GUN_TARGET_POS) * 2 : GUN_WIDTH;
int min_x = fmin(current_x, prev_gun_x) - 10;
int min_y = fmin(current_y, prev_gun_y) - flash_height - 10;
int max_x = fmax(current_x, prev_gun_x) + flash_width + 10;
int max_y = fmax(current_y, prev_gun_y) + GUN_HEIGHT + 10;
min_x = fmax(0, min_x);
min_y = fmax(0, min_y);
max_x = fmin(SCREEN_WIDTH, max_x);
max_y = fmin(RENDER_HEIGHT, max_y);
render_map(painter, false, min_x, max_x);
render_entities(painter);
render_gun(painter, gun_pos, jogging);
painter.fill_rectangle({0, RENDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - RENDER_HEIGHT}, pp_colors[COLOR_BACKGROUND]);
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_red = *ui::Theme::getInstance()->fg_red;
auto style_blue = *ui::Theme::getInstance()->fg_blue;
painter.draw_string({5, RENDER_HEIGHT + 5}, style_yellow, "Health: " + std::to_string(player.health));
painter.draw_string({100, RENDER_HEIGHT + 5}, style_red, "Kills: " + std::to_string(kills));
painter.draw_string({170, RENDER_HEIGHT + 5}, style_blue, "Ammo: " + std::to_string(player.ammo));
needs_gun_redraw = false;
}
}
}
void DoomView::frame_sync() {
if (scene == 1) {
update_game();
if (needs_redraw || needs_gun_redraw) set_dirty();
}
}
bool DoomView::on_key(const KeyEvent key) {
if (key == KeyEvent::Select && scene == 0) {
scene = 1;
initialize_level();
needs_redraw = true;
set_dirty();
return true;
}
if (scene == 1) {
if (player.health > 0) {
if (key == KeyEvent::Up) {
up = true;
needs_redraw = true;
set_dirty();
return true;
}
if (key == KeyEvent::Down) {
down = true;
needs_redraw = true;
set_dirty();
return true;
}
if (key == KeyEvent::Left) {
left = true;
needs_redraw = true;
set_dirty();
return true;
}
if (key == KeyEvent::Right) {
right = true;
needs_redraw = true;
set_dirty();
return true;
}
if (key == KeyEvent::Select) {
fired = true;
set_dirty();
return true;
}
} else if (key == KeyEvent::Select) {
scene = 0;
initialized = false;
needs_redraw = true;
set_dirty();
return true;
}
}
return false;
}
} // namespace ui::external_app::doom