sdlbomber/game.c
2017-06-12 12:47:01 +02:00

981 lines
19 KiB
C

#include "bomber.h"
#include "game.h"
#include "gfx.h"
#include "network.h"
#include "draw.h"
#include "utils.h"
#include "sound.h"
#include "menu.h"
#include "list.h"
static int gameframe;
static listhead activebombs;
static listhead activedecays;
static listhead activebonus;
static listhead activegeneric;
static listhead detonatebombs;
static listhead activeflames;
static listhead activeplayers;
static listhead allplayers;
figure walking[MAXSETS][NUMWALKFRAMES];
solid background, backgroundoriginal;
/* The playfield array, contains FIELD_* equates */
unsigned char field[32][32];
void *info[32][32];
volatile char exitflag = 0;
static int framecount = 0;
char playername[16];
static int mycount;
static int bonustotal;
static const int bonuschances[]= {
TILE_BOMB,20,
TILE_FLAME,20,
TILE_TRIGGER,2,
TILE_GOLDFLAME,2,
TILE_SKATES,20,
TILE_TURTLE,5,
TILE_NONE,160
};
static GameOptions gameoptions;
static const unsigned char playerpositions[MAXNETNODES*3] = { /* color, x, y */
2,0,0,
3,14,10,
4,14,0,
5,0,10,
1,6,0,
6,8,10,
7,0,6,
8,14,4,
};
static void resetplayer(player *pl, int color, int x, int y) {
pl->speed=SPEEDSTART;
pl->flags=0;
pl->flamelength=gameoptions.flames+1;
pl->bombsavailable=gameoptions.bombs+1;
pl->color=color;
pl->xpos=arraytoscreenx(x);
pl->ypos=arraytoscreeny(y);
field[y][x]=FIELD_EMPTY;
if(x) field[y][x-1]=FIELD_EMPTY;
if(y) field[y-1][x]=FIELD_EMPTY;
if(x<arraynumx-1) field[y][x+1]=FIELD_EMPTY;
if(y<arraynumy-1) field[y+1][x]=FIELD_EMPTY;
addtail(&activeplayers, pl);
}
static void initplayer(int color,int x,int y,int controller) {
player *pl;
pl = allocentry(player);
if(!pl)
nomem("Couldn't get player structure (allocentry())");
list_add_tail(&allplayers, &pl->list_all_players);
pl->controller=controller;
pl->fixx=-4;
pl->fixy=-40;
pl->kills = pl->deaths = 0;
resetplayer(pl, color, x, y);
}
static void initplayers(void) {
int i;
const unsigned char *p;
int c,x,y;
if(NETWORK_NONE == network) {
initplayer(2,0,0,-1);
return;
}
p=playerpositions;
for(i=0;i<MAXNETNODES;++i) {
if(!netnodes[i].used) continue;
c=*p++;
x=*p++;
y=*p++;
initplayer(c,x,y,i);
}
}
static void resetplayers(void) {
const unsigned char *p;
int c,x,y;
player *pl;
p=playerpositions;
list_for_each_entry(pl, &allplayers, list_all_players) {
c=*p++;
x=*p++;
y=*p++;
resetplayer(pl, c, x, y);
}
}
static void firstzero(void) {
alloc_things();
list_init_head(&activebombs);
list_init_head(&detonatebombs);
list_init_head(&activeflames);
list_init_head(&activedecays);
list_init_head(&activebonus);
list_init_head(&activeplayers);
list_init_head(&activegeneric);
list_init_head(&allplayers);
mycount = mydatacount = 0;
memset(latestactions,0,sizeof(latestactions));
memset(latestcounts,0,sizeof(latestcounts));
memset(actionblock,0,sizeof(actionblock));
actioncount = 0;
}
static void initgame() {
int i,j;
int x,y;
int bl;
const int *p;
int comp;
if (network != NETWORK_SLAVE)
set_game_options(&configopts);
gameframe=0;
things_list_clear(&activebombs);
things_list_clear(&detonatebombs);
things_list_clear(&activeflames);
things_list_clear(&activedecays);
things_list_clear(&activebonus);
list_init_head(&activeplayers);
things_list_clear(&activegeneric);
p=bonuschances;
bonustotal=0;
for(;;) {
i=*p++;
if(i==TILE_NONE) break;
bonustotal+=*p++;
}
bonustotal += 64*(3-gameoptions.generosity);
memset(field,0,sizeof(field));
comp=gameoptions.density;
for(j=0;j<arraynumy;++j)
for(i=0;i<arraynumx;++i) {
/* if((i&j)&1) {
field[j][i]=FIELD_BORDER;
} else*/
field[j][i]=
(myrand()&3)>=comp ? FIELD_BRICK : FIELD_EMPTY;
}
solidcopyany(&backgroundoriginal,&background,0,0,IXSIZE,IYSIZE);
resetplayers();
for(j=0;j<arraynumy;++j) {
y=arraystarty+j*arrayspacey;
for(i=0;i<arraynumx;++i) {
x=arraystartx+i*arrayspacex;
bl=field[j][i];
if(bl==FIELD_BORDER) bl=2;
else if(bl==FIELD_BRICK) bl=1;
else continue;
drawfigureany(x,y,blocks+bl,&background);
}
}
solidcopy(&background,0,0,IXSIZE,IYSIZE);
copyup();
}
static void addflame(player *owner,int px,int py) {
flame *fl,*fl2;
fl = allocentry(flame);
if(!fl) return;
addtail(&activeflames,fl);
field[py][px]=FIELD_FLAME;
info[py][px]=fl;
fl->px=px;
fl->py=py;
fl->xpos=arraytoscreenx(px);
fl->ypos=arraytoscreeny(py);
fl->owner=owner;
if(px && field[py][px-1]==FIELD_FLAME) {
fl2=info[py][px-1];
fl->lurd|=FL_LEFT;
fl2->lurd|=FL_RIGHT;
}
if(py && field[py-1][px]==FIELD_FLAME) {
fl2=info[py-1][px];
fl->lurd|=FL_UP;
fl2->lurd|=FL_DOWN;
}
if(px<arraynumx-1 && field[py][px+1]==FIELD_FLAME) {
fl2=info[py][px+1];
fl->lurd|=FL_RIGHT;
fl2->lurd|=FL_LEFT;
}
if(py<arraynumy-1 && field[py+1][px]==FIELD_FLAME) {
fl2=info[py+1][px];
fl->lurd|=FL_DOWN;
fl2->lurd|=FL_UP;
}
}
static void dropbomb(player *pl,int px,int py,int type){
bomb *bmb;
if(field[py][px]!=FIELD_EMPTY) return;
bmb = allocentry(bomb);
if(!bmb) return;
playsound(3);
addtail(&activebombs,bmb);
--(pl->bombsavailable);
field[py][px]=FIELD_BOMB;
info[py][px]=bmb;
bmb->type=type;
bmb->px=px;
bmb->py=py;
bmb->xpos=arraytoscreenx(px);
bmb->ypos=arraytoscreeny(py);
bmb->power=pl->flamelength;
bmb->owner=pl;
}
static void adddecay(int px,int py) {
brickdecay *bd;
int xpos,ypos;
bd = allocentry(brickdecay);
if(!bd) return;
field[py][px]=FIELD_EXPLODING;
bd->xpos=arraytoscreenx(px);
bd->ypos=arraytoscreeny(py);
bd->px=px;
bd->py=py;
addtail(&activedecays,bd);
xpos=tovideox(bd->xpos);
ypos=tovideoy(bd->ypos);
solidcopyany(&backgroundoriginal,&background,xpos,ypos,
arrayspacex,arrayspacey);
solidcopy(&background,xpos,ypos,arrayspacex,arrayspacey);
}
static void addbonus(int px,int py,int type) {
bonustile *bonus;
bonus = allocentry(bonustile);
if(!bonus) return;
addtail(&activebonus,bonus);
bonus->px=px;
bonus->py=py;
bonus->xpos=arraytoscreenx(px);
bonus->ypos=arraytoscreeny(py);
bonus->type=type;
field[py][px]=FIELD_BONUS;
info[py][px]=bonus;
}
static void trybonus(int px,int py) {
int i=0, r;
const int *p;
if(field[py][px]!=FIELD_EMPTY) return;
p=bonuschances;
r=myrand()%bonustotal;
while(r>=0) {
i=*p++;
r-=*p++;
}
if(i==TILE_NONE) return;
addbonus(px,py,i);
}
static void deletebonus(bonustile *bonus) {
int px,py;
px=bonus->px;
py=bonus->py;
field[py][px]=0;
info[py][px]=0;
list_del(&bonus->list);
freeentry(bonus);
}
static void adddetonate(bomb *bmb) {
if (bmb->type==BOMB_OFF) return;
bmb->type = BOMB_OFF;
field[bmb->py][bmb->px] = FIELD_EXPLODING;
info[bmb->py][bmb->px] = 0;
removeitem(bmb);
addtail(&detonatebombs, bmb);
}
static void processbombs() {
bomb *bmb, *next;
list_for_each_entry_safe(bmb, next, &activebombs, list) {
++(bmb->figcount);
++bmb->timer;
switch(bmb->type) {
case BOMB_NORMAL:
if (bmb->timer == BOMBLIFE)
adddetonate(bmb);
break;
case BOMB_CONTROLLED:
if (bmb->timer == BOMB_CONTROLLED_LIFE) {
bmb->timer = 0;
bmb->type = BOMB_NORMAL;
}
break;
}
}
}
static void flameshaft(player *owner,int px,int py,int dx,int dy,int power) {
int there;
bomb *bmb;
while(power--) {
px+=dx;
py+=dy;
if(px<0 || py<0 || px>=arraynumx || py>=arraynumy) break;
there=field[py][px];
switch(there) {
case FIELD_BOMB:
bmb=info[py][px];
adddetonate(bmb);
break;
case FIELD_EMPTY:
addflame(owner,px,py);
break;
case FIELD_BRICK:
adddecay(px,py);
power=0;
break;
case FIELD_BONUS:
deletebonus(info[py][px]);
power=0;
break;
case FIELD_BORDER:
case FIELD_EXPLODING:
default:
power=0;
case FIELD_FLAME:
break;
}
}
}
static void detonatebomb(bomb *bmb) {
int px,py;
int power;
player *owner;
++(bmb->owner->bombsavailable);
px=bmb->px;
py=bmb->py;
power=bmb->power;
owner=bmb->owner;
list_del(&bmb->list);
freeentry(bmb);
addflame(owner,px,py);
flameshaft(owner,px,py,-1,0,power);
flameshaft(owner,px,py,0,-1,power);
flameshaft(owner,px,py,1,0,power);
flameshaft(owner,px,py,0,1,power);
}
static void dodetonations(void) {
int i = 0;
bomb *bmb;
while (!list_empty(&detonatebombs)) {
bmb = (bomb*) detonatebombs.next;
++i;
detonatebomb(bmb);
}
if(i) playsound((myrand()&1) ? 0 : 4);
}
static void processflames(void) {
flame *fl, *next;
list_for_each_entry_safe(fl, next, &activeflames, list) {
++(fl->timer);
if(fl->timer==FLAMELIFE) {
field[fl->py][fl->px]=FIELD_EMPTY;
info[fl->py][fl->px]=0;
list_del(&fl->list);
freeentry(fl);
}
}
}
static void processdecays() {
brickdecay *bd, *next;
list_for_each_entry_safe(bd, next, &activedecays, list) {
++(bd->timer);
if(bd->timer == DECAYLIFE) {
field[bd->py][bd->px] = FIELD_EMPTY;
trybonus(bd->px, bd->py);
list_del(&bd->list);
freeentry(bd);
}
}
}
static void drawbombs(void) {
int j;
bomb *bmb;
struct figure *figtab;
int color;
int xpos,ypos;
list_for_each_entry(bmb, &activebombs, list) {
color=bmb->owner->color;
figtab=(bmb->type==BOMB_NORMAL) ? bombs1[color] : bombs2[color];
j=bmb->figcount % (NUMBOMBFRAMES<<1);
if(j>=NUMBOMBFRAMES) j=(NUMBOMBFRAMES<<1)-j-1;
xpos=tovideox(bmb->xpos);
ypos=tovideoy(bmb->ypos)-3;
addsprite(xpos,ypos,figtab+j);
}
}
static void drawflames(void) {
flame *fl;
int xpos,ypos;
/* no player specific flame sprites yet */
/* int color; */
int fig;
list_for_each_entry(fl, &activeflames, list) {
/* color=fl->owner->color; */
xpos=tovideox(fl->xpos);
ypos=tovideoy(fl->ypos);
fig=(fl->timer*10)/FLAMELIFE;
if(fig>=5) fig=9-fig;
fig+=5*fl->lurd;
addsprite(xpos,ypos,flamefigs[0/* color */]+fig);
}
}
static void drawdecays() {
brickdecay *bd;
list_for_each_entry(bd, &activedecays, list) {
addsprite(tovideox(bd->xpos),tovideoy(bd->ypos),
blocksx+(bd->timer*9)/DECAYLIFE);
}
}
static void drawbonus() {
bonustile *bonus;
list_for_each_entry(bonus, &activebonus, list) {
addsprite(tovideox(bonus->xpos),tovideoy(bonus->ypos),
tiles+bonus->type);
}
}
static void drawplayers() {
player *pl;
int xpos,ypos;
list_for_each_entry(pl, &activeplayers, list) {
if(!(pl->flags & FLG_DEAD)) {
if(!pl->figure)
pl->figure=walking[pl->color]+30;
xpos=tovideox(pl->xpos)+pl->fixx;
ypos=tovideoy(pl->ypos)+pl->fixy;
addsprite(xpos,ypos,pl->figure);
}
}
}
static void detonatecontrolled(player *pl) {
bomb *bmb, *next;
list_for_each_entry_safe(bmb, next, &activebombs, list) {
if(bmb->owner==pl && bmb->type==BOMB_CONTROLLED)
adddetonate(bmb);
}
}
static void playonce(generic *gen) {
if (gen->timer == gen->data1) {
list_del(&gen->list);
freeentry(gen);
}
}
static void drawgeneric(generic *gen) {
addsprite(gen->xpos,gen->ypos,((figure *)(gen->ptr1))+gen->timer);
}
static void queuesequence(int xpos,int ypos,figure *fig,int count) {
generic *gen;
gen = allocentry(generic);
if(!gen) return;
gen->xpos=xpos;
gen->ypos=ypos;
gen->data1=count;
gen->process=playonce;
gen->draw=drawgeneric;
gen->ptr1=fig;
addtail(&activegeneric,gen);
}
static void adddeath(player *pl) {
int xpos,ypos;
xpos=tovideox(pl->xpos)+pl->fixx-10;
ypos=tovideoy(pl->ypos)+pl->fixy;
queuesequence(xpos,ypos,death,NUMDEATHFRAMES);
}
static void killplayer(player *pl) {
pl->deaths++;
pl->flags|=FLG_DEAD;
playsound(2);
adddeath(pl);
detonatecontrolled(pl);
}
static void processgenerics(void) {
generic *gen, *next;
list_for_each_entry_safe(gen, next, &activegeneric, list) {
++(gen->timer);
gen->process(gen);
}
}
static void drawgenerics(void) {
generic *gen;
list_for_each_entry(gen, &activegeneric, list) {
gen->draw(gen);
}
}
static void drawstats(void) {
player *pl;
int p = 0;
const char *n;
char buf[16];
solidcopy(&background, 0, 0, IXSIZE, arraystarty);
list_for_each_entry(pl, &allplayers, list_all_players) {
if (pl->controller >= 0) {
n = netnodes[pl->controller].name;
} else {
n = playername;
}
snprintf(buf, sizeof(buf), "%-8.8s %2i/%2i", n, pl->kills % 100, pl->deaths % 100);
drawstring(11 + (p/2) * (15 * fontxsize + 7), 11 + (p%2) * (fontysize+2), buf);
p++;
}
}
static int centerxchange(player *pl) {
int speed;
int val;
int line;
int max;
max=arrayspacex<<FRACTION;
speed=pl->speed;
val=pl->xpos+(max<<2);
val%=max;
line=max>>1;
if(val<line) {
if(val-speed<0)
return -val;
else
return -speed;
} else if(val>=line) {
if(val+speed>max)
return max-val;
else
return speed;
}
return 0;
}
static void centerx(player *pl) {
pl->xpos+=centerxchange(pl);
}
static int centerychange(player *pl) {
int speed;
int val;
int line;
int max;
max=arrayspacey<<FRACTION;
speed=pl->speed;
val=pl->ypos+(max<<2);
val%=max;
line=max>>1;
if(val<line)
{
if(val-speed<0)
return -val;
else
return -speed;
} else if(val>=line)
{
if(val+speed>max)
return max-val;
else
return speed;
}
return 0;
}
static void centery(player *pl) {
pl->ypos+=centerychange(pl);
}
#define SGN(x) ((x)==0 ? 0 : ((x)<0 ? -1 : 1))
static void trymove(player *pl,int dx,int dy) {
int wx,wy;
int sx,sy;
int there;
int px,py;
static int depth=0;
int tx,ty;
++depth;
sx=(dx*(arrayspacex+1)) << (FRACTION-1);
sy=(dy*(arrayspacey+1)) << (FRACTION-1);
wx=screentoarrayx(pl->xpos+sx);
wy=screentoarrayy(pl->ypos+sy);
px=screentoarrayx(pl->xpos);
py=screentoarrayy(pl->ypos);
if(wx<0 || wx>=arraynumx || wy<0 || wy>=arraynumy) {
--depth;
return;
}
there=field[wy][wx];
if((px!=wx || py!=wy) &&
(there==FIELD_BRICK||there==FIELD_BOMB||there==FIELD_BORDER))
{
if(dx && !dy) {
ty=centerychange(pl);
if(ty && depth==1)
trymove(pl,0,-SGN(ty));
} else if(dy && !dx) {
tx=centerxchange(pl);
if(tx && depth==1)
trymove(pl,-SGN(tx),0);
}
} else {
pl->xpos+=dx*pl->speed;
pl->ypos+=dy*pl->speed;
if(dx && !dy) centery(pl);
if(dy && !dx) centerx(pl);
}
--depth;
}
static void applybonus(player *pl,bonustile *bonus) {
int type;
int maxflame;
maxflame=arraynumx>arraynumy ? arraynumx : arraynumy;
type=bonus->type;
deletebonus(bonus);
switch(type) {
case TILE_BOMB:
if (pl->bombsavailable < 9)
++(pl->bombsavailable);
break;
case TILE_FLAME:
if(pl->flamelength<maxflame)
++(pl->flamelength);
break;
case TILE_GOLDFLAME:
pl->flamelength=maxflame;
break;
case TILE_TRIGGER:
pl->flags|=FLG_CONTROL;
break;
case TILE_SKATES:
if (pl->speed < SPEEDSTART) {
pl->speed = SPEEDSTART;
} else {
pl->speed+=SPEEDDELTA;
}
if(pl->speed>SPEEDMAX) pl->speed=SPEEDMAX;
break;
case TILE_TURTLE:
pl->speed=SPEEDTURTLE;
pl->speedturtle_timeout=SPEEDTURTLE_TIMEOUT;
break;
}
}
static void doplayer(player *pl) {
/* int last; */
int color;
/* int speed; */
int px,py;
int there;
int flags;
int what;
if(pl->controller==-1)
what=myaction;
else
what=actions[pl->controller];
flags=pl->flags;
if(flags&FLG_DEAD) return;
color=pl->color;
/* last=pl->doing; */
pl->doing=what;
/* speed=pl->speed; */
px=screentoarrayx(pl->xpos);
py=screentoarrayy(pl->ypos);
there=field[py][px];
if(what==ACT_QUIT) {
killplayer(pl);
list_del(&pl->list_all_players);
return;
}
if(there==FIELD_BONUS) {
playsound((myrand()&1) ? 1 : 5);
applybonus(pl,info[py][px]);
} else if(there==FIELD_FLAME) {
flame *fl = info[py][px];
if (fl->owner == pl) {
pl->kills--;
} else {
fl->owner->kills++;
}
killplayer(pl);
return;
}
// if(what&ACT_TURBO) speed<<=2;
if(what&ACT_PRIMARY) {
if(there==FIELD_EMPTY && pl->bombsavailable)
dropbomb(pl,px,py,
(flags&FLG_CONTROL) ? BOMB_CONTROLLED :BOMB_NORMAL);
}
if(what&ACT_SECONDARY && (flags&FLG_CONTROL))
detonatecontrolled(pl);
switch(what&ACT_MASK) {
case ACT_UP:
trymove(pl,0,-1);
pl->figcount=(pl->figcount+1)%15;
pl->figure=walking[color]+pl->figcount+15;
break;
case ACT_DOWN:
trymove(pl,0,1);
pl->figcount=(pl->figcount+1)%15;
pl->figure=walking[color]+pl->figcount+30;
break;
case ACT_LEFT:
trymove(pl,-1,0);
pl->figcount=(pl->figcount+1)%15;
pl->figure=walking[color]+pl->figcount+45;
break;
case ACT_RIGHT:
trymove(pl,1,0);
pl->figcount=(pl->figcount+1)%15;
pl->figure=walking[color]+pl->figcount;
break;
case ACT_NONE:
break;
}
if (pl->speedturtle_timeout > 0) {
pl->speedturtle_timeout--;
if (0 == pl->speedturtle_timeout) {
if (pl->speed < SPEEDSTART) pl->speed = SPEEDSTART;
}
}
}
static void processplayers(void) {
player *pl, *next;
list_for_each_entry_safe(pl, next, &activeplayers, list) {
doplayer(pl);
}
}
static void processquits(void) {
int i;
if (network != NETWORK_SLAVE) return;
for (i = 0; i < MAXNETNODES; ++i) {
if (netnodes[i].used && actions[i]==ACT_QUIT)
netnodes[i].used=0;
}
}
static int getaction(void) {
int what;
what=ACT_NONE;
if(checkpressed(MYLEFT)) what=ACT_LEFT;
else if(checkpressed(MYRIGHT)) what=ACT_RIGHT;
else if(checkpressed(MYDOWN)) what=ACT_DOWN;
else if(checkpressed(MYUP)) what=ACT_UP;
else if(checkdown(13)) what=ACT_ENTER;
else if(checkdown(0x1b)) what=ACT_QUIT;
if(checkdown(' '))
what|=ACT_PRIMARY;
if(checkdown('b'))
what|=ACT_SECONDARY;
return what;
}
static int iterate(void) {
int i;
int gountil; /* destination tick */
static int deathcount = 0;
mypause();
scaninput();
erasesprites();
clearspritelist();
gfxunlock();
myaction = getaction();
if (NETWORK_NONE == network && myaction==ACT_QUIT) return CODE_QUIT;
if (NETWORK_NONE == network) {
gountil = mycount + 1; /* single step in singly player mode */
} else {
gountil = networktraffic(); /* as master single step, as slave as many as we can do */
}
while (mycount < gountil) { /* simulate ticks up to gountil */
++mycount;
if (NETWORK_NONE != network) {
i = gountil - mycount;
if(i >= ACTIONHIST) // too far behind
goto leave_game;
memcpy(actions, actionblock+i*MAXNETNODES, MAXNETNODES);
if (actions[myslot] == ACT_QUIT) return CODE_QUIT;
}
processbombs();
dodetonations();
processdecays();
processflames();
processgenerics();
processquits();
processplayers();
}
/*
if(!(rand()&127))
{
i=gtime();
while(gtime()-i<100);
}
*/
drawbombs();
drawbonus();
drawgenerics();
drawdecays();
drawflames();
drawplayers();
drawstats();
plotsprites();
copyup();
if (list_empty(&activegeneric)) {
player *pl;
int deadplayers = 0;
i = 0;
list_for_each_entry(pl, &activeplayers, list) {
if(!(pl->flags & FLG_DEAD))
++i;
else
deadplayers++;
}
if (deadplayers > 0 && (!i || (NETWORK_NONE != network && i==1))) {
++deathcount;
if(deathcount==25)
return CODE_ALLDEAD;
} else
deathcount=0;
}
return CODE_CONT;
leave_game: /* client disconnect/timeout: send ACT_QUIT to master */
myaction = ACT_QUIT;
networktraffic();
return CODE_QUIT;
}
void set_game_options(GameOptions *options) {
gameoptions = *options;
}
void run_single_player(void) {
int code;
network = NETWORK_NONE;
firstzero();
initplayers();
do {
initgame();
while(!(code=iterate()) && !exitflag) ++framecount;
} while (code != CODE_QUIT && !exitflag);
}
void run_network_game(void) {
int code;
firstzero();
initplayers();
do {
initgame();
while (!(code=iterate()) && !exitflag) ++framecount;
} while (code != CODE_QUIT && !exitflag);
}