/** * @file weapon.c * * @brief Handle all the weapons in game. * * Weapons are what gets created when a pilot shoots. They are based on * the outfit that created them. */ #include #include #include #include "outfit.h" #include "physics.h" #include "lephisto.h" #include "log.h" #include "rng.h" #include "pilot.h" #include "collision.h" #include "player.h" #include "spfx.h" #include "opengl.h" #include "explosion.h" #include "weapon.h" #define weapon_isSmart(w) (w->think != NULL) /**< Checks if the weapon w is smart. */ #define WEAPON_CHUNK 128 /**< Size to increment array with. */ /* Weapon status. */ #define WEAPON_STATUS_OK 0 /**< Weapon is fine. */ #define WEAPON_STATUS_JAMMED 1 /**< Got jammed. */ #define WEAPON_STATUS_UNJAMMED 2 /**< Surviving jaming. */ /* OpenGL stuff. */ extern Vec2* gl_camera; extern double gui_xoff, gui_yoff; /* Pilot stuff. */ extern Pilot** pilot_stack; extern int pilot_nstack; /* Ai stuff. */ /**< Triggers the 'attacked' function in the ai. */ extern void ai_attacked(Pilot* attacked, const unsigned int attacker); /** * @struct Weapon * * @brief In-game representation of a weapon. */ typedef struct Weapon_ { Solid* solid; /**< Actually has its own solid. :D */ int ID; /**< Only used for beam weapons. */ unsigned int faction; /**< Faction of pilot that shot the weapon. */ unsigned int parent; /**< The pilot that just shot at you! */ unsigned int target; /**< Target to hit. Only used by seeking stuff. */ const Outfit* outfit; /**< Related outfit that fired. */ int voice; /**< Weapons voice. */ double lockon; /**< Some weapons have a lockon delay. */ double timer; /**< Mainly used to see when the weapon was fired. */ double anim; /**< Used for beam weapon graphics and others. */ int sprite; /**< Used for spinning outfits. */ int mount; /**< Used for beam weapons. */ /* Update position and render. */ void(*update)(struct Weapon_*, const double, WeaponLayer); /**< Update the weapon. */ void(*think)(struct Weapon_*, const double); /**< For the smart missiles. */ char status; /**< Weapon status - to check for jamming. */ } Weapon; /* Behind Pilot layer. */ static Weapon** wbackLayer = NULL; /**< Behind pilots. */ static int nwbackLayer = 0; /**< Number of elements. */ static int mwbackLayer = 0; /**< Allocated memory size. */ /* Behind player layer. */ static Weapon** wfrontLayer = NULL; /**< Infront of pilots, behind player. */ static int nwfrontLayer = 0; /**< Number of elements. */ static int mwfrontLayer = 0; /**< Allocated memory size. */ /* Internal stuff. */ static int beam_idgen = 0; /**< Beam identifier generator. */ /* Static. */ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2* pos, const Vec2* vel, const unsigned int parent, const unsigned int target); static void weapon_render(Weapon* w, const double dt); static void weapons_updateLayer(const double dt, const WeaponLayer layer); static void weapon_update(Weapon* w, const double dt, WeaponLayer layer); static void weapon_hit(Weapon* w, Pilot* p, WeaponLayer layer, Vec2* pos); static void weapon_hitBeam(Weapon* w, Pilot* p, WeaponLayer layer, Vec2 pos[2], const double dt); static void weapon_destroy(Weapon* w, WeaponLayer layer); static void weapon_free(Weapon* w); static void weapon_explodeLayer(WeaponLayer layer, double x, double y, double radius, unsigned int parent, int mode); /* Think. */ static void think_seeker(Weapon* w, const double dt); static void think_beam(Weapon* w, const double dt); /* Extern. */ void weapon_minimap(const double res, const double w, const double h, const RadarShape shape, double alpha); /** * @brief Draw the minimap weapons (used in player.c). * @param res Minimap resolution. * @param w Width of minimap. * @param h Height of minimap. * @param shape Shape of the minimap. */ #define PIXEL(x,y) \ if((shape == RADAR_RECT && ABS(x) < w/2. && ABS(y)solid->pos.x - player->solid->pos.x) / res; y = (wbackLayer[i]->solid->pos.y - player->solid->pos.y) / res; PIXEL(x,y); } for(i = 0; i < nwfrontLayer; i++) { x = (wfrontLayer[i]->solid->pos.x - player->solid->pos.x) / res; y = (wfrontLayer[i]->solid->pos.y - player->solid->pos.y) / res; PIXEL(x,y); } /* End the points. */ glEnd(); } #undef PIXEL /** * @brief The AI of seeker missiles. * @param w Weapon to do the thinking. * @param dt Current delta tick. */ static void think_seeker(Weapon* w, const double dt) { double diff; double vel; Pilot* p; int effect; int spfx; if(w->target == w->parent) return; /* HEY! Self harm is not allowed. */ p = pilot_get(w->target); /* No null pilot_nstack. */ if(p == NULL) { w->solid->dir_vel = 0.; /* Go straight. */ vectnull(&w->solid->force); /* No force. */ return; } /* Only run if locked on. */ if(w->lockon < 0.) { switch(w->status) { case WEAPON_STATUS_OK: /* Check to see if can get jammed. */ if((p->jam_range != 0.) && /* Target has jammer and weapon is in range. */ (vect_dist(&w->solid->pos, &p->solid->pos) < p->jam_range)) { /* Check to see if the weapon is gets jammed. */ if(RNGF() < p->jam_chance - w->outfit->u.amm.resist) { w->status = WEAPON_STATUS_JAMMED; /* Give it a nice random effect. */ effect = RNG(0, 4); switch(effect) { case 0: /* Blow up. */ w->timer = -1.; spfx = outfit_spfxArmour(w->outfit); spfx_add(spfx, w->solid->pos.x, w->solid->pos.y, w->solid->vel.x, w->solid->vel.y, SPFX_LAYER_BACK); /* Presume back. */ break; case 1: /* Stuck in left loop. */ w->solid->dir_vel = w->outfit->u.amm.turn; break; case 2: /* Stuck in right loop. */ w->solid->dir_vel = -w->outfit->u.amm.turn; break; default: /* Go straight. */ w->solid->dir_vel = 0.; return; } } else /* Can't get jammed anymore. */ w->status = WEAPON_STATUS_UNJAMMED; } /* Purpose fallthrough. */ case WEAPON_STATUS_UNJAMMED: /* Work as expected. */ diff = angle_diff(w->solid->dir, /* Get angle to target pos. */ vect_angle(&w->solid->pos, &p->solid->pos)); w->solid->dir_vel = 10 * diff * w->outfit->u.amm.turn; /* Face pos. */ /* Check for under/overflows. */ if(w->solid->dir_vel > w->outfit->u.amm.turn) w->solid->dir_vel = w->outfit->u.amm.turn; else if(w->solid->dir_vel < -w->outfit->u.amm.turn) w->solid->dir_vel = -w->outfit->u.amm.turn; break; case WEAPON_STATUS_JAMMED: /* Continue doinng whatever. */ /* Do nothing. */ break; default: WARN("Unknown weapon status for '%s'", w->outfit->name); break; } } /* Limit speed here. */ vel = MIN(w->outfit->u.amm.speed, VMOD(w->solid->vel) + w->outfit->u.amm.thrust*dt); vect_pset(&w->solid->vel, vel, w->solid->dir); /*limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt);*/ } /** * @brief The Pseudo-ai of the beam weapons. * @param w Weapon to do the thinking. * @return dt Current delta tick. */ static void think_beam(Weapon* w, const double dt) { (void) dt; Pilot* p, *t; double diff; Vec2 v; /* Get pilot, if pilot is dead beam is destroyed too. */ p = pilot_get(w->parent); if(p == NULL) { w->timer = -1; /* Hack to make it get destroyed next update. */ return; } /* Check if pilot has enough energy left to keep beam active. */ p->energy -= dt*w->outfit->u.bem.energy; if(p->energy < 0.) { p->energy = 0.; w->timer = -1; return; } /* Use mount position. */ pilot_getMount(p, w->mount, &v); w->solid->pos.x = v.x; w->solid->pos.y = v.y; /* Handle aiming. */ switch(w->outfit->type) { case OUTFIT_TYPE_BEAM: w->solid->dir = p->solid->dir; break; case OUTFIT_TYPE_TURRET_BEAM: /* Get target, if target is dead beam stops moving. */ t = pilot_get(w->target); if(t == NULL) { w->solid->dir_vel = 0.; return; } if(w->target == w->parent) /* Invalid target, try to follow shooter. */ diff = angle_diff(w->solid->dir, p->solid->dir); else diff = angle_diff(w->solid->dir, /* Get angle to target pos. */ vect_angle(&w->solid->pos, &t->solid->pos)); w->solid->dir_vel = 10 * diff * w->outfit->u.bem.turn; /* Face pos. */ /* Check for under/overflows. */ if(w->solid->dir_vel > w->outfit->u.bem.turn) w->solid->dir_vel = w->outfit->u.bem.turn; else if(w->solid->dir_vel < -w->outfit->u.bem.turn) w->solid->dir_vel = -w->outfit->u.bem.turn; break; default: return; } } /** * @brief Update all the weapon layers. * @param dt Current delta tick. */ void weapons_update(const double dt) { weapons_updateLayer(dt, WEAPON_LAYER_BG); weapons_updateLayer(dt, WEAPON_LAYER_FG); } /** * @brief Update all the weapons in the layer. * @param dt Current delta tick. * @param layer Layer to update. */ static void weapons_updateLayer(const double dt, const WeaponLayer layer) { Weapon** wlayer; int* nlayer; Weapon* w; int i; /* Choose layer. */ switch(layer) { case WEAPON_LAYER_BG: wlayer = wbackLayer; nlayer = &nwbackLayer; break; case WEAPON_LAYER_FG: wlayer = wfrontLayer; nlayer = &nwfrontLayer; break; default: WARN("Unkown weapon layer!"); } i = 0; while(i < *nlayer) { w = wlayer[i]; switch(w->outfit->type) { /* Most missiles behave the same. */ case OUTFIT_TYPE_MISSILE_SEEK_AMMO: case OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO: case OUTFIT_TYPE_MISSILE_SWARM_AMMO: case OUTFIT_TYPE_MISSILE_SWARM_SMART_AMMO: if(w->lockon > 0.) /* Decrement lockon. */ w->lockon -= dt; /* Purpose fallthrough. */ /* Bolts too. */ case OUTFIT_TYPE_TURRET_DUMB_AMMO: case OUTFIT_TYPE_MISSILE_DUMB_AMMO: /* Dumb missiles are like bolts. */ limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt); case OUTFIT_TYPE_BOLT: case OUTFIT_TYPE_TURRET_BOLT: w->timer -= dt; if(w->timer < 0.) { weapon_destroy(w, layer); break; } break; /* Beam weapons handled apart. */ case OUTFIT_TYPE_BEAM: case OUTFIT_TYPE_TURRET_BEAM: w->timer -= dt; if(w->timer < 0.) { weapon_destroy(w, layer); break; } /* We use the lockon to tell when we have to create explosions. */ w->lockon -= dt; if(w->lockon < 0.) { if(w->lockon < -1.) w->lockon = 0.100; else w->lockon = -1.; } break; default: WARN("Weapon of type '%s' has no update implemented yet!", w->outfit->name); break; } /* Out of bounds, loop is over. */ if(i >= *nlayer) break; /* Only increment if weapon wasn't deleted. */ if(w == wlayer[i]) { weapon_update(w, dt, layer); if((i < *nlayer) && (w == wlayer[i])) i++; } } } /** * @brief Render all the weapons in a layer. * @param layer Layer to render. * @param Current delta tick. */ void weapons_render(const WeaponLayer layer, const double dt) { Weapon** wlayer; int* nlayer; int i; switch(layer) { case WEAPON_LAYER_BG: wlayer = wbackLayer; nlayer = &nwbackLayer; break; case WEAPON_LAYER_FG: wlayer = wfrontLayer; nlayer = &nwfrontLayer; break; default: WARN("Unkown weapon layer!"); } for(i = 0; i < (*nlayer); i++) weapon_render(wlayer[i], dt); } /** * @brief Render an individual weapon. * @param w Weapon to render. */ static void weapon_render(Weapon* w, const double dt) { int sx, sy; double x, y; glTexture* gfx; switch(w->outfit->type) { case OUTFIT_TYPE_MISSILE_SEEK_AMMO: case OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO: case OUTFIT_TYPE_MISSILE_SWARM_AMMO: case OUTFIT_TYPE_MISSILE_SWARM_SMART_AMMO: case OUTFIT_TYPE_BOLT: case OUTFIT_TYPE_TURRET_BOLT: case OUTFIT_TYPE_MISSILE_DUMB_AMMO: case OUTFIT_TYPE_TURRET_DUMB_AMMO: gfx = outfit_gfx(w->outfit); /* Outfit spins around. */ if(outfit_isProp(w->outfit, OUTFIT_PROP_WEAP_SPIN)) { /* Check timer. */ w->anim -= dt; if(w->anim < 0.) { w->anim = outfit_spin(w->outfit); /* Increment sprite. */ w->sprite++; if(w->sprite >= gfx->sx*gfx->sy) w->sprite = 0; } /* Render. */ gl_blitSprite(gfx, w->solid->pos.x, w->solid->pos.y, w->sprite % (int)gfx->sx, w->sprite / (int)gfx->sx, NULL); } else { /* Outfit faces direction. */ gl_getSpriteFromDir(&sx, &sy, gfx, w->solid->dir); gl_blitSprite(gfx, w->solid->pos.x, w->solid->pos.y, sx, sy, NULL); } break; /* Beam weapons. */ case OUTFIT_TYPE_BEAM: case OUTFIT_TYPE_TURRET_BEAM: gfx = outfit_gfx(w->outfit); /* Position. */ x = w->solid->pos.x - VX(*gl_camera) + gui_xoff; y = w->solid->pos.y - VY(*gl_camera) + gui_yoff; /* Set up the matrix. */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glTranslated(x, y, 0.); glRotated(270. + w->solid->dir / M_PI * 180., 0., 0., 1.); /* Preparatives. */ glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, gfx->texture); glShadeModel(GL_SMOOTH); /* Actual rendering. */ glBegin(GL_QUAD_STRIP); /* Start fading. */ ACOLOUR(cWhite, 0.); glTexCoord2d(w->anim, 0.); glVertex2d(-gfx->sh/2., 0.); glTexCoord2d(w->anim, 1.); glVertex2d(+gfx->sh/2., 0.); /* Full Strength. */ COLOUR(cWhite); glTexCoord2d(w->anim+10. / gfx->sw, 0.); glVertex2d(-gfx->sh/2., 10.); glTexCoord2d(w->anim+10. / gfx->sw, 1.); glVertex2d(+gfx->sh/2., 10.); glTexCoord2d(w->anim+0.8*w->outfit->u.bem.range / gfx->sw, 0.); glVertex2d(-gfx->sh/2., 0.8*w->outfit->u.bem.range); glTexCoord2d(w->anim+0.8*w->outfit->u.bem.range / gfx->sw, 1.); glVertex2d(+gfx->sh/2., 0.8*w->outfit->u.bem.range); /* Fades out. */ ACOLOUR(cWhite, 0.); glTexCoord2d(w->anim+w->outfit->u.bem.range / gfx->sw, 0.); glVertex2d(-gfx->sh/2., w->outfit->u.bem.range); glTexCoord2d(w->anim+w->outfit->u.bem.range / gfx->sw, 1.); glVertex2d(+gfx->sh/2., w->outfit->u.bem.range); glEnd(); /* Do the beam movement. */ w->anim -= 5. * dt; if(w->anim <= -gfx->sw) w->anim += gfx->sw; /* Clean up. */ glDisable(GL_TEXTURE_2D); glShadeModel(GL_FLAT); glPopMatrix(); gl_checkErr(); break; default: WARN("Weapon of type '%s' has no render implmented yet!!!", w->outfit->name); break; } } /** * @brief Updates an individual weapon. * @param w Weapon to update. * @param dt Current delta tick. * @param layer Layer to which the weapon belongs. */ static void weapon_update(Weapon* w, const double dt, WeaponLayer layer) { int i, wsx, wsy, psx, psy; glTexture* gfx; Vec2 crash[2]; /* Get the sprite direction to speed up calculations. */ if(!outfit_isBeam(w->outfit)) { gfx = outfit_gfx(w->outfit); gl_getSpriteFromDir(&wsx, &wsy, gfx, w->solid->dir); } for(i = 0; i < pilot_nstack; i++) { psx = pilot_stack[i]->tsx; psy = pilot_stack[i]->tsy; if(w->parent == pilot_stack[i]->id) continue; /* Hey! That's you. */ /* Beam weapons have special collisions. */ if(outfit_isBeam(w->outfit)) { /* Check for collision. */ if(!areAllies(w->faction, pilot_stack[i]->faction) && CollideLineSprite(&w->solid->pos, w->solid->dir, w->outfit->u.bem.range, pilot_stack[i]->ship->gfx_space, psx, psy, &pilot_stack[i]->solid->pos, crash)) { weapon_hitBeam(w, pilot_stack[i], layer, crash, dt); /* No return because beam can still think, it's not * destroyed like the other weapons. */ } } /* Smart weapons only collide with their target. */ else if(weapon_isSmart(w)) { if((pilot_stack[i]->id == w->target) && CollideSprite(gfx, wsx, wsy, &w->solid->pos, pilot_stack[i]->ship->gfx_space, psx, psy, &pilot_stack[i]->solid->pos, &crash[0])) { weapon_hit(w, pilot_stack[i], layer, &crash[0]); return; /* Weapon is destroyed. */ } } /* Dump weapons hit anything not of the same faction. */ else { if(!areAllies(w->faction, pilot_stack[i]->faction) && CollideSprite(gfx, wsx, wsy, &w->solid->pos, pilot_stack[i]->ship->gfx_space, psx, psy, &pilot_stack[i]->solid->pos, &crash[0])) { weapon_hit(w, pilot_stack[i], layer, &crash[0]); return; /* Weapon is destroyed. */ } } } /* Smart weapons also get to think their next move. */ if(weapon_isSmart(w)) (*w->think)(w,dt); /* Update the solid position. */ (*w->solid->update)(w->solid, dt); /* Update the sound. */ sound_updatePos(w->voice, w->solid->pos.x, w->solid->pos.y); } /** * fn static void weapon_hit(Weapon* w, Pilot* p, WeaponLayer layer, vec2* pos) * * @breif Weapon hit the pilot. * @param w Weapon Involved in the collision. * @param p Pilot that got it. * @param layer Layer to which the weapon belongs. * @param pos Position of the hit. */ static void weapon_hit(Weapon* w, Pilot* p, WeaponLayer layer, Vec2* pos) { Pilot* parent; int spfx; /* Choose spfx. */ if(p->shield > 0.) spfx = outfit_spfxShield(w->outfit); else spfx = outfit_spfxArmour(w->outfit); /* Someone should let the ai know it's been attacked. */ if(!pilot_isPlayer(p)) { if((player != NULL) && (w->parent == player->id) && ((player->target == p->id) || (RNGF() > 0.33))) { /* 33% chance. */ parent = pilot_get(w->parent); if((parent != NULL) && (parent->faction == FACTION_PLAYER) && (!pilot_isFlag(p, PILOT_HOSTILE) || (RNGF() < 0.5))) { /* 50% chance. */ faction_modPlayer(p->faction, -1.); /* Slowly lower faction. */ } pilot_setFlag(p, PILOT_HOSTILE); pilot_rmFlag(p, PILOT_BRIBED); } ai_attacked(p, w->parent); spfx_add(spfx, pos->x, pos->y, VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_BACK); } else spfx_add(spfx, pos->x, pos->y, VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_FRONT); /* Let the ship know that is should take some kind of damage. */ pilot_hit(p, w->solid, w->parent, outfit_damageType(w->outfit), outfit_damage(w->outfit)); /* We don't need the weapon particle any longer. */ weapon_destroy(w, layer); } /** * @brief Weapon hit the pilot. * @param w Weapon involved in the collision. * @param p Pilot that got hit. * @param layer Layer to which the weapon belongs. * @param pos Position of the hit. * @param dt Current delta tick. */ static void weapon_hitBeam(Weapon* w, Pilot* p, WeaponLayer layer, Vec2 pos[2], const double dt) { (void)layer; Pilot* parent; int spfx; /* Choose spfx. */ if(p->shield > 0.) spfx = outfit_spfxShield(w->outfit); else spfx = outfit_spfxArmour(w->outfit); /* Inform the ai it has been attacked, useless if player. */ if(!pilot_isPlayer(p)) { if((player != NULL) && (w->parent == player->id) && ((player->target == p->id) || (RNGF() < 0.30*dt))) { /* 30% chance per second. */ parent = pilot_get(w->parent); if((parent != NULL) && (parent->faction == FACTION_PLAYER) && (!pilot_isFlag(p, PILOT_HOSTILE) || (RNGF() < 0.50*dt))) { /* 50% chance. */ faction_modPlayer(p->faction, -1.); /* Slowly lower faction. */ } pilot_rmFlag(p, PILOT_BRIBED); pilot_setFlag(p, PILOT_HOSTILE); } ai_attacked(p, w->parent); if(w->lockon == -1.) { /* Code to signal create explosions. */ spfx_add(spfx, pos[0].x, pos[0].y, VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_BACK); spfx_add(spfx, pos[1].x, pos[1].y, VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_BACK); w->lockon = -2; } } else if(w->lockon == -1.) { spfx_add(spfx, pos[0].x, pos[0].y, VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_FRONT); spfx_add(spfx, pos[1].x, pos[1].y, VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_FRONT); w->lockon = -2; } /* Inform the ship that it should take some damage. */ pilot_hit(p, w->solid, w->parent, outfit_damageType(w->outfit), outfit_damage(w->outfit)*dt); } /** * @brief Create a new weapon. * @param outfit Outfit which spawned the weapon. * @param dir Direction the shooter is facing. * @param pos Position of the shooter. * @param vel Velocity of the shooter. * @param parent Shooter ID. * @param target Target ID of the shooter. * @return A pointer to the newly created weapon. */ static Weapon* weapon_create(const Outfit* outfit, const double dir, const Vec2* pos, const Vec2* vel, unsigned int parent, const unsigned int target) { Vec2 v; double mass, rdir; Pilot* pilot_target; double x, y, t, dist; Weapon* w; /* Create basic features. */ w = CALLOC_L(Weapon); w->faction = pilot_get(parent)->faction; /*Non-Changeable. */ w->parent = parent; /* Non-Changeable. */ w->target = target; /* Non-Changeable. */ w->outfit = outfit; /* Non-Changeable. */ w->update = weapon_update; w->status = WEAPON_STATUS_OK; switch(outfit->type) { /* Bolts treated together. */ case OUTFIT_TYPE_BOLT: case OUTFIT_TYPE_TURRET_BOLT: if((outfit->type == OUTFIT_TYPE_TURRET_BEAM) && (w->parent != w->target)) { pilot_target = pilot_get(w->target); if(pilot_target == NULL) rdir = dir; else { /* Get the distance. */ dist = vect_dist(pos, &pilot_target->solid->pos); /* Aim. */ if(dist > outfit->u.blt.range*1.2) { x = pilot_target->solid->pos.x - pos->x; y = pilot_target->solid->pos.y - pos->y; } else { /* * Try to predict where the enemy will be. * Time for shots to reach that distance. */ t = dist / w->outfit->u.blt.speed; /* Position is calculated on where it should be. */ x = (pilot_target->solid->pos.x + pilot_target->solid->vel.x*t) - (pos->x + vel->x * t); y = (pilot_target->solid->pos.y + pilot_target->solid->vel.y * t) - (pos->y + vel->y * t); } /* Set angle to face. */ rdir = ANGLE(x, y); } } else /* Fire straight. */ rdir = dir; rdir += RNG_2SIGMA() * outfit->u.blt.accuracy/2. * 1./180.*M_PI; if((rdir > 2.*M_PI) || (rdir < 0.)) rdir = fmod(rdir, 2.*M_PI); mass = 1; /* Lasers are presumed to have unitory mass. */ vectcpy(&v, vel); vect_cadd(&v, outfit->u.blt.speed*cos(rdir), outfit->u.blt.speed*sin(rdir)); w->timer = outfit->u.blt.range/outfit->u.blt.speed; w->solid = solid_create(mass, rdir, pos, &v); w->voice = sound_playPos(w->outfit->u.blt.sound, w->solid->pos.x + w->solid->vel.x, w->solid->pos.y + w->solid->vel.y); break; /* Beam weapons are treated together. */ case OUTFIT_TYPE_BEAM: case OUTFIT_TYPE_TURRET_BEAM: if((outfit->type == OUTFIT_TYPE_TURRET_BOLT) && (w->parent != w->target) && (w->target != 0)) { /* Must have valid target. */ pilot_target = pilot_get(target); rdir = (pilot_target == NULL) ? dir : vect_angle(pos, &pilot_target->solid->pos); } else rdir = dir; mass = 1.; /**< Needs a mass. */ w->solid = solid_create(mass, rdir, pos, NULL); w->think = think_beam; w->timer = outfit->u.bem.duration; w->voice = sound_playPos(w->outfit->u.bem.sound, w->solid->pos.x + vel->x, w->solid->pos.y + vel->y); break; /* Treat seekers togther. */ case OUTFIT_TYPE_MISSILE_SEEK_AMMO: case OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO: mass = w->outfit->mass; rdir = dir; if(outfit->u.amm.accuracy != 0.) { rdir += NormalInverse(RNGF()*0.9 + 0.05) /* Get rid of extreme values. */ * outfit->u.amm.accuracy/2. * 1./180.*M_PI; if((rdir > 2.*M_PI) || (rdir < 0.)) rdir = fmod(rdir, 2.*M_PI); } w->lockon = outfit->u.amm.lockon; w->timer = outfit->u.amm.duration; w->solid = solid_create(mass, dir, pos, vel); /* If they are seeking a pilot, increment lockon counter. */ pilot_target = pilot_get(target); if(pilot_target != NULL) pilot_target->lockons++; /* Only diff is AI. */ w->think = think_seeker; /* AI is the same atm. */ /*if(outfit->type == OUTFIT_TYPE_MISSILE_SEEK_AMMO) w->think = think_seeker; else if(outfit->type == OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO) w->think = think_smart;*/ w->voice = sound_playPos(w->outfit->u.amm.sound, w->solid->pos.x + w->solid->vel.x, w->solid->pos.y + w->solid->vel.y); break; /* Dumb missiles and turrets. */ case OUTFIT_TYPE_TURRET_DUMB_AMMO: case OUTFIT_TYPE_MISSILE_DUMB_AMMO: if(w->outfit->type == OUTFIT_TYPE_TURRET_DUMB_AMMO) { pilot_target = pilot_get(w->target); if(pilot_target == NULL) rdir = dir; else { /* Get the distance. */ dist = vect_dist(pos, &pilot_target->solid->pos); /* Aim. */ /* Try to predict where the enemy will be. */ /* Time for shots to reach that distance. */ t = dist / w->outfit->u.amm.speed; /* Position is calculated on where it should be. */ x = (pilot_target->solid->pos.x + pilot_target->solid->vel.x*t) - (pos->x + vel->x*t); y = (pilot_target->solid->pos.y + pilot_target->solid->vel.y*t) - (pos->y + vel->y * t); /* Set angle to face. */ rdir = ANGLE(x, y); } } else { rdir = dir; } /* If thrust is 0. We assume it starts out at speed. */ vectcpy(&v, vel); if(outfit->u.amm.thrust == 0.) vect_cadd(&v, cos(rdir) * w->outfit->u.amm.speed, sin(rdir) * w->outfit->u.amm.speed); mass = w->outfit->mass; w->timer = outfit->u.amm.duration; w->solid = solid_create(mass, rdir, pos, &v); if(w->outfit->u.amm.thrust != 0.) vect_pset(&w->solid->force, w->outfit->u.amm.thrust * mass, rdir); w->voice = sound_playPos(w->outfit->u.amm.sound, w->solid->pos.x + w->solid->vel.x, w->solid->pos.y + w->solid->vel.y); break; /* Just dump it where the player is. */ default: WARN("Weapon of type '%s' has no create implemented yet!", w->outfit->name); w->solid = solid_create(mass, dir, pos, vel); break; } return w; } /** * @brief Create a new weapon. * @param outfit Outfit which spawns the weapon. * @param dir Direction of the shooter. * @param pos Position of the shooter. * @param vel Velocity of the shooter. * @param parent Pilot ID of the shooter. * @param target Target ID that is getting shot. */ void weapon_add(const Outfit* outfit, const double dir, const Vec2* pos, const Vec2* vel, unsigned int parent, unsigned int target) { WeaponLayer layer; Weapon* w; Weapon** curLayer; int* mLayer, *nLayer; if(!outfit_isBolt(outfit) && !outfit_isAmmo(outfit)) { ERR("Trying to create a weapon from a non-Weapon type Outfit"); return; } layer = (parent == PLAYER_ID) ? WEAPON_LAYER_FG : WEAPON_LAYER_BG; w = weapon_create(outfit, dir, pos, vel, parent, target); /* Set the propper layer. */ switch(layer) { case WEAPON_LAYER_BG: curLayer = wbackLayer; nLayer = &nwbackLayer; mLayer = &mwbackLayer; break; case WEAPON_LAYER_FG: curLayer = wfrontLayer; nLayer = &nwfrontLayer; mLayer = &mwfrontLayer; break; default: WARN("Unkown weapon layer!"); } if(*mLayer > *nLayer) /* More memory allocated than what we need. */ curLayer[(*nLayer)++] = w; else { /* Need to allocate more memory. */ switch(layer) { case WEAPON_LAYER_BG: (*mLayer) += WEAPON_CHUNK; curLayer = wbackLayer = realloc(curLayer, (*mLayer)*sizeof(Weapon*)); break; case WEAPON_LAYER_FG: (*mLayer) += WEAPON_CHUNK; curLayer = wfrontLayer = realloc(curLayer, (*mLayer)*sizeof(Weapon*)); break; } curLayer[(*nLayer)++] = w; } } /** * @brief Start the beam weapon. * @param outfit Outfit which spawns the weapon. * @param dir Direction of the shooter. * @param vel Velocity of the shooter. * @param parent Pilot ID of the shooter. * @param target Target ID that is getting shot. * @param mount Mount on the ship. * @return The identifier of the beam weapon. */ int beam_start(const Outfit* outfit, const double dir, const Vec2* pos, const Vec2* vel, const unsigned int parent, const unsigned int target, const int mount) { WeaponLayer layer; Weapon* w; Weapon** curLayer; int* mLayer, *nLayer; if(!outfit_isBeam(outfit)) { ERR("Trying to create a Beam Weapon from a non-beam outfit."); return -1; } layer = (parent == PLAYER_ID) ? WEAPON_LAYER_FG : WEAPON_LAYER_BG; w = weapon_create(outfit, dir, pos, vel, parent, target); w->ID = ++beam_idgen; w->mount = mount; switch(layer) { case WEAPON_LAYER_BG: curLayer = wbackLayer; nLayer = &nwbackLayer; mLayer = &mwbackLayer; break; case WEAPON_LAYER_FG: curLayer = wfrontLayer; nLayer = &nwfrontLayer; mLayer = &mwbackLayer; break; default: ERR("Invalid WEAPON_LAYER specified."); return -1; } if(*mLayer > *nLayer) /* More memory allocated then needed. */ curLayer[(*nLayer)++] = w; else { /* Need to allocate more memory. */ switch (layer) { case WEAPON_LAYER_BG: (*mLayer) += WEAPON_CHUNK; curLayer = wbackLayer = realloc(curLayer, (*mLayer)*sizeof(Weapon*)); break; case WEAPON_LAYER_FG: (*mLayer) += WEAPON_CHUNK; curLayer = wfrontLayer = realloc(curLayer, (*mLayer)*sizeof(Weapon*)); break; } curLayer[(*nLayer)++] = w; } return w->ID; } /** * @brief End a beam weapon. * @param parent * @param beam */ void beam_end(const unsigned int parent, int beam) { int i; WeaponLayer layer; Weapon** curLayer; int* mLayer, *nLayer; layer = (parent == PLAYER_ID) ? WEAPON_LAYER_FG : WEAPON_LAYER_BG; /* Set the proper layer. */ switch(layer) { case WEAPON_LAYER_BG: curLayer = wbackLayer; nLayer = &nwbackLayer; mLayer = &mwbackLayer; break; case WEAPON_LAYER_FG: curLayer = wfrontLayer; nLayer = &nwfrontLayer; mLayer = &mwfrontLayer; break; default: ERR("Invalid WEAPON_LAYER specified."); return; } /* Now try to destroy the beam. */ for(i = 0; i < *nLayer; i++) { if(curLayer[i]->ID == beam) { /* Found it. */ weapon_destroy(curLayer[i], layer); break; } } } /** * @brief Destroys a weapon. * @param w Weapon to destroy. * @param layer Layer to which the weapon belongs. */ static void weapon_destroy(Weapon* w, WeaponLayer layer) { int i; Weapon** wlayer; int* nlayer; Pilot* pilot_target; /* Decrement target lockons if needed. */ if(outfit_isSeeker(w->outfit)) { pilot_target = pilot_get(w->target); if(pilot_target != NULL) pilot_target->lockons--; } /* Stop playing sound if beam weapon. */ if(outfit_isBeam(w->outfit)) { sound_stop(w->voice); sound_playPos(w->outfit->u.bem.sound_off, w->solid->pos.x, w->solid->pos.y); } switch(layer) { case WEAPON_LAYER_BG: wlayer = wbackLayer; nlayer = &nwbackLayer; break; case WEAPON_LAYER_FG: wlayer = wfrontLayer; nlayer = &nwfrontLayer; break; default: WARN("Unknown weapon layer!"); } for(i = 0; (wlayer[i] != w) && (i < *nlayer); i++); /* Get to the current position. */ if(i >= *nlayer) { /** @todo Figure out why this happens in the tutorial. */ WARN("Trying to destroy weapon not found in stack!"); return; } weapon_free(wlayer[i]); wlayer[i] = NULL; (*nlayer)--; for(; i < (*nlayer); i++) wlayer[i] = wlayer[i+1]; } /** * @brief Free the weapon. * @param w Weapon to free. */ static void weapon_free(Weapon* w) { solid_free(w->solid); free(w); } /** * @brief Clear all the weapons, does *not* free the layers. */ void weapon_clear(void) { int i; for(i = 0; i < nwbackLayer; i++) weapon_free(wbackLayer[i]); nwbackLayer = 0; for(i = 0; i < nwfrontLayer; i++) weapon_free(wfrontLayer[i]); nwfrontLayer = 0; } /** * @brief Destroy all the weapons and free it all. */ void weapon_exit(void) { weapon_clear(); if(wbackLayer != NULL) { free(wbackLayer); wbackLayer = NULL; mwbackLayer = 0; } if(wfrontLayer != NULL) { free(wfrontLayer); wfrontLayer = NULL; mwfrontLayer = 0; } } /** * @brief Clear possible exploded weapons. */ void weapon_explode(double x, double y, double radius, DamageType dtype, double damage, unsigned int parent, int mode) { (void)dtype; (void)damage; weapon_explodeLayer(WEAPON_LAYER_FG, x, y, radius, parent, mode); weapon_explodeLayer(WEAPON_LAYER_BG, x, y, radius, parent, mode); } static void weapon_explodeLayer(WeaponLayer layer, double x, double y, double radius, unsigned int parent, int mode) { (void)parent; int i; Weapon** curLayer; int *nLayer; double dist, rad2; /* Set the proper layer. */ switch(layer) { case WEAPON_LAYER_BG: curLayer = wbackLayer; nLayer = &nwbackLayer; break; case WEAPON_LAYER_FG: curLayer = wfrontLayer; nLayer = &nwbackLayer; break; default: ERR("Invalid WEAPON_LAYER specified."); return; } rad2 = radius*radius; /* Now try to destroy the weapons affected. */ for(i = 0; i < *nLayer; i++) { if(((mode & EXPL_MODE_MISSILE) && outfit_isAmmo(curLayer[i]->outfit)) || ((mode & EXPL_MODE_BOLT) && outfit_isBolt(curLayer[i]->outfit))) { dist = pow2(curLayer[i]->solid->pos.x - x) + pow2(curLayer[i]->solid->pos.y - y); if(dist < rad2) { weapon_destroy(curLayer[i], layer); i--; } } } }