Lephisto/src/weapon.c

1219 lines
35 KiB
C

/**
* @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 <math.h>
#include <stdlib.h>
#include <string.h>
#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 "gui.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)<h/2.) || \
(shape == RADAR_CIRCLE && (((x)*(x)+(y)*(y)) <= rc))) \
glVertex2i((x),(y)) /**< Set a pixel if within range. */
void weapon_minimap(const double res, const double w,
const double h, const RadarShape shape, double alpha) {
int i, rc;
double x, y;
/* Begin the points. */
glBegin(GL_POINTS);
ACOLOUR(cRadar_weap, alpha);
if(shape == RADAR_CIRCLE)
rc = (int)(w*w);
/* Draw the points for the weapons on all layers. */
for(i = 0; i < nwbackLayer; i++) {
x = (wbackLayer[i]->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_setHostile(p);
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_setHostile(p);
pilot_rmFlag(p, PILOT_BRIBED);
}
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 = malloc(sizeof(Weapon));
memset(w, 0, sizeof(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);
#ifdef DEBUGGING
memset(w, 0, sizeof(Weapon));
#endif
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--;
}
}
}
}