g_misc.cpp 98 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298
  1. // g_misc.c
  2. // leave this line at the top for all g_xxxx.cpp files...
  3. #include "g_headers.h"
  4. #include "g_local.h"
  5. #include "g_functions.h"
  6. #include "g_nav.h"
  7. #include "g_items.h"
  8. extern gentity_t *G_FindDoorTrigger( gentity_t *door );
  9. extern void G_SetEnemy( gentity_t *self, gentity_t *enemy );
  10. extern void SetMiscModelDefaults( gentity_t *ent, useFunc_t use_func, char *material, int solid_mask,int animFlag,
  11. qboolean take_damage, qboolean damage_model);
  12. #define MAX_AMMO_GIVE 4
  13. /*QUAKED func_group (0 0 0) ?
  14. Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities.
  15. q3map_onlyvertexlighting 1 = brush only gets vertex lighting (reduces bsp size!)
  16. */
  17. /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) LIGHT
  18. Used as a positional target for calculations in the utilities (spotlights, etc), but removed during gameplay.
  19. LIGHT - If this info_null is only targeted by a non-switchable light (a light without a targetname), it does NOT spawn in at all and doesn't count towards the # of entities on the map, even at map spawn/load
  20. */
  21. void SP_info_null( gentity_t *self ) {
  22. if ( (self->spawnflags&1) )
  23. {//only used as a light target, so bugger off
  24. G_FreeEntity( self );
  25. return;
  26. }
  27. //FIXME: store targetname and vector (origin) in a list for further reference... remove after 1st second of game?
  28. G_SetOrigin( self, self->s.origin );
  29. self->e_ThinkFunc = thinkF_G_FreeEntity;
  30. //Give other ents time to link
  31. self->nextthink = level.time + START_TIME_REMOVE_ENTS;
  32. }
  33. /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
  34. Used as a positional target for in-game calculation, like jumppad targets.
  35. target_position does the same thing
  36. */
  37. void SP_info_notnull( gentity_t *self ){
  38. //FIXME: store in ref_tag system?
  39. G_SetOrigin( self, self->s.origin );
  40. }
  41. /*QUAKED lightJunior (0 0.7 0.3) (-8 -8 -8) (8 8 8) nonlinear angle negative_spot negative_point
  42. Non-displayed light that only affects dynamic game models, but does not contribute to lightmaps
  43. "light" overrides the default 300 intensity.
  44. Nonlinear checkbox gives inverse square falloff instead of linear
  45. Angle adds light:surface angle calculations (only valid for "Linear" lights) (wolf)
  46. Lights pointed at a target will be spotlights.
  47. "radius" overrides the default 64 unit radius of a spotlight at the target point.
  48. "fade" falloff/radius adjustment value. multiply the run of the slope by "fade" (1.0f default) (only valid for "Linear" lights) (wolf)
  49. */
  50. /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) linear noIncidence START_OFF
  51. Non-displayed light.
  52. "light" overrides the default 300 intensity. - affects size
  53. a negative "light" will subtract the light's color
  54. 'Linear' checkbox gives linear falloff instead of inverse square
  55. 'noIncidence' checkbox makes lighting smoother
  56. Lights pointed at a target will be spotlights.
  57. "radius" overrides the default 64 unit radius of a spotlight at the target point.
  58. "scale" multiplier for the light intensity - does not affect size (default 1)
  59. greater than 1 is brighter, between 0 and 1 is dimmer.
  60. "color" sets the light's color
  61. "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)
  62. "style" to specify a specify light style, even for switchable lights!
  63. "style_off" light style to use when switched off (Only for switchable lights)
  64. 1 FLICKER (first variety)
  65. 2 SLOW STRONG PULSE
  66. 3 CANDLE (first variety)
  67. 4 FAST STROBE
  68. 5 GENTLE PULSE 1
  69. 6 FLICKER (second variety)
  70. 7 CANDLE (second variety)
  71. 8 CANDLE (third variety)
  72. 9 SLOW STROBE (fourth variety)
  73. 10 FLUORESCENT FLICKER
  74. 11 SLOW PULSE NOT FADE TO BLACK
  75. 12 FAST PULSE FOR JEREMY
  76. 13 Test Blending
  77. */
  78. static void misc_lightstyle_set ( gentity_t *ent)
  79. {
  80. const int mLightStyle = ent->count;
  81. const int mLightSwitchStyle = ent->bounceCount;
  82. const int mLightOffStyle = ent->fly_sound_debounce_time;
  83. if (!ent->misc_dlight_active)
  84. { //turn off
  85. if (mLightOffStyle) //i have a light style i'd like to use when off
  86. {
  87. char lightstyle[32];
  88. gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+0, lightstyle, 32);
  89. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
  90. gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+1, lightstyle, 32);
  91. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
  92. gi.GetConfigstring(CS_LIGHT_STYLES + (mLightOffStyle*3)+2, lightstyle, 32);
  93. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
  94. }else
  95. {
  96. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "a");
  97. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "a");
  98. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "a");
  99. }
  100. }
  101. else
  102. { //Turn myself on now
  103. if (mLightSwitchStyle) //i have a light style i'd like to use when on
  104. {
  105. char lightstyle[32];
  106. gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+0, lightstyle, 32);
  107. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, lightstyle);
  108. gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+1, lightstyle, 32);
  109. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, lightstyle);
  110. gi.GetConfigstring(CS_LIGHT_STYLES + (mLightSwitchStyle*3)+2, lightstyle, 32);
  111. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, lightstyle);
  112. }
  113. else
  114. {
  115. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+0, "z");
  116. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+1, "z");
  117. gi.SetConfigstring(CS_LIGHT_STYLES + (mLightStyle*3)+2, "z");
  118. }
  119. }
  120. }
  121. void SP_light( gentity_t *self ) {
  122. if (!self->targetname )
  123. {//if i don't have a light style switch, the i go away
  124. G_FreeEntity( self );
  125. return;
  126. }
  127. G_SpawnInt( "style", "0", &self->count );
  128. G_SpawnInt( "switch_style", "0", &self->bounceCount );
  129. G_SpawnInt( "style_off", "0", &self->fly_sound_debounce_time );
  130. G_SetOrigin( self, self->s.origin );
  131. gi.linkentity( self );
  132. self->e_UseFunc = useF_misc_dlight_use;
  133. self->e_clThinkFunc = clThinkF_NULL;
  134. self->s.eType = ET_GENERAL;
  135. self->misc_dlight_active = qfalse;
  136. self->svFlags |= SVF_NOCLIENT;
  137. if ( !(self->spawnflags & 4) )
  138. { //turn myself on now
  139. self->misc_dlight_active = qtrue;
  140. }
  141. misc_lightstyle_set (self);
  142. }
  143. void misc_dlight_use ( gentity_t *ent, gentity_t *other, gentity_t *activator )
  144. {
  145. G_ActivateBehavior(ent,BSET_USE);
  146. ent->misc_dlight_active = !ent->misc_dlight_active; //toggle
  147. misc_lightstyle_set (ent);
  148. }
  149. /*
  150. =================================================================================
  151. TELEPORTERS
  152. =================================================================================
  153. */
  154. void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles )
  155. {
  156. if ( player->NPC && ( player->NPC->aiFlags&NPCAI_FORM_TELE_NAV ) )
  157. {
  158. //My leader teleported, I was trying to catch up, take this off
  159. player->NPC->aiFlags &= ~NPCAI_FORM_TELE_NAV;
  160. }
  161. // unlink to make sure it can't possibly interfere with G_KillBox
  162. gi.unlinkentity (player);
  163. VectorCopy ( origin, player->client->ps.origin );
  164. player->client->ps.origin[2] += 1;
  165. VectorCopy ( player->client->ps.origin, player->currentOrigin );
  166. // spit the player out
  167. AngleVectors( angles, player->client->ps.velocity, NULL, NULL );
  168. VectorScale( player->client->ps.velocity, 0, player->client->ps.velocity );
  169. //player->client->ps.pm_time = 160; // hold time
  170. //player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
  171. // toggle the teleport bit so the client knows to not lerp
  172. player->client->ps.eFlags ^= EF_TELEPORT_BIT;
  173. // set angles
  174. SetClientViewAngle( player, angles );
  175. // kill anything at the destination
  176. G_KillBox (player);
  177. // save results of pmove
  178. PlayerStateToEntityState( &player->client->ps, &player->s );
  179. gi.linkentity (player);
  180. }
  181. void TeleportMover( gentity_t *mover, vec3_t origin, vec3_t diffAngles, qboolean snapAngle )
  182. {//FIXME: need an effect
  183. vec3_t oldAngle, newAngle;
  184. float speed;
  185. // unlink to make sure it can't possibly interfere with G_KillBox
  186. gi.unlinkentity (mover);
  187. //reposition it
  188. VectorCopy( origin, mover->s.pos.trBase );
  189. VectorCopy( origin, mover->currentOrigin );
  190. //Maintain their previous speed, but adjusted for new direction
  191. if ( snapAngle )
  192. {//not a diffAngle, actually an absolute angle
  193. vec3_t dir;
  194. VectorCopy( diffAngles, newAngle );
  195. AngleVectors( newAngle, dir, NULL, NULL );
  196. VectorNormalize( dir );//necessary?
  197. speed = VectorLength( mover->s.pos.trDelta );
  198. VectorScale( dir, speed, mover->s.pos.trDelta );
  199. mover->s.pos.trTime = level.time;
  200. VectorSubtract( newAngle, mover->s.apos.trBase, diffAngles );
  201. VectorCopy( newAngle, mover->s.apos.trBase );
  202. }
  203. else
  204. {
  205. speed = VectorNormalize( mover->s.pos.trDelta );
  206. vectoangles( mover->s.pos.trDelta, oldAngle );
  207. VectorAdd( oldAngle, diffAngles, newAngle );
  208. AngleVectors( newAngle, mover->s.pos.trDelta, NULL, NULL );
  209. VectorNormalize( mover->s.pos.trDelta );
  210. VectorScale( mover->s.pos.trDelta, speed, mover->s.pos.trDelta );
  211. mover->s.pos.trTime = level.time;
  212. //Maintain their previous angles, but adjusted to new orientation
  213. VectorAdd( mover->s.apos.trBase, diffAngles, mover->s.apos.trBase );
  214. }
  215. //Maintain their previous anglespeed, but adjusted to new orientation
  216. speed = VectorNormalize( mover->s.apos.trDelta );
  217. VectorAdd( mover->s.apos.trDelta, diffAngles, mover->s.apos.trDelta );
  218. VectorNormalize( mover->s.apos.trDelta );
  219. VectorScale( mover->s.apos.trDelta, speed, mover->s.apos.trDelta );
  220. mover->s.apos.trTime = level.time;
  221. //Tell them it was teleported this move
  222. mover->s.eFlags |= EF_TELEPORT_BIT;
  223. // kill anything at the destination
  224. //G_KillBox (mover);
  225. //FIXME: call touch func instead of killbox?
  226. gi.linkentity (mover);
  227. }
  228. void teleporter_touch (gentity_t *self, gentity_t *other, trace_t *trace)
  229. {
  230. gentity_t *dest;
  231. if (!other->client)
  232. return;
  233. dest = G_PickTarget( self->target );
  234. if (!dest) {
  235. gi.Printf ("Couldn't find teleporter destination\n");
  236. return;
  237. }
  238. TeleportPlayer( other, dest->s.origin, dest->s.angles );
  239. }
  240. /*QUAK-D misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
  241. Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
  242. */
  243. void SP_misc_teleporter (gentity_t *ent)
  244. {
  245. gentity_t *trig;
  246. if (!ent->target)
  247. {
  248. gi.Printf ("teleporter without a target.\n");
  249. G_FreeEntity( ent );
  250. return;
  251. }
  252. ent->s.modelindex = G_ModelIndex( "models/objects/dmspot.md3" );
  253. ent->s.clientNum = 1;
  254. // ent->s.loopSound = G_SoundIndex("sound/world/amb10.wav");
  255. ent->contents = CONTENTS_SOLID;
  256. G_SetOrigin( ent, ent->s.origin );
  257. VectorSet (ent->mins, -32, -32, -24);
  258. VectorSet (ent->maxs, 32, 32, -16);
  259. gi.linkentity (ent);
  260. trig = G_Spawn ();
  261. trig->e_TouchFunc = touchF_teleporter_touch;
  262. trig->contents = CONTENTS_TRIGGER;
  263. trig->target = ent->target;
  264. trig->owner = ent;
  265. G_SetOrigin( trig, ent->s.origin );
  266. VectorSet (trig->mins, -8, -8, 8);
  267. VectorSet (trig->maxs, 8, 8, 24);
  268. gi.linkentity (trig);
  269. }
  270. /*QUAK-D misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) - - NODRAW
  271. Point teleporters at these.
  272. */
  273. void SP_misc_teleporter_dest( gentity_t *ent ) {
  274. if ( ent->spawnflags & 4 ){
  275. return;
  276. }
  277. G_SetOrigin( ent, ent->s.origin );
  278. gi.linkentity (ent);
  279. }
  280. //===========================================================
  281. /*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) RMG SOLID
  282. "model" arbitrary .md3 or .ase file to display
  283. "_frame" "x" which frame from an animated md3
  284. "modelscale" "x" uniform scale
  285. "modelscale_vec" "x y z" scale model in each axis
  286. "_remap" "from to" remap a shader in this model
  287. turns into BSP triangles - not solid by default (click SOLID or use _clipmodel shader)
  288. */
  289. void SP_misc_model( gentity_t *ent ) {
  290. G_FreeEntity( ent );
  291. }
  292. /*QUAKED misc_model_static (1 0 0) (-16 -16 0) (16 16 16)
  293. "model" arbitrary .md3 file to display
  294. "_frame" "x" which frame from an animated md3
  295. "modelscale" "x" uniform scale
  296. "modelscale_vec" "x y z" scale model in each axis
  297. "zoffset" units to offset vertical culling position by, can be
  298. negative or positive. This does not affect the actual
  299. position of the model, only the culling position. Use
  300. it for models with stupid origins that go below the
  301. ground and whatnot.
  302. loaded as a model in the renderer - does not take up precious bsp space!
  303. */
  304. extern void CG_CreateMiscEntFromGent(gentity_t *ent, const vec3_t scale, float zOff); //cg_main.cpp
  305. void SP_misc_model_static(gentity_t *ent)
  306. {
  307. char *value;
  308. float temp;
  309. float zOff;
  310. vec3_t scale;
  311. G_SpawnString("modelscale_vec", "1 1 1", &value);
  312. sscanf( value, "%f %f %f", &scale[ 0 ], &scale[ 1 ], &scale[ 2 ] );
  313. G_SpawnFloat( "modelscale", "0", &temp);
  314. if (temp != 0.0f)
  315. {
  316. scale[ 0 ] = scale[ 1 ] = scale[ 2 ] = temp;
  317. }
  318. G_SpawnFloat( "zoffset", "0", &zOff);
  319. if (!ent->model)
  320. {
  321. Com_Error( ERR_DROP,"misc_model_static at %s with out a MODEL!\n", vtos(ent->s.origin) );
  322. }
  323. //we can be horrible and cheat since this is SP!
  324. CG_CreateMiscEntFromGent(ent, scale, zOff);
  325. G_FreeEntity( ent );
  326. }
  327. //===========================================================
  328. void setCamera ( gentity_t *ent )
  329. {
  330. vec3_t dir;
  331. gentity_t *target = 0;
  332. // frame holds the rotate speed
  333. if ( ent->owner->spawnflags & 1 )
  334. {
  335. ent->s.frame = 25;
  336. }
  337. else if ( ent->owner->spawnflags & 2 )
  338. {
  339. ent->s.frame = 75;
  340. }
  341. // clientNum holds the rotate offset
  342. ent->s.clientNum = ent->owner->s.clientNum;
  343. VectorCopy( ent->owner->s.origin, ent->s.origin2 );
  344. // see if the portal_camera has a target
  345. if (ent->owner->target) {
  346. target = G_PickTarget( ent->owner->target );
  347. }
  348. if ( target )
  349. {
  350. VectorSubtract( target->s.origin, ent->owner->s.origin, dir );
  351. VectorNormalize( dir );
  352. }
  353. else
  354. {
  355. G_SetMovedir( ent->owner->s.angles, dir );
  356. }
  357. ent->s.eventParm = DirToByte( dir );
  358. }
  359. void cycleCamera( gentity_t *self )
  360. {
  361. self->owner = G_Find( self->owner, FOFS(targetname), self->target );
  362. if ( self->owner == NULL )
  363. {
  364. //Uh oh! Not targeted at any ents! Or reached end of list? Which is it?
  365. //for now assume reached end of list and are cycling
  366. self->owner = G_Find( self->owner, FOFS(targetname), self->target );
  367. if ( self->owner == NULL )
  368. {//still didn't find one
  369. gi.Printf( "Couldn't find target for misc_portal_surface\n" );
  370. G_FreeEntity( self );
  371. return;
  372. }
  373. }
  374. setCamera( self );
  375. if ( self->e_ThinkFunc == thinkF_cycleCamera )
  376. {
  377. if ( self->owner->wait > 0 )
  378. {
  379. self->nextthink = level.time + self->owner->wait;
  380. }
  381. else
  382. {
  383. self->nextthink = level.time + self->wait;
  384. }
  385. }
  386. }
  387. void misc_portal_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  388. {
  389. cycleCamera( self );
  390. }
  391. void locateCamera( gentity_t *ent )
  392. {//FIXME: make this fadeout with distance from misc_camera_portal
  393. ent->owner = G_Find(NULL, FOFS(targetname), ent->target);
  394. if ( !ent->owner )
  395. {
  396. gi.Printf( "Couldn't find target for misc_portal_surface\n" );
  397. G_FreeEntity( ent );
  398. return;
  399. }
  400. setCamera( ent );
  401. if ( !ent->targetname )
  402. {//not targetted, so auto-cycle
  403. if ( G_Find(ent->owner, FOFS(targetname), ent->target) != NULL )
  404. {//targeted at more than one thing
  405. ent->e_ThinkFunc = thinkF_cycleCamera;
  406. if ( ent->owner->wait > 0 )
  407. {
  408. ent->nextthink = level.time + ent->owner->wait;
  409. }
  410. else
  411. {
  412. ent->nextthink = level.time + ent->wait;
  413. }
  414. }
  415. }
  416. }
  417. /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8)
  418. The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted.
  419. This must be within 64 world units of the surface!
  420. targetname - When used, cycles to the next misc_portal_camera it's targeted
  421. wait - makes it auto-cycle between all cameras it's pointed at at intevervals of specified number of seconds.
  422. cameras will be cycled through in the order they were created on the map.
  423. */
  424. void SP_misc_portal_surface(gentity_t *ent)
  425. {
  426. VectorClear( ent->mins );
  427. VectorClear( ent->maxs );
  428. gi.linkentity (ent);
  429. ent->svFlags = SVF_PORTAL;
  430. ent->s.eType = ET_PORTAL;
  431. ent->wait *= 1000;
  432. if ( !ent->target )
  433. {//mirror?
  434. VectorCopy( ent->s.origin, ent->s.origin2 );
  435. }
  436. else
  437. {
  438. ent->e_ThinkFunc = thinkF_locateCamera;
  439. ent->nextthink = level.time + 100;
  440. if ( ent->targetname )
  441. {
  442. ent->e_UseFunc = useF_misc_portal_use;
  443. }
  444. }
  445. }
  446. /*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate
  447. The target for a misc_portal_surface. You can set either angles or target another entity (NOT an info_null) to determine the direction of view.
  448. "roll" an angle modifier to orient the camera around the target vector;
  449. */
  450. void SP_misc_portal_camera(gentity_t *ent) {
  451. float roll;
  452. VectorClear( ent->mins );
  453. VectorClear( ent->maxs );
  454. gi.linkentity (ent);
  455. G_SpawnFloat( "roll", "0", &roll );
  456. ent->s.clientNum = roll/360.0 * 256;
  457. ent->wait *= 1000;
  458. }
  459. void G_SubBSPSpawnEntitiesFromString(const char *entityString, vec3_t posOffset, vec3_t angOffset);
  460. /*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16)
  461. "bspmodel" arbitrary .bsp file to display
  462. */
  463. void SP_misc_bsp(gentity_t *ent)
  464. {
  465. char temp[MAX_QPATH];
  466. char *out;
  467. float newAngle;
  468. int tempint;
  469. G_SpawnFloat( "angle", "0", &newAngle );
  470. if (newAngle != 0.0)
  471. {
  472. ent->s.angles[1] = newAngle;
  473. }
  474. // don't support rotation any other way
  475. ent->s.angles[0] = 0.0;
  476. ent->s.angles[2] = 0.0;
  477. G_SpawnString("bspmodel", "", &out);
  478. ent->s.eFlags = EF_PERMANENT;
  479. // Mainly for debugging
  480. G_SpawnInt( "spacing", "0", &tempint);
  481. ent->s.time2 = tempint;
  482. G_SpawnInt( "flatten", "0", &tempint);
  483. ent->s.time = tempint;
  484. Com_sprintf(temp, MAX_QPATH, "#%s", out);
  485. gi.SetBrushModel( ent, temp ); // SV_SetBrushModel -- sets mins and maxs
  486. G_BSPIndex(temp);
  487. level.mNumBSPInstances++;
  488. Com_sprintf(temp, MAX_QPATH, "%d-", level.mNumBSPInstances);
  489. VectorCopy(ent->s.origin, level.mOriginAdjust);
  490. level.mRotationAdjust = ent->s.angles[1];
  491. level.mTargetAdjust = temp;
  492. level.hasBspInstances = qtrue;
  493. level.mBSPInstanceDepth++;
  494. VectorCopy( ent->s.origin, ent->s.pos.trBase );
  495. VectorCopy( ent->s.origin, ent->currentOrigin );
  496. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  497. VectorCopy( ent->s.angles, ent->currentAngles );
  498. ent->s.eType = ET_MOVER;
  499. gi.linkentity (ent);
  500. const char *ents = gi.SetActiveSubBSP(ent->s.modelindex);
  501. if (ents)
  502. {
  503. G_SubBSPSpawnEntitiesFromString(ents, ent->s.origin, ent->s.angles);
  504. }
  505. gi.SetActiveSubBSP(-1);
  506. level.mBSPInstanceDepth--;
  507. }
  508. #define MAX_INSTANCE_TYPES 16
  509. void AddSpawnField(char *field, char *value);
  510. /*QUAKED terrain (1.0 1.0 1.0) ? NOVEHDMG
  511. NOVEHDMG - don't damage vehicles upon impact with this terrain
  512. Terrain entity
  513. It will stretch to the full height of the brush
  514. numPatches - integer number of patches to split the terrain brush into (default 200)
  515. terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8)
  516. seed - integer seed for random terrain generation (default 0)
  517. textureScale - float scale of texture (default 0.005)
  518. heightmap - name of heightmap data image to use, located in heightmaps/*.png. (must be PNG format)
  519. terrainDef - defines how the game textures the terrain (file is base/ext_data/rmg/*.terrain - default is grassyhills)
  520. instanceDef - defines which bsp instances appear
  521. miscentDef - defines which client models spawn on the terrain (file is base/ext_data/rmg/*.miscents)
  522. densityMap - how dense the client models are packed
  523. */
  524. void SP_terrain(gentity_t *ent)
  525. {
  526. #ifdef _XBOX
  527. assert(0);
  528. #else
  529. char temp[MAX_INFO_STRING];
  530. char final[MAX_QPATH];
  531. // char seed[MAX_QPATH];
  532. // char missionType[MAX_QPATH];
  533. // char soundSet[MAX_QPATH];
  534. int shaderNum, i;
  535. char *value;
  536. int terrainID;
  537. //k, found a terrain, just set rmg to 1.
  538. //This should always get set before RE_LoadWorldMap and all that is
  539. //called which is all that matters.
  540. //gi.cvar_set("RMG", "1");
  541. VectorClear (ent->s.angles);
  542. gi.SetBrushModel( ent, ent->model );
  543. // Get the shader from the top of the brush
  544. // shaderNum = gi.CM_GetShaderNum(s.modelindex);
  545. shaderNum = 0;
  546. //rww - Why not do this all the time? Not like terrain entities are used when you don't want them to be terrain.
  547. /* if (g_RMG->integer)
  548. {
  549. gi.Cvar_VariableStringBuffer("RMG_seed", seed, MAX_QPATH);
  550. gi.Cvar_VariableStringBuffer("RMG_mission", missionType, MAX_QPATH);
  551. // gi.Cvar_VariableStringBuffer("RMG_soundset", soundSet, MAX_QPATH);
  552. // gi.SetConfigstring(CS_AMBIENT_SOUNDSETS, soundSet );
  553. }
  554. */
  555. // Arbitrary (but sane) limits to the number of terxels
  556. // if((mTerxels < MIN_TERXELS) || (mTerxels > MAX_TERXELS))
  557. {
  558. // Com_printf("G_Terrain: terxels out of range - defaulting to 4\n");
  559. // mTerxels = 4;
  560. }
  561. // Get info required for the common init
  562. temp[0] = 0;
  563. G_SpawnString("heightmap", "", &value);
  564. Info_SetValueForKey(temp, "heightMap", value);
  565. G_SpawnString("numpatches", "400", &value);
  566. Info_SetValueForKey(temp, "numPatches", va("%d", atoi(value)));
  567. G_SpawnString("terxels", "4", &value);
  568. Info_SetValueForKey(temp, "terxels", va("%d", atoi(value)));
  569. //Info_SetValueForKey(temp, "seed", seed);
  570. Info_SetValueForKey(temp, "minx", va("%f", ent->mins[0]));
  571. Info_SetValueForKey(temp, "miny", va("%f", ent->mins[1]));
  572. Info_SetValueForKey(temp, "minz", va("%f", ent->mins[2]));
  573. Info_SetValueForKey(temp, "maxx", va("%f", ent->maxs[0]));
  574. Info_SetValueForKey(temp, "maxy", va("%f", ent->maxs[1]));
  575. Info_SetValueForKey(temp, "maxz", va("%f", ent->maxs[2]));
  576. Info_SetValueForKey(temp, "modelIndex", va("%d", ent->s.modelindex));
  577. G_SpawnString("terraindef", "grassyhills", &value);
  578. Info_SetValueForKey(temp, "terrainDef", value);
  579. G_SpawnString("instancedef", "", &value);
  580. Info_SetValueForKey(temp, "instanceDef", value);
  581. G_SpawnString("miscentdef", "", &value);
  582. Info_SetValueForKey(temp, "miscentDef", value);
  583. //Info_SetValueForKey(temp, "missionType", missionType);
  584. for(i = 0; i < MAX_INSTANCE_TYPES; i++)
  585. {
  586. gi.Cvar_VariableStringBuffer(va("RMG_instance%d", i), final, MAX_QPATH);
  587. if(strlen(final))
  588. {
  589. Info_SetValueForKey(temp, va("inst%d", i), final);
  590. }
  591. }
  592. // Set additional data required on the client only
  593. G_SpawnString("densitymap", "", &value);
  594. Info_SetValueForKey(temp, "densityMap", value);
  595. Info_SetValueForKey(temp, "shader", va("%d", shaderNum));
  596. G_SpawnString("texturescale", "0.005", &value);
  597. Info_SetValueForKey(temp, "texturescale", va("%f", atof(value)));
  598. // Initialise the common aspects of the terrain
  599. terrainID = gi.CM_RegisterTerrain(temp);
  600. // SetCommon(common);
  601. Info_SetValueForKey(temp, "terrainId", va("%d", terrainID));
  602. // Let the entity know if it is random generated or not
  603. // SetIsRandom(common->GetIsRandom());
  604. // Let the game remember everything
  605. // level.landScapes[terrainID] = ent;
  606. //rww - I'm not doing this. Because it didn't even appear to be used. Is it?
  607. // Send all the data down to the client
  608. gi.SetConfigstring(CS_TERRAINS + terrainID, temp);
  609. // Make sure the contents are properly set
  610. ent->contents = CONTENTS_TERRAIN;
  611. ent->svFlags = SVF_NOCLIENT;
  612. ent->s.eFlags = EF_PERMANENT;
  613. ent->s.eType = ET_TERRAIN;
  614. // Hook into the world so physics will work
  615. gi.linkentity(ent);
  616. // If running RMG then initialize the terrain and handle team skins
  617. //rww - Why not do this all the time? Not like terrain entities are used when you don't want them to be terrain.
  618. /* not using RMG
  619. if ( g_RMG->integer )
  620. {
  621. gi.RMG_Init(terrainID);
  622. }
  623. */
  624. #endif // _XBOX
  625. }
  626. //rww - Called by skyportal entities. This will check through entities and flag them
  627. //as portal ents if they are in the same pvs as a skyportal entity and pass
  628. //a direct point trace check between origins. I really wanted to use an eFlag for
  629. //flagging portal entities, but too many entities like to reset their eFlags.
  630. //Note that this was not part of the original wolf sky portal stuff.
  631. void G_PortalifyEntities(gentity_t *ent)
  632. {
  633. int i = 0;
  634. gentity_t *scan = NULL;
  635. while (i < MAX_GENTITIES)
  636. {
  637. scan = &g_entities[i];
  638. if (scan && scan->inuse && scan->s.number != ent->s.number && gi.inPVS(ent->s.origin, scan->currentOrigin))
  639. {
  640. trace_t tr;
  641. gi.trace(&tr, ent->s.origin, vec3_origin, vec3_origin, scan->currentOrigin, ent->s.number, CONTENTS_SOLID, G2_NOCOLLIDE, 0);
  642. if (tr.fraction == 1.0 || (tr.entityNum == scan->s.number && tr.entityNum != ENTITYNUM_NONE && tr.entityNum != ENTITYNUM_WORLD))
  643. {
  644. scan->s.isPortalEnt = qtrue; //he's flagged now
  645. }
  646. }
  647. i++;
  648. }
  649. ent->e_ThinkFunc = thinkF_G_FreeEntity; //the portal entity is no longer needed because its information is stored in a config string.
  650. ent->nextthink = level.time;
  651. }
  652. /*QUAKED misc_skyportal (.6 .7 .7) (-8 -8 0) (8 8 16)
  653. To have the portal sky fogged, enter any of the following values:
  654. "fogcolor" (r g b) (values 0.0-1.0)
  655. "fognear" distance from entity to start fogging
  656. "fogfar" distance from entity that fog is opaque
  657. rww - NOTE: fog doesn't work with these currently (at least not in this way).
  658. Use a fog brush instead.
  659. */
  660. void SP_misc_skyportal (gentity_t *ent)
  661. {
  662. vec3_t fogv; //----(SA)
  663. int fogn; //----(SA)
  664. int fogf; //----(SA)
  665. int isfog = 0; // (SA)
  666. isfog += G_SpawnVector ("fogcolor", "0 0 0", fogv);
  667. isfog += G_SpawnInt ("fognear", "0", &fogn);
  668. isfog += G_SpawnInt ("fogfar", "300", &fogf);
  669. gi.SetConfigstring( CS_SKYBOXORG, va("%.2f %.2f %.2f %i %.2f %.2f %.2f %i %i", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], isfog, fogv[0], fogv[1], fogv[2], fogn, fogf ) );
  670. ent->e_ThinkFunc = thinkF_G_PortalifyEntities;
  671. ent->nextthink = level.time + 1050; //give it some time first so that all other entities are spawned.
  672. }
  673. extern qboolean G_ClearViewEntity( gentity_t *ent );
  674. extern void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity );
  675. extern void SP_fx_runner( gentity_t *ent );
  676. void camera_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
  677. {
  678. if ( player && player->client && player->client->ps.viewEntity == self->s.number )
  679. {
  680. G_UseTargets2( self, player, self->target4 );
  681. G_ClearViewEntity( player );
  682. G_Sound( player, self->soundPos2 );
  683. }
  684. G_UseTargets2( self, player, self->closetarget );
  685. //FIXME: explosion fx/sound
  686. //leave sparks at origin- where base's pole is still at?
  687. gentity_t *sparks = G_Spawn();
  688. if ( sparks )
  689. {
  690. sparks->fxFile = "sparks/spark";
  691. sparks->delay = 100;
  692. sparks->random = 500;
  693. sparks->s.angles[0] = 180;//point down
  694. VectorCopy( self->s.origin, sparks->s.origin );
  695. SP_fx_runner( sparks );
  696. }
  697. //bye!
  698. self->takedamage = qfalse;
  699. self->contents = 0;
  700. self->s.eFlags |= EF_NODRAW;
  701. self->s.modelindex = 0;
  702. }
  703. void camera_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  704. {
  705. if ( !activator || !activator->client || activator->s.number )
  706. {//really only usable by the player
  707. return;
  708. }
  709. self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms
  710. // FIXME: I guess we are allowing them to switch to a dead camera. Maybe we should conditionally do this though?
  711. if ( /*self->health <= 0 ||*/ (player && player->client && player->client->ps.viewEntity == self->s.number) )
  712. {//I'm already viewEntity, or I'm destroyed, find next
  713. gentity_t *next = NULL;
  714. if ( self->target2 != NULL )
  715. {
  716. next = G_Find( NULL, FOFS(targetname), self->target2 );
  717. }
  718. if ( next )
  719. {//found another one
  720. if ( !Q_stricmp( "misc_camera", next->classname ) )
  721. {//make sure it's another camera
  722. camera_use( next, other, activator );
  723. }
  724. }
  725. else //if ( self->health > 0 )
  726. {//I was the last (only?) one, clear out the viewentity
  727. G_UseTargets2( self, activator, self->target4 );
  728. G_ClearViewEntity( activator );
  729. G_Sound( activator, self->soundPos2 );
  730. }
  731. }
  732. else
  733. {//set me as view entity
  734. G_UseTargets2( self, activator, self->target3 );
  735. self->s.eFlags |= EF_NODRAW;
  736. self->s.modelindex = 0;
  737. G_SetViewEntity( activator, self );
  738. G_Sound( activator, self->soundPos1 );
  739. }
  740. }
  741. void camera_aim( gentity_t *self )
  742. {
  743. self->nextthink = level.time + FRAMETIME;
  744. if ( player && player->client && player->client->ps.viewEntity == self->s.number )
  745. {//I am the viewEntity
  746. if ( player->client->usercmd.forwardmove || player->client->usercmd.rightmove || player->client->usercmd.upmove )
  747. {//player wants to back out of camera
  748. G_UseTargets2( self, player, self->target4 );
  749. G_ClearViewEntity( player );
  750. G_Sound( player, self->soundPos2 );
  751. self->painDebounceTime = level.time + (self->wait*1000);//FRAMETIME*5;//don't check for player buttons for 500 ms
  752. if ( player->client->usercmd.upmove > 0 )
  753. {//stop player from doing anything for a half second after
  754. player->aimDebounceTime = level.time + 500;
  755. }
  756. }
  757. else if ( self->painDebounceTime < level.time )
  758. {//check for use button
  759. if ( (player->client->usercmd.buttons&BUTTON_USE) )
  760. {//player pressed use button, wants to cycle to next
  761. camera_use( self, player, player );
  762. }
  763. }
  764. else
  765. {//don't draw me when being looked through
  766. self->s.eFlags |= EF_NODRAW;
  767. self->s.modelindex = 0;
  768. }
  769. }
  770. else if ( self->health > 0 )
  771. {//still alive, can draw me again
  772. self->s.eFlags &= ~EF_NODRAW;
  773. self->s.modelindex = self->s.modelindex3;
  774. }
  775. //update my aim
  776. if ( self->target )
  777. {
  778. gentity_t *targ = G_Find( NULL, FOFS(targetname), self->target );
  779. if ( targ )
  780. {
  781. vec3_t angles, dir;
  782. VectorSubtract( targ->currentOrigin, self->currentOrigin, dir );
  783. vectoangles( dir, angles );
  784. //FIXME: if a G2 model, do a bone override..???
  785. VectorCopy( self->currentAngles, self->s.apos.trBase );
  786. for( int i = 0; i < 3; i++ )
  787. {
  788. angles[i] = AngleNormalize180( angles[i] );
  789. self->s.apos.trDelta[i] = AngleNormalize180( (angles[i]-self->currentAngles[i])*10 );
  790. }
  791. //VectorSubtract( angles, self->currentAngles, self->s.apos.trDelta );
  792. //VectorScale( self->s.apos.trDelta, 10, self->s.apos.trDelta );
  793. self->s.apos.trTime = level.time;
  794. self->s.apos.trDuration = FRAMETIME;
  795. VectorCopy( angles, self->currentAngles );
  796. if ( DistanceSquared( self->currentAngles, self->lastAngles ) > 0.01f ) // if it moved at all, start a loop sound? not exactly the "bestest" solution
  797. {
  798. self->s.loopSound = G_SoundIndex( "sound/movers/objects/cameramove_lp2" );
  799. }
  800. else
  801. {
  802. self->s.loopSound = 0; // not moving so don't bother
  803. }
  804. VectorCopy( self->currentAngles, self->lastAngles );
  805. //G_SetAngles( self, angles );
  806. }
  807. }
  808. }
  809. /*QUAKED misc_camera (0 0 1) (-8 -8 -12) (8 8 16) VULNERABLE
  810. A model in the world that can be used by the player to look through it's viewpoint
  811. There will be a video overlay instead of the regular HUD and the FOV will be wider
  812. VULNERABLE - allow camera to be destroyed
  813. "target" - camera will remain pointed at this entity (if it's a train or some other moving object, it will keep following it)
  814. "target2" - when player is in this camera and hits the use button, it will cycle to this next camera (if no target2, returns to normal view )
  815. "target3" - thing to use when player enters this camera view
  816. "target4" - thing to use when player leaves this camera view
  817. "closetarget" - (sigh...) yet another target, fired this when it's destroyed
  818. "wait" - how long to wait between being used (default 0.5)
  819. */
  820. void SP_misc_camera( gentity_t *self )
  821. {
  822. G_SpawnFloat( "wait", "0.5", &self->wait );
  823. //FIXME: spawn base, too
  824. gentity_t *base = G_Spawn();
  825. if ( base )
  826. {
  827. base->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam_base.md3" );
  828. VectorCopy( self->s.origin, base->s.origin );
  829. base->s.origin[2] += 16;
  830. G_SetOrigin( base, base->s.origin );
  831. G_SetAngles( base, self->s.angles );
  832. gi.linkentity( base );
  833. }
  834. self->s.modelindex3 = self->s.modelindex = G_ModelIndex( "models/map_objects/kejim/impcam.md3" );
  835. self->soundPos1 = G_SoundIndex( "sound/movers/camera_on.mp3" );
  836. self->soundPos2 = G_SoundIndex( "sound/movers/camera_off.mp3" );
  837. G_SoundIndex( "sound/movers/objects/cameramove_lp2" );
  838. G_SetOrigin( self, self->s.origin );
  839. G_SetAngles( self, self->s.angles );
  840. self->s.apos.trType = TR_LINEAR_STOP;//TR_INTERPOLATE;//
  841. self->alt_fire = qtrue;
  842. VectorSet( self->mins, -8, -8, -12 );
  843. VectorSet( self->maxs, 8, 8, 0 );
  844. self->contents = CONTENTS_SOLID;
  845. gi.linkentity( self );
  846. self->fxID = G_EffectIndex( "sparks/spark" );
  847. if ( self->spawnflags & 1 ) // VULNERABLE
  848. {
  849. self->takedamage = qtrue;
  850. }
  851. self->health = 10;
  852. self->e_DieFunc = dieF_camera_die;
  853. self->e_UseFunc = useF_camera_use;
  854. self->e_ThinkFunc = thinkF_camera_aim;
  855. self->nextthink = level.time + START_TIME_LINK_ENTS;
  856. }
  857. /*
  858. ======================================================================
  859. SHOOTERS
  860. ======================================================================
  861. */
  862. void Use_Shooter( gentity_t *ent, gentity_t *other, gentity_t *activator )
  863. {
  864. /* vec3_t dir;
  865. float deg;
  866. vec3_t up, right;
  867. */
  868. G_ActivateBehavior(ent,BSET_USE);
  869. /*
  870. // see if we have a target
  871. if ( ent->enemy ) {
  872. VectorSubtract( ent->enemy->currentOrigin, ent->s.origin, dir );
  873. VectorNormalize( dir );
  874. } else {
  875. VectorCopy( ent->movedir, dir );
  876. }
  877. // randomize a bit
  878. PerpendicularVector( up, dir );
  879. CrossProduct( up, dir, right );
  880. deg = crandom() * ent->random;
  881. VectorMA( dir, deg, up, dir );
  882. deg = crandom() * ent->random;
  883. VectorMA( dir, deg, right, dir );
  884. VectorNormalize( dir );
  885. switch ( ent->s.weapon )
  886. {
  887. case WP_GRENADE_LAUNCHER:
  888. fire_grenade( ent, ent->s.origin, dir );
  889. break;
  890. case WP_ROCKET_LAUNCHER:
  891. fire_rocket( ent, ent->s.origin, dir );
  892. break;
  893. case WP_PLASMAGUN:
  894. fire_plasma( ent, ent->s.origin, dir );
  895. break;
  896. }
  897. G_AddEvent( ent, EV_FIRE_WEAPON, 0 );
  898. */
  899. }
  900. void InitShooter( gentity_t *ent, int weapon ) {
  901. ent->e_UseFunc = useF_Use_Shooter;
  902. ent->s.weapon = weapon;
  903. RegisterItem( FindItemForWeapon( (weapon_t) weapon ) );
  904. G_SetMovedir( ent->s.angles, ent->movedir );
  905. if ( !ent->random ) {
  906. ent->random = 1.0;
  907. }
  908. ent->random = sin( M_PI * ent->random / 180 );
  909. // target might be a moving object, so we can't set movedir for it
  910. if ( ent->target ) {
  911. G_SetEnemy(ent, G_PickTarget( ent->target ));
  912. }
  913. gi.linkentity( ent );
  914. }
  915. /*QUAK-ED shooter_rocket (1 0 0) (-16 -16 -16) (16 16 16)
  916. Fires at either the target or the current direction.
  917. "random" the number of degrees of deviance from the taget. (1.0 default)
  918. */
  919. void SP_shooter_rocket( gentity_t *ent )
  920. {
  921. // InitShooter( ent, WP_TETRION_DISRUPTOR );
  922. }
  923. /*QUAK-ED shooter_plasma (1 0 0) (-16 -16 -16) (16 16 16)
  924. Fires at either the target or the current direction.
  925. "random" is the number of degrees of deviance from the taget. (1.0 default)
  926. */
  927. void SP_shooter_plasma( gentity_t *ent )
  928. {
  929. InitShooter( ent, WP_BRYAR_PISTOL);
  930. }
  931. /*QUAK-ED shooter_grenade (1 0 0) (-16 -16 -16) (16 16 16)
  932. Fires at either the target or the current direction.
  933. "random" is the number of degrees of deviance from the taget. (1.0 default)
  934. */
  935. void SP_shooter_grenade( gentity_t *ent )
  936. {
  937. // InitShooter( ent, WP_GRENADE_LAUNCHER);
  938. }
  939. /*QUAKED object_cargo_barrel1 (1 0 0) (-16 -16 -16) (16 16 29) SMALLER KLINGON NO_SMOKE POWDERKEG
  940. Cargo Barrel
  941. if given a targetname, using it makes it explode
  942. SMALLER - (-8, -8, -16) (8, 8, 8)
  943. KLINGON - klingon style barrel
  944. NO_SMOKE - will not leave lingering smoke cloud when killed
  945. POWDERKEG - wooden explosive barrel
  946. health default = 20
  947. splashDamage default = 100
  948. splashRadius default = 200
  949. */
  950. void SP_object_cargo_barrel1(gentity_t *ent)
  951. {
  952. if(ent->spawnflags & 8)
  953. {
  954. ent->s.modelindex = G_ModelIndex( "/models/mapobjects/cargo/barrel_wood2.md3" );
  955. // ent->sounds = G_SoundIndex("sound/weapons/explosions/explode3.wav");
  956. }
  957. else if(ent->spawnflags & 2)
  958. {
  959. ent->s.modelindex = G_ModelIndex( "/models/mapobjects/scavenger/k_barrel.md3" );
  960. // ent->sounds = G_SoundIndex("sound/weapons/explosions/explode4.wav");
  961. }
  962. else
  963. {
  964. ent->s.modelindex = G_ModelIndex( va("/models/mapobjects/cargo/barrel%i.md3", Q_irand( 0, 2 )) );
  965. // ent->sounds = G_SoundIndex("sound/weapons/explosions/explode1.wav");
  966. }
  967. ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE;
  968. if ( ent->spawnflags & 1 )
  969. {
  970. VectorSet (ent->mins, -8, -8, -16);
  971. VectorSet (ent->maxs, 8, 8, 8);
  972. }
  973. else
  974. {
  975. VectorSet (ent->mins, -16, -16, -16);
  976. VectorSet (ent->maxs, 16, 16, 29);
  977. }
  978. G_SetOrigin( ent, ent->s.origin );
  979. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  980. if(!ent->health)
  981. ent->health = 20;
  982. if(!ent->splashDamage)
  983. ent->splashDamage = 100;
  984. if(!ent->splashRadius)
  985. ent->splashRadius = 200;
  986. ent->takedamage = qtrue;
  987. ent->e_DieFunc = dieF_ExplodeDeath_Wait;
  988. if(ent->targetname)
  989. ent->e_UseFunc = useF_GoExplodeDeath;
  990. gi.linkentity (ent);
  991. }
  992. /*QUAKED misc_dlight (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) STARTOFF FADEON FADEOFF PULSE
  993. Dynamic light, toggles on and off when used
  994. STARTOFF - Starts off
  995. FADEON - Fades from 0 Radius to start Radius
  996. FADEOFF - Fades from current Radius to 0 Radius before turning off
  997. PULSE - This flag must be checked if you want it to fade/switch between start and final RGBA, otherwise it will just sit at startRGBA
  998. ownername - Will display the light at the origin of the entity with this targetname
  999. startRGBA - Red Green Blue Radius to start with - This MUST be set or your light won't do anything
  1000. These next values are used only if you want to fade/switch between 2 values (PULSE flag on)
  1001. finalRGBA - Red Green Blue Radius to end with
  1002. speed - how long to take to fade from start to final and final to start. Also how long to fade on and off if appropriate flags are checked (seconds)
  1003. finaltime - how long to hold at final (seconds)
  1004. starttime - how long to hold at start (seconds)
  1005. TODO: Add random to speed/radius?
  1006. */
  1007. void SP_misc_dlight(gentity_t *ent)
  1008. {
  1009. G_SetOrigin( ent, ent->s.origin );
  1010. gi.linkentity( ent );
  1011. ent->speed *= 1000;
  1012. ent->wait *= 1000;
  1013. ent->radius *= 1000;
  1014. //FIXME: attach self to a train or something?
  1015. ent->e_UseFunc = useF_misc_dlight_use;
  1016. ent->misc_dlight_active = qfalse;
  1017. ent->e_clThinkFunc = clThinkF_NULL;
  1018. ent->s.eType = ET_GENERAL;
  1019. //Delay first think so we can find owner
  1020. if ( ent->ownername )
  1021. {
  1022. ent->e_ThinkFunc = thinkF_misc_dlight_think;
  1023. ent->nextthink = level.time + START_TIME_LINK_ENTS;
  1024. }
  1025. if ( !(ent->spawnflags & 1) )
  1026. {//Turn myself on now
  1027. GEntity_UseFunc( ent, ent, ent );
  1028. }
  1029. }
  1030. void misc_dlight_use_old ( gentity_t *ent, gentity_t *other, gentity_t *activator )
  1031. {
  1032. G_ActivateBehavior(ent,BSET_USE);
  1033. if ( ent->misc_dlight_active )
  1034. {//We're on, turn off
  1035. if ( ent->spawnflags & 4 )
  1036. {//fade off
  1037. ent->pushDebounceTime = 3;
  1038. }
  1039. else
  1040. {
  1041. ent->misc_dlight_active = qfalse;
  1042. ent->e_clThinkFunc = clThinkF_NULL;
  1043. ent->s.eType = ET_GENERAL;
  1044. ent->svFlags &= ~SVF_BROADCAST;
  1045. }
  1046. }
  1047. else
  1048. {
  1049. //Start at start regardless of when we were turned off
  1050. if ( ent->spawnflags & 4 )
  1051. {//fade on
  1052. ent->pushDebounceTime = 2;
  1053. }
  1054. else
  1055. {//Just start on
  1056. ent->pushDebounceTime = 0;
  1057. }
  1058. ent->painDebounceTime = level.time;
  1059. ent->misc_dlight_active = qtrue;
  1060. ent->e_ThinkFunc = thinkF_misc_dlight_think;
  1061. ent->nextthink = level.time + FRAMETIME;
  1062. ent->e_clThinkFunc = clThinkF_CG_DLightThink;
  1063. ent->s.eType = ET_THINKER;
  1064. ent->svFlags |= SVF_BROADCAST;// Broadcast to all clients
  1065. }
  1066. }
  1067. void misc_dlight_think ( gentity_t *ent )
  1068. {
  1069. //Stay Attached to owner
  1070. if ( ent->owner )
  1071. {
  1072. G_SetOrigin( ent, ent->owner->currentOrigin );
  1073. gi.linkentity( ent );
  1074. }
  1075. else if ( ent->ownername )
  1076. {
  1077. ent->owner = G_Find( NULL, FOFS(targetname), ent->ownername );
  1078. ent->ownername = NULL;
  1079. }
  1080. ent->nextthink = level.time + FRAMETIME;
  1081. }
  1082. void station_pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc )
  1083. {
  1084. // self->s.modelindex = G_ModelIndex("/models/mapobjects/stasis/plugin2_in.md3");
  1085. // self->s.eFlags &= ~ EF_ANIM_ALLFAST;
  1086. // self->s.eFlags |= EF_ANIM_ONCE;
  1087. // gi.linkentity (self);
  1088. self->s.modelindex = self->s.modelindex2;
  1089. gi.linkentity (self);
  1090. }
  1091. // --------------------------------------------------------------------
  1092. //
  1093. // HEALTH/ARMOR plugin functions
  1094. //
  1095. // --------------------------------------------------------------------
  1096. void health_use( gentity_t *self, gentity_t *other, gentity_t *activator);
  1097. int ITM_AddArmor (gentity_t *ent, int count);
  1098. int ITM_AddHealth (gentity_t *ent, int count);
  1099. void health_shutdown( gentity_t *self )
  1100. {
  1101. if (!(self->s.eFlags & EF_ANIM_ONCE))
  1102. {
  1103. self->s.eFlags &= ~ EF_ANIM_ALLFAST;
  1104. self->s.eFlags |= EF_ANIM_ONCE;
  1105. // Switch to and animate its used up model.
  1106. if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2.md3"))
  1107. {
  1108. self->s.modelindex = self->s.modelindex2;
  1109. }
  1110. else if (!Q_stricmp(self->model,"models/mapobjects/borg/plugin2.md3"))
  1111. {
  1112. self->s.modelindex = self->s.modelindex2;
  1113. }
  1114. else if (!Q_stricmp(self->model,"models/mapobjects/stasis/plugin2_floor.md3"))
  1115. {
  1116. self->s.modelindex = self->s.modelindex2;
  1117. // G_Sound(self, G_SoundIndex("sound/ambience/stasis/shrinkage1.wav") );
  1118. }
  1119. else if (!Q_stricmp(self->model,"models/mapobjects/forge/panels.md3"))
  1120. {
  1121. self->s.modelindex = self->s.modelindex2;
  1122. }
  1123. gi.linkentity (self);
  1124. }
  1125. }
  1126. void health_think( gentity_t *ent )
  1127. {
  1128. int dif;
  1129. // He's dead, Jim. Don't give him health
  1130. if (ent->enemy->health<1)
  1131. {
  1132. ent->count = 0;
  1133. ent->e_ThinkFunc = thinkF_NULL;
  1134. }
  1135. // Still has power to give
  1136. if (ent->count > 0)
  1137. {
  1138. // For every 3 points of health, you get 1 point of armor
  1139. // BUT!!! after health is filled up, you get the full energy going to armor
  1140. dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] - ent->enemy->health;
  1141. if (dif > 3 )
  1142. {
  1143. dif= 3;
  1144. }
  1145. else if (dif < 0)
  1146. {
  1147. dif= 0;
  1148. }
  1149. if (dif > ent->count) // Can't give more than count
  1150. {
  1151. dif = ent->count;
  1152. }
  1153. if ((ITM_AddHealth (ent->enemy,dif)) && (dif>0))
  1154. {
  1155. ITM_AddArmor (ent->enemy,1); // 1 armor for every 3 health
  1156. ent->count-=dif;
  1157. ent->nextthink = level.time + 10;
  1158. }
  1159. else // User has taken all health he can hold, see about giving it all to armor
  1160. {
  1161. dif = ent->enemy->client->ps.stats[STAT_MAX_HEALTH] -
  1162. ent->enemy->client->ps.stats[STAT_ARMOR];
  1163. if (dif > 3)
  1164. {
  1165. dif = 3;
  1166. }
  1167. else if (dif < 0)
  1168. {
  1169. dif= 0;
  1170. }
  1171. if (ent->count < dif) // Can't give more than count
  1172. {
  1173. dif = ent->count;
  1174. }
  1175. if ((!ITM_AddArmor(ent->enemy,dif)) || (dif<=0))
  1176. {
  1177. ent->e_UseFunc = useF_health_use;
  1178. ent->e_ThinkFunc = thinkF_NULL;
  1179. }
  1180. else
  1181. {
  1182. ent->count-=dif;
  1183. ent->nextthink = level.time + 10;
  1184. }
  1185. }
  1186. }
  1187. if (ent->count < 1)
  1188. {
  1189. health_shutdown(ent);
  1190. }
  1191. }
  1192. void misc_model_useup( gentity_t *self, gentity_t *other, gentity_t *activator)
  1193. {
  1194. G_ActivateBehavior(self,BSET_USE);
  1195. self->s.eFlags &= ~ EF_ANIM_ALLFAST;
  1196. self->s.eFlags |= EF_ANIM_ONCE;
  1197. // Switch to and animate its used up model.
  1198. self->s.modelindex = self->s.modelindex2;
  1199. gi.linkentity (self);
  1200. // Use target when used
  1201. if (self->spawnflags & 8)
  1202. {
  1203. G_UseTargets( self, activator );
  1204. }
  1205. self->e_UseFunc = useF_NULL;
  1206. self->e_ThinkFunc = thinkF_NULL;
  1207. self->nextthink = -1;
  1208. }
  1209. void health_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1210. {//FIXME: Heal entire team? Or only those that are undying...?
  1211. int dif;
  1212. int dif2;
  1213. int hold;
  1214. G_ActivateBehavior(self,BSET_USE);
  1215. if (self->e_ThinkFunc != thinkF_NULL)
  1216. {
  1217. self->e_ThinkFunc = thinkF_NULL;
  1218. }
  1219. else
  1220. {
  1221. if (other->client)
  1222. {
  1223. // He's dead, Jim. Don't give him health
  1224. if (other->client->ps.stats[STAT_HEALTH]<1)
  1225. {
  1226. dif = 1;
  1227. self->count = 0;
  1228. }
  1229. else
  1230. { // Health
  1231. dif = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_HEALTH];
  1232. // Armor
  1233. dif2 = other->client->ps.stats[STAT_MAX_HEALTH] - other->client->ps.stats[STAT_ARMOR];
  1234. hold = (dif2 - dif);
  1235. // For every 3 points of health, you get 1 point of armor
  1236. // BUT!!! after health is filled up, you get the full energy going to armor
  1237. if (hold>0) // Need more armor than health
  1238. {
  1239. // Calculate total amount of station energy needed.
  1240. hold = dif / 3; // For every 3 points of health, you get 1 point of armor
  1241. dif2 -= hold;
  1242. dif2 += dif;
  1243. dif = dif2;
  1244. }
  1245. }
  1246. }
  1247. else
  1248. { // Being triggered to be used up
  1249. dif = 1;
  1250. self->count = 0;
  1251. }
  1252. // Does player already have full health and full armor?
  1253. if (dif > 0)
  1254. {
  1255. // G_Sound(self, G_SoundIndex("sound/player/suithealth.wav") );
  1256. if ((dif >= self->count) || (self->count<1)) // use it all up?
  1257. {
  1258. health_shutdown(self);
  1259. }
  1260. // Use target when used
  1261. if (self->spawnflags & 8)
  1262. {
  1263. G_UseTargets( self, activator );
  1264. }
  1265. self->e_UseFunc = useF_NULL;
  1266. self->enemy = other;
  1267. self->e_ThinkFunc = thinkF_health_think;
  1268. self->nextthink = level.time + 50;
  1269. }
  1270. else
  1271. {
  1272. // G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
  1273. }
  1274. }
  1275. }
  1276. // --------------------------------------------------------------------
  1277. //
  1278. // AMMO plugin functions
  1279. //
  1280. // --------------------------------------------------------------------
  1281. void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator);
  1282. int Add_Ammo2 (gentity_t *ent, int ammoType, int count);
  1283. void ammo_shutdown( gentity_t *self )
  1284. {
  1285. if (!(self->s.eFlags & EF_ANIM_ONCE))
  1286. {
  1287. self->s.eFlags &= ~ EF_ANIM_ALLFAST;
  1288. self->s.eFlags |= EF_ANIM_ONCE;
  1289. gi.linkentity (self);
  1290. }
  1291. }
  1292. void ammo_think( gentity_t *ent )
  1293. {
  1294. int dif;
  1295. // Still has ammo to give
  1296. if (ent->count > 0 && ent->enemy )
  1297. {
  1298. dif = ammoData[AMMO_BLASTER].max - ent->enemy->client->ps.ammo[AMMO_BLASTER];
  1299. if (dif > 2 )
  1300. {
  1301. dif= 2;
  1302. }
  1303. else if (dif < 0)
  1304. {
  1305. dif= 0;
  1306. }
  1307. if (ent->count < dif) // Can't give more than count
  1308. {
  1309. dif = ent->count;
  1310. }
  1311. // Give player ammo
  1312. if (Add_Ammo2(ent->enemy,AMMO_BLASTER,dif) && (dif!=0))
  1313. {
  1314. ent->count-=dif;
  1315. ent->nextthink = level.time + 10;
  1316. }
  1317. else // User has taken all ammo he can hold
  1318. {
  1319. ent->e_UseFunc = useF_ammo_use;
  1320. ent->e_ThinkFunc = thinkF_NULL;
  1321. }
  1322. }
  1323. if (ent->count < 1)
  1324. {
  1325. ammo_shutdown(ent);
  1326. }
  1327. }
  1328. //------------------------------------------------------------
  1329. void ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1330. {
  1331. int dif;
  1332. G_ActivateBehavior(self,BSET_USE);
  1333. if (self->e_ThinkFunc != thinkF_NULL)
  1334. {
  1335. if (self->e_UseFunc != useF_NULL)
  1336. {
  1337. self->e_ThinkFunc = thinkF_NULL;
  1338. }
  1339. }
  1340. else
  1341. {
  1342. if (other->client)
  1343. {
  1344. dif = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER];
  1345. }
  1346. else
  1347. { // Being triggered to be used up
  1348. dif = 1;
  1349. self->count = 0;
  1350. }
  1351. // Does player already have full ammo?
  1352. if (dif > 0)
  1353. {
  1354. // G_Sound(self, G_SoundIndex("sound/player/suitenergy.wav") );
  1355. if ((dif >= self->count) || (self->count<1)) // use it all up?
  1356. {
  1357. ammo_shutdown(self);
  1358. }
  1359. }
  1360. else
  1361. {
  1362. // G_Sound(self, G_SoundIndex("sound/weapons/noammo.wav") );
  1363. }
  1364. // Use target when used
  1365. if (self->spawnflags & 8)
  1366. {
  1367. G_UseTargets( self, activator );
  1368. }
  1369. self->e_UseFunc = useF_NULL;
  1370. G_SetEnemy( self, other );
  1371. self->e_ThinkFunc = thinkF_ammo_think;
  1372. self->nextthink = level.time + 50;
  1373. }
  1374. }
  1375. //------------------------------------------------------------
  1376. void mega_ammo_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  1377. {
  1378. G_ActivateBehavior( self, BSET_USE );
  1379. // Use target when used
  1380. G_UseTargets( self, activator );
  1381. // first use, adjust the max ammo a person can hold for each type of ammo
  1382. ammoData[AMMO_BLASTER].max = 999;
  1383. ammoData[AMMO_POWERCELL].max = 999;
  1384. // Set up our count with whatever the max difference will be
  1385. if ( other->client->ps.ammo[AMMO_POWERCELL] > other->client->ps.ammo[AMMO_BLASTER] )
  1386. self->count = ammoData[AMMO_BLASTER].max - other->client->ps.ammo[AMMO_BLASTER];
  1387. else
  1388. self->count = ammoData[AMMO_POWERCELL].max - other->client->ps.ammo[AMMO_POWERCELL];
  1389. // G_Sound( self, G_SoundIndex("sound/player/superenergy.wav") );
  1390. // Clear our usefunc, then think until our ammo is full
  1391. self->e_UseFunc = useF_NULL;
  1392. G_SetEnemy( self, other );
  1393. self->e_ThinkFunc = thinkF_mega_ammo_think;
  1394. self->nextthink = level.time + 50;
  1395. self->s.frame = 0;
  1396. self->s.eFlags |= EF_ANIM_ONCE;
  1397. }
  1398. //------------------------------------------------------------
  1399. void mega_ammo_think( gentity_t *self )
  1400. {
  1401. int ammo_add = 5;
  1402. // If the middle model is done animating, and we haven't switched to the last model yet...
  1403. // chuck up the last model.
  1404. if (!Q_stricmp(self->model,"models/mapobjects/forge/power_up_boss.md3")) // Because the normal forge_ammo model can use this too
  1405. {
  1406. if ( self->s.frame > 16 && self->s.modelindex != self->s.modelindex2 )
  1407. self->s.modelindex = self->s.modelindex2;
  1408. }
  1409. if ( self->enemy && self->count > 0 )
  1410. {
  1411. // Add an equal ammount of ammo to each type
  1412. self->enemy->client->ps.ammo[AMMO_BLASTER] += ammo_add;
  1413. self->enemy->client->ps.ammo[AMMO_POWERCELL] += ammo_add;
  1414. // Now cap to prevent overflows
  1415. if ( self->enemy->client->ps.ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max )
  1416. self->enemy->client->ps.ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max;
  1417. if ( self->enemy->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
  1418. self->enemy->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
  1419. // Decrement the count given counter
  1420. self->count -= ammo_add;
  1421. // If we've given all we should, prevent giving any more, even if they player is no longer full
  1422. if ( self->count <= 0 )
  1423. {
  1424. self->count = 0;
  1425. self->e_ThinkFunc = thinkF_NULL;
  1426. self->nextthink = -1;
  1427. }
  1428. else
  1429. self->nextthink = 20;
  1430. }
  1431. }
  1432. //------------------------------------------------------------
  1433. void switch_models( gentity_t *self, gentity_t *other, gentity_t *activator )
  1434. {
  1435. // FIXME: need a sound here!!
  1436. if ( self->s.modelindex2 )
  1437. self->s.modelindex = self->s.modelindex2;
  1438. }
  1439. //------------------------------------------------------------
  1440. void touch_ammo_crystal_tigger( gentity_t *self, gentity_t *other, trace_t *trace )
  1441. {
  1442. if ( !other->client )
  1443. return;
  1444. // dead people can't pick things up
  1445. if ( other->health < 1 )
  1446. return;
  1447. // Only player can pick it up
  1448. if ( !other->s.number == 0 )
  1449. {
  1450. return;
  1451. }
  1452. if ( other->client->ps.ammo[ AMMO_POWERCELL ] >= ammoData[AMMO_POWERCELL].max )
  1453. {
  1454. return; // can't hold any more
  1455. }
  1456. // Add the ammo
  1457. other->client->ps.ammo[AMMO_POWERCELL] += self->owner->count;
  1458. if ( other->client->ps.ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
  1459. {
  1460. other->client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
  1461. }
  1462. // Trigger once only
  1463. self->e_TouchFunc = touchF_NULL;
  1464. // swap the model to the version without the crystal and ditch the infostring
  1465. self->owner->s.modelindex = self->owner->s.modelindex2;
  1466. // play the normal pickup sound
  1467. // G_AddEvent( other, EV_ITEM_PICKUP, ITM_AMMO_CRYSTAL_BORG );
  1468. // fire item targets
  1469. G_UseTargets( self->owner, other );
  1470. }
  1471. //------------------------------------------------------------
  1472. void spawn_ammo_crystal_trigger( gentity_t *ent )
  1473. {
  1474. gentity_t *other;
  1475. vec3_t mins, maxs;
  1476. // Set the base bounds
  1477. VectorCopy( ent->s.origin, mins );
  1478. VectorCopy( ent->s.origin, maxs );
  1479. // Now add an area of influence around the thing
  1480. for ( int i = 0; i < 3; i++ )
  1481. {
  1482. maxs[i] += 48;
  1483. mins[i] -= 48;
  1484. }
  1485. // create a trigger with this size
  1486. other = G_Spawn( );
  1487. VectorCopy( mins, other->mins );
  1488. VectorCopy( maxs, other->maxs );
  1489. // set up the other bits that the engine needs to know
  1490. other->owner = ent;
  1491. other->contents = CONTENTS_TRIGGER;
  1492. other->e_TouchFunc = touchF_touch_ammo_crystal_tigger;
  1493. gi.linkentity( other );
  1494. }
  1495. void misc_replicator_item_remove ( gentity_t *self, gentity_t *other, gentity_t *activator )
  1496. {
  1497. self->s.eFlags |= EF_NODRAW;
  1498. //self->contents = 0;
  1499. self->s.modelindex = 0;
  1500. self->e_UseFunc = useF_misc_replicator_item_spawn;
  1501. //FIXME: pickup sound?
  1502. if ( activator->client )
  1503. {
  1504. activator->health += 5;
  1505. if ( activator->health > activator->client->ps.stats[STAT_MAX_HEALTH] ) // Past max health
  1506. {
  1507. activator->health = activator->client->ps.stats[STAT_MAX_HEALTH];
  1508. }
  1509. }
  1510. }
  1511. void misc_replicator_item_finish_spawn( gentity_t *self )
  1512. {
  1513. //self->contents = CONTENTS_ITEM;
  1514. //FIXME: blinks out for a couple frames when transporter effect is done?
  1515. self->e_UseFunc = useF_misc_replicator_item_remove;
  1516. }
  1517. void misc_replicator_item_spawn ( gentity_t *self, gentity_t *other, gentity_t *activator )
  1518. {
  1519. switch ( Q_irand( 1, self->count ) )
  1520. {
  1521. case 1:
  1522. self->s.modelindex = self->bounceCount;
  1523. break;
  1524. case 2:
  1525. self->s.modelindex = self->fly_sound_debounce_time;
  1526. break;
  1527. case 3:
  1528. self->s.modelindex = self->painDebounceTime;
  1529. break;
  1530. case 4:
  1531. self->s.modelindex = self->disconnectDebounceTime;
  1532. break;
  1533. case 5:
  1534. self->s.modelindex = self->attackDebounceTime;
  1535. break;
  1536. case 6://max
  1537. self->s.modelindex = self->pushDebounceTime;
  1538. break;
  1539. }
  1540. self->s.eFlags &= ~EF_NODRAW;
  1541. self->e_ThinkFunc = thinkF_misc_replicator_item_finish_spawn;
  1542. self->nextthink = level.time + 4000;//shorter?
  1543. self->e_UseFunc = useF_NULL;
  1544. gentity_t *tent = G_TempEntity( self->currentOrigin, EV_REPLICATOR );
  1545. tent->owner = self;
  1546. }
  1547. /*QUAK-ED misc_replicator_item (0.2 0.8 0.2) (-4 -4 0) (4 4 8)
  1548. When used. this will "spawn" an entity with a random model from the ones provided below...
  1549. Using it again removes the item as if it were picked up.
  1550. model - first random model key
  1551. model2 - second random model key
  1552. model3 - third random model key
  1553. model4 - fourth random model key
  1554. model5 - fifth random model key
  1555. model6 - sixth random model key
  1556. NOTE: do not skip one of these model names, start with the lowest and fill in each next highest one with a value. A gap will cause the item to not work correctly.
  1557. NOTE: if you use an invalid model, it will still try to use it and show the NULL axis model (or nothing at all)
  1558. targetname - how you refer to it for using it
  1559. */
  1560. void SP_misc_replicator_item ( gentity_t *self )
  1561. {
  1562. if ( self->model )
  1563. {
  1564. self->bounceCount = G_ModelIndex( self->model );
  1565. self->count++;
  1566. if ( self->model2 )
  1567. {
  1568. self->fly_sound_debounce_time = G_ModelIndex( self->model2 );
  1569. self->count++;
  1570. if ( self->target )
  1571. {
  1572. self->painDebounceTime = G_ModelIndex( self->target );
  1573. self->count++;
  1574. if ( self->target2 )
  1575. {
  1576. self->disconnectDebounceTime = G_ModelIndex( self->target2 );
  1577. self->count++;
  1578. if ( self->target3 )
  1579. {
  1580. self->attackDebounceTime = G_ModelIndex( self->target3 );
  1581. self->count++;
  1582. if ( self->target4 )
  1583. {
  1584. self->pushDebounceTime = G_ModelIndex( self->target4 );
  1585. self->count++;
  1586. }
  1587. }
  1588. }
  1589. }
  1590. }
  1591. }
  1592. // G_SoundIndex( "sound/movers/switches/replicator.wav" );
  1593. self->e_UseFunc = useF_misc_replicator_item_spawn;
  1594. self->s.eFlags |= EF_NODRAW;
  1595. //self->contents = 0;
  1596. VectorSet( self->mins, -4, -4, 0 );
  1597. VectorSet( self->maxs, 4, 4, 8 );
  1598. G_SetOrigin( self, self->s.origin );
  1599. G_SetAngles( self, self->s.angles );
  1600. gi.linkentity( self );
  1601. }
  1602. /*QUAKED misc_trip_mine (0.2 0.8 0.2) (-4 -4 -4) (4 4 4) START_ON BROADCAST START_OFF
  1603. Place in a map and point the angles at whatever surface you want it to attach to.
  1604. START_ON - If you give it a targetname to make it toggle-able, but want it to start on, set this flag
  1605. BROADCAST - ONLY USE THIS IF YOU HAVE TO! causes the trip wire and loop sound to always happen, use this if the beam drops out in certain situations
  1606. START_OFF - If you give it a targetname, it will start completely off (laser AND base unit) until used.
  1607. The trip mine will attach to that surface and fire it's beam away from the surface at an angle perpendicular to it.
  1608. targetname - starts off, when used, turns on (toggles)
  1609. FIXME: sometimes we want these to not be shootable... maybe just put them behind a force field?
  1610. */
  1611. extern void touchLaserTrap( gentity_t *ent, gentity_t *other, trace_t *trace );
  1612. extern void CreateLaserTrap( gentity_t *laserTrap, vec3_t start, gentity_t *owner );
  1613. void misc_trip_mine_activate( gentity_t *self, gentity_t *other, gentity_t *activator )
  1614. {
  1615. if ( self->e_ThinkFunc == thinkF_laserTrapThink )
  1616. {
  1617. self->s.eFlags &= ~EF_FIRING;
  1618. self->s.loopSound = 0;
  1619. self->e_ThinkFunc = thinkF_NULL;
  1620. self->nextthink = -1;
  1621. }
  1622. else
  1623. {
  1624. self->e_ThinkFunc = thinkF_laserTrapThink;
  1625. self->nextthink = level.time + FRAMETIME;
  1626. self->s.eFlags &= ~EF_NODRAW;
  1627. self->contents = CONTENTS_SHOTCLIP;//CAN'T USE CONTENTS_SOLID because only ARCHITECTURE is contents_solid!!!
  1628. self->takedamage = qtrue;
  1629. }
  1630. }
  1631. void SP_misc_trip_mine( gentity_t *self )
  1632. {
  1633. vec3_t forward, end;
  1634. trace_t trace;
  1635. AngleVectors( self->s.angles, forward, NULL, NULL );
  1636. VectorMA( self->s.origin, 128, forward, end );
  1637. gi.trace( &trace, self->s.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
  1638. if ( trace.allsolid || trace.startsolid )
  1639. {
  1640. Com_Error( ERR_DROP,"misc_trip_mine at %s in solid\n", vtos(self->s.origin) );
  1641. G_FreeEntity( self );
  1642. return;
  1643. }
  1644. if ( trace.fraction == 1.0 )
  1645. {
  1646. Com_Error( ERR_DROP,"misc_trip_mine at %s pointed at no surface\n", vtos(self->s.origin) );
  1647. G_FreeEntity( self );
  1648. return;
  1649. }
  1650. RegisterItem( FindItemForWeapon( WP_TRIP_MINE )); //precache the weapon
  1651. self->count = 2/*TRIPWIRE_STYLE*/;
  1652. vectoangles( trace.plane.normal, end );
  1653. G_SetOrigin( self, trace.endpos );
  1654. G_SetAngles( self, end );
  1655. CreateLaserTrap( self, trace.endpos, self );
  1656. touchLaserTrap( self, self, &trace );
  1657. self->e_ThinkFunc = thinkF_NULL;
  1658. self->nextthink = -1;
  1659. if ( !self->targetname || (self->spawnflags&1) )
  1660. {//starts on
  1661. misc_trip_mine_activate( self, self, self );
  1662. }
  1663. if ( self->targetname )
  1664. {
  1665. self->e_UseFunc = useF_misc_trip_mine_activate;
  1666. }
  1667. if (( self->spawnflags & 2 )) // broadcast...should only be used in very rare cases. could fix networking, perhaps, but james suggested this because it's easier
  1668. {
  1669. self->svFlags |= SVF_BROADCAST;
  1670. }
  1671. // Whether to start completelly off or not.
  1672. if ( self->targetname && self->spawnflags & 4 )
  1673. {
  1674. self->s.eFlags = EF_NODRAW;
  1675. self->contents = 0;
  1676. self->takedamage = qfalse;
  1677. }
  1678. gi.linkentity( self );
  1679. }
  1680. /*QUAKED misc_maglock (0 .5 .8) (-8 -8 -8) (8 8 8) x x x x x x x x
  1681. 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
  1682. NOTE: place these half-way in the door to make it flush with the door's surface.
  1683. "target" thing to use when destoryed (not doors - it automatically unlocks the door it was angled at)
  1684. "health" default is 10
  1685. */
  1686. void maglock_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod, int dFlags, int hitLoc )
  1687. {
  1688. //unlock our door if we're the last lock pointed at the door
  1689. if ( self->activator )
  1690. {
  1691. self->activator->lockCount--;
  1692. if ( !self->activator->lockCount )
  1693. {
  1694. self->activator->svFlags &= ~SVF_INACTIVE;
  1695. }
  1696. }
  1697. //use targets
  1698. G_UseTargets( self, attacker );
  1699. //die
  1700. WP_Explode( self );
  1701. }
  1702. void SP_misc_maglock ( gentity_t *self )
  1703. {
  1704. //NOTE: May have to make these only work on doors that are either untargeted
  1705. // or are targeted by a trigger, not doors fired off by scripts, counters
  1706. // or other such things?
  1707. self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
  1708. self->fxID = G_EffectIndex( "maglock/explosion" );
  1709. G_SetOrigin( self, self->s.origin );
  1710. self->e_ThinkFunc = thinkF_maglock_link;
  1711. //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
  1712. 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!
  1713. }
  1714. void maglock_link( gentity_t *self )
  1715. {
  1716. //find what we're supposed to be attached to
  1717. vec3_t forward, start, end;
  1718. trace_t trace;
  1719. AngleVectors( self->s.angles, forward, NULL, NULL );
  1720. VectorMA( self->s.origin, 128, forward, end );
  1721. VectorMA( self->s.origin, -4, forward, start );
  1722. gi.trace( &trace, start, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT );
  1723. if ( trace.allsolid || trace.startsolid )
  1724. {
  1725. Com_Error( ERR_DROP,"misc_maglock at %s in solid\n", vtos(self->s.origin) );
  1726. G_FreeEntity( self );
  1727. return;
  1728. }
  1729. if ( trace.fraction == 1.0 )
  1730. {
  1731. self->e_ThinkFunc = thinkF_maglock_link;
  1732. self->nextthink = level.time + 100;
  1733. /*
  1734. Com_Error( ERR_DROP,"misc_maglock at %s pointed at no surface\n", vtos(self->s.origin) );
  1735. G_FreeEntity( self );
  1736. */
  1737. return;
  1738. }
  1739. gentity_t *traceEnt = &g_entities[trace.entityNum];
  1740. if ( trace.entityNum >= ENTITYNUM_WORLD || !traceEnt || Q_stricmp( "func_door", traceEnt->classname ) )
  1741. {
  1742. self->e_ThinkFunc = thinkF_maglock_link;
  1743. self->nextthink = level.time + 100;
  1744. //Com_Error( ERR_DROP,"misc_maglock at %s not pointed at a door\n", vtos(self->s.origin) );
  1745. //G_FreeEntity( self );
  1746. return;
  1747. }
  1748. //check the traceEnt, make sure it's a door and give it a lockCount and deactivate it
  1749. //find the trigger for the door
  1750. self->activator = G_FindDoorTrigger( traceEnt );
  1751. if ( !self->activator )
  1752. {
  1753. self->activator = traceEnt;
  1754. }
  1755. self->activator->lockCount++;
  1756. self->activator->svFlags |= SVF_INACTIVE;
  1757. //now position and orient it
  1758. vectoangles( trace.plane.normal, end );
  1759. G_SetOrigin( self, trace.endpos );
  1760. G_SetAngles( self, end );
  1761. //make it hittable
  1762. //FIXME: if rotated/inclined this bbox may be off... but okay if we're a ghoul model?
  1763. //self->s.modelindex = G_ModelIndex( "models/map_objects/imp_detention/door_lock.md3" );
  1764. VectorSet( self->mins, -8, -8, -8 );
  1765. VectorSet( self->maxs, 8, 8, 8 );
  1766. self->contents = CONTENTS_CORPSE;
  1767. //make it destroyable
  1768. self->flags |= FL_SHIELDED;//only damagable by lightsabers
  1769. self->takedamage = qtrue;
  1770. self->health = 10;
  1771. self->e_DieFunc = dieF_maglock_die;
  1772. //self->fxID = G_EffectIndex( "maglock/explosion" );
  1773. gi.linkentity( self );
  1774. }
  1775. /*
  1776. ================
  1777. EnergyShieldStationSettings
  1778. ================
  1779. */
  1780. void EnergyShieldStationSettings(gentity_t *ent)
  1781. {
  1782. G_SpawnInt( "count", "0", &ent->count );
  1783. if (!ent->count)
  1784. {
  1785. switch (g_spskill->integer)
  1786. {
  1787. case 0: // EASY
  1788. ent->count = 100;
  1789. break;
  1790. case 1: // MEDIUM
  1791. ent->count = 75;
  1792. break;
  1793. default :
  1794. case 2: // HARD
  1795. ent->count = 50;
  1796. break;
  1797. }
  1798. }
  1799. }
  1800. /*
  1801. ================
  1802. shield_power_converter_use
  1803. ================
  1804. */
  1805. void shield_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1806. {
  1807. int dif,add;
  1808. if ( !activator || activator->s.number != 0 )
  1809. {
  1810. //only the player gets to use these
  1811. return;
  1812. }
  1813. G_ActivateBehavior( self,BSET_USE );
  1814. if ( self->setTime < level.time )
  1815. {
  1816. self->setTime = level.time + 100;
  1817. dif = 100 - activator->client->ps.stats[STAT_ARMOR]; // FIXME: define for max armor?
  1818. if ( dif > 0 && self->count ) // Already at full armor?..and do I even have anything to give
  1819. {
  1820. if ( dif > MAX_AMMO_GIVE )
  1821. {
  1822. add = MAX_AMMO_GIVE;
  1823. }
  1824. else
  1825. {
  1826. add = dif;
  1827. }
  1828. if ( self->count < add )
  1829. {
  1830. add = self->count;
  1831. }
  1832. self->count -= add;
  1833. activator->client->ps.stats[STAT_ARMOR] += add;
  1834. self->s.loopSound = G_SoundIndex( "sound/interface/shieldcon_run.wav" );
  1835. }
  1836. if ( self->count <= 0 )
  1837. {
  1838. // play empty sound
  1839. self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
  1840. G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_empty.mp3" ));
  1841. self->s.loopSound = 0;
  1842. if ( self->s.eFlags & EF_SHADER_ANIM )
  1843. {
  1844. self->s.frame = 1;
  1845. }
  1846. }
  1847. else if ( activator->client->ps.stats[STAT_ARMOR] >= 100 ) // FIXME: define for max
  1848. {
  1849. // play full sound
  1850. G_Sound( self, G_SoundIndex( "sound/interface/shieldcon_done.mp3" ));
  1851. self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
  1852. self->s.loopSound = 0;
  1853. }
  1854. }
  1855. if ( self->s.loopSound )
  1856. {
  1857. // we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop
  1858. // this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off
  1859. self->e_ThinkFunc = thinkF_poll_converter;
  1860. self->nextthink = level.time + 500;
  1861. }
  1862. else
  1863. {
  1864. // sound is already off, so we don't need to "think" about it.
  1865. self->e_ThinkFunc = thinkF_NULL;
  1866. self->nextthink = 0;
  1867. }
  1868. if ( activator->client->ps.stats[STAT_ARMOR] > 0 )
  1869. {
  1870. activator->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
  1871. }
  1872. }
  1873. /*QUAKED misc_model_shield_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET
  1874. model="models/items/psd_big.md3"
  1875. Gives shield energy when used.
  1876. USETARGET - when used it fires off target
  1877. "health" - how much health the model has - default 60 (zero makes non-breakable)
  1878. "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
  1879. "target" - what to use when it dies
  1880. "paintarget" - target to fire when hit (but not destroyed)
  1881. "count" - the amount of ammo given when used (default 100)
  1882. */
  1883. //------------------------------------------------------------
  1884. void SP_misc_model_shield_power_converter( gentity_t *ent )
  1885. {
  1886. SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL );
  1887. ent->takedamage = qfalse;
  1888. EnergyShieldStationSettings(ent);
  1889. G_SoundIndex("sound/interface/shieldcon_run.wav");
  1890. G_SoundIndex("sound/interface/shieldcon_done.mp3");
  1891. G_SoundIndex("sound/interface/shieldcon_empty.mp3");
  1892. ent->s.modelindex = G_ModelIndex("models/items/psd_big.md3"); // Precache model
  1893. ent->s.modelindex2 = G_ModelIndex("models/items/psd_big.md3"); // Precache model
  1894. }
  1895. void bomb_planted_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1896. {
  1897. if ( self->count == 2 )
  1898. {
  1899. self->s.eFlags &= ~EF_NODRAW;
  1900. self->contents = CONTENTS_SOLID;
  1901. self->count = 1;
  1902. self->s.loopSound = self->noise_index;
  1903. }
  1904. else if ( self->count == 1 )
  1905. {
  1906. self->count = 0;
  1907. // play disarm sound
  1908. self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
  1909. G_Sound( self, G_SoundIndex("sound/weapons/overchargeend"));
  1910. self->s.loopSound = 0;
  1911. // this pauses the shader on one frame (more or less)
  1912. self->s.eFlags |= EF_DISABLE_SHADER_ANIM;
  1913. // this starts the animation for the model
  1914. self->s.eFlags |= EF_ANIM_ONCE;
  1915. self->s.frame = 0;
  1916. //use targets
  1917. G_UseTargets( self, activator );
  1918. }
  1919. }
  1920. /*QUAKED misc_model_bomb_planted (1 0 0) (-16 -16 0) (16 16 70) x x x USETARGET
  1921. model="models/map_objects/factory/bomb_new_deact.md3"
  1922. Planted by evil men for evil purposes.
  1923. "health" - please don't shoot the thermonuclear device
  1924. "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
  1925. */
  1926. //------------------------------------------------------------
  1927. void SP_misc_model_bomb_planted( gentity_t *ent )
  1928. {
  1929. VectorSet( ent->mins, -16, -16, 0 );
  1930. VectorSet( ent->maxs, 16, 16, 70 );
  1931. SetMiscModelDefaults( ent, useF_bomb_planted_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL );
  1932. ent->takedamage = qfalse;
  1933. G_SoundIndex("sound/weapons/overchargeend");
  1934. ent->s.modelindex = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3"); // Precache model
  1935. ent->s.modelindex2 = G_ModelIndex("models/map_objects/factory/bomb_new_deact.md3"); // Precache model
  1936. ent->noise_index = G_SoundIndex("sound/interface/ammocon_run");
  1937. ent->s.loopSound = ent->noise_index;
  1938. //ent->s.eFlags |= EF_SHADER_ANIM;
  1939. //ent->s.frame = ent->startFrame = 0;
  1940. ent->count = 1;
  1941. // If we have a targetname, we're are invisible until we are spawned in by being used.
  1942. if ( ent->targetname )
  1943. {
  1944. ent->s.eFlags = EF_NODRAW;
  1945. ent->contents = 0;
  1946. ent->count = 2;
  1947. ent->s.loopSound = 0;
  1948. }
  1949. int forceVisible = 0;
  1950. G_SpawnInt( "forcevisible", "0", &forceVisible );
  1951. if ( forceVisible )
  1952. {//can see these through walls with force sight, so must be broadcast
  1953. //ent->svFlags |= SVF_BROADCAST;
  1954. ent->s.eFlags |= EF_FORCE_VISIBLE;
  1955. }
  1956. }
  1957. void beacon_deploy( gentity_t *ent )
  1958. {
  1959. ent->e_ThinkFunc = thinkF_beacon_think;
  1960. ent->nextthink = level.time + FRAMETIME * 0.5f;
  1961. ent->s.frame = 0;
  1962. ent->startFrame = 0;
  1963. ent->endFrame = 30;
  1964. ent->loopAnim = qfalse;
  1965. }
  1966. void beacon_think( gentity_t *ent )
  1967. {
  1968. ent->nextthink = level.time + FRAMETIME * 0.5f;
  1969. // Deploy animation complete? Stop thinking and just animate signal forever.
  1970. if ( ent->s.frame == 30 )
  1971. {
  1972. ent->e_ThinkFunc = thinkF_NULL;
  1973. ent->nextthink = -1;
  1974. ent->startFrame = 31;
  1975. ent->endFrame = 60;
  1976. ent->loopAnim = qtrue;
  1977. ent->s.loopSound = ent->noise_index;
  1978. }
  1979. }
  1980. void beacon_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  1981. {
  1982. // Every time it's used it will be toggled on or off.
  1983. if ( self->count == 0 )
  1984. {
  1985. self->s.eFlags &= ~EF_NODRAW;
  1986. self->contents = CONTENTS_SOLID;
  1987. self->count = 1;
  1988. self->svFlags = SVF_ANIMATING;
  1989. beacon_deploy( self );
  1990. }
  1991. else
  1992. {
  1993. self->s.eFlags = EF_NODRAW;
  1994. self->contents = 0;
  1995. self->count = 0;
  1996. self->s.loopSound = 0;
  1997. self->svFlags = 0;
  1998. }
  1999. }
  2000. /*QUAKED misc_model_beacon (1 0 0) (-16 -16 0) (16 16 24) x x x
  2001. model="models/map_objects/wedge/beacon.md3"
  2002. An animating beacon model.
  2003. "forcevisible" - When you turn on force sight (any level), you can see these draw through the entire level...
  2004. */
  2005. //------------------------------------------------------------
  2006. void SP_misc_model_beacon( gentity_t *ent )
  2007. {
  2008. VectorSet( ent->mins, -16, -16, 0 );
  2009. VectorSet( ent->maxs, 16, 16, 24 );
  2010. SetMiscModelDefaults( ent, useF_beacon_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL );
  2011. ent->takedamage = qfalse;
  2012. //G_SoundIndex("sound/weapons/overchargeend");
  2013. ent->s.modelindex = G_ModelIndex("models/map_objects/wedge/beacon.md3"); // Precache model
  2014. ent->s.modelindex2 = G_ModelIndex("models/map_objects/wedge/beacon.md3"); // Precache model
  2015. ent->noise_index = G_SoundIndex("sound/interface/ammocon_run");
  2016. // If we have a targetname, we're are invisible until we are spawned in by being used.
  2017. if ( ent->targetname )
  2018. {
  2019. ent->s.eFlags = EF_NODRAW;
  2020. ent->contents = 0;
  2021. ent->s.loopSound = 0;
  2022. ent->count = 0;
  2023. }
  2024. else
  2025. {
  2026. ent->count = 1;
  2027. beacon_deploy( ent );
  2028. }
  2029. int forceVisible = 0;
  2030. G_SpawnInt( "forcevisible", "0", &forceVisible );
  2031. if ( forceVisible )
  2032. {//can see these through walls with force sight, so must be broadcast
  2033. //ent->svFlags |= SVF_BROADCAST;
  2034. ent->s.eFlags |= EF_FORCE_VISIBLE;
  2035. }
  2036. }
  2037. /*QUAKED misc_shield_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET
  2038. model="models/items/a_shield_converter.md3"
  2039. Gives shield energy when used.
  2040. USETARGET - when used it fires off target
  2041. "health" - how much health the model has - default 60 (zero makes non-breakable)
  2042. "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
  2043. "target" - what to use when it dies
  2044. "paintarget" - target to fire when hit (but not destroyed)
  2045. "count" - the amount of ammo given when used (default 100)
  2046. */
  2047. //------------------------------------------------------------
  2048. void SP_misc_shield_floor_unit( gentity_t *ent )
  2049. {
  2050. VectorSet( ent->mins, -16, -16, 0 );
  2051. VectorSet( ent->maxs, 16, 16, 32 );
  2052. SetMiscModelDefaults( ent, useF_shield_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL );
  2053. ent->takedamage = qfalse;
  2054. EnergyShieldStationSettings(ent);
  2055. G_SoundIndex("sound/interface/shieldcon_run.wav");
  2056. G_SoundIndex("sound/interface/shieldcon_done.mp3");
  2057. G_SoundIndex("sound/interface/shieldcon_empty.mp3");
  2058. ent->s.modelindex = G_ModelIndex( "models/items/a_shield_converter.md3" ); // Precache model
  2059. ent->s.eFlags |= EF_SHADER_ANIM;
  2060. }
  2061. /*
  2062. ================
  2063. EnergyAmmoShieldStationSettings
  2064. ================
  2065. */
  2066. void EnergyAmmoStationSettings(gentity_t *ent)
  2067. {
  2068. G_SpawnInt( "count", "0", &ent->count );
  2069. if (!ent->count)
  2070. {
  2071. switch (g_spskill->integer)
  2072. {
  2073. case 0: // EASY
  2074. ent->count = 100;
  2075. break;
  2076. case 1: // MEDIUM
  2077. ent->count = 75;
  2078. break;
  2079. default :
  2080. case 2: // HARD
  2081. ent->count = 50;
  2082. break;
  2083. }
  2084. }
  2085. }
  2086. // There has to be an easier way to turn off the looping sound...but
  2087. // it's the night before beta and my brain is fried
  2088. //------------------------------------------------------------------
  2089. void poll_converter( gentity_t *self )
  2090. {
  2091. self->s.loopSound = 0;
  2092. self->nextthink = 0;
  2093. self->e_ThinkFunc = thinkF_NULL;
  2094. }
  2095. /*
  2096. ================
  2097. ammo_power_converter_use
  2098. ================
  2099. */
  2100. void ammo_power_converter_use( gentity_t *self, gentity_t *other, gentity_t *activator)
  2101. {
  2102. int add;
  2103. int difBlaster,difPowerCell,difMetalBolts;
  2104. playerState_t *ps;
  2105. if ( !activator || activator->s.number != 0 )
  2106. {//only the player gets to use these
  2107. return;
  2108. }
  2109. G_ActivateBehavior( self, BSET_USE );
  2110. ps = &activator->client->ps;
  2111. if ( self->setTime < level.time )
  2112. {
  2113. difBlaster = ammoData[AMMO_BLASTER].max - ps->ammo[AMMO_BLASTER];
  2114. difPowerCell = ammoData[AMMO_POWERCELL].max - ps->ammo[AMMO_POWERCELL];
  2115. difMetalBolts = ammoData[AMMO_METAL_BOLTS].max - ps->ammo[AMMO_METAL_BOLTS];
  2116. // Has it got any power left...and can we even use any of it?
  2117. if ( self->count && ( difBlaster > 0 || difPowerCell > 0 || difMetalBolts > 0 ))
  2118. {
  2119. // at least one of the ammo types could stand to take on a bit more ammo
  2120. self->setTime = level.time + 100;
  2121. self->s.loopSound = G_SoundIndex( "sound/interface/ammocon_run.wav" );
  2122. // dole out ammo in little packets
  2123. if ( self->count > MAX_AMMO_GIVE )
  2124. {
  2125. add = MAX_AMMO_GIVE;
  2126. }
  2127. else if ( self->count < 0 )
  2128. {
  2129. add = 0;
  2130. }
  2131. else
  2132. {
  2133. add = self->count;
  2134. }
  2135. // all weapons fill at same rate...
  2136. ps->ammo[AMMO_BLASTER] += add;
  2137. ps->ammo[AMMO_POWERCELL] += add;
  2138. ps->ammo[AMMO_METAL_BOLTS] += add;
  2139. // ...then get clamped to max
  2140. if ( ps->ammo[AMMO_BLASTER] > ammoData[AMMO_BLASTER].max )
  2141. {
  2142. ps->ammo[AMMO_BLASTER] = ammoData[AMMO_BLASTER].max;
  2143. }
  2144. if ( ps->ammo[AMMO_POWERCELL] > ammoData[AMMO_POWERCELL].max )
  2145. {
  2146. ps->ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
  2147. }
  2148. if ( ps->ammo[AMMO_METAL_BOLTS] > ammoData[AMMO_METAL_BOLTS].max )
  2149. {
  2150. ps->ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max;
  2151. }
  2152. self->count -= add;
  2153. }
  2154. if ( self->count <= 0 )
  2155. {
  2156. // play empty sound
  2157. self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
  2158. G_Sound( self, G_SoundIndex( "sound/interface/ammocon_empty.mp3" ));
  2159. self->s.loopSound = 0;
  2160. if ( self->s.eFlags & EF_SHADER_ANIM )
  2161. {
  2162. self->s.frame = 1;
  2163. }
  2164. }
  2165. else if ( ps->ammo[AMMO_BLASTER] >= ammoData[AMMO_BLASTER].max
  2166. && ps->ammo[AMMO_POWERCELL] >= ammoData[AMMO_POWERCELL].max
  2167. && ps->ammo[AMMO_METAL_BOLTS] >= ammoData[AMMO_METAL_BOLTS].max )
  2168. {
  2169. // play full sound
  2170. G_Sound( self, G_SoundIndex( "sound/interface/ammocon_done.wav" ));
  2171. self->setTime = level.time + 1000; // extra debounce so that the sounds don't overlap too much
  2172. self->s.loopSound = 0;
  2173. }
  2174. }
  2175. if ( self->s.loopSound )
  2176. {
  2177. // we will have to shut of the loop sound, so I guess try and do it intelligently...NOTE: this could get completely stomped every time through the loop
  2178. // this is fine, since it just controls shutting off the sound when there are situations that could start the sound but not shut it off
  2179. self->e_ThinkFunc = thinkF_poll_converter;
  2180. self->nextthink = level.time + 500;
  2181. }
  2182. else
  2183. {
  2184. // sound is already off, so we don't need to "think" about it.
  2185. self->e_ThinkFunc = thinkF_NULL;
  2186. self->nextthink = 0;
  2187. }
  2188. }
  2189. /*QUAKED misc_model_ammo_power_converter (1 0 0) (-16 -16 -16) (16 16 16) x x x USETARGET
  2190. model="models/items/power_converter.md3"
  2191. Gives ammo energy when used.
  2192. USETARGET - when used it fires off target
  2193. "health" - how much health the model has - default 60 (zero makes non-breakable)
  2194. "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
  2195. "target" - what to use when it dies
  2196. "paintarget" - target to fire when hit (but not destroyed)
  2197. "count" - the amount of ammo given when used (default 100)
  2198. */
  2199. //------------------------------------------------------------
  2200. void SP_misc_model_ammo_power_converter( gentity_t *ent )
  2201. {
  2202. SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL );
  2203. ent->takedamage = qfalse;
  2204. EnergyAmmoStationSettings(ent);
  2205. G_SoundIndex("sound/interface/ammocon_run.wav");
  2206. G_SoundIndex("sound/interface/ammocon_done.mp3");
  2207. G_SoundIndex("sound/interface/ammocon_empty.mp3");
  2208. ent->s.modelindex = G_ModelIndex("models/items/power_converter.md3"); // Precache model
  2209. ent->s.modelindex2 = G_ModelIndex("models/items/power_converter.md3"); // Precache model
  2210. }
  2211. /*QUAKED misc_ammo_floor_unit (1 0 0) (-16 -16 0) (16 16 32) x x x USETARGET
  2212. model="models/items/a_pwr_converter.md3"
  2213. Gives ammo energy when used.
  2214. USETARGET - when used it fires off target
  2215. "health" - how much health the model has - default 60 (zero makes non-breakable)
  2216. "targetname" - dies and displays damagemodel when used, if any (if not, removes itself)
  2217. "target" - what to use when it dies
  2218. "paintarget" - target to fire when hit (but not destroyed)
  2219. "count" - the amount of ammo given when used (default 100)
  2220. */
  2221. //------------------------------------------------------------
  2222. void SP_misc_ammo_floor_unit( gentity_t *ent )
  2223. {
  2224. VectorSet( ent->mins, -16, -16, 0 );
  2225. VectorSet( ent->maxs, 16, 16, 32 );
  2226. SetMiscModelDefaults( ent, useF_ammo_power_converter_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL );
  2227. ent->takedamage = qfalse;
  2228. EnergyAmmoStationSettings(ent);
  2229. G_SoundIndex("sound/interface/ammocon_run.wav");
  2230. G_SoundIndex("sound/interface/ammocon_done.mp3");
  2231. G_SoundIndex("sound/interface/ammocon_empty.mp3");
  2232. ent->s.modelindex = G_ModelIndex("models/items/a_pwr_converter.md3"); // Precache model
  2233. ent->s.eFlags |= EF_SHADER_ANIM;
  2234. }
  2235. /*
  2236. ================
  2237. welder_think
  2238. ================
  2239. */
  2240. void welder_think( gentity_t *self )
  2241. {
  2242. self->nextthink = level.time + 200;
  2243. vec3_t org,
  2244. dir;
  2245. mdxaBone_t boltMatrix;
  2246. if( self->svFlags & SVF_INACTIVE )
  2247. {
  2248. return;
  2249. }
  2250. int newBolt;
  2251. // could alternate between the two... or make it random... ?
  2252. newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash" );
  2253. // newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*flash01" );
  2254. if ( newBolt != -1 )
  2255. {
  2256. G_Sound( self, self->noise_index );
  2257. // G_PlayEffect( "blueWeldSparks", self->playerModel, newBolt, self->s.number);
  2258. // The welder gets rotated around a lot, and since the origin is offset by 352 I have to make this super expensive call to position the hurt...
  2259. gi.G2API_GetBoltMatrix( self->ghoul2, self->playerModel, newBolt,
  2260. &boltMatrix, self->currentAngles, self->currentOrigin, (cg.time?cg.time:level.time),
  2261. NULL, self->s.modelScale );
  2262. gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org );
  2263. VectorSubtract( self->currentOrigin, org, dir );
  2264. VectorNormalize( dir );
  2265. // we want the welder effect to face inwards....
  2266. G_PlayEffect( "sparks/blueWeldSparks", org, dir );
  2267. G_RadiusDamage( org, self, 10, 45, self, MOD_UNKNOWN );
  2268. }
  2269. }
  2270. /*
  2271. ================
  2272. welder_use
  2273. ================
  2274. */
  2275. void welder_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  2276. {
  2277. // Toggle on and off
  2278. if( self->spawnflags & 1 )
  2279. {
  2280. self->nextthink = level.time + FRAMETIME;
  2281. }
  2282. else
  2283. {
  2284. self->nextthink = -1;
  2285. }
  2286. self->spawnflags = (self->spawnflags ^ 1);
  2287. }
  2288. /*QUAKED misc_model_welder (1 0 0) (-16 -16 -16) (16 16 16) START_OFF
  2289. model="models/map_objects/cairn/welder.md3"
  2290. When 'on' emits sparks from it's welding points
  2291. START_OFF - welder starts off, using it toggles it on/off
  2292. */
  2293. //------------------------------------------------------------
  2294. void SP_misc_model_welder( gentity_t *ent )
  2295. {
  2296. VectorSet( ent->mins, 336, -16, 0 );
  2297. VectorSet( ent->maxs, 368, 16, 32 );
  2298. SetMiscModelDefaults( ent, useF_welder_use, "4", CONTENTS_SOLID, NULL, qfalse, NULL );
  2299. ent->takedamage = qfalse;
  2300. ent->contents = 0;
  2301. G_EffectIndex( "sparks/blueWeldSparks" );
  2302. ent->noise_index = G_SoundIndex( "sound/movers/objects/welding.wav" );
  2303. ent->s.modelindex = G_ModelIndex( "models/map_objects/cairn/welder.glm" );
  2304. // ent->s.modelindex2 = G_ModelIndex( "models/map_objects/cairn/welder.md3" );
  2305. ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/cairn/welder.glm", ent->s.modelindex );
  2306. ent->s.radius = 400.0f;// the origin of the welder is offset by approximately 352, so we need the big radius
  2307. ent->e_ThinkFunc = thinkF_welder_think;
  2308. ent->nextthink = level.time + 1000;
  2309. if( ent->spawnflags & 1 )
  2310. {
  2311. ent->nextthink = -1;
  2312. }
  2313. }
  2314. /*
  2315. ================
  2316. jabba_cam_use
  2317. ================
  2318. */
  2319. void jabba_cam_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  2320. {
  2321. if( self->spawnflags & 1 )
  2322. {
  2323. self->spawnflags &= ~1;
  2324. gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 15, 0, BONE_ANIM_OVERRIDE_FREEZE, -1.5f, (cg.time?cg.time:level.time), -1, 0 );
  2325. }
  2326. else
  2327. {
  2328. self->spawnflags |= 1;
  2329. gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 1.5f, (cg.time?cg.time:level.time), -1, 0 );
  2330. }
  2331. }
  2332. /*QUAKED misc_model_jabba_cam (1 0 0) ( 0 -8 0) (60 8 16) EXTENDED
  2333. model="models/map_objects/nar_shaddar/jabacam.md3"
  2334. The eye camera that popped out of Jabba's front door
  2335. EXTENDED - Starts in the extended position
  2336. targetname - Toggles it on/off
  2337. */
  2338. //-----------------------------------------------------
  2339. void SP_misc_model_jabba_cam( gentity_t *ent )
  2340. //-----------------------------------------------------
  2341. {
  2342. VectorSet( ent->mins, -60.0f, -8.0f, 0.0f );
  2343. VectorSet( ent->maxs, 60.0f, 8.0f, 16.0f );
  2344. SetMiscModelDefaults( ent, useF_jabba_cam_use, "4", 0, NULL, qfalse, NULL );
  2345. G_SetAngles( ent, ent->s.angles );
  2346. ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/jabacam/jabacam.glm" );
  2347. ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/map_objects/nar_shaddar/jabacam/jabacam.glm", ent->s.modelindex );
  2348. ent->s.radius = 150.0f; //......
  2349. VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
  2350. ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
  2351. ent->e_UseFunc = useF_jabba_cam_use;
  2352. ent->takedamage = qfalse;
  2353. // start extended..
  2354. if ( ent->spawnflags & 1 )
  2355. {
  2356. gi.G2API_SetBoneAnimIndex( &ent->ghoul2[ent->playerModel], ent->rootBone, 0, 15, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time );
  2357. }
  2358. gi.linkentity( ent );
  2359. }
  2360. //------------------------------------------------------------------------
  2361. void misc_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  2362. {
  2363. misc_model_breakable_die( self, other, activator, 100, MOD_UNKNOWN );
  2364. }
  2365. /*QUAKED misc_exploding_crate (1 0 0.25) (-24 -24 0) (24 24 64)
  2366. model="models/map_objects/nar_shaddar/crate_xplode.md3"
  2367. Basic exploding crate
  2368. "health" - how much health the model has - default 40 (zero makes non-breakable)
  2369. "splashRadius" - radius to do damage in - default 128
  2370. "splashDamage" - amount of damage to do when it explodes - default 50
  2371. "targetname" - auto-explodes
  2372. "target" - what to use when it dies
  2373. */
  2374. //------------------------------------------------------------
  2375. void SP_misc_exploding_crate( gentity_t *ent )
  2376. {
  2377. G_SpawnInt( "health", "40", &ent->health );
  2378. G_SpawnInt( "splashRadius", "128", &ent->splashRadius );
  2379. G_SpawnInt( "splashDamage", "50", &ent->splashDamage );
  2380. ent->s.modelindex = G_ModelIndex( "models/map_objects/nar_shaddar/crate_xplode.md3" );
  2381. G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
  2382. G_EffectIndex( "chunks/metalexplode" );
  2383. VectorSet( ent->mins, -24, -24, 0 );
  2384. VectorSet( ent->maxs, 24, 24, 64 );
  2385. ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//CONTENTS_SOLID;
  2386. ent->takedamage = qtrue;
  2387. G_SetOrigin( ent, ent->s.origin );
  2388. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  2389. gi.linkentity (ent);
  2390. if ( ent->targetname )
  2391. {
  2392. ent->e_UseFunc = useF_misc_use;
  2393. }
  2394. ent->material = MAT_CRATE1;
  2395. ent->e_DieFunc = dieF_misc_model_breakable_die;//ExplodeDeath;
  2396. }
  2397. /*QUAKED misc_gas_tank (1 0 0.25) (-4 -4 0) (4 4 40)
  2398. model="models/map_objects/imp_mine/tank.md3"
  2399. Basic exploding oxygen tank
  2400. "health" - how much health the model has - default 20 (zero makes non-breakable)
  2401. "splashRadius" - radius to do damage in - default 48
  2402. "splashDamage" - amount of damage to do when it explodes - default 32
  2403. "targetname" - auto-explodes
  2404. "target" - what to use when it dies
  2405. */
  2406. void gas_random_jet( gentity_t *self )
  2407. {
  2408. vec3_t pt;
  2409. VectorCopy( self->currentOrigin, pt );
  2410. pt[2] += 50;
  2411. G_PlayEffect( "env/mini_gasjet", pt );
  2412. self->nextthink = level.time + random() * 16000 + 12000; // do this rarely
  2413. }
  2414. //------------------------------------------------------------
  2415. void GasBurst( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc )
  2416. {
  2417. vec3_t pt;
  2418. VectorCopy( self->currentOrigin, pt );
  2419. pt[2] += 46;
  2420. G_PlayEffect( "env/mini_flamejet", pt );
  2421. // do some damage to anything that may be standing on top of it when it bursts into flame
  2422. pt[2] += 32;
  2423. G_RadiusDamage( pt, self, 32, 32, self, MOD_UNKNOWN );
  2424. // only get one burst
  2425. self->e_PainFunc = painF_NULL;
  2426. }
  2427. //------------------------------------------------------------
  2428. void SP_misc_gas_tank( gentity_t *ent )
  2429. {
  2430. G_SpawnInt( "health", "20", &ent->health );
  2431. G_SpawnInt( "splashRadius", "48", &ent->splashRadius );
  2432. G_SpawnInt( "splashDamage", "32", &ent->splashDamage );
  2433. ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/tank.md3" );
  2434. G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
  2435. G_EffectIndex( "chunks/metalexplode" );
  2436. G_EffectIndex( "env/mini_flamejet" );
  2437. G_EffectIndex( "env/mini_gasjet" );
  2438. VectorSet( ent->mins, -4, -4, 0 );
  2439. VectorSet( ent->maxs, 4, 4, 40 );
  2440. ent->contents = CONTENTS_SOLID;
  2441. ent->takedamage = qtrue;
  2442. G_SetOrigin( ent, ent->s.origin );
  2443. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  2444. gi.linkentity (ent);
  2445. ent->e_PainFunc = painF_GasBurst;
  2446. if ( ent->targetname )
  2447. {
  2448. ent->e_UseFunc = useF_misc_use;
  2449. }
  2450. ent->material = MAT_METAL3;
  2451. ent->e_DieFunc = dieF_misc_model_breakable_die;
  2452. ent->e_ThinkFunc = thinkF_gas_random_jet;
  2453. ent->nextthink = level.time + random() * 12000 + 6000; // do this rarely
  2454. }
  2455. /*QUAKED misc_crystal_crate (1 0 0.25) (-34 -34 0) (34 34 44) NON_SOLID
  2456. model="models/map_objects/imp_mine/crate_open.md3"
  2457. Open crate of crystals that explode when shot
  2458. NON_SOLID - can only be shot
  2459. "health" - how much health the crate has, default 80
  2460. "splashRadius" - radius to do damage in, default 80
  2461. "splashDamage" - amount of damage to do when it explodes, default 40
  2462. "targetname" - auto-explodes
  2463. "target" - what to use when it dies
  2464. */
  2465. //------------------------------------------------------------
  2466. void CrystalCratePain( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, const vec3_t point, int damage, int mod, int hitLoc )
  2467. {
  2468. vec3_t pt;
  2469. VectorCopy( self->currentOrigin, pt );
  2470. pt[2] += 36;
  2471. G_PlayEffect( "env/crystal_crate", pt );
  2472. // do some damage, heh
  2473. pt[2] += 32;
  2474. G_RadiusDamage( pt, self, 16, 32, self, MOD_UNKNOWN );
  2475. }
  2476. //------------------------------------------------------------
  2477. void SP_misc_crystal_crate( gentity_t *ent )
  2478. {
  2479. G_SpawnInt( "health", "80", &ent->health );
  2480. G_SpawnInt( "splashRadius", "80", &ent->splashRadius );
  2481. G_SpawnInt( "splashDamage", "40", &ent->splashDamage );
  2482. ent->s.modelindex = G_ModelIndex( "models/map_objects/imp_mine/crate_open.md3" );
  2483. ent->fxID = G_EffectIndex( "thermal/explosion" ); // FIXME: temp
  2484. G_EffectIndex( "env/crystal_crate" );
  2485. G_SoundIndex("sound/weapons/explosions/cargoexplode.wav");
  2486. VectorSet( ent->mins, -34, -34, 0 );
  2487. VectorSet( ent->maxs, 34, 34, 44 );
  2488. //Blocks movement
  2489. ent->contents = CONTENTS_SOLID|CONTENTS_OPAQUE|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;//Was CONTENTS_SOLID, but only architecture should be this
  2490. if ( ent->spawnflags & 1 ) // non-solid
  2491. { // Override earlier contents flag...
  2492. //Can only be shot
  2493. ent->contents = CONTENTS_SHOTCLIP;
  2494. }
  2495. ent->takedamage = qtrue;
  2496. G_SetOrigin( ent, ent->s.origin );
  2497. VectorCopy( ent->s.angles, ent->s.apos.trBase );
  2498. gi.linkentity (ent);
  2499. ent->e_PainFunc = painF_CrystalCratePain;
  2500. if ( ent->targetname )
  2501. {
  2502. ent->e_UseFunc = useF_misc_use;
  2503. }
  2504. ent->material = MAT_CRATE2;
  2505. ent->e_DieFunc = dieF_misc_model_breakable_die;
  2506. }
  2507. /*QUAKED misc_atst_drivable (1 0 0.25) (-40 -40 -24) (40 40 248)
  2508. model="models/players/atst/model.glm"
  2509. Drivable ATST, when used by player, they become the ATST. When the player hits use again, they get out.
  2510. "health" - how much health the atst has - default 800
  2511. "target" - what to use when it dies
  2512. */
  2513. void misc_atst_setanim( gentity_t *self, int bone, int anim )
  2514. {
  2515. if ( bone < 0 || anim < 0 )
  2516. {
  2517. return;
  2518. }
  2519. int firstFrame = -1;
  2520. int lastFrame = -1;
  2521. float animSpeed = 0;
  2522. //try to get anim ranges from the animation.cfg for an AT-ST
  2523. for ( int i = 0; i < level.numKnownAnimFileSets; i++ )
  2524. {
  2525. if ( !Q_stricmp( "atst", level.knownAnimFileSets[i].filename ) )
  2526. {
  2527. firstFrame = level.knownAnimFileSets[i].animations[anim].firstFrame;
  2528. lastFrame = firstFrame+level.knownAnimFileSets[i].animations[anim].numFrames;
  2529. animSpeed = 50.0f / level.knownAnimFileSets[i].animations[anim].frameLerp;
  2530. break;
  2531. }
  2532. }
  2533. if ( firstFrame != -1 && lastFrame != -1 && animSpeed != 0 )
  2534. {
  2535. if (!gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame,
  2536. lastFrame, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeed,
  2537. (cg.time?cg.time:level.time), -1, 150 ))
  2538. {
  2539. gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], bone, firstFrame,
  2540. lastFrame, BONE_ANIM_OVERRIDE_FREEZE, animSpeed,
  2541. (cg.time?cg.time:level.time), -1, 150 );
  2542. }
  2543. }
  2544. }
  2545. void misc_atst_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc )
  2546. {//ATST was destroyed while you weren't in it
  2547. //can't be used anymore
  2548. self->e_UseFunc = useF_NULL;
  2549. //sigh... remove contents so we don't block the player's path...
  2550. self->contents = CONTENTS_CORPSE;
  2551. self->takedamage = qfalse;
  2552. self->maxs[2] = 48;
  2553. //FIXME: match to slope
  2554. vec3_t effectPos;
  2555. VectorCopy( self->currentOrigin, effectPos );
  2556. effectPos[2] -= 15;
  2557. G_PlayEffect( "explosions/droidexplosion1", effectPos );
  2558. // G_PlayEffect( "small_chunks", effectPos );
  2559. //set these to defaults that work in a worst-case scenario (according to current animation.cfg)
  2560. gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone );
  2561. misc_atst_setanim( self, self->rootBone, BOTH_DEATH1 );
  2562. }
  2563. extern void G_DriveATST( gentity_t *ent, gentity_t *atst );
  2564. extern void SetClientViewAngle( gentity_t *ent, vec3_t angle );
  2565. extern qboolean PM_InSlopeAnim( int anim );
  2566. void misc_atst_use( gentity_t *self, gentity_t *other, gentity_t *activator )
  2567. {
  2568. if ( !activator || activator->s.number )
  2569. {//only player can do this
  2570. return;
  2571. }
  2572. int tempLocDmg[HL_MAX];
  2573. int hl, tempHealth;
  2574. if ( activator->client->NPC_class != CLASS_ATST )
  2575. {//get in the ATST
  2576. if ( activator->client->ps.groundEntityNum != self->s.number )
  2577. {//can only get in if on top of me...
  2578. //we *could* even check for the hatch surf...?
  2579. return;
  2580. }
  2581. //copy origin
  2582. G_SetOrigin( activator, self->currentOrigin );
  2583. //copy angles
  2584. VectorCopy( self->s.angles2, self->currentAngles );
  2585. G_SetAngles( activator, self->currentAngles );
  2586. SetClientViewAngle( activator, self->currentAngles );
  2587. //set player to my g2 instance
  2588. gi.G2API_StopBoneAnimIndex( &self->ghoul2[self->playerModel], self->craniumBone );
  2589. G_DriveATST( activator, self );
  2590. activator->activator = self;
  2591. self->s.eFlags |= EF_NODRAW;
  2592. self->svFlags |= SVF_NOCLIENT;
  2593. self->contents = 0;
  2594. self->takedamage = qfalse;
  2595. //transfer armor
  2596. tempHealth = self->health;
  2597. self->health = activator->client->ps.stats[STAT_ARMOR];
  2598. activator->client->ps.stats[STAT_ARMOR] = tempHealth;
  2599. //transfer locationDamage
  2600. for ( hl = HL_NONE; hl < HL_MAX; hl++ )
  2601. {
  2602. tempLocDmg[hl] = activator->locationDamage[hl];
  2603. activator->locationDamage[hl] = self->locationDamage[hl];
  2604. self->locationDamage[hl] = tempLocDmg[hl];
  2605. }
  2606. if ( !self->s.number )
  2607. {
  2608. CG_CenterPrint( "@SP_INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.87 );
  2609. }
  2610. }
  2611. else
  2612. {//get out of ATST
  2613. int legsAnim = activator->client->ps.legsAnim;
  2614. if ( legsAnim != BOTH_STAND1
  2615. && !PM_InSlopeAnim( legsAnim )
  2616. && legsAnim != BOTH_TURN_RIGHT1 && legsAnim != BOTH_TURN_LEFT1 )
  2617. {//can't get out of it while it's still moving
  2618. return;
  2619. }
  2620. //FIXME: after a load/save, this crashes, BAD... somewhere in G2
  2621. G_SetOrigin( self, activator->currentOrigin );
  2622. VectorSet( self->currentAngles, 0, activator->client->ps.legsYaw, 0 );
  2623. //self->currentAngles[PITCH] = activator->currentAngles[ROLL] = 0;
  2624. G_SetAngles( self, self->currentAngles );
  2625. VectorCopy( activator->currentAngles, self->s.angles2 );
  2626. //remove my G2
  2627. if ( self->playerModel >= 0 )
  2628. {
  2629. gi.G2API_RemoveGhoul2Model( self->ghoul2, self->playerModel );
  2630. self->playerModel = -1;
  2631. }
  2632. //copy player's
  2633. gi.G2API_CopyGhoul2Instance( activator->ghoul2, self->ghoul2 );
  2634. self->playerModel = 0;//assumption
  2635. //reset player to kyle
  2636. G_DriveATST( activator, NULL );
  2637. activator->activator = NULL;
  2638. self->s.eFlags &= ~EF_NODRAW;
  2639. self->svFlags &= ~SVF_NOCLIENT;
  2640. self->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;
  2641. self->takedamage = qtrue;
  2642. //transfer armor
  2643. tempHealth = self->health;
  2644. self->health = activator->client->ps.stats[STAT_ARMOR];
  2645. activator->client->ps.stats[STAT_ARMOR] = tempHealth;
  2646. //transfer locationDamage
  2647. for ( hl = HL_NONE; hl < HL_MAX; hl++ )
  2648. {
  2649. tempLocDmg[hl] = self->locationDamage[hl];
  2650. self->locationDamage[hl] = activator->locationDamage[hl];
  2651. activator->locationDamage[hl] = tempLocDmg[hl];
  2652. }
  2653. //link me back in
  2654. gi.linkentity ( self );
  2655. //put activator on top of me?
  2656. vec3_t newOrg = {activator->currentOrigin[0], activator->currentOrigin[1], activator->currentOrigin[2] + (self->maxs[2]-self->mins[2]) + 1 };
  2657. G_SetOrigin( activator, newOrg );
  2658. //open the hatch
  2659. misc_atst_setanim( self, self->craniumBone, BOTH_STAND2 );
  2660. gi.G2API_SetSurfaceOnOff( &self->ghoul2[self->playerModel], "head_hatchcover", 0 );
  2661. G_Sound( self, G_SoundIndex( "sound/chars/atst/atst_hatch_open" ));
  2662. }
  2663. }
  2664. void SP_misc_atst_drivable( gentity_t *ent )
  2665. {
  2666. extern void NPC_ATST_Precache(void);
  2667. extern void NPC_PrecacheAnimationCFG( const char *NPC_type );
  2668. ent->s.modelindex = G_ModelIndex( "models/players/atst/model.glm" );
  2669. ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, "models/players/atst/model.glm", ent->s.modelindex );
  2670. ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
  2671. ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue ); //FIXME: need to somehow set the anim/frame to the equivalent of BOTH_STAND1... use to be that BOTH_STAND1 was the first frame of the glm, but not anymore
  2672. ent->s.radius = 320;
  2673. VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
  2674. //register my weapons, sounds and model
  2675. RegisterItem( FindItemForWeapon( WP_ATST_MAIN )); //precache the weapon
  2676. RegisterItem( FindItemForWeapon( WP_ATST_SIDE )); //precache the weapon
  2677. //HACKHACKHACKTEMP - until ATST gets real weapons of it's own?
  2678. RegisterItem( FindItemForWeapon( WP_EMPLACED_GUN )); //precache the weapon
  2679. // RegisterItem( FindItemForWeapon( WP_ROCKET_LAUNCHER )); //precache the weapon
  2680. // RegisterItem( FindItemForWeapon( WP_BOWCASTER )); //precache the weapon
  2681. //HACKHACKHACKTEMP - until ATST gets real weapons of it's own?
  2682. G_SoundIndex( "sound/chars/atst/atst_hatch_open" );
  2683. G_SoundIndex( "sound/chars/atst/atst_hatch_close" );
  2684. NPC_ATST_Precache();
  2685. ent->NPC_type = "atst";
  2686. NPC_PrecacheAnimationCFG( ent->NPC_type );
  2687. //open the hatch
  2688. misc_atst_setanim( ent, ent->rootBone, BOTH_STAND2 );
  2689. gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "head_hatchcover", 0 );
  2690. VectorSet( ent->mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 );
  2691. VectorSet( ent->maxs, ATST_MAXS0, ATST_MAXS1, ATST_MAXS2 );
  2692. ent->contents = CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP;
  2693. ent->flags |= FL_SHIELDED;
  2694. ent->takedamage = qtrue;
  2695. if ( !ent->health )
  2696. {
  2697. ent->health = 800;
  2698. }
  2699. ent->s.radius = 320;
  2700. ent->max_health = ent->health; // cg_draw needs this
  2701. G_SetOrigin( ent, ent->s.origin );
  2702. G_SetAngles( ent, ent->s.angles );
  2703. VectorCopy( ent->currentAngles, ent->s.angles2 );
  2704. gi.linkentity ( ent );
  2705. //FIXME: test the origin to make sure I'm clear?
  2706. ent->e_UseFunc = useF_misc_atst_use;
  2707. ent->svFlags |= SVF_PLAYER_USABLE;
  2708. //make it able to take damage and die when you're not in it...
  2709. //do an explosion and play the death anim, remove use func.
  2710. ent->e_DieFunc = dieF_misc_atst_die;
  2711. }
  2712. extern int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create );
  2713. /*QUAKED misc_weather_zone (0 .5 .8) ?
  2714. Determines a region to check for weather contents - (optional, used to reduce load times)
  2715. Place surrounding your inside/outside brushes. It will not check for weather contents outside of these zones.
  2716. */
  2717. void SP_misc_weather_zone( gentity_t *ent )
  2718. {
  2719. gi.SetBrushModel(ent, ent->model);
  2720. char temp[256];
  2721. sprintf( temp, "zone ( %f %f %f ) ( %f %f %f )",
  2722. ent->mins[0], ent->mins[1], ent->mins[2],
  2723. ent->maxs[0], ent->maxs[1], ent->maxs[2] );
  2724. G_FindConfigstringIndex(temp, CS_WORLD_FX, MAX_WORLD_FX, qtrue);
  2725. // gi.WE_AddWeatherZone(ent->mins, ent->maxs);
  2726. G_FreeEntity(ent);
  2727. }