/* * action.c * * Handle action queue events */ #include #include #include #include #include #include "graphics.h" #include "grv.h" /* ------------------------------------------------------------------------ * * Primary action event queue machinery * ------------------------------------------------------------------------ */ struct action { int x, y; enum actions what; double when; uint64_t seq; }; #define MAXACT 80 static struct action actions[MAXACT]; static int action_count; static uint64_t next_seq = 0; void addaction(int x, int y, double when, enum actions what) { int a, b; int i; if ( action_count >= MAXACT ) return; a = 0; b = action_count; while (b > a) { i = (a+b) >> 1; if (actions[i].when <= when) a = i+1; else b = i; } if (a < action_count) memmove(&actions[a+1], &actions[a], (action_count-a)*sizeof(struct action)); actions[a].x = x; actions[a].y = y; actions[a].when = when; actions[a].what = what; actions[a].seq = next_seq++; action_count++; } /* Remove any action which matches the given (x,y,what) */ void removeaction(int x, int y, enum actions what) { int i = 0; while ( i < action_count ) { if ( actions[i].x == x && actions[i].y == y && actions[i].what == what ) { memmove(&actions[i], &actions[i+1], (action_count-i-1)*sizeof(struct action)); action_count--; } else { i++; } } } void reset_actions(void) { action_count = 0; } static int sort_by_time(const void *a, const void *b) { const struct action *aa = (const struct action *)a; const struct action *bb = (const struct action *)b; /* This *must* be a stable sort! */ if ( aa->when == bb->when ) return (aa->seq < bb->seq) ? -1 : 1; else return (aa->when < bb->when) ? -1 : 1; } /* This changes the time of *all* actions of a specifc type to "when" */ void retime_all(double when, enum actions what) { int i; for ( i = 0 ; i < action_count ; i++ ) { if ( actions[i].what == what ) actions[i].when = when; } qsort(actions, action_count, sizeof(struct action), sort_by_time); } /* ------------------------------------------------------------------------ * * Stuff that gets triggered by action events... * ------------------------------------------------------------------------ */ /* Blow bomb or smash */ static void blow_bomb(int x, int y, int smash) { /* The first parameter is how far above and below (x,y) does this happen? */ static const int bomb_pattern[] = { 5, 0, 0, 2, 2, 3, 3, 3, 2, 2, 0, 0 }; static const int smash_pattern[] = { 6, 0, 2, 3, 4, 4, 5, 5, 5, 4, 4, 3, 2, 0 }; static const int big_smash_pattern[] = { 9, 0, 2, 4, 5, 6, 6, 7, 7, 7, 7, 7, 7, 7, 6, 6, 5, 4, 2, 0 }; const int *pattern; int path, patw; int x2, y2; int ch, m, f, fl; int erase; if ( !smash ) gp.Bombs--; pattern = smash ? (rnd() < 0.1 ? big_smash_pattern : smash_pattern) : bomb_pattern; patw = *pattern++; /* Note the order in which this happens. We escavate from the bottom up in each column; this means that when we run into rocks we can just let them fall. We can also activate rocks and apples above the hole at the end of each x2 loop. This was a bug in the original BASIC version... it didn't do this. */ for ( y2 = y-patw ; y2 <= y+patw ; y2++ ) { path = *pattern++; if ( y2 < 1 || y2 > 40 ) continue; for ( x2 = x+path ; x2 >= x-path ; x2-- ) { if ( x2 < 2 || x2 > 23 ) continue; ch = screen(x2,y2); m = fg(x2,y2); fl = screen1(x2,y2) & 0x80; /* Flashing? */ erase = 1; switch ( ch ) { case SYM_PLAYER: if ( smash ) erase = 0; else gp.Status = Status_Dead; break; case SYM_GHOST: kill_ghost(x2,y2); break; case SYM_CHERRY: take_cherry(); break; case SYM_BONUS: if ( gp.Bon ) gp.Sc += 15*gp.Level; /* This sucks :) */ break; case SYM_APPLE: erase = 0; color(m+16, 0); lprint(x2,y2,"\xfe"); if ( !fl ) addaction(x2, y2, gp.Tid+30.0/(gp.Level+1), act_apple); break; case SYM_BOMB: case 'H': case 'Y': case 'P': case 'E': case 'R': /* Don't erase, but put it on dark background */ if ( x2 != x || y2 != y ) { erase = 0; color(m + (fl ? 16 : 0), 0); lprintf(x2,y2,"%c",ch); } break; case SYM_ROCK: fall_rock(x2,y2); erase = 0; break; default: break; } if ( erase ) { color(m, 0); lprint(x2,y2," "); } } /* See if there is something above this row that should be activated */ if ( x2 >= 2 && x2 <= 23 ) { ch = screen(x2,y2); m = fg(x2,y2); f = bg(x2,y2); fl = screen1(x2,y2) & 0x80; /* Flashing? */ if ( ch == SYM_ROCK ) { fall_rock(x2,y2); } else if ( ch == SYM_APPLE && !fl ) { color(16+m,f); lprint(x2,y2,"\xfe"); addaction(x2, y2, gp.Tid+30.0/(gp.Level+1), act_apple); } } } update_score(); } /* Turn on or off the bonus dots */ static void bonus_on(void) { gp.Bon = 1; color(30,0); lprint(25, 1, " BONUS "); addaction(0, 0, gp.Tid+12.0, act_bonus_off); } static void bonus_off(void) { gp.Bon = 0; color(0, gp.c); lprint(25, 1, " "); addaction(0, 0, gp.Tid+120.0, act_bonus_on); } /* ------------------------------------------------------------------------ * * Action event queue trigger function * ------------------------------------------------------------------------ */ void handle_action(void) { struct action action; if ( !action_count || actions[0].when > gp.Tid ) return; /* No action currently pending */ action = actions[0]; /* Remove from queue */ memmove(&actions[0], &actions[1], (--action_count)*sizeof(struct action)); switch ( action.what ) { case act_none: break; case act_apple: fall_apple(action.x, action.y); break; case act_bomb: blow_bomb(action.x, action.y, 0); break; case act_bonus_on: bonus_on(); break; case act_bonus_off: bonus_off(); break; case act_smash: blow_bomb(gp.x, gp.y, 1); break; case act_toolate: color(20,0); lprint(12,14," OUT OF TIME! "); gp.Status = Status_Dead; break; case act_droplevel: gp.ZLevel--; update_jump(); if ( gp.ZLevel > 0 ) addaction(0, 0, gp.Tid+10.0, act_droplevel); break; case act_door: /* Bombs can blow holes in doors, so don't assume the door is still there */ if ( strchr(DOORS, screen(action.x,action.y)) ) { color(fg(action.x,action.y), bg(action.x,action.y)); lprint(action.x,action.y," "); if ( screen(action.x-1,action.y) == SYM_ROCK ) fall_rock(action.x-1,action.y); } break; case act_ghost_thaw: { int e; color(28,0); for ( e = 0 ; e < MAXGHOST ; e++ ) { if ( !ghost[e].dead ) { lprint(ghost[e].x, ghost[e].y, "\x02"); } } } break; case act_ghost_normal: gp.FS = 1; /* Normal ghosts */ break; } }