g_func.cpp 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "g_local.h"
  4. /*
  5. =========================================================
  6. PLATS
  7. movement options:
  8. linear
  9. smooth start, hard stop
  10. smooth start, smooth stop
  11. start
  12. end
  13. acceleration
  14. speed
  15. deceleration
  16. begin sound
  17. end sound
  18. target fired when reaching end
  19. wait at end
  20. object characteristics that use move segments
  21. ---------------------------------------------
  22. movetype_push, or movetype_stop
  23. action when touched
  24. action when blocked
  25. action when used
  26. disabled?
  27. auto trigger spawning
  28. =========================================================
  29. */
  30. constexpr spawnflags_t SPAWNFLAG_DOOR_START_OPEN = 1_spawnflag;
  31. constexpr spawnflags_t SPAWNFLAG_DOOR_CRUSHER = 4_spawnflag;
  32. constexpr spawnflags_t SPAWNFLAG_DOOR_NOMONSTER = 8_spawnflag;
  33. constexpr spawnflags_t SPAWNFLAG_DOOR_ANIMATED = 16_spawnflag;
  34. constexpr spawnflags_t SPAWNFLAG_DOOR_TOGGLE = 32_spawnflag;
  35. constexpr spawnflags_t SPAWNFLAG_DOOR_ANIMATED_FAST = 64_spawnflag;
  36. constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_X_AXIS = 64_spawnflag;
  37. constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_Y_AXIS = 128_spawnflag;
  38. constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_INACTIVE = 0x10000_spawnflag; // Paril: moved to non-reserved
  39. constexpr spawnflags_t SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN = 0x20000_spawnflag;
  40. // support routine for setting moveinfo sounds
  41. inline int32_t G_GetMoveinfoSoundIndex(edict_t *self, const char *default_value, const char *wanted_value)
  42. {
  43. if (!wanted_value)
  44. {
  45. if (default_value)
  46. return gi.soundindex(default_value);
  47. return 0;
  48. }
  49. else if (!*wanted_value || *wanted_value == '0' || *wanted_value == ' ')
  50. return 0;
  51. return gi.soundindex(wanted_value);
  52. }
  53. void G_SetMoveinfoSounds(edict_t *self, const char *default_start, const char *default_mid, const char *default_end)
  54. {
  55. self->moveinfo.sound_start = G_GetMoveinfoSoundIndex(self, default_start, st.noise_start);
  56. self->moveinfo.sound_middle = G_GetMoveinfoSoundIndex(self, default_mid, st.noise_middle);
  57. self->moveinfo.sound_end = G_GetMoveinfoSoundIndex(self, default_end, st.noise_end);
  58. }
  59. //
  60. // Support routines for movement (changes in origin using velocity)
  61. //
  62. THINK(Move_Done) (edict_t *ent) -> void
  63. {
  64. ent->velocity = {};
  65. ent->moveinfo.endfunc(ent);
  66. }
  67. THINK(Move_Final) (edict_t *ent) -> void
  68. {
  69. if (ent->moveinfo.remaining_distance == 0)
  70. {
  71. Move_Done(ent);
  72. return;
  73. }
  74. // [Paril-KEX] use exact remaining distance
  75. ent->velocity = (ent->moveinfo.dest - ent->s.origin) * (1.f / gi.frame_time_s);
  76. ent->think = Move_Done;
  77. ent->nextthink = level.time + FRAME_TIME_S;
  78. }
  79. THINK(Move_Begin) (edict_t *ent) -> void
  80. {
  81. float frames;
  82. if ((ent->moveinfo.speed * gi.frame_time_s) >= ent->moveinfo.remaining_distance)
  83. {
  84. Move_Final(ent);
  85. return;
  86. }
  87. ent->velocity = ent->moveinfo.dir * ent->moveinfo.speed;
  88. frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / gi.frame_time_s);
  89. ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * gi.frame_time_s;
  90. ent->nextthink = level.time + (FRAME_TIME_S * frames);
  91. ent->think = Move_Final;
  92. }
  93. void Think_AccelMove_New(edict_t *ent);
  94. void Think_AccelMove(edict_t *ent);
  95. bool Think_AccelMove_MoveInfo(moveinfo_t *moveinfo);
  96. constexpr float AccelerationDistance(float target, float rate)
  97. {
  98. return (target * ((target / rate) + 1) / 2);
  99. }
  100. inline void Move_Regular(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self))
  101. {
  102. if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
  103. {
  104. Move_Begin(ent);
  105. }
  106. else
  107. {
  108. ent->nextthink = level.time + FRAME_TIME_S;
  109. ent->think = Move_Begin;
  110. }
  111. }
  112. void Move_Calc(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self))
  113. {
  114. ent->velocity = {};
  115. ent->moveinfo.dest = dest;
  116. ent->moveinfo.dir = dest - ent->s.origin;
  117. ent->moveinfo.remaining_distance = ent->moveinfo.dir.normalize();
  118. ent->moveinfo.endfunc = endfunc;
  119. if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
  120. {
  121. Move_Regular(ent, dest, endfunc);
  122. }
  123. else
  124. {
  125. // accelerative
  126. ent->moveinfo.current_speed = 0;
  127. if (gi.tick_rate == 10)
  128. ent->think = Think_AccelMove;
  129. else
  130. {
  131. // [Paril-KEX] rewritten to work better at higher tickrates
  132. ent->moveinfo.curve_frame = 0;
  133. ent->moveinfo.num_subframes = (0.1f / gi.frame_time_s) - 1;
  134. float total_dist = ent->moveinfo.remaining_distance;
  135. std::vector<float> distances;
  136. if (ent->moveinfo.num_subframes)
  137. {
  138. distances.push_back(0);
  139. ent->moveinfo.curve_frame = 1;
  140. }
  141. else
  142. ent->moveinfo.curve_frame = 0;
  143. // simulate 10hz movement
  144. while (ent->moveinfo.remaining_distance)
  145. {
  146. if (!Think_AccelMove_MoveInfo(&ent->moveinfo))
  147. break;
  148. ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
  149. distances.push_back(total_dist - ent->moveinfo.remaining_distance);
  150. }
  151. if (ent->moveinfo.num_subframes)
  152. distances.push_back(total_dist);
  153. ent->moveinfo.subframe = 0;
  154. ent->moveinfo.curve_ref = ent->s.origin;
  155. ent->moveinfo.curve_positions = make_savable_memory<float, TAG_LEVEL>(distances.size());
  156. std::copy(distances.begin(), distances.end(), ent->moveinfo.curve_positions.ptr);
  157. ent->moveinfo.num_frames_done = 0;
  158. ent->think = Think_AccelMove_New;
  159. }
  160. ent->nextthink = level.time + FRAME_TIME_S;
  161. }
  162. }
  163. THINK(Think_AccelMove_New) (edict_t *ent) -> void
  164. {
  165. float t = 0.f;
  166. float target_dist;
  167. if (ent->moveinfo.num_subframes)
  168. {
  169. if (ent->moveinfo.subframe == ent->moveinfo.num_subframes + 1)
  170. {
  171. ent->moveinfo.subframe = 0;
  172. ent->moveinfo.curve_frame++;
  173. if (ent->moveinfo.curve_frame == ent->moveinfo.curve_positions.count)
  174. {
  175. Move_Final(ent);
  176. return;
  177. }
  178. }
  179. t = (ent->moveinfo.subframe + 1) / ((float) ent->moveinfo.num_subframes + 1);
  180. target_dist = lerp(ent->moveinfo.curve_positions[ent->moveinfo.curve_frame - 1], ent->moveinfo.curve_positions[ent->moveinfo.curve_frame], t);
  181. ent->moveinfo.subframe++;
  182. }
  183. else
  184. {
  185. if (ent->moveinfo.curve_frame == ent->moveinfo.curve_positions.count)
  186. {
  187. Move_Final(ent);
  188. return;
  189. }
  190. target_dist = ent->moveinfo.curve_positions[ent->moveinfo.curve_frame++];
  191. }
  192. ent->moveinfo.num_frames_done++;
  193. vec3_t target_pos = ent->moveinfo.curve_ref + (ent->moveinfo.dir * target_dist);
  194. ent->velocity = (target_pos - ent->s.origin) * (1.f / gi.frame_time_s);
  195. ent->nextthink = level.time + FRAME_TIME_S;
  196. }
  197. //
  198. // Support routines for angular movement (changes in angle using avelocity)
  199. //
  200. THINK(AngleMove_Done) (edict_t *ent) -> void
  201. {
  202. ent->avelocity = {};
  203. ent->moveinfo.endfunc(ent);
  204. }
  205. THINK(AngleMove_Final) (edict_t *ent) -> void
  206. {
  207. vec3_t move;
  208. if (ent->moveinfo.state == STATE_UP)
  209. {
  210. if (ent->moveinfo.reversing)
  211. move = ent->moveinfo.end_angles_reversed - ent->s.angles;
  212. else
  213. move = ent->moveinfo.end_angles - ent->s.angles;
  214. }
  215. else
  216. move = ent->moveinfo.start_angles - ent->s.angles;
  217. if (!move)
  218. {
  219. AngleMove_Done(ent);
  220. return;
  221. }
  222. ent->avelocity = move * (1.0f / gi.frame_time_s);
  223. ent->think = AngleMove_Done;
  224. ent->nextthink = level.time + FRAME_TIME_S;
  225. }
  226. THINK(AngleMove_Begin) (edict_t *ent) -> void
  227. {
  228. vec3_t destdelta;
  229. float len;
  230. float traveltime;
  231. float frames;
  232. // PGM accelerate as needed
  233. if (ent->moveinfo.speed < ent->speed)
  234. {
  235. ent->moveinfo.speed += ent->accel;
  236. if (ent->moveinfo.speed > ent->speed)
  237. ent->moveinfo.speed = ent->speed;
  238. }
  239. // PGM
  240. // set destdelta to the vector needed to move
  241. if (ent->moveinfo.state == STATE_UP)
  242. {
  243. if (ent->moveinfo.reversing)
  244. destdelta = ent->moveinfo.end_angles_reversed - ent->s.angles;
  245. else
  246. destdelta = ent->moveinfo.end_angles - ent->s.angles;
  247. }
  248. else
  249. destdelta = ent->moveinfo.start_angles - ent->s.angles;
  250. // calculate length of vector
  251. len = destdelta.length();
  252. // divide by speed to get time to reach dest
  253. traveltime = len / ent->moveinfo.speed;
  254. if (traveltime < gi.frame_time_s)
  255. {
  256. AngleMove_Final(ent);
  257. return;
  258. }
  259. frames = floor(traveltime / gi.frame_time_s);
  260. // scale the destdelta vector by the time spent traveling to get velocity
  261. ent->avelocity = destdelta * (1.0f / traveltime);
  262. // PGM
  263. // if we're done accelerating, act as a normal rotation
  264. if (ent->moveinfo.speed >= ent->speed)
  265. {
  266. // set nextthink to trigger a think when dest is reached
  267. ent->nextthink = level.time + (FRAME_TIME_S * frames);
  268. ent->think = AngleMove_Final;
  269. }
  270. else
  271. {
  272. ent->nextthink = level.time + FRAME_TIME_S;
  273. ent->think = AngleMove_Begin;
  274. }
  275. // PGM
  276. }
  277. void AngleMove_Calc(edict_t *ent, void(*endfunc)(edict_t *self))
  278. {
  279. ent->avelocity = {};
  280. ent->moveinfo.endfunc = endfunc;
  281. // PGM
  282. // if we're supposed to accelerate, this will tell anglemove_begin to do so
  283. if (ent->accel != ent->speed)
  284. ent->moveinfo.speed = 0;
  285. // PGM
  286. if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
  287. {
  288. AngleMove_Begin(ent);
  289. }
  290. else
  291. {
  292. ent->nextthink = level.time + FRAME_TIME_S;
  293. ent->think = AngleMove_Begin;
  294. }
  295. }
  296. /*
  297. ==============
  298. Think_AccelMove
  299. The team has completed a frame of movement, so
  300. change the speed for the next frame
  301. ==============
  302. */
  303. void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
  304. {
  305. float accel_dist;
  306. float decel_dist;
  307. if (moveinfo->remaining_distance < moveinfo->accel)
  308. {
  309. moveinfo->move_speed = moveinfo->speed;
  310. moveinfo->current_speed = moveinfo->remaining_distance;
  311. return;
  312. }
  313. accel_dist = AccelerationDistance(moveinfo->speed, moveinfo->accel);
  314. decel_dist = AccelerationDistance(moveinfo->speed, moveinfo->decel);
  315. if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
  316. {
  317. float f;
  318. f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
  319. moveinfo->move_speed = moveinfo->current_speed =
  320. (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
  321. decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel);
  322. }
  323. else
  324. moveinfo->move_speed = moveinfo->speed;
  325. moveinfo->decel_distance = decel_dist;
  326. };
  327. void plat_Accelerate(moveinfo_t *moveinfo)
  328. {
  329. // are we decelerating?
  330. if (moveinfo->remaining_distance <= moveinfo->decel_distance)
  331. {
  332. if (moveinfo->remaining_distance < moveinfo->decel_distance)
  333. {
  334. if (moveinfo->next_speed)
  335. {
  336. moveinfo->current_speed = moveinfo->next_speed;
  337. moveinfo->next_speed = 0;
  338. return;
  339. }
  340. if (moveinfo->current_speed > moveinfo->decel)
  341. {
  342. moveinfo->current_speed -= moveinfo->decel;
  343. // [Paril-KEX] fix platforms in xdm6, etc
  344. if (fabsf(moveinfo->current_speed) < 0.01f)
  345. moveinfo->current_speed = moveinfo->remaining_distance + 1;
  346. }
  347. }
  348. return;
  349. }
  350. // are we at full speed and need to start decelerating during this move?
  351. if (moveinfo->current_speed == moveinfo->move_speed)
  352. if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
  353. {
  354. float p1_distance;
  355. float p2_distance;
  356. float distance;
  357. p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
  358. p2_distance = moveinfo->move_speed * (1.0f - (p1_distance / moveinfo->move_speed));
  359. distance = p1_distance + p2_distance;
  360. moveinfo->current_speed = moveinfo->move_speed;
  361. moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
  362. return;
  363. }
  364. // are we accelerating?
  365. if (moveinfo->current_speed < moveinfo->speed)
  366. {
  367. float old_speed;
  368. float p1_distance;
  369. float p1_speed;
  370. float p2_distance;
  371. float distance;
  372. old_speed = moveinfo->current_speed;
  373. // figure simple acceleration up to move_speed
  374. moveinfo->current_speed += moveinfo->accel;
  375. if (moveinfo->current_speed > moveinfo->speed)
  376. moveinfo->current_speed = moveinfo->speed;
  377. // are we accelerating throughout this entire move?
  378. if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
  379. return;
  380. // during this move we will accelerate from current_speed to move_speed
  381. // and cross over the decel_distance; figure the average speed for the
  382. // entire move
  383. p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
  384. p1_speed = (old_speed + moveinfo->move_speed) / 2.0f;
  385. p2_distance = moveinfo->move_speed * (1.0f - (p1_distance / p1_speed));
  386. distance = p1_distance + p2_distance;
  387. moveinfo->current_speed =
  388. (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
  389. moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
  390. return;
  391. }
  392. // we are at constant velocity (move_speed)
  393. return;
  394. }
  395. bool Think_AccelMove_MoveInfo (moveinfo_t *moveinfo)
  396. {
  397. if (moveinfo->current_speed == 0) // starting or blocked
  398. plat_CalcAcceleratedMove(moveinfo);
  399. plat_Accelerate(moveinfo);
  400. // will the entire move complete on next frame?
  401. return moveinfo->remaining_distance > moveinfo->current_speed;
  402. }
  403. // Paril: old acceleration code; this is here only to support old save games.
  404. THINK(Think_AccelMove) (edict_t *ent) -> void
  405. {
  406. // [Paril-KEX] calculate distance dynamically
  407. if (ent->moveinfo.state == STATE_UP)
  408. ent->moveinfo.remaining_distance = (ent->moveinfo.start_origin - ent->s.origin).length();
  409. else
  410. ent->moveinfo.remaining_distance = (ent->moveinfo.end_origin - ent->s.origin).length();
  411. // will the entire move complete on next frame?
  412. if (!Think_AccelMove_MoveInfo(&ent->moveinfo))
  413. {
  414. Move_Final(ent);
  415. return;
  416. }
  417. if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
  418. {
  419. Move_Final(ent);
  420. return;
  421. }
  422. ent->velocity = ent->moveinfo.dir * (ent->moveinfo.current_speed * 10);
  423. ent->nextthink = level.time + 10_hz;
  424. ent->think = Think_AccelMove;
  425. }
  426. void plat_go_down(edict_t *ent);
  427. MOVEINFO_ENDFUNC(plat_hit_top) (edict_t *ent) -> void
  428. {
  429. if (!(ent->flags & FL_TEAMSLAVE))
  430. {
  431. if (ent->moveinfo.sound_end)
  432. gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
  433. }
  434. ent->s.sound = 0;
  435. ent->moveinfo.state = STATE_TOP;
  436. ent->think = plat_go_down;
  437. ent->nextthink = level.time + 3_sec;
  438. }
  439. MOVEINFO_ENDFUNC(plat_hit_bottom) (edict_t *ent) -> void
  440. {
  441. if (!(ent->flags & FL_TEAMSLAVE))
  442. {
  443. if (ent->moveinfo.sound_end)
  444. gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
  445. }
  446. ent->s.sound = 0;
  447. ent->moveinfo.state = STATE_BOTTOM;
  448. // ROGUE
  449. plat2_kill_danger_area(ent);
  450. // ROGUE
  451. }
  452. THINK(plat_go_down) (edict_t *ent) -> void
  453. {
  454. if (!(ent->flags & FL_TEAMSLAVE))
  455. {
  456. if (ent->moveinfo.sound_start)
  457. gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  458. }
  459. ent->s.sound = ent->moveinfo.sound_middle;
  460. ent->moveinfo.state = STATE_DOWN;
  461. Move_Calc(ent, ent->moveinfo.end_origin, plat_hit_bottom);
  462. }
  463. void plat_go_up(edict_t *ent)
  464. {
  465. if (!(ent->flags & FL_TEAMSLAVE))
  466. {
  467. if (ent->moveinfo.sound_start)
  468. gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  469. }
  470. ent->s.sound = ent->moveinfo.sound_middle;
  471. ent->moveinfo.state = STATE_UP;
  472. Move_Calc(ent, ent->moveinfo.start_origin, plat_hit_top);
  473. // ROGUE
  474. plat2_spawn_danger_area(ent);
  475. // ROGUE
  476. }
  477. MOVEINFO_BLOCKED(plat_blocked) (edict_t *self, edict_t *other) -> void
  478. {
  479. if (!(other->svflags & SVF_MONSTER) && (!other->client))
  480. {
  481. // give it a chance to go away on it's own terms (like gibs)
  482. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH);
  483. // if it's still there, nuke it
  484. if (other && other->inuse && other->solid) // PGM
  485. BecomeExplosion1(other);
  486. return;
  487. }
  488. // PGM
  489. // gib dead things
  490. if (other->health < 1)
  491. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, DAMAGE_NONE, MOD_CRUSH);
  492. // PGM
  493. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
  494. // [Paril-KEX] killed the thing, so don't switch directions
  495. if (!other->inuse || !other->solid)
  496. return;
  497. if (self->moveinfo.state == STATE_UP)
  498. plat_go_down(self);
  499. else if (self->moveinfo.state == STATE_DOWN)
  500. plat_go_up(self);
  501. }
  502. constexpr spawnflags_t SPAWNFLAG_PLAT_LOW_TRIGGER = 1_spawnflag;
  503. constexpr spawnflags_t SPAWNFLAG_PLAT_NO_MONSTER = 2_spawnflag;
  504. USE(Use_Plat) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  505. {
  506. //======
  507. // ROGUE
  508. // if a monster is using us, then allow the activity when stopped.
  509. if ((other->svflags & SVF_MONSTER) && !(ent->spawnflags & SPAWNFLAG_PLAT_NO_MONSTER))
  510. {
  511. if (ent->moveinfo.state == STATE_TOP)
  512. plat_go_down(ent);
  513. else if (ent->moveinfo.state == STATE_BOTTOM)
  514. plat_go_up(ent);
  515. return;
  516. }
  517. // ROGUE
  518. //======
  519. if (ent->think)
  520. return; // already down
  521. plat_go_down(ent);
  522. }
  523. TOUCH(Touch_Plat_Center) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  524. {
  525. if (!other->client)
  526. return;
  527. if (other->health <= 0)
  528. return;
  529. ent = ent->enemy; // now point at the plat, not the trigger
  530. if (ent->moveinfo.state == STATE_BOTTOM)
  531. plat_go_up(ent);
  532. else if (ent->moveinfo.state == STATE_TOP)
  533. ent->nextthink = level.time + 1_sec; // the player is still on the plat, so delay going down
  534. }
  535. // PGM - plat2's change the trigger field
  536. edict_t *plat_spawn_inside_trigger(edict_t *ent)
  537. {
  538. edict_t *trigger;
  539. vec3_t tmin, tmax;
  540. //
  541. // middle trigger
  542. //
  543. trigger = G_Spawn();
  544. trigger->touch = Touch_Plat_Center;
  545. trigger->movetype = MOVETYPE_NONE;
  546. trigger->solid = SOLID_TRIGGER;
  547. trigger->enemy = ent;
  548. tmin[0] = ent->mins[0] + 25;
  549. tmin[1] = ent->mins[1] + 25;
  550. tmin[2] = ent->mins[2];
  551. tmax[0] = ent->maxs[0] - 25;
  552. tmax[1] = ent->maxs[1] - 25;
  553. tmax[2] = ent->maxs[2] + 8;
  554. tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
  555. if (ent->spawnflags.has(SPAWNFLAG_PLAT_LOW_TRIGGER))
  556. tmax[2] = tmin[2] + 8;
  557. if (tmax[0] - tmin[0] <= 0)
  558. {
  559. tmin[0] = (ent->mins[0] + ent->maxs[0]) * 0.5f;
  560. tmax[0] = tmin[0] + 1;
  561. }
  562. if (tmax[1] - tmin[1] <= 0)
  563. {
  564. tmin[1] = (ent->mins[1] + ent->maxs[1]) * 0.5f;
  565. tmax[1] = tmin[1] + 1;
  566. }
  567. trigger->mins = tmin;
  568. trigger->maxs = tmax;
  569. gi.linkentity(trigger);
  570. return trigger; // PGM 11/17/97
  571. }
  572. /*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
  573. speed default 150
  574. Plats are always drawn in the extended position, so they will light correctly.
  575. If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is triggered, when it will lower and become a normal plat.
  576. "speed" overrides default 200.
  577. "accel" overrides default 500
  578. "lip" overrides default 8 pixel lip
  579. If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determined by the model's height.
  580. Set "sounds" to one of the following:
  581. 1) base fast
  582. 2) chain slow
  583. */
  584. void SP_func_plat(edict_t *ent)
  585. {
  586. ent->s.angles = {};
  587. ent->solid = SOLID_BSP;
  588. ent->movetype = MOVETYPE_PUSH;
  589. gi.setmodel(ent, ent->model);
  590. ent->moveinfo.blocked = plat_blocked;
  591. if (!ent->speed)
  592. ent->speed = 20;
  593. else
  594. ent->speed *= 0.1f;
  595. if (!ent->accel)
  596. ent->accel = 5;
  597. else
  598. ent->accel *= 0.1f;
  599. if (!ent->decel)
  600. ent->decel = 5;
  601. else
  602. ent->decel *= 0.1f;
  603. if (!ent->dmg)
  604. ent->dmg = 2;
  605. if (!st.lip)
  606. st.lip = 8;
  607. // pos1 is the top position, pos2 is the bottom
  608. ent->pos1 = ent->s.origin;
  609. ent->pos2 = ent->s.origin;
  610. if (st.height)
  611. ent->pos2[2] -= st.height;
  612. else
  613. ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
  614. ent->use = Use_Plat;
  615. plat_spawn_inside_trigger(ent); // the "start moving" trigger
  616. if (ent->targetname)
  617. {
  618. ent->moveinfo.state = STATE_UP;
  619. }
  620. else
  621. {
  622. ent->s.origin = ent->pos2;
  623. gi.linkentity(ent);
  624. ent->moveinfo.state = STATE_BOTTOM;
  625. }
  626. ent->moveinfo.speed = ent->speed;
  627. ent->moveinfo.accel = ent->accel;
  628. ent->moveinfo.decel = ent->decel;
  629. ent->moveinfo.wait = ent->wait;
  630. ent->moveinfo.start_origin = ent->pos1;
  631. ent->moveinfo.start_angles = ent->s.angles;
  632. ent->moveinfo.end_origin = ent->pos2;
  633. ent->moveinfo.end_angles = ent->s.angles;
  634. G_SetMoveinfoSounds(ent, "plats/pt1_strt.wav", "plats/pt1_mid.wav", "plats/pt1_end.wav");
  635. }
  636. //====================================================================
  637. // Paril: Rogue added a spawnflag in func_rotating that
  638. // is a reserved editor flag.
  639. constexpr spawnflags_t SPAWNFLAG_ROTATING_START_ON = 1_spawnflag;
  640. constexpr spawnflags_t SPAWNFLAG_ROTATING_REVERSE = 2_spawnflag;
  641. constexpr spawnflags_t SPAWNFLAG_ROTATING_X_AXIS = 4_spawnflag;
  642. constexpr spawnflags_t SPAWNFLAG_ROTATING_Y_AXIS = 8_spawnflag;
  643. constexpr spawnflags_t SPAWNFLAG_ROTATING_TOUCH_PAIN = 16_spawnflag;
  644. constexpr spawnflags_t SPAWNFLAG_ROTATING_STOP = 32_spawnflag;
  645. constexpr spawnflags_t SPAWNFLAG_ROTATING_ANIMATED = 64_spawnflag;
  646. constexpr spawnflags_t SPAWNFLAG_ROTATING_ANIMATED_FAST = 128_spawnflag;
  647. constexpr spawnflags_t SPAWNFLAG_ROTATING_ACCEL = 0x00010000_spawnflag;
  648. /*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP RESERVED1 COOP_ONLY RESERVED2 ACCEL
  649. You need to have an origin brush as part of this entity.
  650. The center of that brush will be the point around which it is rotated. It will rotate around the Z axis by default.
  651. You can check either the X_AXIS or Y_AXIS box to change that.
  652. func_rotating will use it's targets when it stops and starts.
  653. "speed" determines how fast it moves; default value is 100.
  654. "dmg" damage to inflict when blocked (2 default)
  655. "accel" if specified, is how much the rotation speed will increase per .1sec.
  656. REVERSE will cause the it to rotate in the opposite direction.
  657. STOP mean it will stop moving instead of pushing entities
  658. ACCEL means it will accelerate to it's final speed and decelerate when shutting down.
  659. */
  660. //============
  661. // PGM
  662. THINK(rotating_accel) (edict_t *self) -> void
  663. {
  664. float current_speed;
  665. current_speed = self->avelocity.length();
  666. if (current_speed >= (self->speed - self->accel)) // done
  667. {
  668. self->avelocity = self->movedir * self->speed;
  669. G_UseTargets(self, self);
  670. }
  671. else
  672. {
  673. current_speed += self->accel;
  674. self->avelocity = self->movedir * current_speed;
  675. self->think = rotating_accel;
  676. self->nextthink = level.time + FRAME_TIME_S;
  677. }
  678. }
  679. THINK(rotating_decel) (edict_t *self) -> void
  680. {
  681. float current_speed;
  682. current_speed = self->avelocity.length();
  683. if (current_speed <= self->decel) // done
  684. {
  685. self->avelocity = {};
  686. G_UseTargets(self, self);
  687. self->touch = nullptr;
  688. }
  689. else
  690. {
  691. current_speed -= self->decel;
  692. self->avelocity = self->movedir * current_speed;
  693. self->think = rotating_decel;
  694. self->nextthink = level.time + FRAME_TIME_S;
  695. }
  696. }
  697. // PGM
  698. //============
  699. MOVEINFO_BLOCKED(rotating_blocked) (edict_t *self, edict_t *other) -> void
  700. {
  701. if (!self->dmg)
  702. return;
  703. if (level.time < self->touch_debounce_time)
  704. return;
  705. self->touch_debounce_time = level.time + 10_hz;
  706. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
  707. }
  708. TOUCH(rotating_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  709. {
  710. if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
  711. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
  712. }
  713. USE(rotating_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  714. {
  715. if (self->avelocity)
  716. {
  717. self->s.sound = 0;
  718. // PGM
  719. if (self->spawnflags.has(SPAWNFLAG_ROTATING_ACCEL)) // Decelerate
  720. rotating_decel(self);
  721. else
  722. {
  723. self->avelocity = {};
  724. G_UseTargets(self, self);
  725. self->touch = nullptr;
  726. }
  727. // PGM
  728. }
  729. else
  730. {
  731. self->s.sound = self->moveinfo.sound_middle;
  732. // PGM
  733. if (self->spawnflags.has(SPAWNFLAG_ROTATING_ACCEL)) // accelerate
  734. rotating_accel(self);
  735. else
  736. {
  737. self->avelocity = self->movedir * self->speed;
  738. G_UseTargets(self, self);
  739. }
  740. if (self->spawnflags.has(SPAWNFLAG_ROTATING_TOUCH_PAIN))
  741. self->touch = rotating_touch;
  742. // PGM
  743. }
  744. }
  745. void SP_func_rotating(edict_t *ent)
  746. {
  747. ent->solid = SOLID_BSP;
  748. if (ent->spawnflags.has(SPAWNFLAG_ROTATING_STOP))
  749. ent->movetype = MOVETYPE_STOP;
  750. else
  751. ent->movetype = MOVETYPE_PUSH;
  752. if (st.noise)
  753. {
  754. ent->moveinfo.sound_middle = gi.soundindex(st.noise);
  755. // [Paril-KEX] for rhangar1 doors
  756. if (!st.was_key_specified("attenuation"))
  757. ent->attenuation = ATTN_STATIC;
  758. else
  759. {
  760. if (ent->attenuation == -1)
  761. {
  762. ent->s.loop_attenuation = ATTN_LOOP_NONE;
  763. ent->attenuation = ATTN_NONE;
  764. }
  765. else
  766. {
  767. ent->s.loop_attenuation = ent->attenuation;
  768. }
  769. }
  770. }
  771. // set the axis of rotation
  772. ent->movedir = {};
  773. if (ent->spawnflags.has(SPAWNFLAG_ROTATING_X_AXIS))
  774. ent->movedir[2] = 1.0;
  775. else if (ent->spawnflags.has(SPAWNFLAG_ROTATING_Y_AXIS))
  776. ent->movedir[0] = 1.0;
  777. else // Z_AXIS
  778. ent->movedir[1] = 1.0;
  779. // check for reverse rotation
  780. if (ent->spawnflags.has(SPAWNFLAG_ROTATING_REVERSE))
  781. ent->movedir = -ent->movedir;
  782. if (!ent->speed)
  783. ent->speed = 100;
  784. if (!st.was_key_specified("dmg"))
  785. ent->dmg = 2;
  786. ent->use = rotating_use;
  787. if (ent->dmg)
  788. ent->moveinfo.blocked = rotating_blocked;
  789. if (ent->spawnflags.has(SPAWNFLAG_ROTATING_START_ON))
  790. ent->use(ent, nullptr, nullptr);
  791. if (ent->spawnflags.has(SPAWNFLAG_ROTATING_ANIMATED))
  792. ent->s.effects |= EF_ANIM_ALL;
  793. if (ent->spawnflags.has(SPAWNFLAG_ROTATING_ANIMATED_FAST))
  794. ent->s.effects |= EF_ANIM_ALLFAST;
  795. // PGM
  796. if (ent->spawnflags.has(SPAWNFLAG_ROTATING_ACCEL)) // Accelerate / Decelerate
  797. {
  798. if (!ent->accel)
  799. ent->accel = 1;
  800. else if (ent->accel > ent->speed)
  801. ent->accel = ent->speed;
  802. if (!ent->decel)
  803. ent->decel = 1;
  804. else if (ent->decel > ent->speed)
  805. ent->decel = ent->speed;
  806. }
  807. // PGM
  808. gi.setmodel(ent, ent->model);
  809. gi.linkentity(ent);
  810. }
  811. THINK(func_spinning_think) (edict_t *ent) -> void
  812. {
  813. if (ent->timestamp <= level.time)
  814. {
  815. ent->timestamp = level.time + random_time(1_sec, 6_sec);
  816. ent->movedir = { ent->decel + frandom(ent->speed - ent->decel), ent->decel + frandom(ent->speed - ent->decel), ent->decel + frandom(ent->speed - ent->decel) };
  817. for (int32_t i = 0; i < 3; i++)
  818. {
  819. if (brandom())
  820. ent->movedir[i] = -ent->movedir[i];
  821. }
  822. }
  823. for (int32_t i = 0; i < 3; i++)
  824. {
  825. if (ent->avelocity[i] == ent->movedir[i])
  826. continue;
  827. if (ent->avelocity[i] < ent->movedir[i])
  828. ent->avelocity[i] = min(ent->movedir[i], ent->avelocity[i] + ent->accel);
  829. else
  830. ent->avelocity[i] = max(ent->movedir[i], ent->avelocity[i] - ent->accel);
  831. }
  832. ent->nextthink = level.time + FRAME_TIME_MS;
  833. }
  834. // [Paril-KEX]
  835. void SP_func_spinning(edict_t *ent)
  836. {
  837. ent->solid = SOLID_BSP;
  838. if (!ent->speed)
  839. ent->speed = 100;
  840. if (!ent->dmg)
  841. ent->dmg = 2;
  842. ent->movetype = MOVETYPE_PUSH;
  843. ent->timestamp = 0_ms;
  844. ent->nextthink = level.time + FRAME_TIME_MS;
  845. ent->think = func_spinning_think;
  846. gi.setmodel(ent, ent->model);
  847. gi.linkentity(ent);
  848. }
  849. /*
  850. ======================================================================
  851. BUTTONS
  852. ======================================================================
  853. */
  854. /*QUAKED func_button (0 .5 .8) ?
  855. When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
  856. "angle" determines the opening direction
  857. "target" all entities with a matching targetname will be used
  858. "speed" override the default 40 speed
  859. "wait" override the default 1 second wait (-1 = never return)
  860. "lip" override the default 4 pixel lip remaining at end of move
  861. "health" if set, the button must be killed instead of touched
  862. "sounds"
  863. 1) silent
  864. 2) steam metal
  865. 3) wooden clunk
  866. 4) metallic click
  867. 5) in-out
  868. */
  869. MOVEINFO_ENDFUNC(button_done) (edict_t *self) -> void
  870. {
  871. self->moveinfo.state = STATE_BOTTOM;
  872. if (!self->bmodel_anim.enabled)
  873. {
  874. if (level.is_n64)
  875. self->s.frame = 0;
  876. else
  877. self->s.effects &= ~EF_ANIM23;
  878. self->s.effects |= EF_ANIM01;
  879. }
  880. else
  881. self->bmodel_anim.alternate = false;
  882. }
  883. THINK(button_return) (edict_t *self) -> void
  884. {
  885. self->moveinfo.state = STATE_DOWN;
  886. Move_Calc(self, self->moveinfo.start_origin, button_done);
  887. if (self->health)
  888. self->takedamage = true;
  889. }
  890. MOVEINFO_ENDFUNC(button_wait) (edict_t *self) -> void
  891. {
  892. self->moveinfo.state = STATE_TOP;
  893. if (!self->bmodel_anim.enabled)
  894. {
  895. self->s.effects &= ~EF_ANIM01;
  896. if (level.is_n64)
  897. self->s.frame = 2;
  898. else
  899. self->s.effects |= EF_ANIM23;
  900. }
  901. else
  902. self->bmodel_anim.alternate = true;
  903. G_UseTargets(self, self->activator);
  904. if (self->moveinfo.wait >= 0)
  905. {
  906. self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait);
  907. self->think = button_return;
  908. }
  909. }
  910. void button_fire(edict_t *self)
  911. {
  912. if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
  913. return;
  914. self->moveinfo.state = STATE_UP;
  915. if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
  916. gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  917. Move_Calc(self, self->moveinfo.end_origin, button_wait);
  918. }
  919. USE(button_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  920. {
  921. self->activator = activator;
  922. button_fire(self);
  923. }
  924. TOUCH(button_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  925. {
  926. if (!other->client)
  927. return;
  928. if (other->health <= 0)
  929. return;
  930. self->activator = other;
  931. button_fire(self);
  932. }
  933. DIE(button_killed) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  934. {
  935. self->activator = attacker;
  936. self->health = self->max_health;
  937. self->takedamage = false;
  938. button_fire(self);
  939. }
  940. void SP_func_button(edict_t *ent)
  941. {
  942. vec3_t abs_movedir;
  943. float dist;
  944. G_SetMovedir(ent->s.angles, ent->movedir);
  945. ent->movetype = MOVETYPE_STOP;
  946. ent->solid = SOLID_BSP;
  947. gi.setmodel(ent, ent->model);
  948. if (ent->sounds != 1)
  949. G_SetMoveinfoSounds(ent, "switches/butn2.wav", nullptr, nullptr);
  950. else
  951. G_SetMoveinfoSounds(ent, nullptr, nullptr, nullptr);
  952. if (!ent->speed)
  953. ent->speed = 40;
  954. if (!ent->accel)
  955. ent->accel = ent->speed;
  956. if (!ent->decel)
  957. ent->decel = ent->speed;
  958. if (!ent->wait)
  959. ent->wait = 3;
  960. if (!st.lip)
  961. st.lip = 4;
  962. ent->pos1 = ent->s.origin;
  963. abs_movedir[0] = fabsf(ent->movedir[0]);
  964. abs_movedir[1] = fabsf(ent->movedir[1]);
  965. abs_movedir[2] = fabsf(ent->movedir[2]);
  966. dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
  967. ent->pos2 = ent->pos1 + (ent->movedir * dist);
  968. ent->use = button_use;
  969. if (!ent->bmodel_anim.enabled)
  970. ent->s.effects |= EF_ANIM01;
  971. if (ent->health)
  972. {
  973. ent->max_health = ent->health;
  974. ent->die = button_killed;
  975. ent->takedamage = true;
  976. }
  977. else if (!ent->targetname)
  978. ent->touch = button_touch;
  979. ent->moveinfo.state = STATE_BOTTOM;
  980. ent->moveinfo.speed = ent->speed;
  981. ent->moveinfo.accel = ent->accel;
  982. ent->moveinfo.decel = ent->decel;
  983. ent->moveinfo.wait = ent->wait;
  984. ent->moveinfo.start_origin = ent->pos1;
  985. ent->moveinfo.start_angles = ent->s.angles;
  986. ent->moveinfo.end_origin = ent->pos2;
  987. ent->moveinfo.end_angles = ent->s.angles;
  988. gi.linkentity(ent);
  989. }
  990. /*
  991. ======================================================================
  992. DOORS
  993. spawn a trigger surrounding the entire team unless it is
  994. already targeted by another
  995. ======================================================================
  996. */
  997. /*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
  998. TOGGLE wait in both the start and end states for a trigger event.
  999. START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
  1000. NOMONSTER monsters will not trigger this door
  1001. "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
  1002. "angle" determines the opening direction
  1003. "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
  1004. "health" if set, door must be shot open
  1005. "speed" movement speed (100 default)
  1006. "wait" wait before returning (3 default, -1 = never return)
  1007. "lip" lip remaining at end of move (8 default)
  1008. "dmg" damage to inflict when blocked (2 default)
  1009. "sounds"
  1010. 1) silent
  1011. 2) light
  1012. 3) medium
  1013. 4) heavy
  1014. */
  1015. void door_use_areaportals(edict_t *self, bool open)
  1016. {
  1017. edict_t *t = nullptr;
  1018. if (!self->target)
  1019. return;
  1020. while ((t = G_FindByString<&edict_t::targetname>(t, self->target)))
  1021. {
  1022. if (Q_strcasecmp(t->classname, "func_areaportal") == 0)
  1023. {
  1024. gi.SetAreaPortalState(t->style, open);
  1025. }
  1026. }
  1027. }
  1028. void door_go_down(edict_t *self);
  1029. static void door_play_sound(edict_t *self, int32_t sound)
  1030. {
  1031. if (!self->teammaster)
  1032. {
  1033. gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0);
  1034. return;
  1035. }
  1036. vec3_t p = {};
  1037. int32_t c = 0;
  1038. for (edict_t *t = self->teammaster; t; t = t->teamchain)
  1039. {
  1040. p += (t->absmin + t->absmax) * 0.5f;
  1041. c++;
  1042. }
  1043. if (c == 1)
  1044. {
  1045. gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0);
  1046. return;
  1047. }
  1048. p /= c;
  1049. if (gi.pointcontents(p) & CONTENTS_SOLID)
  1050. {
  1051. gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0);
  1052. return;
  1053. }
  1054. gi.positioned_sound(p, self, CHAN_NO_PHS_ADD | CHAN_VOICE, sound, 1, self->attenuation, 0);
  1055. }
  1056. MOVEINFO_ENDFUNC(door_hit_top) (edict_t *self) -> void
  1057. {
  1058. if (!(self->flags & FL_TEAMSLAVE))
  1059. {
  1060. if (self->moveinfo.sound_end)
  1061. door_play_sound(self, self->moveinfo.sound_end);
  1062. }
  1063. self->s.sound = 0;
  1064. self->moveinfo.state = STATE_TOP;
  1065. if (self->spawnflags.has(SPAWNFLAG_DOOR_TOGGLE))
  1066. return;
  1067. if (self->moveinfo.wait >= 0)
  1068. {
  1069. self->think = door_go_down;
  1070. self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait);
  1071. }
  1072. if (self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN))
  1073. door_use_areaportals(self, false);
  1074. }
  1075. MOVEINFO_ENDFUNC(door_hit_bottom) (edict_t *self) -> void
  1076. {
  1077. if (!(self->flags & FL_TEAMSLAVE))
  1078. {
  1079. if (self->moveinfo.sound_end)
  1080. door_play_sound(self, self->moveinfo.sound_end);
  1081. }
  1082. self->s.sound = 0;
  1083. self->moveinfo.state = STATE_BOTTOM;
  1084. if (!self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN))
  1085. door_use_areaportals(self, false);
  1086. }
  1087. THINK(door_go_down) (edict_t *self) -> void
  1088. {
  1089. if (!(self->flags & FL_TEAMSLAVE))
  1090. {
  1091. if (self->moveinfo.sound_start)
  1092. door_play_sound(self, self->moveinfo.sound_start);
  1093. }
  1094. self->s.sound = self->moveinfo.sound_middle;
  1095. if (self->max_health)
  1096. {
  1097. self->takedamage = true;
  1098. self->health = self->max_health;
  1099. }
  1100. self->moveinfo.state = STATE_DOWN;
  1101. if (strcmp(self->classname, "func_door") == 0 ||
  1102. strcmp(self->classname, "func_water") == 0 ||
  1103. strcmp(self->classname, "func_door_secret") == 0)
  1104. Move_Calc(self, self->moveinfo.start_origin, door_hit_bottom);
  1105. else if (strcmp(self->classname, "func_door_rotating") == 0)
  1106. AngleMove_Calc(self, door_hit_bottom);
  1107. if (self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN))
  1108. door_use_areaportals(self, true);
  1109. }
  1110. void door_go_up(edict_t *self, edict_t *activator)
  1111. {
  1112. if (self->moveinfo.state == STATE_UP)
  1113. return; // already going up
  1114. if (self->moveinfo.state == STATE_TOP)
  1115. { // reset top wait time
  1116. if (self->moveinfo.wait >= 0)
  1117. self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait);
  1118. return;
  1119. }
  1120. if (!(self->flags & FL_TEAMSLAVE))
  1121. {
  1122. if (self->moveinfo.sound_start)
  1123. door_play_sound(self, self->moveinfo.sound_start);
  1124. }
  1125. self->s.sound = self->moveinfo.sound_middle;
  1126. self->moveinfo.state = STATE_UP;
  1127. if (strcmp(self->classname, "func_door") == 0 ||
  1128. strcmp(self->classname, "func_water") == 0 ||
  1129. strcmp(self->classname, "func_door_secret") == 0)
  1130. Move_Calc(self, self->moveinfo.end_origin, door_hit_top);
  1131. else if (strcmp(self->classname, "func_door_rotating") == 0)
  1132. AngleMove_Calc(self, door_hit_top);
  1133. G_UseTargets(self, activator);
  1134. if (!(self->spawnflags & SPAWNFLAG_DOOR_START_OPEN))
  1135. door_use_areaportals(self, true);
  1136. }
  1137. //======
  1138. // PGM
  1139. THINK(smart_water_go_up) (edict_t *self) -> void
  1140. {
  1141. float distance;
  1142. edict_t *lowestPlayer;
  1143. edict_t *ent;
  1144. float lowestPlayerPt;
  1145. if (self->moveinfo.state == STATE_TOP)
  1146. { // reset top wait time
  1147. if (self->moveinfo.wait >= 0)
  1148. self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait);
  1149. return;
  1150. }
  1151. if (self->health)
  1152. {
  1153. if (self->absmax[2] >= self->health)
  1154. {
  1155. self->velocity = {};
  1156. self->nextthink = 0_ms;
  1157. self->moveinfo.state = STATE_TOP;
  1158. return;
  1159. }
  1160. }
  1161. if (!(self->flags & FL_TEAMSLAVE))
  1162. {
  1163. if (self->moveinfo.sound_start)
  1164. gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  1165. }
  1166. self->s.sound = self->moveinfo.sound_middle;
  1167. // find the lowest player point.
  1168. lowestPlayerPt = 999999;
  1169. lowestPlayer = nullptr;
  1170. for (uint32_t i = 0; i < game.maxclients; i++)
  1171. {
  1172. ent = &g_edicts[1 + i];
  1173. // don't count dead or unused player slots
  1174. if ((ent->inuse) && (ent->health > 0))
  1175. {
  1176. if (ent->absmin[2] < lowestPlayerPt)
  1177. {
  1178. lowestPlayerPt = ent->absmin[2];
  1179. lowestPlayer = ent;
  1180. }
  1181. }
  1182. }
  1183. if (!lowestPlayer)
  1184. {
  1185. return;
  1186. }
  1187. distance = lowestPlayerPt - self->absmax[2];
  1188. // for the calculations, make sure we intend to go up at least a little.
  1189. if (distance < self->accel)
  1190. {
  1191. distance = 100;
  1192. self->moveinfo.speed = 5;
  1193. }
  1194. else
  1195. self->moveinfo.speed = distance / self->accel;
  1196. if (self->moveinfo.speed < 5)
  1197. self->moveinfo.speed = 5;
  1198. else if (self->moveinfo.speed > self->speed)
  1199. self->moveinfo.speed = self->speed;
  1200. // FIXME - should this allow any movement other than straight up?
  1201. self->moveinfo.dir = { 0, 0, 1 };
  1202. self->velocity = self->moveinfo.dir * self->moveinfo.speed;
  1203. self->moveinfo.remaining_distance = distance;
  1204. if (self->moveinfo.state != STATE_UP)
  1205. {
  1206. G_UseTargets(self, lowestPlayer);
  1207. door_use_areaportals(self, true);
  1208. self->moveinfo.state = STATE_UP;
  1209. }
  1210. self->think = smart_water_go_up;
  1211. self->nextthink = level.time + FRAME_TIME_S;
  1212. }
  1213. // PGM
  1214. //======
  1215. USE(door_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1216. {
  1217. edict_t *ent;
  1218. vec3_t center; // PGM
  1219. if (self->flags & FL_TEAMSLAVE)
  1220. return;
  1221. if ((strcmp(self->classname, "func_door_rotating") == 0) && self->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN) &&
  1222. (self->moveinfo.state == STATE_BOTTOM || self->moveinfo.state == STATE_DOWN))
  1223. {
  1224. if (self->moveinfo.dir)
  1225. {
  1226. vec3_t forward = (activator->s.origin - self->s.origin).normalized();
  1227. self->moveinfo.reversing = forward.dot(self->moveinfo.dir) > 0;
  1228. }
  1229. }
  1230. if (self->spawnflags.has(SPAWNFLAG_DOOR_TOGGLE))
  1231. {
  1232. if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
  1233. {
  1234. // trigger all paired doors
  1235. for (ent = self; ent; ent = ent->teamchain)
  1236. {
  1237. ent->message = nullptr;
  1238. ent->touch = nullptr;
  1239. door_go_down(ent);
  1240. }
  1241. return;
  1242. }
  1243. }
  1244. // PGM
  1245. // smart water is different
  1246. center = self->mins + self->maxs;
  1247. center *= 0.5f;
  1248. if ((strcmp(self->classname, "func_water") == 0) && (gi.pointcontents(center) & MASK_WATER) && self->spawnflags.has(SPAWNFLAG_WATER_SMART))
  1249. {
  1250. self->message = nullptr;
  1251. self->touch = nullptr;
  1252. self->enemy = activator;
  1253. smart_water_go_up(self);
  1254. return;
  1255. }
  1256. // PGM
  1257. // trigger all paired doors
  1258. for (ent = self; ent; ent = ent->teamchain)
  1259. {
  1260. ent->message = nullptr;
  1261. ent->touch = nullptr;
  1262. door_go_up(ent, activator);
  1263. }
  1264. };
  1265. TOUCH(Touch_DoorTrigger) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  1266. {
  1267. if (other->health <= 0)
  1268. return;
  1269. if (!(other->svflags & SVF_MONSTER) && (!other->client))
  1270. return;
  1271. if (self->owner->spawnflags.has(SPAWNFLAG_DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
  1272. return;
  1273. if (level.time < self->touch_debounce_time)
  1274. return;
  1275. self->touch_debounce_time = level.time + 1_sec;
  1276. door_use(self->owner, other, other);
  1277. }
  1278. THINK(Think_CalcMoveSpeed) (edict_t *self) -> void
  1279. {
  1280. edict_t *ent;
  1281. float min;
  1282. float time;
  1283. float newspeed;
  1284. float ratio;
  1285. float dist;
  1286. if (self->flags & FL_TEAMSLAVE)
  1287. return; // only the team master does this
  1288. // find the smallest distance any member of the team will be moving
  1289. min = fabsf(self->moveinfo.distance);
  1290. for (ent = self->teamchain; ent; ent = ent->teamchain)
  1291. {
  1292. dist = fabsf(ent->moveinfo.distance);
  1293. if (dist < min)
  1294. min = dist;
  1295. }
  1296. time = min / self->moveinfo.speed;
  1297. // adjust speeds so they will all complete at the same time
  1298. for (ent = self; ent; ent = ent->teamchain)
  1299. {
  1300. newspeed = fabsf(ent->moveinfo.distance) / time;
  1301. ratio = newspeed / ent->moveinfo.speed;
  1302. if (ent->moveinfo.accel == ent->moveinfo.speed)
  1303. ent->moveinfo.accel = newspeed;
  1304. else
  1305. ent->moveinfo.accel *= ratio;
  1306. if (ent->moveinfo.decel == ent->moveinfo.speed)
  1307. ent->moveinfo.decel = newspeed;
  1308. else
  1309. ent->moveinfo.decel *= ratio;
  1310. ent->moveinfo.speed = newspeed;
  1311. }
  1312. }
  1313. THINK(Think_SpawnDoorTrigger) (edict_t *ent) -> void
  1314. {
  1315. edict_t *other;
  1316. vec3_t mins, maxs;
  1317. if (ent->flags & FL_TEAMSLAVE)
  1318. return; // only the team leader spawns a trigger
  1319. mins = ent->absmin;
  1320. maxs = ent->absmax;
  1321. for (other = ent->teamchain; other; other = other->teamchain)
  1322. {
  1323. AddPointToBounds(other->absmin, mins, maxs);
  1324. AddPointToBounds(other->absmax, mins, maxs);
  1325. }
  1326. // expand
  1327. mins[0] -= 60;
  1328. mins[1] -= 60;
  1329. maxs[0] += 60;
  1330. maxs[1] += 60;
  1331. other = G_Spawn();
  1332. other->mins = mins;
  1333. other->maxs = maxs;
  1334. other->owner = ent;
  1335. other->solid = SOLID_TRIGGER;
  1336. other->movetype = MOVETYPE_NONE;
  1337. other->touch = Touch_DoorTrigger;
  1338. gi.linkentity(other);
  1339. Think_CalcMoveSpeed(ent);
  1340. }
  1341. MOVEINFO_BLOCKED(door_blocked) (edict_t *self, edict_t *other) -> void
  1342. {
  1343. edict_t *ent;
  1344. if (!(other->svflags & SVF_MONSTER) && (!other->client))
  1345. {
  1346. // give it a chance to go away on it's own terms (like gibs)
  1347. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH);
  1348. // if it's still there, nuke it
  1349. if (other && other->inuse)
  1350. BecomeExplosion1(other);
  1351. return;
  1352. }
  1353. if (self->dmg && !(level.time < self->touch_debounce_time))
  1354. {
  1355. self->touch_debounce_time = level.time + 10_hz;
  1356. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
  1357. }
  1358. // [Paril-KEX] don't allow wait -1 doors to return
  1359. if (self->spawnflags.has(SPAWNFLAG_DOOR_CRUSHER) || self->wait == -1)
  1360. return;
  1361. // if a door has a negative wait, it would never come back if blocked,
  1362. // so let it just squash the object to death real fast
  1363. if (self->moveinfo.wait >= 0)
  1364. {
  1365. if (self->moveinfo.state == STATE_DOWN)
  1366. {
  1367. for (ent = self->teammaster; ent; ent = ent->teamchain)
  1368. door_go_up(ent, ent->activator);
  1369. }
  1370. else
  1371. {
  1372. for (ent = self->teammaster; ent; ent = ent->teamchain)
  1373. door_go_down(ent);
  1374. }
  1375. }
  1376. }
  1377. DIE(door_killed) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  1378. {
  1379. edict_t *ent;
  1380. for (ent = self->teammaster; ent; ent = ent->teamchain)
  1381. {
  1382. ent->health = ent->max_health;
  1383. ent->takedamage = false;
  1384. }
  1385. door_use(self->teammaster, attacker, attacker);
  1386. }
  1387. TOUCH(door_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  1388. {
  1389. if (!other->client)
  1390. return;
  1391. if (level.time < self->touch_debounce_time)
  1392. return;
  1393. self->touch_debounce_time = level.time + 5_sec;
  1394. gi.LocCenter_Print(other, "{}", self->message);
  1395. gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
  1396. }
  1397. THINK(Think_DoorActivateAreaPortal) (edict_t *ent) -> void
  1398. {
  1399. door_use_areaportals(ent, true);
  1400. if (ent->health || ent->targetname)
  1401. Think_CalcMoveSpeed(ent);
  1402. else
  1403. Think_SpawnDoorTrigger(ent);
  1404. }
  1405. void SP_func_door(edict_t *ent)
  1406. {
  1407. vec3_t abs_movedir;
  1408. if (ent->sounds != 1)
  1409. G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav");
  1410. else
  1411. G_SetMoveinfoSounds(ent, nullptr, nullptr, nullptr);
  1412. // [Paril-KEX] for rhangar1 doors
  1413. if (!st.was_key_specified("attenuation"))
  1414. ent->attenuation = ATTN_STATIC;
  1415. else
  1416. {
  1417. if (ent->attenuation == -1)
  1418. {
  1419. ent->s.loop_attenuation = ATTN_LOOP_NONE;
  1420. ent->attenuation = ATTN_NONE;
  1421. }
  1422. else
  1423. {
  1424. ent->s.loop_attenuation = ent->attenuation;
  1425. }
  1426. }
  1427. G_SetMovedir(ent->s.angles, ent->movedir);
  1428. ent->movetype = MOVETYPE_PUSH;
  1429. ent->solid = SOLID_BSP;
  1430. ent->svflags |= SVF_DOOR;
  1431. gi.setmodel(ent, ent->model);
  1432. ent->moveinfo.blocked = door_blocked;
  1433. ent->use = door_use;
  1434. if (!ent->speed)
  1435. ent->speed = 100;
  1436. if (deathmatch->integer)
  1437. ent->speed *= 2;
  1438. if (!ent->accel)
  1439. ent->accel = ent->speed;
  1440. if (!ent->decel)
  1441. ent->decel = ent->speed;
  1442. if (!ent->wait)
  1443. ent->wait = 3;
  1444. if (!st.lip)
  1445. st.lip = 8;
  1446. if (!ent->dmg)
  1447. ent->dmg = 2;
  1448. // calculate second position
  1449. ent->pos1 = ent->s.origin;
  1450. abs_movedir[0] = fabsf(ent->movedir[0]);
  1451. abs_movedir[1] = fabsf(ent->movedir[1]);
  1452. abs_movedir[2] = fabsf(ent->movedir[2]);
  1453. ent->moveinfo.distance =
  1454. abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
  1455. ent->pos2 = ent->pos1 + (ent->movedir * ent->moveinfo.distance);
  1456. // if it starts open, switch the positions
  1457. if (ent->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN))
  1458. {
  1459. ent->s.origin = ent->pos2;
  1460. ent->pos2 = ent->pos1;
  1461. ent->pos1 = ent->s.origin;
  1462. }
  1463. ent->moveinfo.state = STATE_BOTTOM;
  1464. if (ent->health)
  1465. {
  1466. ent->takedamage = true;
  1467. ent->die = door_killed;
  1468. ent->max_health = ent->health;
  1469. }
  1470. else if (ent->targetname)
  1471. {
  1472. if (ent->message)
  1473. {
  1474. gi.soundindex("misc/talk.wav");
  1475. ent->touch = door_touch;
  1476. }
  1477. ent->flags |= FL_LOCKED;
  1478. }
  1479. ent->moveinfo.speed = ent->speed;
  1480. ent->moveinfo.accel = ent->accel;
  1481. ent->moveinfo.decel = ent->decel;
  1482. ent->moveinfo.wait = ent->wait;
  1483. ent->moveinfo.start_origin = ent->pos1;
  1484. ent->moveinfo.start_angles = ent->s.angles;
  1485. ent->moveinfo.end_origin = ent->pos2;
  1486. ent->moveinfo.end_angles = ent->s.angles;
  1487. if (ent->spawnflags.has(SPAWNFLAG_DOOR_ANIMATED))
  1488. ent->s.effects |= EF_ANIM_ALL;
  1489. if (ent->spawnflags.has(SPAWNFLAG_DOOR_ANIMATED_FAST))
  1490. ent->s.effects |= EF_ANIM_ALLFAST;
  1491. // to simplify logic elsewhere, make non-teamed doors into a team of one
  1492. if (!ent->team)
  1493. ent->teammaster = ent;
  1494. gi.linkentity(ent);
  1495. ent->nextthink = level.time + FRAME_TIME_S;
  1496. if (ent->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN))
  1497. ent->think = Think_DoorActivateAreaPortal;
  1498. else if (ent->health || ent->targetname)
  1499. ent->think = Think_CalcMoveSpeed;
  1500. else
  1501. ent->think = Think_SpawnDoorTrigger;
  1502. }
  1503. // PGM
  1504. USE(Door_Activate) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1505. {
  1506. self->use = nullptr;
  1507. if (self->health)
  1508. {
  1509. self->takedamage = true;
  1510. self->die = door_killed;
  1511. self->max_health = self->health;
  1512. }
  1513. if (self->health)
  1514. self->think = Think_CalcMoveSpeed;
  1515. else
  1516. self->think = Think_SpawnDoorTrigger;
  1517. self->nextthink = level.time + FRAME_TIME_S;
  1518. }
  1519. // PGM
  1520. /*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP RESERVED1 COOP_ONLY RESERVED2 INACTIVE SAFE_OPEN
  1521. TOGGLE causes the door to wait in both the start and end states for a trigger event.
  1522. START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
  1523. NOMONSTER monsters will not trigger this door
  1524. You need to have an origin brush as part of this entity. The center of that brush will be
  1525. the point around which it is rotated. It will rotate around the Z axis by default. You can
  1526. check either the X_AXIS or Y_AXIS box to change that.
  1527. "distance" is how many degrees the door will be rotated.
  1528. "speed" determines how fast the door moves; default value is 100.
  1529. "accel" if specified,is how much the rotation speed will increase each .1 sec. (default: no accel)
  1530. REVERSE will cause the door to rotate in the opposite direction.
  1531. INACTIVE will cause the door to be inactive until triggered.
  1532. SAFE_OPEN will cause the door to open in reverse if you are on the `angles` side of the door.
  1533. "message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
  1534. "angle" determines the opening direction
  1535. "targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
  1536. "health" if set, door must be shot open
  1537. "speed" movement speed (100 default)
  1538. "wait" wait before returning (3 default, -1 = never return)
  1539. "dmg" damage to inflict when blocked (2 default)
  1540. "sounds"
  1541. 1) silent
  1542. 2) light
  1543. 3) medium
  1544. 4) heavy
  1545. */
  1546. void SP_func_door_rotating(edict_t *ent)
  1547. {
  1548. if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN))
  1549. G_SetMovedir(ent->s.angles, ent->moveinfo.dir);
  1550. ent->s.angles = {};
  1551. // set the axis of rotation
  1552. ent->movedir = {};
  1553. if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_X_AXIS))
  1554. ent->movedir[2] = 1.0;
  1555. else if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_Y_AXIS))
  1556. ent->movedir[0] = 1.0;
  1557. else // Z_AXIS
  1558. ent->movedir[1] = 1.0;
  1559. // check for reverse rotation
  1560. if (ent->spawnflags.has(SPAWNFLAG_DOOR_REVERSE))
  1561. ent->movedir = -ent->movedir;
  1562. if (!st.distance)
  1563. {
  1564. gi.Com_PrintFmt("{}: no distance set\n", *ent);
  1565. st.distance = 90;
  1566. }
  1567. ent->pos1 = ent->s.angles;
  1568. ent->pos2 = ent->s.angles + (ent->movedir * st.distance);
  1569. ent->pos3 = ent->s.angles + (ent->movedir * -st.distance);
  1570. ent->moveinfo.distance = (float) st.distance;
  1571. ent->movetype = MOVETYPE_PUSH;
  1572. ent->solid = SOLID_BSP;
  1573. ent->svflags |= SVF_DOOR;
  1574. gi.setmodel(ent, ent->model);
  1575. ent->moveinfo.blocked = door_blocked;
  1576. ent->use = door_use;
  1577. if (!ent->speed)
  1578. ent->speed = 100;
  1579. if (!ent->accel)
  1580. ent->accel = ent->speed;
  1581. if (!ent->decel)
  1582. ent->decel = ent->speed;
  1583. if (!ent->wait)
  1584. ent->wait = 3;
  1585. if (!ent->dmg)
  1586. ent->dmg = 2;
  1587. if (ent->sounds != 1)
  1588. G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav");
  1589. else
  1590. G_SetMoveinfoSounds(ent, nullptr, nullptr, nullptr);
  1591. // [Paril-KEX] for rhangar1 doors
  1592. if (!st.was_key_specified("attenuation"))
  1593. ent->attenuation = ATTN_STATIC;
  1594. else
  1595. {
  1596. if (ent->attenuation == -1)
  1597. {
  1598. ent->s.loop_attenuation = ATTN_LOOP_NONE;
  1599. ent->attenuation = ATTN_NONE;
  1600. }
  1601. else
  1602. {
  1603. ent->s.loop_attenuation = ent->attenuation;
  1604. }
  1605. }
  1606. // if it starts open, switch the positions
  1607. if (ent->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN))
  1608. {
  1609. if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN))
  1610. {
  1611. ent->spawnflags &= ~SPAWNFLAG_DOOR_ROTATING_SAFE_OPEN;
  1612. gi.Com_PrintFmt("{}: SAFE_OPEN is not compatible with START_OPEN\n", *ent);
  1613. }
  1614. ent->s.angles = ent->pos2;
  1615. ent->pos2 = ent->pos1;
  1616. ent->pos1 = ent->s.angles;
  1617. ent->movedir = -ent->movedir;
  1618. }
  1619. if (ent->health)
  1620. {
  1621. ent->takedamage = true;
  1622. ent->die = door_killed;
  1623. ent->max_health = ent->health;
  1624. }
  1625. if (ent->targetname && ent->message)
  1626. {
  1627. gi.soundindex("misc/talk.wav");
  1628. ent->touch = door_touch;
  1629. }
  1630. ent->moveinfo.state = STATE_BOTTOM;
  1631. ent->moveinfo.speed = ent->speed;
  1632. ent->moveinfo.accel = ent->accel;
  1633. ent->moveinfo.decel = ent->decel;
  1634. ent->moveinfo.wait = ent->wait;
  1635. ent->moveinfo.start_origin = ent->s.origin;
  1636. ent->moveinfo.start_angles = ent->pos1;
  1637. ent->moveinfo.end_origin = ent->s.origin;
  1638. ent->moveinfo.end_angles = ent->pos2;
  1639. ent->moveinfo.end_angles_reversed = ent->pos3;
  1640. if (ent->spawnflags.has(SPAWNFLAG_DOOR_ANIMATED))
  1641. ent->s.effects |= EF_ANIM_ALL;
  1642. // to simplify logic elsewhere, make non-teamed doors into a team of one
  1643. if (!ent->team)
  1644. ent->teammaster = ent;
  1645. gi.linkentity(ent);
  1646. ent->nextthink = level.time + FRAME_TIME_S;
  1647. if (ent->health || ent->targetname)
  1648. ent->think = Think_CalcMoveSpeed;
  1649. else
  1650. ent->think = Think_SpawnDoorTrigger;
  1651. // PGM
  1652. if (ent->spawnflags.has(SPAWNFLAG_DOOR_ROTATING_INACTIVE))
  1653. {
  1654. ent->takedamage = false;
  1655. ent->die = nullptr;
  1656. ent->think = nullptr;
  1657. ent->nextthink = 0_ms;
  1658. ent->use = Door_Activate;
  1659. }
  1660. // PGM
  1661. }
  1662. MOVEINFO_BLOCKED(smart_water_blocked) (edict_t *self, edict_t *other) -> void
  1663. {
  1664. if (!(other->svflags & SVF_MONSTER) && (!other->client))
  1665. {
  1666. // give it a chance to go away on it's own terms (like gibs)
  1667. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_LAVA);
  1668. // if it's still there, nuke it
  1669. if (other && other->inuse && other->solid) // PGM
  1670. BecomeExplosion1(other);
  1671. return;
  1672. }
  1673. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, DAMAGE_NONE, MOD_LAVA);
  1674. }
  1675. /*QUAKED func_water (0 .5 .8) ? START_OPEN SMART
  1676. func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk.
  1677. START_OPEN causes the water to move to its destination when spawned and operate in reverse.
  1678. SMART causes the water to adjust its speed depending on distance to player.
  1679. (speed = distance/accel, min 5, max self->speed)
  1680. "accel" for smart water, the divisor to determine water speed. default 20 (smaller = faster)
  1681. "health" maximum height of this water brush
  1682. "angle" determines the opening direction (up or down only)
  1683. "speed" movement speed (25 default)
  1684. "wait" wait before returning (-1 default, -1 = TOGGLE)
  1685. "lip" lip remaining at end of move (0 default)
  1686. "sounds" (yes, these need to be changed)
  1687. 0) no sound
  1688. 1) water
  1689. 2) lava
  1690. */
  1691. void SP_func_water(edict_t *self)
  1692. {
  1693. vec3_t abs_movedir;
  1694. G_SetMovedir(self->s.angles, self->movedir);
  1695. self->movetype = MOVETYPE_PUSH;
  1696. self->solid = SOLID_BSP;
  1697. gi.setmodel(self, self->model);
  1698. switch (self->sounds)
  1699. {
  1700. default:
  1701. G_SetMoveinfoSounds(self, nullptr, nullptr, nullptr);
  1702. break;
  1703. case 1: // water
  1704. case 2: // lava
  1705. G_SetMoveinfoSounds(self, "world/mov_watr.wav", nullptr, "world/stp_watr.wav");
  1706. break;
  1707. }
  1708. self->attenuation = ATTN_STATIC;
  1709. // calculate second position
  1710. self->pos1 = self->s.origin;
  1711. abs_movedir[0] = fabsf(self->movedir[0]);
  1712. abs_movedir[1] = fabsf(self->movedir[1]);
  1713. abs_movedir[2] = fabsf(self->movedir[2]);
  1714. self->moveinfo.distance =
  1715. abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
  1716. self->pos2 = self->pos1 + (self->movedir * self->moveinfo.distance);
  1717. // if it starts open, switch the positions
  1718. if (self->spawnflags.has(SPAWNFLAG_DOOR_START_OPEN))
  1719. {
  1720. self->s.origin = self->pos2;
  1721. self->pos2 = self->pos1;
  1722. self->pos1 = self->s.origin;
  1723. }
  1724. self->moveinfo.start_origin = self->pos1;
  1725. self->moveinfo.start_angles = self->s.angles;
  1726. self->moveinfo.end_origin = self->pos2;
  1727. self->moveinfo.end_angles = self->s.angles;
  1728. self->moveinfo.state = STATE_BOTTOM;
  1729. if (!self->speed)
  1730. self->speed = 25;
  1731. self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
  1732. // ROGUE
  1733. if (self->spawnflags.has(SPAWNFLAG_WATER_SMART)) // smart water
  1734. {
  1735. // this is actually the divisor of the lowest player's distance to determine speed.
  1736. // self->speed then becomes the cap of the speed.
  1737. if (!self->accel)
  1738. self->accel = 20;
  1739. self->moveinfo.blocked = smart_water_blocked;
  1740. }
  1741. // ROGUE
  1742. if (!self->wait)
  1743. self->wait = -1;
  1744. self->moveinfo.wait = self->wait;
  1745. self->use = door_use;
  1746. if (self->wait == -1)
  1747. self->spawnflags |= SPAWNFLAG_DOOR_TOGGLE;
  1748. gi.linkentity(self);
  1749. }
  1750. constexpr spawnflags_t SPAWNFLAG_TRAIN_TOGGLE = 2_spawnflag;
  1751. constexpr spawnflags_t SPAWNFLAG_TRAIN_BLOCK_STOPS = 4_spawnflag;
  1752. constexpr spawnflags_t SPAWNFLAG_TRAIN_FIX_OFFSET = 16_spawnflag;
  1753. constexpr spawnflags_t SPAWNFLAG_TRAIN_USE_ORIGIN = 32_spawnflag;
  1754. /*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS MOVE_TEAMCHAIN FIX_OFFSET USE_ORIGIN
  1755. Trains are moving platforms that players can ride.
  1756. The targets origin specifies the min point of the train at each corner.
  1757. The train spawns at the first target it is pointing at.
  1758. If the train is the target of a button or trigger, it will not begin moving until activated.
  1759. speed default 100
  1760. dmg default 2
  1761. noise looping sound to play when the train is in motion
  1762. To have other entities move with the train, set all the piece's team value to the same thing. They will move in unison.
  1763. */
  1764. void train_next(edict_t *self);
  1765. MOVEINFO_BLOCKED(train_blocked) (edict_t *self, edict_t *other) -> void
  1766. {
  1767. if (!(other->svflags & SVF_MONSTER) && (!other->client))
  1768. {
  1769. // give it a chance to go away on it's own terms (like gibs)
  1770. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH);
  1771. // if it's still there, nuke it
  1772. if (other && other->inuse && other->solid)
  1773. BecomeExplosion1(other);
  1774. return;
  1775. }
  1776. if (level.time < self->touch_debounce_time)
  1777. return;
  1778. if (!self->dmg)
  1779. return;
  1780. self->touch_debounce_time = level.time + 500_ms;
  1781. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
  1782. }
  1783. MOVEINFO_ENDFUNC(train_wait) (edict_t *self) -> void
  1784. {
  1785. if (self->target_ent->pathtarget)
  1786. {
  1787. const char *savetarget;
  1788. edict_t *ent;
  1789. ent = self->target_ent;
  1790. savetarget = ent->target;
  1791. ent->target = ent->pathtarget;
  1792. G_UseTargets(ent, self->activator);
  1793. ent->target = savetarget;
  1794. // make sure we didn't get killed by a killtarget
  1795. if (!self->inuse)
  1796. return;
  1797. }
  1798. if (self->moveinfo.wait)
  1799. {
  1800. if (self->moveinfo.wait > 0)
  1801. {
  1802. self->nextthink = level.time + gtime_t::from_sec(self->moveinfo.wait);
  1803. self->think = train_next;
  1804. }
  1805. else if (self->spawnflags.has(SPAWNFLAG_TRAIN_TOGGLE)) // && wait < 0
  1806. {
  1807. // PMM - clear target_ent, let train_next get called when we get used
  1808. // train_next (self);
  1809. self->target_ent = nullptr;
  1810. // pmm
  1811. self->spawnflags &= ~SPAWNFLAG_TRAIN_START_ON;
  1812. self->velocity = {};
  1813. self->nextthink = 0_ms;
  1814. }
  1815. if (!(self->flags & FL_TEAMSLAVE))
  1816. {
  1817. if (self->moveinfo.sound_end)
  1818. gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
  1819. }
  1820. self->s.sound = 0;
  1821. }
  1822. else
  1823. {
  1824. train_next(self);
  1825. }
  1826. }
  1827. // PGM
  1828. MOVEINFO_ENDFUNC(train_piece_wait) (edict_t *self) -> void
  1829. {
  1830. }
  1831. // PGM
  1832. THINK(train_next) (edict_t *self) -> void
  1833. {
  1834. edict_t *ent;
  1835. vec3_t dest;
  1836. bool first;
  1837. first = true;
  1838. again:
  1839. if (!self->target)
  1840. {
  1841. self->s.sound = 0;
  1842. return;
  1843. }
  1844. ent = G_PickTarget(self->target);
  1845. if (!ent)
  1846. {
  1847. gi.Com_PrintFmt("{}: train_next: bad target {}\n", *self, self->target);
  1848. return;
  1849. }
  1850. self->target = ent->target;
  1851. // check for a teleport path_corner
  1852. if (ent->spawnflags.has(SPAWNFLAG_PATH_CORNER_TELEPORT))
  1853. {
  1854. if (!first)
  1855. {
  1856. gi.Com_PrintFmt("{}: connected teleport path_corners\n", *ent);
  1857. return;
  1858. }
  1859. first = false;
  1860. if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN))
  1861. self->s.origin = ent->s.origin;
  1862. else
  1863. {
  1864. self->s.origin = ent->s.origin - self->mins;
  1865. if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET))
  1866. self->s.origin -= vec3_t{1.f, 1.f, 1.f};
  1867. }
  1868. self->s.old_origin = self->s.origin;
  1869. self->s.event = EV_OTHER_TELEPORT;
  1870. gi.linkentity(self);
  1871. goto again;
  1872. }
  1873. // PGM
  1874. if (ent->speed)
  1875. {
  1876. self->speed = ent->speed;
  1877. self->moveinfo.speed = ent->speed;
  1878. if (ent->accel)
  1879. self->moveinfo.accel = ent->accel;
  1880. else
  1881. self->moveinfo.accel = ent->speed;
  1882. if (ent->decel)
  1883. self->moveinfo.decel = ent->decel;
  1884. else
  1885. self->moveinfo.decel = ent->speed;
  1886. self->moveinfo.current_speed = 0;
  1887. }
  1888. // PGM
  1889. self->moveinfo.wait = ent->wait;
  1890. self->target_ent = ent;
  1891. if (!(self->flags & FL_TEAMSLAVE))
  1892. {
  1893. if (self->moveinfo.sound_start)
  1894. gi.sound(self, CHAN_NO_PHS_ADD | CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
  1895. }
  1896. self->s.sound = self->moveinfo.sound_middle;
  1897. if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN))
  1898. dest = ent->s.origin;
  1899. else
  1900. {
  1901. dest = ent->s.origin - self->mins;
  1902. if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET))
  1903. dest -= vec3_t{1.f, 1.f, 1.f};
  1904. }
  1905. self->moveinfo.state = STATE_TOP;
  1906. self->moveinfo.start_origin = self->s.origin;
  1907. self->moveinfo.end_origin = dest;
  1908. Move_Calc(self, dest, train_wait);
  1909. self->spawnflags |= SPAWNFLAG_TRAIN_START_ON;
  1910. // PGM
  1911. if (self->spawnflags.has(SPAWNFLAG_TRAIN_MOVE_TEAMCHAIN))
  1912. {
  1913. edict_t *e;
  1914. vec3_t dir, dst;
  1915. dir = dest - self->s.origin;
  1916. for (e = self->teamchain; e; e = e->teamchain)
  1917. {
  1918. dst = dir + e->s.origin;
  1919. e->moveinfo.start_origin = e->s.origin;
  1920. e->moveinfo.end_origin = dst;
  1921. e->moveinfo.state = STATE_TOP;
  1922. e->speed = self->speed;
  1923. e->moveinfo.speed = self->moveinfo.speed;
  1924. e->moveinfo.accel = self->moveinfo.accel;
  1925. e->moveinfo.decel = self->moveinfo.decel;
  1926. e->movetype = MOVETYPE_PUSH;
  1927. Move_Calc(e, dst, train_piece_wait);
  1928. }
  1929. }
  1930. // PGM
  1931. }
  1932. void train_resume(edict_t *self)
  1933. {
  1934. edict_t *ent;
  1935. vec3_t dest;
  1936. ent = self->target_ent;
  1937. if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN))
  1938. dest = ent->s.origin;
  1939. else
  1940. {
  1941. dest = ent->s.origin - self->mins;
  1942. if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET))
  1943. dest -= vec3_t{1.f, 1.f, 1.f};
  1944. }
  1945. self->s.sound = self->moveinfo.sound_middle;
  1946. self->moveinfo.state = STATE_TOP;
  1947. self->moveinfo.start_origin = self->s.origin;
  1948. self->moveinfo.end_origin = dest;
  1949. Move_Calc(self, dest, train_wait);
  1950. self->spawnflags |= SPAWNFLAG_TRAIN_START_ON;
  1951. }
  1952. THINK(func_train_find) (edict_t *self) -> void
  1953. {
  1954. edict_t *ent;
  1955. if (!self->target)
  1956. {
  1957. gi.Com_PrintFmt("{}: train_find: no target\n", *self);
  1958. return;
  1959. }
  1960. ent = G_PickTarget(self->target);
  1961. if (!ent)
  1962. {
  1963. gi.Com_PrintFmt("{}: train_find: target {} not found\n", *self, self->target);
  1964. return;
  1965. }
  1966. self->target = ent->target;
  1967. if (self->spawnflags.has(SPAWNFLAG_TRAIN_USE_ORIGIN))
  1968. self->s.origin = ent->s.origin;
  1969. else
  1970. {
  1971. self->s.origin = ent->s.origin - self->mins;
  1972. if (self->spawnflags.has(SPAWNFLAG_TRAIN_FIX_OFFSET))
  1973. self->s.origin -= vec3_t{1.f, 1.f, 1.f};
  1974. }
  1975. gi.linkentity(self);
  1976. // if not triggered, start immediately
  1977. if (!self->targetname)
  1978. self->spawnflags |= SPAWNFLAG_TRAIN_START_ON;
  1979. if (self->spawnflags.has(SPAWNFLAG_TRAIN_START_ON))
  1980. {
  1981. self->nextthink = level.time + FRAME_TIME_S;
  1982. self->think = train_next;
  1983. self->activator = self;
  1984. }
  1985. }
  1986. USE(train_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1987. {
  1988. self->activator = activator;
  1989. if (self->spawnflags.has(SPAWNFLAG_TRAIN_START_ON))
  1990. {
  1991. if (!self->spawnflags.has(SPAWNFLAG_TRAIN_TOGGLE))
  1992. return;
  1993. self->spawnflags &= ~SPAWNFLAG_TRAIN_START_ON;
  1994. self->velocity = {};
  1995. self->nextthink = 0_ms;
  1996. }
  1997. else
  1998. {
  1999. if (self->target_ent)
  2000. train_resume(self);
  2001. else
  2002. train_next(self);
  2003. }
  2004. }
  2005. void SP_func_train(edict_t *self)
  2006. {
  2007. self->movetype = MOVETYPE_PUSH;
  2008. self->s.angles = {};
  2009. self->moveinfo.blocked = train_blocked;
  2010. if (self->spawnflags.has(SPAWNFLAG_TRAIN_BLOCK_STOPS))
  2011. self->dmg = 0;
  2012. else
  2013. {
  2014. if (!self->dmg)
  2015. self->dmg = 100;
  2016. }
  2017. self->solid = SOLID_BSP;
  2018. gi.setmodel(self, self->model);
  2019. if (st.noise)
  2020. {
  2021. self->moveinfo.sound_middle = gi.soundindex(st.noise);
  2022. // [Paril-KEX] for rhangar1 doors
  2023. if (!st.was_key_specified("attenuation"))
  2024. self->attenuation = ATTN_STATIC;
  2025. else
  2026. {
  2027. if (self->attenuation == -1)
  2028. {
  2029. self->s.loop_attenuation = ATTN_LOOP_NONE;
  2030. self->attenuation = ATTN_NONE;
  2031. }
  2032. else
  2033. {
  2034. self->s.loop_attenuation = self->attenuation;
  2035. }
  2036. }
  2037. }
  2038. if (!self->speed)
  2039. self->speed = 100;
  2040. self->moveinfo.speed = self->speed;
  2041. self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
  2042. self->use = train_use;
  2043. gi.linkentity(self);
  2044. if (self->target)
  2045. {
  2046. // start trains on the second frame, to make sure their targets have had
  2047. // a chance to spawn
  2048. self->nextthink = level.time + FRAME_TIME_S;
  2049. self->think = func_train_find;
  2050. }
  2051. else
  2052. {
  2053. gi.Com_PrintFmt("{}: no target\n", *self);
  2054. }
  2055. }
  2056. /*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
  2057. */
  2058. USE(trigger_elevator_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  2059. {
  2060. edict_t *target;
  2061. if (self->movetarget->nextthink)
  2062. return;
  2063. if (!other->pathtarget)
  2064. {
  2065. gi.Com_PrintFmt("{}: elevator used with no pathtarget\n", *self);
  2066. return;
  2067. }
  2068. target = G_PickTarget(other->pathtarget);
  2069. if (!target)
  2070. {
  2071. gi.Com_PrintFmt("{}: elevator used with bad pathtarget: {}\n", *self, other->pathtarget);
  2072. return;
  2073. }
  2074. self->movetarget->target_ent = target;
  2075. train_resume(self->movetarget);
  2076. }
  2077. THINK(trigger_elevator_init) (edict_t *self) -> void
  2078. {
  2079. if (!self->target)
  2080. {
  2081. gi.Com_PrintFmt("{}: has no target\n", *self);
  2082. return;
  2083. }
  2084. self->movetarget = G_PickTarget(self->target);
  2085. if (!self->movetarget)
  2086. {
  2087. gi.Com_PrintFmt("{}: unable to find target {}\n", *self, self->target);
  2088. return;
  2089. }
  2090. if (strcmp(self->movetarget->classname, "func_train") != 0)
  2091. {
  2092. gi.Com_PrintFmt("{}: target {} is not a train\n", *self, self->target);
  2093. return;
  2094. }
  2095. self->use = trigger_elevator_use;
  2096. self->svflags = SVF_NOCLIENT;
  2097. }
  2098. void SP_trigger_elevator(edict_t *self)
  2099. {
  2100. self->think = trigger_elevator_init;
  2101. self->nextthink = level.time + FRAME_TIME_S;
  2102. }
  2103. /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
  2104. "wait" base time between triggering all targets, default is 1
  2105. "random" wait variance, default is 0
  2106. so, the basic time between firing is a random time between
  2107. (wait - random) and (wait + random)
  2108. "delay" delay before first firing when turned on, default is 0
  2109. "pausetime" additional delay used only the very first time
  2110. and only if spawned with START_ON
  2111. These can used but not touched.
  2112. */
  2113. constexpr spawnflags_t SPAWNFLAG_TIMER_START_ON = 1_spawnflag;
  2114. THINK(func_timer_think) (edict_t *self) -> void
  2115. {
  2116. G_UseTargets(self, self->activator);
  2117. self->nextthink = level.time + gtime_t::from_sec(self->wait + crandom() * self->random);
  2118. }
  2119. USE(func_timer_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  2120. {
  2121. self->activator = activator;
  2122. // if on, turn it off
  2123. if (self->nextthink)
  2124. {
  2125. self->nextthink = 0_ms;
  2126. return;
  2127. }
  2128. // turn it on
  2129. if (self->delay)
  2130. self->nextthink = level.time + gtime_t::from_sec(self->delay);
  2131. else
  2132. func_timer_think(self);
  2133. }
  2134. void SP_func_timer(edict_t *self)
  2135. {
  2136. if (!self->wait)
  2137. self->wait = 1.0;
  2138. self->use = func_timer_use;
  2139. self->think = func_timer_think;
  2140. if (self->random >= self->wait)
  2141. {
  2142. self->random = self->wait - gi.frame_time_s;
  2143. gi.Com_PrintFmt("{}: random >= wait\n", *self);
  2144. }
  2145. if (self->spawnflags.has(SPAWNFLAG_TIMER_START_ON))
  2146. {
  2147. self->nextthink = level.time + 1_sec + gtime_t::from_sec(st.pausetime + self->delay + self->wait + crandom() * self->random);
  2148. self->activator = self;
  2149. }
  2150. self->svflags = SVF_NOCLIENT;
  2151. }
  2152. constexpr spawnflags_t SPAWNFLAG_CONVEYOR_START_ON = 1_spawnflag;
  2153. constexpr spawnflags_t SPAWNFLAG_CONVEYOR_TOGGLE = 2_spawnflag;
  2154. /*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
  2155. Conveyors are stationary brushes that move what's on them.
  2156. The brush should be have a surface with at least one current content enabled.
  2157. speed default 100
  2158. */
  2159. USE(func_conveyor_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  2160. {
  2161. if (self->spawnflags.has(SPAWNFLAG_CONVEYOR_START_ON))
  2162. {
  2163. self->speed = 0;
  2164. self->spawnflags &= ~SPAWNFLAG_CONVEYOR_START_ON;
  2165. }
  2166. else
  2167. {
  2168. self->speed = (float) self->count;
  2169. self->spawnflags |= SPAWNFLAG_CONVEYOR_START_ON;
  2170. }
  2171. if (!self->spawnflags.has(SPAWNFLAG_CONVEYOR_TOGGLE))
  2172. self->count = 0;
  2173. }
  2174. void SP_func_conveyor(edict_t *self)
  2175. {
  2176. if (!self->speed)
  2177. self->speed = 100;
  2178. if (!self->spawnflags.has(SPAWNFLAG_CONVEYOR_START_ON))
  2179. {
  2180. self->count = (int) self->speed;
  2181. self->speed = 0;
  2182. }
  2183. self->use = func_conveyor_use;
  2184. gi.setmodel(self, self->model);
  2185. self->solid = SOLID_BSP;
  2186. gi.linkentity(self);
  2187. }
  2188. /*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
  2189. A secret door. Slide back and then to the side.
  2190. open_once doors never closes
  2191. 1st_left 1st move is left of arrow
  2192. 1st_down 1st move is down from arrow
  2193. always_shoot door is shootebale even if targeted
  2194. "angle" determines the direction
  2195. "dmg" damage to inflic when blocked (default 2)
  2196. "wait" how long to hold in the open position (default 5, -1 means hold)
  2197. */
  2198. constexpr spawnflags_t SPAWNFLAG_SECRET_ALWAYS_SHOOT = 1_spawnflag;
  2199. constexpr spawnflags_t SPAWNFLAG_SECRET_1ST_LEFT = 2_spawnflag;
  2200. constexpr spawnflags_t SPAWNFLAG_SECRET_1ST_DOWN = 4_spawnflag;
  2201. void door_secret_move1(edict_t *self);
  2202. void door_secret_move2(edict_t *self);
  2203. void door_secret_move3(edict_t *self);
  2204. void door_secret_move4(edict_t *self);
  2205. void door_secret_move5(edict_t *self);
  2206. void door_secret_move6(edict_t *self);
  2207. void door_secret_done(edict_t *self);
  2208. USE(door_secret_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  2209. {
  2210. // make sure we're not already moving
  2211. if (self->s.origin)
  2212. return;
  2213. Move_Calc(self, self->pos1, door_secret_move1);
  2214. door_use_areaportals(self, true);
  2215. }
  2216. MOVEINFO_ENDFUNC(door_secret_move1) (edict_t *self) -> void
  2217. {
  2218. self->nextthink = level.time + 1_sec;
  2219. self->think = door_secret_move2;
  2220. }
  2221. THINK(door_secret_move2) (edict_t *self) -> void
  2222. {
  2223. Move_Calc(self, self->pos2, door_secret_move3);
  2224. }
  2225. MOVEINFO_ENDFUNC(door_secret_move3) (edict_t *self) -> void
  2226. {
  2227. if (self->wait == -1)
  2228. return;
  2229. self->nextthink = level.time + gtime_t::from_sec(self->wait);
  2230. self->think = door_secret_move4;
  2231. }
  2232. THINK(door_secret_move4) (edict_t *self) -> void
  2233. {
  2234. Move_Calc(self, self->pos1, door_secret_move5);
  2235. }
  2236. MOVEINFO_ENDFUNC(door_secret_move5) (edict_t *self) -> void
  2237. {
  2238. self->nextthink = level.time + 1_sec;
  2239. self->think = door_secret_move6;
  2240. }
  2241. THINK(door_secret_move6) (edict_t *self) -> void
  2242. {
  2243. Move_Calc(self, vec3_origin, door_secret_done);
  2244. }
  2245. MOVEINFO_ENDFUNC(door_secret_done) (edict_t *self) -> void
  2246. {
  2247. if (!(self->targetname) || self->spawnflags.has(SPAWNFLAG_SECRET_ALWAYS_SHOOT))
  2248. {
  2249. self->health = 0;
  2250. self->takedamage = true;
  2251. }
  2252. door_use_areaportals(self, false);
  2253. }
  2254. MOVEINFO_BLOCKED(door_secret_blocked) (edict_t *self, edict_t *other) -> void
  2255. {
  2256. if (!(other->svflags & SVF_MONSTER) && (!other->client))
  2257. {
  2258. // give it a chance to go away on it's own terms (like gibs)
  2259. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH);
  2260. // if it's still there, nuke it
  2261. if (other && other->inuse && other->solid)
  2262. BecomeExplosion1(other);
  2263. return;
  2264. }
  2265. if (level.time < self->touch_debounce_time)
  2266. return;
  2267. self->touch_debounce_time = level.time + 500_ms;
  2268. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
  2269. }
  2270. DIE(door_secret_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  2271. {
  2272. self->takedamage = false;
  2273. door_secret_use(self, attacker, attacker);
  2274. }
  2275. void SP_func_door_secret(edict_t *ent)
  2276. {
  2277. vec3_t forward, right, up;
  2278. float side;
  2279. float width;
  2280. float length;
  2281. G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav");
  2282. ent->attenuation = ATTN_STATIC;
  2283. ent->movetype = MOVETYPE_PUSH;
  2284. ent->solid = SOLID_BSP;
  2285. ent->svflags |= SVF_DOOR;
  2286. gi.setmodel(ent, ent->model);
  2287. ent->moveinfo.blocked = door_secret_blocked;
  2288. ent->use = door_secret_use;
  2289. if (!(ent->targetname) || ent->spawnflags.has(SPAWNFLAG_SECRET_ALWAYS_SHOOT))
  2290. {
  2291. ent->health = 0;
  2292. ent->takedamage = true;
  2293. ent->die = door_secret_die;
  2294. }
  2295. if (!ent->dmg)
  2296. ent->dmg = 2;
  2297. if (!ent->wait)
  2298. ent->wait = 5;
  2299. ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = 50;
  2300. // calculate positions
  2301. AngleVectors(ent->s.angles, forward, right, up);
  2302. ent->s.angles = {};
  2303. side = 1.0f - (ent->spawnflags.has(SPAWNFLAG_SECRET_1ST_LEFT) ? 2 : 0);
  2304. if (ent->spawnflags.has(SPAWNFLAG_SECRET_1ST_DOWN))
  2305. width = fabsf(up.dot(ent->size));
  2306. else
  2307. width = fabsf(right.dot(ent->size));
  2308. length = fabsf(forward.dot(ent->size));
  2309. if (ent->spawnflags.has(SPAWNFLAG_SECRET_1ST_DOWN))
  2310. ent->pos1 = ent->s.origin + (up * (-1 * width));
  2311. else
  2312. ent->pos1 = ent->s.origin + (right * (side * width));
  2313. ent->pos2 = ent->pos1 + (forward * length);
  2314. if (ent->health)
  2315. {
  2316. ent->takedamage = true;
  2317. ent->die = door_killed;
  2318. ent->max_health = ent->health;
  2319. }
  2320. else if (ent->targetname && ent->message)
  2321. {
  2322. gi.soundindex("misc/talk.wav");
  2323. ent->touch = door_touch;
  2324. }
  2325. gi.linkentity(ent);
  2326. }
  2327. /*QUAKED func_killbox (1 0 0) ?
  2328. Kills everything inside when fired, irrespective of protection.
  2329. */
  2330. constexpr spawnflags_t SPAWNFLAG_KILLBOX_DEADLY_COOP = 2_spawnflag;
  2331. constexpr spawnflags_t SPAWNFLAG_KILLBOX_EXACT_COLLISION = 4_spawnflag;
  2332. USE(use_killbox) (edict_t *self, edict_t *other, edict_t *activator) -> void
  2333. {
  2334. if (self->spawnflags.has(SPAWNFLAG_KILLBOX_DEADLY_COOP))
  2335. level.deadly_kill_box = true;
  2336. self->solid = SOLID_TRIGGER;
  2337. gi.linkentity(self);
  2338. KillBox(self, false, MOD_TELEFRAG, self->spawnflags.has(SPAWNFLAG_KILLBOX_EXACT_COLLISION));
  2339. self->solid = SOLID_NOT;
  2340. gi.linkentity(self);
  2341. level.deadly_kill_box = false;
  2342. }
  2343. void SP_func_killbox(edict_t *ent)
  2344. {
  2345. gi.setmodel(ent, ent->model);
  2346. ent->use = use_killbox;
  2347. ent->svflags = SVF_NOCLIENT;
  2348. }
  2349. /*QUAKED func_eye (0 1 0) ?
  2350. Camera-like eye that can track entities.
  2351. "pathtarget" point to an info_notnull (which gets freed after spawn) to automatically set
  2352. the eye_position
  2353. "target"/"killtarget"/"delay"/"message" target keys to fire when we first spot a player
  2354. "eye_position" manually set the eye position; note that this is in "forward right up" format, relative to
  2355. the origin of the brush and using the entity's angles
  2356. "radius" default 512, detection radius for entities
  2357. "speed" default 45, how fast, in degrees per second, we should move on each axis to reach the target
  2358. "vision_cone" default 0.5 for half cone; how wide the cone of vision should be (relative to their initial angles)
  2359. "wait" default 0, the amount of time to wait before returning to neutral angles
  2360. */
  2361. constexpr spawnflags_t SPAWNFLAG_FUNC_EYE_FIRED_TARGETS = 17_spawnflag_bit; // internal use only
  2362. THINK(func_eye_think) (edict_t *self) -> void
  2363. {
  2364. // find enemy to track
  2365. float closest_dist = 0;
  2366. edict_t *closest_player = nullptr;
  2367. for (auto player : active_players())
  2368. {
  2369. vec3_t dir = player->s.origin - self->s.origin;
  2370. float dist = dir.normalize();
  2371. if (dir.dot(self->movedir) < self->yaw_speed)
  2372. continue;
  2373. if (dist >= self->dmg_radius)
  2374. continue;
  2375. if (!closest_player || dist < closest_dist)
  2376. {
  2377. closest_player = player;
  2378. closest_dist = dist;
  2379. }
  2380. }
  2381. self->enemy = closest_player;
  2382. // tracking player
  2383. vec3_t wanted_angles;
  2384. vec3_t fwd, rgt, up;
  2385. AngleVectors(self->s.angles, fwd, rgt, up);
  2386. vec3_t eye_pos = self->s.origin;
  2387. eye_pos += fwd * self->move_origin[0];
  2388. eye_pos += rgt * self->move_origin[1];
  2389. eye_pos += up * self->move_origin[2];
  2390. if (self->enemy)
  2391. {
  2392. if (!(self->spawnflags & SPAWNFLAG_FUNC_EYE_FIRED_TARGETS))
  2393. {
  2394. G_UseTargets(self, self->enemy);
  2395. self->spawnflags |= SPAWNFLAG_FUNC_EYE_FIRED_TARGETS;
  2396. }
  2397. vec3_t dir = (self->enemy->s.origin - eye_pos).normalized();
  2398. wanted_angles = vectoangles(dir);
  2399. self->s.frame = 2;
  2400. self->timestamp = level.time + gtime_t::from_sec(self->wait);
  2401. }
  2402. else
  2403. {
  2404. if (self->timestamp <= level.time)
  2405. {
  2406. // return to neutral
  2407. wanted_angles = self->move_angles;
  2408. self->s.frame = 0;
  2409. }
  2410. else
  2411. wanted_angles = self->s.angles;
  2412. }
  2413. for (int i = 0; i < 2; i++)
  2414. {
  2415. float current = anglemod(self->s.angles[i]);
  2416. float ideal = wanted_angles[i];
  2417. if (current == ideal)
  2418. continue;
  2419. float move = ideal - current;
  2420. if (ideal > current)
  2421. {
  2422. if (move >= 180)
  2423. move = move - 360;
  2424. }
  2425. else
  2426. {
  2427. if (move <= -180)
  2428. move = move + 360;
  2429. }
  2430. if (move > 0)
  2431. {
  2432. if (move > self->speed)
  2433. move = self->speed;
  2434. }
  2435. else
  2436. {
  2437. if (move < -self->speed)
  2438. move = -self->speed;
  2439. }
  2440. self->s.angles[i] = anglemod(current + move);
  2441. }
  2442. self->nextthink = level.time + FRAME_TIME_S;
  2443. }
  2444. THINK(func_eye_setup) (edict_t *self) -> void
  2445. {
  2446. edict_t *eye_pos = G_PickTarget(self->pathtarget);
  2447. if (!eye_pos)
  2448. gi.Com_PrintFmt("{}: bad target\n", *self);
  2449. else
  2450. self->move_origin = eye_pos->s.origin - self->s.origin;
  2451. self->movedir = self->move_origin.normalized();
  2452. self->think = func_eye_think;
  2453. self->nextthink = level.time + 10_hz;
  2454. }
  2455. void SP_func_eye(edict_t *ent)
  2456. {
  2457. ent->movetype = MOVETYPE_PUSH;
  2458. ent->solid = SOLID_BSP;
  2459. gi.setmodel(ent, ent->model);
  2460. if (!st.radius)
  2461. ent->dmg_radius = 512;
  2462. else
  2463. ent->dmg_radius = st.radius;
  2464. if (!ent->speed)
  2465. ent->speed = 45;
  2466. if (!ent->yaw_speed)
  2467. ent->yaw_speed = 0.5f;
  2468. ent->speed *= gi.frame_time_s;
  2469. ent->move_angles = ent->s.angles;
  2470. ent->wait = 1.0f;
  2471. if (ent->pathtarget)
  2472. {
  2473. ent->think = func_eye_setup;
  2474. ent->nextthink = level.time + 10_hz;
  2475. }
  2476. else
  2477. {
  2478. ent->think = func_eye_think;
  2479. ent->nextthink = level.time + 10_hz;
  2480. vec3_t right, up;
  2481. AngleVectors(ent->move_angles, ent->movedir, right, up);
  2482. vec3_t move_origin = ent->move_origin;
  2483. ent->move_origin = ent->movedir * move_origin[0];
  2484. ent->move_origin += right * move_origin[1];
  2485. ent->move_origin += up * move_origin[2];
  2486. }
  2487. gi.linkentity(ent);
  2488. }