/** * @brief Adds some ammo to the pilot stock. * * @param pilot Pilot to add ammo to. * @param s Slot to add ammo to. * @param ammo Ammo to add. * @param quantity Amount to add. * @return Amount actually added. */ int pilot_addAmmo( Pilot* pilot, PilotOutfitSlot *s, Outfit* ammo, int quantity ) { int q, max; (void) pilot; /* Failure cases. */ if (s->outfit == NULL) { WARN("Pilot '%s': Trying to add ammo to unequiped slot.", pilot->name ); return 0; } else if (!outfit_isLauncher(s->outfit) && !outfit_isFighterBay(s->outfit)) { WARN("Pilot '%s': Trying to add ammo to non-launcher/fighterbay type outfit '%s'", pilot->name, s->outfit->name); return 0; } else if (!outfit_isAmmo(ammo) && !outfit_isFighter(ammo)) { WARN( "Pilot '%s': Trying to add non-ammo/fighter type outfit '%s' as ammo.", pilot->name, ammo->name ); return 0; } else if (outfit_isLauncher(s->outfit) && outfit_isFighter(ammo)) { WARN("Pilot '%s': Trying to add fighter '%s' as launcher '%s' ammo", pilot->name, ammo->name, s->outfit->name ); return 0; } else if (outfit_isFighterBay(s->outfit) && outfit_isAmmo(ammo)) { WARN("Pilot '%s': Trying to add ammo '%s' as fighter bay '%s' ammo", pilot->name, ammo->name, s->outfit->name ); return 0; } else if ((s->u.ammo.outfit != NULL) && (s->u.ammo.quantity > 0) && (s->u.ammo.outfit != ammo)) { WARN("Pilot '%s': Trying to add ammo to outfit that already has ammo.", pilot->name ); return 0; } /* Set the ammo type. */ s->u.ammo.outfit = ammo; /* Add the ammo. */ max = outfit_amount(s->outfit) - s->u.ammo.deployed; q = s->u.ammo.quantity; /* Amount have. */ s->u.ammo.quantity += quantity; s->u.ammo.quantity = MIN( max, s->u.ammo.quantity ); q = s->u.ammo.quantity - q; /* Amount actually added. */ pilot->mass_outfit += q * s->u.ammo.outfit->mass; pilot_updateMass( pilot ); return q; }
/** * @brief Frees the outfit stack. */ void outfit_free (void) { int i; Outfit *o; for (i=0; i < outfit_nstack; i++) { o = &outfit_stack[i]; /* free graphics */ if (outfit_gfx(&outfit_stack[i])) gl_freeTexture(outfit_gfx(&outfit_stack[i])); /* Type specific. */ if (outfit_isLauncher(o) && o->u.lau.ammo_name) free(o->u.lau.ammo_name); if (outfit_isFighterBay(o) && o->u.bay.ammo_name) free(o->u.bay.ammo_name); if (outfit_isFighter(o) && o->u.fig.ship) free(o->u.fig.ship); /* strings */ if (o->description) free(o->description); if (o->gfx_store) gl_freeTexture(o->gfx_store); if (o->license) free(o->license); free(o->name); } free(outfit_stack); }
/** * @brief Removes some ammo from the pilot stock. * * @param pilot Pilot to remove ammo from. * @param s Slot to remove ammo from. * @param quantity Amount to remove. * @return Amount actually removed. */ int pilot_rmAmmo( Pilot* pilot, PilotOutfitSlot *s, int quantity ) { (void) pilot; int q; /* Failure cases. */ if (s->outfit == NULL) { WARN("Pilot '%s': Trying to remove ammo from unequiped slot.", pilot->name ); return 0; } else if (!outfit_isLauncher(s->outfit) && !outfit_isFighterBay(s->outfit)) { WARN("Pilot '%s': Trying to remove ammo from non-launcher/fighter bay type outfit '%s'", pilot->name, s->outfit->name); return 0; } /* No ammo already. */ if (s->u.ammo.outfit == NULL) return 0; /* Remove ammo. */ q = MIN( quantity, s->u.ammo.quantity ); s->u.ammo.quantity -= q; pilot->mass_outfit -= q * s->u.ammo.outfit->mass; pilot_updateMass( pilot ); /* We don't set the outfit to null so it "remembers" old ammo. */ return q; }
/** * @brief Computes an estimation of ammo flying time * * @param w the weapon that shoot * @param parent Parent of the weapon * @param target Target of the weapon */ double pilot_weapFlyTime( Outfit *o, Pilot *parent, Vector2d *pos, Vector2d *vel) { Vector2d approach_vector, relative_location, orthoradial_vector; double speed, radial_speed, orthoradial_speed, dist, t; dist = vect_dist( &parent->solid->pos, pos ); /* Beam weapons */ if (outfit_isBeam(o)) { if (dist > o->u.bem.range) return INFINITY; return 0.; } /* A bay doesn't have range issues */ if (outfit_isFighterBay(o)) return 0.; /* Rockets use absolute velocity while bolt use relative vel */ if (outfit_isLauncher(o)) vect_cset( &approach_vector, - vel->x, - vel->y ); else vect_cset( &approach_vector, VX(parent->solid->vel) - vel->x, VY(parent->solid->vel) - vel->y ); speed = outfit_speed(o); /* Get the vector : shooter -> target */ vect_cset( &relative_location, pos->x - VX(parent->solid->pos), pos->y - VY(parent->solid->pos) ); /* Get the orthogonal vector */ vect_cset(&orthoradial_vector, VY(parent->solid->pos) - pos->y, pos->x - VX(parent->solid->pos) ); radial_speed = vect_dot( &approach_vector, &relative_location ); radial_speed = radial_speed / VMOD(relative_location); orthoradial_speed = vect_dot(&approach_vector, &orthoradial_vector); orthoradial_speed = orthoradial_speed / VMOD(relative_location); if( ((speed*speed - VMOD(approach_vector)*VMOD(approach_vector)) != 0) && (speed*speed - orthoradial_speed*orthoradial_speed) > 0) t = dist * (sqrt( speed*speed - orthoradial_speed*orthoradial_speed ) - radial_speed) / (speed*speed - VMOD(approach_vector)*VMOD(approach_vector)); else return INFINITY; /* if t < 0, try the other solution */ if (t < 0) t = - dist * (sqrt( speed*speed - orthoradial_speed*orthoradial_speed ) + radial_speed) / (speed*speed - VMOD(approach_vector)*VMOD(approach_vector)); /* if t still < 0, no solution */ if (t < 0) return INFINITY; return t; }
/** * @brief Gets the outfit's delay. * @param o Outfit to get information from. */ int outfit_delay( const Outfit* o ) { if (outfit_isBolt(o)) return o->u.blt.delay; else if (outfit_isBeam(o)) return o->u.bem.delay; else if (outfit_isLauncher(o)) return o->u.lau.delay; else if (outfit_isFighterBay(o)) return o->u.bay.delay; return -1; }
/** * @brief Adds an outfit to the pilot, ignoring CPU or other limits. * * @note Does not call pilot_calcStats(). * * @param pilot Pilot to add the outfit to. * @param outfit Outfit to add to the pilot. * @param s Slot to add ammo to. * @return 0 on success. */ int pilot_addOutfitRaw( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s ) { Outfit *o; /* Set the outfit. */ s->outfit = outfit; s->quantity = 1; /* Sort of pointless, but hey. */ /* Set some default parameters. */ s->timer = 0.; /* Some per-case scenarios. */ if (outfit_isFighterBay(outfit)) { s->u.ammo.outfit = NULL; s->u.ammo.quantity = 0; s->u.ammo.deployed = 0; } if (outfit_isTurret(outfit)) /* used to speed up AI */ pilot_setFlag(pilot, PILOT_HASTURRET); if (outfit_isBeam(outfit)) { /* Used to speed up some calculations. */ s->u.beamid = -1; pilot_setFlag(pilot, PILOT_HASBEAMS); } if (outfit_isLauncher(outfit)) { s->u.ammo.outfit = NULL; s->u.ammo.quantity = 0; s->u.ammo.deployed = 0; /* Just in case. */ } /* Check if active. */ o = s->outfit; if (outfit_isForward(o) || outfit_isTurret(o) || outfit_isLauncher(o) || outfit_isFighterBay(o)) s->active = 1; else s->active = 0; /* Update heat. */ pilot_heatCalcSlot( s ); return 0; }
/** * @brief Checks to see if the pilot has deployed ships. * * @param p Pilot to see if has deployed ships. * @return 1 if pilot has deployed ships, 0 otherwise. */ int pilot_hasDeployed( Pilot *p ) { int i; for (i=0; i<p->noutfits; i++) { if (p->outfits[i]->outfit == NULL) continue; if (outfit_isFighterBay(p->outfits[i]->outfit)) if (p->outfits[i]->u.ammo.deployed > 0) return 1; } return 0; }
/** * @brief Loads all the outfits. * * @return 0 on success. */ int outfit_load (void) { int i, mem; uint32_t bufsize; char *buf = ndata_read( OUTFIT_DATA, &bufsize ); xmlNodePtr node; xmlDocPtr doc = xmlParseMemory( buf, bufsize ); node = doc->xmlChildrenNode; if (!xml_isNode(node,XML_OUTFIT_ID)) { ERR("Malformed '"OUTFIT_DATA"' file: missing root element '"XML_OUTFIT_ID"'"); return -1; } node = node->xmlChildrenNode; /* first system node */ if (node == NULL) { ERR("Malformed '"OUTFIT_DATA"' file: does not contain elements"); return -1; } /* First pass, loads up ammunition. */ mem = 0; do { if (xml_isNode(node,XML_OUTFIT_TAG)) { outfit_nstack++; if (outfit_nstack > mem) { mem += CHUNK_SIZE; outfit_stack = realloc(outfit_stack, sizeof(Outfit)*mem); } outfit_parse( &outfit_stack[outfit_nstack-1], node ); } } while (xml_nextNode(node)); /* Shrink back to minimum - shouldn't change ever. */ outfit_stack = realloc(outfit_stack, sizeof(Outfit) * outfit_nstack); /* Second pass, sets up ammunition relationships. */ for (i=0; i<outfit_nstack; i++) { if (outfit_isLauncher(&outfit_stack[i])) outfit_stack[i].u.lau.ammo = outfit_get( outfit_stack[i].u.lau.ammo_name ); else if (outfit_isFighterBay(&outfit_stack[i])) outfit_stack[i].u.bay.ammo = outfit_get( outfit_stack[i].u.bay.ammo_name ); } xmlFreeDoc(doc); free(buf); DEBUG("Loaded %d Outfit%s", outfit_nstack, (outfit_nstack==1) ? "" : "s" ); return 0; }
/** * @brief Gets the outfit's broad type. * * @param o Outfit to get the type of. * @return The outfit's broad type in human readable form. */ const char* outfit_getTypeBroad( const Outfit* o ) { if (outfit_isBolt(o)) return "Bolt Weapon"; else if (outfit_isBeam(o)) return "Beam Weapon"; else if (outfit_isLauncher(o)) return "Launcher"; else if (outfit_isAmmo(o)) return "Ammo"; else if (outfit_isTurret(o)) return "Turret"; else if (outfit_isMod(o)) return "Modification"; else if (outfit_isAfterburner(o)) return "Afterburner"; else if (outfit_isJammer(o)) return "Jammer"; else if (outfit_isFighterBay(o)) return "Fighter Bay"; else if (outfit_isFighter(o)) return "Fighter"; else if (outfit_isMap(o)) return "Map"; else if (outfit_isLicense(o)) return "License"; else return "Unknown"; }
/** * @brief Adds an outfit to a weapon set. * * @param p Pilot to manipulate. * @param id ID of the weapon set. * @param o Outfit to add. * @param level Level of the trigger. */ void pilot_weapSetAdd( Pilot* p, int id, PilotOutfitSlot *o, int level ) { PilotWeaponSet *ws; PilotWeaponSetOutfit *slot; Outfit *oo; int i; ws = pilot_weapSet(p,id); /* Make sure outfit is valid. */ oo = o->outfit; if (oo == NULL) return; /* Make sure outfit type is weapon (or usable). */ if (!(outfit_isBeam(oo) || outfit_isBolt(oo) || outfit_isLauncher(oo) || outfit_isFighterBay(oo))) return; /* Create if needed. */ if (ws->slots == NULL) ws->slots = array_create( PilotWeaponSetOutfit ); /* Check if already there. */ for (i=0; i<array_size(ws->slots); i++) { if (ws->slots[i].slot == o) { ws->slots[i].level = level; /* Update if needed. */ if (id == p->active_set) pilot_weapSetUpdateOutfits( p, ws ); return; } } /* Add it. */ slot = &array_grow( &ws->slots ); slot->level = level; slot->slot = o; /* Update range. */ pilot_weapSetUpdateRange( ws ); /* Update if needed. */ if (id == p->active_set) pilot_weapSetUpdateOutfits( p, ws ); }
/** * @brief Adds an outfit to the pilot, ignoring CPU or other limits. * * @note Does not call pilot_calcStats(). * * @param pilot Pilot to add the outfit to. * @param outfit Outfit to add to the pilot. * @param s Slot to add ammo to. * @return 0 on success. */ int pilot_addOutfitRaw( Pilot* pilot, Outfit* outfit, PilotOutfitSlot *s ) { Outfit *o; /* Set the outfit. */ s->outfit = outfit; /* Set some default parameters. */ s->timer = 0.; /* Some per-case scenarios. */ if (outfit_isFighterBay(outfit)) { s->u.ammo.outfit = NULL; s->u.ammo.quantity = 0; s->u.ammo.deployed = 0; } if (outfit_isTurret(outfit)) /* used to speed up AI */ pilot->nturrets++; else if (outfit_isBolt(outfit)) pilot->ncannons++; else if (outfit_isJammer(outfit)) pilot->njammers++; else if (outfit_isAfterburner(outfit)) pilot->nafterburners++; if (outfit_isBeam(outfit)) { /* Used to speed up some calculations. */ s->u.beamid = 0; pilot->nbeams++; } if (outfit_isLauncher(outfit)) { s->u.ammo.outfit = NULL; s->u.ammo.quantity = 0; s->u.ammo.deployed = 0; /* Just in case. */ } /* Check if active. */ o = s->outfit; s->active = outfit_isActive(o); /* Update heat. */ pilot_heatCalcSlot( s ); return 0; }
/** * @brief Docks the pilot on its target pilot. * * @param p Pilot that wants to dock. * @param target Pilot to dock on. * @param deployed Was pilot already deployed? * @return 0 on successful docking. */ int pilot_dock( Pilot *p, Pilot *target, int deployed ) { int i; Outfit *o = NULL; /* Must be close. */ if (vect_dist(&p->solid->pos, &target->solid->pos) > target->ship->gfx_space->sw * PILOT_SIZE_APROX ) return -1; /* Cannot be going much faster. */ if ((pow2(VX(p->solid->vel)-VX(target->solid->vel)) + pow2(VY(p->solid->vel)-VY(target->solid->vel))) > (double)pow2(MAX_HYPERSPACE_VEL)) return -1; /* Check to see if target has an available bay. */ for (i=0; i<target->noutfits; i++) { /* Must have outfit. */ if (target->outfits[i]->outfit == NULL) continue; /* Must be fighter bay. */ if (!outfit_isFighterBay(target->outfits[i]->outfit)) continue; /* Must have deployed. */ if (deployed && (target->outfits[i]->u.ammo.deployed <= 0)) continue; o = outfit_ammo(target->outfits[i]->outfit); /* Try to add fighter. */ if (outfit_isFighter(o) && (strcmp(p->ship->name,o->u.fig.ship)==0)) { if (deployed) target->outfits[i]->u.ammo.deployed -= 1; break; } } if ((o==NULL) || (i >= target->noutfits)) return -1; /* Add the pilot's outfit. */ if (pilot_addAmmo(target, target->outfits[i], o, 1) != 1) return -1; /* Remove from pilot's escort list. */ if (deployed) { for (i=0; i<target->nescorts; i++) { if ((target->escorts[i].type == ESCORT_TYPE_BAY) && (target->escorts[i].id == p->id)) break; } /* Not found as pilot's escorts. */ if (i >= target->nescorts) return -1; /* Free if last pilot. */ if (target->nescorts == 1) { free(target->escorts); target->escorts = NULL; target->nescorts = 0; } else { memmove( &target->escorts[i], &target->escorts[i+1], sizeof(Escort_t) * (target->nescorts-i-1) ); target->nescorts--; } } /* Destroy the pilot. */ pilot_delete(p); return 0; }
/** * @brief Checks to see if can equip/remove an outfit from a slot. * * @param p Pilot to check if can equip. * @param s Slot being checked to see if it can equip/remove an outfit. * @param o Outfit to check. * @param add Whether or not to consider it's being added or removed. * @return NULL if can swap, or error message if can't. */ const char* pilot_canEquip( Pilot *p, PilotOutfitSlot *s, Outfit *o, int add ) { /* Just in case. */ if ((p==NULL) || (o==NULL)) return "Nothing selected."; /* Check slot type. */ if ((s != NULL) && !outfit_fitsSlot( o, &s->sslot->slot )) return "Does not fit slot."; /* Adding outfit. */ if (add) { if ((outfit_cpu(o) > 0) && (p->cpu < outfit_cpu(o))) return "Insufficient CPU"; /* Can't add more than one outfit of the same type if the outfit type is limited. */ if ((o->limit != NULL) && pilot_hasOutfitLimit( p, o->limit )) return "Already have an outfit of this type installed"; /* Must not drive some things negative. */ if (outfit_isMod(o)) { /* * Movement. */ /* TODO fix this to work with ship stats. CHECK_STAT_R( o->u.mod.thrust, o->u.mod.thrust_rel, p->ship->thrust, "Insufficient thrust" ); CHECK_STAT_R( o->u.mod.turn, o->u.mod.turn_rel, p->ship->turn, "Insufficient turn" ); CHECK_STAT_R( o->u.mod.speed, o->u.mod.speed_rel, p->ship->speed, "Insufficient speed" ); */ /* * Health. */ /* Max. */ /* TODO fix this to work with ship stats. CHECK_STAT_R( o->u.mod.armour, o->u.mod.armour_rel, p->armour_max, "Insufficient armour" ); CHECK_STAT_R( o->u.mod.shield, o->u.mod.shield_rel, p->shield_max, "Insufficient shield" ); CHECK_STAT_R( o->u.mod.energy, o->u.mod.energy_rel, p->energy_max, "Insufficient energy" ); */ /* Regen. */ /* TODO fix this to work with ship stats. CHECK_STAT( o->u.mod.armour_regen, p->armour_regen, "Insufficient armour regeneration" ); CHECK_STAT( o->u.mod.shield_regen, p->shield_regen, "Insufficient shield regeneration" ); CHECK_STAT( o->u.mod.energy_regen, p->energy_regen, "Insufficient energy regeneration" ); */ /* * Misc. */ CHECK_STAT( o->u.mod.fuel, p->fuel_max, "Insufficient fuel" ); CHECK_STAT( o->u.mod.cargo, p->cargo_free, "Insufficient cargo space" ); } } /* Removing outfit. */ else { if ((outfit_cpu(o) < 0) && (p->cpu < fabs(outfit_cpu(o)))) return "Lower CPU usage first"; /* Must not drive some things negative. */ if (outfit_isMod(o)) { /* * Movement. */ /* TODO fix this to work with ship stats. if (((o->u.mod.thrust + o->u.mod.thrust_rel * p->ship->thrust) > 0) && (o->u.mod.thrust + o->u.mod.thrust_rel * p->ship->thrust > p->thrust)) return "Increase thrust first"; if (((o->u.mod.speed + o->u.mod.speed_rel * p->ship->speed) > 0) && (o->u.mod.speed + o->u.mod.speed_rel * p->ship->speed > p->speed)) return "Increase speed first"; if (((o->u.mod.turn + o->u.mod.turn_rel * p->ship->turn * p->ship->mass/p->solid->mass) > 0) && (fabs(o->u.mod.turn + o->u.mod.turn_rel * p->ship->turn * p->ship->mass/p->solid->mass) > p->turn_base)) return "Increase turn first"; */ /* * Health. */ /* Max. */ /* TODO fix this to work with ship stats. if ((o->u.mod.armour > 0) && (o->u.mod.armour > p->armour_max)) return "Increase armour first"; if ((o->u.mod.shield > 0) && (o->u.mod.shield > p->shield_max)) return "Increase shield first"; if ((o->u.mod.energy > 0) && (o->u.mod.energy > p->energy_max)) return "Increase energy first"; */ /* Regen. */ /* TODO fix this to work with ship stats. if ((o->u.mod.armour_regen > 0) && (o->u.mod.armour_regen > p->armour_regen)) return "Lower energy usage first"; if ((o->u.mod.shield_regen > 0) && (o->u.mod.shield_regen > p->shield_regen)) return "Lower shield usage first"; if ((o->u.mod.energy_regen > 0) && (o->u.mod.energy_regen > p->energy_regen)) return "Lower energy usage first"; */ /* * Misc. */ if ((o->u.mod.fuel > 0) && (o->u.mod.fuel > p->fuel_max)) return "Increase fuel first"; if ((o->u.mod.cargo > 0) && (o->u.mod.cargo > p->cargo_free)) return "Increase free cargo space first"; } else if (outfit_isFighterBay(o)) { if ((s!=NULL) && (s->u.ammo.deployed > 0)) return "Recall the fighters first"; } } /* Can equip. */ return NULL; }
/** * @brief Actually handles the shooting, how often the player.p can shoot and such. * * @param p Pilot that is shooting. * @param w Pilot's outfit to shoot. * @param time Expected flight time. * @return 0 if nothing was shot and 1 if something was shot. */ static int pilot_shootWeapon( Pilot* p, PilotOutfitSlot* w, double time ) { Vector2d vp, vv; double rate_mod, energy_mod; double energy; int j; /* Make sure weapon has outfit. */ if (w->outfit == NULL) return 0; /* Reset beam shut-off if needed. */ if (outfit_isBeam(w->outfit) && w->outfit->u.bem.min_duration) w->stimer = INFINITY; /* check to see if weapon is ready */ if (w->timer > 0.) return 0; /* Calculate rate modifier. */ pilot_getRateMod( &rate_mod, &energy_mod, p, w->outfit ); /* Get weapon mount position. */ pilot_getMount( p, w, &vp ); vp.x += p->solid->pos.x; vp.y += p->solid->pos.y; /* Modify velocity to take into account the rotation. */ vect_cset( &vv, p->solid->vel.x + vp.x*p->solid->dir_vel, p->solid->vel.y + vp.y*p->solid->dir_vel ); /* * regular bolt weapons */ if (outfit_isBolt(w->outfit)) { /* enough energy? */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target, time ); } /* * Beam weapons. */ else if (outfit_isBeam(w->outfit)) { /* Don't fire if the existing beam hasn't been destroyed yet. */ if (w->u.beamid > 0) return 0; /* Check if enough energy to last a second. */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; /** @todo Handle warmup stage. */ w->state = PILOT_OUTFIT_ON; w->u.beamid = beam_start( w->outfit, p->solid->dir, &vp, &p->solid->vel, p, p->target, w ); w->timer = w->outfit->u.bem.duration; return 1; /* Return early due to custom timer logic. */ } /* * missile launchers * * must be a secondary weapon */ else if (outfit_isLauncher(w->outfit)) { /* Shooter can't be the target - sanity check for the player.p */ if ((w->outfit->u.lau.ammo->u.amm.ai != AMMO_AI_DUMB) && (p->id==p->target)) return 0; /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* enough energy? */ if (outfit_energy(w->u.ammo.outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->u.ammo.outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target, time ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; p->solid->mass -= w->u.ammo.outfit->mass; pilot_updateMass( p ); /* If last ammo was shot, update the range */ if (w->u.ammo.quantity <= 0) { for (j=0; j<PILOT_WEAPON_SETS; j++) pilot_weapSetUpdateRange( &p->weapon_sets[j] ); } } /* * Fighter bays. */ else if (outfit_isFighterBay(w->outfit)) { /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* Create the escort. */ escort_create( p, w->u.ammo.outfit->u.fig.ship, &vp, &p->solid->vel, p->solid->dir, ESCORT_TYPE_BAY, 1 ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; w->u.ammo.deployed += 1; /* Mark as deployed. */ pilot_updateMass( p ); } else WARN(_("Shooting unknown weapon type: %s"), w->outfit->name); /* Reset timer. */ w->timer += rate_mod * outfit_delay( w->outfit ); return 1; }
/** * @brief Tries to automatically set and create the pilot's weapon set. * * Weapon set 0 is for all weapons. <br /> * Weapon set 1 is for forward weapons. Ammo using weapons are secondaries. <br /> * Weapon set 2 is for turret weapons. Ammo using weapons are secondaries. <br /> * Weapon set 3 is for all weapons. Forwards are primaries and turrets are secondaries. <br /> * Weapon set 4 is for seeking weapons. High payload variants are secondaries. <br /> * Weapon set 5 is for fighter bays. <br /> * * @param p Pilot to automagically generate weapon lists. */ void pilot_weaponAuto( Pilot *p ) { PilotOutfitSlot *slot; Outfit *o; int i, level, id; /* Clear weapons. */ pilot_weaponClear( p ); /* Set modes. */ pilot_weapSetMode( p, 0, 0 ); pilot_weapSetMode( p, 1, 0 ); pilot_weapSetMode( p, 2, 0 ); pilot_weapSetMode( p, 3, 0 ); pilot_weapSetMode( p, 4, 1 ); pilot_weapSetMode( p, 5, 1 ); pilot_weapSetMode( p, 6, 0 ); pilot_weapSetMode( p, 7, 0 ); pilot_weapSetMode( p, 8, 0 ); pilot_weapSetMode( p, 9, 0 ); /* Set names. */ pilot_weapSetNameSet( p, 0, "All" ); pilot_weapSetNameSet( p, 1, "Forward" ); pilot_weapSetNameSet( p, 2, "Turret" ); pilot_weapSetNameSet( p, 3, "Fwd/Tur" ); pilot_weapSetNameSet( p, 4, "Seekers" ); pilot_weapSetNameSet( p, 5, "Fighter Bays" ); pilot_weapSetNameSet( p, 6, "Weaponset 7" ); pilot_weapSetNameSet( p, 7, "Weaponset 8" ); pilot_weapSetNameSet( p, 8, "Weaponset 9" ); pilot_weapSetNameSet( p, 9, "Weaponset 0" ); /* Iterate through all the outfits. */ for (i=0; i<p->outfit_nweapon; i++) { slot = &p->outfit_weapon[i]; o = slot->outfit; /* Must have outfit. */ if (o == NULL) { slot->level = -1; /* Clear level. */ continue; } /* Bolts and beams. */ if (outfit_isBolt(o) || outfit_isBeam(o) || (outfit_isLauncher(o) && !outfit_isSeeker(o->u.lau.ammo))) { id = outfit_isTurret(o) ? 2 : 1; level = (outfit_ammo(o) != NULL) ? 1 : 0; } /* Seekers. */ else if (outfit_isLauncher(o) && outfit_isSeeker(o->u.lau.ammo)) { id = 4; level = 1; } /* Fighter bays. */ else if (outfit_isFighterBay(o)) { id = 5; level = 0; } /* Ignore rest. */ else continue; /* Add to it's base group. */ pilot_weapSetAdd( p, id, slot, level ); /* Also add another copy to another group. */ if (id == 1) { /* Forward. */ pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */ pilot_weapSetAdd( p, 3, slot, 0 ); /* Also get added to 'Fwd/Tur'. */ } else if (id == 2) { /* Turrets. */ pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */ pilot_weapSetAdd( p, 3, slot, 1 ); /* Also get added to 'Fwd/Tur'. */ } else if (id == 4) { /* Seekers */ pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */ } } }
/** * @brief Gets the outfit's ammo. * @param o Outfit to get information from. */ Outfit* outfit_ammo( const Outfit* o ) { if (outfit_isLauncher(o)) return o->u.lau.ammo; else if (outfit_isFighterBay(o)) return o->u.bay.ammo; return NULL; }
/** * @brief Checks to see if can equip/remove an outfit from a slot. * * @return NULL if can swap, or error message if can't. */ const char* pilot_canEquip( Pilot *p, PilotOutfitSlot *s, Outfit *o, int add ) { /* Just in case. */ if ((p==NULL) || (o==NULL)) return "Nothing selected."; /* Check slot type. */ if ((s != NULL) && !outfit_fitsSlot( o, &s->slot )) return "Does not fit slot."; /* Adding outfit. */ if (add) { if ((outfit_cpu(o) > 0) && (p->cpu < outfit_cpu(o))) return "Insufficient CPU"; /* Can't add more than one afterburner. */ if (outfit_isAfterburner(o) && (p->afterburner != NULL)) return "Already have an afterburner"; /* Must not drive some things negative. */ if (outfit_isMod(o)) { /* * Movement. */ if (((o->u.mod.thrust + o->u.mod.thrust_rel * p->ship->thrust) < 0) && (fabs(o->u.mod.thrust + o->u.mod.thrust_rel * p->ship->thrust) > p->thrust)) return "Insufficient thrust"; if (((o->u.mod.speed + o->u.mod.speed_rel * p->ship->speed) < 0) && (fabs(o->u.mod.speed + o->u.mod.speed_rel * p->ship->speed) > p->speed)) return "Insufficient speed"; if (((o->u.mod.turn + o->u.mod.turn_rel * p->ship->turn * p->ship->mass/p->solid->mass) < 0) && (fabs(o->u.mod.turn + o->u.mod.turn_rel * p->ship->turn * p->ship->mass/p->solid->mass) > p->turn_base)) return "Insufficient turn"; /* * Health. */ /* Max. */ if ((o->u.mod.armour < 0) && (fabs(o->u.mod.armour) > p->armour_max)) return "Insufficient armour"; if ((o->u.mod.armour_rel < 0.) && (fabs(o->u.mod.armour_rel * p->ship->armour) > p->armour_max)) return "Insufficient armour"; if ((o->u.mod.shield < 0) && (fabs(o->u.mod.shield) > p->shield_max)) return "Insufficient shield"; if ((o->u.mod.shield_rel < 0.) && (fabs(o->u.mod.shield_rel * p->ship->shield) > p->shield_max)) return "Insufficient shield"; if ((o->u.mod.energy < 0) && (fabs(o->u.mod.energy) > p->armour_max)) return "Insufficient energy"; if ((o->u.mod.energy_rel < 0.) && (fabs(o->u.mod.energy_rel * p->ship->energy) > p->energy_max)) return "Insufficient energy"; /* Regen. */ if ((o->u.mod.armour_regen < 0) && (fabs(o->u.mod.armour_regen) > p->armour_regen)) return "Insufficient energy regeneration"; if ((o->u.mod.shield_regen < 0) && (fabs(o->u.mod.shield_regen) > p->shield_regen)) return "Insufficient shield regeneration"; if ((o->u.mod.energy_regen < 0) && (fabs(o->u.mod.energy_regen) > p->energy_regen)) return "Insufficient energy regeneration"; /* * Misc. */ if ((o->u.mod.fuel < 0) && (fabs(o->u.mod.fuel) > p->fuel_max)) return "Insufficient fuel"; if ((o->u.mod.cargo < 0) && (fabs(o->u.mod.cargo) > p->cargo_free)) return "Insufficient cargo space"; } } /* Removing outfit. */ else { if ((outfit_cpu(o) < 0) && (p->cpu < fabs(outfit_cpu(o)))) return "Lower CPU usage first"; /* Must not drive some things negative. */ if (outfit_isMod(o)) { /* * Movement. */ if (((o->u.mod.thrust + o->u.mod.thrust_rel * p->ship->thrust) > 0) && (o->u.mod.thrust + o->u.mod.thrust_rel * p->ship->thrust > p->thrust)) return "Increase thrust first"; if (((o->u.mod.speed + o->u.mod.speed_rel * p->ship->speed) > 0) && (o->u.mod.speed + o->u.mod.speed_rel * p->ship->speed > p->speed)) return "Increase speed first"; if (((o->u.mod.turn + o->u.mod.turn_rel * p->ship->turn * p->ship->mass/p->solid->mass) > 0) && (fabs(o->u.mod.turn + o->u.mod.turn_rel * p->ship->turn * p->ship->mass/p->solid->mass) > p->turn_base)) return "Increase turn first"; /* * Health. */ /* Max. */ if ((o->u.mod.armour > 0) && (o->u.mod.armour > p->armour_max)) return "Increase armour first"; if ((o->u.mod.shield > 0) && (o->u.mod.shield > p->shield_max)) return "Increase shield first"; if ((o->u.mod.energy > 0) && (o->u.mod.energy > p->energy_max)) return "Increase energy first"; /* Regen. */ if ((o->u.mod.armour_regen > 0) && (o->u.mod.armour_regen > p->armour_regen)) return "Lower energy usage first"; if ((o->u.mod.shield_regen > 0) && (o->u.mod.shield_regen > p->shield_regen)) return "Lower shield usage first"; if ((o->u.mod.energy_regen > 0) && (o->u.mod.energy_regen > p->energy_regen)) return "Lower energy usage first"; /* * Misc. */ if ((o->u.mod.fuel > 0) && (o->u.mod.fuel > p->fuel_max)) return "Increase fuel first"; if ((o->u.mod.cargo > 0) && (o->u.mod.cargo > p->cargo_free)) return "Increase free cargo space first"; } else if (outfit_isFighterBay(o)) { if ((s!=NULL) && (s->u.ammo.deployed > 0)) return "Recall the fighters first"; } } /* Can equip. */ return NULL; }
/** * @brief Fires a weapon set. * * @param p Pilot firing weaponsets. * @param ws Weapon set to fire. * @param level Level of the firing weapon set. */ static int pilot_weapSetFire( Pilot *p, PilotWeaponSet *ws, int level ) { int i, j, ret, s; Pilot *pt; double dist2; Outfit *o; /* Case no outfits. */ if (ws->slots == NULL) return 0; /* If inrange is set we only fire at targets in range. */ dist2 = INFINITY; /* With no target we just set distance to infinity. */ if (ws->inrange) { if (p->target != p->id) { pt = pilot_get( p->target ); if (pt != NULL) dist2 = vect_dist2( &p->solid->pos, &pt->solid->pos ); } } /* Fire. */ ret = 0; for (i=0; i<array_size(ws->slots); i++) { o = ws->slots[i].slot->outfit; /* Ignore NULL outfits. */ if (o == NULL) continue; /* Only "active" outfits. */ if ((level != -1) && (ws->slots[i].level != level)) continue; /* Only run once for each weapon type in the group. */ s = 0; for (j=0; j<i; j++) { /* Only active outfits. */ if ((level != -1) && (ws->slots[j].level != level)) continue; /* Found a match. */ if (ws->slots[j].slot->outfit == o) { s = 1; break; } } if (s!=0) continue; /* Only "locked on" outfits. */ if (outfit_isSeeker(o) && (ws->slots[i].slot->u.ammo.lockon_timer > 0.)) continue; /* Only "inrange" outfits. */ if (!outfit_isFighterBay(o) && (ws->inrange && (dist2 > ws->slots[i].range2))) continue; /* Shoot the weapon of the weaponset. */ ret += pilot_shootWeaponSetOutfit( p, ws, o, level ); } return ret; }
/** * @brief Tries to automatically set and create the pilot's weapon set. * * Weapon set 0 is for all weapons. <br /> * Weapon set 1 is for forward weapons. Ammo using weapons are secondaries. <br /> * Weapon set 2 is for turret weapons. Ammo using weapons are secondaries. <br /> * Weapon set 3 is for all weapons. Forwards are primaries and turrets are secondaries. <br /> * Weapon set 4 is for seeking weapons. High payload variants are secondaries. <br /> * Weapon set 5 is for fighter bays. <br /> * * @param p Pilot to automagically generate weapon lists. */ void pilot_weaponAuto( Pilot *p ) { PilotOutfitSlot *slot; Outfit *o; int i, level, id; /* Clear weapons. */ pilot_weaponClear( p ); /* Set modes. */ pilot_weapSetType( p, 0, WEAPSET_TYPE_CHANGE ); pilot_weapSetType( p, 1, WEAPSET_TYPE_CHANGE ); pilot_weapSetType( p, 2, WEAPSET_TYPE_CHANGE ); pilot_weapSetType( p, 3, WEAPSET_TYPE_CHANGE ); pilot_weapSetType( p, 4, WEAPSET_TYPE_WEAPON ); pilot_weapSetType( p, 5, WEAPSET_TYPE_WEAPON ); pilot_weapSetType( p, 6, WEAPSET_TYPE_ACTIVE ); pilot_weapSetType( p, 7, WEAPSET_TYPE_ACTIVE ); pilot_weapSetType( p, 8, WEAPSET_TYPE_ACTIVE ); pilot_weapSetType( p, 9, WEAPSET_TYPE_ACTIVE ); /* All should be inrange. */ if (!pilot_isPlayer(p)) for (i=0; i<PILOT_WEAPON_SETS; i++){ pilot_weapSetInrange( p, i, 1 ); /* Update range and speed (at 0)*/ pilot_weapSetUpdateRange( &p->weapon_sets[i] ); } /* Iterate through all the outfits. */ for (i=0; i<p->noutfits; i++) { slot = p->outfits[i]; o = slot->outfit; /* Must be non-empty, and a weapon or active outfit. */ if ((o == NULL) || !outfit_isActive(o)) { slot->level = -1; /* Clear level. */ slot->weapset = -1; continue; } /* Manually defined group preempts others. */ if (o->group) { id = o->group; } /* Bolts and beams. */ else if (outfit_isBolt(o) || outfit_isBeam(o) || (outfit_isLauncher(o) && !outfit_isSeeker(o->u.lau.ammo))) { id = outfit_isTurret(o) ? 2 : 1; } /* Seekers. */ else if (outfit_isLauncher(o) && outfit_isSeeker(o->u.lau.ammo)) { id = 4; } /* Fighter bays. */ else if (outfit_isFighterBay(o)) { id = 5; } /* Ignore rest. */ else { slot->level = -1; continue; } /* Set level based on secondary flag. */ level = outfit_isSecondary(o); /* Add to its base group. */ pilot_weapSetAdd( p, id, slot, level ); /* Also add another copy to another group. */ if (id == 1) { /* Forward. */ pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */ pilot_weapSetAdd( p, 3, slot, 0 ); /* Also get added to 'Fwd/Tur'. */ } else if (id == 2) { /* Turrets. */ pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */ pilot_weapSetAdd( p, 3, slot, 1 ); /* Also get added to 'Fwd/Tur'. */ } else if (id == 4) /* Seekers */ pilot_weapSetAdd( p, 0, slot, level ); /* Also get added to 'All'. */ } /* Update active weapon set. */ pilot_weapSetUpdateOutfits( p, &p->weapon_sets[ p->active_set ] ); }
/** * @brief Actually handles the shooting, how often the player.p can shoot and such. * * @param p Pilot that is shooting. * @param w Pilot's outfit to shoot. * @return 0 if nothing was shot and 1 if something was shot. */ static int pilot_shootWeapon( Pilot* p, PilotOutfitSlot* w ) { Vector2d vp, vv; double rate_mod, energy_mod; double energy; /* Make sure weapon has outfit. */ if (w->outfit == NULL) return 0; /* check to see if weapon is ready */ if (w->timer > 0.) return 0; /* Calculate rate modifier. */ pilot_getRateMod( &rate_mod, &energy_mod, p, w->outfit ); /* Get weapon mount position. */ pilot_getMount( p, w, &vp ); vp.x += p->solid->pos.x; vp.y += p->solid->pos.y; /* Modify velocity to take into account the rotation. */ vect_cset( &vv, p->solid->vel.x + vp.x*p->solid->dir_vel, p->solid->vel.y + vp.y*p->solid->dir_vel ); /* * regular bolt weapons */ if (outfit_isBolt(w->outfit)) { /* enough energy? */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target ); } /* * Beam weapons. */ else if (outfit_isBeam(w->outfit)) { /* Check if enough energy to last a second. */ if (outfit_energy(w->outfit)*energy_mod > p->energy) return 0; /** @todo Handle warmup stage. */ w->state = PILOT_OUTFIT_ON; w->u.beamid = beam_start( w->outfit, p->solid->dir, &vp, &p->solid->vel, p, p->target, w ); } /* * missile launchers * * must be a secondary weapon */ else if (outfit_isLauncher(w->outfit)) { /* Shooter can't be the target - sanity check for the player.p */ if ((w->outfit->u.lau.ammo->u.amm.ai > 0) && (p->id==p->target)) return 0; /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* enough energy? */ if (outfit_energy(w->u.ammo.outfit)*energy_mod > p->energy) return 0; energy = outfit_energy(w->u.ammo.outfit)*energy_mod; p->energy -= energy; pilot_heatAddSlot( p, w ); weapon_add( w->outfit, w->heat_T, p->solid->dir, &vp, &p->solid->vel, p, p->target ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; pilot_updateMass( p ); } /* * Fighter bays. */ else if (outfit_isFighterBay(w->outfit)) { /* Must have ammo left. */ if ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0)) return 0; /* Create the escort. */ escort_create( p, w->u.ammo.outfit->u.fig.ship, &vp, &p->solid->vel, p->solid->dir, ESCORT_TYPE_BAY, 1 ); w->u.ammo.quantity -= 1; /* we just shot it */ p->mass_outfit -= w->u.ammo.outfit->mass; w->u.ammo.deployed += 1; /* Mark as deployed. */ pilot_updateMass( p ); } else WARN("Shooting unknown weapon type: %s", w->outfit->name); /* Reset timer. */ w->timer += rate_mod * outfit_delay( w->outfit ); return 1; }
/** * @brief Calculates and shoots the appropriate weapons in a weapon set matching an outfit. */ static int pilot_shootWeaponSetOutfit( Pilot* p, PilotWeaponSet *ws, Outfit *o, int level, double time ) { int i, ret; int is_launcher, is_bay; double rate_mod, energy_mod; PilotOutfitSlot *w; int maxp, minh; double q, maxt; /* Store number of shots. */ ret = 0; /** @TODO Make beams not fire all at once. */ if (outfit_isBeam(o)) { for (i=0; i<array_size(ws->slots); i++) if (ws->slots[i].slot->outfit == o) ret += pilot_shootWeapon( p, ws->slots[i].slot, 0 ); return ret; } /* Stores if it is a launcher. */ is_launcher = outfit_isLauncher(o); is_bay = outfit_isFighterBay(o); /* Calculate rate modifier. */ pilot_getRateMod( &rate_mod, &energy_mod, p, o ); /* Find optimal outfit, coolest that can fire. */ minh = -1; maxt = 0.; maxp = -1; q = 0.; for (i=0; i<array_size(ws->slots); i++) { /* Only matching outfits. */ if (ws->slots[i].slot->outfit != o) continue; /* Only match levels. */ if ((level != -1) && (ws->slots[i].level != level)) continue; /* Simplicity. */ w = ws->slots[i].slot; /* Launcher only counts with ammo. */ if ((is_launcher || is_bay) && ((w->u.ammo.outfit == NULL) || (w->u.ammo.quantity <= 0))) continue; /* Get coolest that can fire. */ if (w->timer <= 0.) { if (is_launcher) { if ((minh < 0) || (ws->slots[minh].slot->u.ammo.quantity < w->u.ammo.quantity)) minh = i; } else { if ((minh < 0) || (ws->slots[minh].slot->heat_T > w->heat_T)) minh = i; } } /* Save some stuff. */ if ((maxp < 0) || (w->timer > maxt)) { maxp = i; maxt = w->timer; } q += 1.; } /* No weapon can fire. */ if (minh < 0) return 0; /* Only fire if the last weapon to fire fired more than (q-1)/q ago. */ if (maxt > rate_mod * outfit_delay(o) * ((q-1.) / q)) return 0; /* Shoot the weapon. */ ret += pilot_shootWeapon( p, ws->slots[minh].slot, time ); return ret; }
/** * @brief Parses and returns Outfit from parent node. * * @param temp Outfit to load into. * @param parent Parent node to parse outfit from. * @return 0 on success. */ static int outfit_parse( Outfit* temp, const xmlNodePtr parent ) { xmlNodePtr cur, node; char *prop; /* Clear data. */ memset( temp, 0, sizeof(Outfit) ); temp->name = xml_nodeProp(parent,"name"); /* already mallocs */ if (temp->name == NULL) WARN("Outfit in "OUTFIT_DATA" has invalid or no name"); node = parent->xmlChildrenNode; do { /* load all the data */ if (xml_isNode(node,"general")) { cur = node->children; do { xmlr_int(cur,"max",temp->max); xmlr_int(cur,"tech",temp->tech); xmlr_strd(cur,"license",temp->license); xmlr_int(cur,"mass",temp->mass); xmlr_int(cur,"price",temp->price); xmlr_strd(cur,"description",temp->description); if (xml_isNode(cur,"gfx_store")) { temp->gfx_store = xml_parseTexture( cur, OUTFIT_GFX"store/%s.png", 1, 1, 0 ); } } while (xml_nextNode(cur)); } else if (xml_isNode(node,"specific")) { /* has to be processed seperately */ /* get the type */ prop = xml_nodeProp(node,"type"); if (prop == NULL) ERR("Outfit '%s' element 'specific' missing property 'type'",temp->name); temp->type = outfit_strToOutfitType(prop); free(prop); /* is secondary weapon? */ prop = xml_nodeProp(node,"secondary"); if (prop != NULL) { if ((int)atoi(prop)) outfit_setProp(temp, OUTFIT_PROP_WEAP_SECONDARY); free(prop); } if (temp->type==OUTFIT_TYPE_NULL) WARN("Outfit '%s' is of type NONE", temp->name); else if (outfit_isBolt(temp)) outfit_parseSBolt( temp, node ); else if (outfit_isBeam(temp)) outfit_parseSBeam( temp, node ); else if (outfit_isLauncher(temp)) outfit_parseSLauncher( temp, node ); else if (outfit_isAmmo(temp)) outfit_parseSAmmo( temp, node ); else if (outfit_isMod(temp)) outfit_parseSMod( temp, node ); else if (outfit_isAfterburner(temp)) outfit_parseSAfterburner( temp, node ); else if (outfit_isJammer(temp)) outfit_parseSJammer( temp, node ); else if (outfit_isFighterBay(temp)) outfit_parseSFighterBay( temp, node ); else if (outfit_isFighter(temp)) outfit_parseSFighter( temp, node ); else if (outfit_isMap(temp)) outfit_parseSMap( temp, node ); else if (outfit_isLicense(temp)) outfit_parseSLicense( temp, node ); } } while (xml_nextNode(node)); #define MELEMENT(o,s) \ if (o) WARN("Outfit '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */ MELEMENT(temp->name==NULL,"name"); MELEMENT(temp->max==0,"max"); MELEMENT(temp->tech==0,"tech"); MELEMENT(temp->gfx_store==NULL,"gfx_store"); /*MELEMENT(temp->mass==0,"mass"); Not really needed */ MELEMENT(temp->type==0,"type"); MELEMENT(temp->price==0,"price"); MELEMENT(temp->description==NULL,"description"); #undef MELEMENT return 0; }