/* arena.cpp - The arena is the place in which everything happens Copyright (C) 2006-2007 Mark boyd This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be fun to play, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "arena.h" #include "loadimag.h" #include "etc.h" #include "movers.h" #include "game.h" #include "sound.h" #include "enemies.h" /* take_hit */ #include "debug.h" class tractor_indicator:public mover { public: tractor_indicator(); SDL_Surface *pic() const {return m_pic;} void move(); private: SDL_Surface *m_pic; }; tractor_indicator::tractor_indicator() { m_mass=0; m_pic = game::the_game->m_bmp_pool.get_bitmap("tractor.png"); } void tractor_indicator::move() { //do nothing - the arena will "move" us later. } class force_indicator:public mover { public: force_indicator(); SDL_Surface *pic() const {return m_pic;} private: void do_stuff(float time_interval); SDL_Surface *m_pic; float m_life_expectancy; }; force_indicator::force_indicator() { m_pic = game::the_game->m_bmp_pool.get_bitmap("wcircle50.png"); m_life_expectancy=0.15; m_mass=0; } void force_indicator::do_stuff(float time_interval) { m_life_expectancy-=time_interval; if (m_life_expectancy<=0) die(); } #define ARENA(x,y) y, const char* image_files[]= { #include "arena_table.h" }; #undef ARENA arena::arena() { int nfiles=sizeof(image_files)/sizeof(*image_files); int negative_index=-1; for(int i=0; iw > TILE_SIZE || bmp->h > TILE_SIZE)) { //Oversized bitmap //cut it up into suitably sized chunks. //The arena_t enum starts at 0. So we use the negative numbers to index extra tiles //generated by cutting up large bitmaps //Force alpha info to be copied (rather than used!) SDL_SetAlpha(bmp, 0, 0); extra_bmps eb; eb.m_nx=(bmp->w+TILE_SIZE-1)/TILE_SIZE; eb.m_ny=(bmp->h+TILE_SIZE-1)/TILE_SIZE; //Initialise to "all zeros" - Bleagh! eb.m_bmps=std::vector >(eb.m_nx, std::vector(eb.m_ny, 0)); for (int x=0; x::iterator i=m_bitmaps.begin(); i!=m_bitmaps.end(); ++i) { if (i->second) SDL_FreeSurface(i->second); } if (m_background) SDL_FreeSurface(m_background); destroy_everything(); } void arena::destroy_everything() { for(int i=0; iformat, 0,0,0,SDL_ALPHA_OPAQUE)); SDL_UnlockSurface(m_background); } void arena::set(int x, int y, arena_t t) { if (m_extra_bmps.find(t) == m_extra_bmps.end()) { //normal case m_scenery[x][y]=t; } else { //Original bitmap is oversized and was cut up, so add all extra bitmaps extra_bmps eb=m_extra_bmps.find(t)->second; for(int ex=0; exteleport(x*TILE_SIZE,y*TILE_SIZE); ball_src bs={x,y,b}; m_movers.push_back(b); m_ball_srcs.push_back(bs); } } ship* the_ship=new ship; the_ship->teleport(TILE_SIZE*m_player_spawn_x, TILE_SIZE*m_player_spawn_y); add_mover(the_ship); m_player_alive=true; update_stuff(); } void arena::add_mover(mover *m) { m_waiting_room.push_back(m); if (dynamic_cast(m)) { m_player_x=int(m->x_centre()); m_player_y=int(m->y_centre()); } } //This function is called at an undefined rate //so don't implement crude timers by incrementing an integer counter void arena::update_stuff() { //Handle newly created movers for(int i=0; i=0; --i) { if (m_movers[i]->is_dead()) { if (dynamic_cast(m_movers[i])) { m_player_alive=false; game::the_game->m_player_status.lives--; m_player_respawn=100; } untractor(m_movers[i]); delete m_movers[i]; m_movers.erase(m_movers.begin()+i); } } //Dead players sometimes come back to life if (m_player_respawn) { m_player_respawn--; if (!m_player_respawn && game::the_game->m_player_status.lives>0) { //TODO: think about where to respawn the player //This code puts him back at the same spot where he died. //This may be a very dangerous place. ship *s=new ship; s->teleport_centre(m_player_x, m_player_y); add_mover(s); game::the_game->m_player_status.shield=MAX_SHIELD; game::the_game->m_player_status.invulnerability=MAX_INVULNERABILITY; } } //Abominable hack: update ship's mass //todo: put this somewhere else! for(int i=0; i(m_movers[i])) { m_movers[i]->m_mass=calc_ship_mass(); } } } arena_t arena::get(int x, int y) const { if (x<0 || x>=width() || y<0 || y>=height()) { DBG_LOG("warning: Invalid arena coords: ("+int2str(x)+","+int2str(y)+")"); return A_EMPTY; } return arena_t(m_scenery[x][y]); } Uint8 arena::getalpha(int x, int y) const { arena_t block=get(x/TILE_SIZE, y/TILE_SIZE); Uint8 r,g,b,a; if (m_bitmaps.find(block) != m_bitmaps.end() && m_bitmaps.find(block)->second) { SDL_Surface *bmp=m_bitmaps.find(block)->second; SDL_GetRGBA(getpixel(bmp, x%TILE_SIZE, y%TILE_SIZE), bmp->format, &r,&g,&b,&a); return a; } return SDL_ALPHA_TRANSPARENT; } mover* arena::get_nearest_tractor_target(float x, float y) { float min_dis=100; int min_ind=-1; for(int i=0; ix_centre(), y-m_movers[i]->y_centre()); //todo: maybe there should be an is_tractorable function? if (distance < min_dis && (dynamic_cast(m_movers[i]) || dynamic_cast(m_movers[i]) || dynamic_cast(m_movers[i]) || dynamic_cast(m_movers[i]))) { min_ind=i; min_dis=distance; } } return min_ind==-1?0:m_movers[min_ind]; } void arena::apply_forces(float time_interval) { for(int i=0; im_x - m_tractors[i].from->m_x; float y_dis = m_tractors[i].to->m_y - m_tractors[i].from->m_y; //float dis=std::sqrt(x_dis*x_dis+y_dis*y_dis); m.m_dx += time_interval*x_dis*tractor_force/(m.m_mass*tractor_elastic)*mul; m.m_dy += time_interval*y_dis*tractor_force/(m.m_mass*tractor_elastic)*mul; } } //Explosions! //Don't multiply be time_interval because an expolosion applies all its force (err.. maybe impulse then) //at one instant, rather than acting over time if (m.m_mass >0 && !m.is_stuck()) { for (int i=0; i &arena::movers() const { return m_movers; } void arena::create_tractor(mover *from, mover *to) { tractor t={from,to}; for(int i=0; i<10; ++i) { t.indicators.push_back(new tractor_indicator); add_mover(t.indicators.back()); } m_tractors.push_back(t); //balls become unstuck when tractored ball* b=dynamic_cast(to); if(b) b->unstick(); } void arena::destroy_tractor(mover *m) { untractor(m); } void arena::do_stuff(float time_interval) { for(int i=0; ido_stuff(time_interval); } update_stuff(); } void arena::move_stuff_and_do_collisions(float seconds) { //Scenery animations (does this really belong in move_stuff?) const Uint32 black=SDL_MapRGBA(m_background->format,0,0,0,SDL_ALPHA_OPAQUE); for(int i=0; i=0) { if (m_extra_bmps.find(an.frames[an.current]) == m_extra_bmps.end()) { //Easy case: there's just 1 tile m_scenery[an.x][an.y]=an.frames[an.current]; SDL_Rect to={an.x*TILE_SIZE, an.y*TILE_SIZE, TILE_SIZE, TILE_SIZE}; SDL_FillRect(m_background, &to, black); SDL_BlitSurface(m_bitmaps[get(an.x,an.y)], 0, m_background, &to); SDL_UpdateRect(m_background,to.x,to.y,to.w,to.h); } else { //Animation involves oversized objects -> use m_extra_bitmaps const extra_bmps &eb=m_extra_bmps[an.frames[an.current]]; SDL_Rect to={an.x*TILE_SIZE, an.y*TILE_SIZE, eb.m_nx*TILE_SIZE, eb.m_ny*TILE_SIZE}; SDL_FillRect(m_background, &to, black); for(int x=0; x= an.frames.size()) { //This animation is finished! m_animations.erase(m_animations.begin()+i); --i; } } //move_stuff is split into 4 to prevent nasty effects such as rockets passing through walls. // //todo: see if this is slow (now collisions are fixed, it doesn't appear to be) move_stuff(seconds/4.0); do_collisions(); move_stuff(seconds/4.0); do_collisions(); move_stuff(seconds/4.0); do_collisions(); move_stuff(seconds/4.0); do_collisions(); } void arena::do_collisions() { //mover-scenery collisions for(int i=0; idoes_arena_collisions()) { arena_t obstacle=A_EMPTY; //What the mover hit int cx,cy; //where it hit if (!m->is_stuck() && arena_collision(m->pic(), int(m->m_x), int(m->m_y), &obstacle, &cx, &cy)) { handle_collision(m, obstacle, cx, cy); m->notify_collision(obstacle); } } } //mover-mover collisions for(int i1=0; i1does_collisions(m2) || m2->does_collisions(m1)) { //bounding rectangle test int xmin=int(std::max(m1->m_x, m2->m_x)); int xmax=int(std::min(m1->m_x+m1->pic()->w, m2->m_x+m2->pic()->w)); int ymin=int(std::max(m1->m_y, m2->m_y)); int ymax=int(std::min(m1->m_y+m1->pic()->h, m2->m_y+m2->pic()->h)); if (xminpic(); SDL_LockSurface(p1); SDL_Surface *p2=m2->pic(); SDL_LockSurface(p2); for(int x=xmin; xm_x),y-int(m1->m_y)), p1->format, &r, &g, &b, &a1); SDL_GetRGBA(getpixel(p2,x-int(m2->m_x),y-int(m2->m_y)), p2->format, &r, &g, &b, &a2); if (a1 != SDL_ALPHA_TRANSPARENT && a2 != SDL_ALPHA_TRANSPARENT) { ouch=true; goto done2; } } done2: SDL_UnlockSurface(p2); SDL_UnlockSurface(p1); if (ouch) { if (m1->does_collisions(m2)) m1->notify_collision(m2); if (m2->does_collisions(m1)) m2->notify_collision(m1); } } } } } update_stuff(); } bool arena::arena_collision(SDL_Surface *p, int px, int py, arena_t *obstacle, int *tile_x, int *tile_y, int *pic_x, int *pic_y) const { //Quick test: If the mover is entirely inside empty squares, //then of course there is no collision. for (int x=std::max(0,px/TILE_SIZE); xw)/TILE_SIZE+1); ++x) for (int y=std::max(0,py/TILE_SIZE); yh)/TILE_SIZE+1); ++y) if (get(x,y)) goto not_so_quick_test; return false; //no need to do any more not_so_quick_test: //This is putting it mildly. bool collision=false; SDL_LockSurface(p); //*Very* nasty hack: search through y backwards. This is so that if the //ship is in the shop, and about to fall into the blocks below, we'll register //a block collision, and not a shop collision. //(If shops were attached to the ceiling and not the floor, this wouldn't work) // //Todo: implement partial quick test? (currently we look in all tiles even //if only one is nonempty; this is dumb) for(int y=p->h-1; y>=0; --y) for(int x=0; xw; ++x) { float adj_x=x+px, adj_y=y+py; int arena_x=minmax(0, int(adj_x/32), width()-1); int arena_y=minmax(0, int(adj_y/32), height()-1); if (get(arena_x,arena_y) && get(arena_x,arena_y)!=A_SOURCE) //Evil hack! { Uint8 r,g,b,a1,a2; SDL_GetRGBA(getpixel(p,x,y), p->format, &r, &g, &b, &a1); a2=getalpha(minmax(0, int(adj_x), width()*32-1) , minmax(0, int(adj_y), height()*32-1)); if (a1 != SDL_ALPHA_TRANSPARENT && a2 != SDL_ALPHA_TRANSPARENT) { collision = true; if (tile_x) *tile_x=arena_x; if (tile_y) *tile_y=arena_y; if (pic_x) *pic_x=x; if (pic_y) *pic_y=y; if (obstacle) *obstacle = m_orig_bitmap.find(get(arena_x,arena_y))->second; //ob_x=arena_x, ob_y=arena_y; goto done; } } } done: SDL_UnlockSurface(p); return collision; } void arena::handle_collision(mover *m, arena_t obstacle, int x, int y) { if (dynamic_cast(m)) { ball *b=dynamic_cast(m); untractor(b); if (obstacle == A_SINK) { b->stick(); //Eugh... do something better than this! //Have all balls been placed? bool finished = true; for(int i=0; iis_stuck()) || m_ball_srcs[i].b->m_x == m_ball_srcs[i].x*TILE_SIZE || m_ball_srcs[i].b->m_y == m_ball_srcs[i].y*TILE_SIZE) { finished = false; break; } } if (finished) { //points for completing this level //todo: maybe award bonus points for completing it well game::the_game->m_player_status.score+=1000; open_exit(); } } else //ball hit something other than sink - break it! { //Put it back on its starting position //(in-game, this is a replacement) for (int bsi=0; bsim_exp_pool.get_explosion("ball", b->pic()); e->teleport_centre(b->x_centre(), b->y_centre()); add_mover(e); b->stick(); b->teleport(m_ball_srcs[bsi].x*32,m_ball_srcs[bsi].y*32); } } } } if (dynamic_cast(m)) { switch(obstacle) { case A_BUTTON_L: add_animation(x,y, (make >(), A_BUTTON_L_ANIM1, A_BUTTON_L_ANIM2, A_BUTTON_L_ANIM1, A_BUTTON_L)); do_causality(x,y); break; case A_BUTTON_U: add_animation(x,y, (make >(), A_BUTTON_U_ANIM1, A_BUTTON_U_ANIM2, A_BUTTON_U_ANIM1, A_BUTTON_U)); do_causality(x,y); break; case A_BUTTON_R: add_animation(x,y, (make >(), A_BUTTON_R_ANIM1, A_BUTTON_R_ANIM2, A_BUTTON_R_ANIM1, A_BUTTON_R)); do_causality(x,y); break; case A_BUTTON_D: add_animation(x,y, (make >(), A_BUTTON_D_ANIM1, A_BUTTON_D_ANIM2, A_BUTTON_D_ANIM1, A_BUTTON_D)); do_causality(x,y); break; default: break; } } } void arena::open_exit() { //BOOM! //make balls, sink and exit blocks explode! for(int i=0; im_exp_pool.get_explosion(std::string("ball"), tb->pic()); e->teleport_centre(tb->x_centre(), tb->y_centre()); add_mover(e); m_ball_srcs[i].b=0; tb->die(); } SDL_Rect blank={0,0,TILE_SIZE,TILE_SIZE}; const Uint32 black=SDL_MapRGBA(m_background->format,0,0,0,SDL_ALPHA_OPAQUE); for(int x=0; xm_exp_pool.get_explosion(std::string("block"), m_bitmaps[A_EXIT_BLOCK]); e->teleport_centre(x*TILE_SIZE +TILE_SIZE/2, y*TILE_SIZE+TILE_SIZE/2); add_mover(e); } blank.x=TILE_SIZE*x;blank.y=TILE_SIZE*y; SDL_FillRect(m_background, &blank, black); SDL_UpdateRect(m_background,blank.x,blank.y,blank.w,blank.h); if (get(x,y) == A_SINK) { SDL_Surface *sink=game::the_game->m_bmp_pool.get_bitmap("sink.png"); explosion *e=game::the_game->m_exp_pool.get_explosion(std::string("sink"), sink); e->teleport_centre(x*TILE_SIZE+sink->w/2, y*TILE_SIZE+sink->h/2); add_mover(e); } set(x,y,A_EMPTY); } } } void arena::apply_effect(int x, int y) { switch(get(x,y)) { case A_VGATE_CLOSED: add_animation(x, y, (make >(), A_VGATE_ANIM_1, A_VGATE_ANIM_2, A_VGATE_ANIM_3, A_VGATE_ANIM_4, A_VGATE_ANIM_5, A_VGATE_OPEN)); break; case A_VGATE_OPEN: add_animation(x, y, (make >(), A_VGATE_ANIM_5, A_VGATE_ANIM_4, A_VGATE_ANIM_3, A_VGATE_ANIM_2, A_VGATE_ANIM_1, A_VGATE_CLOSED)); break; case A_HGATE_CLOSED: add_animation(x, y, (make >(), A_HGATE_ANIM_1, A_HGATE_ANIM_2, A_HGATE_ANIM_3, A_HGATE_ANIM_4, A_HGATE_ANIM_5, A_HGATE_OPEN)); break; case A_HGATE_OPEN: add_animation(x, y, (make >(), A_HGATE_ANIM_5, A_HGATE_ANIM_4, A_HGATE_ANIM_3, A_HGATE_ANIM_2, A_HGATE_ANIM_1, A_HGATE_CLOSED)); break; default: //todo: warn? break; } } void arena::do_causality(int x, int y) { for(int i=0; i &frames) { animation an={x,y,frames,0}; m_animations.push_back(an); } void arena::move_stuff(float time_interval) { //movers for(int i=0; imove(time_interval); if (dynamic_cast(m_movers[i])) { m_player_x=int(m_movers[i]->x_centre()); m_player_y=int(m_movers[i]->y_centre()); } } //Here is a big ugly cheat: "move" tractor indicators for(int ti=0; titeleport_centre( (m_tractors[ti].from->x_centre()*tr+m_tractors[ti].to->x_centre()*(n-1-tr))/(n-1), (m_tractors[ti].from->y_centre()*tr+m_tractors[ti].to->y_centre()*(n-1-tr))/(n-1) ); } } } void arena::untractor(mover *m) { for (int bi=0; biroughly_untractor(); m_tractors[bi].from->roughly_untractor(); for (int ind=0; inddie(); } m_tractors.erase(m_tractors.begin()+bi); --bi; } } } mover *arena::get_player_target() { //todo: avoid search! for(int i=0; i(m_movers[i])) return m_movers[i]; return 0; } void arena::AOE_damage(float x, float y, float radius, int dam_centre, int dam_edge, damage_type dt) { if (radius <=0) return; //WTF? for(int i=0; iis_dead()) { //Distance between enemy and explosion //Don't just use distance between centres because some enemies (bosses) //are so big that they can be hit by a rocket but still have their centre //outside. //Use the closest point of the enemy (actaully, closest point of the // rectangle containing it) to the centre. float distance = dist(minmax(m->m_x, x, m->m_x+m->pic()->w)-x, minmax(m->m_y, y, m->m_y+m->pic()->h)-y); if (distance < radius) { if (dt & HURT_ENEMIES) { enemy *en = dynamic_cast(m); if (en) { en->take_hit(int(dam_centre - distance*(dam_centre-dam_edge)/radius)); } } if (dt & HURT_PLAYER) { ship *sh = dynamic_cast(m); if (sh) { sh->take_hit(int(dam_centre - distance*(dam_centre-dam_edge)/radius)); } } } } } } void arena::AOE_force(float x, float y, float radius, float force_centre, float force_edge) { force_ball fb={x,y,radius,force_centre, force_edge}; m_force_balls.push_back(fb); force_indicator *fi=new force_indicator; fi->teleport_centre(x,y); add_mover(fi); } bool arena::level_complete() const { //Player outside arena! return m_player_x<0 || m_player_x>width()*TILE_SIZE || m_player_y<0 || m_player_y>height()*TILE_SIZE; } bool arena::player_is_dead() const { return game::the_game->m_player_status.lives==0 && m_player_respawn==0 && !m_player_alive; }