g_fx.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
  1. // leave this line at the top for all g_xxxx.cpp files...
  2. #include "g_headers.h"
  3. #include "g_local.h"
  4. #include "g_functions.h"
  5. extern int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create );
  6. #define FX_ENT_RADIUS 32
  7. extern int BMS_START;
  8. extern int BMS_MID;
  9. extern int BMS_END;
  10. //----------------------------------------------------------
  11. /*QUAKED fx_runner (0 0 1) (-8 -8 -8) (8 8 8) STARTOFF ONESHOT DAMAGE
  12. Runs the specified effect, can also be targeted at an info_notnull to orient the effect
  13. STARTOFF - effect starts off, toggles on/off when used
  14. ONESHOT - effect fires only when used
  15. DAMAGE - does radius damage around effect every "delay" milliseonds
  16. "fxFile" - name of the effect file to play
  17. "target" - direction to aim the effect in, otherwise defaults to up
  18. "target2" - uses its target2 when the fx gets triggered
  19. "delay" - how often to call the effect, don't over-do this ( default 200 )
  20. "random" - random amount of time to add to delay, ( default 0, 200 = 0ms to 200ms )
  21. "splashRadius" - only works when damage is checked ( default 16 )
  22. "splashDamage" - only works when damage is checked ( default 5 )
  23. "soundset" - bmodel set to use, plays start sound when toggled on, loop sound while on ( doesn't play on a oneshot), and a stop sound when turned off
  24. */
  25. #define FX_RUNNER_RESERVED 0x800000
  26. //----------------------------------------------------------
  27. void fx_runner_think( gentity_t *ent )
  28. {
  29. vec3_t temp;
  30. EvaluateTrajectory( &ent->s.pos, level.time, ent->currentOrigin );
  31. EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
  32. // call the effect with the desired position and orientation
  33. G_AddEvent( ent, EV_PLAY_EFFECT, ent->fxID );
  34. // Assume angles, we'll do a cross product on the other end to finish up
  35. AngleVectors( ent->currentAngles, ent->pos3, NULL, NULL );
  36. MakeNormalVectors( ent->pos3, ent->pos4, temp ); // there IS a reason this is done...it's so that it doesn't break every effect in the game...
  37. ent->nextthink = level.time + ent->delay + random() * ent->random;
  38. if ( ent->spawnflags & 4 ) // damage
  39. {
  40. G_RadiusDamage( ent->currentOrigin, ent, ent->splashDamage, ent->splashRadius, ent, MOD_UNKNOWN );
  41. }
  42. if ( ent->target2 )
  43. {
  44. // let our target know that we have spawned an effect
  45. G_UseTargets2( ent, ent, ent->target2 );
  46. }
  47. if ( !(ent->spawnflags & 2 ) && !ent->s.loopSound ) // NOT ONESHOT...this is an assy thing to do
  48. {
  49. if ( VALIDSTRING( ent->soundSet ) == true )
  50. {
  51. ent->s.loopSound = CAS_GetBModelSound( ent->soundSet, BMS_MID );
  52. if ( ent->s.loopSound < 0 )
  53. {
  54. ent->s.loopSound = 0;
  55. }
  56. }
  57. }
  58. }
  59. //----------------------------------------------------------
  60. void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  61. {
  62. if (self->s.isPortalEnt)
  63. { //rww - mark it as broadcast upon first use if it's within the area of a skyportal
  64. self->svFlags |= SVF_BROADCAST;
  65. }
  66. if ( self->spawnflags & 2 ) // ONESHOT
  67. {
  68. // call the effect with the desired position and orientation, as a safety thing,
  69. // make sure we aren't thinking at all.
  70. fx_runner_think( self );
  71. self->nextthink = -1;
  72. if ( self->target2 )
  73. {
  74. // let our target know that we have spawned an effect
  75. G_UseTargets2( self, self, self->target2 );
  76. }
  77. if ( VALIDSTRING( self->soundSet ) == true )
  78. {
  79. G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START ));
  80. }
  81. }
  82. else
  83. {
  84. // ensure we are working with the right think function
  85. self->e_ThinkFunc = thinkF_fx_runner_think;
  86. // toggle our state
  87. if ( self->nextthink == -1 )
  88. {
  89. // NOTE: we fire the effect immediately on use, the fx_runner_think func will set
  90. // up the nextthink time.
  91. fx_runner_think( self );
  92. if ( VALIDSTRING( self->soundSet ) == true )
  93. {
  94. G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START ));
  95. self->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID );
  96. if ( self->s.loopSound < 0 )
  97. {
  98. self->s.loopSound = 0;
  99. }
  100. }
  101. }
  102. else
  103. {
  104. // turn off for now
  105. self->nextthink = -1;
  106. if ( VALIDSTRING( self->soundSet ) == true )
  107. {
  108. G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_END ));
  109. self->s.loopSound = 0;
  110. }
  111. }
  112. }
  113. }
  114. //----------------------------------------------------------
  115. void fx_runner_link( gentity_t *ent )
  116. {
  117. vec3_t dir;
  118. if ( ent->target )
  119. {
  120. // try to use the target to override the orientation
  121. gentity_t *target = NULL;
  122. target = G_Find( target, FOFS(targetname), ent->target );
  123. if ( !target )
  124. {
  125. // Bah, no good, dump a warning, but continue on and use the UP vector
  126. Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->target );
  127. Com_Printf( " -assuming UP orientation.\n" );
  128. }
  129. else
  130. {
  131. // Our target is valid so let's override the default UP vector
  132. VectorSubtract( target->s.origin, ent->s.origin, dir );
  133. VectorNormalize( dir );
  134. vectoangles( dir, ent->s.angles );
  135. }
  136. }
  137. // don't really do anything with this right now other than do a check to warn the designers if the target2 is bogus
  138. if ( ent->target2 )
  139. {
  140. gentity_t *target = NULL;
  141. target = G_Find( target, FOFS(targetname), ent->target2 );
  142. if ( !target )
  143. {
  144. // Target2 is bogus, but we can still continue
  145. Com_Printf( "fx_runner_link: target2 was specified but is not valid: %s\n", ent->target2 );
  146. }
  147. }
  148. G_SetAngles( ent, ent->s.angles );
  149. if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT
  150. {
  151. // We won't even consider thinking until we are used
  152. ent->nextthink = -1;
  153. }
  154. else
  155. {
  156. if ( VALIDSTRING( ent->soundSet ) == true )
  157. {
  158. ent->s.loopSound = CAS_GetBModelSound( ent->soundSet, BMS_MID );
  159. if ( ent->s.loopSound < 0 )
  160. {
  161. ent->s.loopSound = 0;
  162. }
  163. }
  164. // Let's get to work right now!
  165. ent->e_ThinkFunc = thinkF_fx_runner_think;
  166. ent->nextthink = level.time + 200; // wait a small bit, then start working
  167. }
  168. // make us useable if we can be targeted
  169. if ( ent->targetname )
  170. {
  171. ent->e_UseFunc = useF_fx_runner_use;
  172. }
  173. }
  174. //----------------------------------------------------------
  175. void SP_fx_runner( gentity_t *ent )
  176. {
  177. // Get our defaults
  178. G_SpawnInt( "delay", "200", &ent->delay );
  179. G_SpawnFloat( "random", "0", &ent->random );
  180. G_SpawnInt( "splashRadius", "16", &ent->splashRadius );
  181. G_SpawnInt( "splashDamage", "5", &ent->splashDamage );
  182. if ( !G_SpawnAngleHack( "angle", "0", ent->s.angles ))
  183. {
  184. // didn't have angles, so give us the default of up
  185. VectorSet( ent->s.angles, -90, 0, 0 );
  186. }
  187. if ( !ent->fxFile )
  188. {
  189. gi.Printf( S_COLOR_RED"ERROR: fx_runner %s at %s has no fxFile specified\n", ent->targetname, vtos(ent->s.origin) );
  190. G_FreeEntity( ent );
  191. return;
  192. }
  193. // Try and associate an effect file, unfortunately we won't know if this worked or not
  194. // until the CGAME trys to register it...
  195. ent->fxID = G_EffectIndex( ent->fxFile );
  196. ent->s.eType = ET_MOVER;
  197. // Give us a bit of time to spawn in the other entities, since we may have to target one of 'em
  198. ent->e_ThinkFunc = thinkF_fx_runner_link;
  199. ent->nextthink = level.time + 400;
  200. // Save our position and link us up!
  201. G_SetOrigin( ent, ent->s.origin );
  202. VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
  203. VectorScale( ent->maxs, -1, ent->mins );
  204. gi.linkentity( ent );
  205. }
  206. /*QUAKED fx_snow (1 0 0) (-16 -16 -16) (16 16 16) LIGHT MEDIUM HEAVY MISTY_FOG
  207. This world effect will spawn snow globally into the level.
  208. */
  209. void SP_CreateSnow( gentity_t *ent )
  210. {
  211. cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
  212. if ( r_weatherScale->value == 0.0f )
  213. {
  214. return;
  215. }
  216. // Different Types Of Rain
  217. //-------------------------
  218. if (ent->spawnflags & 1)
  219. {
  220. G_FindConfigstringIndex("lightsnow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  221. }
  222. else if (ent->spawnflags & 2)
  223. {
  224. G_FindConfigstringIndex("snow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  225. }
  226. else if (ent->spawnflags & 4)
  227. {
  228. G_FindConfigstringIndex("heavysnow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  229. }
  230. else
  231. {
  232. G_FindConfigstringIndex("snow", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  233. G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  234. }
  235. // MISTY FOG
  236. //===========
  237. if (ent->spawnflags & 8)
  238. {
  239. G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  240. }
  241. }
  242. /*QUAKED fx_wind (0 .5 .8) (-16 -16 -16) (16 16 16) NORMAL CONSTANT GUSTING SWIRLING x FOG LIGHT_FOG
  243. Generates global wind forces
  244. NORMAL creates a random light global wind
  245. CONSTANT forces all wind to go in a specified direction
  246. GUSTING causes random gusts of wind
  247. SWIRLING causes random swirls of wind
  248. "angles" the direction for constant wind
  249. "speed" the speed for constant wind
  250. */
  251. void SP_CreateWind( gentity_t *ent )
  252. {
  253. cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
  254. if ( r_weatherScale->value <= 0.0f )
  255. {
  256. return;
  257. }
  258. char temp[256];
  259. // Normal Wind
  260. //-------------
  261. if (ent->spawnflags & 1)
  262. {
  263. G_FindConfigstringIndex("wind", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  264. }
  265. // Constant Wind
  266. //---------------
  267. if (ent->spawnflags & 2)
  268. {
  269. vec3_t windDir;
  270. AngleVectors(ent->s.angles, windDir, 0, 0);
  271. G_SpawnFloat( "speed", "500", &ent->speed );
  272. VectorScale(windDir, ent->speed, windDir);
  273. sprintf( temp, "constantwind ( %f %f %f )", windDir[0], windDir[1], windDir[2] );
  274. G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  275. }
  276. // Gusting Wind
  277. //--------------
  278. if (ent->spawnflags & 4)
  279. {
  280. G_FindConfigstringIndex("gustingwind", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  281. }
  282. // Swirling Wind
  283. //---------------
  284. if (ent->spawnflags & 8)
  285. {
  286. G_FindConfigstringIndex("swirlingwind", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  287. }
  288. // MISTY FOG
  289. //===========
  290. if (ent->spawnflags & 32)
  291. {
  292. G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  293. }
  294. // MISTY FOG
  295. //===========
  296. if (ent->spawnflags & 64)
  297. {
  298. G_FindConfigstringIndex("light_fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  299. }
  300. }
  301. /*QUAKED fx_wind_zone (0 .5 .8) ? Creates a constant wind in a local area
  302. Generates local wind forces
  303. "angles" the direction for constant wind
  304. "speed" the speed for constant wind
  305. */
  306. void SP_CreateWindZone( gentity_t *ent )
  307. {
  308. cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
  309. if ( r_weatherScale->value <= 0.0f )
  310. {
  311. return;
  312. }
  313. gi.SetBrushModel(ent, ent->model);
  314. vec3_t windDir;
  315. AngleVectors(ent->s.angles, windDir, 0, 0);
  316. G_SpawnFloat( "speed", "500", &ent->speed );
  317. VectorScale(windDir, ent->speed, windDir);
  318. char temp[256];
  319. sprintf( temp, "windzone ( %f %f %f ) ( %f %f %f ) ( %f %f %f )",
  320. ent->mins[0], ent->mins[1], ent->mins[2],
  321. ent->maxs[0], ent->maxs[1], ent->maxs[2],
  322. windDir[0], windDir[1], windDir[2]
  323. );
  324. G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  325. }
  326. /*QUAKED fx_rain (1 0 0) (-16 -16 -16) (16 16 16) LIGHT MEDIUM HEAVY ACID OUTSIDE_SHAKE MISTY_FOG
  327. This world effect will spawn rain globally into the level.
  328. LIGHT create light drizzle
  329. MEDIUM create average medium rain
  330. HEAVY create heavy downpour (with fog and lightning automatically)
  331. ACID create acid rain
  332. OUTSIDE_SHAKE will cause the camera to shake slightly whenever outside
  333. MISTY_FOG causes clouds of misty fog to float through the level
  334. LIGHTNING causes random bursts of lightning and thunder in the level
  335. The following fields are for lightning:
  336. "flashcolor" "200 200 200" (r g b) (values 0.0-255.0)
  337. "flashdelay" "12000" maximum time delay between lightning strikes
  338. "chanceflicker" "2" 1 in 2 chance of flickering fog
  339. "chancesound" "3" 1 in 3 chance of playing a sound
  340. "chanceeffect" "4" 1 in 4 chance of playing the effect
  341. */
  342. //----------------------------------------------------------
  343. //----------------------------------------------------------
  344. extern void G_SoundAtSpot( vec3_t org, int soundIndex, qboolean broadcast );
  345. void fx_rain_think( gentity_t *ent )
  346. {
  347. if (player)
  348. {
  349. if (ent->count!=0)
  350. {
  351. ent->count--;
  352. if (ent->count==0 || (ent->count%2)==0)
  353. {
  354. gi.WE_SetTempGlobalFogColor(ent->pos2); // Turn Off
  355. if (ent->count==0)
  356. {
  357. ent->nextthink = level.time + Q_irand(1000, 12000);
  358. }
  359. else if (ent->count==2)
  360. {
  361. ent->nextthink = level.time + Q_irand(150, 450);
  362. }
  363. else
  364. {
  365. ent->nextthink = level.time + Q_irand(50, 150);
  366. }
  367. }
  368. else
  369. {
  370. gi.WE_SetTempGlobalFogColor(ent->pos3); // Turn On
  371. ent->nextthink = level.time + 50;
  372. }
  373. }
  374. else if (gi.WE_IsOutside(player->currentOrigin))
  375. {
  376. vec3_t effectPos;
  377. vec3_t effectDir;
  378. VectorClear(effectDir);
  379. effectDir[0] += Q_flrand(-1.0f, 1.0f);
  380. effectDir[1] += Q_flrand(-1.0f, 1.0f);
  381. bool PlayEffect = Q_irand(1,ent->aimDebounceTime)==1;
  382. bool PlayFlicker = Q_irand(1,ent->attackDebounceTime)==1;
  383. bool PlaySound = (PlayEffect || PlayFlicker || Q_irand(1,ent->pushDebounceTime)==1);
  384. // Play The Sound
  385. //----------------
  386. if (PlaySound && !PlayEffect)
  387. {
  388. VectorMA(player->currentOrigin, 250.0f, effectDir, effectPos);
  389. G_SoundAtSpot(effectPos, G_SoundIndex(va("sound/ambience/thunder%d", Q_irand(1,4))), qtrue);
  390. }
  391. // Play The Effect
  392. //-----------------
  393. if (PlayEffect)
  394. {
  395. VectorMA(player->currentOrigin, 400.0f, effectDir, effectPos);
  396. if (PlaySound)
  397. {
  398. G_Sound(player, G_SoundIndex(va("sound/ambience/thunder_close%d", Q_irand(1,2))));
  399. }
  400. // Raise It Up Into The Sky
  401. //--------------------------
  402. effectPos[2] += Q_flrand(600.0f, 1000.0f);
  403. VectorClear(effectDir);
  404. effectDir[2] = -1.0f;
  405. G_PlayEffect("env/huge_lightning", effectPos, effectDir);
  406. ent->nextthink = level.time + Q_irand(100, 200);
  407. }
  408. // Change The Fog Color
  409. //----------------------
  410. if (PlayFlicker)
  411. {
  412. ent->count = (Q_irand(1,4) * 2);
  413. ent->nextthink = level.time + 50;
  414. gi.WE_SetTempGlobalFogColor(ent->pos3);
  415. }
  416. else
  417. {
  418. ent->nextthink = level.time + Q_irand(1000, ent->delay);
  419. }
  420. }
  421. else
  422. {
  423. ent->nextthink = level.time + Q_irand(1000, ent->delay);
  424. }
  425. }
  426. else
  427. {
  428. ent->nextthink = level.time + Q_irand(1000, ent->delay);
  429. }
  430. }
  431. void SP_CreateRain( gentity_t *ent )
  432. {
  433. // Different Types Of Rain
  434. //-------------------------
  435. if (ent->spawnflags & 1)
  436. {
  437. G_FindConfigstringIndex("lightrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  438. }
  439. else if (ent->spawnflags & 2)
  440. {
  441. G_FindConfigstringIndex("rain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  442. }
  443. else if (ent->spawnflags & 4)
  444. {
  445. G_FindConfigstringIndex("heavyrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  446. // Automatically Get Heavy Fog
  447. //-----------------------------
  448. G_FindConfigstringIndex("heavyrainfog", CS_WORLD_FX, MAX_WORLD_FX, qtrue );
  449. // Automatically Get Lightning & Thunder
  450. //---------------------------------------
  451. ent->spawnflags |= 64;
  452. }
  453. else if (ent->spawnflags & 8)
  454. {
  455. G_EffectIndex( "world/acid_fizz" );
  456. G_FindConfigstringIndex("acidrain", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  457. }
  458. // OUTSIDE SHAKE
  459. //===============
  460. if (ent->spawnflags & 16)
  461. {
  462. G_FindConfigstringIndex("outsideShake", CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  463. }
  464. // MISTY FOG
  465. //===========
  466. if (ent->spawnflags & 32)
  467. {
  468. G_FindConfigstringIndex("fog", CS_WORLD_FX, MAX_WORLD_FX, qtrue );
  469. }
  470. // LIGHTNING
  471. //===========
  472. if (ent->spawnflags & 64)
  473. {
  474. G_SoundIndex("sound/ambience/thunder1");
  475. G_SoundIndex("sound/ambience/thunder2");
  476. G_SoundIndex("sound/ambience/thunder3");
  477. G_SoundIndex("sound/ambience/thunder4");
  478. G_SoundIndex("sound/ambience/thunder_close1");
  479. G_SoundIndex("sound/ambience/thunder_close2");
  480. G_EffectIndex( "env/huge_lightning" );
  481. ent->e_ThinkFunc = thinkF_fx_rain_think;
  482. ent->nextthink = level.time + Q_irand(4000, 8000);
  483. if (!G_SpawnVector( "flashcolor", "200 200 200", ent->pos3))
  484. {
  485. VectorSet(ent->pos3, 200, 200, 200);
  486. }
  487. VectorClear(ent->pos2); // the "off" color
  488. G_SpawnInt("flashdelay", "12000", &ent->delay);
  489. G_SpawnInt("chanceflicker", "2", &ent->attackDebounceTime);
  490. G_SpawnInt("chancesound", "3", &ent->pushDebounceTime);
  491. G_SpawnInt("chanceeffect", "4", &ent->aimDebounceTime);
  492. }
  493. }
  494. // Added by Aurelio Reis on 10/20/02.
  495. /*QUAKED fx_puff (1 0 0) (-16 -16 -16) (16 16 16)
  496. This world effect will spawn a puff system globally into the level.
  497. Enter any valid puff command as a key and value to setup the puff
  498. system properties.
  499. "count" The number of puffs/particles (default of 1000).
  500. "whichsystem" Which puff system to use (currently 0 and 1. Default 0).
  501. // Apply a default puff system.
  502. default <value>
  503. Current defaults are "snowstorm", "sandstorm", "foggy", and "smokey"
  504. // Set the color of the particles (0-1.0).
  505. color ( <red>, <green>, <blue> )
  506. default ( 0.5, 0.5, 0.5 )
  507. // Set the alpha (transparency) value for the particles (0-1.0).
  508. alpha <value>
  509. default 0.5
  510. // Set which texture to use for the particles (make sure to include full path).
  511. texture <textures/texture.tga>
  512. default gfx/effects/alpha_smoke2b.tga
  513. // Set the size of particles (from center, like a radius) (MIN 4, MAX 2048).
  514. size <value>
  515. default 100
  516. // Whether the saber should flicker and spark or not (0 false, 1 true).
  517. sabersparks <value>
  518. default 0
  519. // Set texture filtering mode (0 = Bilinear(default), 1 = Nearest(less quality).
  520. filtermode <value>
  521. default 0
  522. // Set the alpha blending mode (0 = src, src-1, 1 = one, one (additive)).
  523. blendmode <value>
  524. default 0
  525. // How much to rotate particles per second (in degree's).
  526. rotate ( <min>, <max> )
  527. default ( 0, 0 )
  528. // Set the area around the player the puffs cover:
  529. spread ( minX minY minZ ) ( maxX maxY maxZ )
  530. default: ( -600 -600 -500 ) ( 600 600 550 )
  531. // Set the random range that sets the speed the puffs fall:
  532. velocity ( minX minY minZ ) ( maxX maxY maxZ )
  533. default: ( -15 -15 -20 ) ( 15 15 -70 )
  534. // Set an area of puff blowing:
  535. wind ( windOriginX windOriginY windOriginZ ) ( windVelocityX windVelocityY windVelocityZ ) ( sizeX sizeY sizeZ )
  536. // Set puff blowing data:
  537. blowing duration <int>
  538. blowing low <int>
  539. default: 3
  540. blowing velocity ( min max )
  541. default: ( 30 70 )
  542. blowing size ( minX minY minZ )
  543. default: ( 1000 300 300 )
  544. */
  545. //----------------------------------------------------------
  546. void SP_CreatePuffSystem( gentity_t *ent )
  547. {
  548. char temp[128];
  549. // Initialize the puff system to either 1000 particles or whatever they choose.
  550. G_SpawnInt( "count", "1000", &ent->count );
  551. cvar_t *r_weatherScale = gi.cvar( "r_weatherScale", "1", CVAR_ARCHIVE );
  552. // See which puff system to use.
  553. int iPuffSystem = 0;
  554. int iVal = 0;
  555. if ( G_SpawnInt( "whichsystem", "0", &iVal ) )
  556. {
  557. iPuffSystem = iVal;
  558. if ( iPuffSystem < 0 || iPuffSystem > 1 )
  559. {
  560. iPuffSystem = 0;
  561. //ri.Error( ERR_DROP, "Weather Effect: Invalid value for whichsystem key" );
  562. Com_Printf( "Weather Effect: Invalid value for whichsystem key\n" );
  563. }
  564. }
  565. if ( r_weatherScale->value > 0.0f )
  566. {
  567. sprintf( temp, "puff%i init %i", iPuffSystem, (int)( ent->count * r_weatherScale->value ));
  568. G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue );
  569. // Should return here??? It didn't originally...
  570. }
  571. // See whether we should have the saber spark from the puff system.
  572. iVal = 0;
  573. G_SpawnInt( "sabersparks", "0", &iVal );
  574. if ( iVal == 1 )
  575. level.worldFlags |= WF_PUFFING;
  576. else
  577. level.worldFlags &= ~WF_PUFFING;
  578. // Go through all the fields and assign the values to the created puff system now.
  579. for ( int i = 0; i < 20; i++ )
  580. {
  581. char *key = NULL;
  582. char *value = NULL;
  583. // Fetch a field.
  584. if ( !G_SpawnField( i, &key, &value ) )
  585. continue;
  586. // Make sure we don't get key's that are worthless.
  587. if ( Q_stricmp( key, "origin" ) == 0 || Q_stricmp( key, "classname" ) == 0 ||
  588. Q_stricmp( key, "count" ) == 0 || Q_stricmp( key, "targetname" ) == 0 ||
  589. Q_stricmp( key, "sabersparks" ) == 0 || Q_stricmp( key, "whichsystem" ) == 0 )
  590. continue;
  591. // Send the command.
  592. _snprintf( temp, 128, "puff%i %s %s", iPuffSystem, key, value );
  593. G_FindConfigstringIndex( temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue );
  594. }
  595. }
  596. // Don't use this! Too powerful! - Aurelio
  597. /*NOTEINUSE! fx_command (1 0 0) (-16 -16 -16) (16 16 16)
  598. //This effect allows you to issue console commands from within the world editor.
  599. //Use the variables c00 to c99 to issue a maximum of 100 console commands.
  600. //example: c00 r_showtris 1
  601. */
  602. //----------------------------------------------------------
  603. /*void SP_Command( gentity_t *ent )
  604. {
  605. char *strCommand;
  606. // Go through all the commands.
  607. for ( int i = 0; i < 100; i++ )
  608. {
  609. strCommand = NULL;
  610. // Fetch a command.
  611. G_SpawnString( va("c%02d", i), NULL, &strCommand );
  612. // If it's valid, issue it.
  613. if ( strCommand && strCommand[0] )
  614. {
  615. gi.SendConsoleCommand( strCommand );
  616. }
  617. }
  618. }*/
  619. //-----------------
  620. // Explosion Trail
  621. //-----------------
  622. //----------------------------------------------------------
  623. void fx_explosion_trail_think( gentity_t *ent )
  624. {
  625. vec3_t origin;
  626. trace_t tr;
  627. if ( ent->spawnflags & 1 ) // gravity
  628. {
  629. ent->s.pos.trType = TR_GRAVITY;
  630. }
  631. else
  632. {
  633. ent->s.pos.trType = TR_LINEAR;
  634. }
  635. EvaluateTrajectory( &ent->s.pos, level.time, origin );
  636. gi.trace( &tr, ent->currentOrigin, vec3_origin, vec3_origin, origin,
  637. ent->owner ? ent->owner->s.number : ENTITYNUM_NONE, ent->clipmask, G2_RETURNONHIT, 10 );
  638. if ( tr.fraction < 1.0f )
  639. {
  640. // never explode or bounce on sky
  641. if ( !( tr.surfaceFlags & SURF_NOIMPACT ))
  642. {
  643. if ( ent->splashDamage && ent->splashRadius )
  644. {
  645. G_RadiusDamage( tr.endpos, ent, ent->splashDamage, ent->splashRadius, ent, MOD_EXPLOSIVE_SPLASH );
  646. }
  647. }
  648. if ( ent->cameraGroup )
  649. {
  650. // fxFile2....in other words, impact fx
  651. G_PlayEffect( ent->cameraGroup, tr.endpos, tr.plane.normal );
  652. }
  653. if ( VALIDSTRING( ent->soundSet ) == true )
  654. {
  655. G_AddEvent( ent, EV_BMODEL_SOUND, CAS_GetBModelSound( ent->soundSet, BMS_END ));
  656. }
  657. G_FreeEntity( ent );
  658. return;
  659. }
  660. G_RadiusDamage( origin, ent, ent->damage, ent->radius, ent, MOD_EXPLOSIVE_SPLASH );
  661. // call the effect with the desired position and orientation
  662. G_PlayEffect( ent->fxID, origin, ent->currentAngles );
  663. ent->nextthink = level.time + 50;
  664. gi.linkentity( ent );
  665. }
  666. //----------------------------------------------------------
  667. void fx_explosion_trail_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  668. {
  669. gentity_t *missile = G_Spawn();
  670. // We aren't a missile in the truest sense, rather we just move through the world and spawn effects
  671. if ( missile )
  672. {
  673. missile->classname = "fx_exp_trail";
  674. missile->nextthink = level.time + 50;
  675. missile->e_ThinkFunc = thinkF_fx_explosion_trail_think;
  676. missile->s.eType = ET_MOVER;
  677. missile->owner = self;
  678. missile->s.modelindex = self->s.modelindex2;
  679. missile->s.pos.trTime = level.time;
  680. G_SetOrigin( missile, self->currentOrigin );
  681. if ( self->spawnflags & 1 ) // gravity
  682. {
  683. missile->s.pos.trType = TR_GRAVITY;
  684. }
  685. else
  686. {
  687. missile->s.pos.trType = TR_LINEAR;
  688. }
  689. missile->spawnflags = self->spawnflags;
  690. G_SetAngles( missile, self->currentAngles );
  691. VectorScale( self->currentAngles, self->speed, missile->s.pos.trDelta );
  692. missile->s.pos.trTime = level.time;
  693. missile->radius = self->radius;
  694. missile->damage = self->damage;
  695. missile->splashDamage = self->splashDamage;
  696. missile->splashRadius = self->splashRadius;
  697. missile->fxID = self->fxID;
  698. missile->cameraGroup = self->cameraGroup; //fxfile2
  699. missile->clipmask = MASK_SHOT;
  700. gi.linkentity( missile );
  701. if ( VALIDSTRING( self->soundSet ) == true )
  702. {
  703. G_AddEvent( self, EV_BMODEL_SOUND, CAS_GetBModelSound( self->soundSet, BMS_START ));
  704. missile->s.loopSound = CAS_GetBModelSound( self->soundSet, BMS_MID );
  705. missile->soundSet = G_NewString(self->soundSet);//get my own copy so i can free it when i die
  706. if ( missile->s.loopSound < 0 )
  707. {
  708. missile->s.loopSound = 0;
  709. }
  710. }
  711. }
  712. }
  713. //----------------------------------------------------------
  714. void fx_explosion_trail_link( gentity_t *ent )
  715. {
  716. vec3_t dir;
  717. gentity_t *target = NULL;
  718. // we ony activate when used
  719. ent->e_UseFunc = useF_fx_explosion_trail_use;
  720. if ( ent->target )
  721. {
  722. // try to use the target to override the orientation
  723. target = G_Find( target, FOFS(targetname), ent->target );
  724. if ( !target )
  725. {
  726. gi.Printf( S_COLOR_RED"ERROR: fx_explosion_trail %s could not find target %s\n", ent->targetname, ent->target );
  727. G_FreeEntity( ent );
  728. return;
  729. }
  730. // Our target is valid so lets use that
  731. VectorSubtract( target->s.origin, ent->s.origin, dir );
  732. VectorNormalize( dir );
  733. }
  734. else
  735. {
  736. // we are assuming that we have angles, but there are no checks to verify this
  737. AngleVectors( ent->s.angles, dir, NULL, NULL );
  738. }
  739. // NOTE: this really isn't an angle, but rather an orientation vector
  740. G_SetAngles( ent, dir );
  741. }
  742. /*QUAKED fx_explosion_trail (0 0 1) (-8 -8 -8) (8 8 8) GRAVITY
  743. Creates an explosion type trail using the specified effect file, damaging things as it moves through the environment
  744. Can also be used for something like a meteor, just add an impact effect ( fxFile2 ) and a splashDamage and splashRadius
  745. GRAVITY - object uses gravity instead of linear motion
  746. "fxFile" - name of the effect to play for the trail ( default "env/exp_trail_comp" )
  747. "fxFile2" - effect file to play on impact
  748. "model" - model to attach to the trail
  749. "target" - direction to aim the trail in, required unless you specify angles
  750. "targetname" - (required) trail effect spawns only when used.
  751. "speed" - velocity through the world, ( default 350 )
  752. "radius" - damage radius around trail as it travels through the world ( default 128 )
  753. "damage" - radius damage ( default 128 )
  754. "splashDamage" - damage when thing impacts ( default 0 )
  755. "splashRadius" - damage radius on impact ( default 0 )
  756. "soundset" - soundset to use, start sound plays when explosion trail starts, loop sound plays on explosion trail, end sound plays when it impacts
  757. */
  758. //----------------------------------------------------------
  759. void SP_fx_explosion_trail( gentity_t *ent )
  760. {
  761. // We have to be useable, otherwise we won't spawn in
  762. if ( !ent->targetname )
  763. {
  764. gi.Printf( S_COLOR_RED"ERROR: fx_explosion_trail at %s has no targetname specified\n", vtos( ent->s.origin ));
  765. G_FreeEntity( ent );
  766. return;
  767. }
  768. // Get our defaults
  769. G_SpawnString( "fxFile", "env/exp_trail_comp", &ent->fxFile );
  770. G_SpawnInt( "damage", "128", &ent->damage );
  771. G_SpawnFloat( "radius", "128", &ent->radius );
  772. G_SpawnFloat( "speed", "350", &ent->speed );
  773. // Try to associate an effect file, unfortunately we won't know if this worked or not until the CGAME trys to register it...
  774. ent->fxID = G_EffectIndex( ent->fxFile );
  775. if ( ent->cameraGroup )
  776. {
  777. G_EffectIndex( ent->cameraGroup );
  778. }
  779. if ( ent->model )
  780. {
  781. ent->s.modelindex2 = G_ModelIndex( ent->model );
  782. }
  783. // Give us a bit of time to spawn in the other entities, since we may have to target one of 'em
  784. ent->e_ThinkFunc = thinkF_fx_explosion_trail_link;
  785. ent->nextthink = level.time + 500;
  786. // Save our position and link us up!
  787. G_SetOrigin( ent, ent->s.origin );
  788. VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
  789. VectorScale( ent->maxs, -1, ent->mins );
  790. gi.linkentity( ent );
  791. }
  792. //
  793. //
  794. //------------------------------------------
  795. void fx_target_beam_set_debounce( gentity_t *self )
  796. {
  797. if ( self->wait >= FRAMETIME )
  798. {
  799. self->attackDebounceTime = level.time + self->wait + Q_irand( -self->random, self->random );
  800. }
  801. else if ( self->wait < 0 )
  802. {
  803. self->e_UseFunc = useF_NULL;
  804. }
  805. else
  806. {
  807. self->attackDebounceTime = level.time + FRAMETIME + Q_irand( -self->random, self->random );
  808. }
  809. }
  810. //------------------------------------------
  811. void fx_target_beam_fire( gentity_t *ent )
  812. {
  813. trace_t trace;
  814. vec3_t dir, org, end;
  815. int ignore;
  816. qboolean open;
  817. if ( !ent->enemy || !ent->enemy->inuse )
  818. {//info_null most likely
  819. ignore = ent->s.number;
  820. ent->enemy = NULL;
  821. VectorCopy( ent->s.origin2, org );
  822. }
  823. else
  824. {
  825. ignore = ent->enemy->s.number;
  826. VectorCopy( ent->enemy->currentOrigin, org );
  827. }
  828. VectorCopy( org, ent->s.origin2 );
  829. VectorSubtract( org, ent->s.origin, dir );
  830. VectorNormalize( dir );
  831. gi.trace( &trace, ent->s.origin, NULL, NULL, org, ENTITYNUM_NONE, MASK_SHOT );//ignore
  832. if ( ent->spawnflags & 2 )
  833. {
  834. open = qtrue;
  835. VectorCopy( org, end );
  836. }
  837. else
  838. {
  839. open = qfalse;
  840. VectorCopy( trace.endpos, end );
  841. }
  842. if ( trace.fraction < 1.0 )
  843. {
  844. if ( trace.entityNum < ENTITYNUM_WORLD )
  845. {
  846. gentity_t *victim = &g_entities[trace.entityNum];
  847. if ( victim && victim->takedamage )
  848. {
  849. if ( ent->spawnflags & 4 ) // NO_KNOCKBACK
  850. {
  851. G_Damage( victim, ent, ent->activator, dir, trace.endpos, ent->damage, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN );
  852. }
  853. else
  854. {
  855. G_Damage( victim, ent, ent->activator, dir, trace.endpos, ent->damage, 0, MOD_UNKNOWN );
  856. }
  857. }
  858. }
  859. }
  860. G_AddEvent( ent, EV_TARGET_BEAM_DRAW, ent->fxID );
  861. VectorCopy( end, ent->s.origin2 );
  862. if ( open )
  863. {
  864. VectorScale( dir, -1, ent->pos1 );
  865. }
  866. else
  867. {
  868. VectorCopy( trace.plane.normal, ent->pos1 );
  869. }
  870. ent->e_ThinkFunc = thinkF_fx_target_beam_think;
  871. ent->nextthink = level.time + FRAMETIME;
  872. }
  873. //------------------------------------------
  874. void fx_target_beam_fire_start( gentity_t *self )
  875. {
  876. fx_target_beam_set_debounce( self );
  877. self->e_ThinkFunc = thinkF_fx_target_beam_think;
  878. self->nextthink = level.time + FRAMETIME;
  879. self->painDebounceTime = level.time + self->speed + Q_irand( -500, 500 );
  880. fx_target_beam_fire( self );
  881. }
  882. //------------------------------------------
  883. void fx_target_beam_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  884. {
  885. if ( self->spawnflags & 8 ) // one shot
  886. {
  887. fx_target_beam_fire( self );
  888. self->e_ThinkFunc = thinkF_NULL;
  889. }
  890. else if ( self->e_ThinkFunc == thinkF_NULL )
  891. {
  892. self->e_ThinkFunc = thinkF_fx_target_beam_think;
  893. self->nextthink = level.time + 50;
  894. }
  895. else
  896. {
  897. self->e_ThinkFunc = thinkF_NULL;
  898. }
  899. self->activator = activator;
  900. }
  901. //------------------------------------------
  902. void fx_target_beam_think( gentity_t *ent )
  903. {
  904. if ( ent->attackDebounceTime > level.time )
  905. {
  906. ent->nextthink = level.time + FRAMETIME;
  907. return;
  908. }
  909. fx_target_beam_fire_start( ent );
  910. }
  911. //------------------------------------------
  912. void fx_target_beam_link( gentity_t *ent )
  913. {
  914. gentity_t *target = NULL;
  915. vec3_t dir;
  916. float len;
  917. target = G_Find( target, FOFS(targetname), ent->target );
  918. if ( !target )
  919. {
  920. Com_Printf( "bolt_link: unable to find target %s\n", ent->target );
  921. G_FreeEntity( ent );
  922. return;
  923. }
  924. ent->attackDebounceTime = level.time;
  925. if ( !target->classname || Q_stricmp( "info_null", target->classname ) )
  926. {//don't want to set enemy to something that's going to free itself... actually, this could be bad in other ways, too... ent pointer could be freed up and re-used by the time we check it next
  927. G_SetEnemy( ent, target );
  928. }
  929. VectorSubtract( target->s.origin, ent->s.origin, dir );//er, does it ever use dir?
  930. len = VectorNormalize( dir );//er, does it use len or dir?
  931. vectoangles( dir, ent->s.angles );//er, does it use s.angles?
  932. VectorCopy( target->s.origin, ent->s.origin2 );
  933. if ( ent->spawnflags & 1 )
  934. {
  935. // Do nothing
  936. ent->e_ThinkFunc = thinkF_NULL;
  937. }
  938. else
  939. {
  940. if ( !(ent->spawnflags & 8 )) // one_shot, only calls when used
  941. {
  942. // switch think functions to avoid doing the bolt_link every time
  943. ent->e_ThinkFunc = thinkF_fx_target_beam_think;
  944. ent->nextthink = level.time + FRAMETIME;
  945. }
  946. }
  947. ent->e_UseFunc = useF_fx_target_beam_use;
  948. gi.linkentity( ent );
  949. }
  950. /*QUAKED fx_target_beam (1 0.5 0.5) (-8 -8 -8) (8 8 8) STARTOFF OPEN NO_KNOCKBACK ONE_SHOT NO_IMPACT
  951. Emits specified effect file, doing damage if required
  952. STARTOFF - must be used before it's on
  953. OPEN - will draw all the way to the target, regardless of where the trace hits
  954. NO_KNOCKBACK - beam damage does no knockback
  955. "fxFile" - Effect used to draw the beam, ( default "env/targ_beam" )
  956. "fxFile2" - Effect used for the beam impact effect, ( default "env/targ_beam_impact" )
  957. "targetname" - Fires only when used
  958. "duration" - How many seconds each burst lasts, -1 will make it stay on forever
  959. "wait" - If always on, how long to wait between blasts, in MILLISECONDS - default/min is 100 (1 frame at 10 fps), -1 means it will never fire again
  960. "random" - random amount of seconds added to/subtracted from "wait" each firing
  961. "damage" - How much damage to inflict PER FRAME (so probably want it kind of low), default is none
  962. "target" - ent to point at- you MUST have this. This can be anything you want, including a moving ent - for static beams, just use info_null
  963. */
  964. //------------------------------------------
  965. void SP_fx_target_beam( gentity_t *ent )
  966. {
  967. G_SetOrigin( ent, ent->s.origin );
  968. ent->speed *= 1000;
  969. ent->wait *= 1000;
  970. ent->random *= 1000;
  971. if ( ent->speed < FRAMETIME )
  972. {
  973. ent->speed = FRAMETIME;
  974. }
  975. G_SpawnInt( "damage", "0", &ent->damage );
  976. G_SpawnString( "fxFile", "env/targ_beam", &ent->fxFile );
  977. if ( ent->spawnflags & 16 ) // NO_IMPACT FX
  978. {
  979. ent->delay = 0;
  980. }
  981. else
  982. {
  983. G_SpawnString( "fxFile2", "env/targ_beam_impact", &ent->cameraGroup );
  984. ent->delay = G_EffectIndex( ent->cameraGroup );
  985. }
  986. ent->fxID = G_EffectIndex( ent->fxFile );
  987. ent->activator = ent;
  988. ent->owner = NULL;
  989. ent->e_ThinkFunc = thinkF_fx_target_beam_link;
  990. ent->nextthink = level.time + START_TIME_LINK_ENTS;
  991. VectorSet( ent->maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
  992. VectorScale( ent->maxs, -1, ent->mins );
  993. gi.linkentity( ent );
  994. }
  995. /*QUAKED fx_cloudlayer (1 0.3 0.5) (-8 -8 -8) (8 8 8) TUBE ALT
  996. Creates a scalable scrolling cloud layer, mostly for bespin undercity but could be used other places
  997. TUBE - creates cloud layer with tube opening in the middle, must an INNER radius also
  998. ALT - uses slightly different shader, good if using two layers sort of close together
  999. "radius" - outer radius of cloud layer, (default 2048)
  1000. "random" - inner radius of cloud layer, (default 128) only works for TUBE type
  1001. "wait" - adds curvature as it moves out to the edge of the layer. ( default 0 ), 1 = small up, 3 = up more, -1 = small down, -3 = down more, etc.
  1002. */
  1003. void SP_fx_cloudlayer( gentity_t *ent )
  1004. {
  1005. // HACK: this effect is never played, rather it just caches the shaders I need cgame side
  1006. G_EffectIndex( "world/haze_cache" );
  1007. G_SpawnFloat( "radius", "2048", &ent->radius );
  1008. G_SpawnFloat( "random", "128", &ent->random );
  1009. G_SpawnFloat( "wait", "0", &ent->wait );
  1010. ent->s.eType = ET_CLOUD; // dumb
  1011. G_SetOrigin( ent, ent->s.origin );
  1012. ent->contents = 0;
  1013. VectorSet( ent->maxs, 200, 200, 200 );
  1014. VectorScale( ent->maxs, -1, ent->mins );
  1015. gi.linkentity( ent );
  1016. }