#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(xlist_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=comp ? FIELD_BRICK : FIELD_EMPTY; } solidcopyany(&backgroundoriginal,&background,0,0,IXSIZE,IYSIZE); resetplayers(); for(j=0;jpx=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(pxlurd|=FL_RIGHT; fl2->lurd|=FL_LEFT; } if(pylurd|=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<speed; val=pl->xpos+(max<<2); val%=max; line=max>>1; 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<speed; val=pl->ypos+(max<<2); val%=max; line=max>>1; 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->flamelengthflamelength); 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); }