1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-14 00:29:35 +00:00
8bitworkshop/presets/sms-sms-libcv/climber.c
2019-08-06 23:27:06 -04:00

522 lines
12 KiB
C

#include <stdlib.h>
#include <string.h>
#include <cv.h>
#include <cvu.h>
#include "common.h"
//#link "common.c"
//#link "chr_generic.c"
extern const unsigned char CHR_GENERIC[8192];
#define XOFS -16 // sprite horiz. offset
#define LADDER_DELTA_X 8
#define BGCOL CV_COLOR_BLUE
#define CH_BORDER 0x8f
#define CH_FLOOR 0xf4
#define CH_LADDER 0xd4
#define CH_ITEM 0xc8
#define CH_PLAYER 0xd8
/*{pal:222,n:16}*/
const char PALETTE0[16] = {
0x30, 0x38, 0x3E, 0x3F,
0x30, 0x38, 0x3E, 0x3F,
0x30, 0x38, 0x3E, 0x3F,
0x30, 0x38, 0x3E, 0x3F,
};
/*{pal:222,n:16}*/
const char PALETTE1[16] = {
0x30, 0x00, 0x3F, 0x03,
0x30, 0x38, 0x3E, 0x3F,
0x30, 0x38, 0x3E, 0x3F,
0x30, 0x38, 0x3E, 0x3F,
};
#define NUM_SPRITE_PATTERNS 5
#define NUM_SPRITE_STATES 4
///
typedef struct Level {
byte ypos;
byte height; // TODO: why does bitmask not work?
byte gap:4;
byte ladder1:4;
byte ladder2:4;
byte objtype:4;
byte objpos:4;
} Level;
#define MAX_LEVELS 32
#define GAPSIZE 3
Level levels[MAX_LEVELS];
bool is_in_gap(byte x, byte gap) {
if (gap) {
byte x1 = gap*16 + 26 - XOFS;
return (x > x1 && x < x1+GAPSIZE*8-4);
} else {
return false;
}
}
bool ladder_in_gap(byte x, byte gap) {
return gap && x >= gap && x < gap+GAPSIZE*2;
}
void make_levels() {
byte i;
byte y=0;
Level* prevlev = &levels[0];
for (i=0; i<MAX_LEVELS; i++) {
Level* lev = &levels[i];
lev->height = rndint(4,7);
lev->ladder1 = rndint(1,14);
lev->ladder2 = rndint(1,14);
do {
lev->gap = i>0 ? rndint(0,13) : 0;
} while (ladder_in_gap(prevlev->ladder1, lev->gap) ||
ladder_in_gap(prevlev->ladder2, lev->gap));
lev->objtype = rndint(1,2);
lev->objpos = rndint(1,14);
lev->ypos = y;
y += lev->height;
prevlev = lev;
}
// top level is special
levels[MAX_LEVELS-1].height = 15;
levels[MAX_LEVELS-1].gap = 0;
levels[MAX_LEVELS-1].ladder1 = 0;
levels[MAX_LEVELS-1].ladder2 = 0;
levels[MAX_LEVELS-1].objtype = 0;
}
static byte scroll_y = 0;
void create_actors_on_level(byte i);
void draw_level_line(byte screen_y) {
char buf[COLS*2];
byte i;
byte y = screen_y + scroll_y;
for (i=0; i<MAX_LEVELS; i++) {
Level* lev = &levels[i];
byte dy = y - lev->ypos;
// is this level visible on-screen?
if (dy < lev->height) {
if (dy == 0) {
// draw floor
memset(buf, CH_FLOOR, sizeof(buf));
// draw the gap
if (lev->gap)
memset(buf+lev->gap*2, 0, GAPSIZE*2);
} else {
// draw empty space
memset(buf, 0, sizeof(buf));
// draw walls
if (i < MAX_LEVELS-1) {
buf[0] = CH_FLOOR;
buf[COLS*2-1] = CH_FLOOR;
}
// draw ladders
if (lev->ladder1) {
buf[lev->ladder1*4] = CH_LADDER;
buf[lev->ladder1*4+2] = CH_LADDER+2;
}
if (lev->ladder2) {
buf[lev->ladder2*4] = CH_LADDER;
buf[lev->ladder2*4+2] = CH_LADDER+2;
}
}
// draw object, if it exists
if (lev->objtype) {
byte ch = lev->objtype*4 + CH_ITEM;
if (dy == 1) {
buf[lev->objpos*2] = ch+1;
buf[lev->objpos*2+2] = ch+3;
}
else if (dy == 2) {
buf[lev->objpos*2] = ch;
buf[lev->objpos*2+2] = ch+2;
}
}
// copy line to screen buffer
cvu_memtovmemcpy(IMAGE + COLS*2*(ROWS-1) - COLS*2*screen_y, buf, sizeof(buf));
// create actors on this level, if needed
// (only when drawing top and bottom of screen)
if (screen_y == 0 || screen_y == ROWS-1)
create_actors_on_level(i);
break;
}
}
}
void draw_screen() {
byte y;
for (y=0; y<ROWS; y++) {
draw_level_line(y);
}
}
word get_floor_yy(byte level) {
return levels[level].ypos * 8 + 8;
}
word get_ceiling_yy(byte level) {
return (levels[level].ypos + levels[level].height) * 8 + 8;
}
#define MAX_ACTORS 5
typedef enum ActorState {
INACTIVE, WALKING, CLIMBING, JUMPING, FALLING
};
typedef struct Actor {
word yy;
byte x;
byte name;
byte color1:4;
byte color2:4;
byte level;
byte state:4;
byte dir:1;
byte onscreen:1;
union {
struct {
sbyte yvel;
sbyte xvel;
} jumping;
} u;
} Actor;
Actor actors[MAX_ACTORS];
void create_actors_on_level(byte level_index) {
byte actor_index = (level_index % (MAX_ACTORS-1)) + 1;
struct Actor* a = &actors[actor_index];
if (!a->onscreen) {
Level *level = &levels[level_index];
a->state = WALKING;
a->color1 = level->ladder1;
a->color2 = level->ladder2;
a->name = CH_PLAYER;
a->x = level->ladder1 ^ (level->ladder2<<3) ^ (level->gap<<6);
a->yy = get_floor_yy(level_index);
a->level = level_index;
}
}
void draw_actor(byte i) {
struct Actor* a = &actors[i];
struct cvu_sprite4 sprite;
int screen_y = 168 - a->yy + scroll_y*8;
if (screen_y > 192+8 || screen_y < -18) {
a->onscreen = 0;
return; // offscreen vertically
}
sprite.name = a->name;
switch (a->state) {
case INACTIVE:
a->onscreen = 0;
return; // inactive, offscreen
case WALKING:
sprite.name += (a->x & 4) ? 12 : 16;
break;
case JUMPING:
sprite.name += 4;
break;
case FALLING:
sprite.name += 24;
break;
case CLIMBING:
// TODO: animation
sprite.name += 20;
break;
}
sprite.x = a->x;
sprite.y = screen_y;
//sprite.tag = a->color1 | 0x80;
sprite.x += XOFS;
cvu_set_sprite4(SPRITES, i*2, &sprite);
//sprite.tag ^= a->color1 ^ a->color2;
sprite.x += 8;
sprite.name += 2;
cvu_set_sprite4(SPRITES, i*2+1, &sprite);
a->onscreen = 1;
}
void refresh_actors() {
byte i;
for (i=0; i<MAX_ACTORS; i++)
draw_actor(i);
}
void refresh_screen() {
wait_vsync();
draw_screen();
refresh_actors();
}
byte is_ladder_close(byte actor_x, byte ladder_pos) {
byte ladder_x;
if (ladder_pos == 0)
return 0;
ladder_x = ladder_pos * 16 + LADDER_DELTA_X;
return ((byte)(actor_x - ladder_x) < 16) ? ladder_x : 0;
}
byte get_closest_ladder(byte player_x, byte level_index) {
Level* level = &levels[level_index];
byte x;
if (level_index >= MAX_LEVELS) return 0;
x = is_ladder_close(player_x, level->ladder1);
if (x) return x;
x = is_ladder_close(player_x, level->ladder2);
if (x) return x;
return 0;
}
byte mount_ladder(Actor* player, signed char level_adjust) {
byte x = get_closest_ladder(player->x, player->level + level_adjust);
if (x) {
player->x = x + 8;
player->state = CLIMBING;
player->level += level_adjust;
return 1;
} else
return 0;
}
void check_scroll_up() {
byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos
if (player_screen_y < 192/2-4) {
scroll_y++;
refresh_screen();
check_scroll_up();
}
}
void check_scroll_down() {
byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos
if (player_screen_y > 192/2+4 && scroll_y > 0) {
scroll_y--;
refresh_screen();
check_scroll_down();
}
}
void fall_down(struct Actor* actor) {
actor->level--;
actor->state = FALLING;
actor->u.jumping.xvel = 0;
actor->u.jumping.yvel = 0;
}
void move_actor(struct Actor* actor, byte joystick, bool scroll) {
switch (actor->state) {
case WALKING:
// left/right has priority over climbing
if (joystick & CV_FIRE_0) {
actor->state = JUMPING;
actor->u.jumping.xvel = 0;
actor->u.jumping.yvel = 15;
if (joystick & CV_LEFT) actor->u.jumping.xvel = -1;
if (joystick & CV_RIGHT) actor->u.jumping.xvel = 1;
} else if (joystick & CV_LEFT) {
actor->x--;
actor->dir = 1;
} else if (joystick & CV_RIGHT) {
actor->x++;
actor->dir = 0;
} else if (joystick & CV_UP) {
mount_ladder(actor, 0); // state -> CLIMBING
if (scroll) check_scroll_up();
} else if (joystick & CV_DOWN) {
mount_ladder(actor, -1); // state -> CLIMBING, level -= 1
if (scroll) check_scroll_down();
}
break;
case CLIMBING:
if (joystick & CV_UP) {
if (actor->yy >= get_ceiling_yy(actor->level)) {
actor->level++;
actor->state = WALKING;
if (scroll) check_scroll_up();
} else {
actor->yy++;
}
} else if (joystick & CV_DOWN) {
if (actor->yy <= get_floor_yy(actor->level)) {
actor->state = WALKING;
if (scroll) check_scroll_down();
} else {
actor->yy--;
}
}
break;
case JUMPING:
case FALLING:
actor->x += actor->u.jumping.xvel;
actor->yy += actor->u.jumping.yvel/4;
actor->u.jumping.yvel -= 1;
if (actor->yy <= get_floor_yy(actor->level)) {
actor->yy = get_floor_yy(actor->level);
actor->state = WALKING;
if (scroll) check_scroll_down();
}
break;
}
// don't allow player to travel past left/right edges of screen
if (actor->x == 0) actor->x = 255; // we wrapped around right edge
if (actor->x < 24) actor->x = 24;
// if player lands in a gap, they fall (switch to JUMPING state)
if (actor->state == WALKING &&
is_in_gap(actor->x, levels[actor->level].gap)) {
fall_down(actor);
}
}
void pickup_object(Actor* actor) {
Level* level = &levels[actor->level];
byte objtype = level->objtype;
if (objtype && actor->state == WALKING) {
byte objx = level->objpos * 16 + 24 - XOFS;
if (actor->x >= objx && actor->x < objx+16) {
level->objtype = 0;
refresh_screen();
}
}
}
void move_player() {
struct cv_controller_state ctrl;
cv_get_controller_state(&ctrl, 0);
move_actor(&actors[0], ctrl.joystick, true);
pickup_object(&actors[0]);
}
inline byte iabs(int x) {
return x >= 0 ? x : -x;
}
bool check_collision(Actor* a) {
byte i;
for (i=1; i<MAX_ACTORS; i++) {
Actor* b = &actors[i];
// actors must be on same level
// no need to apply XOFS because both sprites are offset
if (a->level == b->level &&
iabs(a->yy - b->yy) < 8 &&
iabs(a->x - b->x) < 8) {
return true;
}
}
return false;
}
///
void preview_stage() {
scroll_y = levels[MAX_LEVELS-1].ypos;
while (scroll_y > 0) {
wait_vsync();
refresh_screen();
refresh_actors();
scroll_y--;
}
}
void draw_blimp(struct cvu_sprite4* sprite) {
sprite->name = 48;
wait_vsync();
cvu_set_sprite4(SPRITES, 28, sprite);
sprite->name += 4;
sprite->x += 16;
cvu_set_sprite4(SPRITES, 29, sprite);
sprite->name += 4;
sprite->x += 16;
cvu_set_sprite4(SPRITES, 30, sprite);
sprite->name += 4;
sprite->x += 16;
cvu_set_sprite4(SPRITES, 31, sprite);
refresh_actors();
}
void blimp_pickup_scene() {
struct cvu_sprite4 sprite;
byte player_screen_y = cvu_vinb(SPRITES + 0); // sprite Y pos
sprite.x = actors[0].x-14;
sprite.y = 240;
//sprite.tag = 0x8f;
while (sprite.y != player_screen_y-16) {
draw_blimp(&sprite);
sprite.x -= 48;
sprite.y++;
}
while (sprite.y != 240) {
draw_blimp(&sprite);
sprite.x -= 48;
sprite.y--;
actors[0].yy++;
}
}
void play_scene() {
byte i;
memset(actors, 0, sizeof(actors));
actors[0].state = WALKING;
actors[0].color1 = 0xf;
actors[0].color2 = 0xb;
actors[0].name = CH_PLAYER;
actors[0].x = 64;
actors[0].yy = 8;
actors[0].level = 0;
create_actors_on_level(2);
refresh_screen();
while (actors[0].level != MAX_LEVELS-1) {
wait_vsync();
refresh_actors();
move_player();
// move all the actors
for (i=1; i<MAX_ACTORS; i++) {
move_actor(&actors[i], rand(), false);
}
// see if the player hit another actor
if (cv_get_sprite_collission()) {
if (actors[0].level > 0 && check_collision(&actors[0])) {
fall_down(&actors[0]);
}
}
}
blimp_pickup_scene();
}
void setup_graphics() {
cvu_memtovmemcpy(PATTERN, CHR_GENERIC, sizeof(CHR_GENERIC));
cvu_memtocmemcpy(0xc000, PALETTE0, 16);
cvu_memtocmemcpy(0xc010, PALETTE1, 16);
}
void main() {
vdp_setup();
setup_graphics();
cv_set_screen_active(true);
cv_set_vint_handler(&vint_handler);
make_levels();
play_scene();
}