#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 "weapon.h" #define weapon_isSmart(w) (w->think != NULL) #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. */ /* Some stuff from pilot. */ extern Pilot** pilot_stack; extern int pilot_nstack; /* Player stuff. */ extern unsigned int player_target; /* Ai stuff. */ extern void ai_attacked(Pilot* attacked, const unsigned int attacker); typedef struct Weapon_ { Solid* solid; /* Actually has its own solid. :D */ 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. */ double lockon; /* Some weapons have a lockon delay. */ double timer; /* Mainly used to see when the weapon was fired. */ /* Update position and render. */ void(*update)(struct Weapon_*, const double, WeaponLayer); /* Position update and render. */ void(*think)(struct Weapon_*, const double); /* Some missiles need to be inteligent.a */ 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; /* Behind pilots. */ static int nwfrontLayer = 0; /* Number of elements. */ static int mwfrontLayer = 0; /* Allocated memory size. */ /* 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(const Weapon* w); 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); static void weapon_destroy(Weapon* w, WeaponLayer layer); static void weapon_free(Weapon* w); /* Think. */ static void think_seeker(Weapon* w, const double dt); /*static void think_smart(Weapon* w, const double dt);*/ /* Extern. */ void weapon_minimap(const double res, const double w, const double h, const RadarShape shape); /* Draw the minimap weapons (player.c). */ #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); } } #undef PIXEL /* Seeker brain, You get what you pay for. :) */ static void think_seeker(Weapon* w, const double dt) { double diff; double vel; Pilot* p; int effect; 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; } /* Ammo isn't locked on yet.. */ if(SDL_GetTicks() > (w->outfit->u.amm.lockon)) { 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.; 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);*/ } /* ======================================================== * Smart seeker brain. Much better at homing. * ======================================================== */ #if 0 static void think_smart(Weapon* w, const double dt) { Vec2 sv, tv; double t; if(w->target == w->parent) return; /* No self shooting here. */ Pilot* p = pilot_get(w->target); /* No null pilots.. */ if(p == NULL) { limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt); return; } /* Ammo isn't locked on yet. */ if(w->lockon < 0.) { vect_cset(&tv, VX(p->solid->pos) + dt*VX(p->solid->vel), VY(p->solid->pos) + dt*VY(p->solid->vel)); vect_cset(&sv, VX(w->solid->pos) + dt*VX(w->solid->vel), VY(w->solid->pos) + dt*VY(w->solid->vel)); t = -angle_diff(w->solid->dir, vect_angle(&tv, &sv)); w->solid->dir_vel = t * w->outfit->u.amm.turn; /* Face the target. */ 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; } vect_pset(&w->solid->vel, w->outfit->u.amm.speed, w->solid->dir); limit_speed(&w->solid->vel, w->outfit->u.amm.speed, dt); } #endif /* Update all the weapon layers. */ void weapons_update(const double dt) { weapons_updateLayer(dt, WEAPON_LAYER_BG); weapons_updateLayer(dt, WEAPON_LAYER_FG); } /* Update all weapons in the layer. */ static void weapons_updateLayer(const double dt, const WeaponLayer layer) { Weapon** wlayer; int* nlayer; switch(layer) { case WEAPON_LAYER_BG: wlayer = wbackLayer; nlayer = &nwbackLayer; break; case WEAPON_LAYER_FG: wlayer = wfrontLayer; nlayer = &nwfrontLayer; break; } int i; Weapon* w; for(i = 0; i < (*nlayer); i++) { w = wlayer[i]; switch(wlayer[i]->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(wlayer[i]->lockon > 0.) /* Decrement lockon. */ wlayer[i]->lockon -= dt; /* Purpose fallthrough. */ /* Bolts too. */ case OUTFIT_TYPE_BOLT: case OUTFIT_TYPE_TURRET_BOLT: wlayer[i]->timer -= dt; if(wlayer[i]->timer < 0.) { weapon_destroy(wlayer[i],layer); continue; } break; default: break; } weapon_update(wlayer[i], dt, layer); /* If the weapon has been deleted we are going to have to hold back one. */ if(w != wlayer[i]) i--; } } /* Render all the weapons. */ void weapons_render(const WeaponLayer layer) { 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; } for(i = 0; i < (*nlayer); i++) weapon_render(wlayer[i]); } /* Render the weapons. */ static void weapon_render(const Weapon* w) { int sx, sy; glTexture* gfx; gfx = outfit_gfx(w->outfit); /* Get the sprite corresponding to the direction facing. */ gl_getSpriteFromDir(&sx, &sy, gfx, w->solid->dir); gl_blitSprite(gfx, w->solid->pos.x, w->solid->pos.y, sx, sy, NULL); } /* Update the weapon. */ static void weapon_update(Weapon* w, const double dt, WeaponLayer layer) { int i, wsx, wsy, psx, psy; glTexture* gfx; gfx = outfit_gfx(w->outfit); gl_getSpriteFromDir(&wsx, &wsy, gfx, w->solid->dir); for(i = 0; i < pilot_nstack; i++) { /* Check for player to exist. */ if((i == 0) && (player == NULL)) continue; psx = pilot_stack[i]->tsx; psy = pilot_stack[i]->tsy; if(w->parent == pilot_stack[i]->id) continue; /* Hey! That's you. */ /* Smart weapons only collide with their target. */ if((weapon_isSmart(w)) && (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)) { weapon_hit(w, pilot_stack[i], layer); return; } /* Dumb weapons hit anything not of the same faction. */ if(!weapon_isSmart(w) && !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)) { weapon_hit(w, pilot_stack[i], layer); return; } } /* Smart weapons also get to think their next move. */ if(weapon_isSmart(w)) (*w->think)(w,dt); (*w->solid->update)(w->solid, dt); } /* Good shot. */ static void weapon_hit(Weapon* w, Pilot* p, WeaponLayer layer) { /* Someone should let the ai know it's been attacked. */ if(!pilot_isPlayer(p)) { if((player_target == p->id) || (RNG(0,2) == 0)) { /* 33% chance. */ if((w->parent == PLAYER_ID) && (!pilot_isFlag(p, PILOT_HOSTILE) || (RNG(0, 1) == 0))) { /* 50% chance. */ faction_modPlayer(p->faction, -1); /* Slowly lower faction. */ pilot_setFlag(p, PILOT_HOSTILE); } ai_attacked(p, w->parent); } spfx_add(outfit_spfx(w->outfit), VX(w->solid->pos), VY(w->solid->pos), VX(p->solid->vel), VY(p->solid->vel), SPFX_LAYER_BACK); } else spfx_add(outfit_spfx(w->outfit), VX(w->solid->pos), VY(w->solid->pos), 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); } 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_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->think = NULL; w->status = WEAPON_STATUS_OK; switch(outfit->type) { /* Bolts treated together. */ case OUTFIT_TYPE_BOLT: case OUTFIT_TYPE_TURRET_BOLT: /* Only difference is the direction of fire. */ if((outfit->type == OUTFIT_TYPE_TURRET_BOLT) && (w->parent != w->target) && (w->target != 0)) { /* Must have a valid target. */ pilot_target = pilot_get(w->target); /* Get the distance. */ dist = vect_dist(pos, &pilot_target->solid->pos); /* 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); vect_cset(&v, x, y); rdir = VANGLE(v); } else /* Fire straight. */ rdir = dir; rdir += NormalInverse(RNGF()*0.9 + 0.05) /* Get rid of extreme values. */ * outfit->u.blt.accuracy/2. * 1./180.0*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); sound_play(w->outfit->u.blt.sound); break; /* Treat seekers togther. */ case OUTFIT_TYPE_MISSILE_SEEK_AMMO: case OUTFIT_TYPE_MISSILE_SEEK_SMART_AMMO: mass = w->outfit->mass; 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;*/ sound_play(w->outfit->u.amm.sound); break; /* Just dump it where the player is. */ default: w->solid = solid_create(mass, dir, pos, vel); break; } return w; } /* Add a new weapon. */ void weapon_add(const Outfit* outfit, const double dir, const Vec2* pos, const Vec2* vel, unsigned int parent, unsigned int target) { if(!outfit_isWeapon(outfit) && !outfit_isAmmo(outfit) && !outfit_isTurret(outfit)) { ERR("Trying to create a weapon from a non-Weapon type Outfit"); return; } WeaponLayer layer = (parent == PLAYER_ID) ? WEAPON_LAYER_FG : WEAPON_LAYER_BG; Weapon* w = weapon_create(outfit, dir, pos, vel, parent, target); /* Set the propper layer. */ Weapon** curLayer = NULL; int* mLayer = NULL; int* nLayer = NULL; 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; } 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; } } /* Destroy the weapon. */ 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--; } switch(layer) { case WEAPON_LAYER_BG: wlayer = wbackLayer; nlayer = &nwbackLayer; break; case WEAPON_LAYER_FG: wlayer = wfrontLayer; nlayer = &nwfrontLayer; break; } for(i = 0; wlayer[i] != w; i++); /* Get us to the current posision. */ weapon_free(wlayer[i]); wlayer[i] = NULL; (*nlayer)--; for(; i < (*nlayer); i++) wlayer[i] = wlayer[i+1]; } /* Clear the weapon. */ static void weapon_free(Weapon* w) { solid_free(w->solid); free(w); } /* Clear all the weapons, do 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; } void weapon_exit(void) { weapon_clear(); free(wbackLayer); free(wfrontLayer); }