PageRenderTime 32ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/codemp/game/g_misc.c

https://github.com/Stoiss/jaMME
C | 3661 lines | 2211 code | 579 blank | 871 comment | 403 complexity | 026239744d1ac742a3b4818d6d229b2e MD5 | raw file
Possible License(s): GPL-2.0
  1. // Copyright (C) 1999-2000 Id Software, Inc.
  2. //
  3. // g_misc.c
  4. #include "g_local.h"
  5. #include "ghoul2/G2.h"
  6. #include "ai_main.h" //for the g2animents
  7. #define HOLOCRON_RESPAWN_TIME 30000
  8. #define MAX_AMMO_GIVE 2
  9. #define STATION_RECHARGE_TIME 100
  10. void HolocronThink(gentity_t *ent);
  11. /*QUAKED func_group (0 0 0) ?
  12. Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
  13. */
  14. /*QUAKED info_camp (0 0.5 0) (-4 -4 -4) (4 4 4)
  15. Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
  16. */
  17. void SP_info_camp( gentity_t *self ) {
  18. G_SetOrigin( self, self->s.origin );
  19. }
  20. /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
  21. Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
  22. */
  23. void SP_info_null( gentity_t *self ) {
  24. G_FreeEntity( self );
  25. }
  26. /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
  27. Used as a positional target for in-game calculation, like jumppad targets.
  28. target_position does the same thing
  29. */
  30. void SP_info_notnull( gentity_t *self ){
  31. G_SetOrigin( self, self->s.origin );
  32. }
  33. /*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
  34. Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
  35. "light" overrides the default 300 intensity.
  36. Nonlinear checkbox gives inverse square falloff instead of linear
  37. Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
  38. Lights pointed at a target will be spotlights.
  39. "radius" overrides the default 64 unit radius of a spotlight at the target point.
  40. "fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
  41. */
  42. /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence START_OFF
  43. Non-displayed light.
  44. "light" overrides the default 300 intensity. - affects size
  45. a negative "light" will subtract the light's color
  46. 'Linear' checkbox gives linear falloff instead of inverse square
  47. 'noIncidence' checkbox makes lighting smoother
  48. Lights pointed at a target will be spotlights.
  49. "radius" overrides the default 64 unit radius of a spotlight at the target point.
  50. "scale" multiplier for the light intensity - does not affect size (default 1)
  51. greater than 1 is brighter, between 0 and 1 is dimmer.
  52. "color" sets the light's color
  53. "targetname" to indicate a switchable light - NOTE that all lights with the same targetname will be grouped together and act as one light (ie: don't mix colors, styles or start_off flag)
  54. "style" to specify a specify light style, even for switchable lights!
  55. "style_off" light style to use when switched off (Only for switchable lights)
  56. 1 FLICKER (first variety)
  57. 2 SLOW STRONG PULSE
  58. 3 CANDLE (first variety)
  59. 4 FAST STROBE
  60. 5 GENTLE PULSE 1
  61. 6 FLICKER (second variety)
  62. 7 CANDLE (second variety)
  63. 8 CANDLE (third variety)
  64. 9 SLOW STROBE (fourth variety)
  65. 10 FLUORESCENT FLICKER
  66. 11 SLOW PULSE NOT FADE TO BLACK
  67. 12 FAST PULSE FOR JEREMY
  68. 13 Test Blending
  69. */
  70. static void misc_lightstyle_set ( gentity_t *ent)
  71. {
  72. const int mLightStyle = ent->count;
  73. const int mLightSwitchStyle = ent->bounceCount;
  74. const int mLightOffStyle = ent->fly_sound_debounce_time;
  75. if (!ent->alt_fire)
  76. { //turn off
  77. if (mLightOffStyle) //i have a light style i'd like to use when off
  78. {
  79. char lightstyle[32];
  80. trap_GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+0, lightstyle, 32);
  81. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
  82. trap_GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+1, lightstyle, 32);
  83. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
  84. trap_GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+2, lightstyle, 32);
  85. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
  86. }else
  87. {
  88. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "a");
  89. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "a");
  90. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "a");
  91. }
  92. }
  93. else
  94. { //Turn myself on now
  95. if (mLightSwitchStyle) //i have a light style i'd like to use when on
  96. {
  97. char lightstyle[32];
  98. trap_GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+0, lightstyle, 32);
  99. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
  100. trap_GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+1, lightstyle, 32);
  101. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
  102. trap_GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+2, lightstyle, 32);
  103. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
  104. }
  105. else
  106. {
  107. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "z");
  108. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "z");
  109. trap_SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "z");
  110. }
  111. }
  112. }
  113. void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
  114. {
  115. G_ActivateBehavior(ent,BSET_USE);
  116. ent->alt_fire = !ent->alt_fire; //toggle
  117. misc_lightstyle_set (ent);
  118. }
  119. void SP_light( gentity_t *self ) {
  120. if (!self->targetname )
  121. {//if i don't have a light style switch, the i go away
  122. G_FreeEntity( self );
  123. return;
  124. }
  125. G_SpawnInt( "style", "0", &self->count );
  126. G_SpawnInt( "switch_style", "0", &self->bounceCount );
  127. G_SpawnInt( "style_off", "0", &self->fly_sound_debounce_time );
  128. G_SetOrigin( self, self->s.origin );
  129. trap_LinkEntity( self );
  130. self->use = misc_dlight_use;
  131. self->s.eType = ET_GENERAL;
  132. self->alt_fire = qfalse;
  133. self->r.svFlags |= SVF_NOCLIENT;
  134. if ( !(self->spawnflags & 4) )
  135. { //turn myself on now
  136. self->alt_fire = qtrue;
  137. }
  138. misc_lightstyle_set (self);
  139. }
  140. /*
  141. =================================================================================
  142. TELEPORTERS
  143. =================================================================================
  144. */
  145. void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) {
  146. gentity_t *tent;
  147. qboolean isNPC = qfalse;
  148. if (player->s.eType == ET_NPC)
  149. {
  150. isNPC = qtrue;
  151. }
  152. // use temp events at source and destination to prevent the effect
  153. // from getting dropped by a second player event
  154. if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
  155. tent = G_TempEntity( player->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
  156. tent->s.clientNum = player->s.clientNum;
  157. tent = G_TempEntity( origin, EV_PLAYER_TELEPORT_IN );
  158. tent->s.clientNum = player->s.clientNum;
  159. }
  160. // unlink to make sure it can't possibly interfere with G_KillBox
  161. trap_UnlinkEntity (player);
  162. VectorCopy ( origin, player->client->ps.origin );
  163. player->client->ps.origin[2] += 1;
  164. // spit the player out
  165. AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
  166. VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity );
  167. player->client->ps.pm_time = 160; // hold time
  168. player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
  169. // toggle the teleport bit so the client knows to not lerp
  170. player->client->ps.eFlags ^= EF_TELEPORT_BIT;
  171. // set angles
  172. SetClientViewAngle( player, angles );
  173. // kill anything at the destination
  174. if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
  175. G_KillBox (player);
  176. }
  177. // save results of pmove
  178. BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );
  179. if (isNPC)
  180. {
  181. player->s.eType = ET_NPC;
  182. }
  183. // use the precise origin for linking
  184. VectorCopy( player->client->ps.origin, player->r.currentOrigin );
  185. if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) {
  186. trap_LinkEntity (player);
  187. }
  188. }
  189. /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
  190. Point teleporters at these.
  191. Now that we don't have teleport destination pads, this is just
  192. an info_notnull
  193. */
  194. void SP_misc_teleporter_dest( gentity_t *ent ) {
  195. }
  196. //===========================================================
  197. /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16)
  198. "model" arbitrary .md3 or .ase file to display
  199. turns into map triangles - not solid
  200. */
  201. void SP_misc_model( gentity_t *ent ) {
  202. #if 0
  203. ent->s.modelindex = G_ModelIndex( ent->model );
  204. VectorSet (ent->r.mins, -16, -16, -16);
  205. VectorSet (ent->r.maxs, 16, 16, 16);
  206. trap_LinkEntity (ent);
  207. G_SetOrigin( ent, ent->s.origin );
  208. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  209. #else
  210. G_FreeEntity( ent );
  211. #endif
  212. }
  213. /*QUAKED misc_model_static (1 0 0) (-16 -16 0) (16 16 16)
  214. "model" arbitrary .md3 file to display
  215. "zoffset" units to offset vertical culling position by, can be
  216. negative or positive. This does not affect the actual
  217. position of the model, only the culling position. Use
  218. it for models with stupid origins that go below the
  219. ground and whatnot.
  220. "modelscale" scale on all axis
  221. "modelscale_vec" scale difference axis
  222. loaded as a model in the renderer - does not take up precious
  223. bsp space!
  224. */
  225. void SP_misc_model_static(gentity_t *ent)
  226. {
  227. G_FreeEntity( ent );
  228. }
  229. /*QUAKED misc_model_breakable (1 0 0) (-16 -16 -16) (16 16 16) SOLID AUTOANIMATE DEADSOLID NO_DMODEL NO_SMOKE USE_MODEL USE_NOT_BREAK PLAYER_USE NO_EXPLOSION
  230. SOLID - Movement is blocked by it, if not set, can still be broken by explosions and shots if it has health
  231. AUTOANIMATE - Will cycle it's anim
  232. DEADSOLID - Stay solid even when destroyed (in case damage model is rather large).
  233. NO_DMODEL - Makes it NOT display a damage model when destroyed, even if one exists
  234. USE_MODEL - When used, will toggle to it's usemodel (model name + "_u1.md3")... this obviously does nothing if USE_NOT_BREAK is not checked
  235. USE_NOT_BREAK - Using it, doesn't make it break, still can be destroyed by damage
  236. PLAYER_USE - Player can use it with the use button
  237. NO_EXPLOSION - By default, will explode when it dies...this is your override.
  238. "model" arbitrary .md3 file to display
  239. "health" how much health to have - default is zero (not breakable) If you don't set the SOLID flag, but give it health, it can be shot but will not block NPCs or players from moving
  240. "targetname" when used, dies and displays damagemodel, if any (if not, removes itself)
  241. "target" What to use when it dies
  242. "target2" What to use when it's repaired
  243. "target3" What to use when it's used while it's broken
  244. "paintarget" target to fire when hit (but not destroyed)
  245. "count" the amount of armor/health/ammo given (default 50)
  246. "gravity" if set to 1, this will be affected by gravity
  247. "radius" Chunk code tries to pick a good volume of chunks, but you can alter this to scale the number of spawned chunks. (default 1) (.5) is half as many chunks, (2) is twice as many chunks
  248. Damage: default is none
  249. "splashDamage" - damage to do (will make it explode on death)
  250. "splashRadius" - radius for above damage
  251. "team" - This cannot take damage from members of this team:
  252. "player"
  253. "neutral"
  254. "enemy"
  255. "material" - default is "8 - MAT_NONE" - choose from this list:
  256. 0 = MAT_METAL (grey metal)
  257. 1 = MAT_GLASS
  258. 2 = MAT_ELECTRICAL (sparks only)
  259. 3 = MAT_ELEC_METAL (METAL chunks and sparks)
  260. 4 = MAT_DRK_STONE (brown stone chunks)
  261. 5 = MAT_LT_STONE (tan stone chunks)
  262. 6 = MAT_GLASS_METAL (glass and METAL chunks)
  263. 7 = MAT_METAL2 (blue/grey metal)
  264. 8 = MAT_NONE (no chunks-DEFAULT)
  265. 9 = MAT_GREY_STONE (grey colored stone)
  266. 10 = MAT_METAL3 (METAL and METAL2 chunk combo)
  267. 11 = MAT_CRATE1 (yellow multi-colored crate chunks)
  268. 12 = MAT_GRATE1 (grate chunks--looks horrible right now)
  269. 13 = MAT_ROPE (for yavin_trial, no chunks, just wispy bits )
  270. 14 = MAT_CRATE2 (red multi-colored crate chunks)
  271. 15 = MAT_WHITE_METAL (white angular chunks for Stu, NS_hideout )
  272. FIXME/TODO:
  273. set size better?
  274. multiple damage models?
  275. custom explosion effect/sound?
  276. */
  277. void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor );
  278. void misc_model_breakable_init( gentity_t *ent );
  279. void SP_misc_model_breakable( gentity_t *ent )
  280. {
  281. float grav;
  282. G_SpawnInt( "material", "8", (int*)&ent->material );
  283. G_SpawnFloat( "radius", "1", &ent->radius ); // used to scale chunk code if desired by a designer
  284. misc_model_breakable_init( ent );
  285. if ( !ent->r.mins[0] && !ent->r.mins[1] && !ent->r.mins[2] )
  286. {
  287. VectorSet (ent->r.mins, -16, -16, -16);
  288. }
  289. if ( !ent->r.maxs[0] && !ent->r.maxs[1] && !ent->r.maxs[2] )
  290. {
  291. VectorSet (ent->r.maxs, 16, 16, 16);
  292. }
  293. G_SetOrigin( ent, ent->s.origin );
  294. G_SetAngles( ent, ent->s.angles );
  295. trap_LinkEntity (ent);
  296. if ( ent->spawnflags & 128 )
  297. {//Can be used by the player's BUTTON_USE
  298. ent->r.svFlags |= SVF_PLAYER_USABLE;
  299. }
  300. ent->s.teamowner = 0;
  301. G_SpawnFloat( "gravity", "0", &grav );
  302. if ( grav )
  303. {//affected by gravity
  304. G_SetAngles( ent, ent->s.angles );
  305. G_SetOrigin( ent, ent->r.currentOrigin );
  306. misc_model_breakable_gravity_init( ent, qtrue );
  307. }
  308. }
  309. void misc_model_breakable_gravity_init( gentity_t *ent, qboolean dropToFloor )
  310. {
  311. trace_t tr;
  312. vec3_t top, bottom;
  313. ent->s.eType = ET_GENERAL;
  314. //ent->s.eFlags |= EF_BOUNCE_HALF; // FIXME
  315. ent->clipmask = MASK_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//?
  316. ent->physicsBounce = ent->mass = VectorLength( ent->r.maxs ) + VectorLength( ent->r.mins );
  317. //drop to floor
  318. if ( dropToFloor )
  319. {
  320. VectorCopy( ent->r.currentOrigin, top );
  321. top[2] += 1;
  322. VectorCopy( ent->r.currentOrigin, bottom );
  323. bottom[2] = MIN_WORLD_COORD;
  324. trap_Trace( &tr, top, ent->r.mins, ent->r.maxs, bottom, ent->s.number, MASK_NPCSOLID );
  325. if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 )
  326. {
  327. G_SetOrigin( ent, tr.endpos );
  328. trap_LinkEntity( ent );
  329. }
  330. }
  331. else
  332. {
  333. G_SetOrigin( ent, ent->r.currentOrigin );
  334. trap_LinkEntity( ent );
  335. }
  336. //set up for object thinking
  337. if ( VectorCompare( ent->s.pos.trDelta, vec3_origin ) )
  338. {//not moving
  339. ent->s.pos.trType = TR_STATIONARY;
  340. }
  341. else
  342. {
  343. ent->s.pos.trType = TR_GRAVITY;
  344. }
  345. VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
  346. VectorClear( ent->s.pos.trDelta );
  347. ent->s.pos.trTime = level.time;
  348. if ( VectorCompare( ent->s.apos.trDelta, vec3_origin ) )
  349. {//not moving
  350. ent->s.apos.trType = TR_STATIONARY;
  351. }
  352. else
  353. {
  354. ent->s.apos.trType = TR_LINEAR;
  355. }
  356. VectorCopy( ent->r.currentAngles, ent->s.apos.trBase );
  357. VectorClear( ent->s.apos.trDelta );
  358. ent->s.apos.trTime = level.time;
  359. }
  360. void misc_model_breakable_init( gentity_t *ent )
  361. {
  362. if (!ent->model) {
  363. G_Error("no model set on %s at (%.1f %.1f %.1f)\n", ent->classname, ent->s.origin[0],ent->s.origin[1],ent->s.origin[2]);
  364. }
  365. //Main model
  366. ent->s.modelindex = ent->sound2to1 = G_ModelIndex( ent->model );
  367. if ( ent->spawnflags & 1 )
  368. {//Blocks movement
  369. ent->r.contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
  370. }
  371. else if ( ent->health )
  372. {//Can only be shot
  373. ent->r.contents = CONTENTS_SHOTCLIP;
  374. }
  375. // TODO: fix using
  376. // TODO: fix health/dying funcs
  377. }
  378. /*QUAKED misc_G2model (1 0 0) (-16 -16 -16) (16 16 16)
  379. "model" arbitrary .glm file to display
  380. */
  381. void SP_misc_G2model( gentity_t *ent ) {
  382. #if 0
  383. char name1[200] = "models/players/kyle/modelmp.glm";
  384. trap_G2API_InitGhoul2Model(&ent->s, name1, G_ModelIndex( name1 ), 0, 0, 0, 0);
  385. trap_G2API_SetBoneAnim(ent->s.ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1);
  386. ent->s.radius = 150;
  387. // VectorSet (ent->r.mins, -16, -16, -16);
  388. // VectorSet (ent->r.maxs, 16, 16, 16);
  389. trap_LinkEntity (ent);
  390. G_SetOrigin( ent, ent->s.origin );
  391. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  392. #else
  393. G_FreeEntity( ent );
  394. #endif
  395. }
  396. //===========================================================
  397. void locateCamera( gentity_t *ent ) {
  398. vec3_t dir;
  399. gentity_t *target;
  400. gentity_t *owner;
  401. owner = G_PickTarget( ent->target );
  402. if ( !owner ) {
  403. G_Printf( "Couldn't find target for misc_partal_surface\n" );
  404. G_FreeEntity( ent );
  405. return;
  406. }
  407. ent->r.ownerNum = owner->s.number;
  408. // frame holds the rotate speed
  409. if ( owner->spawnflags & 1 ) {
  410. ent->s.frame = 25;
  411. } else if ( owner->spawnflags & 2 ) {
  412. ent->s.frame = 75;
  413. }
  414. // swing camera ?
  415. if ( owner->spawnflags & 4 ) {
  416. // set to 0 for no rotation at all
  417. ent->s.powerups = 0;
  418. }
  419. else {
  420. ent->s.powerups = 1;
  421. }
  422. // clientNum holds the rotate offset
  423. ent->s.clientNum = owner->s.clientNum;
  424. VectorCopy( owner->s.origin, ent->s.origin2 );
  425. // see if the portal_camera has a target
  426. target = G_PickTarget( owner->target );
  427. if ( target ) {
  428. VectorSubtract( target->s.origin, owner->s.origin, dir );
  429. VectorNormalize( dir );
  430. } else {
  431. G_SetMovedir( owner->s.angles, dir );
  432. }
  433. ent->s.eventParm = DirToByte( dir );
  434. }
  435. /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
  436. The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
  437. This must be within 64 world units of the surface!
  438. */
  439. void SP_misc_portal_surface(gentity_t *ent) {
  440. VectorClear( ent->r.mins );
  441. VectorClear( ent->r.maxs );
  442. trap_LinkEntity (ent);
  443. ent->r.svFlags = SVF_PORTAL;
  444. ent->s.eType = ET_PORTAL;
  445. if ( !ent->target ) {
  446. VectorCopy( ent->s.origin, ent->s.origin2 );
  447. } else {
  448. ent->think = locateCamera;
  449. ent->nextthink = level.time + 100;
  450. }
  451. }
  452. /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing
  453. The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view.
  454. "roll" an angle modifier to orient the camera around the target vector;
  455. */
  456. void SP_misc_portal_camera(gentity_t *ent) {
  457. float roll;
  458. VectorClear( ent->r.mins );
  459. VectorClear( ent->r.maxs );
  460. trap_LinkEntity (ent);
  461. G_SpawnFloat( "roll", "0", &roll );
  462. ent->s.clientNum = roll/360.0 * 256;
  463. }
  464. /*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16)
  465. "bspmodel" arbitrary .bsp file to display
  466. */
  467. void SP_misc_bsp(gentity_t *ent)
  468. {
  469. char temp[MAX_QPATH];
  470. char *out;
  471. float newAngle;
  472. int tempint;
  473. G_SpawnFloat( "angle", "0", &newAngle );
  474. if (newAngle != 0.0)
  475. {
  476. ent->s.angles[1] = newAngle;
  477. }
  478. // don't support rotation any other way
  479. ent->s.angles[0] = 0.0;
  480. ent->s.angles[2] = 0.0;
  481. G_SpawnString("bspmodel", "", &out);
  482. ent->s.eFlags = EF_PERMANENT;
  483. // Mainly for debugging
  484. G_SpawnInt( "spacing", "0", &tempint);
  485. ent->s.time2 = tempint;
  486. G_SpawnInt( "flatten", "0", &tempint);
  487. ent->s.time = tempint;
  488. Com_sprintf(temp, MAX_QPATH, "#%s", out);
  489. trap_SetBrushModel( ent, temp ); // SV_SetBrushModel -- sets mins and maxs
  490. G_BSPIndex(temp);
  491. level.mNumBSPInstances++;
  492. Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances);
  493. VectorCopy(ent->s.origin, level.mOriginAdjust);
  494. level.mRotationAdjust = ent->s.angles[1];
  495. level.mTargetAdjust = temp;
  496. //level.hasBspInstances = qtrue; //rww - also not referenced anywhere.
  497. level.mBSPInstanceDepth++;
  498. /*
  499. G_SpawnString("filter", "", &out);
  500. strcpy(level.mFilter, out);
  501. */
  502. G_SpawnString("teamfilter", "", &out);
  503. strcpy(level.mTeamFilter, out);
  504. VectorCopy( ent->s.origin, ent->s.pos.trBase );
  505. VectorCopy( ent->s.origin, ent->r.currentOrigin );
  506. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  507. VectorCopy( ent->s.angles, ent->r.currentAngles );
  508. ent->s.eType = ET_MOVER;
  509. trap_LinkEntity (ent);
  510. trap_SetActiveSubBSP(ent->s.modelindex);
  511. G_SpawnEntitiesFromString(qtrue);
  512. trap_SetActiveSubBSP(-1);
  513. level.mBSPInstanceDepth--;
  514. //level.mFilter[0] = level.mTeamFilter[0] = 0;
  515. level.mTeamFilter[0] = 0;
  516. /*
  517. if ( g_debugRMG.integer )
  518. {
  519. G_SpawnDebugCylinder ( ent->s.origin, ent->s.time2, &g_entities[0], 2000, COLOR_WHITE );
  520. if ( ent->s.time )
  521. {
  522. G_SpawnDebugCylinder ( ent->s.origin, ent->s.time, &g_entities[0], 2000, COLOR_RED );
  523. }
  524. }
  525. */
  526. }
  527. /*QUAKED terrain (1.0 1.0 1.0) ? NOVEHDMG
  528. NOVEHDMG - don't damage vehicles upon impact with this terrain
  529. Terrain entity
  530. It will stretch to the full height of the brush
  531. numPatches - integer number of patches to split the terrain brush into (default 200)
  532. terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8)
  533. seed - integer seed for random terrain generation (default 0)
  534. textureScale - float scale of texture (default 0.005)
  535. heightmap - name of heightmap data image to use, located in heightmaps/*.png. (must be PNG format)
  536. terrainDef - defines how the game textures the terrain (file is base/ext_data/rmg/*.terrain - default is grassyhills)
  537. instanceDef - defines which bsp instances appear
  538. miscentDef - defines which client models spawn on the terrain (file is base/ext_data/rmg/*.miscents)
  539. densityMap - how dense the client models are packed
  540. */
  541. void AddSpawnField(char *field, char *value);
  542. #define MAX_INSTANCE_TYPES 16
  543. void SP_terrain(gentity_t *ent)
  544. {
  545. char temp[MAX_INFO_STRING];
  546. char final[MAX_QPATH];
  547. char seed[MAX_QPATH];
  548. char missionType[MAX_QPATH];
  549. //char soundSet[MAX_QPATH];
  550. int shaderNum, i;
  551. char *value;
  552. int terrainID;
  553. //Force it to 1 when there is terrain on the level.
  554. trap_Cvar_Set("RMG", "1");
  555. RMG.integer = 1;
  556. VectorClear (ent->s.angles);
  557. trap_SetBrushModel( ent, ent->model );
  558. // Get the shader from the top of the brush
  559. // shaderNum = gi.CM_GetShaderNum(s.modelindex);
  560. shaderNum = 0;
  561. if (RMG.integer)
  562. {
  563. /*
  564. // Grab the default terrain file from the RMG cvar
  565. trap_Cvar_VariableStringBuffer("RMG_terrain", temp, MAX_QPATH);
  566. Com_sprintf(final, MAX_QPATH, "%s", temp);
  567. AddSpawnField("terrainDef", temp);
  568. trap_Cvar_VariableStringBuffer("RMG_instances", temp, MAX_QPATH);
  569. Com_sprintf(final, MAX_QPATH, "%s", temp);
  570. AddSpawnField("instanceDef", temp);
  571. trap_Cvar_VariableStringBuffer("RMG_miscents", temp, MAX_QPATH);
  572. Com_sprintf(final, MAX_QPATH, "%s", temp);
  573. AddSpawnField("miscentDef", temp);
  574. */
  575. //rww - disabled for now, don't want cvar overrides.
  576. trap_Cvar_VariableStringBuffer("RMG_seed", seed, MAX_QPATH);
  577. trap_Cvar_VariableStringBuffer("RMG_mission", missionType, MAX_QPATH);
  578. //rww - May want to implement these at some point.
  579. //trap_Cvar_VariableStringBuffer("RMG_soundset", soundSet, MAX_QPATH);
  580. //trap_SetConfigstring(CS_AMBIENT_SOUNDSETS, soundSet );
  581. }
  582. // Get info required for the common init
  583. temp[0] = 0;
  584. G_SpawnString("heightmap", "", &value);
  585. Info_SetValueForKey(temp, "heightMap", value);
  586. G_SpawnString("numpatches", "400", &value);
  587. Info_SetValueForKey(temp, "numPatches", va("%d", atoi(value)));
  588. G_SpawnString("terxels", "4", &value);
  589. Info_SetValueForKey(temp, "terxels", va("%d", atoi(value)));
  590. Info_SetValueForKey(temp, "seed", seed);
  591. Info_SetValueForKey(temp, "minx", va("%f", ent->r.mins[0]));
  592. Info_SetValueForKey(temp, "miny", va("%f", ent->r.mins[1]));
  593. Info_SetValueForKey(temp, "minz", va("%f", ent->r.mins[2]));
  594. Info_SetValueForKey(temp, "maxx", va("%f", ent->r.maxs[0]));
  595. Info_SetValueForKey(temp, "maxy", va("%f", ent->r.maxs[1]));
  596. Info_SetValueForKey(temp, "maxz", va("%f", ent->r.maxs[2]));
  597. Info_SetValueForKey(temp, "modelIndex", va("%d", ent->s.modelindex));
  598. G_SpawnString("terraindef", "grassyhills", &value);
  599. Info_SetValueForKey(temp, "terrainDef", value);
  600. G_SpawnString("instancedef", "", &value);
  601. Info_SetValueForKey(temp, "instanceDef", value);
  602. G_SpawnString("miscentdef", "", &value);
  603. Info_SetValueForKey(temp, "miscentDef", value);
  604. Info_SetValueForKey(temp, "missionType", missionType);
  605. for(i = 0; i < MAX_INSTANCE_TYPES; i++)
  606. {
  607. trap_Cvar_VariableStringBuffer(va("RMG_instance%d", i), final, MAX_QPATH);
  608. if(strlen(final))
  609. {
  610. Info_SetValueForKey(temp, va("inst%d", i), final);
  611. }
  612. }
  613. // Set additional data required on the client only
  614. G_SpawnString("densitymap", "", &value);
  615. Info_SetValueForKey(temp, "densityMap", value);
  616. Info_SetValueForKey(temp, "shader", va("%d", shaderNum));
  617. G_SpawnString("texturescale", "0.005", &value);
  618. Info_SetValueForKey(temp, "texturescale", va("%f", atof(value)));
  619. // Initialise the common aspects of the terrain
  620. terrainID = trap_CM_RegisterTerrain(temp);
  621. // SetCommon(common);
  622. Info_SetValueForKey(temp, "terrainId", va("%d", terrainID));
  623. // Let the entity know if it is random generated or not
  624. // SetIsRandom(common->GetIsRandom());
  625. // Let the game remember everything
  626. //level.landScapes[terrainID] = ent; //rww - also not referenced
  627. // Send all the data down to the client
  628. trap_SetConfigstring(CS_TERRAINS + terrainID, temp);
  629. // Make sure the contents are properly set
  630. ent->r.contents = CONTENTS_TERRAIN;
  631. ent->r.svFlags = SVF_NOCLIENT;
  632. ent->s.eFlags = EF_PERMANENT;
  633. ent->s.eType = ET_TERRAIN;
  634. // Hook into the world so physics will work
  635. trap_LinkEntity(ent);
  636. // If running RMG then initialize the terrain and handle team skins
  637. if ( RMG.integer )
  638. {
  639. trap_RMG_Init(terrainID);
  640. /*
  641. if ( level.gametypeData->teams )
  642. {
  643. char temp[MAX_QPATH];
  644. // Red team change from RMG ?
  645. trap_GetConfigstring ( CS_GAMETYPE_REDTEAM, temp, MAX_QPATH );
  646. if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_RED] ) )
  647. {
  648. level.gametypeTeam[TEAM_RED] = trap_VM_LocalStringAlloc ( temp );
  649. }
  650. // Blue team change from RMG ?
  651. trap_GetConfigstring ( CS_GAMETYPE_BLUETEAM, temp, MAX_QPATH );
  652. if ( Q_stricmp ( temp, level.gametypeTeam[TEAM_BLUE] ) )
  653. {
  654. level.gametypeTeam[TEAM_BLUE] = trap_VM_LocalStringAlloc ( temp );
  655. }
  656. }
  657. */
  658. }
  659. }
  660. //rww - Called by skyportal entities. This will check through entities and flag them
  661. //as portal ents if they are in the same pvs as a skyportal entity and pass
  662. //a direct point trace check between origins. I really wanted to use an eFlag for
  663. //flagging portal entities, but too many entities like to reset their eFlags.
  664. //Note that this was not part of the original wolf sky portal stuff.
  665. void G_PortalifyEntities(gentity_t *ent)
  666. {
  667. int i = 0;
  668. gentity_t *scan = NULL;
  669. while (i < MAX_GENTITIES)
  670. {
  671. scan = &g_entities[i];
  672. if (scan && scan->inuse && scan->s.number != ent->s.number && trap_InPVS(ent->s.origin, scan->r.currentOrigin))
  673. {
  674. trace_t tr;
  675. trap_Trace(&tr, ent->s.origin, vec3_origin, vec3_origin, scan->r.currentOrigin, ent->s.number, CONTENTS_SOLID);
  676. if (tr.fraction == 1.0 || (tr.entityNum == scan->s.number && tr.entityNum != ENTITYNUM_NONE && tr.entityNum != ENTITYNUM_WORLD))
  677. {
  678. if (!scan->client || scan->s.eType == ET_NPC)
  679. { //making a client a portal entity would be bad.
  680. scan->s.isPortalEnt = qtrue; //he's flagged now
  681. }
  682. }
  683. }
  684. i++;
  685. }
  686. ent->think = G_FreeEntity; //the portal entity is no longer needed because its information is stored in a config string.
  687. ent->nextthink = level.time;
  688. }
  689. /*QUAKED misc_skyportal_orient (.6 .7 .7) (-8 -8 0) (8 8 16)
  690. point from which to orient the sky portal cam in relation
  691. to the regular view position.
  692. "modelscale" the scale at which to scale positions
  693. */
  694. void SP_misc_skyportal_orient (gentity_t *ent)
  695. {
  696. G_FreeEntity(ent);
  697. }
  698. /*QUAKED misc_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16)
  699. "fov" for the skybox default is 80
  700. To have the portal sky fogged, enter any of the following values:
  701. "onlyfoghere" if non-0 allows you to set a global fog, but will only use that fog within this sky portal.
  702. Also note that entities in the same PVS and visible (via point trace) from this
  703. object will be flagged as portal entities. This means they will be sent and
  704. updated from the server for every client every update regardless of where
  705. they are, and they will essentially be added to the scene twice if the client
  706. is in the same PVS as them (only once otherwise, but still once no matter
  707. where the client is). In other words, don't go overboard with it or everything
  708. will explode.
  709. */
  710. void SP_misc_skyportal (gentity_t *ent)
  711. {
  712. char *fov;
  713. vec3_t fogv; //----(SA)
  714. int fogn; //----(SA)
  715. int fogf; //----(SA)
  716. int isfog = 0; // (SA)
  717. float fov_x;
  718. G_SpawnString ("fov", "80", &fov);
  719. fov_x = atof (fov);
  720. isfog += G_SpawnVector ("fogcolor", "0 0 0", fogv);
  721. isfog += G_SpawnInt ("fognear", "0", &fogn);
  722. isfog += G_SpawnInt ("fogfar", "300", &fogf);
  723. trap_SetConfigstring( CS_SKYBOXORG, va("%.2f %.2f %.2f %.1f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], fov_x, (int)isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) );
  724. ent->think = G_PortalifyEntities;
  725. ent->nextthink = level.time + 1050; //give it some time first so that all other entities are spawned.
  726. }
  727. /*QUAKED misc_holocron (0 0 1) (-8 -8 -8) (8 8 8)
  728. count Set to type of holocron (based on force power value)
  729. HEAL = 0
  730. JUMP = 1
  731. SPEED = 2
  732. PUSH = 3
  733. PULL = 4
  734. TELEPATHY = 5
  735. GRIP = 6
  736. LIGHTNING = 7
  737. RAGE = 8
  738. PROTECT = 9
  739. ABSORB = 10
  740. TEAM HEAL = 11
  741. TEAM FORCE = 12
  742. DRAIN = 13
  743. SEE = 14
  744. SABERATTACK = 15
  745. SABERDEFEND = 16
  746. SABERTHROW = 17
  747. */
  748. /*char *holocronTypeModels[] = {
  749. "models/chunks/rock/rock_big.md3",//FP_HEAL,
  750. "models/chunks/rock/rock_big.md3",//FP_LEVITATION,
  751. "models/chunks/rock/rock_big.md3",//FP_SPEED,
  752. "models/chunks/rock/rock_big.md3",//FP_PUSH,
  753. "models/chunks/rock/rock_big.md3",//FP_PULL,
  754. "models/chunks/rock/rock_big.md3",//FP_TELEPATHY,
  755. "models/chunks/rock/rock_big.md3",//FP_GRIP,
  756. "models/chunks/rock/rock_big.md3",//FP_LIGHTNING,
  757. "models/chunks/rock/rock_big.md3",//FP_RAGE,
  758. "models/chunks/rock/rock_big.md3",//FP_PROTECT,
  759. "models/chunks/rock/rock_big.md3",//FP_ABSORB,
  760. "models/chunks/rock/rock_big.md3",//FP_TEAM_HEAL,
  761. "models/chunks/rock/rock_big.md3",//FP_TEAM_FORCE,
  762. "models/chunks/rock/rock_big.md3",//FP_DRAIN,
  763. "models/chunks/rock/rock_big.md3",//FP_SEE
  764. "models/chunks/rock/rock_big.md3",//FP_SABER_OFFENSE
  765. "models/chunks/rock/rock_big.md3",//FP_SABER_DEFENSE
  766. "models/chunks/rock/rock_big.md3"//FP_SABERTHROW
  767. };*/
  768. void HolocronRespawn(gentity_t *self)
  769. {
  770. self->s.modelindex = (self->count - 128);
  771. }
  772. void HolocronPopOut(gentity_t *self)
  773. {
  774. if (Q_irand(1, 10) < 5)
  775. {
  776. self->s.pos.trDelta[0] = 150 + Q_irand(1, 100);
  777. }
  778. else
  779. {
  780. self->s.pos.trDelta[0] = -150 - Q_irand(1, 100);
  781. }
  782. if (Q_irand(1, 10) < 5)
  783. {
  784. self->s.pos.trDelta[1] = 150 + Q_irand(1, 100);
  785. }
  786. else
  787. {
  788. self->s.pos.trDelta[1] = -150 - Q_irand(1, 100);
  789. }
  790. self->s.pos.trDelta[2] = 150 + Q_irand(1, 100);
  791. }
  792. void HolocronTouch(gentity_t *self, gentity_t *other, trace_t *trace)
  793. {
  794. int i = 0;
  795. int othercarrying = 0;
  796. float time_lowest = 0;
  797. int index_lowest = -1;
  798. int hasall = 1;
  799. int forceReselect = WP_NONE;
  800. if (trace)
  801. {
  802. self->s.groundEntityNum = trace->entityNum;
  803. }
  804. if (!other || !other->client || other->health < 1)
  805. {
  806. return;
  807. }
  808. if (!self->s.modelindex)
  809. {
  810. return;
  811. }
  812. if (self->enemy)
  813. {
  814. return;
  815. }
  816. if (other->client->ps.holocronsCarried[self->count])
  817. {
  818. return;
  819. }
  820. if (other->client->ps.holocronCantTouch == self->s.number && other->client->ps.holocronCantTouchTime > level.time)
  821. {
  822. return;
  823. }
  824. while (i < NUM_FORCE_POWERS)
  825. {
  826. if (other->client->ps.holocronsCarried[i])
  827. {
  828. othercarrying++;
  829. if (index_lowest == -1 || other->client->ps.holocronsCarried[i] < time_lowest)
  830. {
  831. index_lowest = i;
  832. time_lowest = other->client->ps.holocronsCarried[i];
  833. }
  834. }
  835. else if (i != self->count)
  836. {
  837. hasall = 0;
  838. }
  839. i++;
  840. }
  841. if (hasall)
  842. { //once we pick up this holocron we'll have all of them, so give us super special best prize!
  843. //G_Printf("You deserve a pat on the back.\n");
  844. }
  845. if (!(other->client->ps.fd.forcePowersActive & (1 << other->client->ps.fd.forcePowerSelected)))
  846. { //If the player isn't using his currently selected force power, select this one
  847. if (self->count != FP_SABER_OFFENSE && self->count != FP_SABER_DEFENSE && self->count != FP_SABERTHROW && self->count != FP_LEVITATION)
  848. {
  849. other->client->ps.fd.forcePowerSelected = self->count;
  850. }
  851. }
  852. if (g_maxHolocronCarry.integer && othercarrying >= g_maxHolocronCarry.integer)
  853. { //make the oldest holocron carried by the player pop out to make room for this one
  854. other->client->ps.holocronsCarried[index_lowest] = 0;
  855. /*
  856. if (index_lowest == FP_SABER_OFFENSE && !HasSetSaberOnly())
  857. { //you lost your saberattack holocron, so no more saber for you
  858. other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_STUN_BATON);
  859. other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER);
  860. if (other->client->ps.weapon == WP_SABER)
  861. {
  862. forceReselect = WP_SABER;
  863. }
  864. }
  865. */
  866. //NOTE: No longer valid as we are now always giving a force level 1 saber attack level in holocron
  867. }
  868. //G_Sound(other, CHAN_AUTO, G_SoundIndex("sound/weapons/w_pkup.wav"));
  869. G_AddEvent( other, EV_ITEM_PICKUP, self->s.number );
  870. other->client->ps.holocronsCarried[self->count] = level.time;
  871. self->s.modelindex = 0;
  872. self->enemy = other;
  873. self->pos2[0] = 1;
  874. self->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME;
  875. /*
  876. if (self->count == FP_SABER_OFFENSE && !HasSetSaberOnly())
  877. { //player gets a saber
  878. other->client->ps.stats[STAT_WEAPONS] |= (1 << WP_SABER);
  879. other->client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_STUN_BATON);
  880. if (other->client->ps.weapon == WP_STUN_BATON)
  881. {
  882. forceReselect = WP_STUN_BATON;
  883. }
  884. }
  885. */
  886. if (forceReselect != WP_NONE)
  887. {
  888. G_AddEvent(other, EV_NOAMMO, forceReselect);
  889. }
  890. //G_Printf("DON'T TOUCH ME\n");
  891. }
  892. void HolocronThink(gentity_t *ent)
  893. {
  894. if (ent->pos2[0] && (!ent->enemy || !ent->enemy->client || ent->enemy->health < 1))
  895. {
  896. if (ent->enemy && ent->enemy->client)
  897. {
  898. HolocronRespawn(ent);
  899. VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase);
  900. VectorCopy(ent->enemy->client->ps.origin, ent->s.origin);
  901. VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin);
  902. //copy to person carrying's origin before popping out of them
  903. HolocronPopOut(ent);
  904. ent->enemy->client->ps.holocronsCarried[ent->count] = 0;
  905. ent->enemy = NULL;
  906. goto justthink;
  907. }
  908. }
  909. else if (ent->pos2[0] && ent->enemy && ent->enemy->client)
  910. {
  911. ent->pos2[1] = level.time + HOLOCRON_RESPAWN_TIME;
  912. }
  913. if (ent->enemy && ent->enemy->client)
  914. {
  915. if (!ent->enemy->client->ps.holocronsCarried[ent->count])
  916. {
  917. ent->enemy->client->ps.holocronCantTouch = ent->s.number;
  918. ent->enemy->client->ps.holocronCantTouchTime = level.time + 5000;
  919. HolocronRespawn(ent);
  920. VectorCopy(ent->enemy->client->ps.origin, ent->s.pos.trBase);
  921. VectorCopy(ent->enemy->client->ps.origin, ent->s.origin);
  922. VectorCopy(ent->enemy->client->ps.origin, ent->r.currentOrigin);
  923. //copy to person carrying's origin before popping out of them
  924. HolocronPopOut(ent);
  925. ent->enemy = NULL;
  926. goto justthink;
  927. }
  928. if (!ent->enemy->inuse || (ent->enemy->client && ent->enemy->client->ps.fallingToDeath))
  929. {
  930. if (ent->enemy->inuse && ent->enemy->client)
  931. {
  932. ent->enemy->client->ps.holocronBits &= ~(1 << ent->count);
  933. ent->enemy->client->ps.holocronsCarried[ent->count] = 0;
  934. }
  935. ent->enemy = NULL;
  936. HolocronRespawn(ent);
  937. VectorCopy(ent->s.origin2, ent->s.pos.trBase);
  938. VectorCopy(ent->s.origin2, ent->s.origin);
  939. VectorCopy(ent->s.origin2, ent->r.currentOrigin);
  940. ent->s.pos.trTime = level.time;
  941. ent->pos2[0] = 0;
  942. trap_LinkEntity(ent);
  943. goto justthink;
  944. }
  945. }
  946. if (ent->pos2[0] && ent->pos2[1] < level.time)
  947. { //isn't in original place and has been there for (HOLOCRON_RESPAWN_TIME) seconds without being picked up, so respawn
  948. VectorCopy(ent->s.origin2, ent->s.pos.trBase);
  949. VectorCopy(ent->s.origin2, ent->s.origin);
  950. VectorCopy(ent->s.origin2, ent->r.currentOrigin);
  951. ent->s.pos.trTime = level.time;
  952. ent->pos2[0] = 0;
  953. trap_LinkEntity(ent);
  954. }
  955. justthink:
  956. ent->nextthink = level.time + 50;
  957. if (ent->s.pos.trDelta[0] || ent->s.pos.trDelta[1] || ent->s.pos.trDelta[2])
  958. {
  959. G_RunObject(ent);
  960. }
  961. }
  962. void SP_misc_holocron(gentity_t *ent)
  963. {
  964. vec3_t dest;
  965. trace_t tr;
  966. if (level.gametype != GT_HOLOCRON)
  967. {
  968. G_FreeEntity(ent);
  969. return;
  970. }
  971. if (HasSetSaberOnly())
  972. {
  973. if (ent->count == FP_SABER_OFFENSE ||
  974. ent->count == FP_SABER_DEFENSE ||
  975. ent->count == FP_SABERTHROW)
  976. { //having saber holocrons in saber only mode is pointless
  977. G_FreeEntity(ent);
  978. return;
  979. }
  980. }
  981. ent->s.isJediMaster = qtrue;
  982. VectorSet( ent->r.maxs, 8, 8, 8 );
  983. VectorSet( ent->r.mins, -8, -8, -8 );
  984. ent->s.origin[2] += 0.1f;
  985. ent->r.maxs[2] -= 0.1f;
  986. VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
  987. trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
  988. if ( tr.startsolid )
  989. {
  990. G_Printf ("SP_misc_holocron: misc_holocron startsolid at %s\n", vtos(ent->s.origin));
  991. G_FreeEntity( ent );
  992. return;
  993. }
  994. //add the 0.1 back after the trace
  995. ent->r.maxs[2] += 0.1f;
  996. // allow to ride movers
  997. // ent->s.groundEntityNum = tr.entityNum;
  998. G_SetOrigin( ent, tr.endpos );
  999. if (ent->count < 0)
  1000. {
  1001. ent->count = 0;
  1002. }
  1003. if (ent->count >= NUM_FORCE_POWERS)
  1004. {
  1005. ent->count = NUM_FORCE_POWERS-1;
  1006. }
  1007. /*
  1008. if (g_forcePowerDisable.integer &&
  1009. (g_forcePowerDisable.integer & (1 << ent->count)))
  1010. {
  1011. G_FreeEntity(ent);
  1012. return;
  1013. }
  1014. */
  1015. //No longer doing this, causing too many complaints about accidentally setting no force powers at all
  1016. //and starting a holocron game (making it basically just FFA)
  1017. ent->enemy = NULL;
  1018. ent->flags = FL_BOUNCE_HALF;
  1019. ent->s.modelindex = (ent->count - 128);//G_ModelIndex(holocronTypeModels[ent->count]);
  1020. ent->s.eType = ET_HOLOCRON;
  1021. ent->s.pos.trType = TR_GRAVITY;
  1022. ent->s.pos.trTime = level.time;
  1023. ent->r.contents = CONTENTS_TRIGGER;
  1024. ent->clipmask = MASK_SOLID;
  1025. ent->s.trickedentindex4 = ent->count;
  1026. if (forcePowerDarkLight[ent->count] == FORCE_DARKSIDE)
  1027. {
  1028. ent->s.trickedentindex3 = 1;
  1029. }
  1030. else if (forcePowerDarkLight[ent->count] == FORCE_LIGHTSIDE)
  1031. {
  1032. ent->s.trickedentindex3 = 2;
  1033. }
  1034. else
  1035. {
  1036. ent->s.trickedentindex3 = 3;
  1037. }
  1038. ent->physicsObject = qtrue;
  1039. VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot
  1040. ent->touch = HolocronTouch;
  1041. trap_LinkEntity(ent);
  1042. ent->think = HolocronThink;
  1043. ent->nextthink = level.time + 50;
  1044. }
  1045. /*
  1046. ======================================================================
  1047. SHOOTERS
  1048. ======================================================================
  1049. */
  1050. void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
  1051. vec3_t dir;
  1052. float deg;
  1053. vec3_t up, right;
  1054. // see if we have a target
  1055. if ( ent->enemy ) {
  1056. VectorSubtract( ent->enemy->r.currentOrigin, ent->s.origin, dir );
  1057. VectorNormalize( dir );
  1058. } else {
  1059. VectorCopy( ent->movedir, dir );
  1060. }
  1061. // randomize a bit
  1062. PerpendicularVector( up, dir );
  1063. CrossProduct( up, dir, right );
  1064. deg = crandom() * ent->random;
  1065. VectorMA( dir, deg, up, dir );
  1066. deg = crandom() * ent->random;
  1067. VectorMA( dir, deg, right, dir );
  1068. VectorNormalize( dir );
  1069. switch ( ent->s.weapon ) {
  1070. case WP_BLASTER:
  1071. WP_FireBlasterMissile( ent, ent->s.origin, dir, qfalse );
  1072. break;
  1073. }
  1074. G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
  1075. }
  1076. static void InitShooter_Finish( gentity_t *ent ) {
  1077. ent->enemy = G_PickTarget( ent->target );
  1078. ent->think = 0;
  1079. ent->nextthink = 0;
  1080. }
  1081. void InitShooter( gentity_t *ent, int weapon ) {
  1082. ent->use = Use_Shooter;
  1083. ent->s.weapon = weapon;
  1084. RegisterItem( BG_FindItemForWeapon( weapon ) );
  1085. G_SetMovedir( ent->s.angles, ent->movedir );
  1086. if ( !ent->random ) {
  1087. ent->random = 1.0;
  1088. }
  1089. ent->random = sin( M_PI * ent->random / 180 );
  1090. // target might be a moving object, so we can't set movedir for it
  1091. if ( ent->target ) {
  1092. ent->think = InitShooter_Finish;
  1093. ent->nextthink = level.time + 500;
  1094. }
  1095. trap_LinkEntity( ent );
  1096. }
  1097. /*QUAKED shooter_blaster (1 0 0) (-16 -16 -16) (16 16 16)
  1098. Fires at either the target or the current direction.
  1099. "random" is the number of degrees of deviance from the taget. (1.0 default)
  1100. */
  1101. void SP_shooter_blaster( gentity_t *ent ) {
  1102. InitShooter( ent, WP_BLASTER);
  1103. }
  1104. void check_recharge(gentity_t *ent)
  1105. {
  1106. if (ent->fly_sound_debounce_time < level.time ||
  1107. !ent->activator ||
  1108. !ent->activator->client ||
  1109. !(ent->activator->client->pers.cmd.buttons & BUTTON_USE))
  1110. {
  1111. if (ent->activator)
  1112. {
  1113. G_Sound(ent, CHAN_AUTO, ent->genericValue7);
  1114. }
  1115. ent->s.loopSound = 0;
  1116. ent->s.loopIsSoundset = qfalse;
  1117. ent->activator = NULL;
  1118. ent->fly_sound_debounce_time = 0;
  1119. }
  1120. if (!ent->activator)
  1121. { //don't recharge during use
  1122. if (ent->genericValue8 < level.time)
  1123. {
  1124. if (ent->count < ent->genericValue4)
  1125. {
  1126. ent->count++;
  1127. }
  1128. ent->genericValue8 = level.time + ent->genericValue5;
  1129. }
  1130. }
  1131. ent->s.health = ent->count; //the "health bar" is gonna be how full we are
  1132. ent->nextthink = level.time;
  1133. }
  1134. /*
  1135. ================
  1136. EnergyShieldStationSettings
  1137. ================
  1138. */
  1139. void EnergyShieldStationSettings(gentity_t *ent)
  1140. {
  1141. G_SpawnInt( "count", "200", &ent->count );
  1142. G_SpawnInt("chargerate", "0", &ent->genericValue5);
  1143. if (!ent->genericValue5)
  1144. {
  1145. ent->genericValue5 = STATION_RECHARGE_TIME;
  1146. }
  1147. }
  1148. /*
  1149. ================
  1150. shield_power_converter_use
  1151. ================
  1152. */
  1153. void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1154. {
  1155. int dif,add;
  1156. int stop = 1;
  1157. if (!activator || !activator->client)
  1158. {
  1159. return;
  1160. }
  1161. if ( level.gametype == GT_SIEGE
  1162. && other
  1163. && other->client
  1164. && other->client->siegeClass )
  1165. {
  1166. if ( !bgSiegeClasses[other->client->siegeClass].maxarmor )
  1167. {//can't use it!
  1168. G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/shieldcon_empty"));
  1169. return;
  1170. }
  1171. }
  1172. if (self->setTime < level.time)
  1173. {
  1174. int maxArmor;
  1175. if (!self->s.loopSound)
  1176. {
  1177. self->s.loopSound = G_SoundIndex("sound/interface/shieldcon_run");
  1178. self->s.loopIsSoundset = qfalse;
  1179. }
  1180. self->setTime = level.time + 100;
  1181. if ( level.gametype == GT_SIEGE
  1182. && other
  1183. && other->client
  1184. && other->client->siegeClass != -1 )
  1185. {
  1186. maxArmor = bgSiegeClasses[other->client->siegeClass].maxarmor;
  1187. }
  1188. else
  1189. {
  1190. maxArmor = activator->client->ps.stats[STAT_MAX_HEALTH];
  1191. }
  1192. dif = maxArmor - activator->client->ps.stats[STAT_ARMOR];
  1193. if (dif > 0) // Already at full armor?
  1194. {
  1195. if (dif >MAX_AMMO_GIVE)
  1196. {
  1197. add = MAX_AMMO_GIVE;
  1198. }
  1199. else
  1200. {
  1201. add = dif;
  1202. }
  1203. if (self->count<add)
  1204. {
  1205. add = self->count;
  1206. }
  1207. if (!self->genericValue12)
  1208. {
  1209. self->count -= add;
  1210. }
  1211. if (self->count <= 0)
  1212. {
  1213. self->setTime = 0;
  1214. }
  1215. stop = 0;
  1216. self->fly_sound_debounce_time = level.time + 500;
  1217. self->activator = activator;
  1218. activator->client->ps.stats[STAT_ARMOR] += add;
  1219. }
  1220. }
  1221. if (stop || self->count <= 0)
  1222. {
  1223. if (self->s.loopSound && self->setTime < level.time)
  1224. {
  1225. if (self->count <= 0)
  1226. {
  1227. G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/shieldcon_empty"));
  1228. }
  1229. else
  1230. {
  1231. G_Sound(self, CHAN_AUTO, self->genericValue7);
  1232. }
  1233. }
  1234. self->s.loopSound = 0;
  1235. self->s.loopIsSoundset = qfalse;
  1236. if (self->setTime < level.time)
  1237. {
  1238. self->setTime = level.time + self->genericValue5+100;
  1239. }
  1240. }
  1241. }
  1242. //dispense generic ammo
  1243. void ammo_generic_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1244. {
  1245. int /*dif,*/ add;
  1246. //int ammoType;
  1247. int stop = 1;
  1248. if (!activator || !activator->client)
  1249. {
  1250. return;
  1251. }
  1252. if (self->setTime < level.time)
  1253. {
  1254. qboolean gaveSome = qfalse;
  1255. /*
  1256. while (i < 3)
  1257. {
  1258. if (!self->s.loopSound)
  1259. {
  1260. self->s.loopSound = G_SoundIndex("sound/interface/ammocon_run");
  1261. self->s.loopIsSoundset = qfalse;
  1262. }
  1263. self->setTime = level.time + 100;
  1264. //dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->client->ps.stats[STAT_ARMOR];
  1265. switch (i)
  1266. { //don't give rockets I guess
  1267. case 0:
  1268. ammoType = AMMO_BLASTER;
  1269. break;
  1270. case 1:
  1271. ammoType = AMMO_POWERCELL;
  1272. break;
  1273. case 2:
  1274. ammoType = AMMO_METAL_BOLTS;
  1275. break;
  1276. default:
  1277. ammoType = -1;
  1278. break;
  1279. }
  1280. if (ammoType != -1)
  1281. {
  1282. dif = ammoData[ammoType].max - activator->client->ps.ammo[ammoType];
  1283. }
  1284. else
  1285. {
  1286. dif = 0;
  1287. }
  1288. if (dif > 0)
  1289. { //only give if not full
  1290. if (dif > MAX_AMMO_GIVE)
  1291. {
  1292. add = MAX_AMMO_GIVE;
  1293. }
  1294. else
  1295. {
  1296. add = dif;
  1297. }
  1298. if (self->count<add)
  1299. {
  1300. add = self->count;
  1301. }
  1302. self->count -= add;
  1303. if (self->count <= 0)
  1304. {
  1305. self->setTime = 0;
  1306. break;
  1307. }
  1308. stop = 0;
  1309. self->fly_sound_debounce_time = level.time + 500;
  1310. self->activator = activator;
  1311. activator->client->ps.ammo[ammoType] += add;
  1312. }
  1313. i++;
  1314. }
  1315. */
  1316. int i = AMMO_BLASTER;
  1317. if (!self->s.loopSound)
  1318. {
  1319. self->s.loopSound = G_SoundIndex("sound/interface/ammocon_run");
  1320. self->s.loopIsSoundset = qfalse;
  1321. }
  1322. //self->setTime = level.time + 100;
  1323. self->fly_sound_debounce_time = level.time + 500;
  1324. self->activator = activator;
  1325. while (i < AMMO_MAX)
  1326. {
  1327. add = ammoData[i].max*0.05;
  1328. if (add < 1)
  1329. {
  1330. add = 1;
  1331. }
  1332. if ( ( (activator->client->ps.eFlags & EF_DOUBLE_AMMO) && (activator->client->ps.ammo[i] < ammoData[i].max*2)) ||
  1333. ( activator->client->ps.ammo[i] < ammoData[i].max ) )
  1334. {
  1335. gaveSome = qtrue;
  1336. if ( level.gametype == GT_SIEGE && i == AMMO_ROCKETS && activator->client->ps.ammo[i] >= 10 )
  1337. { //this stuff is already a freaking mess, so..
  1338. gaveSome = qfalse;
  1339. }
  1340. activator->client->ps.ammo[i] += add;
  1341. if ( level.gametype == GT_SIEGE && i == AMMO_ROCKETS && activator->client->ps.ammo[i] >= 10 )
  1342. { // fixme - this should SERIOUSLY be externed.
  1343. activator->client->ps.ammo[i] = 10;
  1344. }
  1345. else if ( activator->client->ps.eFlags & EF_DOUBLE_AMMO )
  1346. {
  1347. if (activator->client->ps.ammo[i] >= ammoData[i].max * 2)
  1348. { // yuck.
  1349. activator->client->ps.ammo[i] = ammoData[i].max * 2;
  1350. }
  1351. else
  1352. {
  1353. stop = 0;
  1354. }
  1355. }
  1356. else
  1357. {
  1358. if (activator->client->ps.ammo[i] >= ammoData[i].max)
  1359. {
  1360. activator->client->ps.ammo[i] = ammoData[i].max;
  1361. }
  1362. else
  1363. {
  1364. stop = 0;
  1365. }
  1366. }
  1367. }
  1368. i++;
  1369. if (!self->genericValue12 && gaveSome)
  1370. {
  1371. int sub = (add*0.2);
  1372. if (sub < 1)
  1373. {
  1374. sub = 1;
  1375. }
  1376. self->count -= sub;
  1377. if (self->count <= 0)
  1378. {
  1379. self->count = 0;
  1380. stop = 1;
  1381. break;
  1382. }
  1383. }
  1384. }
  1385. }
  1386. if (stop || self->count <= 0)
  1387. {
  1388. if (self->s.loopSound && self->setTime < level.time)
  1389. {
  1390. if (self->count <= 0)
  1391. {
  1392. G_Sound(self, CHAN_AUTO, G_SoundIndex("sound/interface/ammocon_empty"));
  1393. }
  1394. else
  1395. {
  1396. G_Sound(self, CHAN_AUTO, self->genericValue7);
  1397. }
  1398. }
  1399. self->s.loopSound = 0;
  1400. self->s.loopIsSoundset = qfalse;
  1401. if (self->setTime < level.time)
  1402. {
  1403. self->setTime = level.time + self->genericValue5+100;
  1404. }
  1405. }
  1406. }
  1407. /*QUAKED misc_ammo_floor_unit (1 0 0) (-16 -16 0) (16 16 40)
  1408. model="/models/items/a_pwr_converter.md3"
  1409. Gives generic ammo when used
  1410. "count" - max charge value (default 200)
  1411. "chargerate" - rechage 1 point every this many milliseconds (default 2000)
  1412. "nodrain" - don't drain power from station if 1
  1413. */
  1414. void SP_misc_ammo_floor_unit(gentity_t *ent)
  1415. {
  1416. vec3_t dest;
  1417. trace_t tr;
  1418. VectorSet( ent->r.mins, -16, -16, 0 );
  1419. VectorSet( ent->r.maxs, 16, 16, 40 );
  1420. ent->s.origin[2] += 0.1f;
  1421. ent->r.maxs[2] -= 0.1f;
  1422. VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
  1423. trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
  1424. if ( tr.startsolid )
  1425. {
  1426. G_Printf ("SP_misc_ammo_floor_unit: misc_ammo_floor_unit startsolid at %s\n", vtos(ent->s.origin));
  1427. G_FreeEntity( ent );
  1428. return;
  1429. }
  1430. //add the 0.1 back after the trace
  1431. ent->r.maxs[2] += 0.1f;
  1432. // allow to ride movers
  1433. ent->s.groundEntityNum = tr.entityNum;
  1434. G_SetOrigin( ent, tr.endpos );
  1435. if (!ent->health)
  1436. {
  1437. ent->health = 60;
  1438. }
  1439. if (!ent->model || !ent->model[0])
  1440. {
  1441. ent->model = "/models/items/a_pwr_converter.md3";
  1442. }
  1443. ent->s.modelindex = G_ModelIndex( ent->model );
  1444. ent->s.eFlags = 0;
  1445. ent->r.svFlags |= SVF_PLAYER_USABLE;
  1446. ent->r.contents = CONTENTS_SOLID;
  1447. ent->clipmask = MASK_SOLID;
  1448. EnergyShieldStationSettings(ent);
  1449. ent->genericValue4 = ent->count; //initial value
  1450. ent->think = check_recharge;
  1451. G_SpawnInt("nodrain", "0", &ent->genericValue12);
  1452. if (!ent->genericValue12)
  1453. {
  1454. ent->s.maxhealth = ent->s.health = ent->count;
  1455. }
  1456. ent->s.shouldtarget = qtrue;
  1457. ent->s.teamowner = 0;
  1458. ent->s.owner = ENTITYNUM_NONE;
  1459. ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
  1460. ent->use = ammo_generic_power_converter_use;
  1461. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  1462. trap_LinkEntity (ent);
  1463. G_SoundIndex("sound/interface/ammocon_run");
  1464. ent->genericValue7 = G_SoundIndex("sound/interface/ammocon_done");
  1465. G_SoundIndex("sound/interface/ammocon_empty");
  1466. if (level.gametype == GT_SIEGE)
  1467. { //show on radar from everywhere
  1468. ent->r.svFlags |= SVF_BROADCAST;
  1469. ent->s.eFlags |= EF_RADAROBJECT;
  1470. ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/weapon_recharge");
  1471. }
  1472. }
  1473. /*QUAKED misc_shield_floor_unit (1 0 0) (-16 -16 0) (16 16 40)
  1474. model="/models/items/a_shield_converter.md3"
  1475. Gives shield energy when used.
  1476. "count" - max charge value (default 50)
  1477. "chargerate" - rechage 1 point every this many milliseconds (default 3000)
  1478. "nodrain" - don't drain power from me
  1479. */
  1480. void SP_misc_shield_floor_unit( gentity_t *ent )
  1481. {
  1482. vec3_t dest;
  1483. trace_t tr;
  1484. if (level.gametype != GT_CTF &&
  1485. level.gametype != GT_CTY &&
  1486. level.gametype != GT_SIEGE)
  1487. {
  1488. G_FreeEntity( ent );
  1489. return;
  1490. }
  1491. VectorSet( ent->r.mins, -16, -16, 0 );
  1492. VectorSet( ent->r.maxs, 16, 16, 40 );
  1493. ent->s.origin[2] += 0.1f;
  1494. ent->r.maxs[2] -= 0.1f;
  1495. VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
  1496. trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
  1497. if ( tr.startsolid )
  1498. {
  1499. G_Printf ("SP_misc_shield_floor_unit: misc_shield_floor_unit startsolid at %s\n", vtos(ent->s.origin));
  1500. G_FreeEntity( ent );
  1501. return;
  1502. }
  1503. //add the 0.1 back after the trace
  1504. ent->r.maxs[2] += 0.1f;
  1505. // allow to ride movers
  1506. ent->s.groundEntityNum = tr.entityNum;
  1507. G_SetOrigin( ent, tr.endpos );
  1508. if (!ent->health)
  1509. {
  1510. ent->health = 60;
  1511. }
  1512. if (!ent->model || !ent->model[0])
  1513. {
  1514. ent->model = "/models/items/a_shield_converter.md3";
  1515. }
  1516. ent->s.modelindex = G_ModelIndex( ent->model );
  1517. ent->s.eFlags = 0;
  1518. ent->r.svFlags |= SVF_PLAYER_USABLE;
  1519. ent->r.contents = CONTENTS_SOLID;
  1520. ent->clipmask = MASK_SOLID;
  1521. EnergyShieldStationSettings(ent);
  1522. ent->genericValue4 = ent->count; //initial value
  1523. ent->think = check_recharge;
  1524. G_SpawnInt("nodrain", "0", &ent->genericValue12);
  1525. if (!ent->genericValue12)
  1526. {
  1527. ent->s.maxhealth = ent->s.health = ent->count;
  1528. }
  1529. ent->s.shouldtarget = qtrue;
  1530. ent->s.teamowner = 0;
  1531. ent->s.owner = ENTITYNUM_NONE;
  1532. ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
  1533. ent->use = shield_power_converter_use;
  1534. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  1535. trap_LinkEntity (ent);
  1536. G_SoundIndex("sound/interface/shieldcon_run");
  1537. ent->genericValue7 = G_SoundIndex("sound/interface/shieldcon_done");
  1538. G_SoundIndex("sound/interface/shieldcon_empty");
  1539. if (level.gametype == GT_SIEGE)
  1540. { //show on radar from everywhere
  1541. ent->r.svFlags |= SVF_BROADCAST;
  1542. ent->s.eFlags |= EF_RADAROBJECT;
  1543. ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/shield_recharge");
  1544. }
  1545. }
  1546. /*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
  1547. model="models/items/psd_big.md3"
  1548. Gives shield energy when used.
  1549. "count" - the amount of ammo given when used (default 200)
  1550. */
  1551. //------------------------------------------------------------
  1552. void SP_misc_model_shield_power_converter( gentity_t *ent )
  1553. {
  1554. if (!ent->health)
  1555. {
  1556. ent->health = 60;
  1557. }
  1558. VectorSet (ent->r.mins, -16, -16, -16);
  1559. VectorSet (ent->r.maxs, 16, 16, 16);
  1560. ent->s.modelindex = G_ModelIndex( ent->model );
  1561. ent->s.eFlags = 0;
  1562. ent->r.svFlags |= SVF_PLAYER_USABLE;
  1563. ent->r.contents = CONTENTS_SOLID;
  1564. ent->clipmask = MASK_SOLID;
  1565. EnergyShieldStationSettings(ent);
  1566. ent->genericValue4 = ent->count; //initial value
  1567. ent->think = check_recharge;
  1568. ent->s.maxhealth = ent->s.health = ent->count;
  1569. ent->s.shouldtarget = qtrue;
  1570. ent->s.teamowner = 0;
  1571. ent->s.owner = ENTITYNUM_NONE;
  1572. ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
  1573. ent->use = shield_power_converter_use;
  1574. G_SetOrigin( ent, ent->s.origin );
  1575. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  1576. trap_LinkEntity (ent);
  1577. //G_SoundIndex("sound/movers/objects/useshieldstation.wav");
  1578. ent->s.modelindex2 = G_ModelIndex("/models/items/psd_big.md3"); // Precache model
  1579. }
  1580. /*
  1581. ================
  1582. EnergyAmmoShieldStationSettings
  1583. ================
  1584. */
  1585. void EnergyAmmoStationSettings(gentity_t *ent)
  1586. {
  1587. G_SpawnInt( "count", "200", &ent->count );
  1588. }
  1589. /*
  1590. ================
  1591. ammo_power_converter_use
  1592. ================
  1593. */
  1594. void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1595. {
  1596. int add = 0.0f;//,highest;
  1597. qboolean overcharge;
  1598. // int difBlaster,difPowerCell,difMetalBolts;
  1599. int stop = 1;
  1600. if (!activator || !activator->client)
  1601. {
  1602. return;
  1603. }
  1604. if (self->setTime < level.time)
  1605. {
  1606. overcharge = qfalse;
  1607. if (!self->s.loopSound)
  1608. {
  1609. self->s.loopSound = G_SoundIndex("sound/player/pickupshield.wav");
  1610. }
  1611. self->setTime = level.time + 100;
  1612. if (self->count) // Has it got any power left?
  1613. {
  1614. int i = AMMO_BLASTER;
  1615. while (i < AMMO_MAX)
  1616. {
  1617. add = ammoData[i].max*0.1;
  1618. if (add < 1)
  1619. {
  1620. add = 1;
  1621. }
  1622. if (activator->client->ps.ammo[i] < ammoData[i].max)
  1623. {
  1624. activator->client->ps.ammo[i] += add;
  1625. if (activator->client->ps.ammo[i] > ammoData[i].max)
  1626. {
  1627. activator->client->ps.ammo[i] = ammoData[i].max;
  1628. }
  1629. }
  1630. i++;
  1631. }
  1632. if (!self->genericValue12)
  1633. {
  1634. self->count -= add;
  1635. }
  1636. stop = 0;
  1637. self->fly_sound_debounce_time = level.time + 500;
  1638. self->activator = activator;
  1639. /*
  1640. if (self->count > MAX_AMMO_GIVE)
  1641. {
  1642. add = MAX_AMMO_GIVE;
  1643. }
  1644. else if (self->count<0)
  1645. {
  1646. add = 0;
  1647. }
  1648. else
  1649. {
  1650. add = self->count;
  1651. }
  1652. activator->client->ps.ammo[AMMO_BLASTER] += add;
  1653. activator->client->ps.ammo[AMMO_POWERCELL] += add;
  1654. activator->client->ps.ammo[AMMO_METAL_BOLTS] += add;
  1655. self->count -= add;
  1656. stop = 0;
  1657. self->fly_sound_debounce_time = level.time + 500;
  1658. self->activator = activator;
  1659. difBlaster = activator->client->ps.ammo[AMMO_BLASTER] - ammoData[AMMO_BLASTER].max;
  1660. difPowerCell = activator->client->ps.ammo[AMMO_POWERCELL] - ammoData[AMMO_POWERCELL].max;
  1661. difMetalBolts = activator->client->ps.ammo[AMMO_METAL_BOLTS] - ammoData[AMMO_METAL_BOLTS].max;
  1662. // Find the highest one
  1663. highest = difBlaster;
  1664. if (difPowerCell>difBlaster)
  1665. {
  1666. highest = difPowerCell;
  1667. }
  1668. if (difMetalBolts > highest)
  1669. {
  1670. highest = difMetalBolts;
  1671. }
  1672. */
  1673. }
  1674. }
  1675. if (stop)
  1676. {
  1677. self->s.loopSound = 0;
  1678. self->s.loopIsSoundset = qfalse;
  1679. }
  1680. }
  1681. /*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
  1682. model="models/items/power_converter.md3"
  1683. Gives ammo energy when used.
  1684. "count" - the amount of ammo given when used (default 200)
  1685. "nodrain" - don't drain power from me
  1686. */
  1687. //------------------------------------------------------------
  1688. void SP_misc_model_ammo_power_converter( gentity_t *ent )
  1689. {
  1690. if (!ent->health)
  1691. {
  1692. ent->health = 60;
  1693. }
  1694. VectorSet (ent->r.mins, -16, -16, -16);
  1695. VectorSet (ent->r.maxs, 16, 16, 16);
  1696. ent->s.modelindex = G_ModelIndex( ent->model );
  1697. ent->s.eFlags = 0;
  1698. ent->r.svFlags |= SVF_PLAYER_USABLE;
  1699. ent->r.contents = CONTENTS_SOLID;
  1700. ent->clipmask = MASK_SOLID;
  1701. G_SpawnInt("nodrain", "0", &ent->genericValue12);
  1702. ent->use = ammo_power_converter_use;
  1703. EnergyAmmoStationSettings(ent);
  1704. ent->genericValue4 = ent->count; //initial value
  1705. ent->think = check_recharge;
  1706. if (!ent->genericValue12)
  1707. {
  1708. ent->s.maxhealth = ent->s.health = ent->count;
  1709. }
  1710. ent->s.shouldtarget = qtrue;
  1711. ent->s.teamowner = 0;
  1712. ent->s.owner = ENTITYNUM_NONE;
  1713. ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
  1714. G_SetOrigin( ent, ent->s.origin );
  1715. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  1716. trap_LinkEntity (ent);
  1717. //G_SoundIndex("sound/movers/objects/useshieldstation.wav");
  1718. }
  1719. /*
  1720. ================
  1721. EnergyHealthStationSettings
  1722. ================
  1723. */
  1724. void EnergyHealthStationSettings(gentity_t *ent)
  1725. {
  1726. G_SpawnInt( "count", "200", &ent->count );
  1727. }
  1728. /*
  1729. ================
  1730. health_power_converter_use
  1731. ================
  1732. */
  1733. void health_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1734. {
  1735. int dif,add;
  1736. int stop = 1;
  1737. if (!activator || !activator->client)
  1738. {
  1739. return;
  1740. }
  1741. if (self->setTime < level.time)
  1742. {
  1743. if (!self->s.loopSound)
  1744. {
  1745. self->s.loopSound = G_SoundIndex("sound/player/pickuphealth.wav");
  1746. }
  1747. self->setTime = level.time + 100;
  1748. dif = activator->client->ps.stats[STAT_MAX_HEALTH] - activator->health;
  1749. if (dif > 0) // Already at full armor?
  1750. {
  1751. if (dif >/*MAX_AMMO_GIVE*/5)
  1752. {
  1753. add = 5;//MAX_AMMO_GIVE;
  1754. }
  1755. else
  1756. {
  1757. add = dif;
  1758. }
  1759. if (self->count<add)
  1760. {
  1761. add = self->count;
  1762. }
  1763. //self->count -= add;
  1764. stop = 0;
  1765. self->fly_sound_debounce_time = level.time + 500;
  1766. self->activator = activator;
  1767. activator->health += add;
  1768. }
  1769. }
  1770. if (stop)
  1771. {
  1772. self->s.loopSound = 0;
  1773. self->s.loopIsSoundset = qfalse;
  1774. }
  1775. }
  1776. /*QUAKED misc_model_health_power_converter (1 0 0) (-16 -16 -16) (16 16 16)
  1777. model="models/items/power_converter.md3"
  1778. Gives ammo energy when used.
  1779. "count" - the amount of ammo given when used (default 200)
  1780. */
  1781. //------------------------------------------------------------
  1782. void SP_misc_model_health_power_converter( gentity_t *ent )
  1783. {
  1784. if (!ent->health)
  1785. {
  1786. ent->health = 60;
  1787. }
  1788. VectorSet (ent->r.mins, -16, -16, -16);
  1789. VectorSet (ent->r.maxs, 16, 16, 16);
  1790. ent->s.modelindex = G_ModelIndex( ent->model );
  1791. ent->s.eFlags = 0;
  1792. ent->r.svFlags |= SVF_PLAYER_USABLE;
  1793. ent->r.contents = CONTENTS_SOLID;
  1794. ent->clipmask = MASK_SOLID;
  1795. ent->use = health_power_converter_use;
  1796. EnergyHealthStationSettings(ent);
  1797. ent->genericValue4 = ent->count; //initial value
  1798. ent->think = check_recharge;
  1799. //ent->s.maxhealth = ent->s.health = ent->count;
  1800. ent->s.shouldtarget = qtrue;
  1801. ent->s.teamowner = 0;
  1802. ent->s.owner = ENTITYNUM_NONE;
  1803. ent->nextthink = level.time + 200;// + STATION_RECHARGE_TIME;
  1804. G_SetOrigin( ent, ent->s.origin );
  1805. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  1806. trap_LinkEntity (ent);
  1807. //G_SoundIndex("sound/movers/objects/useshieldstation.wav");
  1808. G_SoundIndex("sound/player/pickuphealth.wav");
  1809. ent->genericValue7 = G_SoundIndex("sound/interface/shieldcon_done");
  1810. if (level.gametype == GT_SIEGE)
  1811. { //show on radar from everywhere
  1812. ent->r.svFlags |= SVF_BROADCAST;
  1813. ent->s.eFlags |= EF_RADAROBJECT;
  1814. ent->s.genericenemyindex = G_IconIndex("gfx/mp/siegeicons/desert/bacta");
  1815. }
  1816. }
  1817. #if 0 //damage box stuff
  1818. void DmgBoxHit( gentity_t *self, gentity_t *other, trace_t *trace )
  1819. {
  1820. return;
  1821. }
  1822. void DmgBoxUpdateSelf(gentity_t *self)
  1823. {
  1824. gentity_t *owner = &g_entities[self->r.ownerNum];
  1825. if (!owner || !owner->client || !owner->inuse)
  1826. {
  1827. goto killMe;
  1828. }
  1829. if (self->damageRedirect == DAMAGEREDIRECT_HEAD &&
  1830. owner->client->damageBoxHandle_Head != self->s.number)
  1831. {
  1832. goto killMe;
  1833. }
  1834. if (self->damageRedirect == DAMAGEREDIRECT_RLEG &&
  1835. owner->client->damageBoxHandle_RLeg != self->s.number)
  1836. {
  1837. goto killMe;
  1838. }
  1839. if (self->damageRedirect == DAMAGEREDIRECT_LLEG &&
  1840. owner->client->damageBoxHandle_LLeg != self->s.number)
  1841. {
  1842. goto killMe;
  1843. }
  1844. if (owner->health < 1)
  1845. {
  1846. goto killMe;
  1847. }
  1848. //G_TestLine(self->r.currentOrigin, owner->client->ps.origin, 0x0000ff, 100);
  1849. trap_LinkEntity(self);
  1850. self->nextthink = level.time;
  1851. return;
  1852. killMe:
  1853. self->think = G_FreeEntity;
  1854. self->nextthink = level.time;
  1855. }
  1856. void DmgBoxAbsorb_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )
  1857. {
  1858. self->health = 1;
  1859. }
  1860. void DmgBoxAbsorb_Pain(gentity_t *self, gentity_t *attacker, int damage)
  1861. {
  1862. self->health = 1;
  1863. }
  1864. gentity_t *CreateNewDamageBox( gentity_t *ent )
  1865. {
  1866. gentity_t *dmgBox;
  1867. //We do not want the client to have any real knowledge of the entity whatsoever. It will only
  1868. //ever be used on the server.
  1869. dmgBox = G_Spawn();
  1870. dmgBox->classname = "dmg_box";
  1871. dmgBox->r.svFlags = SVF_USE_CURRENT_ORIGIN;
  1872. dmgBox->r.ownerNum = ent->s.number;
  1873. dmgBox->clipmask = 0;
  1874. dmgBox->r.contents = MASK_PLAYERSOLID;
  1875. dmgBox->mass = 5000;
  1876. dmgBox->s.eFlags |= EF_NODRAW;
  1877. dmgBox->r.svFlags |= SVF_NOCLIENT;
  1878. dmgBox->touch = DmgBoxHit;
  1879. dmgBox->takedamage = qtrue;
  1880. dmgBox->health = 1;
  1881. dmgBox->pain = DmgBoxAbsorb_Pain;
  1882. dmgBox->die = DmgBoxAbsorb_Die;
  1883. dmgBox->think = DmgBoxUpdateSelf;
  1884. dmgBox->nextthink = level.time + 50;
  1885. return dmgBox;
  1886. }
  1887. void ATST_ManageDamageBoxes(gentity_t *ent)
  1888. {
  1889. vec3_t headOrg, lLegOrg, rLegOrg;
  1890. vec3_t fwd, right, up, flatAngle;
  1891. if (!ent->client->damageBoxHandle_Head)
  1892. {
  1893. gentity_t *dmgBox = CreateNewDamageBox(ent);
  1894. if (dmgBox)
  1895. {
  1896. VectorSet( dmgBox->r.mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 );
  1897. VectorSet( dmgBox->r.maxs, ATST_MAXS0, ATST_MAXS1, ATST_HEADSIZE );
  1898. ent->client->damageBoxHandle_Head = dmgBox->s.number;
  1899. dmgBox->damageRedirect = DAMAGEREDIRECT_HEAD;
  1900. dmgBox->damageRedirectTo = ent->s.number;
  1901. }
  1902. }
  1903. if (!ent->client->damageBoxHandle_RLeg)
  1904. {
  1905. gentity_t *dmgBox = CreateNewDamageBox(ent);
  1906. if (dmgBox)
  1907. {
  1908. VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 );
  1909. VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE );
  1910. ent->client->damageBoxHandle_RLeg = dmgBox->s.number;
  1911. dmgBox->damageRedirect = DAMAGEREDIRECT_RLEG;
  1912. dmgBox->damageRedirectTo = ent->s.number;
  1913. }
  1914. }
  1915. if (!ent->client->damageBoxHandle_LLeg)
  1916. {
  1917. gentity_t *dmgBox = CreateNewDamageBox(ent);
  1918. if (dmgBox)
  1919. {
  1920. VectorSet( dmgBox->r.mins, ATST_MINS0/4, ATST_MINS1/4, ATST_MINS2 );
  1921. VectorSet( dmgBox->r.maxs, ATST_MAXS0/4, ATST_MAXS1/4, ATST_MAXS2-ATST_HEADSIZE );
  1922. ent->client->damageBoxHandle_LLeg = dmgBox->s.number;
  1923. dmgBox->damageRedirect = DAMAGEREDIRECT_LLEG;
  1924. dmgBox->damageRedirectTo = ent->s.number;
  1925. }
  1926. }
  1927. if (!ent->client->damageBoxHandle_Head ||
  1928. !ent->client->damageBoxHandle_LLeg ||
  1929. !ent->client->damageBoxHandle_RLeg)
  1930. {
  1931. return;
  1932. }
  1933. VectorCopy(ent->client->ps.origin, headOrg);
  1934. headOrg[2] += (ATST_MAXS2-ATST_HEADSIZE);
  1935. VectorCopy(ent->client->ps.viewangles, flatAngle);
  1936. flatAngle[PITCH] = 0;
  1937. flatAngle[ROLL] = 0;
  1938. AngleVectors(flatAngle, fwd, right, up);
  1939. VectorCopy(ent->client->ps.origin, lLegOrg);
  1940. VectorCopy(ent->client->ps.origin, rLegOrg);
  1941. lLegOrg[0] -= right[0]*32;
  1942. lLegOrg[1] -= right[1]*32;
  1943. lLegOrg[2] -= right[2]*32;
  1944. rLegOrg[0] += right[0]*32;
  1945. rLegOrg[1] += right[1]*32;
  1946. rLegOrg[2] += right[2]*32;
  1947. G_SetOrigin(&g_entities[ent->client->damageBoxHandle_Head], headOrg);
  1948. G_SetOrigin(&g_entities[ent->client->damageBoxHandle_LLeg], lLegOrg);
  1949. G_SetOrigin(&g_entities[ent->client->damageBoxHandle_RLeg], rLegOrg);
  1950. }
  1951. int G_PlayerBecomeATST(gentity_t *ent)
  1952. {
  1953. if (!ent || !ent->client)
  1954. {
  1955. return 0;
  1956. }
  1957. if (ent->client->ps.weaponTime > 0)
  1958. {
  1959. return 0;
  1960. }
  1961. if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE)
  1962. {
  1963. return 0;
  1964. }
  1965. if (ent->client->ps.zoomMode)
  1966. {
  1967. return 0;
  1968. }
  1969. if (ent->client->ps.usingATST)
  1970. {
  1971. ent->client->ps.usingATST = qfalse;
  1972. ent->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY;
  1973. }
  1974. else
  1975. {
  1976. ent->client->ps.usingATST = qtrue;
  1977. }
  1978. ent->client->ps.weaponTime = 1000;
  1979. return 1;
  1980. }
  1981. #endif
  1982. //----------------------------------------------------------
  1983. /*QUAKED fx_runner (0 0 1) (-8 -8 -8) (8 8 8) STARTOFF ONESHOT DAMAGE
  1984. Runs the specified effect, can also be targeted at an info_notnull to orient the effect
  1985. STARTOFF - effect starts off, toggles on/off when used
  1986. ONESHOT - effect fires only when used
  1987. DAMAGE - does radius damage around effect every "delay" milliseonds
  1988. "fxFile" - name of the effect file to play
  1989. "target" - direction to aim the effect in, otherwise defaults to up
  1990. "target2" - uses its target2 when the fx gets triggered
  1991. "delay" - how often to call the effect, don't over-do this ( default 200 )
  1992. "random" - random amount of time to add to delay, ( default 0, 200 = 0ms to 200ms )
  1993. "splashRadius" - only works when damage is checked ( default 16 )
  1994. "splashDamage" - only works when damage is checked ( default 5 )
  1995. "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
  1996. */
  1997. #define FX_RUNNER_RESERVED 0x800000
  1998. #define FX_ENT_RADIUS 32
  1999. extern int BMS_START;
  2000. extern int BMS_MID;
  2001. extern int BMS_END;
  2002. //----------------------------------------------------------
  2003. void fx_runner_think( gentity_t *ent )
  2004. {
  2005. BG_EvaluateTrajectory( &ent->s.pos, level.time, ent->r.currentOrigin );
  2006. BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles );
  2007. // call the effect with the desired position and orientation
  2008. if (ent->s.isPortalEnt)
  2009. {
  2010. // G_AddEvent( ent, EV_PLAY_PORTAL_EFFECT_ID, ent->genericValue5 );
  2011. }
  2012. else
  2013. {
  2014. // G_AddEvent( ent, EV_PLAY_EFFECT_ID, ent->genericValue5 );
  2015. }
  2016. // start the fx on the client (continuous)
  2017. ent->s.modelindex2 = FX_STATE_CONTINUOUS;
  2018. VectorCopy(ent->r.currentAngles, ent->s.angles);
  2019. VectorCopy(ent->r.currentOrigin, ent->s.origin);
  2020. ent->nextthink = level.time + ent->delay + random() * ent->random;
  2021. if ( ent->spawnflags & 4 ) // damage
  2022. {
  2023. G_RadiusDamage( ent->r.currentOrigin, ent, ent->splashDamage, ent->splashRadius, ent, ent, MOD_UNKNOWN );
  2024. }
  2025. if ( ent->target2 && ent->target2[0] )
  2026. {
  2027. // let our target know that we have spawned an effect
  2028. G_UseTargets2( ent, ent, ent->target2 );
  2029. }
  2030. if ( !(ent->spawnflags & 2 ) && !ent->s.loopSound ) // NOT ONESHOT...this is an assy thing to do
  2031. {
  2032. if ( ent->soundSet && ent->soundSet[0] )
  2033. {
  2034. ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
  2035. ent->s.loopIsSoundset = qtrue;
  2036. ent->s.loopSound = BMS_MID;
  2037. }
  2038. }
  2039. }
  2040. //----------------------------------------------------------
  2041. void fx_runner_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  2042. {
  2043. if (self->s.isPortalEnt)
  2044. { //rww - mark it as broadcast upon first use if it's within the area of a skyportal
  2045. self->r.svFlags |= SVF_BROADCAST;
  2046. }
  2047. if ( self->spawnflags & 2 ) // ONESHOT
  2048. {
  2049. // call the effect with the desired position and orientation, as a safety thing,
  2050. // make sure we aren't thinking at all.
  2051. int saveState = self->s.modelindex2 + 1;
  2052. fx_runner_think( self );
  2053. self->nextthink = -1;
  2054. // one shot indicator
  2055. self->s.modelindex2 = saveState;
  2056. if (self->s.modelindex2 > FX_STATE_ONE_SHOT_LIMIT)
  2057. {
  2058. self->s.modelindex2 = FX_STATE_ONE_SHOT;
  2059. }
  2060. if ( self->target2 )
  2061. {
  2062. // let our target know that we have spawned an effect
  2063. G_UseTargets2( self, self, self->target2 );
  2064. }
  2065. if ( self->soundSet && self->soundSet[0] )
  2066. {
  2067. self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
  2068. G_AddEvent( self, EV_BMODEL_SOUND, BMS_START);
  2069. }
  2070. }
  2071. else
  2072. {
  2073. // ensure we are working with the right think function
  2074. self->think = fx_runner_think;
  2075. // toggle our state
  2076. if ( self->nextthink == -1 )
  2077. {
  2078. // NOTE: we fire the effect immediately on use, the fx_runner_think func will set
  2079. // up the nextthink time.
  2080. fx_runner_think( self );
  2081. if ( self->soundSet && self->soundSet[0] )
  2082. {
  2083. self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
  2084. G_AddEvent( self, EV_BMODEL_SOUND, BMS_START);
  2085. self->s.loopSound = BMS_MID;
  2086. self->s.loopIsSoundset = qtrue;
  2087. }
  2088. }
  2089. else
  2090. {
  2091. // turn off for now
  2092. self->nextthink = -1;
  2093. // turn off fx on client
  2094. self->s.modelindex2 = FX_STATE_OFF;
  2095. if ( self->soundSet && self->soundSet[0] )
  2096. {
  2097. self->s.soundSetIndex = G_SoundSetIndex(self->soundSet);
  2098. G_AddEvent( self, EV_BMODEL_SOUND, BMS_END );
  2099. self->s.loopSound = 0;
  2100. self->s.loopIsSoundset = qfalse;
  2101. }
  2102. }
  2103. }
  2104. }
  2105. //----------------------------------------------------------
  2106. void fx_runner_link( gentity_t *ent )
  2107. {
  2108. vec3_t dir;
  2109. if ( ent->target && ent->target[0] )
  2110. {
  2111. // try to use the target to override the orientation
  2112. gentity_t *target = NULL;
  2113. target = G_Find( target, FOFS(targetname), ent->target );
  2114. if ( !target )
  2115. {
  2116. // Bah, no good, dump a warning, but continue on and use the UP vector
  2117. Com_Printf( "fx_runner_link: target specified but not found: %s\n", ent->target );
  2118. Com_Printf( " -assuming UP orientation.\n" );
  2119. }
  2120. else
  2121. {
  2122. // Our target is valid so let's override the default UP vector
  2123. VectorSubtract( target->s.origin, ent->s.origin, dir );
  2124. VectorNormalize( dir );
  2125. vectoangles( dir, ent->s.angles );
  2126. }
  2127. }
  2128. // don't really do anything with this right now other than do a check to warn the designers if the target2 is bogus
  2129. if ( ent->target2 && ent->target2[0] )
  2130. {
  2131. gentity_t *target = NULL;
  2132. target = G_Find( target, FOFS(targetname), ent->target2 );
  2133. if ( !target )
  2134. {
  2135. // Target2 is bogus, but we can still continue
  2136. Com_Printf( "fx_runner_link: target2 was specified but is not valid: %s\n", ent->target2 );
  2137. }
  2138. }
  2139. G_SetAngles( ent, ent->s.angles );
  2140. if ( ent->spawnflags & 1 || ent->spawnflags & 2 ) // STARTOFF || ONESHOT
  2141. {
  2142. // We won't even consider thinking until we are used
  2143. ent->nextthink = -1;
  2144. }
  2145. else
  2146. {
  2147. if ( ent->soundSet && ent->soundSet[0] )
  2148. {
  2149. ent->s.soundSetIndex = G_SoundSetIndex(ent->soundSet);
  2150. ent->s.loopSound = BMS_MID;
  2151. ent->s.loopIsSoundset = qtrue;
  2152. }
  2153. // Let's get to work right now!
  2154. ent->think = fx_runner_think;
  2155. ent->nextthink = level.time + 200; // wait a small bit, then start working
  2156. }
  2157. // make us useable if we can be targeted
  2158. if ( ent->targetname && ent->targetname[0] )
  2159. {
  2160. ent->use = fx_runner_use;
  2161. }
  2162. }
  2163. //----------------------------------------------------------
  2164. void SP_fx_runner( gentity_t *ent )
  2165. {
  2166. char *fxFile;
  2167. G_SpawnString( "fxFile", "", &fxFile );
  2168. // Get our defaults
  2169. G_SpawnInt( "delay", "200", &ent->delay );
  2170. G_SpawnFloat( "random", "0", &ent->random );
  2171. G_SpawnInt( "splashRadius", "16", &ent->splashRadius );
  2172. G_SpawnInt( "splashDamage", "5", &ent->splashDamage );
  2173. if (!ent->s.angles[0] && !ent->s.angles[1] && !ent->s.angles[2])
  2174. {
  2175. // didn't have angles, so give us the default of up
  2176. VectorSet( ent->s.angles, -90, 0, 0 );
  2177. }
  2178. if ( !fxFile || !fxFile[0] )
  2179. {
  2180. Com_Printf( S_COLOR_RED"ERROR: fx_runner %s at %s has no fxFile specified\n", ent->targetname, vtos(ent->s.origin) );
  2181. G_FreeEntity( ent );
  2182. return;
  2183. }
  2184. // Try and associate an effect file, unfortunately we won't know if this worked or not
  2185. // until the CGAME trys to register it...
  2186. ent->s.modelindex = G_EffectIndex( fxFile );
  2187. // important info transmitted
  2188. ent->s.eType = ET_FX;
  2189. ent->s.speed = ent->delay;
  2190. ent->s.time = ent->random;
  2191. ent->s.modelindex2 = FX_STATE_OFF;
  2192. // Give us a bit of time to spawn in the other entities, since we may have to target one of 'em
  2193. ent->think = fx_runner_link;
  2194. ent->nextthink = level.time + 400;
  2195. // Save our position and link us up!
  2196. G_SetOrigin( ent, ent->s.origin );
  2197. VectorSet( ent->r.maxs, FX_ENT_RADIUS, FX_ENT_RADIUS, FX_ENT_RADIUS );
  2198. VectorScale( ent->r.maxs, -1, ent->r.mins );
  2199. trap_LinkEntity( ent );
  2200. }
  2201. /*QUAKED fx_spacedust (1 0 0) (-16 -16 -16) (16 16 16)
  2202. This world effect will spawn space dust globally into the level.
  2203. "count" the number of snow particles (default of 1000)
  2204. */
  2205. //----------------------------------------------------------
  2206. void SP_CreateSpaceDust( gentity_t *ent )
  2207. {
  2208. G_EffectIndex(va("*spacedust %i", ent->count));
  2209. //G_EffectIndex("*constantwind ( 10 -10 0 )");
  2210. }
  2211. /*QUAKED fx_snow (1 0 0) (-16 -16 -16) (16 16 16)
  2212. This world effect will spawn snow globally into the level.
  2213. "count" the number of snow particles (default of 1000)
  2214. */
  2215. //----------------------------------------------------------
  2216. void SP_CreateSnow( gentity_t *ent )
  2217. {
  2218. G_EffectIndex("*snow");
  2219. G_EffectIndex("*fog");
  2220. G_EffectIndex("*constantwind (100 100 -100)");
  2221. }
  2222. /*QUAKED fx_rain (1 0 0) (-16 -16 -16) (16 16 16)
  2223. This world effect will spawn rain globally into the level.
  2224. "count" the number of rain particles (default of 500)
  2225. */
  2226. //----------------------------------------------------------
  2227. void SP_CreateRain( gentity_t *ent )
  2228. {
  2229. G_EffectIndex(va("*rain init %i", ent->count));
  2230. }
  2231. qboolean gEscaping = qfalse;
  2232. int gEscapeTime = 0;
  2233. void Use_Target_Screenshake( gentity_t *ent, gentity_t *other, gentity_t *activator )
  2234. {
  2235. qboolean bGlobal = qfalse;
  2236. if (ent->genericValue6)
  2237. {
  2238. bGlobal = qtrue;
  2239. }
  2240. G_ScreenShake(ent->s.origin, NULL, ent->speed, ent->genericValue5, bGlobal);
  2241. }
  2242. void SP_target_screenshake(gentity_t *ent)
  2243. {
  2244. G_SpawnFloat( "intensity", "10", &ent->speed );
  2245. //intensity of the shake
  2246. G_SpawnInt( "duration", "800", &ent->genericValue5 );
  2247. //duration of the shake
  2248. G_SpawnInt( "globalshake", "1", &ent->genericValue6 );
  2249. //non-0 if shake should be global (all clients). Otherwise, only in the PVS.
  2250. ent->use = Use_Target_Screenshake;
  2251. }
  2252. void LogExit( const char *string );
  2253. void Use_Target_Escapetrig( gentity_t *ent, gentity_t *other, gentity_t *activator )
  2254. {
  2255. if (!ent->genericValue6)
  2256. {
  2257. gEscaping = qtrue;
  2258. gEscapeTime = level.time + ent->genericValue5;
  2259. }
  2260. else if (gEscaping)
  2261. {
  2262. int i = 0;
  2263. gEscaping = qfalse;
  2264. while (i < MAX_CLIENTS)
  2265. { //all of the survivors get 100 points!
  2266. if (g_entities[i].inuse && g_entities[i].client && g_entities[i].health > 0 &&
  2267. g_entities[i].client->sess.sessionTeam != TEAM_SPECTATOR &&
  2268. !(g_entities[i].client->ps.pm_flags & PMF_FOLLOW))
  2269. {
  2270. AddScore(&g_entities[i], g_entities[i].client->ps.origin, 100);
  2271. }
  2272. i++;
  2273. }
  2274. if (activator && activator->inuse && activator->client)
  2275. { //the one who escaped gets 500
  2276. AddScore(activator, activator->client->ps.origin, 500);
  2277. }
  2278. LogExit("Escaped!");
  2279. }
  2280. }
  2281. void SP_target_escapetrig(gentity_t *ent)
  2282. {
  2283. if (level.gametype != GT_SINGLE_PLAYER)
  2284. {
  2285. G_FreeEntity(ent);
  2286. return;
  2287. }
  2288. G_SpawnInt( "escapetime", "60000", &ent->genericValue5);
  2289. //time given (in ms) for the escape
  2290. G_SpawnInt( "escapegoal", "0", &ent->genericValue6);
  2291. //if non-0, when used, will end an ongoing escape instead of start it
  2292. ent->use = Use_Target_Escapetrig;
  2293. }
  2294. /*QUAKED misc_maglock (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x x
  2295. Place facing a door (using the angle, not a targetname) and it will lock that door. Can only be destroyed by lightsaber and will automatically unlock the door it's attached to
  2296. NOTE: place these half-way in the door to make it flush with the door's surface.
  2297. "target" thing to use when destoryed (not doors - it automatically unlocks the door it was angled at)
  2298. "health" default is 10
  2299. */
  2300. void maglock_die(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
  2301. {
  2302. //unlock our door if we're the last lock pointed at the door
  2303. if ( self->activator )
  2304. {
  2305. self->activator->lockCount--;
  2306. if ( !self->activator->lockCount )
  2307. {
  2308. self->activator->flags &= ~FL_INACTIVE;
  2309. }
  2310. }
  2311. //use targets
  2312. G_UseTargets( self, attacker );
  2313. //die
  2314. //rwwFIXMEFIXME - weap expl func
  2315. // WP_Explode( self );
  2316. }
  2317. void maglock_link( gentity_t *self );
  2318. gentity_t *G_FindDoorTrigger( gentity_t *ent );
  2319. void SP_misc_maglock ( gentity_t *self )
  2320. {
  2321. //NOTE: May have to make these only work on doors that are either untargeted
  2322. // or are targeted by a trigger, not doors fired off by scripts, counters
  2323. // or other such things?
  2324. self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
  2325. self->genericValue1 = G_EffectIndex( "maglock/explosion" );
  2326. G_SetOrigin( self, self->s.origin );
  2327. self->think = maglock_link;
  2328. //FIXME: for some reason, when you re-load a level, these fail to find their doors...? Random? Testing an additional 200ms after the START_TIME_FIND_LINKS
  2329. self->nextthink = level.time + START_TIME_FIND_LINKS+200;//START_TIME_FIND_LINKS;//because we need to let the doors link up and spawn their triggers first!
  2330. }
  2331. void maglock_link( gentity_t *self )
  2332. {
  2333. //find what we're supposed to be attached to
  2334. vec3_t forward, start, end;
  2335. trace_t trace;
  2336. gentity_t *traceEnt;
  2337. AngleVectors( self->s.angles, forward, NULL, NULL );
  2338. VectorMA( self->s.origin, 128, forward, end );
  2339. VectorMA( self->s.origin, -4, forward, start );
  2340. trap_Trace( &trace, start, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
  2341. if ( trace.allsolid || trace.startsolid )
  2342. {
  2343. Com_Error( ERR_DROP,"misc_maglock at %s in solid\n", vtos(self->s.origin) );
  2344. G_FreeEntity( self );
  2345. return;
  2346. }
  2347. if ( trace.fraction == 1.0 )
  2348. {
  2349. self->think = maglock_link;
  2350. self->nextthink = level.time + 100;
  2351. /*
  2352. Com_Error( ERR_DROP,"misc_maglock at %s pointed at no surface\n", vtos(self->s.origin) );
  2353. G_FreeEntity( self );
  2354. */
  2355. return;
  2356. }
  2357. traceEnt = &g_entities[trace.entityNum];
  2358. if ( trace.entityNum >= ENTITYNUM_WORLD || !traceEnt || Q_stricmp( "func_door", traceEnt->classname ) )
  2359. {
  2360. self->think = maglock_link;
  2361. self->nextthink = level.time + 100;
  2362. //Com_Error( ERR_DROP,"misc_maglock at %s not pointed at a door\n", vtos(self->s.origin) );
  2363. //G_FreeEntity( self );
  2364. return;
  2365. }
  2366. //check the traceEnt, make sure it's a door and give it a lockCount and deactivate it
  2367. //find the trigger for the door
  2368. self->activator = G_FindDoorTrigger( traceEnt );
  2369. if ( !self->activator )
  2370. {
  2371. self->activator = traceEnt;
  2372. }
  2373. self->activator->lockCount++;
  2374. self->activator->flags |= FL_INACTIVE;
  2375. //now position and orient it
  2376. vectoangles( trace.plane.normal, end );
  2377. G_SetOrigin( self, trace.endpos );
  2378. G_SetAngles( self, end );
  2379. //make it hittable
  2380. //FIXME: if rotated/inclined this bbox may be off... but okay if we're a ghoul model?
  2381. //self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
  2382. VectorSet( self->r.mins, -8, -8, -8 );
  2383. VectorSet( self->r.maxs, 8, 8, 8 );
  2384. self->r.contents = CONTENTS_CORPSE;
  2385. //make it destroyable
  2386. self->flags |= FL_SHIELDED;//only damagable by lightsabers
  2387. self->takedamage = qtrue;
  2388. self->health = 10;
  2389. self->die = maglock_die;
  2390. //self->fxID = G_EffectIndex( "maglock/explosion" );
  2391. trap_LinkEntity( self );
  2392. }
  2393. void faller_touch(gentity_t *self, gentity_t *other, trace_t *trace)
  2394. {
  2395. if (self->epVelocity[2] < -100 && self->genericValue7 < level.time)
  2396. {
  2397. int r = Q_irand(1, 3);
  2398. if (r == 1)
  2399. {
  2400. self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain25");
  2401. }
  2402. else if (r == 2)
  2403. {
  2404. self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain50");
  2405. }
  2406. else
  2407. {
  2408. self->genericValue11 = G_SoundIndex("sound/chars/stofficer1/misc/pain75");
  2409. }
  2410. G_EntitySound(self, CHAN_VOICE, self->genericValue11);
  2411. G_EntitySound(self, CHAN_AUTO, self->genericValue10);
  2412. self->genericValue6 = level.time + 3000;
  2413. self->genericValue7 = level.time + 200;
  2414. }
  2415. }
  2416. void faller_think(gentity_t *ent)
  2417. {
  2418. float gravity = 3.0f;
  2419. float mass = 0.09f;
  2420. float bounce = 1.1f;
  2421. if (ent->genericValue6 < level.time)
  2422. {
  2423. ent->think = G_FreeEntity;
  2424. ent->nextthink = level.time;
  2425. return;
  2426. }
  2427. if (ent->epVelocity[2] < -100)
  2428. {
  2429. if (!ent->genericValue8)
  2430. {
  2431. G_EntitySound(ent, CHAN_VOICE, ent->genericValue9);
  2432. ent->genericValue8 = 1;
  2433. }
  2434. }
  2435. else
  2436. {
  2437. ent->genericValue8 = 0;
  2438. }
  2439. G_RunExPhys(ent, gravity, mass, bounce, qtrue, NULL, 0);
  2440. VectorScale(ent->epVelocity, 10.0f, ent->s.pos.trDelta);
  2441. ent->nextthink = level.time + 25;
  2442. }
  2443. void misc_faller_create( gentity_t *ent, gentity_t *other, gentity_t *activator )
  2444. {
  2445. gentity_t *faller = G_Spawn();
  2446. faller->genericValue10 = G_SoundIndex("sound/player/fallsplat");
  2447. faller->genericValue9 = G_SoundIndex("sound/chars/stofficer1/misc/falling1");
  2448. faller->genericValue8 = 0;
  2449. faller->genericValue7 = 0;
  2450. faller->genericValue6 = level.time + 15000;
  2451. G_SetOrigin(faller, ent->s.origin);
  2452. faller->s.modelGhoul2 = 1;
  2453. faller->s.modelindex = G_ModelIndex("models/players/stormtrooper/model.glm");
  2454. faller->s.g2radius = 100;
  2455. faller->s.customRGBA[0]=Q_irand(1,255);
  2456. faller->s.customRGBA[1]=Q_irand(1,255);
  2457. faller->s.customRGBA[2]=Q_irand(1,255);
  2458. faller->s.customRGBA[3]=255;
  2459. VectorSet(faller->r.mins, -15, -15, DEFAULT_MINS_2);
  2460. VectorSet(faller->r.maxs, 15, 15, DEFAULT_MAXS_2);
  2461. faller->clipmask = MASK_PLAYERSOLID;
  2462. faller->r.contents = MASK_PLAYERSOLID;
  2463. faller->s.eFlags = (EF_RAG|EF_CLIENTSMOOTH);
  2464. faller->think = faller_think;
  2465. faller->nextthink = level.time;
  2466. faller->touch = faller_touch;
  2467. faller->epVelocity[0] = flrand(-256.0f, 256.0f);
  2468. faller->epVelocity[1] = flrand(-256.0f, 256.0f);
  2469. trap_LinkEntity(faller);
  2470. }
  2471. void misc_faller_think(gentity_t *ent)
  2472. {
  2473. misc_faller_create(ent, ent, ent);
  2474. ent->nextthink = level.time + ent->genericValue1 + Q_irand(0, ent->genericValue2);
  2475. }
  2476. /*QUAKED misc_faller (1 0 0) (-8 -8 -8) (8 8 8)
  2477. Falling stormtrooper - spawned every interval+random fudgefactor,
  2478. or if specified, when used.
  2479. targetname - if specified, will only spawn when used
  2480. interval - spawn every so often (milliseconds)
  2481. fudgefactor - milliseconds between 0 and this number randomly added to interval
  2482. */
  2483. void SP_misc_faller(gentity_t *ent)
  2484. {
  2485. G_ModelIndex("models/players/stormtrooper/model.glm");
  2486. G_SoundIndex("sound/chars/stofficer1/misc/pain25");
  2487. G_SoundIndex("sound/chars/stofficer1/misc/pain50");
  2488. G_SoundIndex("sound/chars/stofficer1/misc/pain75");
  2489. G_SoundIndex("sound/chars/stofficer1/misc/falling1");
  2490. G_SoundIndex("sound/player/fallsplat");
  2491. G_SpawnInt("interval", "500", &ent->genericValue1);
  2492. G_SpawnInt("fudgefactor", "0", &ent->genericValue2);
  2493. if (!ent->targetname || !ent->targetname[0])
  2494. {
  2495. ent->think = misc_faller_think;
  2496. ent->nextthink = level.time + ent->genericValue1 + Q_irand(0, ent->genericValue2);
  2497. }
  2498. else
  2499. {
  2500. ent->use = misc_faller_create;
  2501. }
  2502. }
  2503. //rww - ref tag stuff ported from SP (and C-ified)
  2504. #define TAG_GENERIC_NAME "__WORLD__" //If a designer chooses this name, cut a finger off as an example to the others
  2505. //MAX_TAG_OWNERS is 16 for now in order to not use too much VM memory.
  2506. //Each tag owner has preallocated space for tags up to MAX_TAGS.
  2507. //As is this means 16*256 sizeof(reference_tag_t)'s in addition to name+inuse*16.
  2508. #define MAX_TAGS 256
  2509. #define MAX_TAG_OWNERS 16
  2510. //Maybe I should use my trap_TrueMalloc/trap_TrueFree stuff with this.
  2511. //But I am not yet confident that it can be used without exploding at some point.
  2512. typedef struct tagOwner_s
  2513. {
  2514. char name[MAX_REFNAME];
  2515. reference_tag_t tags[MAX_TAGS];
  2516. qboolean inuse;
  2517. } tagOwner_t;
  2518. tagOwner_t refTagOwnerMap[MAX_TAG_OWNERS];
  2519. tagOwner_t *FirstFreeTagOwner(void)
  2520. {
  2521. int i = 0;
  2522. while (i < MAX_TAG_OWNERS)
  2523. {
  2524. if (!refTagOwnerMap[i].inuse)
  2525. {
  2526. return &refTagOwnerMap[i];
  2527. }
  2528. i++;
  2529. }
  2530. Com_Printf("WARNING: MAX_TAG_OWNERS (%i) REF TAG LIMIT HIT\n", MAX_TAG_OWNERS);
  2531. return NULL;
  2532. }
  2533. reference_tag_t *FirstFreeRefTag(tagOwner_t *tagOwner)
  2534. {
  2535. int i = 0;
  2536. assert(tagOwner);
  2537. while (i < MAX_TAGS)
  2538. {
  2539. if (!tagOwner->tags[i].inuse)
  2540. {
  2541. return &tagOwner->tags[i];
  2542. }
  2543. i++;
  2544. }
  2545. Com_Printf("WARNING: MAX_TAGS (%i) REF TAG LIMIT HIT\n", MAX_TAGS);
  2546. return NULL;
  2547. }
  2548. /*
  2549. -------------------------
  2550. TAG_Init
  2551. -------------------------
  2552. */
  2553. void TAG_Init( void )
  2554. {
  2555. int i = 0;
  2556. int x = 0;
  2557. while (i < MAX_TAG_OWNERS)
  2558. {
  2559. while (x < MAX_TAGS)
  2560. {
  2561. memset(&refTagOwnerMap[i].tags[x], 0, sizeof(refTagOwnerMap[i].tags[x]));
  2562. x++;
  2563. }
  2564. memset(&refTagOwnerMap[i], 0, sizeof(refTagOwnerMap[i]));
  2565. i++;
  2566. }
  2567. }
  2568. /*
  2569. -------------------------
  2570. TAG_FindOwner
  2571. -------------------------
  2572. */
  2573. tagOwner_t *TAG_FindOwner( const char *owner )
  2574. {
  2575. int i = 0;
  2576. while (i < MAX_TAG_OWNERS)
  2577. {
  2578. if (refTagOwnerMap[i].inuse && !Q_stricmp(refTagOwnerMap[i].name, owner))
  2579. {
  2580. return &refTagOwnerMap[i];
  2581. }
  2582. i++;
  2583. }
  2584. return NULL;
  2585. }
  2586. /*
  2587. -------------------------
  2588. TAG_Find
  2589. -------------------------
  2590. */
  2591. reference_tag_t *TAG_Find( const char *owner, const char *name )
  2592. {
  2593. tagOwner_t *tagOwner = NULL;
  2594. int i = 0;
  2595. if (owner && owner[0])
  2596. {
  2597. tagOwner = TAG_FindOwner(owner);
  2598. }
  2599. if (!tagOwner)
  2600. {
  2601. tagOwner = TAG_FindOwner(TAG_GENERIC_NAME);
  2602. }
  2603. //Not found...
  2604. if (!tagOwner)
  2605. {
  2606. tagOwner = TAG_FindOwner( TAG_GENERIC_NAME );
  2607. if (!tagOwner)
  2608. {
  2609. return NULL;
  2610. }
  2611. }
  2612. while (i < MAX_TAGS)
  2613. {
  2614. if (tagOwner->tags[i].inuse && !Q_stricmp(tagOwner->tags[i].name, name))
  2615. {
  2616. return &tagOwner->tags[i];
  2617. }
  2618. i++;
  2619. }
  2620. //Try the generic owner instead
  2621. tagOwner = TAG_FindOwner( TAG_GENERIC_NAME );
  2622. if (!tagOwner)
  2623. {
  2624. return NULL;
  2625. }
  2626. i = 0;
  2627. while (i < MAX_TAGS)
  2628. {
  2629. if (tagOwner->tags[i].inuse && !Q_stricmp(tagOwner->tags[i].name, name))
  2630. {
  2631. return &tagOwner->tags[i];
  2632. }
  2633. i++;
  2634. }
  2635. return NULL;
  2636. }
  2637. /*
  2638. -------------------------
  2639. TAG_Add
  2640. -------------------------
  2641. */
  2642. reference_tag_t *TAG_Add( const char *name, const char *owner, vec3_t origin, vec3_t angles, int radius, int flags )
  2643. {
  2644. reference_tag_t *tag = NULL;
  2645. tagOwner_t *tagOwner = NULL;
  2646. //Make sure this tag's name isn't alread in use
  2647. if ( TAG_Find( owner, name ) )
  2648. {
  2649. Com_Printf(S_COLOR_RED"Duplicate tag name \"%s\"\n", name );
  2650. return NULL;
  2651. }
  2652. //Attempt to add this to the owner's list
  2653. if ( !owner || !owner[0] )
  2654. {
  2655. //If the owner isn't found, use the generic world name
  2656. owner = TAG_GENERIC_NAME;
  2657. }
  2658. tagOwner = TAG_FindOwner( owner );
  2659. if (!tagOwner)
  2660. {
  2661. //Create a new owner list
  2662. tagOwner = FirstFreeTagOwner();//new tagOwner_t;
  2663. if (!tagOwner)
  2664. {
  2665. assert(0);
  2666. return 0;
  2667. }
  2668. }
  2669. //This is actually reverse order of how SP does it because of the way we're storing/allocating.
  2670. //Now that we have the owner, we want to get the first free reftag on the owner itself.
  2671. tag = FirstFreeRefTag(tagOwner);
  2672. if (!tag)
  2673. {
  2674. assert(0);
  2675. return NULL;
  2676. }
  2677. //Copy the information
  2678. VectorCopy( origin, tag->origin );
  2679. VectorCopy( angles, tag->angles );
  2680. tag->radius = radius;
  2681. tag->flags = flags;
  2682. if ( !name || !name[0] )
  2683. {
  2684. Com_Printf(S_COLOR_RED"ERROR: Nameless ref_tag found at (%i %i %i)\n", (int)origin[0], (int)origin[1], (int)origin[2]);
  2685. return NULL;
  2686. }
  2687. //Copy the name
  2688. Q_strncpyz( (char *) tagOwner->name, owner, MAX_REFNAME );
  2689. Q_strlwr( (char *) tagOwner->name ); //NOTENOTE: For case insensitive searches on a map
  2690. //Copy the name
  2691. Q_strncpyz( (char *) tag->name, name, MAX_REFNAME );
  2692. Q_strlwr( (char *) tag->name ); //NOTENOTE: For case insensitive searches on a map
  2693. tagOwner->inuse = qtrue;
  2694. tag->inuse = qtrue;
  2695. return tag;
  2696. }
  2697. /*
  2698. -------------------------
  2699. TAG_GetOrigin
  2700. -------------------------
  2701. */
  2702. int TAG_GetOrigin( const char *owner, const char *name, vec3_t origin )
  2703. {
  2704. reference_tag_t *tag = TAG_Find( owner, name );
  2705. if (!tag)
  2706. {
  2707. VectorClear(origin);
  2708. return 0;
  2709. }
  2710. VectorCopy( tag->origin, origin );
  2711. return 1;
  2712. }
  2713. /*
  2714. -------------------------
  2715. TAG_GetOrigin2
  2716. Had to get rid of that damn assert for dev
  2717. -------------------------
  2718. */
  2719. int TAG_GetOrigin2( const char *owner, const char *name, vec3_t origin )
  2720. {
  2721. reference_tag_t *tag = TAG_Find( owner, name );
  2722. if( tag == NULL )
  2723. {
  2724. return 0;
  2725. }
  2726. VectorCopy( tag->origin, origin );
  2727. return 1;
  2728. }
  2729. /*
  2730. -------------------------
  2731. TAG_GetAngles
  2732. -------------------------
  2733. */
  2734. int TAG_GetAngles( const char *owner, const char *name, vec3_t angles )
  2735. {
  2736. reference_tag_t *tag = TAG_Find( owner, name );
  2737. if (!tag)
  2738. {
  2739. assert(0);
  2740. return 0;
  2741. }
  2742. VectorCopy( tag->angles, angles );
  2743. return 1;
  2744. }
  2745. /*
  2746. -------------------------
  2747. TAG_GetRadius
  2748. -------------------------
  2749. */
  2750. int TAG_GetRadius( const char *owner, const char *name )
  2751. {
  2752. reference_tag_t *tag = TAG_Find( owner, name );
  2753. if (!tag)
  2754. {
  2755. assert(0);
  2756. return 0;
  2757. }
  2758. return tag->radius;
  2759. }
  2760. /*
  2761. -------------------------
  2762. TAG_GetFlags
  2763. -------------------------
  2764. */
  2765. int TAG_GetFlags( const char *owner, const char *name )
  2766. {
  2767. reference_tag_t *tag = TAG_Find( owner, name );
  2768. if (!tag)
  2769. {
  2770. assert(0);
  2771. return 0;
  2772. }
  2773. return tag->flags;
  2774. }
  2775. /*
  2776. ==============================================================================
  2777. Spawn functions
  2778. ==============================================================================
  2779. */
  2780. /*QUAKED ref_tag_huge (0.5 0.5 1) (-128 -128 -128) (128 128 128)
  2781. SAME AS ref_tag, JUST BIGGER SO YOU CAN SEE THEM IN EDITOR ON HUGE MAPS!
  2782. Reference tags which can be positioned throughout the level.
  2783. These tags can later be refered to by the scripting system
  2784. so that their origins and angles can be referred to.
  2785. If you set angles on the tag, these will be retained.
  2786. If you target a ref_tag at an entity, that will set the ref_tag's
  2787. angles toward that entity.
  2788. If you set the ref_tag's ownername to the ownername of an entity,
  2789. it makes that entity is the owner of the ref_tag. This means
  2790. that the owner, and only the owner, may refer to that tag.
  2791. Tags may not have the same name as another tag with the same
  2792. owner. However, tags with different owners may have the same
  2793. name as one another. In this way, scripts can generically
  2794. refer to tags by name, and their owners will automatically
  2795. specifiy which tag is being referred to.
  2796. targetname - the name of this tag
  2797. ownername - the owner of this tag
  2798. target - use to point the tag at something for angles
  2799. */
  2800. /*QUAKED ref_tag (0.5 0.5 1) (-8 -8 -8) (8 8 8)
  2801. Reference tags which can be positioned throughout the level.
  2802. These tags can later be refered to by the scripting system
  2803. so that their origins and angles can be referred to.
  2804. If you set angles on the tag, these will be retained.
  2805. If you target a ref_tag at an entity, that will set the ref_tag's
  2806. angles toward that entity.
  2807. If you set the ref_tag's ownername to the ownername of an entity,
  2808. it makes that entity is the owner of the ref_tag. This means
  2809. that the owner, and only the owner, may refer to that tag.
  2810. Tags may not have the same name as another tag with the same
  2811. owner. However, tags with different owners may have the same
  2812. name as one another. In this way, scripts can generically
  2813. refer to tags by name, and their owners will automatically
  2814. specifiy which tag is being referred to.
  2815. targetname - the name of this tag
  2816. ownername - the owner of this tag
  2817. target - use to point the tag at something for angles
  2818. */
  2819. void ref_link ( gentity_t *ent )
  2820. {
  2821. reference_tag_t *tag;
  2822. if ( ent->target )
  2823. {
  2824. //TODO: Find the target and set our angles to that direction
  2825. gentity_t *target = G_Find( NULL, FOFS(targetname), ent->target );
  2826. vec3_t dir;
  2827. if ( target )
  2828. {
  2829. //Find the direction to the target
  2830. VectorSubtract( target->s.origin, ent->s.origin, dir );
  2831. VectorNormalize( dir );
  2832. vectoangles( dir, ent->s.angles );
  2833. //FIXME: Does pitch get flipped?
  2834. }
  2835. else
  2836. {
  2837. Com_Printf( S_COLOR_RED"ERROR: ref_tag (%s) has invalid target (%s)\n", ent->targetname, ent->target );
  2838. }
  2839. }
  2840. //Add the tag
  2841. tag = TAG_Add( ent->targetname, ent->ownername, ent->s.origin, ent->s.angles, 16, 0 );
  2842. //Delete immediately, cannot be refered to as an entity again
  2843. //NOTE: this means if you wanted to link them in a chain for, say, a path, you can't
  2844. G_FreeEntity( ent );
  2845. }
  2846. void SP_reference_tag ( gentity_t *ent )
  2847. {
  2848. if ( ent->target )
  2849. {
  2850. //Init cannot occur until all entities have been spawned
  2851. ent->think = ref_link;
  2852. ent->nextthink = level.time + START_TIME_LINK_ENTS;
  2853. }
  2854. else
  2855. {
  2856. ref_link( ent );
  2857. }
  2858. }
  2859. /*QUAKED misc_weapon_shooter (1 0 0) (-8 -8 -8) (8 8 8) ALTFIRE TOGGLE
  2860. ALTFIRE - fire the alt-fire of the chosen weapon
  2861. TOGGLE - keep firing until used again (fires at intervals of "wait")
  2862. "wait" - debounce time between refires (defaults to 500)
  2863. "target" - what to aim at (will update aim every frame if it's a moving target)
  2864. "weapon" - specify the weapon to use (default is WP_BLASTER)
  2865. WP_BRYAR_PISTOL
  2866. WP_BLASTER
  2867. WP_DISRUPTOR
  2868. WP_BOWCASTER
  2869. WP_REPEATER
  2870. WP_DEMP2
  2871. WP_FLECHETTE
  2872. WP_ROCKET_LAUNCHER
  2873. WP_THERMAL
  2874. WP_TRIP_MINE
  2875. WP_DET_PACK
  2876. WP_STUN_BATON
  2877. WP_EMPLACED_GUN
  2878. WP_BOT_LASER
  2879. WP_TURRET
  2880. WP_ATST_MAIN
  2881. WP_ATST_SIDE
  2882. WP_TIE_FIGHTER
  2883. WP_RAPID_FIRE_CONC
  2884. WP_BLASTER_PISTOL
  2885. */
  2886. //kind of hacky, but we have to do this with no dynamic allocation
  2887. #define MAX_SHOOTERS 16
  2888. typedef struct shooterClient_s
  2889. {
  2890. gclient_t cl;
  2891. qboolean inuse;
  2892. } shooterClient_t;
  2893. static shooterClient_t g_shooterClients[MAX_SHOOTERS];
  2894. static qboolean g_shooterClientInit = qfalse;
  2895. gclient_t *G_ClientForShooter(void)
  2896. {
  2897. int i = 0;
  2898. if (!g_shooterClientInit)
  2899. { //in theory it should be initialized to 0 on the stack, but just in case.
  2900. memset(g_shooterClients, 0, sizeof(shooterClient_t)*MAX_SHOOTERS);
  2901. g_shooterClientInit = qtrue;
  2902. }
  2903. while (i < MAX_SHOOTERS)
  2904. {
  2905. if (!g_shooterClients[i].inuse)
  2906. {
  2907. return &g_shooterClients[i].cl;
  2908. }
  2909. i++;
  2910. }
  2911. Com_Error(ERR_DROP, "No free shooter clients - hit MAX_SHOOTERS");
  2912. return NULL;
  2913. }
  2914. void G_FreeClientForShooter(gclient_t *cl)
  2915. {
  2916. int i = 0;
  2917. while (i < MAX_SHOOTERS)
  2918. {
  2919. if (&g_shooterClients[i].cl == cl)
  2920. {
  2921. g_shooterClients[i].inuse = qfalse;
  2922. return;
  2923. }
  2924. i++;
  2925. }
  2926. }
  2927. void misc_weapon_shooter_fire( gentity_t *self )
  2928. {
  2929. FireWeapon( self, (self->spawnflags&1) );
  2930. if ( (self->spawnflags&2) )
  2931. {//repeat
  2932. self->think = misc_weapon_shooter_fire;
  2933. self->nextthink = level.time + self->wait;
  2934. }
  2935. }
  2936. void misc_weapon_shooter_use ( gentity_t *self, gentity_t *other, gentity_t *activator )
  2937. {
  2938. if ( self->think == misc_weapon_shooter_fire )
  2939. {//repeating fire, stop
  2940. /*
  2941. G_FreeClientForShooter(self->client);
  2942. self->think = G_FreeEntity;
  2943. self->nextthink = level.time;
  2944. */
  2945. self->nextthink = 0;
  2946. return;
  2947. }
  2948. //otherwise, fire
  2949. misc_weapon_shooter_fire( self );
  2950. }
  2951. void misc_weapon_shooter_aim( gentity_t *self )
  2952. {
  2953. //update my aim
  2954. if ( self->target )
  2955. {
  2956. gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
  2957. if ( targ )
  2958. {
  2959. self->enemy = targ;
  2960. VectorSubtract( targ->r.currentOrigin, self->r.currentOrigin, self->pos1 );
  2961. VectorCopy( targ->r.currentOrigin, self->pos1 );
  2962. vectoangles( self->pos1, self->client->ps.viewangles );
  2963. SetClientViewAngle( self, self->client->ps.viewangles );
  2964. //FIXME: don't keep doing this unless target is a moving target?
  2965. self->nextthink = level.time + FRAMETIME;
  2966. }
  2967. else
  2968. {
  2969. self->enemy = NULL;
  2970. }
  2971. }
  2972. }
  2973. extern stringID_table_t WPTable[];
  2974. void SP_misc_weapon_shooter( gentity_t *self )
  2975. {
  2976. char *s;
  2977. //alloc a client just for the weapon code to use
  2978. self->client = G_ClientForShooter();//(gclient_s *)gi.Malloc(sizeof(gclient_s), TAG_G_ALLOC, qtrue);
  2979. G_SpawnString("weapon", "", &s);
  2980. //set weapon
  2981. self->s.weapon = self->client->ps.weapon = WP_BLASTER;
  2982. if ( s && s[0] )
  2983. {//use a different weapon
  2984. self->s.weapon = self->client->ps.weapon = GetIDForString( WPTable, s );
  2985. }
  2986. RegisterItem(BG_FindItemForWeapon(self->s.weapon));
  2987. //set where our muzzle is
  2988. VectorCopy( self->s.origin, self->client->renderInfo.muzzlePoint );
  2989. //permanently updated (don't need for MP)
  2990. //self->client->renderInfo.mPCalcTime = Q3_INFINITE;
  2991. //set up to link
  2992. if ( self->target )
  2993. {
  2994. self->think = misc_weapon_shooter_aim;
  2995. self->nextthink = level.time + START_TIME_LINK_ENTS;
  2996. }
  2997. else
  2998. {//just set aim angles
  2999. VectorCopy( self->s.angles, self->client->ps.viewangles );
  3000. AngleVectors( self->s.angles, self->pos1, NULL, NULL );
  3001. }
  3002. //set up to fire when used
  3003. self->use = misc_weapon_shooter_use;
  3004. if ( !self->wait )
  3005. {
  3006. self->wait = 500;
  3007. }
  3008. }
  3009. /*QUAKED misc_weather_zone (0 .5 .8) ?
  3010. Determines a region to check for weather contents - will significantly reduce load time
  3011. */
  3012. void SP_misc_weather_zone( gentity_t *ent )
  3013. {
  3014. G_FreeEntity(ent);
  3015. }