/* movers.cpp - some sprites, including the player ship 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 #include #include #include #include "movers.h" #include "etc.h" #include "game.h" #include "loadimag.h" #include "arena.h" #include "enemies.h" #include "sound.h" #include "debug.h" float mover::x_centre() const {return m_x+(pic()->w)/2;} float mover::y_centre() const {return m_y+(pic()->h)/2;} void mover::teleport_centre(float x, float y) { m_x=x-(pic()->w+1)/2; m_y=y-(pic()->h+1)/2; } //Exhaust trail from the player ship - pure eye-candy class exhaust:public mover { public: exhaust(); ~exhaust(); void do_stuff(float time_interval); SDL_Surface* pic() const{return m_pic;} private: SDL_Surface* m_pic; float m_life_expectancy; }; ship::ship() { SDL_Surface *orig=load_image("ship.png"); int w=orig->w*3/2,h=orig->h*3/2; for (int i=0; i<32; ++i) { m_pics[i]=create_surface(w, h); rotate(orig, m_pics[i], 2.0*pi*i/32); } m_angle=0; m_pic_index=0; m_tractor_on=false; m_mass = calc_ship_mass(); //Note: this will change as equipment is added (or removed) m_shop_timeout=0; m_last_interval=0; } void ship::do_stuff(float time_interval) { game &g=*(game::the_game); //Regenerate energy //I'm not totaly sure this belongs here g.m_player_status.max_energy=calc_ship_battery(); g.m_player_status.energy=std::min(g.m_player_status.max_energy, g.m_player_status.energy+calc_ship_generate()); if (g.m_key_flags.up) { float thrust=engine_types()[game::the_game->m_engine].thrust; m_dy-=time_interval*thrust*std::cos(m_angle)/m_mass; m_dx+=time_interval*thrust*std::sin(m_angle)/m_mass; const float exhaust_x1=-16; const float exhaust_x2=16; const float exhaust_y=16; const float exhaust_speed=thrust/50.0; { exhaust *e=new exhaust(); float ex = x_centre()+cos(m_angle)*exhaust_x1-sin(m_angle)*exhaust_y; float ey = y_centre()+sin(m_angle)*exhaust_x1+cos(m_angle)*exhaust_y; e->teleport_centre(ex,ey); e->m_dx=m_dx; e->m_dy=m_dy; float fudged_angle=m_angle+0.4*(randu()-0.5); e->m_dy+=exhaust_speed*std::cos(fudged_angle); e->m_dx-=exhaust_speed*std::sin(fudged_angle); g.m_arena->add_mover(e); } { exhaust *e=new exhaust(); float ex = x_centre()+cos(m_angle)*exhaust_x2-sin(m_angle)*exhaust_y; float ey = y_centre()+sin(m_angle)*exhaust_x2+cos(m_angle)*exhaust_y; e->teleport_centre(ex,ey); e->m_dx=m_dx; e->m_dy=m_dy; float fudged_angle=m_angle+0.4*(randu()-0.5); e->m_dy+=exhaust_speed*std::cos(fudged_angle); e->m_dx-=exhaust_speed*std::sin(fudged_angle); g.m_arena->add_mover(e); } } if (g.m_key_flags.right-g.m_key_flags.left) { m_angle+=d_angle*int(g.m_key_flags.right-g.m_key_flags.left); if(m_angle>2.0*pi) m_angle-=2.0*pi; if(m_angle<0) m_angle+=2.0*pi; m_pic_index=std::max(0,std::min(31,int(0.5+m_angle*32.0/2.0/pi))); } if (g.m_key_flags.tractor) { g.m_key_flags.tractor=0; //handled if (m_tractor_on) { g.m_arena->destroy_tractor(this); m_tractor_on=false; } else { mover *tractoree = g.m_arena->get_nearest_tractor_target(x_centre(), y_centre()); if (tractoree) { g.m_arena->create_tractor(this, tractoree); m_tractor_on=true; } } } if (g.m_key_flags.fire) { fire_weapons(); } for(int i=0; im_weapon_mounts.size(); ++i) { weapon_mount &m=game::the_game->m_weapon_mounts[i]; if (m.reload >0) m.reload--; } if (m_shop_timeout >0) m_shop_timeout--; } void ship::move(float t) { m_last_interval=t; mover::move(t); } SDL_Surface *ship::pic() const { return m_pics[m_pic_index]; } void ship::notify_collision(arena_t what) { float restitution=0.8; //"colliding" with the shop enters it. if (what==A_SHOP) { if (m_shop_timeout == 0) { game::the_game->m_player_status.in_shop=true; m_dx=0;m_dy=-4;m_angle=0;m_pic_index=0; m_shop_timeout=100; } } else { //Special collision function which ignores the shop struct _{static bool noshop_coll(SDL_Surface *p, int x, int y) { arena_t obs; return game::the_game->m_arena->arena_collision(p,x,y,&obs) && (obs!=A_SHOP); }}; //Try to determine the "shape" of what we just hit, so we can bounce off in a reasonable direction arena_t obs; int ix,iy; //point of impact game::the_game->m_arena->arena_collision(pic(),int(m_x),int(m_y),&obs, 0, 0, &ix, &iy); int impact_radius=5; float total_x=0, total_y=0; for (int x=-impact_radius; x<=impact_radius; ++x) for (int y=-impact_radius; y<=impact_radius; ++y) { float r = sqrt(x*x+y*y); if (r && (game::the_game->m_arena->getalpha(int(m_x+ix+x), int(m_y+iy+y)) == SDL_ALPHA_OPAQUE)) { total_x+=1.0*x/r; total_y+=1.0*y/r; } } //normalise vector { float r = sqrt(total_x*total_x+total_y*total_y); if (r>0.01) { total_x/=r; total_y/=r; } else { total_x=total_y=0; } } //(total_x, total_y) is now the "average direction" of the pixels //behave as if we hit a flat wall with normal vector (total_x, total_y) float rdotv = total_x*m_dx+total_y*m_dy; m_x -= rdotv*total_x*m_last_interval; m_y -= rdotv*total_y*m_last_interval; m_dx-=(1.0+restitution)*rdotv*total_x; m_dy-=(1.0+restitution)*rdotv*total_y; float impact_speed = rdotv; //todo: deal with problems caused by rotating the ship into a wall if (_::noshop_coll(pic(), int(m_x), int(m_y))) { //I'm sure by now I don't need to tell you guys what I'm fairly certain this wall is asking for... //Perform an exhaustive search for safe spots outside this wall //This works surprisingly well for such an evil hack. for (int r=1; r3) { //very loud clang! sound::play(sound::CLANG); sound::play(sound::CLANG); } goto escaped; } } } //todo: do something really drastic! escaped: void(0); //Vary volume depending on just how hard we smacked into that wall :) if (impact_speed > 100) { sound::play(sound::CLANG); } else if (impact_speed > 8) { sound::play(sound::CLANG, impact_speed/100); } } } bool ship::does_collisions(mover *m) const { return dynamic_cast(m); } void ship::notify_collision(mover *m) { enemy *en = dynamic_cast(m); if (en) { en->take_hit(1); take_hit(1); } } void ship::take_hit(int damage) { if (game::the_game->m_player_status.invulnerability) return; game::the_game->m_player_status.shield-=damage; if (game::the_game->m_player_status.shield<0) { explosion *e=game::the_game->m_exp_pool.get_explosion(std::string("ship"), pic()); e->teleport_centre(x_centre(), y_centre()); e->m_dx=m_dx;e->m_dy=m_dy; game::the_game->m_arena->add_mover(e); die(); } } void ship::fire_weapons() { for(int i=0; im_weapon_mounts.size(); ++i) { weapon_mount &m=game::the_game->m_weapon_mounts[i]; if (m.type != WT_NOTHING && m.reload==0 && game::the_game->m_player_status.energy >= weapon_types()[m.type].energy_cost) { float rloc_x = x_centre()+m.loc_x*cos(m_angle)-m.loc_y*sin(m_angle); float rloc_y = y_centre()+m.loc_x*sin(m_angle)+m.loc_y*cos(m_angle); const weapon_type &wt=weapon_types()[m.type]; shot *s=new shot(wt.image_file, wt.ttl, wt.det_fn, wt.exp_fn); s->teleport_centre(rloc_x,rloc_y); s->m_dx=m_dx+wt.exit_speed*sin(m_angle+m.angle); s->m_dy=m_dy-wt.exit_speed*cos(m_angle+m.angle); game::the_game->m_arena->add_mover(s); //25=fudge factor - stops numbers getting to big in weapons table m_dx-=25.0*wt.recoil*sin(m_angle+m.angle)/m_mass; m_dy+=25.0*wt.recoil*cos(m_angle+m.angle)/m_mass; m.reload=wt.reload; game::the_game->m_player_status.energy -= wt.energy_cost; //Play at low volume - otherwise a fully pimped-out ship would be deafening! sound::play(wt.sound_effect, 0.2); } } } ball::ball() { m_pic=game::the_game->m_bmp_pool.get_bitmap("ball.png"); m_stuck = true; m_mass=ball_mass; } ball::~ball() { } shot::shot(const std::string &pic_name, float li_ex, detonation_fn_t *det_fn, expiration_fn_t *exp_fn) { m_pic_name=pic_name; m_det_fn = det_fn; m_exp_fn = exp_fn; m_pic=game::the_game->m_bmp_pool.get_bitmap(m_pic_name); m_life_expectancy=li_ex; m_mass=0; } void shot::do_stuff(float time_interval) { m_life_expectancy-=time_interval; if (m_life_expectancy<=0) { if (m_exp_fn) (*m_exp_fn)(this); die(); } } shot::~shot() { } void shot::notify_collision(arena_t) { //You can't shoot through walls! if (m_det_fn) { (*m_det_fn)(this, 0); } die(); } bool shot::does_collisions(mover *m) const { return (dynamic_cast(m) || dynamic_cast(m)); } void shot::notify_collision(mover *m) { if (!is_dead()) { if (m_det_fn) { (*m_det_fn)(this, m); } } } std::string shot::pic_name() const { return m_pic_name+"_"+"12345"[randint(5)]; } exhaust::exhaust() { //TODO: make less icky m_pic=create_surface(1,1, no_alpha_channel); SDL_LockSurface(m_pic); putpixel(m_pic, 0, 0, SDL_MapRGBA(m_pic->format, 255,0,0,SDL_ALPHA_OPAQUE)); SDL_UnlockSurface(m_pic); m_life_expectancy=0.25; m_mass=0; } exhaust::~exhaust() { SDL_FreeSurface(m_pic); } void exhaust::do_stuff(float time_interval) { m_life_expectancy-=time_interval; SDL_SetAlpha(m_pic, SDL_SRCALPHA, int((SDL_ALPHA_OPAQUE*m_life_expectancy + SDL_ALPHA_TRANSPARENT*(0.25-m_life_expectancy))/0.25)); if (m_life_expectancy<=0) die(); } coin::coin() { m_pic=game::the_game->m_bmp_pool.get_bitmap("coin.png"); m_mass=7; m_dy=-3; m_bounces=3; } void coin::do_stuff(float) { arena *ar=game::the_game->m_arena; if (m_x>ar->width()*TILE_SIZE && m_dx>0 || m_x<0 && m_dx<0) m_dx=-m_dx; if (m_y+pic()->h > ar->height()*TILE_SIZE && m_dy>0) { if (m_bounces) { sound::play(sound::BOINK); m_bounces--; m_dy=-m_dy; } else { explosion *e=game::the_game->m_exp_pool.get_explosion("coin.png", pic()); e->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(e); die(); } } m_dy+=0.1; } bool coin::does_collisions(mover *m) const { return dynamic_cast(m); } void coin::notify_collision(mover *m) { if (dynamic_cast(m)) { game::the_game->m_player_status.money+=50; sound::play(sound::KACHING, 0.3); die(); } } recking_ball::recking_ball() { m_mass=50; //very heavy m_pic=game::the_game->m_bmp_pool.get_bitmap("recking_ball.png"); } bool recking_ball::does_collisions(mover *m) const { return dynamic_cast(m); } void recking_ball::notify_collision(arena_t) { explosion *e=game::the_game->m_exp_pool.get_explosion("recking_ball.png", pic()); e->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(e); die(); } void recking_ball::notify_collision(mover *m) { enemy *e = dynamic_cast(m); if (e) //which should always be true, but I'm paranoid { e->take_hit(20); //And no damage to the ball } }