7a91a704c5
git-svn-id: svn://kolibrios.org@1806 a494cfbc-eb01-0410-851d-a64ba20cac60
1333 lines
28 KiB
C
1333 lines
28 KiB
C
/*
|
|
------------------------------------------------------------
|
|
Fixed Rate Pig - a fixed logic frame rate demo
|
|
------------------------------------------------------------
|
|
* Copyright (C) 2004 David Olofson <david@olofson.net>
|
|
*
|
|
* This software is released under the terms of the GPL.
|
|
*
|
|
* Contact author for permission if you want to use this
|
|
* software, or work derived from it, under other terms.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include "engine.h"
|
|
|
|
|
|
/* Graphics defines */
|
|
#define SCREEN_W 800
|
|
#define SCREEN_H 600
|
|
#define TILE_W 32
|
|
#define TILE_H 32
|
|
#define MAP_W 25
|
|
#define MAP_H 17
|
|
#define FONT_SPACING 45
|
|
#define PIG_FRAMES 12
|
|
|
|
/* World/physics constants */
|
|
#define GRAV_ACC 4
|
|
#define JUMP_SPEED 28
|
|
|
|
/* Sprite collision groups */
|
|
#define GROUP_ENEMY 0x0001
|
|
#define GROUP_POWERUP 0x0002
|
|
|
|
typedef enum
|
|
{
|
|
POWER_LIFE,
|
|
POWER_BONUS1,
|
|
POWER_BONUS2
|
|
} POWERUPS;
|
|
|
|
|
|
typedef struct GAMESTATE
|
|
{
|
|
/* I/O */
|
|
PIG_engine *pe;
|
|
Uint8 *keys;
|
|
int nice;
|
|
int refresh_screen;
|
|
int jump;
|
|
|
|
/* Sprites */
|
|
int lifepig;
|
|
int scorefont;
|
|
int glassfont;
|
|
int icons;
|
|
int stars;
|
|
int pigframes;
|
|
int evil;
|
|
int slime;
|
|
|
|
/* Global game state */
|
|
int running;
|
|
int level;
|
|
int lives;
|
|
float lives_wobble;
|
|
float lives_wobble_time;
|
|
int score;
|
|
float score_wobble;
|
|
float score_wobble_time;
|
|
float dashboard_time;
|
|
int fun_count;
|
|
int enemycount;
|
|
int messages;
|
|
|
|
/* Objects */
|
|
PIG_object *player;
|
|
|
|
/* Statistics */
|
|
int logic_frames;
|
|
int rendered_frames;
|
|
} GAMESTATE;
|
|
|
|
|
|
static void add_life(GAMESTATE *gs);
|
|
static void remove_life(GAMESTATE *gs);
|
|
static void inc_score(GAMESTATE *gs, int v);
|
|
static void inc_score_nobonus(GAMESTATE *gs, int v);
|
|
static PIG_object *new_player(GAMESTATE *gs);
|
|
static void message(GAMESTATE *gs, const char *s);
|
|
static PIG_object *new_powerup(GAMESTATE *gs,
|
|
int x, int y, int speed, POWERUPS type);
|
|
static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy);
|
|
static PIG_object *new_evil(GAMESTATE *gs,
|
|
int x, int y, int speed);
|
|
static PIG_object *new_slime(GAMESTATE *gs,
|
|
int x, int y, int speed);
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Init, load stuff etc
|
|
----------------------------------------------------------*/
|
|
|
|
static int load_level(GAMESTATE *gs, int map)
|
|
{
|
|
const char *m;
|
|
const char *k;
|
|
if(map > 4)
|
|
map = 1;
|
|
gs->level = map;
|
|
pig_object_close_all(gs->pe);
|
|
gs->enemycount = 0;
|
|
gs->messages = 0;
|
|
switch(map)
|
|
{
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
k = "abcd" "efgh" "ijkl" /* Red, green, yellov */
|
|
"0123456789ABCDEFG" /* Sky */
|
|
"xyz"; /* Single R, G, Y */
|
|
break;
|
|
case 0:
|
|
case 3:
|
|
k = "abcd" "efgh" "ijkl" /* Red, green, yellov */
|
|
"................."
|
|
"xyz" /* Single R, G, Y */
|
|
"-+012345..ABCDEF"; /* Night sky */
|
|
break;
|
|
}
|
|
switch(map)
|
|
{
|
|
case 0: m = "-------------ad----------"
|
|
"-abcd-x-ad--ad-abcd-acd--"
|
|
"-x----x--abcd--x----x--x-"
|
|
"-abd--x---ad---abd--x--x-"
|
|
"-x----x--abcd--x----x--x-"
|
|
"-x----x-ad--ad-abcd-abd--"
|
|
"----efhad-eh--egh-efh----"
|
|
"----y--y-y--y--y--y------"
|
|
"++++efh++efgh++y++eh+++++"
|
|
"0123y50y2y45y12y45y123450"
|
|
"ABCDyFAyCyEFyBCyEFeghDEFA"
|
|
"----ijkjl-ijkl--ijkjl----"
|
|
"----il--il-il--il--------"
|
|
"----ijkjl--il--il-ikl----"
|
|
"----il-----il--il--il----"
|
|
"----il----ijkl--ijkjl----"
|
|
"-------------------------";
|
|
break;
|
|
case 1: m = "0000000000000000000000000"
|
|
"1111111111111111111111111"
|
|
"2222222222222222222222222"
|
|
"3333333333333333333333333"
|
|
"4444444444444444444444444"
|
|
"5555555555555555555555555"
|
|
"6666666666666666666666666"
|
|
"7777777ijkjkjjkjkl7777777"
|
|
"8888888888888888888888888"
|
|
"9999999999999999999999999"
|
|
"abcdAAAAAAAAAAAAAAAAAabcd"
|
|
"BBBBBBBBBBBBBBBBBBBBBBBBB"
|
|
"CCCCCCCCCCCCCCCCCCCCCCCCC"
|
|
"efgfgffgfgfgfgfggffgfgfgh"
|
|
"EEEEEEEEEEEEEEEEEEEEEEEEE"
|
|
"FFFFFFFFFFFFFFFFFFFFFFFFF"
|
|
"GGGGGGGGGGGGGGGGGGGGGGGGG";
|
|
new_evil(gs, 2, 0, 5);
|
|
new_evil(gs, 22, 0, 5);
|
|
new_evil(gs, 5, 0, 7);
|
|
new_evil(gs, 19, 0, 7);
|
|
break;
|
|
case 2: m = "0000000000000000000000000"
|
|
"1111111111111111111111111"
|
|
"2222222222222222222222222"
|
|
"3333333333333333333333333"
|
|
"4444444444xxxxx4444444444"
|
|
"5555555555x555x5555555555"
|
|
"6666666666x666x6666666666"
|
|
"7777777xxxx777xxxx7777777"
|
|
"8888888x888888888x8888888"
|
|
"9999999x999999999x9999999"
|
|
"AAAAAAAxxxxAAAxxxxAAAAAAA"
|
|
"BBBBBBBBBBxBBBxBBBBBBBBBB"
|
|
"CCCCCCCCCCxCCCxCCCCCCCCCC"
|
|
"DDDDDDDDDDxxxxxDDDDDDDDDD"
|
|
"EEEEEEEEEEEEEEEEEEEEEEEEE"
|
|
"ijklFFFFFFFFFFFFFFFFFijkl"
|
|
"GGGijlGilGilGilGilGiklGGG";
|
|
new_slime(gs, 2, 0, -5);
|
|
new_slime(gs, 22, 0, 5);
|
|
new_evil(gs, 8, 0, 7);
|
|
new_evil(gs, 16, 0, -7);
|
|
break;
|
|
case 3: m = "-------------------------"
|
|
"-------------------------"
|
|
"-------------------------"
|
|
"-------------------------"
|
|
"ijkl----------efgh-------"
|
|
"-------------------------"
|
|
"-------------------------"
|
|
"z----------------abcbcbbd"
|
|
"+++++++++++++++++++++++++"
|
|
"01z3450123450123450123450"
|
|
"ABCDEFABCefgfgfghFABCDEFA"
|
|
"----z--------------------"
|
|
"-------------------------"
|
|
"------z--------------ijkl"
|
|
"-------------------------"
|
|
"-------------------------"
|
|
"abdefghijkl---efghijklabd";
|
|
new_slime(gs, 5, 0, -5);
|
|
new_slime(gs, 20, 15, -5);
|
|
new_evil(gs, 1, 0, 7);
|
|
new_evil(gs, 20, 0, 10);
|
|
new_evil(gs, 15, 0, 7);
|
|
break;
|
|
case 4: m = "0000000000000000000000000"
|
|
"1111111111111111111111111"
|
|
"2222222222222222222222222"
|
|
"3333333333333333333333333"
|
|
"4444444444444444444444444"
|
|
"555555555555z555555555555"
|
|
"66666666666ijl66666666666"
|
|
"7777777777ijlil7777777777"
|
|
"888888888ijlikkl888888888"
|
|
"99999999ijkjklikl99999999"
|
|
"AAAAAAAikjlijkjkjlAAAAAAA"
|
|
"BBBBBBiklijkjlijkjlBBBBBB"
|
|
"CCCCCijkjlikkjklikklCCCCC"
|
|
"DDDDijklijjklikjkjkklDDDD"
|
|
"EEEijkkjkjlikjkjlijjklEEE"
|
|
"FFijkjlilijkjklikjlikklFF"
|
|
"efggfggfgfgfggfgfgfgfgfgh";
|
|
new_evil(gs, 11, 0, 5);
|
|
new_evil(gs, 10, 0, 6);
|
|
new_evil(gs, 9, 0, 7);
|
|
new_evil(gs, 8, 0, 8);
|
|
new_evil(gs, 7, 0, 9);
|
|
new_evil(gs, 6, 0, 10);
|
|
new_evil(gs, 5, 0, 11);
|
|
new_evil(gs, 4, 0, 12);
|
|
new_evil(gs, 3, 0, 13);
|
|
new_slime(gs, 1, 0, 16);
|
|
new_slime(gs, 24, 0, -14);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
pig_map_from_string(gs->pe->map, k, m);
|
|
gs->refresh_screen = gs->pe->pages;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static GAMESTATE *init_all(SDL_Surface *screen)
|
|
{
|
|
int i;
|
|
PIG_map *pm;
|
|
GAMESTATE *gs = (GAMESTATE *)calloc(1, sizeof(GAMESTATE));
|
|
if(!gs)
|
|
return NULL;
|
|
|
|
gs->running = 1;
|
|
|
|
gs->pe = pig_open(screen);
|
|
if(!gs->pe)
|
|
{
|
|
fprintf(stderr, "Could not open the Pig Engine!\n");
|
|
free(gs);
|
|
return NULL;
|
|
}
|
|
gs->pe->userdata = gs;
|
|
|
|
pig_viewport(gs->pe, 0, 0, SCREEN_W, MAP_H * TILE_H);
|
|
|
|
i = gs->lifepig = pig_sprites(gs->pe, "lifepig.png", 0, 0);
|
|
i |= gs->scorefont = pig_sprites(gs->pe, "font.png", 44, 56);
|
|
i |= gs->glassfont = pig_sprites(gs->pe, "glassfont.png", 60, 60);
|
|
i |= gs->icons = pig_sprites(gs->pe, "icons.png", 48, 48);
|
|
i |= gs->stars = pig_sprites(gs->pe, "stars.png", 32, 32);
|
|
i |= gs->pigframes = pig_sprites(gs->pe, "pigframes.png", 64, 48);
|
|
i |= gs->evil = pig_sprites(gs->pe, "evil.png", 48, 48);
|
|
i |= gs->slime = pig_sprites(gs->pe, "slime.png", 48, 48);
|
|
if(i < 0)
|
|
{
|
|
fprintf(stderr, "Could not load graphics!\n");
|
|
pig_close(gs->pe);
|
|
free(gs);
|
|
return NULL;
|
|
}
|
|
for(i = gs->icons; i < gs->icons + 3*8; ++i)
|
|
pig_hotspot(gs->pe, i, PIG_CENTER, 45);
|
|
for(i = gs->pigframes; i < gs->pigframes + 12; ++i)
|
|
pig_hotspot(gs->pe, i, PIG_CENTER, 43);
|
|
for(i = gs->evil; i < gs->evil + 16; ++i)
|
|
pig_hotspot(gs->pe, i, PIG_CENTER, 46);
|
|
for(i = gs->slime; i < gs->slime + 16; ++i)
|
|
pig_hotspot(gs->pe, i, PIG_CENTER, 46);
|
|
|
|
pm = pig_map_open(gs->pe, MAP_W, MAP_H);
|
|
if(!pm)
|
|
{
|
|
fprintf(stderr, "Could not create map!\n");
|
|
pig_close(gs->pe);
|
|
free(gs);
|
|
return NULL;
|
|
}
|
|
if(pig_map_tiles(pm, "tiles.png", TILE_W, TILE_H) < 0)
|
|
{
|
|
fprintf(stderr, "Could not load background graphics!\n");
|
|
pig_close(gs->pe);
|
|
free(gs);
|
|
return NULL;
|
|
}
|
|
|
|
/* Mark tiles for collision detection */
|
|
pig_map_collisions(pm, 0, 12, PIG_ALL); /* Red, green, yellov */
|
|
pig_map_collisions(pm, 12, 17, PIG_NONE);/* Sky */
|
|
pig_map_collisions(pm, 29, 3, PIG_ALL); /* Single R, G, Y */
|
|
|
|
load_level(gs, 0);
|
|
return gs;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Render the dashboard
|
|
----------------------------------------------------------*/
|
|
static void dashboard(GAMESTATE *gs)
|
|
{
|
|
SDL_Rect r;
|
|
int i, v;
|
|
float x;
|
|
float t = SDL_GetTicks() * 0.001;
|
|
r.x = 0;
|
|
r.y = SCREEN_H - 56;
|
|
r.w = SCREEN_W;
|
|
r.h = 56;
|
|
SDL_SetClipRect(gs->pe->surface, &r);
|
|
|
|
/* Render "plasma bar" */
|
|
for(i = 0; i < 56; ++i)
|
|
{
|
|
float f1, f2, m;
|
|
SDL_Rect cr;
|
|
cr.x = 0;
|
|
cr.w = SCREEN_W;
|
|
cr.y = SCREEN_H - 56 + i;
|
|
cr.h = 1;
|
|
f1 = .25 + .25 * sin(t * 1.7 + (float)i / SCREEN_H * 42);
|
|
f1 += .25 + .25 * sin(-t * 2.1 + (float)i / SCREEN_H * 66);
|
|
f2 = .25 + .25 * sin(t * 3.31 + (float)i / SCREEN_H * 90);
|
|
f2 += .25 + .25 * sin(-t * 1.1 + (float)i / SCREEN_H * 154);
|
|
m = sin((float)i * M_PI / 56.0);
|
|
m = sin(m * M_PI * 0.5);
|
|
m = sin(m * M_PI * 0.5);
|
|
SDL_FillRect(gs->pe->surface,
|
|
&cr, SDL_MapRGB(gs->pe->surface->format,
|
|
((int)128.0 * f1 + 64) * m,
|
|
((int)64.0 * f1 * f2 + 64) * m,
|
|
((int)128.0 * f2 + 32) * m
|
|
));
|
|
}
|
|
|
|
/* Draw pigs... uh, lives! */
|
|
x = -10;
|
|
for(i = 0; i < gs->lives; ++i)
|
|
{
|
|
x += 48 + gs->lives_wobble *
|
|
sin(gs->lives_wobble_time * 12) * .2;
|
|
pig_draw_sprite(gs->pe, gs->lifepig,
|
|
(int)x + gs->lives_wobble *
|
|
sin(gs->lives_wobble_time * 20 + i * 1.7),
|
|
SCREEN_H - 56/2);
|
|
}
|
|
|
|
/* Print score */
|
|
x = SCREEN_W + 5;
|
|
v = gs->score;
|
|
for(i = 9; i >= 0; --i)
|
|
{
|
|
int n = v % 10;
|
|
x -= 39 - gs->score_wobble *
|
|
sin(gs->score_wobble_time * 15 + i * .5);
|
|
pig_draw_sprite(gs->pe, gs->scorefont + n, (int)x,
|
|
SCREEN_H - 56/2);
|
|
v /= 10;
|
|
if(!v)
|
|
break;
|
|
}
|
|
|
|
pig_dirty(gs->pe, &r);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Game logic event handlers
|
|
----------------------------------------------------------*/
|
|
static void before_objects(PIG_engine *pe)
|
|
{
|
|
GAMESTATE *gs = (GAMESTATE *)pe->userdata;
|
|
if(gs->lives_wobble > 0)
|
|
{
|
|
gs->lives_wobble *= 0.95;
|
|
gs->lives_wobble -= 0.3;
|
|
if(gs->lives_wobble < 0)
|
|
gs->lives_wobble = 0;
|
|
}
|
|
if(gs->score_wobble > 0)
|
|
{
|
|
gs->score_wobble *= 0.95;
|
|
gs->score_wobble -= 0.3;
|
|
if(gs->score_wobble < 0)
|
|
gs->score_wobble = 0;
|
|
}
|
|
++gs->logic_frames;
|
|
|
|
if(0 == gs->level)
|
|
{
|
|
switch(gs->fun_count % 60)
|
|
{
|
|
case 17:
|
|
new_powerup(gs, 250, -20, -10, POWER_LIFE);
|
|
break;
|
|
case 29:
|
|
new_powerup(gs, 550, -20, 10, POWER_LIFE);
|
|
break;
|
|
case 37:
|
|
new_powerup(gs, 250, -20, 10, POWER_BONUS2);
|
|
break;
|
|
case 51:
|
|
new_powerup(gs, 550, -20, -10, POWER_BONUS1);
|
|
break;
|
|
}
|
|
if(150 == gs->fun_count % 300)
|
|
message(gs, "Press Space!");
|
|
++gs->fun_count;
|
|
}
|
|
}
|
|
|
|
|
|
typedef enum
|
|
{
|
|
WAITING,
|
|
WALKING,
|
|
FALLING,
|
|
KNOCKED,
|
|
NEXT_LEVEL,
|
|
DEAD
|
|
} OBJECT_states;
|
|
|
|
|
|
static void player_handler(PIG_object *po, const PIG_event *ev)
|
|
{
|
|
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
|
|
switch(ev->type)
|
|
{
|
|
case PIG_PREFRAME:
|
|
switch(po->state)
|
|
{
|
|
case WAITING:
|
|
if(1 == po->age)
|
|
message(gs, "Get ready!");
|
|
else if(po->age > 50)
|
|
po->state = FALLING;
|
|
break;
|
|
case WALKING:
|
|
if(gs->keys[SDLK_LEFT])
|
|
{
|
|
po->ax = -(20 + po->vx) * .4;
|
|
po->target = 3 + po->age % 4 - 1;
|
|
if(5 == po->target)
|
|
po->target = 3;
|
|
}
|
|
else if(gs->keys[SDLK_RIGHT])
|
|
{
|
|
po->ax = (20 - po->vx) * .4;
|
|
po->target = 9 + po->age % 4 - 1;
|
|
if(11 == po->target)
|
|
po->target = 9;
|
|
}
|
|
else
|
|
{
|
|
po->ax = -po->vx * .8;
|
|
if(po->target >= 6)
|
|
po->target = (po->target + 1) %
|
|
PIG_FRAMES;
|
|
else if(po->target)
|
|
--po->target;
|
|
}
|
|
break;
|
|
case FALLING:
|
|
if(gs->keys[SDLK_LEFT])
|
|
po->ax = -(20 + po->vx) * .2;
|
|
else if(gs->keys[SDLK_RIGHT])
|
|
po->ax = (20 - po->vx) * .2;
|
|
else
|
|
po->ax = -po->vx * .2;
|
|
po->target = (po->target + 1) % PIG_FRAMES;
|
|
break;
|
|
}
|
|
po->timer[0] = 1;
|
|
break;
|
|
case PIG_TIMER0:
|
|
if(po->x < 0)
|
|
po->x = 0;
|
|
else if(po->x > po->owner->view.w - 1)
|
|
po->x = po->owner->view.w - 1;
|
|
switch(po->state)
|
|
{
|
|
case WALKING:
|
|
if(po->power)
|
|
--po->power;
|
|
po->image = po->target % PIG_FRAMES;
|
|
if(!pig_test_map(gs->pe, po->x, po->y + 1))
|
|
{
|
|
po->state = FALLING;
|
|
po->ay = GRAV_ACC;
|
|
}
|
|
if(gs->jump || gs->keys[SDLK_UP])
|
|
{
|
|
po->ay = 0;
|
|
po->vy = -JUMP_SPEED;
|
|
po->state = FALLING;
|
|
gs->jump = 0;
|
|
}
|
|
break;
|
|
case FALLING:
|
|
if(po->vy > 2)
|
|
po->power = 3;
|
|
po->ay = GRAV_ACC;
|
|
po->image = po->target;
|
|
break;
|
|
case KNOCKED:
|
|
po->power = 0;
|
|
po->ay = GRAV_ACC;
|
|
po->target = (po->target + 2) % PIG_FRAMES;
|
|
po->image = po->target;
|
|
po->ax = -po->vx * .2;
|
|
break;
|
|
case NEXT_LEVEL:
|
|
po->vx = (SCREEN_W / 2 - po->x) * .1;
|
|
po->target = (po->target + 1) % PIG_FRAMES;
|
|
po->image = po->target;
|
|
break;
|
|
case DEAD:
|
|
po->ax = po->ay = 0;
|
|
po->vx = po->vy = 0;
|
|
break;
|
|
}
|
|
if(gs->jump)
|
|
--gs->jump;
|
|
if(NEXT_LEVEL != po->state)
|
|
{
|
|
if(gs->enemycount <= 0)
|
|
{
|
|
message(gs, "Well Done!");
|
|
po->state = NEXT_LEVEL;
|
|
po->vy = 0;
|
|
po->ay = -1;
|
|
po->tilemask = 0;
|
|
po->hitgroup = 0;
|
|
po->timer[2] = 50;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PIG_TIMER1:
|
|
/* Snap out of KNOCKED mode */
|
|
po->state = FALLING;
|
|
break;
|
|
|
|
case PIG_TIMER2:
|
|
switch(po->state)
|
|
{
|
|
case NEXT_LEVEL:
|
|
add_life(gs);
|
|
pig_object_close(po);
|
|
load_level(gs, gs->level + 1);
|
|
new_player(gs);
|
|
break;
|
|
default:
|
|
pig_object_close(po);
|
|
if(!new_player(gs))
|
|
load_level(gs, 0);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case PIG_HIT_TILE:
|
|
if(KNOCKED == po->state)
|
|
break;
|
|
|
|
if(ev->cinfo.sides & PIG_TOP)
|
|
{
|
|
po->y = ev->cinfo.y;
|
|
po->vy = 0;
|
|
po->ay = 0;
|
|
}
|
|
po->state = WALKING;
|
|
break;
|
|
|
|
case PIG_HIT_OBJECT:
|
|
if(KNOCKED == po->state)
|
|
break;
|
|
|
|
switch(ev->obj->hitgroup)
|
|
{
|
|
case GROUP_ENEMY:
|
|
if((po->power && ev->cinfo.sides & PIG_TOP) ||
|
|
(po->vy - ev->obj->vy) >= 15)
|
|
{
|
|
/* Win: Stomp! */
|
|
inc_score(gs, ev->obj->score);
|
|
ev->obj->y = ev->cinfo.y + 10;
|
|
if(po->vy > 0)
|
|
ev->obj->vy = po->vy;
|
|
else
|
|
ev->obj->vy = 10;
|
|
ev->obj->ay = GRAV_ACC;
|
|
ev->obj->tilemask = 0;
|
|
ev->obj->hitgroup = 0;
|
|
if(gs->jump || gs->keys[SDLK_UP])
|
|
{
|
|
/* Mega jump! */
|
|
po->vy = -(JUMP_SPEED + 7);
|
|
gs->jump = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Bounce a little */
|
|
po->vy = -15;
|
|
}
|
|
po->y = ev->cinfo.y;
|
|
po->ay = 0;
|
|
po->state = FALLING;
|
|
}
|
|
else
|
|
{
|
|
/* Lose: Knocked! */
|
|
po->vy = -15;
|
|
po->ay = GRAV_ACC;
|
|
po->state = KNOCKED;
|
|
po->timer[1] = 11;
|
|
new_star(gs, po->x, po->y - 20, -5, 3);
|
|
new_star(gs, po->x, po->y - 20, 2, -6);
|
|
new_star(gs, po->x, po->y - 20, 4, 4);
|
|
}
|
|
break;
|
|
case GROUP_POWERUP:
|
|
switch(ev->obj->score)
|
|
{
|
|
case POWER_LIFE:
|
|
add_life(gs);
|
|
message(gs, "Extra Life!");
|
|
break;
|
|
case POWER_BONUS1:
|
|
/* Double or 100k bonus! */
|
|
if(gs->score < 100000)
|
|
{
|
|
inc_score_nobonus(gs, gs->score);
|
|
message(gs, "Double Score!");
|
|
}
|
|
else
|
|
{
|
|
inc_score_nobonus(gs, 100000);
|
|
message(gs, "100 000!");
|
|
}
|
|
break;
|
|
case POWER_BONUS2:
|
|
inc_score_nobonus(gs, 1000);
|
|
message(gs, "1000!");
|
|
break;
|
|
}
|
|
ev->obj->state = DEAD;
|
|
ev->obj->tilemask = 0;
|
|
ev->obj->hitgroup = 0;
|
|
ev->obj->vy = -20;
|
|
ev->obj->ay = -2;
|
|
break;
|
|
}
|
|
break;
|
|
case PIG_OFFSCREEN:
|
|
/*
|
|
* Dead pigs don't care about being off-screen.
|
|
* A timer is used to remove them, and to continue
|
|
* the game with a new life.
|
|
*/
|
|
if(DEAD == po->state)
|
|
break;
|
|
if(po->y < 0) /* Above the playfield is ok. */
|
|
break;
|
|
if(gs->lives)
|
|
message(gs, "Oiiiiiiink!!!");
|
|
else
|
|
message(gs, "Game Over!");
|
|
po->state = DEAD;
|
|
po->timer[2] = 50;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void powerup_handler(PIG_object *po, const PIG_event *ev)
|
|
{
|
|
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
|
|
switch(ev->type)
|
|
{
|
|
case PIG_PREFRAME:
|
|
if(DEAD == po->state)
|
|
break;
|
|
po->ax = (po->target - po->vx) * .3;
|
|
po->ay = GRAV_ACC;
|
|
po->image = po->age % 8;
|
|
++po->power;
|
|
break;
|
|
case PIG_HIT_TILE:
|
|
if(DEAD == po->state)
|
|
break;
|
|
if(po->power > 2)
|
|
po->target = -po->target;
|
|
po->power = 0;
|
|
po->vy = 0;
|
|
po->ay = 0;
|
|
po->x = ev->cinfo.x + po->vx;
|
|
po->y = ev->cinfo.y;
|
|
break;
|
|
case PIG_OFFSCREEN:
|
|
if(po->y > SCREEN_H || (po->y < -100))
|
|
{
|
|
pig_object_close(po);
|
|
--gs->enemycount;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void star_handler(PIG_object *po, const PIG_event *ev)
|
|
{
|
|
switch(ev->type)
|
|
{
|
|
case PIG_PREFRAME:
|
|
if(po->age >= 8)
|
|
pig_object_close(po);
|
|
else
|
|
po->image = po->age;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void evil_handler(PIG_object *po, const PIG_event *ev)
|
|
{
|
|
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
|
|
int look_x;
|
|
switch(ev->type)
|
|
{
|
|
case PIG_PREFRAME:
|
|
if(DEAD == po->state)
|
|
break;
|
|
po->ax = (po->target - po->vx) * .5;
|
|
po->ay = GRAV_ACC;
|
|
po->image = po->age % 16;
|
|
break;
|
|
case PIG_HIT_TILE:
|
|
if(DEAD == po->state)
|
|
break;
|
|
po->vy = 0;
|
|
po->ay = 0;
|
|
po->x = ev->cinfo.x + po->vx;
|
|
po->y = ev->cinfo.y;
|
|
break;
|
|
case PIG_OFFSCREEN:
|
|
if(po->y > SCREEN_H)
|
|
{
|
|
pig_object_close(po);
|
|
--gs->enemycount;
|
|
}
|
|
break;
|
|
case PIG_POSTFRAME:
|
|
if(DEAD == po->state)
|
|
break;
|
|
look_x = 10 + fabs(po->vx * 2);
|
|
if(po->target < 0)
|
|
look_x = -look_x;
|
|
if(!pig_test_map(po->owner, po->x + look_x, po->y + 1))
|
|
po->target = -po->target;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void slime_handler(PIG_object *po, const PIG_event *ev)
|
|
{
|
|
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
|
|
int look_x;
|
|
switch(ev->type)
|
|
{
|
|
case PIG_PREFRAME:
|
|
if(DEAD == po->state)
|
|
break;
|
|
po->ax = (po->target - po->vx) * .2;
|
|
po->ay = GRAV_ACC;
|
|
po->image = po->age % 16;
|
|
break;
|
|
case PIG_HIT_TILE:
|
|
po->vy = -(JUMP_SPEED + GRAV_ACC);
|
|
po->ay = 0;
|
|
po->y = ev->cinfo.y;
|
|
break;
|
|
case PIG_OFFSCREEN:
|
|
if(po->y > SCREEN_H)
|
|
{
|
|
pig_object_close(po);
|
|
--gs->enemycount;
|
|
}
|
|
break;
|
|
case PIG_POSTFRAME:
|
|
if(DEAD == po->state)
|
|
break;
|
|
/* Don't bother looking if we're close to a floor. */
|
|
if(pig_test_map_vector(po->owner,
|
|
po->x, po->y,
|
|
po->x, po->y + 48,
|
|
PIG_TOP, NULL))
|
|
break;
|
|
/* Turn around if there's no floor! */
|
|
look_x = 10 + fabs(po->vx * 4);
|
|
if(po->target < 0)
|
|
look_x = -look_x;
|
|
if(!pig_test_map_vector(po->owner,
|
|
po->x + look_x, po->y,
|
|
po->x + look_x, SCREEN_H,
|
|
PIG_TOP, NULL))
|
|
po->target = -po->target;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void chain_head_handler(PIG_object *po, const PIG_event *ev)
|
|
{
|
|
GAMESTATE *gs = (GAMESTATE *)po->owner->userdata;
|
|
switch(ev->type)
|
|
{
|
|
case PIG_PREFRAME:
|
|
po->vx = (po->target - po->x) * .3;
|
|
po->vy = 15 * cos(po->age * .3) - 9;
|
|
if((gs->messages > 1) && (1 == po->state))
|
|
po->timer[1] = 0;
|
|
if(po->timer[1])
|
|
break;
|
|
case PIG_TIMER1:
|
|
switch(po->state)
|
|
{
|
|
case 0:
|
|
po->timer[1] = 35;
|
|
++po->state;
|
|
break;
|
|
case 1:
|
|
po->target = -SCREEN_W;
|
|
po->timer[1] = 50;
|
|
++po->state;
|
|
if(gs->messages > 0)
|
|
--gs->messages;
|
|
break;
|
|
case 2:
|
|
pig_object_close(po);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void chain_link_handler(PIG_object *po, const PIG_event *ev)
|
|
{
|
|
PIG_object *target = pig_object_find(po, po->target);
|
|
switch(ev->type)
|
|
{
|
|
case PIG_PREFRAME:
|
|
if(target)
|
|
{
|
|
po->vx = ((target->x + FONT_SPACING) - po->x) * .6;
|
|
po->vy = (target->y - po->y) * .6 - 9;
|
|
}
|
|
else
|
|
pig_object_close(po);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Accounting (score, lives etc)
|
|
----------------------------------------------------------*/
|
|
|
|
static void add_life(GAMESTATE *gs)
|
|
{
|
|
++gs->lives;
|
|
gs->lives_wobble += 10;
|
|
if(gs->lives_wobble > 15)
|
|
gs->lives_wobble = 15;
|
|
gs->lives_wobble_time = 0;
|
|
}
|
|
|
|
|
|
static void remove_life(GAMESTATE *gs)
|
|
{
|
|
--gs->lives;
|
|
gs->lives_wobble += 10;
|
|
if(gs->lives_wobble > 15)
|
|
gs->lives_wobble = 15;
|
|
gs->lives_wobble_time = 0;
|
|
}
|
|
|
|
|
|
static void inc_score_nobonus(GAMESTATE *gs, int v)
|
|
{
|
|
int os = gs->score;
|
|
gs->score += v;
|
|
while(v)
|
|
{
|
|
gs->score_wobble += 1;
|
|
v /= 10;
|
|
}
|
|
if(gs->score_wobble > 15)
|
|
gs->score_wobble = 15;
|
|
gs->score_wobble_time = 0;
|
|
if(os / 10000 != gs->score / 10000)
|
|
new_powerup(gs, SCREEN_W / 2, -20, -4, POWER_LIFE);
|
|
}
|
|
|
|
static void inc_score(GAMESTATE *gs, int v)
|
|
{
|
|
int os = gs->score;
|
|
inc_score_nobonus(gs, v);
|
|
if(os / 5000 != gs->score / 5000)
|
|
new_powerup(gs, SCREEN_W / 2, -20, 8, POWER_BONUS1);
|
|
else if(os / 1000 != gs->score / 1000)
|
|
new_powerup(gs, SCREEN_W / 2, -20, -6, POWER_BONUS2);
|
|
}
|
|
|
|
|
|
static PIG_object *new_player(GAMESTATE *gs)
|
|
{
|
|
PIG_object *po;
|
|
if(!gs->lives)
|
|
return NULL;
|
|
|
|
po = pig_object_open(gs->pe, SCREEN_W / 2, -50, 1);
|
|
if(!po)
|
|
return NULL;
|
|
|
|
remove_life(gs);
|
|
po->ibase = gs->pigframes;
|
|
po->handler = player_handler;
|
|
po->hitmask = GROUP_POWERUP | GROUP_ENEMY;
|
|
return po;
|
|
}
|
|
|
|
|
|
static PIG_object *new_powerup(GAMESTATE *gs,
|
|
int x, int y, int speed, POWERUPS type)
|
|
{
|
|
PIG_object *po = pig_object_open(gs->pe, x, y, 1);
|
|
if(!po)
|
|
return NULL;
|
|
|
|
++gs->enemycount;
|
|
po->score = type;
|
|
po->ibase = gs->icons + 8 * po->score;
|
|
po->target = speed;
|
|
po->handler = powerup_handler;
|
|
po->tilemask = PIG_TOP;
|
|
po->hitgroup = GROUP_POWERUP;
|
|
return po;
|
|
}
|
|
|
|
|
|
static PIG_object *new_star(GAMESTATE *gs, int x, int y, int vx, int vy)
|
|
{
|
|
PIG_object *po = pig_object_open(gs->pe, x + vx, y + vy, 1);
|
|
if(!po)
|
|
return NULL;
|
|
|
|
po->ibase = gs->stars;
|
|
po->ax = -vx * 0.3;
|
|
po->vx = vx * 3;
|
|
po->ay = -vy * 0.3;
|
|
po->vy = vy * 3;
|
|
po->handler = star_handler;
|
|
return po;
|
|
}
|
|
|
|
|
|
static PIG_object *new_evil(GAMESTATE *gs,
|
|
int x, int y, int speed)
|
|
{
|
|
PIG_object *po = pig_object_open(gs->pe,
|
|
x * TILE_W, y * TILE_H, 1);
|
|
if(!po)
|
|
return NULL;
|
|
|
|
++gs->enemycount;
|
|
po->ibase = gs->evil;
|
|
po->target = speed;
|
|
po->handler = evil_handler;
|
|
po->score = 200;
|
|
po->tilemask = PIG_TOP;
|
|
po->hitgroup = GROUP_ENEMY;
|
|
return po;
|
|
}
|
|
|
|
|
|
static PIG_object *new_slime(GAMESTATE *gs,
|
|
int x, int y, int speed)
|
|
{
|
|
PIG_object *po = pig_object_open(gs->pe,
|
|
x * TILE_W, y * TILE_H, 1);
|
|
if(!po)
|
|
return NULL;
|
|
|
|
++gs->enemycount;
|
|
po->ibase = gs->slime;
|
|
po->target = speed;
|
|
po->handler = slime_handler;
|
|
po->score = 300;
|
|
po->tilemask = PIG_TOP;
|
|
po->hitgroup = GROUP_ENEMY;
|
|
return po;
|
|
}
|
|
|
|
|
|
static PIG_object *new_chain_head(GAMESTATE *gs,
|
|
int x, int y, int image, int target_x)
|
|
{
|
|
PIG_object *po = pig_object_open(gs->pe, x, y, 1);
|
|
if(!po)
|
|
return NULL;
|
|
|
|
po->ibase = image;
|
|
po->handler = chain_head_handler;
|
|
po->target = target_x;
|
|
return po;
|
|
}
|
|
|
|
|
|
static PIG_object *new_chain_link(GAMESTATE *gs,
|
|
int x, int y, int image, int target)
|
|
{
|
|
PIG_object *po = pig_object_open(gs->pe, x, y, 1);
|
|
if(!po)
|
|
return NULL;
|
|
|
|
po->ibase = image;
|
|
po->handler = chain_link_handler;
|
|
po->target = target;
|
|
return po;
|
|
}
|
|
|
|
|
|
static void message(GAMESTATE *gs, const char *s)
|
|
{
|
|
int i = 0;
|
|
const int x = SCREEN_W + FONT_SPACING;
|
|
const int y = MAP_H * TILE_H - 30;
|
|
int tx = (SCREEN_W - ((signed)strlen(s) - 1) * FONT_SPACING) / 2;
|
|
PIG_object *po = NULL;
|
|
while(s[i])
|
|
{
|
|
int c = toupper(s[i]) - 32 + gs->glassfont;
|
|
if(0 == i)
|
|
po = new_chain_head(gs, x, y, c, tx);
|
|
else
|
|
po = new_chain_link(gs, x, y, c, po->id);
|
|
if(!po)
|
|
return;
|
|
++i;
|
|
}
|
|
++gs->messages;
|
|
}
|
|
|
|
|
|
static int start_game(GAMESTATE *gs)
|
|
{
|
|
if(0 != gs->level)
|
|
return 0; /* Already playing! --> */
|
|
|
|
gs->score = 0;
|
|
gs->lives = 5;
|
|
|
|
if(load_level(gs, 1) < 0)
|
|
return -1;
|
|
|
|
gs->player = new_player(gs);
|
|
if(!gs->player)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Input; events and game control keys
|
|
----------------------------------------------------------*/
|
|
static void handle_input(GAMESTATE *gs, SDL_Event *ev)
|
|
{
|
|
switch(ev->type)
|
|
{
|
|
case SDL_MOUSEBUTTONUP:
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
switch(ev->key.keysym.sym)
|
|
{
|
|
case SDLK_UP:
|
|
gs->jump = 3;
|
|
break;
|
|
case SDLK_F1:
|
|
gs->pe->interpolation = !gs->pe->interpolation;
|
|
if(gs->pe->interpolation)
|
|
message(gs, "Interpolation: ON");
|
|
else
|
|
message(gs, "Interpolation: OFF");
|
|
break;
|
|
case SDLK_F2:
|
|
gs->pe->direct = !gs->pe->direct;
|
|
if(gs->pe->direct)
|
|
message(gs, "Rendering: Direct");
|
|
else
|
|
message(gs, "Rendering: Buffered");
|
|
break;
|
|
case SDLK_F3:
|
|
gs->pe->show_dirtyrects = !gs->pe->show_dirtyrects;
|
|
if(gs->pe->show_dirtyrects)
|
|
message(gs, "Dirtyrects: ON");
|
|
else
|
|
message(gs, "Dirtyrects: OFF");
|
|
break;
|
|
case SDLK_F4:
|
|
gs->nice = !gs->nice;
|
|
if(gs->nice)
|
|
message(gs, "Be Nice: ON");
|
|
else
|
|
message(gs, "Be Nice: OFF");
|
|
break;
|
|
case SDLK_SPACE:
|
|
start_game(gs);
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_KEYUP:
|
|
switch(ev->key.keysym.sym)
|
|
{
|
|
case SDLK_ESCAPE:
|
|
gs->running = 0;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_QUIT:
|
|
gs->running = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void handle_keys(GAMESTATE *gs)
|
|
{
|
|
}
|
|
|
|
|
|
static int break_received = 0;
|
|
|
|
#ifndef RETSIGTYPE
|
|
#define RETSIGTYPE void
|
|
#endif
|
|
static RETSIGTYPE breakhandler(int sig)
|
|
{
|
|
/* For platforms that drop the handlers on the first signal... */
|
|
signal(SIGTERM, breakhandler);
|
|
signal(SIGINT, breakhandler);
|
|
break_received = 1;
|
|
#if (RETSIGTYPE != void)
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
main()
|
|
----------------------------------------------------------*/
|
|
int main(int argc, char* argv[])
|
|
{
|
|
SDL_Surface *screen;
|
|
GAMESTATE *gs;
|
|
int i;
|
|
int bpp = 0;
|
|
int last_tick, start_time, end_time;
|
|
int dashframe;
|
|
float logic_fps = 20.0;
|
|
int flags = SDL_DOUBLEBUF | SDL_HWSURFACE;
|
|
|
|
SDL_Init(SDL_INIT_VIDEO);
|
|
atexit(SDL_Quit);
|
|
signal(SIGTERM, breakhandler);
|
|
signal(SIGINT, breakhandler);
|
|
|
|
for(i = 1; i < argc; ++i)
|
|
{
|
|
if(strncmp(argv[i], "-s", 2) == 0)
|
|
flags &= ~SDL_DOUBLEBUF;
|
|
else if(strncmp(argv[i], "-f", 2) == 0)
|
|
flags |= SDL_FULLSCREEN;
|
|
else
|
|
bpp = atoi(&argv[i][1]);
|
|
}
|
|
|
|
screen = SDL_SetVideoMode(SCREEN_W, SCREEN_H, bpp, flags);
|
|
if(!screen)
|
|
{
|
|
fprintf(stderr, "Failed to open screen!\n");
|
|
return 1;
|
|
}
|
|
|
|
SDL_WM_SetCaption("Fixed Rate Pig", "Pig");
|
|
SDL_ShowCursor(0);
|
|
|
|
gs = init_all(screen);
|
|
if(!gs)
|
|
return 1;
|
|
|
|
gs->keys = SDL_GetKeyState(&i);
|
|
|
|
gs->logic_frames = 0;
|
|
gs->rendered_frames = 0;
|
|
gs->pe->before_objects = before_objects;
|
|
|
|
pig_start(gs->pe, 0);
|
|
gs->refresh_screen = gs->pe->pages;
|
|
start_time = last_tick = SDL_GetTicks();
|
|
while(gs->running)
|
|
{
|
|
int tick;
|
|
float frames, dt;
|
|
SDL_Event ev;
|
|
|
|
/* Handle input */
|
|
while(SDL_PollEvent(&ev) > 0)
|
|
handle_input(gs, &ev);
|
|
handle_keys(gs);
|
|
if(break_received)
|
|
gs->running = 0;
|
|
|
|
/* Calculate time since last update */
|
|
tick = SDL_GetTicks();
|
|
dt = (tick - last_tick) * 0.001;
|
|
frames = dt * logic_fps;
|
|
|
|
/* Run the game logic */
|
|
pig_animate(gs->pe, frames);
|
|
|
|
/*
|
|
* Limit the dashboard frame rate to 15 fps
|
|
* when there's no wobbling going on.
|
|
*
|
|
* The 'dashframe' deal is about keeping the
|
|
* pages in sync on a double buffered display.
|
|
*/
|
|
if(gs->lives_wobble || gs->score_wobble ||
|
|
(gs->dashboard_time > 1.0/15.0))
|
|
{
|
|
dashframe = gs->pe->pages;
|
|
gs->dashboard_time = 0;
|
|
}
|
|
if(dashframe)
|
|
{
|
|
--dashframe;
|
|
dashboard(gs);
|
|
}
|
|
|
|
/* Update sprites */
|
|
if(gs->refresh_screen)
|
|
{
|
|
--gs->refresh_screen;
|
|
pig_refresh_all(gs->pe);
|
|
}
|
|
else
|
|
pig_refresh(gs->pe);
|
|
|
|
/* Make the new frame visible */
|
|
pig_flip(gs->pe);
|
|
|
|
/* Update statistics, timers and stuff */
|
|
++gs->rendered_frames;
|
|
gs->lives_wobble_time += dt;
|
|
gs->score_wobble_time += dt;
|
|
gs->dashboard_time += dt;
|
|
|
|
last_tick = tick;
|
|
if(gs->nice)
|
|
SDL_Delay(10);
|
|
}
|
|
|
|
/* Print some statistics */
|
|
end_time = SDL_GetTicks();
|
|
i = end_time - start_time;
|
|
printf(" Total time running: %d ms\n", i);
|
|
if(!i)
|
|
i = 1;
|
|
printf("Average rendering frame rate: %.2f fps\n",
|
|
gs->rendered_frames * 1000.0 / i);
|
|
printf(" Average logic frame rate: %.2f fps\n",
|
|
gs->logic_frames * 1000.0 / i);
|
|
|
|
pig_close(gs->pe);
|
|
return 0;
|
|
}
|