mirror of
https://github.com/luksamuk/engine-psx.git
synced 2025-04-28 13:28:02 +03:00
1047 lines
32 KiB
C
1047 lines
32 KiB
C
#include "screens/level.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <inline_c.h>
|
|
#include "util.h"
|
|
#include "player.h"
|
|
#include "camera.h"
|
|
#include "render.h"
|
|
#include "sound.h"
|
|
#include "input.h"
|
|
#include "screen.h"
|
|
#include "level.h"
|
|
#include "timer.h"
|
|
#include "model.h"
|
|
#include "object.h"
|
|
#include "parallax.h"
|
|
#include "basic_font.h"
|
|
#include "demo.h"
|
|
|
|
extern int debug_mode;
|
|
|
|
static uint8_t level = 0;
|
|
static PlayerCharacter level_character = CHARA_SONIC;
|
|
|
|
// Accessible in other source
|
|
Player player;
|
|
uint8_t paused = 0;
|
|
TileMap16 map16;
|
|
TileMap128 map128;
|
|
LevelData leveldata;
|
|
Camera camera;
|
|
ObjectTable obj_table_common;
|
|
uint8_t level_fade;
|
|
uint8_t level_ring_count;
|
|
uint32_t level_score_count;
|
|
uint8_t level_finished;
|
|
int32_t level_water_y;
|
|
LEVELMODE level_mode;
|
|
|
|
|
|
// Forward function declarations
|
|
static void level_load_player(PlayerCharacter character);
|
|
static void level_load_level();
|
|
static void level_set_clearcolor();
|
|
static void level_play_music(uint8_t round, uint8_t act);
|
|
|
|
typedef struct {
|
|
uint8_t level_transition;
|
|
Parallax parallax;
|
|
uint8_t parallax_tx_mode;
|
|
int32_t parallax_px;
|
|
int32_t parallax_py;
|
|
int32_t parallax_cx;
|
|
int32_t parallax_cy;
|
|
const char *level_name;
|
|
uint8_t level_round;
|
|
uint8_t level_act;
|
|
uint16_t level_counter;
|
|
|
|
// Title card variables
|
|
int16_t tc_ribbon_y;
|
|
int16_t tc_title_x;
|
|
int16_t tc_zone_x;
|
|
int16_t tc_act_x;
|
|
|
|
int16_t tc_ribbon_tgt_y;
|
|
int16_t tc_title_tgt_x;
|
|
int16_t tc_zone_tgt_x;
|
|
int16_t tc_act_tgt_x;
|
|
|
|
// Water overlay primitives
|
|
TILE waterquad[2];
|
|
POLY_FT4 wavequad[2][5];
|
|
uint8_t waterbuffer;
|
|
uint8_t water_last_fade[2];
|
|
} screen_level_data;
|
|
|
|
void
|
|
screen_level_load()
|
|
{
|
|
screen_level_data *data = screen_alloc(sizeof(screen_level_data));
|
|
data->level_transition = 0;
|
|
data->level_name = "PLAYGROUND";
|
|
data->level_act = 0;
|
|
|
|
camera_init(&camera);
|
|
|
|
level_load_player(level_character);
|
|
level_load_level(data);
|
|
camera_set(&camera, player.pos.vx, player.pos.vy);
|
|
|
|
reset_elapsed_frames();
|
|
pause_elapsed_frames();
|
|
|
|
level_fade = 0;
|
|
data->level_counter = 120;
|
|
|
|
level_ring_count = 0;
|
|
level_finished = 0;
|
|
|
|
// Init water quads
|
|
for(int i = 0; i < 2; i++) {
|
|
TILE *poly = &data->waterquad[i];
|
|
setTile(poly);
|
|
setSemiTrans(poly, 1);
|
|
setRGB0(poly, 0, 0, 0);
|
|
setXY0(poly, 0, 0);
|
|
setWH(poly, SCREEN_XRES, 0);
|
|
|
|
for(int j = 0; j < 5; j++) {
|
|
POLY_FT4 *tx = &data->wavequad[i][j];
|
|
setPolyFT4(tx);
|
|
setSemiTrans(tx, 1);
|
|
tx->tpage = getTPage(1, 0, 576, 0);
|
|
tx->clut = getClut(0, 481);
|
|
setRGB0(tx, 0, 0, 0);
|
|
setXYWH(tx, j * 64, 0, 64, 9);
|
|
}
|
|
}
|
|
data->waterbuffer = 0;
|
|
data->water_last_fade[0] = 0;
|
|
data->water_last_fade[1] = 0;
|
|
|
|
// Init water wave quads
|
|
|
|
demo_init();
|
|
|
|
// init proper RNG per level
|
|
srand(get_global_frames());
|
|
|
|
// If it is a demo or we're recording, skip title card
|
|
if(level_mode == LEVEL_MODE_DEMO
|
|
|| level_mode == LEVEL_MODE_RECORD) {
|
|
data->level_transition = 1;
|
|
}
|
|
|
|
// Recover control if mode is "hold forward"
|
|
if(level_mode == LEVEL_MODE_FINISHED)
|
|
level_mode = LEVEL_MODE_NORMAL;
|
|
}
|
|
|
|
void
|
|
screen_level_unload(void *)
|
|
{
|
|
level_fade = 0;
|
|
sound_stop_xa();
|
|
level_reset();
|
|
sound_reset_mem();
|
|
screen_free();
|
|
}
|
|
|
|
void
|
|
screen_level_update(void *d)
|
|
{
|
|
screen_level_data *data = (screen_level_data *)d;
|
|
|
|
// Debug mode cycling
|
|
{
|
|
if(pad_pressing(PAD_L1) && pad_pressed(PAD_R1))
|
|
debug_mode++;
|
|
else if(pad_pressed(PAD_L1) && pad_pressing(PAD_R1))
|
|
debug_mode--;
|
|
if(debug_mode > 2) debug_mode = 0;
|
|
else if(debug_mode < 0) debug_mode = 2;
|
|
}
|
|
|
|
level_set_clearcolor();
|
|
|
|
// Manage fade in and fade out.
|
|
// States:
|
|
// 0: Showing title card
|
|
// 1: Fade in
|
|
// 2: Gameplay (differentiate if level is finished with level_finished global)
|
|
// 3: Fade out
|
|
// 4: Go to next level (managed by goal sign object)
|
|
if(data->level_transition == 0) { // Show title card
|
|
data->level_counter--;
|
|
if(data->level_counter == 0)
|
|
data->level_transition = 1;
|
|
} else if(data->level_transition == 1) { // Fade in
|
|
level_fade += 2;
|
|
if(level_fade >= 128) {
|
|
level_fade = 128;
|
|
data->level_transition = 2;
|
|
|
|
// Start level timer
|
|
reset_elapsed_frames();
|
|
}
|
|
} else if(data->level_transition == 3) { // Fade out
|
|
level_fade -= 2;
|
|
if(level_fade == 0) {
|
|
data->level_transition = 4;
|
|
}
|
|
}
|
|
|
|
// Manage title card depending on level transition
|
|
{
|
|
const uint16_t speed = 16;
|
|
if(data->level_transition == 0) {
|
|
data->tc_ribbon_y += speed;
|
|
data->tc_title_x -= speed;
|
|
data->tc_zone_x -= speed;
|
|
data->tc_act_x -= speed;
|
|
|
|
if(data->tc_ribbon_y > data->tc_ribbon_tgt_y)
|
|
data->tc_ribbon_y = data->tc_ribbon_tgt_y;
|
|
if(data->tc_title_x < data->tc_title_tgt_x)
|
|
data->tc_title_x = data->tc_title_tgt_x;
|
|
if(data->tc_zone_x < data->tc_zone_tgt_x)
|
|
data->tc_zone_x = data->tc_zone_tgt_x;
|
|
if(data->tc_act_x < data->tc_act_tgt_x)
|
|
data->tc_act_x = data->tc_act_tgt_x;
|
|
} else if(data->level_transition == 1) {
|
|
data->tc_ribbon_y -= speed;
|
|
data->tc_title_x += speed;
|
|
data->tc_zone_x += speed;
|
|
data->tc_act_x += speed;
|
|
}
|
|
}
|
|
|
|
// Toggle pause. But only if not playing a demo!
|
|
if(level_mode != LEVEL_MODE_DEMO) {
|
|
if(pad_pressed(PAD_START)
|
|
&& !level_finished
|
|
&& (data->level_transition == 2)) {
|
|
paused = !paused;
|
|
if(paused) sound_xa_set_volume(0x00);
|
|
else sound_xa_set_volume(XA_DEFAULT_VOLUME);
|
|
}
|
|
} else {
|
|
// If in demo mode, absolutely any button press will
|
|
// trigger its end!
|
|
uint32_t seconds = get_elapsed_frames() / 60;
|
|
if((pad_pressed_any() || (seconds >= 30))
|
|
&& (screen_level_getstate() == 2)) {
|
|
screen_level_setstate(3);
|
|
}
|
|
|
|
if(screen_level_getstate() == 4) {
|
|
// Go back to title screen
|
|
scene_change(SCREEN_TITLE);
|
|
}
|
|
}
|
|
|
|
if(paused) {
|
|
if(pad_pressed(PAD_SELECT)) {
|
|
scene_change(SCREEN_LEVELSELECT);
|
|
}
|
|
|
|
if(debug_mode) {
|
|
uint8_t updated = 0;
|
|
if(pad_pressing(PAD_UP)) {
|
|
player.pos.vy -= 40960;
|
|
updated = 1;
|
|
}
|
|
|
|
if(pad_pressing(PAD_DOWN)) {
|
|
player.pos.vy += 40960;
|
|
updated = 1;
|
|
}
|
|
|
|
if(pad_pressing(PAD_LEFT)) {
|
|
player.pos.vx -= 40960;
|
|
updated = 1;
|
|
}
|
|
|
|
if(pad_pressing(PAD_RIGHT)) {
|
|
player.pos.vx += 40960;
|
|
updated = 1;
|
|
}
|
|
|
|
if(updated) {
|
|
camera_update(&camera, &player);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if(debug_mode > 0) {
|
|
// Create a little falling ring
|
|
if(pad_pressed(PAD_TRIANGLE)) {
|
|
PoolObject *ring = object_pool_create(OBJ_RING);
|
|
ring->freepos.vx = camera.pos.vx;
|
|
ring->freepos.vy = camera.pos.vy - (CENTERY << 12) + (20 << 12);
|
|
ring->props |= OBJ_FLAG_ANIM_LOCK;
|
|
ring->props |= OBJ_FLAG_RING_MOVING;
|
|
}
|
|
|
|
// Respawn
|
|
if(pad_pressed(PAD_SELECT) && !level_finished) {
|
|
player.pos = player.respawnpos;
|
|
camera.pos = camera.realpos = player.respawnpos;
|
|
player.grnd = 0;
|
|
player.anim_dir = 1;
|
|
player.vel.vx = player.vel.vy = player.vel.vz = 0;
|
|
player.psmode = player.gsmode = CDIR_FLOOR;
|
|
player.underwater = 0;
|
|
player.cnst = getconstants(player.character, PC_DEFAULT);
|
|
player.speedshoes_frames = (player.speedshoes_frames > 0) ? 0 : -1;
|
|
}
|
|
|
|
if(pad_pressed(PAD_CIRCLE)) {
|
|
player_do_damage(&player, player.pos.vx);
|
|
}
|
|
}
|
|
|
|
// Record level demo. Uncomment to print.
|
|
switch(level_mode) {
|
|
case LEVEL_MODE_DEMO:
|
|
demo_update_playback(level, &player.input);
|
|
break;
|
|
case LEVEL_MODE_RECORD:
|
|
demo_record();
|
|
input_get_state(&player.input);
|
|
break;
|
|
case LEVEL_MODE_FINISHED:
|
|
player.input.current = player.input.old = 0x0020;
|
|
break;
|
|
default:
|
|
input_get_state(&player.input);
|
|
break;
|
|
}
|
|
|
|
camera_update(&camera, &player);
|
|
update_obj_window(&leveldata, &obj_table_common, camera.pos.vx, camera.pos.vy);
|
|
object_pool_update(&obj_table_common);
|
|
|
|
// Limit player left position
|
|
if((player.pos.vx - (PUSH_RADIUS << 12)) < (camera.min_x - (CENTERX << 12))) {
|
|
player.pos.vx = camera.min_x - (CENTERX << 12) + (PUSH_RADIUS << 12);
|
|
if(player.vel.vx < 0) {
|
|
if(player.grnd) player.vel.vz = 0;
|
|
else player.vel.vx = 0;
|
|
}
|
|
}
|
|
|
|
// Only update these if past fade in!
|
|
if(data->level_transition > 0) {
|
|
player_update(&player);
|
|
}
|
|
|
|
// If speed shoes are finished, we use the player's values
|
|
// as a flag to resume music playback.
|
|
// Player constants are managed within player update
|
|
if(player.speedshoes_frames == 0) {
|
|
if(!level_finished)
|
|
level_play_music(data->level_round, data->level_act);
|
|
player.speedshoes_frames = -1;
|
|
}
|
|
}
|
|
|
|
void
|
|
_screen_level_draw_water(screen_level_data *data)
|
|
{
|
|
if(level_water_y >= 0) {
|
|
int32_t camera_bottom = camera.pos.vy + (CENTERY << 12);
|
|
|
|
if(camera_bottom > level_water_y) {
|
|
int32_t water_vh = camera_bottom - level_water_y;
|
|
uint16_t water_rh = water_vh >> 12;
|
|
uint16_t water_h = MIN(water_rh, SCREEN_YRES);
|
|
int16_t water_y = MAX(0, SCREEN_YRES - water_h);
|
|
int16_t water_ry = SCREEN_YRES - water_rh;
|
|
|
|
// Draw water overlay
|
|
{
|
|
/* GOURAUD SHADE */
|
|
/* POLY_G4 *poly = get_next_prim(); */
|
|
/* increment_prim(sizeof(POLY_G4)); */
|
|
/* setPolyG4(poly); */
|
|
/* setXYWH(poly, 0, water_y, SCREEN_XRES, water_h); */
|
|
/* setSemiTrans(poly, 1); */
|
|
/* setRGB0(poly, */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0xb8)); */
|
|
/* setRGB1(poly, */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0xb8)); */
|
|
/* setRGB2(poly, */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0x18)); */
|
|
/* setRGB3(poly, */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0), */
|
|
/* LERPC(level_fade, 0x18)); */
|
|
/* sort_prim(poly, OTZ_LAYER_LEVEL_FG_FRONT); */
|
|
|
|
/* FLAT SHADE, STATIC BUFFERS */
|
|
TILE *poly = &data->waterquad[data->waterbuffer];
|
|
poly->y0 = water_y;
|
|
poly->h = water_h;
|
|
|
|
if(data->water_last_fade[data->waterbuffer] != level_fade) {
|
|
data->water_last_fade[data->waterbuffer] = level_fade;
|
|
setRGB0(poly,
|
|
LERPC(level_fade, 0),
|
|
LERPC(level_fade, 0),
|
|
LERPC(level_fade, 0xb8));
|
|
setRGB0(&data->waterquad[data->waterbuffer ^ 1],
|
|
LERPC(level_fade, 0),
|
|
LERPC(level_fade, 0),
|
|
LERPC(level_fade, 0xb8));
|
|
}
|
|
sort_prim(poly, OTZ_LAYER_LEVEL_FG_FRONT);
|
|
}
|
|
|
|
// Draw water waves
|
|
{
|
|
static uint8_t wave_dir = 0;
|
|
static uint8_t wave_visible = 1;
|
|
|
|
uint32_t frame = get_global_frames();
|
|
|
|
if(!(frame % 6)) wave_dir = !wave_dir;
|
|
if(!(frame % 3)) wave_visible = !wave_visible;
|
|
|
|
for(uint8_t i = 0; i < 5; i++) {
|
|
uint8_t current_visible = (i + wave_visible) % 2;
|
|
POLY_FT4 *poly = &data->wavequad[data->waterbuffer][i];
|
|
if(poly->r0 != level_fade)
|
|
setRGB0(poly, level_fade, level_fade, level_fade);
|
|
if(!current_visible)
|
|
setUVWH(poly, 0, 0, 0, 0);
|
|
else if(wave_dir)
|
|
setUVWH(poly, 146, 183, 64, 9);
|
|
else setUV4(poly,
|
|
146 + 63, 183,
|
|
146, 183,
|
|
146 + 63, 183 + 9,
|
|
146, 183 + 9);
|
|
poly->y0 = poly->y1 = water_ry - 6;
|
|
poly->y2 = poly->y3 = water_ry - 6 + 9;
|
|
sort_prim(poly, OTZ_LAYER_LEVEL_FG_FRONT);
|
|
}
|
|
}
|
|
|
|
// Flip buffers
|
|
data->waterbuffer ^= 1;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void
|
|
screen_level_draw(void *d)
|
|
{
|
|
screen_level_data *data = (screen_level_data *)d;
|
|
char buffer[255] = { 0 };
|
|
|
|
// As a rule of thumb, things are drawn in specific otz's.
|
|
// When things are drawn on the same otz, anything drawn first
|
|
// is shown on front, as the ordering table is drawn backwards.
|
|
|
|
_screen_level_draw_water(data);
|
|
|
|
// Draw player
|
|
if(abs((player.pos.vx - camera.pos.vx) >> 12) <= SCREEN_XRES
|
|
&& abs((player.pos.vy - camera.pos.vy) >> 12) <= SCREEN_YRES) {
|
|
VECTOR player_canvas_pos = {
|
|
player.pos.vx - camera.pos.vx + (CENTERX << 12),
|
|
player.pos.vy - camera.pos.vy + (CENTERY << 12),
|
|
0
|
|
};
|
|
player_draw(&player, &player_canvas_pos);
|
|
}
|
|
|
|
// Draw free objects
|
|
object_pool_render(&obj_table_common, camera.pos.vx, camera.pos.vy);
|
|
|
|
// Draw level and level objects
|
|
render_lvl(&leveldata, &map128, &map16, &obj_table_common, camera.pos.vx, camera.pos.vy);
|
|
|
|
// Draw background and parallax
|
|
if(level_get_num_sprites() < 1312)
|
|
parallax_draw(&data->parallax, &camera);
|
|
|
|
// If we're in R4, draw a gradient on the background.
|
|
if(level == 8 || level == 9) {
|
|
POLY_G4 *poly = get_next_prim();
|
|
increment_prim(sizeof(POLY_G4));
|
|
setPolyG4(poly);
|
|
setXYWH(poly, 0, 0, SCREEN_XRES, CENTERY);
|
|
setRGB0(poly,
|
|
LERPC(level_fade, 0x13),
|
|
LERPC(level_fade, 0x12),
|
|
LERPC(level_fade, 0x3c));
|
|
setRGB1(poly,
|
|
LERPC(level_fade, 0x13),
|
|
LERPC(level_fade, 0x12),
|
|
LERPC(level_fade, 0x3c));
|
|
setRGB2(poly,
|
|
LERPC(level_fade, 0x30),
|
|
LERPC(level_fade, 0x66),
|
|
LERPC(level_fade, 0xb5));
|
|
setRGB3(poly,
|
|
LERPC(level_fade, 0x30),
|
|
LERPC(level_fade, 0x66),
|
|
LERPC(level_fade, 0xb5));
|
|
sort_prim(poly, OTZ_LAYER_LEVEL_BG);
|
|
}
|
|
else if(data->level_round == 5) {
|
|
// If we're in R5, draw a dark gradient on
|
|
}
|
|
// If we're in R8, draw a gradient as well, but at a lower position.
|
|
else if(level == 16 || level == 17 || level == 18) {
|
|
POLY_G4 *poly = get_next_prim();
|
|
increment_prim(sizeof(POLY_G4));
|
|
setPolyG4(poly);
|
|
setXYWH(poly, 0, 120, SCREEN_XRES, SCREEN_YRES - 120);
|
|
setRGB0(poly, LERPC(level_fade, 0x21), 0x00, 0x00);
|
|
setRGB1(poly, LERPC(level_fade, 0x21), 0x00, 0x00);
|
|
setRGB2(poly, LERPC(level_fade, 0xbd), 0x00, LERPC(level_fade, 0xbd));
|
|
setRGB3(poly, LERPC(level_fade, 0xbd), 0x00, LERPC(level_fade, 0xbd));
|
|
sort_prim(poly, OTZ_LAYER_LEVEL_BG);
|
|
}
|
|
|
|
// Pause text
|
|
if(paused) {
|
|
const char *line1 = "Paused";
|
|
int16_t x = CENTERX - (strlen(line1) * 4);
|
|
font_set_color(
|
|
LERPC(level_fade, 0xc8),
|
|
LERPC(level_fade, 0xc8),
|
|
LERPC(level_fade, 0xc8));
|
|
font_draw_big(line1, x, CENTERY - 12);
|
|
}
|
|
|
|
// Title card
|
|
if(data->level_transition <= 1) {
|
|
font_reset_color();
|
|
font_draw_hg(data->level_name, data->tc_title_x, 70);
|
|
font_draw_hg("ZONE", data->tc_zone_x, 70 + GLYPH_HG_WHITE_HEIGHT + 5);
|
|
|
|
// ACT card
|
|
char buffer[5];
|
|
snprintf(buffer, 5, "*%d", data->level_act + 1);
|
|
font_draw_hg(buffer, data->tc_act_x, 70 + GLYPH_HG_WHITE_HEIGHT + 40);
|
|
|
|
// Game text
|
|
//font_set_color(0xc8, 0xc8, 0x00);
|
|
uint16_t wt = font_measurew_sm("SONIC XA");
|
|
font_draw_sm("SONIC XA", 50 + ((80 - wt) >> 1), data->tc_ribbon_y + 180);
|
|
font_reset_color();
|
|
|
|
// Title card ribbon background
|
|
{
|
|
POLY_G4 *polyg = get_next_prim();
|
|
increment_prim(sizeof(POLY_G4));
|
|
setPolyG4(polyg);
|
|
setRGB0(polyg, 0xfc, 0xfc, 0xfc);
|
|
setRGB1(polyg, 0xfc, 0xfc, 0xfc);
|
|
setRGB2(polyg, 0x00, 0x24, 0xd8);
|
|
setRGB3(polyg, 0x00, 0x24, 0xd8);
|
|
setXYWH(polyg, 50, data->tc_ribbon_y, 80, 200);
|
|
sort_prim(polyg, OTZ_LAYER_HUD);
|
|
|
|
POLY_F4 *polyf = get_next_prim();
|
|
increment_prim(sizeof(POLY_F4));
|
|
setPolyF4(polyf);
|
|
setRGB0(polyf, 0x1c, 0x1a, 0x1a);
|
|
setXYWH(polyf, 55, data->tc_ribbon_y, 80, 205);
|
|
sort_prim(polyf, OTZ_LAYER_HUD);
|
|
}
|
|
}
|
|
|
|
// Demo HUD. Only when playing AutoDemo!
|
|
/* if(level_mode == LEVEL_MODE_DEMO) { */
|
|
/* // Uses HUD layer to draw! */
|
|
/* font_draw_logo(20, SCREEN_YRES - 65, 120, 45); */
|
|
/* } */
|
|
|
|
// Heads-up display
|
|
if((debug_mode <= 1) && (level_mode != LEVEL_MODE_DEMO)) {
|
|
font_set_color(
|
|
LERPC(level_fade, 0xc8),
|
|
LERPC(level_fade, 0xc8),
|
|
0);
|
|
font_draw_big("SCORE", 10, 10);
|
|
font_draw_big("TIME", 10, 24);
|
|
|
|
// Flash red every 8 frames
|
|
if(level_ring_count == 0
|
|
&& ((get_elapsed_frames() >> 3) % 2 == 1)) {
|
|
font_set_color(
|
|
LERPC(level_fade, 0xc8),
|
|
0,
|
|
0);
|
|
} else {
|
|
font_set_color(
|
|
LERPC(level_fade, 0xc8),
|
|
LERPC(level_fade, 0xc8),
|
|
0);
|
|
}
|
|
font_draw_big("RINGS", 10, 38);
|
|
font_set_color(
|
|
LERPC(level_fade, 0xc8),
|
|
LERPC(level_fade, 0xc8),
|
|
LERPC(level_fade, 0xc8));
|
|
|
|
snprintf(buffer, 255, "%8d", level_score_count);
|
|
font_draw_big(buffer, 60, 10);
|
|
|
|
{
|
|
uint32_t seconds = get_elapsed_frames() / 60;
|
|
snprintf(buffer, 255, "%2d:%02d", seconds / 60, seconds % 60);
|
|
font_draw_big(buffer, 54, 24);
|
|
}
|
|
|
|
snprintf(buffer, 255, "%3d", level_ring_count);
|
|
font_draw_big(buffer, 60, 38);
|
|
|
|
font_reset_color();
|
|
}
|
|
|
|
if(debug_mode) {
|
|
font_set_color(0xc8, 0xc8, 0xc8);
|
|
|
|
// Video debug
|
|
snprintf(buffer, 255,
|
|
"%4s %3d",
|
|
GetVideoMode() == MODE_PAL ? "PAL" : "NTSC", get_frame_rate());
|
|
font_draw_sm(buffer, 248, 12);
|
|
|
|
// Sound debug
|
|
uint32_t elapsed_sectors;
|
|
sound_xa_get_elapsed_sectors(&elapsed_sectors);
|
|
snprintf(buffer, 255, "%08u", elapsed_sectors);
|
|
font_draw_sm(buffer, 248, 20);
|
|
|
|
// Free object debug
|
|
snprintf(buffer, 255, "OBJS %3d", object_pool_get_count());
|
|
font_draw_sm(buffer, 248, 28);
|
|
|
|
// Rings, time and air for convenience
|
|
snprintf(buffer, 255, "RING %03d", level_ring_count);
|
|
font_draw_sm(buffer, 248, 36);
|
|
|
|
snprintf(buffer, 255, "TIME %03d", (get_elapsed_frames() / 60));
|
|
font_draw_sm(buffer, 248, 44);
|
|
|
|
snprintf(buffer, 255, "AIR %02d", player.remaining_air_frames / 60);
|
|
font_draw_sm(buffer, 248, 52);
|
|
|
|
snprintf(buffer, 255, "SPR %4d", level_get_num_sprites());
|
|
font_draw_sm(buffer, 248, 60);
|
|
|
|
// Player debug
|
|
if(debug_mode > 1) {
|
|
snprintf(buffer, 255,
|
|
"GSP %08x\n"
|
|
"SPD %08x %08x\n"
|
|
"ANG %08x G.P %s %s %3d\n"
|
|
"POS %08x %08x\n"
|
|
"ACT %02u\n"
|
|
"GRN CEI %01u %01u\n"
|
|
,
|
|
player.vel.vz,
|
|
player.vel.vx, player.vel.vy,
|
|
player.angle,
|
|
(player.gsmode == CDIR_FLOOR)
|
|
? "FL"
|
|
: (player.gsmode == CDIR_RWALL)
|
|
? "RW"
|
|
: (player.gsmode == CDIR_LWALL)
|
|
? "LW"
|
|
: (player.gsmode == CDIR_CEILING)
|
|
? "CE"
|
|
: " ",
|
|
(player.psmode == CDIR_FLOOR)
|
|
? "FL"
|
|
: (player.psmode == CDIR_RWALL)
|
|
? "RW"
|
|
: (player.psmode == CDIR_LWALL)
|
|
? "LW"
|
|
: (player.psmode == CDIR_CEILING)
|
|
? "CE"
|
|
: " ",
|
|
(int32_t)(((int32_t)player.angle * (int32_t)(360 << 12)) >> 24), // angle in deg
|
|
player.pos.vx, player.pos.vy,
|
|
player.action,
|
|
player.grnd, player.ceil
|
|
);
|
|
font_draw_sm(buffer, 8, 12);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ============================== */
|
|
|
|
void
|
|
screen_level_setlevel(uint8_t menuchoice)
|
|
{
|
|
level = menuchoice;
|
|
}
|
|
|
|
uint8_t
|
|
screen_level_getlevel(void)
|
|
{
|
|
return level;
|
|
}
|
|
|
|
|
|
/* ============================== */
|
|
|
|
static void
|
|
level_load_player(PlayerCharacter character)
|
|
{
|
|
const char *chara_file = "\\SPRITES\\SONIC.CHARA;1";
|
|
const char *tim_file = "\\SPRITES\\SONIC.TIM;1";
|
|
|
|
switch(character) {
|
|
case CHARA_MILES:
|
|
chara_file = "\\SPRITES\\MILES.CHARA;1";
|
|
tim_file = "\\SPRITES\\MILES.TIM;1";
|
|
break;
|
|
case CHARA_KNUCKLES:
|
|
chara_file = "\\SPRITES\\KNUX.CHARA;1";
|
|
tim_file = "\\SPRITES\\KNUX.TIM;1";
|
|
break;
|
|
case CHARA_SONIC:
|
|
default: break;
|
|
}
|
|
|
|
uint32_t filelength;
|
|
TIM_IMAGE tim;
|
|
uint8_t *timfile = file_read(tim_file, &filelength);
|
|
if(timfile) {
|
|
load_texture(timfile, &tim);
|
|
free(timfile);
|
|
}
|
|
|
|
load_player(&player, character, chara_file, &tim);
|
|
player.startpos = (VECTOR){ 250 << 12, CENTERY << 12, 0 };
|
|
player.pos = player.startpos;
|
|
}
|
|
|
|
static void
|
|
level_load_level(screen_level_data *data)
|
|
{
|
|
paused = 0;
|
|
|
|
// Negative water means no water
|
|
level_water_y = -1;
|
|
|
|
data->level_name = "PLACEHOLDER";
|
|
|
|
switch(level) {
|
|
case 0: case 1: case 2: case 3: // Playground
|
|
data->level_name = "TEST LEVEL";
|
|
data->level_round = 0;
|
|
data->level_act = level;
|
|
if(level == 2) {
|
|
level_water_y = 0x00c43401;
|
|
}
|
|
break;
|
|
case 4: case 5:
|
|
data->level_name = "GREEN HILL";
|
|
data->level_round = 2;
|
|
data->level_act = level - 4;
|
|
break;
|
|
case 6: case 7:
|
|
data->level_name = "SURELY WOOD";
|
|
data->level_round = 3;
|
|
data->level_act = level - 6;
|
|
break;
|
|
case 8: case 9:
|
|
data->level_name = "DAWN CANYON";
|
|
data->level_round = 4;
|
|
data->level_act = level - 8;
|
|
break;
|
|
case 10: case 11:
|
|
data->level_name = "AMAZING OCEAN";
|
|
data->level_round = 5;
|
|
data->level_act = level - 10;
|
|
level_water_y = 0x002c0000;
|
|
break;
|
|
case 12: case 13:
|
|
/* data->level_name = "R6"; */
|
|
data->level_round = 6;
|
|
data->level_act = level - 12;
|
|
break;
|
|
case 14: case 15:
|
|
/* data->level_name = "R7"; */
|
|
data->level_round = 7;
|
|
data->level_act = level - 14;
|
|
break;
|
|
case 16: case 17: case 18:
|
|
data->level_name = "EGGMANLAND";
|
|
data->level_round = 8;
|
|
data->level_act = level - 16;
|
|
break;
|
|
case 19:
|
|
data->level_name = "WINDMILL ISLE";
|
|
data->level_round = 9;
|
|
data->level_act = level - 19;
|
|
break;
|
|
default:
|
|
data->level_name = "TEST LEVEL";
|
|
data->level_round = 0xff;
|
|
data->level_act = 0;
|
|
break;
|
|
}
|
|
|
|
char basepath[255];
|
|
char filename0[255], filename1[255];
|
|
|
|
level_set_clearcolor();
|
|
|
|
snprintf(basepath, 255, "\\LEVELS\\R%1u", data->level_round);
|
|
|
|
TIM_IMAGE tim;
|
|
uint32_t filelength;
|
|
|
|
|
|
|
|
/* === LEVEL TILES === */
|
|
// Load level tiles
|
|
snprintf(filename0, 255, "%s\\TILES.TIM;1", basepath);
|
|
printf("Loading %s...\n", filename0);
|
|
uint8_t *timfile = file_read(filename0, &filelength);
|
|
if(timfile) {
|
|
load_texture(timfile, &tim);
|
|
leveldata.clutmode = tim.mode;
|
|
free(timfile);
|
|
} else {
|
|
// If not single "TILES.TIM" was found, then perharps try a
|
|
// "TILES0.TIM" and a "TILES1.TIM".
|
|
snprintf(filename0, 255, "%s\\TILES0.TIM;1", basepath);
|
|
printf("Loading %s...\n", filename0);
|
|
timfile = file_read(filename0, &filelength);
|
|
if(timfile) {
|
|
load_texture(timfile, &tim);
|
|
leveldata.clutmode = tim.mode; // Use CLUT mode from 1st texture
|
|
free(timfile);
|
|
}
|
|
|
|
snprintf(filename0, 255, "%s\\TILES1.TIM;1", basepath);
|
|
printf("Loading %s...\n", filename0);
|
|
timfile = file_read(filename0, &filelength);
|
|
if(timfile) {
|
|
load_texture(timfile, &tim);
|
|
free(timfile);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* === PARALLAX === */
|
|
// Load level parallax textures
|
|
snprintf(filename0, 255, "%s\\BG0.TIM;1", basepath);
|
|
printf("Loading %s...\n", filename0);
|
|
timfile = file_read(filename0, &filelength);
|
|
if(timfile) {
|
|
load_texture(timfile, &tim);
|
|
|
|
// Background compression must be the same for both background
|
|
// images. Also, they must be one texture page apart (64 bytes)
|
|
// horizontally, and their clut must be vertically aligned
|
|
data->parallax_tx_mode = tim.mode;
|
|
data->parallax_px = 448;
|
|
data->parallax_py = 256;
|
|
data->parallax_cx = 0;
|
|
data->parallax_cy = 483;
|
|
|
|
free(timfile);
|
|
} else printf("Warning: Level BG0 not found, ignoring\n");
|
|
|
|
snprintf(filename0, 255, "%s\\BG1.TIM;1", basepath);
|
|
printf("Loading %s...\n", filename0);
|
|
timfile = file_read(filename0, &filelength);
|
|
if(timfile) {
|
|
load_texture(timfile, &tim);
|
|
free(timfile);
|
|
} else printf("Warning: Level BG1 not found, ignoring\n");
|
|
|
|
// Load level parallax data
|
|
snprintf(filename0, 255, "%s\\PRL.PRL;1", basepath);
|
|
printf("Loading parallax data...\n");
|
|
load_parallax(&data->parallax, filename0,
|
|
data->parallax_tx_mode,
|
|
data->parallax_px, data->parallax_py,
|
|
data->parallax_cx, data->parallax_cy);
|
|
printf("Loaded parallax strips: %d\n", data->parallax.num_strips);
|
|
|
|
|
|
/* === TILE MAPPINGS === */
|
|
snprintf(filename0, 255, "%s\\MAP16.MAP;1", basepath);
|
|
snprintf(filename1, 255, "%s\\MAP16.COL;1", basepath);
|
|
printf("Loading %s and %s...\n", filename0, filename1);
|
|
load_map16(&map16, filename0, filename1);
|
|
snprintf(filename0, 255, "%s\\MAP128.MAP;1", basepath);
|
|
printf("Loading %s...\n", filename0);
|
|
load_map128(&map128, filename0);
|
|
|
|
|
|
|
|
/* === LEVEL LAYOUT === */
|
|
snprintf(filename0, 255, "%s\\Z%1u.LVL;1", basepath, data->level_act + 1);
|
|
printf("Loading %s...\n", filename0);
|
|
load_lvl(&leveldata, filename0);
|
|
|
|
|
|
|
|
/* === OBJECTS === */
|
|
// Load common objects
|
|
printf("Loading common object texture...\n");
|
|
timfile = file_read("\\LEVELS\\COMMON\\OBJ.TIM;1", &filelength);
|
|
if(timfile) {
|
|
load_texture(timfile, &tim);
|
|
free(timfile);
|
|
}
|
|
printf("Loading common object table...\n");
|
|
load_object_table("\\LEVELS\\COMMON\\OBJ.OTD;1", &obj_table_common);
|
|
|
|
// Load level objects
|
|
// TODO
|
|
|
|
// Load object positioning on level
|
|
snprintf(filename0, 255, "%s\\Z%1u.OMP;1", basepath, data->level_act + 1);
|
|
load_object_placement(filename0, &leveldata);
|
|
|
|
|
|
/* === OBJECT POOL / FREE OBJECTS === */
|
|
object_pool_init();
|
|
|
|
|
|
/* === RENDERING PREPARATION === */
|
|
// Pre-allocate and initialize level primitive buffer
|
|
prepare_renderer(&leveldata);
|
|
|
|
level_debrief();
|
|
|
|
printf("Number of level layers: %d\n", leveldata.num_layers);
|
|
|
|
// Start playback after we don't need the CD anymore.
|
|
level_play_music(data->level_round, data->level_act);
|
|
|
|
// Pre-calculate title card target X and Y positions
|
|
{
|
|
uint16_t wt = font_measurew_hg(data->level_name);
|
|
uint16_t wz = font_measurew_hg("ZONE");
|
|
uint16_t vx = CENTERX - (wt >> 1) + 20;
|
|
|
|
data->tc_ribbon_tgt_y = 0;
|
|
data->tc_title_tgt_x = vx;
|
|
data->tc_zone_tgt_x = vx + wt - wz;
|
|
data->tc_act_tgt_x = vx + wt - 40;
|
|
|
|
data->tc_ribbon_y = -200;
|
|
data->tc_title_x = SCREEN_XRES + wt;
|
|
data->tc_zone_x = SCREEN_XRES + wt;
|
|
data->tc_act_x = SCREEN_XRES + wt;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
level_set_clearcolor()
|
|
{
|
|
if(level == 2 || level == 3) // R1
|
|
set_clear_color(LERPC(level_fade, 26),
|
|
LERPC(level_fade, 104),
|
|
LERPC(level_fade, 200));
|
|
else if(level == 4 || level == 5) // R2 (GHZ)
|
|
set_clear_color(LERPC(level_fade, 36),
|
|
LERPC(level_fade, 0),
|
|
LERPC(level_fade, 180));
|
|
else if(level == 6 || level == 7) // R3 (SWZ)
|
|
set_clear_color(0, 0, 0);
|
|
else if(level == 10 || level == 11) // R5 (AOZ)
|
|
set_clear_color(LERPC(level_fade, 56),
|
|
LERPC(level_fade, 104),
|
|
LERPC(level_fade, 200));
|
|
else if(level == 16 || level == 17 || level == 18) // R8 (EZ)
|
|
set_clear_color(LERPC(level_fade, 0x21), 0x00, 0x00);
|
|
else if(level == 19) // R9 (WIZ)
|
|
set_clear_color(0x00, LERPC(level_fade, 0xa0), LERPC(level_fade, 0xe0));
|
|
// R0
|
|
else set_clear_color(LERPC(level_fade, 63),
|
|
LERPC(level_fade, 0),
|
|
LERPC(level_fade, 127));
|
|
}
|
|
|
|
void
|
|
screen_level_setstate(uint8_t state)
|
|
{
|
|
screen_level_data *data = screen_get_data();
|
|
data->level_transition = state;
|
|
}
|
|
|
|
uint8_t
|
|
screen_level_getstate()
|
|
{
|
|
screen_level_data *data = screen_get_data();
|
|
return data->level_transition;
|
|
}
|
|
|
|
|
|
void
|
|
screen_level_setmode(LEVELMODE mode)
|
|
{
|
|
level_mode = mode;
|
|
}
|
|
|
|
void
|
|
screen_level_setcharacter(PlayerCharacter character)
|
|
{
|
|
level_character = character;
|
|
}
|
|
|
|
PlayerCharacter
|
|
screen_level_getcharacter()
|
|
{
|
|
return level_character;
|
|
}
|
|
|
|
|
|
static void
|
|
level_play_music(uint8_t round, uint8_t act)
|
|
{
|
|
switch(round) {
|
|
case 0:
|
|
switch(act) {
|
|
case 0: sound_bgm_play(BGM_PLAYGROUND1); break;
|
|
case 1: sound_bgm_play(BGM_PLAYGROUND2); break;
|
|
case 2: sound_bgm_play(BGM_PLAYGROUND3); break;
|
|
case 3: sound_bgm_play(BGM_PLAYGROUND4); break;
|
|
};
|
|
break;
|
|
case 2: sound_bgm_play(BGM_GREENHILL); break;
|
|
case 3: sound_bgm_play(BGM_SURELYWOOD); break;
|
|
case 4: sound_bgm_play(BGM_DAWNCANYON); break;
|
|
case 5: sound_bgm_play(BGM_AMAZINGOCEAN); break;
|
|
case 6: break; // TODO
|
|
case 7: break; // TODO
|
|
case 8: sound_bgm_play(BGM_EGGMANLAND); break;
|
|
case 9: sound_bgm_play(BGM_WINDMILLISLE); break;
|
|
default: break;
|
|
}
|
|
}
|