/* enemies.cpp - nasty enemies which shoot at you Copyright (C) 2006 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 "game.h" #include "enemies.h" #include "etc.h" #include "explosion.h" #include "sound.h" #include "debug.h" int enemy::take_hit(int damage) { if (is_dead()) { return 0; } else { m_hitpoints-=std::max(0, damage-m_dam_red); int accepted_damage = damage+std::min(0,m_hitpoints); if (m_hitpoints<=0) { m_hitpoints=0; die(); notify_dead(); game::the_game->m_player_status.score += m_score; } return accepted_damage; } } turret::turret(direction d) { m_pic=game::the_game->m_bmp_pool.get_bitmap(d==UP ? "turret_up.png": d==LEFT ? "turret_left.png": d==DOWN ? "turret_down.png": "turret_right.png"); m_direction=d; m_reload=0; m_hitpoints=5; m_score=200; } void turret::do_stuff(float time_interval) { mover *target = game::the_game->m_arena->get_player_target(); if (target) { //shoot at it then! float shot_speed=250; float tx=target->x_centre(), ty=target->y_centre(); float sx=x_centre(), sy=y_centre(); float distance=dist(tx-sx, ty-sy); if (distance > 20 && distance < 400 && //Don't shoot if player is colliding with turret! m_reload==0) { //turrets can only shoot within 90 degrees of their facing direction if ((m_direction==UP && tysy) || (m_direction==LEFT && txsx)) { t_shot *s=new t_shot(); s->teleport_centre(sx,sy); s->m_dx = (tx-sx)/distance*shot_speed; s->m_dy = (ty-sy)/distance*shot_speed; game::the_game->m_arena->add_mover(s); m_reload=0.5; } } } if(m_reload>0) m_reload=std::max(0, m_reload-time_interval); } void turret::notify_dead() { std::string name("turret_"); name+="UDLR"[m_direction]; //blegh! explosion *e=game::the_game->m_exp_pool.get_explosion(name, pic()); e->teleport_centre(x_centre(), y_centre()); //Explode away from the wall we're mounted on switch(m_direction) { case UP: e->m_dy=-100; break; case DOWN: e->m_dy=+100; break; case LEFT: e->m_dx=-100; break; case RIGHT: e->m_dx=+100; break; } game::the_game->m_arena->add_mover(e); } t_shot::t_shot() { m_pic=game::the_game->m_bmp_pool.get_bitmap("t_shot.png"); m_life_expectancy=40; } void t_shot::do_stuff(float time_interval) { m_life_expectancy--; if (m_life_expectancy==0) die(); } void t_shot::notify_collision(arena_t) { die(); } bobbler::bobbler() { m_pic=game::the_game->m_bmp_pool.get_bitmap("bobbler.png"); m_phase=randint(40); m_mass=2; //bobblers are light and fluffy m_score=10; } void bobbler::do_stuff(float time_interval) { m_phase++; m_phase%=40; m_dy += 8.0*cos(2.0*pi*m_phase/40); m_dy -= time_interval*gravity; //HACK bobblers have buoyancy which is exactly equal to gravity } void bobbler::notify_dead() { sound::play(sound::POP); explosion *e=game::the_game->m_exp_pool.get_explosion("bobbler", pic()); e->m_dx=m_dx;e->m_dy=m_dy; e->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(e); coin *c=new coin; c->m_dx+=m_dx;c->m_dy+=m_dy; c->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(c); } miner::miner() { m_pic=game::the_game->m_bmp_pool.get_bitmap("miner.png"); m_mass=30; m_hitpoints=20; m_dam_red=5; //I'm not sure if the player should be awarded points for a miner five //screens away drifting into a wall... but all miner deaths are //ultimately caused by the player m_score=20; } void miner::do_stuff(float time_interval) { m_dy -= time_interval*gravity; //Bouyancy hack } void miner::notify_collision(arena_t) { die(); detonate(); coin *c=new coin; c->m_dx+=m_dx;c->m_dy+=m_dy; c->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(c); } bool miner::does_collisions(mover *m) const { return dynamic_cast(m); } void miner::notify_collision(mover *m) { die(); detonate(); } void miner::notify_dead() { die(); detonate(); } void miner::detonate() { sound::play(sound::BOOM); arena *ar=game::the_game->m_arena; ar->AOE_damage(x_centre(), y_centre(), 50, 10, 5, damage_type(HURT_PLAYER|HURT_ENEMIES)); ar->AOE_force(x_centre(), y_centre(), 200, 150, 0); explosion *e=game::the_game->m_exp_pool.get_explosion("miner", pic()); e->m_dx=m_dx;e->m_dy=m_dy; e->teleport_centre(x_centre(), y_centre()); ar->add_mover(e); } //Graldor??? this could usea better name const float graldor_reload=11; const float graldor_fire_time=1.5; const float graldor_accel=100; graldor::graldor() { m_pic=game::the_game->m_bmp_pool.get_bitmap("graldor0.png"); m_mass=30; //heavy! m_hitpoints=20; m_dam_red=2; //immune to feeble weapons! m_reload=graldor_reload; m_firing=0; } void graldor::do_stuff(float time_interval) { if (m_mass) { //buoyancy hack m_dy -= time_interval*gravity; } m_reload-=time_interval; if (m_reload <=0) { m_reload+=graldor_reload; m_firing=graldor_fire_time; } if (m_firing) { char *pics[]={"graldor1.png","graldor2.png","graldor3.png","graldor4.png","graldor5.png", "graldor6.png","graldor7.png","graldor8.png","graldor9.png","graldor0.png"}; const int npics=sizeof(pics)/sizeof(*pics); int oldpic=minmax(0, int(1.0*npics*(graldor_fire_time-m_firing)/graldor_fire_time), npics-1); m_firing-=time_interval; int newpic=minmax(0, int(1.0*npics*(graldor_fire_time-m_firing)/graldor_fire_time), npics-1); m_pic=game::the_game->m_bmp_pool.get_bitmap(pics[newpic]); if (newpic==4 && oldpic==3) { //fire! baby_graldor* bg=new baby_graldor(); bg->teleport(m_x+12, m_y+19); //magic numbers based on graldor pics game::the_game->m_arena->add_mover(bg); } if (m_firing<0) m_firing=0; } } bool graldor::does_arena_collisions() const { return m_dx || m_dy; } void graldor::notify_collision(arena_t) { if (m_dx || m_dy) { //stick to whatever we hit m_dx=m_dy=0; sound::play(sound::CLANG); } } void graldor::notify_dead() { explosion *e=game::the_game->m_exp_pool.get_explosion("graldor", pic()); e->m_dx=m_dx;e->m_dy=m_dy; e->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(e); } baby_graldor::baby_graldor() { m_pic=game::the_game->m_bmp_pool.get_bitmap("baby_graldor.png"); m_mass=5; m_hitpoints=3; } void baby_graldor::do_stuff(float time_interval) { //buoyancy hack m_dy -= time_interval*gravity; //accelerate towards player! mover *target = game::the_game->m_arena->get_player_target(); if(target) { float dx=target->x_centre()-x_centre(); float dy=target->y_centre()-y_centre(); float dd=dist(dx,dy); if (dd>1) //avoid division by zero { m_dy+=time_interval*graldor_accel*dy/dd; m_dx+=time_interval*graldor_accel*dx/dd; } } } void baby_graldor::notify_collision(arena_t) { die(); notify_dead(); } void baby_graldor::notify_dead() { explosion *e=game::the_game->m_exp_pool.get_explosion("graldor", pic()); e->m_dx=m_dx;e->m_dy=m_dy; e->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(e); } const float invader_speed=0.4; const float invader_fire_rate=0.2; //in Hz! invader::invader(float left, float right) { m_pic=game::the_game->m_bmp_pool.get_bitmap("invader1.png"); m_mass=15; m_hitpoints=5; m_timer=0; m_left=left; m_right=right; m_dir=1; } void invader::do_stuff(float time_interval) { //buoyancy hack m_dy -= time_interval*gravity; //left/right if (m_xm_right) m_dir=-1; m_dx += m_dir*invader_speed; //cycle animation m_timer += time_interval; while(m_timer>2) m_timer-=2; m_pic=game::the_game->m_bmp_pool.get_bitmap(m_timer<1?"invader1.png":"invader2.png"); //drop things at player if (randu() < time_interval*invader_fire_rate) { t_shot *s=new t_shot(); s->teleport_centre(x_centre(), y_centre()); s->m_dx = 0; s->m_dy = 250; game::the_game->m_arena->add_mover(s); } } void invader::notify_dead() { //explosion! the standard explosion won't look right so concoct something. class block_explosion:public mover { public: block_explosion(){m_timer=0;} void do_stuff(float time_interval) { m_timer+=time_interval; if (m_timer>0.25) die(); } SDL_Surface* pic() const {return game::the_game->m_bmp_pool.get_bitmap("blockexplo"+ int2str(minmax(0,int(m_timer*16),3))+".png");} private: float m_timer; }; mover *b=new block_explosion; b->teleport_centre(x_centre(), y_centre()); game::the_game->m_arena->add_mover(b); sound::play(sound::POP); } const float ufo_rocket_reload=10; const float ufo_rocket_speed=450; const float ufo_shot_speed=250; const float ufo_rockets_per_sec=5; const float ufo_bullets_per_sec=10; //but it fires 2 at once, so it's actually twice that. ufo::ufo(float desired_y, int left, int right) { m_pic=m_pic=game::the_game->m_bmp_pool.get_bitmap("ufo.png"); m_mass=50; m_hitpoints=400; m_timer=20; m_dam_red=1; m_desired_y=desired_y; m_left=left; m_right=right; m_dx=100; } void ufo::do_stuff(float time_interval) { //buoyancy hack m_dy -= time_interval*gravity; //timer float old_timer=m_timer; m_timer+=time_interval; while(m_timer>ufo_rocket_reload) m_timer-=ufo_rocket_reload; //fire bullets mover *target = game::the_game->m_arena->get_player_target(); if(target) { float dx=target->x_centre()-x_centre(); float dy=target->y_centre()-y_centre(); float dd=dist(dx,dy); if (dd>1 && dd<400) { if(int(old_timer*ufo_bullets_per_sec)< int(m_timer*ufo_bullets_per_sec)) { int fire_points[][2]={{-40,10},{40,10}}; for(int i=0; ix_centre()-x_centre()-fire_points[i][0]; float dy=target->y_centre()-y_centre()-fire_points[i][1]; float dd=dist(dx,dy); if (dd>1) { t_shot *s=new t_shot(); s->teleport_centre(x_centre()+fire_points[i][0], y_centre()+fire_points[i][1]); s->m_dx = (dx)/dd*ufo_shot_speed; s->m_dy = (dy)/dd*ufo_shot_speed; game::the_game->m_arena->add_mover(s); } sound::play(sound::BOINK); } } } } //fire rockets if (m_timer>ufo_rocket_reload-1.0) { if (int(old_timer*ufo_rockets_per_sec)< int(m_timer*ufo_rockets_per_sec)) { mover *target = game::the_game->m_arena->get_player_target(); if(target) { float dx=target->x_centre()-x_centre(); float dy=target->y_centre()-y_centre(); float dd=dist(dx,dy); if (dd>1) //avoid /0 { ufo_rocket *r=new ufo_rocket(); r->teleport_centre(x_centre(), y_centre()); //todo! r->m_dx=ufo_rocket_speed*dx/dd; r->m_dy=ufo_rocket_speed*dy/dd; game::the_game->m_arena->add_mover(r); //todo: needs unique sound, as this could be masked by //players with shovers. sound::play(sound::DOOF); } } } } //left/right if (x_centre()>m_right) m_dx=-100; if (x_centre()m_bmp_pool.get_bitmap("rocket.png"); } void ufo_rocket::notify_collision(arena_t) { die(); notify_dead(); } void ufo_rocket::notify_dead() { sound::play(sound::BOOM); //This may not seem like much, but the UFO fires a lot of these! game::the_game->m_arena->AOE_damage(x_centre(), y_centre(), 50, 2, 1, damage_type(HURT_ENEMIES|HURT_PLAYER)); game::the_game->m_arena->AOE_force(x_centre(), y_centre(), 100, 20, 10); } void ufo::notify_dead() { //todo: make it fall to the bottom of the screen and then explode! sound::play(sound::BOOM); sound::play(sound::BOOM); sound::play(sound::BOOM); for(int i=0; i<30; ++i) { coin *c = new coin; c->teleport_centre(x_centre()+20.0*randn(), y_centre()+20.0*randn()); game::the_game->m_arena->add_mover(c); } game::the_game->m_arena->AOE_force(x_centre(), y_centre(), 50, 40, 100); //TODO: the UFO really should drop a lot of coins //(but as it's curently the final boss, there's not much point) game::the_game->m_arena->open_exit(); }