/* * $Id: mech.combat.missile.c,v 1.2 2005/01/15 16:57:14 kstevens Exp $ * * Author: Cord Awtry <kipsta@mediaone.net> * Copyright (c) 2000-2002 Cord Awtry * All rights reserved * * Based on work that was: * Copyright (c) 1997 Markus Stenberg * Copyright (c) 1998-2000 Thomas Wouters */ #include <stdio.h> #include <stdlib.h> #include "mech.h" #include "btmacros.h" #include "mech.combat.h" #include "mech.events.h" #include "p.pcombat.h" #include "p.mech.combat.h" #include "p.mech.combat.misc.h" #include "p.mech.combat.missile.h" #include "p.mech.damage.h" #include "p.mech.ecm.h" #include "p.mech.hitloc.h" #include "p.mech.los.h" #include "p.mech.utils.h" int pilot_override; void Missile_Hit(MECH * mech, MECH * target, int hitX, int hitY, int isrear, int iscritical, int weapindx, int fireMode, int ammoMode, int num_missiles_hit, int damage, int salvo_size, int LOS, int bth, int tIsSwarmAttack) { int orig_num_missiles = num_missiles_hit; int this_time; int this_damage; int total_damage = 0; int clear_damage = 0; int hitloc; int tCheckWoodsDamageDecrement = 0; MAP *mech_map = getMap(mech->mapindex); char buf[SBUF_SIZE]; total_damage = num_missiles_hit * damage; if (target && mudconf.btech_moddamagewithwoods && IsForestHex(mech_map, MechX(target), MechY(target)) && (fireMode > -1) && (ammoMode > -1) && ((MechZ(target) - 2) <= Elevation(mech_map, MechX(target), MechY(target)))) { tCheckWoodsDamageDecrement = 1; clear_damage = total_damage; if (GetRTerrain(mech_map, MechX(target), MechY(target)) == LIGHT_FOREST) total_damage -= 2; else if (GetRTerrain(mech_map, MechX(target), MechY(target)) == HEAVY_FOREST) total_damage -= 4; if (total_damage <= 0) num_missiles_hit = 0; else num_missiles_hit = total_damage / damage; possibly_ignite_or_clear(mech, weapindx, ammoMode, clear_damage, MechX(target), MechY(target), 1); strcpy(buf, ""); if (IsMissile(weapindx)) sprintf(buf, "%s%s", "missile", orig_num_missiles > 1 ? "s" : ""); else if (ammoMode & LBX_MODE) sprintf(buf, "%s%s", "pellet", orig_num_missiles > 1 ? "s" : ""); else if ((fireMode && ULTRA_MODE) || (fireMode && RFAC_MODE) || (fireMode && RAC_MODES)) sprintf(buf, "%s%s", "slug", orig_num_missiles > 1 ? "s" : ""); else sprintf(buf, "%s", "damage"); mech_notify(mech, MECHALL, tprintf("%s %s %s absorbed by the trees!", (orig_num_missiles == 1 ? "The" : num_missiles_hit == 0 ? "All of the" : "Some of the"), buf, (orig_num_missiles == 1 ? "is" : "are"))); mech_notify(target, MECHALL, tprintf("The trees absorb %s %s", ((orig_num_missiles == 1) || (num_missiles_hit == 0) ? "the" : "some of the"), buf)); } while (num_missiles_hit) { this_time = MIN(salvo_size, num_missiles_hit); this_damage = this_time * damage; if (target) { hitloc = FindTargetHitLoc(mech, target, &isrear, &iscritical); DamageMech(target, mech, LOS, GunPilot(mech), hitloc, isrear, iscritical, pc_to_dam_conversion(target, weapindx, this_damage), 0, weapindx, bth, -1, 0, tIsSwarmAttack); } else { hex_hit(mech, hitX, hitY, weapindx, ammoMode, this_damage, 1); } num_missiles_hit -= this_time; } } int MissileHitIndex(MECH * mech, MECH * hitMech, int weapindx, int wSection, int wCritSlot) { int hit_roll; int r1, r2, r3, rtmp; int tHotloading = HotLoading(weapindx, GetPartFireMode(mech, wSection, wCritSlot)); int wRollInc = 0; int wFinalRoll = 0; int tUseArtemisBonus = GetPartAmmoMode(mech, wSection, wCritSlot) & ARTEMIS_MODE; int tUseNARCBonus = 0; if (hitMech) { if (ECMProtected(hitMech) || AngelECMProtected(hitMech)) { tUseArtemisBonus = 0; tUseNARCBonus = 0; } else { tUseNARCBonus = (GetPartAmmoMode(mech, wSection, wCritSlot) & NARC_MODE) && (checkAllSections(hitMech, NARC_ATTACHED) || checkAllSections(hitMech, INARC_HOMING_ATTACHED)); } } if (AnyECMDisturbed(mech)) { tUseArtemisBonus = 0; tUseNARCBonus = 0; } /* * Figure out the modifiers to the roll table for missiles */ if (IsMissile(weapindx) && (tUseArtemisBonus || tUseNARCBonus)) wRollInc = 2; /* Roll 3 times... if we're hotloading, we'll use the 2 lowest */ r1 = Number(1, 6); r2 = Number(1, 6); r3 = Number(1, 6); if (r1 > r2) Swap(r1, r2); if (r2 > r3) Swap(r2, r3); if (tHotloading) hit_roll = r1 + r2 - 2; else hit_roll = Roll() - 2; if ((!hitMech || (hitMech && !AngelECMProtected(hitMech))) && !AngelECMDisturbed(mech) && (MechWeapons[weapindx].special & STREAK)) { return 10; } if (wRollInc) hit_roll = hit_roll + wRollInc; wFinalRoll = MAX(MIN(hit_roll, 10), 0); return wFinalRoll; } int MissileHitTarget(MECH * mech, int weapindx, int wSection, int wCritSlot, MECH * hitMech, int hitX, int hitY, int LOS, int baseToHit, int roll, int incoming, int tIsSwarmAttack) { int isrear = 0, iscritical = 0; int AMStype, ammoLoc, ammoCrit; int AMSShotdown = 0; int hit; int i, j = -1, k, l = 0; int wNARCType = 0; int ammoMode = GetPartAmmoMode(mech, wSection, wCritSlot); int tIsInferno = (ammoMode & INFERNO_MODE); int wNARCHitLoc = 0; int tIsRear = 0; char strLocName[30]; /* Check to see if we're a NARC or iNARC launcher firing homing missiles */ if (IsMissile(weapindx)) { if ((MechWeapons[weapindx].special & NARC) && !(GetPartAmmoMode(mech, wSection, wCritSlot) & NARC_MODE)) wNARCType = 1; else if ((MechWeapons[weapindx].special & INARC) && !(GetPartAmmoMode(mech, wSection, wCritSlot) & INARC_EXPLO_MODE)) { if (GetPartAmmoMode(mech, wSection, wCritSlot) & INARC_HAYWIRE_MODE) wNARCType = 3; else if (GetPartAmmoMode(mech, wSection, wCritSlot) & INARC_ECM_MODE) wNARCType = 4; else wNARCType = 2; } /* Prefill our AMS data */ if (hitMech && (!((ammoMode & SWARM_MODE) || (ammoMode & SWARM1_MODE) || (ammoMode & MINE_MODE)))) { if (LocateAMSDefenses(hitMech, &AMStype, &ammoLoc, &ammoCrit)) AMSShotdown = AMSMissiles(mech, hitMech, wNARCType ? 1 : incoming, AMStype, ammoLoc, ammoCrit, LOS, roll >= baseToHit); } if (wNARCType) { if (roll >= baseToHit) { if (hitMech) { if (AMSShotdown > 0) { if (LOS) mech_notify(mech, MECHALL, "The pod is shot down by the target!"); mech_notify(hitMech, MECHALL, "Your Anti-Missile System activates and shoots down the incoming pod!"); return 0; } wNARCHitLoc = findNARCHitLoc(mech, hitMech, &tIsRear); /* sanity check */ if (wNARCHitLoc < 0) { mech_notify(mech, MECHALL, "Your NARC Beacon attaches to the target!"); return 0; } ArmorStringFromIndex(wNARCHitLoc, strLocName, MechType(hitMech), MechMove(hitMech)); if (wNARCType == 1) MechSections(hitMech)[wNARCHitLoc].specials |= NARC_ATTACHED; else if (wNARCType == 2) MechSections(hitMech)[wNARCHitLoc].specials |= INARC_HOMING_ATTACHED; else if (wNARCType == 3) { MechSections(hitMech)[wNARCHitLoc].specials |= INARC_HAYWIRE_ATTACHED; mech_notify(hitMech, MECHALL, "Your targetting systems goes a bit haywire!"); } else if (wNARCType == 4) { MechSections(hitMech)[wNARCHitLoc].specials |= INARC_ECM_ATTACHED; checkECM(hitMech); } mech_notify(hitMech, MECHALL, tprintf ("A NARC Beacon has been attached to your %s%s!", strLocName, tIsRear == 1 ? " (Rear)" : "")); mech_notify(mech, MECHALL, tprintf ("Your NARC Beacon attaches to the target's %s%s!", strLocName, tIsRear == 1 ? " (Rear)" : "")); } } else mech_notify(mech, MECHALL, "Your NARC Beacon flies off into the distance."); return 0; } } if (roll < baseToHit) return incoming; for (i = 0; MissileHitTable[i].key >= 0; i++) if ((k = MissileHitTable[i].num_missiles[10]) <= incoming && ((MechWeapons[MissileHitTable[i].key].special & STREAK) == (MechWeapons[weapindx].special & STREAK))) if (k >= l && (j < 0 || MissileHitTable[i].key != weapindx || k > l)) { j = i; l = k; } if (j < 0) return 0; hit = MIN(incoming, MissileHitTable[j].num_missiles[MissileHitIndex(mech, hitMech, weapindx, wSection, wCritSlot)]); if (LOS) { mech_notify(mech, MECHALL, tprintf("%%cg%s with %d missile%s!%%c", LOS == 1 ? "You hit" : "The swarm hits", hit, hit > 1 ? "s" : "")); } if (AMSShotdown > 0) { if (AMSShotdown >= hit) { if (LOS) mech_notify(mech, MECHALL, "All of your missiles are shot down by the target!"); mech_notify(hitMech, MECHALL, "Your Anti-Missile System activates and shoots all the incoming missiles!"); } else { mech_notify(mech, MECHALL, tprintf("The target shoots down %d of your missiles!", AMSShotdown)); mech_notify(hitMech, MECHALL, tprintf ("Your Anti-Missile System activates and shoots down %d incoming missiles!", AMSShotdown)); } } hit = MAX(0, hit - AMSShotdown); if (hit <= 0) return 0; if (tIsInferno) { if (hitMech) Inferno_Hit(mech, hitMech, hit, LOS); else hex_hit(mech, hitX, hitY, weapindx, GetPartAmmoMode(mech, wSection, wCritSlot), 0, 0); } else Missile_Hit(mech, hitMech, hitX, hitY, isrear, iscritical, weapindx, GetPartFireMode(mech, wSection, wCritSlot), GetPartAmmoMode(mech, wSection, wCritSlot), hit, MechWeapons[weapindx].damage, Clustersize(weapindx), LOS, baseToHit, tIsSwarmAttack); return incoming - hit; } void SwarmHitTarget(MECH * mech, int weapindx, int wSection, int wCritSlot, MECH * hitMech, int LOS, int baseToHit, int roll, int incoming, int fof, int tIsSwarmAttack) { #define MAX_STAR 10 /* Max # of targets we'll try to hit: 10 */ MECH *star[MAX_STAR]; int present_target = 0; int missiles; int loop; MAP *map = FindObjectsData(mech->mapindex); float r = 0.0, ran = 0, flrange = 0.0; MECH *source = mech, *tempMech; int i, j; for (loop = 0; MissileHitTable[loop].key != -1; loop++) if (MissileHitTable[loop].key == weapindx) break; if (!(MissileHitTable[loop].key == weapindx)) return; missiles = MissileHitTable[loop].num_missiles[10]; while (missiles > 0) { flrange = flrange + FaMechRange(source, hitMech); ran = FaMechRange(mech, hitMech); if (flrange > EGunRange(weapindx)) { mech_notify(hitMech, MECHALL, "Luckily, the missiles fall short of you!"); return; } if (!(missiles = MissileHitTarget(mech, weapindx, wSection, wCritSlot, hitMech, -1, -1, InLineOfSight_NB(mech, hitMech, MechX(mech), MechY(mech), ran) ? present_target == 0 ? 1 : 2 : 0, baseToHit, present_target == 0 ? roll : Roll(), missiles, tIsSwarmAttack))) return; /* Try to acquire a new target NOT in the star */ if (present_target == MAX_STAR) return; star[present_target++] = hitMech; source = hitMech; hitMech = NULL; for (i = 0; i < map->first_free; i++) if ((tempMech = FindObjectsData(map->mechsOnMap[i]))) if (!fof || (MechTeam(tempMech) != MechTeam(mech))) { for (j = 0; j < present_target; j++) if (tempMech == star[j]) break; if (j != present_target) continue; if (!hitMech || (r = FaMechRange(source, tempMech)) < 1.9) if (InLineOfSight_NB(source, tempMech, MechX(source), MechY(source), r)) { hitMech = tempMech; ran = r; } } if (!hitMech) return; if (mech != hitMech) mech_notify(hitMech, MECHALL, "The missile-swarm turns towards you!"); if (InLineOfSight_NB(mech, source, MechX(mech), MechY(mech), FaMechRange(mech, source))) mech_notify(mech, MECHALL, tprintf ("Your missile-swarm of %d missile%s targets %s!", missiles, missiles > 1 ? "s" : "", mech == hitMech ? "YOU!!" : GetMechToMechID(mech, hitMech))); MechLOSBroadcasti(mech, hitMech, "'s missile-swarm targets %s!"); } } /* * Fix AMS: * * - Applied after number missiles is determined * - Ammo used == missiles shot down * - d6 for IS, 2d6 for clan * - Not used against Arrow IV, Thunder, Flare, Swarm or Swarm-1 */ /**************************************** * START: AMS related functions ****************************************/ int AMSMissiles(MECH * mech, MECH * hitMech, int incoming, int type, int ammoLoc, int ammoCrit, int LOS, int missilesDidHit) { int num_missiles_shotdown; if (MechWeapons[type].special & CLAT) num_missiles_shotdown = Roll(); else num_missiles_shotdown = Number(1, 6); if (num_missiles_shotdown > incoming) num_missiles_shotdown = incoming; if (num_missiles_shotdown >= GetPartData(hitMech, ammoLoc, ammoCrit)) GetPartData(hitMech, ammoLoc, ammoCrit) = 0; else GetPartData(hitMech, ammoLoc, ammoCrit) -= num_missiles_shotdown; if (!missilesDidHit) { mech_notify(hitMech, MECHALL, "Your Anti-Missile System activates and shoots at the incoming missiles!"); return 0; } return num_missiles_shotdown; } int LocateAMSDefenses(MECH * target, int *AMStype, int *ammoLoc, int *ammoCrit) { int AMSsect, AMScrit; int i, j = 0, w, t = 0; if (!(MechSpecials(target) & (IS_ANTI_MISSILE_TECH | CL_ANTI_MISSILE_TECH)) || !Started(target) || !(MechStatus(target) & AMS_ENABLED)) return 0; for (i = 0; i < NUM_SECTIONS; i++) { for (j = 0; j < NUM_CRITICALS; j++) if (IsWeapon((t = GetPartType(target, i, j)))) if (IsAMS(Weapon2I(t))) if (!(PartIsNonfunctional(target, i, j) || WpnIsRecycling(target, i, j))) break; if (j < NUM_CRITICALS) break; } if (i == NUM_SECTIONS) return 0; w = Weapon2I(t); AMSsect = i; AMScrit = j; *AMStype = w; if (!(FindAmmoForWeapon(target, w, AMSsect, ammoLoc, ammoCrit))) return 0; if (!(GetPartData(target, *ammoLoc, *ammoCrit))) return 0; SetRecyclePart(target, AMSsect, AMScrit, MechWeapons[w].vrt); MechWeapHeat(target) += (float) MechWeapons[w].heat; return 1; } /**************************************** * END: AMS related functions ****************************************/