#include "game.h" #include "bomber.h" #include "draw.h" #include "gfx.h" #include "list.h" #include "menu.h" #include "network.h" #include "sound.h" #include "utils.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[17]; 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); }