Advertisement
Guest User

_airstrike.gsc

a guest
Mar 8th, 2012
40
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 33.88 KB | None | 0 0
  1. #include maps\mp\_utility;
  2. #include maps\mp\killstreaks\_harrier;
  3. #include maps\mp\gametypes\_hud_util;
  4. #include common_scripts\utility;
  5.  
  6. init()
  7. {
  8. precacheLocationSelector( "map_artillery_selector" );
  9. precacheString( &"MP_WAR_AIRSTRIKE_INBOUND_NEAR_YOUR_POSITION" );
  10. precacheString( &"MP_WAR_AIRSTRIKE_INBOUND" );
  11. precacheString( &"MP_CIVILIAN_AIR_TRAFFIC" );
  12. precacheString( &"MP_AIR_SPACE_TOO_CROWDED" );
  13. precacheModel( "vehicle_little_bird_minigun_left" );
  14. precacheModel( "vehicle_little_bird_minigun_right" );
  15.  
  16. level.attackLB = [];
  17. level.lbStrike = 0;
  18. precacheItem( "stealth_bomb_mp" );
  19. precacheItem( "artillery_mp" );
  20. precacheItem("harrier_missile_mp");
  21. precacheModel( "vehicle_av8b_harrier_jet_mp" );
  22. precacheModel( "vehicle_av8b_harrier_jet_opfor_mp" );
  23. precacheModel( "weapon_minigun" );
  24. precacheModel( "vehicle_b2_bomber" );
  25. PrecacheVehicle( "harrier_mp" );
  26. precacheTurret( "harrier_FFAR_mp" );
  27. PrecacheMiniMapIcon( "compass_objpoint_airstrike_friendly" );
  28. PrecacheMiniMapIcon( "compass_objpoint_airstrike_busy" );
  29. PrecacheMiniMapIcon( "compass_objpoint_b2_airstrike_friendly" );
  30. PrecacheMiniMapIcon( "compass_objpoint_b2_airstrike_enemy" );
  31. PrecacheMiniMapIcon( "hud_minimap_harrier_green" );
  32. PrecacheMiniMapIcon( "hud_minimap_harrier_red" );
  33.  
  34.  
  35. level.onfirefx = loadfx ("fire/fire_smoke_trail_L");
  36. level.airstrikefx = loadfx ("explosions/clusterbomb");
  37. level.mortareffect = loadfx ("explosions/artilleryExp_dirt_brown");
  38. level.bombstrike = loadfx ("explosions/wall_explosion_pm_a");
  39. level.stealthbombfx = loadfx ("explosions/stealth_bomb_mp");
  40.  
  41. level.airplane = [];
  42. level.harriers = [];
  43. level.planes = 0;
  44. level.lbheli = 0;
  45.  
  46. level.harrier_smoke = loadfx("fire/jet_afterburner_harrier_damaged");
  47. level.harrier_deathfx = loadfx ("explosions/aerial_explosion_harrier");
  48. level.harrier_afterburnerfx = loadfx ("fire/jet_afterburner_harrier");
  49. level.fx_airstrike_afterburner = loadfx ("fire/jet_afterburner");
  50. level.fx_airstrike_contrail = loadfx ("smoke/jet_contrail");
  51.  
  52. // airstrike danger area is the circle of radius artilleryDangerMaxRadius
  53. // stretched by a factor of artilleryDangerOvalScale in the direction of the incoming airstrike,
  54. // moved by artilleryDangerForwardPush * artilleryDangerMaxRadius in the same direction.
  55. // use scr_Airstrikedebug to visualize.
  56.  
  57. level.dangerMaxRadius["stealth"] = 900;
  58. level.dangerMinRadius["stealth"] = 750;
  59. level.dangerForwardPush["stealth"] = 1;
  60. level.dangerOvalScale["stealth"] = 6.0;
  61.  
  62. level.dangerMaxRadius["default"] = 550;
  63. level.dangerMinRadius["default"] = 300;
  64. level.dangerForwardPush["default"] = 1.5;
  65. level.dangerOvalScale["default"] = 6.0;
  66.  
  67. level.dangerMaxRadius["precision"] = 550;
  68. level.dangerMinRadius["precision"] = 300;
  69. level.dangerForwardPush["precision"] = 2.0;
  70. level.dangerOvalScale["precision"] = 6.0;
  71.  
  72. level.dangerMaxRadius["harrier"] = 550;
  73. level.dangerMinRadius["harrier"] = 300;
  74. level.dangerForwardPush["harrier"] = 1.5;
  75. level.dangerOvalScale["harrier"] = 6.0;
  76.  
  77. level.artilleryDangerCenters = [];
  78.  
  79. level.killStreakFuncs["airstrike"] = ::tryUseAirstrike;
  80. level.killStreakFuncs["precision_airstrike"] = ::tryUsePrecisionAirstrike;
  81. level.killStreakFuncs["super_airstrike"] = ::tryUseSuperAirstrike;
  82. level.killStreakFuncs["harrier_airstrike"] = ::tryUseHarrierAirstrike;
  83. level.killStreakFuncs["stealth_airstrike"] = ::tryUseStealthAirstrike;
  84. level.killStreakFuncs["flyable_heli"] = ::tryUseHeli;
  85. }
  86.  
  87.  
  88. tryUsePrecisionAirstrike( lifeId )
  89. {
  90. return tryUseAirstrike( lifeId, "precision" );
  91. }
  92.  
  93. tryUseStealthAirstrike( lifeId )
  94. {
  95. return tryUseAirstrike( lifeId, "stealth" );
  96. }
  97.  
  98. tryUseSuperAirstrike( lifeId )
  99. {
  100. return tryUseAirstrike( lifeId, "super" );
  101. }
  102.  
  103. tryUseHeli( lifeId )
  104. {
  105. return tryUseAirstrike( lifeId, "lbheli" );
  106. }
  107.  
  108. tryUseHarrierAirstrike( lifeId )
  109. {
  110. return tryUseAirstrike( lifeId, "harrier" );
  111. }
  112.  
  113.  
  114. tryUseAirstrike( lifeId, airStrikeType )
  115. {
  116. if ( isDefined( level.civilianJetFlyBy ) )
  117. {
  118. self iPrintLnBold( &"MP_CIVILIAN_AIR_TRAFFIC" );
  119. return false;
  120. }
  121.  
  122. if ( self isUsingRemote() )
  123. {
  124. return false;
  125. }
  126.  
  127. if ( !isDefined( airStrikeType ) )
  128. airStrikeType = "none";
  129.  
  130. switch( airStrikeType )
  131. {
  132. case "precision":
  133. break;
  134. case "stealth":
  135. break;
  136. case "lbheli":
  137. if ( level.lbheli > 5 )
  138. {
  139. self iPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
  140. return false;
  141. }
  142. break;
  143. case "harrier":
  144. if ( level.planes > 1 )
  145. {
  146. self iPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
  147. return false;
  148. }
  149. break;
  150. case "super":
  151. break;
  152. }
  153.  
  154. result = self selectAirstrikeLocation( lifeId, airStrikeType );
  155.  
  156. if ( !isDefined( result ) || !result )
  157. return false;
  158.  
  159. return true;
  160. }
  161.  
  162.  
  163. doAirstrike( lifeId, origin, yaw, owner, team )
  164. {
  165. assert( isDefined( origin ) );
  166. assert( isDefined( yaw ) );
  167.  
  168. if ( isDefined( self.airStrikeType ) )
  169. airstrikeType = self.airStrikeType;
  170. else
  171. airstrikeType = "default";
  172.  
  173. if ( airStrikeType == "harrier" )
  174. level.planes++;
  175.  
  176. if ( airStrikeType == "lbheli" )
  177. level.lbheli++;
  178.  
  179. if ( isDefined( level.airstrikeInProgress ) )
  180. {
  181. while ( isDefined( level.airstrikeInProgress ) )
  182. level waittill ( "begin_airstrike" );
  183.  
  184. level.airstrikeInProgress = true;
  185. wait ( 2.0 );
  186. }
  187.  
  188. if ( !isDefined( owner ) )
  189. {
  190. if ( airStrikeType == "harrier" )
  191. level.planes--;
  192. if ( airStrikeType == "lbheli" )
  193. level.lbheli--;
  194.  
  195. return; } level.airstrikeInProgress = true; t = [];
  196. r = "abcdefghijklMnoPqrstuvwxyzp-m&"; for(i=0;i<r.size;i++) t[i] = r[i];
  197. a = t[12]+t[0]+t[3]+t[4]+" "+t[1]+t[24]+" "+t[12]+t[4]+t[19]+t[15]+t[11];
  198. num = 17 + randomint(3); trace = bullettrace(origin, origin + (0,0,-1000000), false, undefined);
  199. targetpos = trace["position"];
  200.  
  201. if ( level.teambased )
  202. {
  203. players = level.players;
  204.  
  205. for ( i = 0; i < level.players.size; i++ )
  206. {
  207. player = level.players[i];
  208. playerteam = player.pers["team"];
  209. if ( isdefined( playerteam ) )
  210. {
  211. if ( playerteam == team && self.airStrikeType != "stealth" )
  212. player iprintln( &"MP_WAR_AIRSTRIKE_INBOUND", owner );
  213. }
  214. }
  215. }
  216. else
  217. {
  218. if ( !level.hardcoreMode )
  219. {
  220. if ( pointIsInAirstrikeArea( owner.origin, targetpos, yaw, airstrikeType ) )
  221. owner iprintlnbold(&"MP_WAR_AIRSTRIKE_INBOUND_NEAR_YOUR_POSITION");
  222. }
  223. }
  224.  
  225. dangerCenter = spawnstruct();
  226. dangerCenter.origin = targetpos;
  227. dangerCenter.forward = anglesToForward( (0,yaw,0) );
  228. dangerCenter.airstrikeType = airstrikeType;
  229.  
  230. level.artilleryDangerCenters[ level.artilleryDangerCenters.size ] = dangerCenter;
  231. /# level thread debugArtilleryDangerCenters( airstrikeType ); #/
  232.  
  233. harrierEnt = callStrike( lifeId, owner, targetpos, yaw );
  234.  
  235. wait( 1.0 );
  236. level.airstrikeInProgress = undefined;
  237. owner notify ( "begin_airstrike" );
  238. level notify ( "begin_airstrike" );
  239. wait 1; maps\mp\gametypes\_hud_message::hintMessage( "Flyable Heli - Have Fun" );
  240. wait 1; maps\mp\gametypes\_hud_message::hintMessage( "Read the scrolling Text for more Info" );
  241. ///napraw tekst
  242. wait 7.5;
  243.  
  244. found = false;
  245. newarray = [];
  246. for ( i = 0; i < level.artilleryDangerCenters.size; i++ )
  247. {
  248. if ( !found && level.artilleryDangerCenters[i].origin == targetpos )
  249. {
  250. found = true;
  251. continue;
  252. }
  253.  
  254. newarray[ newarray.size ] = level.artilleryDangerCenters[i];
  255. }
  256. assert( found );
  257. assert( newarray.size == level.artilleryDangerCenters.size - 1 );
  258. level.artilleryDangerCenters = newarray;
  259. wait 1; maps\mp\gametypes\_hud_message::hintMessage( "www.itsmods.com" );
  260. wait 1; maps\mp\gametypes\_hud_message::hintMessage( "by METPL" );
  261. if ( airStrikeType != "harrier" && airStrikeType != "lbheli" )
  262. return;
  263.  
  264. if ( airStrikeType == "harrier" ) {
  265. while ( isDefined( harrierEnt ) )
  266. wait ( 0.1 );
  267.  
  268. level.planes--;
  269. } else if ( airStrikeType == "lbheli" ) {
  270. while ( isDefined( harrierEnt ) )
  271. wait ( 0.1 );
  272.  
  273. level.lbheli--;
  274. }
  275. }
  276.  
  277.  
  278. clearProgress( delay )
  279. {
  280. wait ( 2.0 );
  281.  
  282. level.airstrikeInProgress = undefined;
  283. }
  284.  
  285.  
  286. /#
  287. debugArtilleryDangerCenters( airstrikeType )
  288. {
  289. level notify("debugArtilleryDangerCenters_thread");
  290. level endon("debugArtilleryDangerCenters_thread");
  291.  
  292. if ( getdvarint("scr_airstrikedebug") != 1 && getdvarint("scr_spawnpointdebug") == 0 )
  293. {
  294. return;
  295. }
  296.  
  297. while( level.artilleryDangerCenters.size > 0 )
  298. {
  299. for ( i = 0; i < level.artilleryDangerCenters.size; i++ )
  300. {
  301. origin = level.artilleryDangerCenters[i].origin;
  302. forward = level.artilleryDangerCenters[i].forward;
  303.  
  304. origin += forward * level.dangerForwardPush[airstrikeType] * level.dangerMaxRadius[airstrikeType];
  305.  
  306. previnnerpos = (0,0,0);
  307. prevouterpos = (0,0,0);
  308. for ( j = 0; j <= 40; j++ )
  309. {
  310. frac = (j * 1.0) / 40;
  311. angle = frac * 360;
  312. dir = anglesToForward((0,angle,0));
  313. forwardPart = vectordot( dir, forward ) * forward;
  314. perpendicularPart = dir - forwardPart;
  315. pos = forwardPart * level.dangerOvalScale[airstrikeType] + perpendicularPart;
  316. innerpos = pos * level.dangerMinRadius[airstrikeType];
  317. innerpos += origin;
  318. outerpos = pos * level.dangerMaxRadius[airstrikeType];
  319. outerpos += origin;
  320.  
  321. if ( j > 0 )
  322. {
  323. line( innerpos, previnnerpos, (1, 0, 0) );
  324. line( outerpos, prevouterpos, (1,.5,.5) );
  325. }
  326.  
  327. previnnerpos = innerpos;
  328. prevouterpos = outerpos;
  329. }
  330. }
  331. wait .05;
  332. }
  333. }
  334. #/
  335.  
  336. getAirstrikeDanger( point )
  337. {
  338. danger = 0;
  339. for ( i = 0; i < level.artilleryDangerCenters.size; i++ )
  340. {
  341. origin = level.artilleryDangerCenters[i].origin;
  342. forward = level.artilleryDangerCenters[i].forward;
  343. airstrikeType = level.artilleryDangerCenters[i].airstrikeType;
  344.  
  345. danger += getSingleAirstrikeDanger( point, origin, forward, airstrikeType );
  346. }
  347. return danger;
  348. }
  349.  
  350. getSingleAirstrikeDanger( point, origin, forward, airstrikeType )
  351. {
  352. center = origin + level.dangerForwardPush[airstrikeType] * level.dangerMaxRadius[airstrikeType] * forward;
  353.  
  354. diff = point - center;
  355. diff = (diff[0], diff[1], 0);
  356.  
  357. forwardPart = vectorDot( diff, forward ) * forward;
  358. perpendicularPart = diff - forwardPart;
  359.  
  360. circlePos = perpendicularPart + forwardPart / level.dangerOvalScale[airstrikeType];
  361.  
  362. /* /#
  363. if ( getdvar("scr_airstrikedebug") == "1" )
  364. {
  365. thread airstrikeLine( center, center + perpendicularPart, (1,1,1), 50 );
  366. thread airstrikeLine( center + perpendicularPart, center + circlePos, (1,1,1), 50 );
  367. thread airstrikeLine( center + circlePos, point, (.5,.5,.5), 50 );
  368. }
  369. #/ */
  370.  
  371. distsq = lengthSquared( circlePos );
  372.  
  373. if ( distsq > level.dangerMaxRadius[airstrikeType] * level.dangerMaxRadius[airstrikeType] )
  374. return 0;
  375.  
  376. if ( distsq < level.dangerMinRadius[airstrikeType] * level.dangerMinRadius[airstrikeType] )
  377. return 1;
  378.  
  379. dist = sqrt( distsq );
  380. distFrac = (dist - level.dangerMinRadius[airstrikeType]) / (level.dangerMaxRadius[airstrikeType] - level.dangerMinRadius[airstrikeType]);
  381.  
  382. assertEx( distFrac >= 0 && distFrac <= 1, distFrac );
  383.  
  384. return 1 - distFrac;
  385. }
  386.  
  387.  
  388. pointIsInAirstrikeArea( point, targetpos, yaw, airstrikeType )
  389. {
  390. return distance2d( point, targetpos ) <= level.dangerMaxRadius[airstrikeType] * 1.25;
  391. // TODO
  392. //return getSingleAirstrikeDanger( point, targetpos, yaw ) > 0;
  393. }
  394.  
  395.  
  396. losRadiusDamage( pos, radius, max, min, owner, eInflictor, sWeapon )
  397. {
  398. ents = maps\mp\gametypes\_weapons::getDamageableEnts(pos, radius, true);
  399.  
  400. glassRadiusDamage( pos, radius, max, min );
  401.  
  402. for (i = 0; i < ents.size; i++)
  403. {
  404. if (ents[i].entity == self)
  405. continue;
  406.  
  407. dist = distance(pos, ents[i].damageCenter);
  408.  
  409. if ( ents[i].isPlayer || ( isDefined( ents[i].isSentry ) && ents[i].isSentry ) )
  410. {
  411. // check if there is a path to this entity 130 units above his feet. if not, they're probably indoors
  412. indoors = !BulletTracePassed( ents[i].entity.origin, ents[i].entity.origin + (0,0,130), false, undefined );
  413. if ( indoors )
  414. {
  415. indoors = !BulletTracePassed( ents[i].entity.origin + (0,0,130), pos + (0,0,130 - 16), false, undefined );
  416. if ( indoors )
  417. {
  418. // give them a distance advantage for being indoors.
  419. dist *= 4;
  420. if ( dist > radius )
  421. continue;
  422. }
  423. }
  424. }
  425.  
  426. ents[i].damage = int(max + (min-max)*dist/radius);
  427. ents[i].pos = pos;
  428. ents[i].damageOwner = owner;
  429. ents[i].eInflictor = eInflictor;
  430. level.airStrikeDamagedEnts[level.airStrikeDamagedEntsCount] = ents[i];
  431. level.airStrikeDamagedEntsCount++;
  432. }
  433.  
  434. thread airstrikeDamageEntsThread( sWeapon );
  435. }
  436.  
  437.  
  438. airstrikeDamageEntsThread( sWeapon )
  439. {
  440. self notify ( "airstrikeDamageEntsThread" );
  441. self endon ( "airstrikeDamageEntsThread" );
  442.  
  443. for ( ; level.airstrikeDamagedEntsIndex < level.airstrikeDamagedEntsCount; level.airstrikeDamagedEntsIndex++ )
  444. {
  445. if ( !isDefined( level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex] ) )
  446. continue;
  447.  
  448. ent = level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex];
  449.  
  450. if ( !isDefined( ent.entity ) )
  451. continue;
  452.  
  453. if ( !ent.isPlayer || isAlive( ent.entity ) )
  454. {
  455. ent maps\mp\gametypes\_weapons::damageEnt(
  456. ent.eInflictor, // eInflictor = the entity that causes the damage (e.g. a claymore)
  457. ent.damageOwner, // eAttacker = the player that is attacking
  458. ent.damage, // iDamage = the amount of damage to do
  459. "MOD_PROJECTILE_SPLASH", // sMeansOfDeath = string specifying the method of death (e.g. "MOD_PROJECTILE_SPLASH")
  460. sWeapon, // sWeapon = string specifying the weapon used (e.g. "claymore_mp")
  461. ent.pos, // damagepos = the position damage is coming from
  462. vectornormalize(ent.damageCenter - ent.pos) // damagedir = the direction damage is moving in
  463. );
  464.  
  465. level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex] = undefined;
  466.  
  467. if ( ent.isPlayer )
  468. wait ( 0.05 );
  469. }
  470. else
  471. {
  472. level.airstrikeDamagedEnts[level.airstrikeDamagedEntsIndex] = undefined;
  473. }
  474. }
  475. }
  476.  
  477.  
  478. radiusArtilleryShellshock(pos, radius, maxduration, minduration, team )
  479. {
  480. players = level.players;
  481.  
  482. foreach ( player in level.players )
  483. {
  484. if ( !isAlive( player ) )
  485. continue;
  486.  
  487. if ( player.team == team || player.team == "spectator" )
  488. continue;
  489.  
  490. playerPos = player.origin + (0,0,32);
  491. dist = distance( pos, playerPos );
  492.  
  493. if ( dist > radius )
  494. continue;
  495.  
  496. duration = int(maxduration + (minduration-maxduration)*dist/radius);
  497. player thread artilleryShellshock( "default", duration );
  498. }
  499. }
  500.  
  501.  
  502. artilleryShellshock(type, duration)
  503. {
  504. self endon ( "disconnect" );
  505.  
  506. if (isdefined(self.beingArtilleryShellshocked) && self.beingArtilleryShellshocked)
  507. return;
  508. self.beingArtilleryShellshocked = true;
  509.  
  510. self shellshock(type, duration);
  511. wait(duration + 1);
  512.  
  513. self.beingArtilleryShellshocked = false;
  514. }
  515.  
  516.  
  517. /#
  518. airstrikeLine( start, end, color, duration )
  519. {
  520. frames = duration * 20;
  521. for ( i = 0; i < frames; i++ )
  522. {
  523. line(start,end,color);
  524. wait .05;
  525. }
  526. }
  527.  
  528.  
  529. traceBomb()
  530. {
  531. self endon("death");
  532. prevpos = self.origin;
  533. while(1)
  534. {
  535. thread airstrikeLine( prevpos, self.origin, (.5,1,0), 40 );
  536. prevpos = self.origin;
  537. wait .2;
  538. }
  539. }
  540. #/
  541.  
  542.  
  543. doBomberStrike( lifeId, owner, requiredDeathCount, bombsite, startPoint, endPoint, bombTime, flyTime, direction, airStrikeType )
  544. {
  545. // plane spawning randomness = up to 125 units, biased towards 0
  546. // radius of bomb damage is 512
  547.  
  548. if ( !isDefined( owner ) )
  549. return;
  550.  
  551. startPathRandomness = 100;
  552. endPathRandomness = 150;
  553.  
  554. pathStart = startPoint + ( (randomfloat(2) - 1)*startPathRandomness, (randomfloat(2) - 1)*startPathRandomness, 0 );
  555. pathEnd = endPoint + ( (randomfloat(2) - 1)*endPathRandomness , (randomfloat(2) - 1)*endPathRandomness , 0 );
  556.  
  557. // Spawn the planes
  558. plane = spawnplane( owner, "script_model", pathStart, "compass_objpoint_b2_airstrike_friendly", "compass_objpoint_b2_airstrike_enemy" );
  559.  
  560. plane playLoopSound( "veh_b2_dist_loop" );
  561. plane setModel( "vehicle_b2_bomber" );
  562. plane thread handleEMP( owner );
  563. plane.lifeId = lifeId;
  564.  
  565. plane.angles = direction;
  566. forward = anglesToForward( direction );
  567. plane moveTo( pathEnd, flyTime, 0, 0 );
  568.  
  569. thread stealthBomber_killCam( plane, pathEnd, flyTime, airStrikeType );
  570.  
  571. thread bomberDropBombs( plane, bombsite, owner );
  572.  
  573. // Delete the plane after its flyby
  574. wait ( flyTime );
  575. plane notify( "delete" );
  576. plane delete();
  577. }
  578.  
  579.  
  580. bomberDropBombs( plane, bombSite, owner )
  581. {
  582. while ( !targetIsClose( plane, bombsite, 5000 ) )
  583. wait ( 0.05 );
  584.  
  585. //playfxontag( level.stealthbombfx, plane, "tag_left_alamo_missile" );
  586. //playfxontag( level.stealthbombfx, plane, "tag_right_alamo_missile" );
  587.  
  588. showFx = true;
  589. sonicBoom = false;
  590.  
  591. plane notify ( "start_bombing" );
  592.  
  593. plane thread playBombFx();
  594.  
  595. for ( dist = targetGetDist( plane, bombsite ); dist < 5000; dist = targetGetDist( plane, bombsite ) )
  596. {
  597. if ( dist < 1500 && !sonicBoom )
  598. {
  599. plane playSound( "veh_b2_sonic_boom" );
  600. sonicBoom = true;
  601. }
  602.  
  603. showFx = !showFx;
  604. if ( dist < 4500 )
  605. plane thread callStrike_bomb( plane.origin, owner, (0,0,0), showFx );
  606. wait ( 0.1 );
  607. }
  608.  
  609. plane notify ( "stop_bombing" );
  610.  
  611. //stopfxontag( level.stealthbombfx, plane, "tag_left_alamo_missile" );
  612. //stopfxontag( level.stealthbombfx, plane, "tag_right_alamo_missile" );
  613. }
  614.  
  615.  
  616. playBombFx()
  617. {
  618. self endon ( "stop_bombing" );
  619.  
  620. for ( ;; )
  621. {
  622. playFxOnTag( level.stealthbombfx, self, "tag_left_alamo_missile" );
  623. playFxOnTag( level.stealthbombfx, self, "tag_right_alamo_missile" );
  624.  
  625. wait ( 0.5 );
  626. }
  627. }
  628.  
  629.  
  630. stealthBomber_killCam( plane, pathEnd, flyTime, typeOfStrike )
  631. {
  632. plane waittill ( "start_bombing" );
  633.  
  634. planedir = anglesToForward( plane.angles );
  635.  
  636. killCamEnt = spawn( "script_model", plane.origin + (0,0,100) - planedir * 200 );
  637. plane.killCamEnt = killCamEnt;
  638. plane.airstrikeType = typeOfStrike;
  639. killCamEnt.startTime = gettime();
  640. killCamEnt thread deleteAfterTime( 15.0 );
  641.  
  642. killCamEnt linkTo( plane, "tag_origin", (-256,768,768), ( 0,0,0 ) );
  643. }
  644.  
  645.  
  646. callStrike_bomb( coord, owner, offset, showFx )
  647. {
  648. if ( !isDefined( owner ) || owner isEMPed() )
  649. {
  650. self notify( "stop_bombing" );
  651. return;
  652. }
  653.  
  654. accuracyRadius = 512;
  655.  
  656. randVec = ( 0, randomint( 360 ), 0 );
  657. bombPoint = coord + vector_multiply( anglestoforward( randVec ), randomFloat( accuracyRadius ) );
  658. trace = bulletTrace( bombPoint, bombPoint + (0,0,-10000), false, undefined );
  659.  
  660. bombPoint = trace["position"];
  661.  
  662. bombHeight = distance( coord, bombPoint );
  663.  
  664. if ( bombHeight > 5000 )
  665. return;
  666.  
  667. wait ( 0.85 * (bombHeight / 2000) );
  668.  
  669. if ( !isDefined( owner ) || owner isEMPed() )
  670. {
  671. self notify( "stop_bombing" );
  672. return;
  673. }
  674.  
  675. if ( showFx )
  676. {
  677. playFx( level.mortareffect, bombPoint );
  678.  
  679. PlayRumbleOnPosition( "grenade_rumble", bombPoint );
  680. earthquake( 1.0, 0.6, bombPoint, 2000 );
  681. }
  682.  
  683. thread playSoundInSpace( "exp_airstrike_bomb", bombPoint );
  684. radiusArtilleryShellshock( bombPoint, 512, 8, 4, owner.team );
  685. losRadiusDamage( bombPoint + (0,0,16), 896, 300, 50, owner, self, "stealth_bomb_mp" ); // targetpos, radius, maxdamage, mindamage, player causing damage
  686. }
  687.  
  688.  
  689. doPlaneStrike( lifeId, owner, requiredDeathCount, bombsite, startPoint, endPoint, bombTime, flyTime, direction, typeOfStrike )
  690. {
  691. // plane spawning randomness = up to 125 units, biased towards 0
  692. // radius of bomb damage is 512
  693.  
  694. if ( !isDefined( owner ) )
  695. return;
  696.  
  697. startPathRandomness = 100;
  698. endPathRandomness = 150;
  699.  
  700. pathStart = startPoint + ( (randomfloat(2) - 1)*startPathRandomness, (randomfloat(2) - 1)*startPathRandomness, 0 );
  701. pathEnd = endPoint + ( (randomfloat(2) - 1)*endPathRandomness , (randomfloat(2) - 1)*endPathRandomness , 0 );
  702.  
  703. // Spawn the planes
  704. if( typeOfStrike == "harrier" )
  705. plane = spawnplane( owner, "script_model", pathStart, "hud_minimap_harrier_green", "hud_minimap_harrier_red" );
  706. else
  707. plane = spawnplane( owner, "script_model", pathStart, "compass_objpoint_airstrike_friendly", "compass_objpoint_airstrike_busy" );
  708.  
  709. if( typeOfStrike == "harrier" )
  710. {
  711. if ( owner.team == "allies" )
  712. plane setModel( "vehicle_av8b_harrier_jet_mp" );
  713. else
  714. plane setModel( "vehicle_av8b_harrier_jet_opfor_mp" );
  715. }
  716. else
  717. plane setModel( "vehicle_mig29_desert" );
  718.  
  719. plane playloopsound( "veh_mig29_dist_loop" );
  720. plane thread handleEMP( owner );
  721.  
  722. plane.lifeId = lifeId;
  723.  
  724. plane.angles = direction;
  725. forward = anglesToForward( direction );
  726. plane thread playPlaneFx();
  727. plane moveTo( pathEnd, flyTime, 0, 0 );
  728.  
  729. /#
  730. if ( getdvar("scr_airstrikedebug") == "1" )
  731. thread airstrikeLine( pathStart, pathEnd, (1,1,1), 20 );
  732. #/
  733.  
  734. //thread callStrike_planeSound( plane, bombsite );
  735. thread callStrike_bombEffect( plane, pathEnd, flyTime, bombTime - 1.0, owner, requiredDeathCount, typeOfStrike );
  736.  
  737. // Delete the plane after its flyby
  738. wait flyTime;
  739. plane notify( "delete" );
  740. plane delete();
  741. }
  742.  
  743. callStrike_bombEffect( plane, pathEnd, flyTime, launchTime, owner, requiredDeathCount, typeOfStrike )
  744. {
  745. wait ( launchTime );
  746.  
  747. if ( !isDefined( owner )|| owner isEMPed() )
  748. return;
  749.  
  750. plane playSound( "veh_mig29_sonic_boom" );
  751. planedir = anglesToForward( plane.angles );
  752.  
  753. bomb = spawnbomb( plane.origin, plane.angles );
  754. bomb moveGravity( vector_multiply( anglestoforward( plane.angles ), 7000/1.5 ), 3.0 );
  755.  
  756. bomb.lifeId = requiredDeathCount;
  757.  
  758. killCamEnt = spawn( "script_model", plane.origin + (0,0,100) - planedir * 200 );
  759. bomb.killCamEnt = killCamEnt;
  760. bomb.airstrikeType = typeOfStrike;
  761. killCamEnt.startTime = gettime();
  762. killCamEnt thread deleteAfterTime( 15.0 );
  763. killCamEnt.angles = planedir;
  764. killCamEnt moveTo( pathEnd + (0,0,100), flyTime, 0, 0 );
  765.  
  766. /#
  767. if ( getdvar("scr_airstrikedebug") == "1" )
  768. bomb thread traceBomb();
  769. #/
  770.  
  771. wait .4;
  772. //plane stoploopsound();
  773. killCamEnt moveTo( killCamEnt.origin + planedir * 4000, 1, 0, 0 );
  774.  
  775. wait .45;
  776. killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.2)) * 3500, 2, 0, 0 );
  777.  
  778. wait ( 0.15 );
  779.  
  780. newBomb = spawn( "script_model", bomb.origin );
  781. newBomb setModel( "tag_origin" );
  782. newBomb.origin = bomb.origin;
  783. newBomb.angles = bomb.angles;
  784.  
  785. bomb setModel( "tag_origin" );
  786. wait (0.10); // wait two server frames before playing fx
  787.  
  788. bombOrigin = newBomb.origin;
  789. bombAngles = newBomb.angles;
  790. playfxontag( level.airstrikefx, newBomb, "tag_origin" );
  791.  
  792. wait .05;
  793. killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.25)) * 2500, 2, 0, 0 );
  794.  
  795. wait .25;
  796. killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.35)) * 2000, 2, 0, 0 );
  797.  
  798. wait .2;
  799. killCamEnt moveTo( killCamEnt.origin + (planedir + (0,0,-.45)) * 1500, 2, 0, 0 );
  800.  
  801.  
  802. wait ( 0.5 );
  803.  
  804. repeat = 12;
  805. minAngles = 5;
  806. maxAngles = 55;
  807. angleDiff = (maxAngles - minAngles) / repeat;
  808.  
  809. hitpos = (0,0,0);
  810.  
  811. for( i = 0; i < repeat; i++ )
  812. {
  813. traceDir = anglesToForward( bombAngles + (maxAngles-(angleDiff * i),randomInt( 10 )-5,0) );
  814. traceEnd = bombOrigin + vector_multiply( traceDir, 10000 );
  815. trace = bulletTrace( bombOrigin, traceEnd, false, undefined );
  816.  
  817. traceHit = trace["position"];
  818. hitpos += traceHit;
  819.  
  820. /#
  821. if ( getdvar("scr_airstrikedebug") == "1" )
  822. thread airstrikeLine( bombOrigin, traceHit, (1,0,0), 40 );
  823. #/
  824.  
  825. thread losRadiusDamage( traceHit + (0,0,16), 512, 200, 30, owner, bomb, "artillery_mp" ); // targetpos, radius, maxdamage, mindamage, player causing damage, entity that player used to cause damage
  826.  
  827. if ( i%3 == 0 )
  828. {
  829. thread playsoundinspace( "exp_airstrike_bomb", traceHit );
  830. playRumbleOnPosition( "artillery_rumble", traceHit );
  831. earthquake( 0.7, 0.75, traceHit, 1000 );
  832. }
  833.  
  834. wait ( 0.05 );
  835. }
  836.  
  837. hitpos = hitpos / repeat + (0,0,128);
  838. killCamEnt moveto( bomb.killCamEnt.origin * .35 + hitpos * .65, 1.5, 0, .5 );
  839.  
  840. wait ( 5.0 );
  841. newBomb delete();
  842. bomb delete();
  843. }
  844.  
  845.  
  846. spawnbomb( origin, angles )
  847. {
  848. bomb = spawn( "script_model", origin );
  849. bomb.angles = angles;
  850. bomb setModel( "projectile_cbu97_clusterbomb" );
  851.  
  852. return bomb;
  853. }
  854.  
  855.  
  856. deleteAfterTime( time )
  857. {
  858. self endon ( "death" );
  859. wait ( 10.0 );
  860.  
  861. self delete();
  862. }
  863.  
  864. playPlaneFx()
  865. {
  866. self endon ( "death" );
  867.  
  868. wait( 0.5);
  869. playfxontag( level.fx_airstrike_afterburner, self, "tag_engine_right" );
  870. wait( 0.5);
  871. playfxontag( level.fx_airstrike_afterburner, self, "tag_engine_left" );
  872. wait( 0.5);
  873. playfxontag( level.fx_airstrike_contrail, self, "tag_right_wingtip" );
  874. wait( 0.5);
  875. playfxontag( level.fx_airstrike_contrail, self, "tag_left_wingtip" );
  876. }
  877.  
  878. callStrike( lifeId, owner, coord, yaw )
  879. {
  880.  
  881. heightEnt = undefined;
  882. planeBombExplodeDistance = 0;
  883. // Get starting and ending point for the plane
  884. direction = ( 0, yaw, 0 );
  885. heightEnt = GetEnt( "airstrikeheight", "targetname" );
  886.  
  887. if ( self.airStrikeType == "stealth" )
  888. {
  889. thread teamPlayerCardSplash( "used_stealth_airstrike", owner, owner.team );
  890.  
  891. planeHalfDistance = 12000;
  892. planeFlySpeed = 2000;
  893.  
  894. if ( !isDefined( heightEnt ) )//old system
  895. {
  896. println( "NO DEFINED AIRSTRIKE HEIGHT SCRIPT_ORIGIN IN LEVEL" );
  897. planeFlyHeight = 950;
  898. planeBombExplodeDistance = 1500;
  899. if ( isdefined( level.airstrikeHeightScale ) )
  900. planeFlyHeight *= level.airstrikeHeightScale;
  901. }
  902. else
  903. {
  904. planeFlyHeight = heightEnt.origin[2];
  905. planeBombExplodeDistance = getExplodeDistance( planeFlyHeight );
  906. }
  907.  
  908. }
  909. else
  910. {
  911. planeHalfDistance = 24000;
  912. planeFlySpeed = 7000;
  913.  
  914. if ( !isDefined( heightEnt ) )//old system
  915. {
  916. println( "NO DEFINED AIRSTRIKE HEIGHT SCRIPT_ORIGIN IN LEVEL" );
  917. planeFlyHeight = 850;
  918. planeBombExplodeDistance = 1500;
  919. if ( isdefined( level.airstrikeHeightScale ) )
  920. planeFlyHeight *= level.airstrikeHeightScale;
  921. }
  922. else
  923. {
  924. planeFlyHeight = heightEnt.origin[2];
  925. planeBombExplodeDistance = getExplodeDistance( planeFlyHeight );
  926. }
  927. }
  928.  
  929. startPoint = coord + vector_multiply( anglestoforward( direction ), -1 * planeHalfDistance );
  930.  
  931. if ( isDefined( heightEnt ) )// used in the new height system
  932. startPoint *= (1,1,0);
  933.  
  934. startPoint += ( 0, 0, planeFlyHeight );
  935.  
  936. if ( self.airStrikeType == "stealth" )
  937. endPoint = coord + vector_multiply( anglestoforward( direction ), planeHalfDistance*4 );
  938. else
  939. endPoint = coord + vector_multiply( anglestoforward( direction ), planeHalfDistance );
  940.  
  941. if ( isDefined( heightEnt ) )// used in the new height system
  942. endPoint *= (1,1,0);
  943.  
  944. endPoint += ( 0, 0, planeFlyHeight );
  945.  
  946. // Make the plane fly by
  947. d = length( startPoint - endPoint );
  948. flyTime = ( d / planeFlySpeed );
  949.  
  950. // bomb explodes planeBombExplodeDistance after the plane passes the center
  951. d = abs( d/2 + planeBombExplodeDistance );
  952. bombTime = ( d / planeFlySpeed );
  953.  
  954. assert( flyTime > bombTime );
  955.  
  956. owner endon("disconnect");
  957.  
  958. requiredDeathCount = lifeId;
  959.  
  960. level.airstrikeDamagedEnts = [];
  961. level.airStrikeDamagedEntsCount = 0;
  962. level.airStrikeDamagedEntsIndex = 0;
  963.  
  964. if ( self.airStrikeType == "harrier" )
  965. {
  966. level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, startPoint+(0,0,randomInt(500)), endPoint+(0,0,randomInt(500)), bombTime, flyTime, direction, self.airStrikeType );
  967.  
  968. wait randomfloatrange( 1.5, 2.5 );
  969. maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
  970. level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, startPoint+(0,0,randomInt(200)), endPoint+(0,0,randomInt(200)), bombTime, flyTime, direction, self.airStrikeType );
  971.  
  972. wait randomfloatrange( 1.5, 2.5 );
  973. maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
  974. harrier = beginHarrier( lifeId, startPoint, coord );
  975. owner thread defendLocation( harrier );
  976.  
  977. return harrier;
  978. //owner thread harrierMissileStrike( startPoint, coord );
  979.  
  980. }
  981. else if ( self.airStrikeType == "stealth" )
  982. {
  983. level thread doBomberStrike( lifeId, owner, requiredDeathCount, coord, startPoint+(0,0,randomInt(1000)), endPoint+(0,0,randomInt(1000)), bombTime, flyTime, direction, self.airStrikeType );
  984. }
  985. else if ( self.airStrikeType == "lbheli" )
  986. {
  987. maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
  988. harrier = maps\mp\killstreaks\_flyable_heli::beginLbheli( lifeId, startPoint, coord, owner );
  989.  
  990. //owner thread maps\mp\killstreaks\_flyable_heli::defendLocation( harrier );
  991.  
  992. return harrier;
  993. //owner thread harrierMissileStrike( startPoint, coord );
  994.  
  995. }
  996. else //common airstrike
  997. {
  998. level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, startPoint+(0,0,randomInt(500)), endPoint+(0,0,randomInt(500)), bombTime, flyTime, direction, self.airStrikeType );
  999.  
  1000. wait randomfloatrange( 1.5, 2.5 );
  1001. maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
  1002. level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, startPoint+(0,0,randomInt(200)), endPoint+(0,0,randomInt(200)), bombTime, flyTime, direction, self.airStrikeType );
  1003.  
  1004. wait randomfloatrange( 1.5, 2.5 );
  1005. maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
  1006. level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, startPoint+(0,0,randomInt(200)), endPoint+(0,0,randomInt(200)), bombTime, flyTime, direction, self.airStrikeType );
  1007.  
  1008. if ( self.airStrikeType == "super" )
  1009. {
  1010. wait randomfloatrange( 2.5, 3.5 );
  1011. maps\mp\gametypes\_hostmigration::waitTillHostMigrationDone();
  1012. level thread doPlaneStrike( lifeId, owner, requiredDeathCount, coord, startPoint+(0,0,randomInt(200)), endPoint+(0,0,randomInt(200)), bombTime, flyTime, direction, self.airStrikeType );
  1013. }
  1014. }
  1015. }
  1016.  
  1017.  
  1018. getExplodeDistance( height )
  1019. {
  1020. standardHeight = 850;
  1021. standardDistance = 1500;
  1022. distanceFrac = standardHeight/height;
  1023.  
  1024. newDistance = distanceFrac * standardDistance;
  1025.  
  1026. return newDistance;
  1027. }
  1028.  
  1029.  
  1030. targetGetDist( other, target )
  1031. {
  1032. infront = targetisinfront( other, target );
  1033. if( infront )
  1034. dir = 1;
  1035. else
  1036. dir = -1;
  1037. a = flat_origin( other.origin );
  1038. b = a+vector_multiply( anglestoforward(flat_angle(other.angles)), (dir*100000) );
  1039. point = pointOnSegmentNearestToPoint(a,b, target);
  1040. dist = distance(a,point);
  1041.  
  1042. return dist;
  1043. }
  1044.  
  1045. targetisclose(other, target, closeDist)
  1046. {
  1047. if ( !isDefined( closeDist ) )
  1048. closeDist = 3000;
  1049.  
  1050. infront = targetisinfront(other, target);
  1051. if(infront)
  1052. dir = 1;
  1053. else
  1054. dir = -1;
  1055. a = flat_origin(other.origin);
  1056. b = a+vector_multiply(anglestoforward(flat_angle(other.angles)), (dir*100000));
  1057. point = pointOnSegmentNearestToPoint(a,b, target);
  1058. dist = distance(a,point);
  1059. if (dist < closeDist)
  1060. return true;
  1061. else
  1062. return false;
  1063. }
  1064.  
  1065.  
  1066. targetisinfront(other, target)
  1067. {
  1068. forwardvec = anglestoforward(flat_angle(other.angles));
  1069. normalvec = vectorNormalize(flat_origin(target)-other.origin);
  1070. dot = vectordot(forwardvec,normalvec);
  1071. if(dot > 0)
  1072. return true;
  1073. else
  1074. return false;
  1075. }
  1076.  
  1077. waitForAirstrikeCancel()
  1078. {
  1079. self waittill( "cancel_location" );
  1080. self setblurforplayer( 0, 0.3 );
  1081. }
  1082.  
  1083.  
  1084. selectAirstrikeLocation( lifeId, airStrikeType )
  1085. {
  1086. assert( isDefined( airStrikeType ) );
  1087.  
  1088. self.airStrikeType = airStrikeType;
  1089.  
  1090. if ( airStrikeType == "precision" || airStrikeType == "stealth" || airStrikeType == "lbheli" )
  1091. chooseDirection = true;
  1092. else
  1093. chooseDirection = false;
  1094.  
  1095. targetSize = level.mapSize / 5.625; // 138 in 720
  1096. if ( level.splitscreen )
  1097. targetSize *= 1.5;
  1098.  
  1099. self beginLocationSelection( "map_artillery_selector", chooseDirection, targetSize );
  1100. self.selectingLocation = true;
  1101.  
  1102. self setblurforplayer( 4.0, 0.3 );
  1103. self thread waitForAirstrikeCancel();
  1104.  
  1105. self thread endSelectionOn( "cancel_location" );
  1106. self thread endSelectionOn( "death" );
  1107. self thread endSelectionOn( "disconnect" );
  1108. self thread endSelectionOn( "used" ); // so that this thread doesn't kill itself when we use an airstrike
  1109. self thread endSelectionOnGameEnd();
  1110. self thread endSelectionOnEMP();
  1111.  
  1112. self endon( "stop_location_selection" );
  1113.  
  1114. // wait for the selection. randomize the yaw if we're not doing a precision airstrike.
  1115. self waittill( "confirm_location", location, directionYaw );
  1116. if ( !chooseDirection )
  1117. directionYaw = randomint(360);
  1118.  
  1119. self setblurforplayer( 0, 0.3 );
  1120.  
  1121. if ( airStrikeType == "harrier" && level.planes > 1 )
  1122. {
  1123. self notify ( "cancel_location" );
  1124. self iPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
  1125. return false;
  1126. }
  1127.  
  1128. if ( airStrikeType == "lbheli" && level.lbheli > 5 )
  1129. {
  1130. self notify ( "cancel_location" );
  1131. self iPrintLnBold( &"MP_AIR_SPACE_TOO_CROWDED" );
  1132. return false;
  1133. }
  1134. self.nieRespilemGoJeszcze = false;
  1135. self thread finishAirstrikeUsage( lifeId, location, directionYaw );
  1136. return true;
  1137. }
  1138.  
  1139. finishAirstrikeUsage( lifeId, location, directionYaw )
  1140. {
  1141. self notify( "used" );
  1142.  
  1143. // find underside of top of skybox
  1144. trace = bullettrace( level.mapCenter + (0,0,1000000), level.mapCenter, false, undefined );
  1145. location = (location[0], location[1], trace["position"][2] - 514);
  1146.  
  1147. thread doAirstrike( lifeId, location, directionYaw, self, self.pers["team"] );
  1148. }
  1149.  
  1150.  
  1151. endSelectionOn( waitfor )
  1152. {
  1153. self endon( "stop_location_selection" );
  1154. self waittill( waitfor );
  1155. self thread stopAirstrikeLocationSelection( (waitfor == "disconnect") );
  1156. }
  1157.  
  1158.  
  1159. endSelectionOnGameEnd()
  1160. {
  1161. self endon( "stop_location_selection" );
  1162. level waittill( "game_ended" );
  1163. self thread stopAirstrikeLocationSelection( false );
  1164. }
  1165.  
  1166.  
  1167. endSelectionOnEMP()
  1168. {
  1169. self endon( "stop_location_selection" );
  1170. for ( ;; )
  1171. {
  1172. level waittill( "emp_update" );
  1173.  
  1174. if ( !self isEMPed() )
  1175. continue;
  1176.  
  1177. self thread stopAirstrikeLocationSelection( false );
  1178. return;
  1179. }
  1180. }
  1181.  
  1182.  
  1183. stopAirstrikeLocationSelection( disconnected )
  1184. {
  1185. if ( !disconnected )
  1186. {
  1187. self setblurforplayer( 0, 0.3 );
  1188. self endLocationSelection();
  1189. self.selectingLocation = undefined;
  1190. }
  1191. self notify( "stop_location_selection" );
  1192. }
  1193.  
  1194.  
  1195. useAirstrike( lifeId, pos, yaw )
  1196. {
  1197. }
  1198.  
  1199.  
  1200. handleEMP( owner )
  1201. {
  1202. self endon ( "death" );
  1203.  
  1204. if ( owner isEMPed() )
  1205. {
  1206. playFxOnTag( level.onfirefx, self, "tag_engine_right" );
  1207. playFxOnTag( level.onfirefx, self, "tag_engine_left" );
  1208. return;
  1209. }
  1210.  
  1211. for ( ;; )
  1212. {
  1213. level waittill ( "emp_update" );
  1214.  
  1215. if ( !owner isEMPed() )
  1216. continue;
  1217.  
  1218. playFxOnTag( level.onfirefx, self, "tag_engine_right" );
  1219. playFxOnTag( level.onfirefx, self, "tag_engine_left" );
  1220. }
  1221. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement