COMBAT.CPP (21703B)
1 // 2 // Copyright 2020 Electronic Arts Inc. 3 // 4 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is free 5 // software: you can redistribute it and/or modify it under the terms of 6 // the GNU General Public License as published by the Free Software Foundation, 7 // either version 3 of the License, or (at your option) any later version. 8 9 // TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed 10 // in the hope that it will be useful, but with permitted additional restrictions 11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 12 // distributed with this program. You should have received a copy of the 13 // GNU General Public License along with permitted additional restrictions 14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection 15 16 /* $Header: /CounterStrike/COMBAT.CPP 1 3/03/97 10:24a Joe_bostic $ */ 17 /*********************************************************************************************** 18 *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** 19 *********************************************************************************************** 20 * * 21 * Project Name : Command & Conquer * 22 * * 23 * File Name : COMBAT.CPP * 24 * * 25 * Programmer : Joe L. Bostic * 26 * * 27 * Start Date : September 19, 1994 * 28 * * 29 * Last Update : July 26, 1996 [JLB] * 30 * * 31 *---------------------------------------------------------------------------------------------* 32 * Functions: * 33 * Combat_Anim -- Determines explosion animation to play. * 34 * Explosion_Damage -- Inflict an explosion damage affect. * 35 * Modify_Damage -- Adjusts damage to reflect the nature of the target. * 36 * Wide_Area_Damage -- Apply wide area damage to the map. * 37 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 38 39 #include "function.h" 40 41 42 /*********************************************************************************************** 43 * Modify_Damage -- Adjusts damage to reflect the nature of the target. * 44 * * 45 * This routine is the core of combat tactics. It implements the * 46 * affect various armor types have against various weapon types. By * 47 * careful exploitation of this table, tactical advantage can be * 48 * obtained. * 49 * * 50 * INPUT: damage -- The damage points to process. * 51 * * 52 * warhead -- The source of the damage points. * 53 * * 54 * armor -- The type of armor defending against the damage. * 55 * * 56 * distance -- The distance (in leptons) from the source of the damage. * 57 * * 58 * OUTPUT: Returns with the adjusted damage points to inflict upon the * 59 * target. * 60 * * 61 * WARNINGS: none * 62 * * 63 * HISTORY: * 64 * 04/16/1994 JLB : Created. * 65 * 04/17/1994 JLB : Always does a minimum of damage. * 66 * 01/01/1995 JLB : Takes into account distance from damage source. * 67 * 04/11/1996 JLB : Changed damage fall-off formula for less damage fall-off. * 68 *=============================================================================================*/ 69 int Modify_Damage(int damage, WarheadType warhead, ArmorType armor, int distance) 70 { 71 if (!damage) return(damage); 72 73 /* 74 ** If there is no raw damage value to start with, then 75 ** there can be no modified damage either. 76 */ 77 if (Special.IsInert || !damage || warhead == WARHEAD_NONE) return(0); 78 79 /* 80 ** Negative damage (i.e., heal) is always applied full strength, but only if the heal 81 ** effect is close enough. 82 */ 83 if (damage < 0) { 84 #ifdef FIXIT_CSII // checked - ajw 9/28/98 85 if (distance < 0x008) { 86 if(warhead != WARHEAD_MECHANICAL && armor == ARMOR_NONE) return(damage); 87 if(warhead == WARHEAD_MECHANICAL && armor != ARMOR_NONE) return(damage); 88 } 89 #else 90 if (distance < 0x008 && armor == ARMOR_NONE) return(damage); 91 #endif 92 return(0); 93 } 94 95 WarheadTypeClass const * whead = WarheadTypeClass::As_Pointer(warhead); 96 // WarheadTypeClass const * whead = &Warheads[warhead]; 97 98 damage = damage * whead->Modifier[armor]; 99 100 /* 101 ** Reduce damage according to the distance from the impact point. 102 */ 103 if (damage) { 104 if (!whead->SpreadFactor) { 105 distance /= PIXEL_LEPTON_W/4; 106 } else { 107 distance /= whead->SpreadFactor * (PIXEL_LEPTON_W/2); 108 } 109 distance = Bound(distance, 0, 16); 110 if (distance) { 111 damage = damage / distance; 112 } 113 114 /* 115 ** Allow damage to drop to zero only if the distance would have 116 ** reduced damage to less than 1/4 full damage. Otherwise, ensure 117 ** that at least one damage point is done. 118 */ 119 if (distance < 4) { 120 damage = max(damage, Rule.MinDamage); 121 } 122 } 123 124 damage = min(damage, Rule.MaxDamage); 125 return(damage); 126 } 127 128 129 /*********************************************************************************************** 130 * Explosion_Damage -- Inflict an explosion damage affect. * 131 * * 132 * Processes the collateral damage affects typically caused by an * 133 * explosion. * 134 * * 135 * INPUT: coord -- The coordinate of ground zero. * 136 * * 137 * strength -- Raw damage points at ground zero. * 138 * * 139 * source -- Source of the explosion (who is responsible). * 140 * * 141 * warhead -- The kind of explosion to process. * 142 * * 143 * OUTPUT: none * 144 * * 145 * WARNINGS: This routine can consume some time and will affect the AI * 146 * of nearby enemy units (possibly). * 147 * * 148 * HISTORY: * 149 * 08/16/1991 JLB : Created. * 150 * 11/30/1991 JLB : Uses coordinate system. * 151 * 12/27/1991 JLB : Radius of explosion damage effect. * 152 * 04/13/1994 JLB : Streamlined. * 153 * 04/16/1994 JLB : Warhead damage type modifier. * 154 * 04/17/1994 JLB : Cleaned up. * 155 * 06/20/1994 JLB : Uses object pointers to distribute damage. * 156 * 06/20/1994 JLB : Source is a pointer. * 157 * 06/18/1996 JLB : Strength could be negative for healing effects. * 158 *=============================================================================================*/ 159 void Explosion_Damage(COORDINATE coord, int strength, TechnoClass * source, WarheadType warhead) 160 { 161 CELL cell; // Cell number under explosion. 162 ObjectClass * object; // Working object pointer. 163 ObjectClass * objects[32]; // Maximum number of objects that can be damaged. 164 int distance; // Distance to unit. 165 int range; // Damage effect radius. 166 int count; // Number of vehicle IDs in list. 167 168 if (!strength || Special.IsInert || warhead == WARHEAD_NONE) return; 169 170 WarheadTypeClass const * whead = WarheadTypeClass::As_Pointer(warhead); 171 // WarheadTypeClass const * whead = &Warheads[warhead]; 172 // range = ICON_LEPTON_W*2; 173 range = ICON_LEPTON_W + (ICON_LEPTON_W >> 1); 174 cell = Coord_Cell(coord); 175 if ((unsigned)cell >= MAP_CELL_TOTAL) return; 176 177 CellClass * cellptr = &Map[cell]; 178 ObjectClass * impacto = cellptr->Cell_Occupier(); 179 180 /* 181 ** Fill the list of unit IDs that will have damage 182 ** assessed upon them. The units can be lifted from 183 ** the cell data directly. 184 */ 185 count = 0; 186 for (FacingType i = FACING_NONE; i < FACING_COUNT; i++) { 187 /* 188 ** Fetch a pointer to the cell to examine. This is either 189 ** an adjacent cell or the center cell. Damage never spills 190 ** further than one cell away. 191 */ 192 if (i != FACING_NONE) { 193 cellptr = Map[cell].Adjacent_Cell(i); 194 if (!cellptr) continue; 195 } 196 197 /* 198 ** Add all objects in this cell to the list of objects to possibly apply 199 ** damage to. The list stops building when the object pointer list becomes 200 ** full. Do not include overlapping objects; selection state can affect 201 ** the overlappers, and this causes multiplayer games to go out of sync. 202 */ 203 object = cellptr->Cell_Occupier(); 204 while (object) { 205 if (!object->IsToDamage && object != source) { 206 object->IsToDamage = true; 207 objects[count++] = object; 208 if (count >= ARRAY_SIZE(objects)) break; 209 } 210 object = object->Next; 211 } 212 if (count >= ARRAY_SIZE(objects)) break; 213 } 214 215 /* 216 ** Sweep through the units to be damaged and damage them. When damaging 217 ** buildings, consider a hit on any cell the building occupies as if it 218 ** were a direct hit on the building's center. 219 */ 220 for (int index = 0; index < count; index++) { 221 object = objects[index]; 222 223 object->IsToDamage = false; 224 if (object->IsActive) { 225 if (object->What_Am_I() == RTTI_BUILDING && impacto == object) { 226 distance = 0; 227 } else { 228 distance = Distance(coord, object->Center_Coord()); 229 } 230 if (object->IsDown && !object->IsInLimbo && distance < range) { 231 int damage = strength; 232 object->Take_Damage(damage, distance, warhead, source); 233 } 234 } 235 } 236 237 /* 238 ** If there is a wall present at this location, it may be destroyed. Check to 239 ** make sure that the warhead is of the kind that can destroy walls. 240 */ 241 cellptr = &Map[cell]; 242 if (cellptr->Overlay != OVERLAY_NONE) { 243 OverlayTypeClass const * optr = &OverlayTypeClass::As_Reference(cellptr->Overlay); 244 245 if (optr->IsTiberium && whead->IsTiberiumDestroyer) { 246 cellptr->Reduce_Tiberium(strength / 10); 247 } 248 if (optr->IsWall) { 249 if (whead->IsWallDestroyer || (whead->IsWoodDestroyer && optr->IsWooden)) { 250 Map[cell].Reduce_Wall(strength); 251 } 252 } 253 } 254 255 /* 256 ** If there is a bridge at this location, then it may be destroyed by the 257 ** combat damage. 258 */ 259 if (cellptr->TType == TEMPLATE_BRIDGE1 || cellptr->TType == TEMPLATE_BRIDGE2 || 260 cellptr->TType == TEMPLATE_BRIDGE1H || cellptr->TType == TEMPLATE_BRIDGE2H || 261 cellptr->TType == TEMPLATE_BRIDGE_1A || cellptr->TType == TEMPLATE_BRIDGE_1B || 262 cellptr->TType == TEMPLATE_BRIDGE_2A || cellptr->TType == TEMPLATE_BRIDGE_2B || 263 cellptr->TType == TEMPLATE_BRIDGE_3A || cellptr->TType == TEMPLATE_BRIDGE_3B ) { 264 265 if (((warhead == WARHEAD_AP || warhead == WARHEAD_HE) && Random_Pick(1, Rule.BridgeStrength) < strength)) { 266 Map.Destroy_Bridge_At(cell); 267 } 268 } 269 } 270 271 272 /*********************************************************************************************** 273 * Combat_Anim -- Determines explosion animation to play. * 274 * * 275 * This routine is called when a projectile impacts. This routine will determine what * 276 * animation should be played. * 277 * * 278 * INPUT: damage -- The amount of damage this warhead possess (warhead size). * 279 * * 280 * warhead -- The type of warhead. * 281 * * 282 * land -- The land type that this explosion is over. Sometimes, this makes * 283 * a difference (especially over water). * 284 * * 285 * OUTPUT: Returns with the animation to play. If no animation is to be played, then * 286 * ANIM_NONE is returned. * 287 * * 288 * WARNINGS: none * 289 * * 290 * HISTORY: * 291 * 05/19/1996 JLB : Created. * 292 *=============================================================================================*/ 293 AnimType Combat_Anim(int damage, WarheadType warhead, LandType land) 294 { 295 /* 296 ** For cases of no damage or invalid warhead, don't have any 297 ** animation effect at all. 298 */ 299 if (damage == 0 || warhead == WARHEAD_NONE) { 300 return(ANIM_NONE); 301 } 302 303 static AnimType _aplist[] = { 304 ANIM_VEH_HIT3, // Small fragment throwing explosion -- burn/exp mix. 305 ANIM_VEH_HIT2, // Small fragment throwing explosion -- pop & sparkles. 306 ANIM_FRAG1, // Medium fragment throwing explosion -- short decay. 307 ANIM_FBALL1, // Large fireball explosion (bulges rightward). 308 }; 309 310 static AnimType _helist[] = { 311 ANIM_VEH_HIT1, // Small fireball explosion (bulges rightward). 312 ANIM_VEH_HIT2, // Small fragment throwing explosion -- pop & sparkles. 313 ANIM_ART_EXP1, // Large fragment throwing explosion -- many sparkles. 314 ANIM_FBALL1, // Large fireball explosion (bulges rightward). 315 }; 316 317 static AnimType _firelist[] = { 318 ANIM_NAPALM1, // Small napalm burn. 319 ANIM_NAPALM2, // Medium napalm burn. 320 ANIM_NAPALM3, // Large napalm burn. 321 }; 322 323 static AnimType _waterlist[] = { 324 ANIM_WATER_EXP3, 325 ANIM_WATER_EXP2, 326 ANIM_WATER_EXP1, 327 }; 328 329 WarheadTypeClass const * wptr = WarheadTypeClass::As_Pointer(warhead); 330 // WarheadTypeClass const * wptr = &Warheads[warhead]; 331 switch (wptr->ExplosionSet) { 332 case 6: 333 return(ANIM_ATOM_BLAST); 334 335 case 2: 336 if (damage > 15) { 337 return(ANIM_PIFFPIFF); 338 } 339 return(ANIM_PIFF); 340 341 case 4: 342 if (land == LAND_NONE) return(ANIM_FLAK); 343 // Fixed math error 344 if (land == LAND_WATER) return(_waterlist[(ARRAY_SIZE(_waterlist)-1) * fixed(min(damage, 90), 90)]); 345 return(_aplist[(ARRAY_SIZE(_aplist)-1) * fixed(min(damage, 90), 90)]); 346 347 case 5: 348 if (land == LAND_NONE) return(ANIM_FLAK); 349 if (land == LAND_WATER) return(_waterlist[(ARRAY_SIZE(_waterlist)-1) * fixed(min(damage, 130), 130)]); 350 return(_helist[(ARRAY_SIZE(_helist)-1) * fixed(min(damage, 130), 130)]); 351 352 case 3: 353 if (land == LAND_NONE) return(ANIM_FLAK); 354 if (land == LAND_WATER) return(_waterlist[(ARRAY_SIZE(_waterlist)-1) * fixed(min(damage, 150), 150)]); 355 return(_firelist[(ARRAY_SIZE(_firelist)-1) * fixed(min(damage, 150), 150)]); 356 357 case 1: 358 return(ANIM_PIFF); 359 360 default: 361 break; 362 } 363 return(ANIM_NONE); 364 } 365 366 367 /*********************************************************************************************** 368 * Wide_Area_Damage -- Apply wide area damage to the map. * 369 * * 370 * This routine will apply damage to a very wide area on the map. The damage will be * 371 * spread out from the coordinate specified by the radius specified. The amount of damage * 372 * will attenuate according to the distance from center. * 373 * * 374 * INPUT: coord -- The coordinate that the explosion damage will center about. * 375 * * 376 * radius -- The radius of the explosion. * 377 * * 378 * damage -- The amount of damage to apply at the center location. * 379 * * 380 * source -- Pointer to the purpetrator of the damage (if any). * 381 * * 382 * warhead -- The type of warhead that is causing the damage. * 383 * * 384 * OUTPUT: none * 385 * * 386 * WARNINGS: none * 387 * * 388 * HISTORY: * 389 * 07/26/1996 JLB : Created. * 390 *=============================================================================================*/ 391 void Wide_Area_Damage(COORDINATE coord, LEPTON radius, int rawdamage, TechnoClass * source, WarheadType warhead) 392 { 393 int cell_radius = (radius + CELL_LEPTON_W-1) / CELL_LEPTON_W; 394 CELL cell = Coord_Cell(coord); 395 396 for (int x = -cell_radius; x <= cell_radius; x++) { 397 for (int y = -cell_radius; y <= cell_radius; y++) { 398 int xpos = Cell_X(cell) + x; 399 int ypos = Cell_Y(cell) + y; 400 401 /* 402 ** If the potential damage cell is outside of the map bounds, 403 ** then don't process it. This unusual check method ensures that 404 ** damage won't wrap from one side of the map to the other. 405 */ 406 if ((unsigned)xpos > MAP_CELL_W) { 407 continue; 408 } 409 if ((unsigned)ypos > MAP_CELL_H) { 410 continue; 411 } 412 CELL tcell = XY_Cell(xpos, ypos); 413 if (!Map.In_Radar(tcell)) continue; 414 415 int dist_from_center = Distance(XY_Coord(x+cell_radius, y+cell_radius), XY_Coord(cell_radius, cell_radius)); 416 int damage = rawdamage * Inverse(fixed(cell_radius, dist_from_center)); 417 Explosion_Damage(Cell_Coord(tcell), damage, source, warhead); 418 if (warhead == WARHEAD_FIRE && damage > 100) { 419 new SmudgeClass(Random_Pick(SMUDGE_SCORCH1, SMUDGE_SCORCH6), Cell_Coord(tcell)); 420 } 421 } 422 } 423 }