g_target.cpp 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "g_local.h"
  4. /*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
  5. Fire an origin based temp entity event to the clients.
  6. "style" type byte
  7. */
  8. USE(Use_Target_Tent) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  9. {
  10. gi.WriteByte(svc_temp_entity);
  11. gi.WriteByte(ent->style);
  12. gi.WritePosition(ent->s.origin);
  13. gi.multicast(ent->s.origin, MULTICAST_PVS, false);
  14. }
  15. void SP_target_temp_entity(edict_t *ent)
  16. {
  17. if (level.is_n64 && ent->style == 27)
  18. ent->style = TE_TELEPORT_EFFECT;
  19. ent->use = Use_Target_Tent;
  20. }
  21. //==========================================================
  22. //==========================================================
  23. /*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
  24. "noise" wav file to play
  25. "attenuation"
  26. -1 = none, send to whole level
  27. 1 = normal fighting sounds
  28. 2 = idle sound level
  29. 3 = ambient sound level
  30. "volume" 0.0 to 1.0
  31. Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers.
  32. [Paril-KEX] looped sounds are by default atten 3 / vol 1, and the use function toggles it on/off.
  33. */
  34. constexpr spawnflags_t SPAWNFLAG_SPEAKER_LOOPED_ON = 1_spawnflag;
  35. constexpr spawnflags_t SPAWNFLAG_SPEAKER_LOOPED_OFF = 2_spawnflag;
  36. constexpr spawnflags_t SPAWNFLAG_SPEAKER_RELIABLE = 4_spawnflag;
  37. constexpr spawnflags_t SPAWNFLAG_SPEAKER_NO_STEREO = 8_spawnflag;
  38. USE(Use_Target_Speaker) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  39. {
  40. soundchan_t chan;
  41. if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_ON | SPAWNFLAG_SPEAKER_LOOPED_OFF))
  42. { // looping sound toggles
  43. if (ent->s.sound)
  44. ent->s.sound = 0; // turn it off
  45. else
  46. ent->s.sound = ent->noise_index; // start it
  47. }
  48. else
  49. { // normal sound
  50. if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_RELIABLE))
  51. chan = CHAN_VOICE | CHAN_RELIABLE;
  52. else
  53. chan = CHAN_VOICE;
  54. // use a positioned_sound, because this entity won't normally be
  55. // sent to any clients because it is invisible
  56. gi.positioned_sound(ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
  57. }
  58. }
  59. void SP_target_speaker(edict_t *ent)
  60. {
  61. if (!st.noise)
  62. {
  63. gi.Com_PrintFmt("{}: no noise set\n", *ent);
  64. return;
  65. }
  66. if (!strstr(st.noise, ".wav"))
  67. ent->noise_index = gi.soundindex(G_Fmt("{}.wav", st.noise).data());
  68. else
  69. ent->noise_index = gi.soundindex(st.noise);
  70. if (!ent->volume)
  71. ent->volume = ent->s.loop_volume = 1.0;
  72. if (!ent->attenuation)
  73. {
  74. if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_OFF | SPAWNFLAG_SPEAKER_LOOPED_ON))
  75. ent->attenuation = ATTN_STATIC;
  76. else
  77. ent->attenuation = ATTN_NORM;
  78. }
  79. else if (ent->attenuation == -1) // use -1 so 0 defaults to 1
  80. {
  81. if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_OFF | SPAWNFLAG_SPEAKER_LOOPED_ON))
  82. {
  83. ent->attenuation = ATTN_LOOP_NONE;
  84. ent->svflags |= SVF_NOCULL;
  85. }
  86. else
  87. ent->attenuation = ATTN_NONE;
  88. }
  89. ent->s.loop_attenuation = ent->attenuation;
  90. // check for prestarted looping sound
  91. if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_LOOPED_ON))
  92. ent->s.sound = ent->noise_index;
  93. if (ent->spawnflags.has(SPAWNFLAG_SPEAKER_NO_STEREO))
  94. ent->s.renderfx |= RF_NO_STEREO;
  95. ent->use = Use_Target_Speaker;
  96. // must link the entity so we get areas and clusters so
  97. // the server can determine who to send updates to
  98. gi.linkentity(ent);
  99. }
  100. //==========================================================
  101. constexpr spawnflags_t SPAWNFLAG_HELP_HELP1 = 1_spawnflag;
  102. constexpr spawnflags_t SPAWNFLAG_SET_POI = 2_spawnflag;
  103. extern void target_poi_use(edict_t* ent, edict_t* other, edict_t* activator);
  104. USE(Use_Target_Help) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  105. {
  106. if (ent->spawnflags.has(SPAWNFLAG_HELP_HELP1))
  107. {
  108. if (strcmp(game.helpmessage1, ent->message))
  109. {
  110. Q_strlcpy(game.helpmessage1, ent->message, sizeof(game.helpmessage1));
  111. game.help1changed++;
  112. }
  113. }
  114. else
  115. {
  116. if (strcmp(game.helpmessage2, ent->message))
  117. {
  118. Q_strlcpy(game.helpmessage2, ent->message, sizeof(game.helpmessage2));
  119. game.help2changed++;
  120. }
  121. }
  122. if (ent->spawnflags.has(SPAWNFLAG_SET_POI))
  123. {
  124. target_poi_use(ent, other, activator);
  125. }
  126. }
  127. /*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 setpoi
  128. When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
  129. */
  130. void SP_target_help(edict_t *ent)
  131. {
  132. if (deathmatch->integer)
  133. { // auto-remove for deathmatch
  134. G_FreeEdict(ent);
  135. return;
  136. }
  137. if (!ent->message)
  138. {
  139. gi.Com_PrintFmt("{}: no message\n", *ent);
  140. G_FreeEdict(ent);
  141. return;
  142. }
  143. ent->use = Use_Target_Help;
  144. if (ent->spawnflags.has(SPAWNFLAG_SET_POI))
  145. {
  146. if (st.image)
  147. ent->noise_index = gi.imageindex(st.image);
  148. else
  149. ent->noise_index = gi.imageindex("friend");
  150. }
  151. }
  152. //==========================================================
  153. /*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
  154. Counts a secret found.
  155. These are single use targets.
  156. */
  157. USE(use_target_secret) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  158. {
  159. gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
  160. level.found_secrets++;
  161. G_UseTargets(ent, activator);
  162. G_FreeEdict(ent);
  163. }
  164. THINK(G_VerifyTargetted) (edict_t *ent) -> void
  165. {
  166. if (!ent->targetname || !*ent->targetname)
  167. gi.Com_PrintFmt("WARNING: missing targetname on {}\n", *ent);
  168. else if (!G_FindByString<&edict_t::target>(nullptr, ent->targetname))
  169. gi.Com_PrintFmt("WARNING: doesn't appear to be anything targeting {}\n", *ent);
  170. }
  171. void SP_target_secret(edict_t *ent)
  172. {
  173. if (deathmatch->integer)
  174. { // auto-remove for deathmatch
  175. G_FreeEdict(ent);
  176. return;
  177. }
  178. ent->think = G_VerifyTargetted;
  179. ent->nextthink = level.time + 10_ms;
  180. ent->use = use_target_secret;
  181. if (!st.noise)
  182. st.noise = "misc/secret.wav";
  183. ent->noise_index = gi.soundindex(st.noise);
  184. ent->svflags = SVF_NOCLIENT;
  185. level.total_secrets++;
  186. }
  187. //==========================================================
  188. // [Paril-KEX] notify this player of a goal change
  189. void G_PlayerNotifyGoal(edict_t *player)
  190. {
  191. // no goals in DM
  192. if (deathmatch->integer)
  193. return;
  194. if (!player->client->pers.spawned)
  195. return;
  196. else if ((level.time - player->client->resp.entertime) < 300_ms)
  197. return;
  198. // N64 goals
  199. if (level.goals)
  200. {
  201. // if the goal has updated, commit it first
  202. if (game.help1changed != game.help2changed)
  203. {
  204. const char *current_goal = level.goals;
  205. // skip ahead by the number of goals we've finished
  206. for (int32_t i = 0; i < level.goal_num; i++)
  207. {
  208. while (*current_goal && *current_goal != '\t')
  209. current_goal++;
  210. if (!*current_goal)
  211. gi.Com_Error("invalid n64 goals; tell Paril\n");
  212. current_goal++;
  213. }
  214. // find the end of this goal
  215. const char *goal_end = current_goal;
  216. while (*goal_end && *goal_end != '\t')
  217. goal_end++;
  218. Q_strlcpy(game.helpmessage1, current_goal, min((size_t) (goal_end - current_goal + 1), sizeof(game.helpmessage1)));
  219. game.help2changed = game.help1changed;
  220. }
  221. if (player->client->pers.game_help1changed != game.help1changed)
  222. {
  223. gi.LocClient_Print(player, PRINT_TYPEWRITER, game.helpmessage1);
  224. gi.local_sound(player, player, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk.wav"), 1.0f, ATTN_NONE, 0.0f, GetUnicastKey());
  225. player->client->pers.game_help1changed = game.help1changed;
  226. }
  227. // no regular goals
  228. return;
  229. }
  230. if (player->client->pers.game_help1changed != game.help1changed)
  231. {
  232. player->client->pers.game_help1changed = game.help1changed;
  233. player->client->pers.helpchanged = 1;
  234. player->client->pers.help_time = level.time + 5_sec;
  235. if (*game.helpmessage1)
  236. // [Sam-KEX] Print objective to screen
  237. gi.LocClient_Print(player, PRINT_TYPEWRITER, "$g_primary_mission_objective", game.helpmessage1);
  238. }
  239. if (player->client->pers.game_help2changed != game.help2changed)
  240. {
  241. player->client->pers.game_help2changed = game.help2changed;
  242. player->client->pers.helpchanged = 1;
  243. player->client->pers.help_time = level.time + 5_sec;
  244. if (*game.helpmessage2)
  245. // [Sam-KEX] Print objective to screen
  246. gi.LocClient_Print(player, PRINT_TYPEWRITER, "$g_secondary_mission_objective", game.helpmessage2);
  247. }
  248. }
  249. /*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) KEEP_MUSIC
  250. Counts a goal completed.
  251. These are single use targets.
  252. */
  253. constexpr spawnflags_t SPAWNFLAG_GOAL_KEEP_MUSIC = 1_spawnflag;
  254. USE(use_target_goal) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  255. {
  256. gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
  257. level.found_goals++;
  258. if (level.found_goals == level.total_goals && !ent->spawnflags.has(SPAWNFLAG_GOAL_KEEP_MUSIC))
  259. {
  260. if (ent->sounds)
  261. gi.configstring (CS_CDTRACK, G_Fmt("{}", ent->sounds).data() );
  262. else
  263. gi.configstring(CS_CDTRACK, "0");
  264. }
  265. // [Paril-KEX] n64 goals
  266. if (level.goals)
  267. {
  268. level.goal_num++;
  269. game.help1changed++;
  270. for (auto player : active_players())
  271. G_PlayerNotifyGoal(player);
  272. }
  273. G_UseTargets(ent, activator);
  274. G_FreeEdict(ent);
  275. }
  276. void SP_target_goal(edict_t *ent)
  277. {
  278. if (deathmatch->integer)
  279. { // auto-remove for deathmatch
  280. G_FreeEdict(ent);
  281. return;
  282. }
  283. ent->use = use_target_goal;
  284. if (!st.noise)
  285. st.noise = "misc/secret.wav";
  286. ent->noise_index = gi.soundindex(st.noise);
  287. ent->svflags = SVF_NOCLIENT;
  288. level.total_goals++;
  289. }
  290. //==========================================================
  291. /*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
  292. Spawns an explosion temporary entity when used.
  293. "delay" wait this long before going off
  294. "dmg" how much radius damage should be done, defaults to 0
  295. */
  296. THINK(target_explosion_explode) (edict_t *self) -> void
  297. {
  298. float save;
  299. gi.WriteByte(svc_temp_entity);
  300. gi.WriteByte(TE_EXPLOSION1);
  301. gi.WritePosition(self->s.origin);
  302. gi.multicast(self->s.origin, MULTICAST_PHS, false);
  303. T_RadiusDamage(self, self->activator, (float) self->dmg, nullptr, (float) self->dmg + 40, DAMAGE_NONE, MOD_EXPLOSIVE);
  304. save = self->delay;
  305. self->delay = 0;
  306. G_UseTargets(self, self->activator);
  307. self->delay = save;
  308. }
  309. USE(use_target_explosion) (edict_t *self, edict_t *other, edict_t *activator) -> void
  310. {
  311. self->activator = activator;
  312. if (!self->delay)
  313. {
  314. target_explosion_explode(self);
  315. return;
  316. }
  317. self->think = target_explosion_explode;
  318. self->nextthink = level.time + gtime_t::from_sec(self->delay);
  319. }
  320. void SP_target_explosion(edict_t *ent)
  321. {
  322. ent->use = use_target_explosion;
  323. ent->svflags = SVF_NOCLIENT;
  324. }
  325. //==========================================================
  326. /*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) END_OF_UNIT UNKNOWN UNKNOWN CLEAR_INVENTORY NO_END_OF_UNIT FADE_OUT IMMEDIATE_LEAVE
  327. Changes level to "map" when fired
  328. */
  329. USE(use_target_changelevel) (edict_t *self, edict_t *other, edict_t *activator) -> void
  330. {
  331. if (level.intermissiontime)
  332. return; // already activated
  333. if (!deathmatch->integer && !coop->integer)
  334. {
  335. if (g_edicts[1].health <= 0)
  336. return;
  337. }
  338. // if noexit, do a ton of damage to other
  339. if (deathmatch->integer && !g_dm_allow_exit->integer && other != world)
  340. {
  341. T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, DAMAGE_NONE, MOD_EXIT);
  342. return;
  343. }
  344. // if multiplayer, let everyone know who hit the exit
  345. if (deathmatch->integer)
  346. {
  347. if (level.time < 10_sec)
  348. return;
  349. if (activator && activator->client)
  350. gi.LocBroadcast_Print(PRINT_HIGH, "$g_exited_level", activator->client->pers.netname);
  351. }
  352. // if going to a new unit, clear cross triggers
  353. if (strstr(self->map, "*"))
  354. game.cross_level_flags &= ~(SFL_CROSS_TRIGGER_MASK);
  355. // if map has a landmark, store position instead of using spawn next map
  356. if (activator && activator->client && !deathmatch->integer)
  357. {
  358. activator->client->landmark_name = nullptr;
  359. activator->client->landmark_rel_pos = vec3_origin;
  360. self->target_ent = G_PickTarget(self->target);
  361. if (self->target_ent && activator && activator->client)
  362. {
  363. activator->client->landmark_name = G_CopyString(self->target_ent->targetname, TAG_GAME);
  364. // get relative vector to landmark pos, and unrotate by the landmark angles in preparation to be
  365. // rotated by the next map
  366. activator->client->landmark_rel_pos = activator->s.origin - self->target_ent->s.origin;
  367. activator->client->landmark_rel_pos = RotatePointAroundVector({ 1, 0, 0 }, activator->client->landmark_rel_pos, -self->target_ent->s.angles[0]);
  368. activator->client->landmark_rel_pos = RotatePointAroundVector({ 0, 1, 0 }, activator->client->landmark_rel_pos, -self->target_ent->s.angles[2]);
  369. activator->client->landmark_rel_pos = RotatePointAroundVector({ 0, 0, 1 }, activator->client->landmark_rel_pos, -self->target_ent->s.angles[1]);
  370. activator->client->oldvelocity = RotatePointAroundVector({ 1, 0, 0 }, activator->client->oldvelocity, -self->target_ent->s.angles[0]);
  371. activator->client->oldvelocity = RotatePointAroundVector({ 0, 1, 0 }, activator->client->oldvelocity, -self->target_ent->s.angles[2]);
  372. activator->client->oldvelocity = RotatePointAroundVector({ 0, 0, 1 }, activator->client->oldvelocity, -self->target_ent->s.angles[1]);
  373. // unrotate our view angles for the next map too
  374. activator->client->oldviewangles = activator->client->ps.viewangles - self->target_ent->s.angles;
  375. }
  376. }
  377. BeginIntermission(self);
  378. }
  379. void SP_target_changelevel(edict_t *ent)
  380. {
  381. if (!ent->map)
  382. {
  383. gi.Com_PrintFmt("{}: no map\n", *ent);
  384. G_FreeEdict(ent);
  385. return;
  386. }
  387. ent->use = use_target_changelevel;
  388. ent->svflags = SVF_NOCLIENT;
  389. }
  390. //==========================================================
  391. /*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
  392. Creates a particle splash effect when used.
  393. Set "sounds" to one of the following:
  394. 1) sparks
  395. 2) blue water
  396. 3) brown water
  397. 4) slime
  398. 5) lava
  399. 6) blood
  400. "count" how many pixels in the splash
  401. "dmg" if set, does a radius damage at this location when it splashes
  402. useful for lava/sparks
  403. */
  404. USE(use_target_splash) (edict_t *self, edict_t *other, edict_t *activator) -> void
  405. {
  406. gi.WriteByte(svc_temp_entity);
  407. gi.WriteByte(TE_SPLASH);
  408. gi.WriteByte(self->count);
  409. gi.WritePosition(self->s.origin);
  410. gi.WriteDir(self->movedir);
  411. gi.WriteByte(self->sounds);
  412. gi.multicast(self->s.origin, MULTICAST_PVS, false);
  413. if (self->dmg)
  414. T_RadiusDamage(self, activator, (float) self->dmg, nullptr, (float) self->dmg + 40, DAMAGE_NONE, MOD_SPLASH);
  415. }
  416. void SP_target_splash(edict_t *self)
  417. {
  418. self->use = use_target_splash;
  419. G_SetMovedir(self->s.angles, self->movedir);
  420. if (!self->count)
  421. self->count = 32;
  422. // N64 "sparks" are blue, not yellow.
  423. if (level.is_n64 && self->sounds == 1)
  424. self->sounds = 7;
  425. self->svflags = SVF_NOCLIENT;
  426. }
  427. //==========================================================
  428. /*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
  429. Set target to the type of entity you want spawned.
  430. Useful for spawning monsters and gibs in the factory levels.
  431. For monsters:
  432. Set direction to the facing you want it to have.
  433. For gibs:
  434. Set direction if you want it moving and
  435. speed how fast it should be moving otherwise it
  436. will just be dropped
  437. */
  438. void ED_CallSpawn(edict_t *ent);
  439. USE(use_target_spawner) (edict_t *self, edict_t *other, edict_t *activator) -> void
  440. {
  441. edict_t *ent;
  442. ent = G_Spawn();
  443. ent->classname = self->target;
  444. // RAFAEL
  445. ent->flags = self->flags;
  446. // RAFAEL
  447. ent->s.origin = self->s.origin;
  448. ent->s.angles = self->s.angles;
  449. st = {};
  450. // [Paril-KEX] although I fixed these in our maps, this is just
  451. // in case anybody else does this by accident. Don't count these monsters
  452. // so they don't inflate the monster count.
  453. ent->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
  454. ED_CallSpawn(ent);
  455. gi.linkentity(ent);
  456. KillBox(ent, false);
  457. if (self->speed)
  458. ent->velocity = self->movedir;
  459. ent->s.renderfx |= RF_IR_VISIBLE; // PGM
  460. }
  461. void SP_target_spawner(edict_t *self)
  462. {
  463. self->use = use_target_spawner;
  464. self->svflags = SVF_NOCLIENT;
  465. if (self->speed)
  466. {
  467. G_SetMovedir(self->s.angles, self->movedir);
  468. self->movedir *= self->speed;
  469. }
  470. }
  471. //==========================================================
  472. /*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
  473. Fires a blaster bolt in the set direction when triggered.
  474. dmg default is 15
  475. speed default is 1000
  476. */
  477. constexpr spawnflags_t SPAWNFLAG_BLASTER_NOTRAIL = 1_spawnflag;
  478. constexpr spawnflags_t SPAWNFLAG_BLASTER_NOEFFECTS = 2_spawnflag;
  479. USE(use_target_blaster) (edict_t *self, edict_t *other, edict_t *activator) -> void
  480. {
  481. effects_t effect;
  482. if (self->spawnflags.has(SPAWNFLAG_BLASTER_NOEFFECTS))
  483. effect = EF_NONE;
  484. else if (self->spawnflags.has(SPAWNFLAG_BLASTER_NOTRAIL))
  485. effect = EF_HYPERBLASTER;
  486. else
  487. effect = EF_BLASTER;
  488. fire_blaster(self, self->s.origin, self->movedir, self->dmg, (int) self->speed, effect, MOD_TARGET_BLASTER);
  489. gi.sound(self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
  490. }
  491. void SP_target_blaster(edict_t *self)
  492. {
  493. self->use = use_target_blaster;
  494. G_SetMovedir(self->s.angles, self->movedir);
  495. self->noise_index = gi.soundindex("weapons/laser2.wav");
  496. if (!self->dmg)
  497. self->dmg = 15;
  498. if (!self->speed)
  499. self->speed = 1000;
  500. self->svflags = SVF_NOCLIENT;
  501. }
  502. //==========================================================
  503. /*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
  504. Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work.
  505. */
  506. USE(trigger_crosslevel_trigger_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  507. {
  508. game.cross_level_flags |= self->spawnflags.value;
  509. G_FreeEdict(self);
  510. }
  511. void SP_target_crosslevel_trigger(edict_t *self)
  512. {
  513. self->svflags = SVF_NOCLIENT;
  514. self->use = trigger_crosslevel_trigger_use;
  515. }
  516. /*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 - - - - - - - - trigger9 trigger10 trigger11 trigger12 trigger13 trigger14 trigger15 trigger16
  517. Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and
  518. killtarget also work.
  519. "delay" delay before using targets if the trigger has been activated (default 1)
  520. */
  521. THINK(target_crosslevel_target_think) (edict_t *self) -> void
  522. {
  523. if (self->spawnflags.value == (game.cross_level_flags & SFL_CROSS_TRIGGER_MASK & self->spawnflags.value))
  524. {
  525. G_UseTargets(self, self);
  526. G_FreeEdict(self);
  527. }
  528. }
  529. void SP_target_crosslevel_target(edict_t *self)
  530. {
  531. if (!self->delay)
  532. self->delay = 1;
  533. self->svflags = SVF_NOCLIENT;
  534. self->think = target_crosslevel_target_think;
  535. self->nextthink = level.time + gtime_t::from_sec(self->delay);
  536. }
  537. //==========================================================
  538. /*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP
  539. When triggered, fires a laser. You can either set a target or a direction.
  540. WINDOWSTOP - stops at CONTENTS_WINDOW
  541. */
  542. //======
  543. // PGM
  544. constexpr spawnflags_t SPAWNFLAG_LASER_STOPWINDOW = 0x0080_spawnflag;
  545. // PGM
  546. //======
  547. struct laser_pierce_t : pierce_args_t
  548. {
  549. edict_t *self;
  550. int32_t count;
  551. bool damaged_thing = false;
  552. inline laser_pierce_t(edict_t *self, int32_t count) :
  553. pierce_args_t(),
  554. self(self),
  555. count(count)
  556. {
  557. }
  558. // we hit an entity; return false to stop the piercing.
  559. // you can adjust the mask for the re-trace (for water, etc).
  560. virtual bool hit(contents_t &mask, vec3_t &end) override
  561. {
  562. // hurt it if we can
  563. if (self->dmg > 0 && (tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && self->damage_debounce_time <= level.time)
  564. {
  565. damaged_thing = true;
  566. T_Damage(tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
  567. }
  568. // if we hit something that's not a monster or player or is immune to lasers, we're done
  569. // ROGUE
  570. if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) && !(tr.ent->flags & FL_DAMAGEABLE))
  571. // ROGUE
  572. {
  573. if (self->spawnflags.has(SPAWNFLAG_LASER_ZAP))
  574. {
  575. self->spawnflags &= ~SPAWNFLAG_LASER_ZAP;
  576. gi.WriteByte(svc_temp_entity);
  577. gi.WriteByte(TE_LASER_SPARKS);
  578. gi.WriteByte(count);
  579. gi.WritePosition(tr.endpos);
  580. gi.WriteDir(tr.plane.normal);
  581. gi.WriteByte(self->s.skinnum);
  582. gi.multicast(tr.endpos, MULTICAST_PVS, false);
  583. }
  584. return false;
  585. }
  586. if (!mark(tr.ent))
  587. return false;
  588. return true;
  589. }
  590. };
  591. THINK(target_laser_think) (edict_t *self) -> void
  592. {
  593. int32_t count;
  594. if (self->spawnflags.has(SPAWNFLAG_LASER_ZAP))
  595. count = 8;
  596. else
  597. count = 4;
  598. if (self->enemy)
  599. {
  600. vec3_t last_movedir = self->movedir;
  601. vec3_t point = (self->enemy->absmin + self->enemy->absmax) * 0.5f;
  602. self->movedir = point - self->s.origin;
  603. self->movedir.normalize();
  604. if (self->movedir != last_movedir)
  605. self->spawnflags |= SPAWNFLAG_LASER_ZAP;
  606. }
  607. vec3_t start = self->s.origin;
  608. vec3_t end = start + (self->movedir * 2048);
  609. laser_pierce_t args {
  610. self,
  611. count
  612. };
  613. contents_t mask = self->spawnflags.has(SPAWNFLAG_LASER_STOPWINDOW) ? MASK_SHOT : (CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_DEADMONSTER);
  614. pierce_trace(start, end, self, args, mask);
  615. self->s.old_origin = args.tr.endpos;
  616. if (args.damaged_thing)
  617. self->damage_debounce_time = level.time + 10_hz;
  618. self->nextthink = level.time + FRAME_TIME_S;
  619. gi.linkentity(self);
  620. }
  621. void target_laser_on(edict_t *self)
  622. {
  623. if (!self->activator)
  624. self->activator = self;
  625. self->spawnflags |= SPAWNFLAG_LASER_ZAP | SPAWNFLAG_LASER_ON;
  626. self->svflags &= ~SVF_NOCLIENT;
  627. self->flags |= FL_TRAP;
  628. target_laser_think(self);
  629. }
  630. void target_laser_off(edict_t *self)
  631. {
  632. self->spawnflags &= ~SPAWNFLAG_LASER_ON;
  633. self->svflags |= SVF_NOCLIENT;
  634. self->flags &= ~FL_TRAP;
  635. self->nextthink = 0_ms;
  636. }
  637. USE(target_laser_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  638. {
  639. self->activator = activator;
  640. if (self->spawnflags.has(SPAWNFLAG_LASER_ON))
  641. target_laser_off(self);
  642. else
  643. target_laser_on(self);
  644. }
  645. THINK(target_laser_start) (edict_t *self) -> void
  646. {
  647. edict_t *ent;
  648. self->movetype = MOVETYPE_NONE;
  649. self->solid = SOLID_NOT;
  650. self->s.renderfx |= RF_BEAM;
  651. self->s.modelindex = MODELINDEX_WORLD; // must be non-zero
  652. // [Sam-KEX] On Q2N64, spawnflag of 128 turns it into a lightning bolt
  653. if (level.is_n64)
  654. {
  655. // Paril: fix for N64
  656. if (self->spawnflags.has(SPAWNFLAG_LASER_STOPWINDOW))
  657. {
  658. self->spawnflags &= ~SPAWNFLAG_LASER_STOPWINDOW;
  659. self->spawnflags |= SPAWNFLAG_LASER_LIGHTNING;
  660. }
  661. }
  662. if (self->spawnflags.has(SPAWNFLAG_LASER_LIGHTNING))
  663. {
  664. self->s.renderfx |= RF_BEAM_LIGHTNING; // tell renderer it is lightning
  665. if (!self->s.skinnum)
  666. self->s.skinnum = 0xf3f3f1f1; // default lightning color
  667. }
  668. // set the beam diameter
  669. // [Paril-KEX] lab has this set prob before lightning was implemented
  670. if (!level.is_n64 && self->spawnflags.has(SPAWNFLAG_LASER_FAT))
  671. self->s.frame = 16;
  672. else
  673. self->s.frame = 4;
  674. // set the color
  675. if (!self->s.skinnum)
  676. {
  677. if (self->spawnflags.has(SPAWNFLAG_LASER_RED))
  678. self->s.skinnum = 0xf2f2f0f0;
  679. else if (self->spawnflags.has(SPAWNFLAG_LASER_GREEN))
  680. self->s.skinnum = 0xd0d1d2d3;
  681. else if (self->spawnflags.has(SPAWNFLAG_LASER_BLUE))
  682. self->s.skinnum = 0xf3f3f1f1;
  683. else if (self->spawnflags.has(SPAWNFLAG_LASER_YELLOW))
  684. self->s.skinnum = 0xdcdddedf;
  685. else if (self->spawnflags.has(SPAWNFLAG_LASER_ORANGE))
  686. self->s.skinnum = 0xe0e1e2e3;
  687. }
  688. if (!self->enemy)
  689. {
  690. if (self->target)
  691. {
  692. ent = G_FindByString<&edict_t::targetname>(nullptr, self->target);
  693. if (!ent)
  694. gi.Com_PrintFmt("{}: {} is a bad target\n", *self, self->target);
  695. else
  696. {
  697. self->enemy = ent;
  698. // N64 fix
  699. // FIXME: which map was this for again? oops
  700. if (level.is_n64 && !strcmp(self->enemy->classname, "func_train") && !(self->enemy->spawnflags & SPAWNFLAG_TRAIN_START_ON))
  701. self->enemy->use(self->enemy, self, self);
  702. }
  703. }
  704. else
  705. {
  706. G_SetMovedir(self->s.angles, self->movedir);
  707. }
  708. }
  709. self->use = target_laser_use;
  710. self->think = target_laser_think;
  711. if (!self->dmg)
  712. self->dmg = 1;
  713. self->mins = { -8, -8, -8 };
  714. self->maxs = { 8, 8, 8 };
  715. gi.linkentity(self);
  716. if (self->spawnflags.has(SPAWNFLAG_LASER_ON))
  717. target_laser_on(self);
  718. else
  719. target_laser_off(self);
  720. }
  721. void SP_target_laser(edict_t *self)
  722. {
  723. // let everything else get spawned before we start firing
  724. self->think = target_laser_start;
  725. self->flags |= FL_TRAP_LASER_FIELD;
  726. self->nextthink = level.time + 1_sec;
  727. }
  728. //==========================================================
  729. /*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
  730. speed How many seconds the ramping will take
  731. message two letters; starting lightlevel and ending lightlevel
  732. */
  733. constexpr spawnflags_t SPAWNFLAG_LIGHTRAMP_TOGGLE = 1_spawnflag;
  734. THINK(target_lightramp_think) (edict_t *self) -> void
  735. {
  736. char style[2];
  737. style[0] = (char) ('a' + self->movedir[0] + ((level.time - self->timestamp) / gi.frame_time_s).seconds() * self->movedir[2]);
  738. style[1] = 0;
  739. gi.configstring(CS_LIGHTS + self->enemy->style, style);
  740. if ((level.time - self->timestamp).seconds() < self->speed)
  741. {
  742. self->nextthink = level.time + FRAME_TIME_S;
  743. }
  744. else if (self->spawnflags.has(SPAWNFLAG_LIGHTRAMP_TOGGLE))
  745. {
  746. char temp;
  747. temp = (char) self->movedir[0];
  748. self->movedir[0] = self->movedir[1];
  749. self->movedir[1] = temp;
  750. self->movedir[2] *= -1;
  751. }
  752. }
  753. USE(target_lightramp_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  754. {
  755. if (!self->enemy)
  756. {
  757. edict_t *e;
  758. // check all the targets
  759. e = nullptr;
  760. while (1)
  761. {
  762. e = G_FindByString<&edict_t::targetname>(e, self->target);
  763. if (!e)
  764. break;
  765. if (strcmp(e->classname, "light") != 0)
  766. {
  767. gi.Com_PrintFmt("{}: target {} ({}) is not a light\n", *self, self->target, *e);
  768. }
  769. else
  770. {
  771. self->enemy = e;
  772. }
  773. }
  774. if (!self->enemy)
  775. {
  776. gi.Com_PrintFmt("{}: target {} not found\n", *self, self->target);
  777. G_FreeEdict(self);
  778. return;
  779. }
  780. }
  781. self->timestamp = level.time;
  782. target_lightramp_think(self);
  783. }
  784. void SP_target_lightramp(edict_t *self)
  785. {
  786. if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
  787. {
  788. gi.Com_PrintFmt("{}: bad ramp ({})\n", *self, self->message ? self->message : "null string");
  789. G_FreeEdict(self);
  790. return;
  791. }
  792. if (deathmatch->integer)
  793. {
  794. G_FreeEdict(self);
  795. return;
  796. }
  797. if (!self->target)
  798. {
  799. gi.Com_PrintFmt("{}: no target\n", *self);
  800. G_FreeEdict(self);
  801. return;
  802. }
  803. self->svflags |= SVF_NOCLIENT;
  804. self->use = target_lightramp_use;
  805. self->think = target_lightramp_think;
  806. self->movedir[0] = (float) (self->message[0] - 'a');
  807. self->movedir[1] = (float) (self->message[1] - 'a');
  808. self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / gi.frame_time_s);
  809. }
  810. //==========================================================
  811. /*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT TOGGLE UNKNOWN_ROGUE ONE_SHOT
  812. When triggered, this initiates a level-wide earthquake.
  813. All players are affected with a screen shake.
  814. "speed" severity of the quake (default:200)
  815. "count" duration of the quake (default:5)
  816. */
  817. constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_SILENT = 1_spawnflag;
  818. constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_TOGGLE = 2_spawnflag;
  819. [[maybe_unused]] constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_UNKNOWN_ROGUE = 4_spawnflag;
  820. constexpr spawnflags_t SPAWNFLAGS_EARTHQUAKE_ONE_SHOT = 8_spawnflag;
  821. THINK(target_earthquake_think) (edict_t *self) -> void
  822. {
  823. uint32_t i;
  824. edict_t *e;
  825. if (!(self->spawnflags & SPAWNFLAGS_EARTHQUAKE_SILENT)) // PGM
  826. { // PGM
  827. if (self->last_move_time < level.time)
  828. {
  829. gi.positioned_sound(self->s.origin, self, CHAN_VOICE, self->noise_index, 1.0, ATTN_NONE, 0);
  830. self->last_move_time = level.time + 6.5_sec;
  831. }
  832. } // PGM
  833. for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
  834. {
  835. if (!e->inuse)
  836. continue;
  837. if (!e->client)
  838. break;
  839. e->client->quake_time = level.time + 1000_ms;
  840. }
  841. if (level.time < self->timestamp)
  842. self->nextthink = level.time + 10_hz;
  843. }
  844. USE(target_earthquake_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  845. {
  846. if (self->spawnflags.has(SPAWNFLAGS_EARTHQUAKE_ONE_SHOT))
  847. {
  848. uint32_t i;
  849. edict_t *e;
  850. for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++)
  851. {
  852. if (!e->inuse)
  853. continue;
  854. if (!e->client)
  855. break;
  856. e->client->v_dmg_pitch = -self->speed * 0.1f;
  857. e->client->v_dmg_time = level.time + DAMAGE_TIME();
  858. }
  859. return;
  860. }
  861. self->timestamp = level.time + gtime_t::from_sec(self->count);
  862. if (self->spawnflags.has(SPAWNFLAGS_EARTHQUAKE_TOGGLE))
  863. {
  864. if (self->style)
  865. self->nextthink = 0_ms;
  866. else
  867. self->nextthink = level.time + FRAME_TIME_S;
  868. self->style = !self->style;
  869. }
  870. else
  871. {
  872. self->nextthink = level.time + FRAME_TIME_S;
  873. self->last_move_time = 0_ms;
  874. }
  875. self->activator = activator;
  876. }
  877. void SP_target_earthquake(edict_t *self)
  878. {
  879. if (!self->targetname)
  880. gi.Com_PrintFmt("{}: untargeted\n", *self);
  881. if (level.is_n64)
  882. {
  883. self->spawnflags |= SPAWNFLAGS_EARTHQUAKE_TOGGLE;
  884. self->speed = 5;
  885. }
  886. if (!self->count)
  887. self->count = 5;
  888. if (!self->speed)
  889. self->speed = 200;
  890. self->svflags |= SVF_NOCLIENT;
  891. self->think = target_earthquake_think;
  892. self->use = target_earthquake_use;
  893. if (!(self->spawnflags & SPAWNFLAGS_EARTHQUAKE_SILENT)) // PGM
  894. self->noise_index = gi.soundindex("world/quake.wav");
  895. }
  896. /*QUAKED target_camera (1 0 0) (-8 -8 -8) (8 8 8)
  897. [Sam-KEX] Creates a camera path as seen in the N64 version.
  898. */
  899. constexpr size_t HACKFLAG_TELEPORT_OUT = 2;
  900. constexpr size_t HACKFLAG_SKIPPABLE = 64;
  901. constexpr size_t HACKFLAG_END_OF_UNIT = 128;
  902. static void camera_lookat_pathtarget(edict_t* self, vec3_t origin, vec3_t* dest)
  903. {
  904. if(self->pathtarget)
  905. {
  906. edict_t* pt = nullptr;
  907. pt = G_FindByString<&edict_t::targetname>(pt, self->pathtarget);
  908. if(pt)
  909. {
  910. float yaw, pitch;
  911. vec3_t delta = pt->s.origin - origin;
  912. float d = delta[0] * delta[0] + delta[1] * delta[1];
  913. if(d == 0.0f)
  914. {
  915. yaw = 0.0f;
  916. pitch = (delta[2] > 0.0f) ? 90.0f : -90.0f;
  917. }
  918. else
  919. {
  920. yaw = atan2(delta[1], delta[0]) * (180.0f / PIf);
  921. pitch = atan2(delta[2], sqrt(d)) * (180.0f / PIf);
  922. }
  923. (*dest)[YAW] = yaw;
  924. (*dest)[PITCH] = -pitch;
  925. (*dest)[ROLL] = 0;
  926. }
  927. }
  928. }
  929. THINK(update_target_camera) (edict_t *self) -> void
  930. {
  931. bool do_skip = false;
  932. // only allow skipping after 2 seconds
  933. if ((self->hackflags & HACKFLAG_SKIPPABLE) && level.time > 2_sec)
  934. {
  935. for (uint32_t i = 0; i < game.maxclients; i++)
  936. {
  937. edict_t *client = g_edicts + 1 + i;
  938. if (!client->inuse || !client->client->pers.connected)
  939. continue;
  940. if (client->client->buttons & BUTTON_ANY)
  941. {
  942. do_skip = true;
  943. break;
  944. }
  945. }
  946. }
  947. if (!do_skip && self->movetarget)
  948. {
  949. self->moveinfo.remaining_distance -= (self->moveinfo.move_speed * gi.frame_time_s) * 0.8f;
  950. if(self->moveinfo.remaining_distance <= 0)
  951. {
  952. if (self->movetarget->hackflags & HACKFLAG_TELEPORT_OUT)
  953. {
  954. if (self->enemy)
  955. {
  956. self->enemy->s.event = EV_PLAYER_TELEPORT;
  957. self->enemy->hackflags = HACKFLAG_TELEPORT_OUT;
  958. self->enemy->pain_debounce_time = self->enemy->timestamp = gtime_t::from_sec(self->movetarget->wait);
  959. }
  960. }
  961. self->s.origin = self->movetarget->s.origin;
  962. self->nextthink = level.time + gtime_t::from_sec(self->movetarget->wait);
  963. if (self->movetarget->target)
  964. {
  965. self->movetarget = G_PickTarget(self->movetarget->target);
  966. if (self->movetarget)
  967. {
  968. self->moveinfo.move_speed = self->movetarget->speed ? self->movetarget->speed : 55;
  969. self->moveinfo.remaining_distance = (self->movetarget->s.origin - self->s.origin).normalize();
  970. self->moveinfo.distance = self->moveinfo.remaining_distance;
  971. }
  972. }
  973. else
  974. self->movetarget = nullptr;
  975. return;
  976. }
  977. else
  978. {
  979. float frac = 1.0f - (self->moveinfo.remaining_distance / self->moveinfo.distance);
  980. if (self->enemy && (self->enemy->hackflags & HACKFLAG_TELEPORT_OUT))
  981. self->enemy->s.alpha = max(1.f / 255.f, frac);
  982. vec3_t delta = self->movetarget->s.origin - self->s.origin;
  983. delta *= frac;
  984. vec3_t newpos = self->s.origin + delta;
  985. camera_lookat_pathtarget(self, newpos, &level.intermission_angle);
  986. level.intermission_origin = newpos;
  987. // move all clients to the intermission point
  988. for (uint32_t i = 0; i < game.maxclients; i++)
  989. {
  990. edict_t* client = g_edicts + 1 + i;
  991. if (!client->inuse)
  992. {
  993. continue;
  994. }
  995. MoveClientToIntermission(client);
  996. }
  997. }
  998. }
  999. else
  1000. {
  1001. if (self->killtarget)
  1002. {
  1003. // destroy dummy player
  1004. if (self->enemy)
  1005. G_FreeEdict(self->enemy);
  1006. edict_t* t = nullptr;
  1007. level.intermissiontime = 0_ms;
  1008. level.level_intermission_set = true;
  1009. while ((t = G_FindByString<&edict_t::targetname>(t, self->killtarget)))
  1010. {
  1011. t->use(t, self, self->activator);
  1012. }
  1013. level.intermissiontime = level.time;
  1014. level.intermission_server_frame = gi.ServerFrame();
  1015. // end of unit requires a wait
  1016. if (level.changemap && !strchr(level.changemap, '*'))
  1017. level.exitintermission = true;
  1018. }
  1019. self->think = nullptr;
  1020. return;
  1021. }
  1022. self->nextthink = level.time + FRAME_TIME_S;
  1023. }
  1024. void G_SetClientFrame(edict_t *ent);
  1025. extern float xyspeed;
  1026. THINK(target_camera_dummy_think) (edict_t *self) -> void
  1027. {
  1028. // bit of a hack, but this will let the dummy
  1029. // move like a player
  1030. self->client = self->owner->client;
  1031. xyspeed = sqrtf(self->velocity[0] * self->velocity[0] + self->velocity[1] * self->velocity[1]);
  1032. G_SetClientFrame(self);
  1033. self->client = nullptr;
  1034. // alpha fade out for voops
  1035. if (self->hackflags & HACKFLAG_TELEPORT_OUT)
  1036. {
  1037. self->timestamp = max(0_ms, self->timestamp - 10_hz);
  1038. self->s.alpha = max(1.f / 255.f, (self->timestamp.seconds() / self->pain_debounce_time.seconds()));
  1039. }
  1040. self->nextthink = level.time + 10_hz;
  1041. }
  1042. USE(use_target_camera) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1043. {
  1044. if (self->sounds)
  1045. gi.configstring (CS_CDTRACK, G_Fmt("{}", self->sounds).data() );
  1046. if (!self->target)
  1047. return;
  1048. self->movetarget = G_PickTarget(self->target);
  1049. if (!self->movetarget)
  1050. return;
  1051. level.intermissiontime = level.time;
  1052. level.intermission_server_frame = gi.ServerFrame();
  1053. level.exitintermission = 0;
  1054. // spawn fake player dummy where we were
  1055. if (activator->client)
  1056. {
  1057. edict_t *dummy = self->enemy = G_Spawn();
  1058. dummy->owner = activator;
  1059. dummy->clipmask = activator->clipmask;
  1060. dummy->s.origin = activator->s.origin;
  1061. dummy->s.angles = activator->s.angles;
  1062. dummy->groundentity = activator->groundentity;
  1063. dummy->groundentity_linkcount = dummy->groundentity ? dummy->groundentity->linkcount : 0;
  1064. dummy->think = target_camera_dummy_think;
  1065. dummy->nextthink = level.time + 10_hz;
  1066. dummy->solid = SOLID_BBOX;
  1067. dummy->movetype = MOVETYPE_STEP;
  1068. dummy->mins = activator->mins;
  1069. dummy->maxs = activator->maxs;
  1070. dummy->s.modelindex = dummy->s.modelindex2 = MODELINDEX_PLAYER;
  1071. dummy->s.skinnum = activator->s.skinnum;
  1072. dummy->velocity = activator->velocity;
  1073. dummy->s.renderfx = RF_MINLIGHT;
  1074. dummy->s.frame = activator->s.frame;
  1075. gi.linkentity(dummy);
  1076. }
  1077. camera_lookat_pathtarget(self, self->s.origin, &level.intermission_angle);
  1078. level.intermission_origin = self->s.origin;
  1079. // move all clients to the intermission point
  1080. for (uint32_t i = 0; i < game.maxclients; i++)
  1081. {
  1082. edict_t* client = g_edicts + 1 + i;
  1083. if (!client->inuse)
  1084. {
  1085. continue;
  1086. }
  1087. // respawn any dead clients
  1088. if (client->health <= 0)
  1089. {
  1090. // give us our max health back since it will reset
  1091. // to pers.health; in instanced items we'd lose the items
  1092. // we touched so we always want to respawn with our max.
  1093. if (P_UseCoopInstancedItems())
  1094. client->client->pers.health = client->client->pers.max_health = client->max_health;
  1095. respawn(client);
  1096. }
  1097. MoveClientToIntermission(client);
  1098. }
  1099. self->activator = activator;
  1100. self->think = update_target_camera;
  1101. self->nextthink = level.time + gtime_t::from_sec(self->wait);
  1102. self->moveinfo.move_speed = self->speed;
  1103. self->moveinfo.remaining_distance = (self->movetarget->s.origin - self->s.origin).normalize();
  1104. self->moveinfo.distance = self->moveinfo.remaining_distance;
  1105. if (self->hackflags & HACKFLAG_END_OF_UNIT)
  1106. G_EndOfUnitMessage();
  1107. }
  1108. void SP_target_camera(edict_t* self)
  1109. {
  1110. if (deathmatch->integer)
  1111. { // auto-remove for deathmatch
  1112. G_FreeEdict(self);
  1113. return;
  1114. }
  1115. self->use = use_target_camera;
  1116. self->svflags = SVF_NOCLIENT;
  1117. }
  1118. /*QUAKED target_gravity (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
  1119. [Sam-KEX] Changes gravity, as seen in the N64 version
  1120. */
  1121. USE(use_target_gravity) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1122. {
  1123. gi.cvar_set("sv_gravity", G_Fmt("{}", self->gravity).data());
  1124. level.gravity = self->gravity;
  1125. }
  1126. void SP_target_gravity(edict_t* self)
  1127. {
  1128. self->use = use_target_gravity;
  1129. self->gravity = atof(st.gravity);
  1130. }
  1131. /*QUAKED target_soundfx (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
  1132. [Sam-KEX] Plays a sound fx, as seen in the N64 version
  1133. */
  1134. THINK(update_target_soundfx) (edict_t *self) -> void
  1135. {
  1136. gi.positioned_sound(self->s.origin, self, CHAN_VOICE, self->noise_index, self->volume, self->attenuation, 0);
  1137. }
  1138. USE(use_target_soundfx) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1139. {
  1140. self->think = update_target_soundfx;
  1141. self->nextthink = level.time + gtime_t::from_sec(self->delay);
  1142. }
  1143. void SP_target_soundfx(edict_t* self)
  1144. {
  1145. if (!self->volume)
  1146. self->volume = 1.0;
  1147. if (!self->attenuation)
  1148. self->attenuation = 1.0;
  1149. else if (self->attenuation == -1) // use -1 so 0 defaults to 1
  1150. self->attenuation = 0;
  1151. self->noise_index = atoi(st.noise);
  1152. switch(self->noise_index)
  1153. {
  1154. case 1:
  1155. self->noise_index = gi.soundindex("world/x_alarm.wav");
  1156. break;
  1157. case 2:
  1158. self->noise_index = gi.soundindex("world/flyby1.wav");
  1159. break;
  1160. case 4:
  1161. self->noise_index = gi.soundindex("world/amb12.wav");
  1162. break;
  1163. case 5:
  1164. self->noise_index = gi.soundindex("world/amb17.wav");
  1165. break;
  1166. case 7:
  1167. self->noise_index = gi.soundindex("world/bigpump2.wav");
  1168. break;
  1169. default:
  1170. gi.Com_PrintFmt("{}: unknown noise {}\n", *self, self->noise_index);
  1171. return;
  1172. }
  1173. self->use = use_target_soundfx;
  1174. }
  1175. /*QUAKED target_light (1 0 0) (-8 -8 -8) (8 8 8) START_ON NO_LERP FLICKER
  1176. [Paril-KEX] dynamic light entity that follows a lightstyle.
  1177. */
  1178. constexpr spawnflags_t SPAWNFLAG_TARGET_LIGHT_START_ON = 1_spawnflag;
  1179. constexpr spawnflags_t SPAWNFLAG_TARGET_LIGHT_NO_LERP = 2_spawnflag; // not used in N64, but I'll use it for this
  1180. constexpr spawnflags_t SPAWNFLAG_TARGET_LIGHT_FLICKER = 4_spawnflag;
  1181. THINK(target_light_flicker_think) (edict_t *self) -> void
  1182. {
  1183. if (brandom())
  1184. self->svflags ^= SVF_NOCLIENT;
  1185. self->nextthink = level.time + 10_hz;
  1186. }
  1187. // think function handles interpolation from start to finish.
  1188. THINK(target_light_think) (edict_t *self) -> void
  1189. {
  1190. if (self->spawnflags.has(SPAWNFLAG_TARGET_LIGHT_FLICKER))
  1191. target_light_flicker_think(self);
  1192. const char *style = gi.get_configstring(CS_LIGHTS + self->style);
  1193. self->delay += self->speed;
  1194. int32_t index = ((int32_t) self->delay) % strlen(style);
  1195. char style_value = style[index];
  1196. float current_lerp = (float) (style_value - 'a') / (float) ('z' - 'a');
  1197. float lerp;
  1198. if (!(self->spawnflags & SPAWNFLAG_TARGET_LIGHT_NO_LERP))
  1199. {
  1200. int32_t next_index = (index + 1) % strlen(style);
  1201. char next_style_value = style[next_index];
  1202. float next_lerp = (float) (next_style_value - 'a') / (float) ('z' - 'a');
  1203. float mod_lerp = fmod(self->delay, 1.0f);
  1204. lerp = (next_lerp * mod_lerp) + (current_lerp * (1.f - mod_lerp));
  1205. }
  1206. else
  1207. lerp = current_lerp;
  1208. int my_rgb = self->count;
  1209. int target_rgb = self->chain->s.skinnum;
  1210. int my_b = ((my_rgb >> 8 ) & 0xff);
  1211. int my_g = ((my_rgb >> 16) & 0xff);
  1212. int my_r = ((my_rgb >> 24) & 0xff);
  1213. int target_b = ((target_rgb >> 8 ) & 0xff);
  1214. int target_g = ((target_rgb >> 16) & 0xff);
  1215. int target_r = ((target_rgb >> 24) & 0xff);
  1216. float backlerp = 1.0f - lerp;
  1217. int b = (target_b * lerp) + (my_b * backlerp);
  1218. int g = (target_g * lerp) + (my_g * backlerp);
  1219. int r = (target_r * lerp) + (my_r * backlerp);
  1220. self->s.skinnum = (b << 8) | (g << 16) | (r << 24);
  1221. self->nextthink = level.time + 10_hz;
  1222. }
  1223. USE(target_light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1224. {
  1225. self->health = !self->health;
  1226. if (self->health)
  1227. self->svflags &= ~SVF_NOCLIENT;
  1228. else
  1229. self->svflags |= SVF_NOCLIENT;
  1230. if (!self->health)
  1231. {
  1232. self->think = nullptr;
  1233. self->nextthink = 0_ms;
  1234. return;
  1235. }
  1236. // has dynamic light "target"
  1237. if (self->chain)
  1238. {
  1239. self->think = target_light_think;
  1240. self->nextthink = level.time + 10_hz;
  1241. }
  1242. else if (self->spawnflags.has(SPAWNFLAG_TARGET_LIGHT_FLICKER))
  1243. {
  1244. self->think = target_light_flicker_think;
  1245. self->nextthink = level.time + 10_hz;
  1246. }
  1247. }
  1248. void SP_target_light(edict_t *self)
  1249. {
  1250. self->s.modelindex = 1;
  1251. self->s.renderfx = RF_CUSTOM_LIGHT;
  1252. self->s.frame = st.radius ? st.radius : 150;
  1253. self->count = self->s.skinnum;
  1254. self->svflags |= SVF_NOCLIENT;
  1255. self->health = 0;
  1256. if (self->target)
  1257. self->chain = G_PickTarget(self->target);
  1258. if (self->spawnflags.has(SPAWNFLAG_TARGET_LIGHT_START_ON))
  1259. target_light_use(self, self, self);
  1260. if (!self->speed)
  1261. self->speed = 1.0f;
  1262. else
  1263. self->speed = 0.1f / self->speed;
  1264. if (level.is_n64)
  1265. self->style += 10;
  1266. self->use = target_light_use;
  1267. gi.linkentity(self);
  1268. }
  1269. /*QUAKED target_poi (1 0 0) (-4 -4 -4) (4 4 4) NEAREST DUMMY DYNAMIC
  1270. [Paril-KEX] point of interest for help in player navigation.
  1271. Without any additional setup, targeting this entity will switch
  1272. the current POI in the level to the one this is linked to.
  1273. "count": if set, this value is the 'stage' linked to this POI. A POI
  1274. with this set that is activated will only take effect if the current
  1275. level's stage value is <= this value, and if it is, will also set
  1276. the current level's stage value to this value.
  1277. "style": only used for teamed POIs; the POI with the lowest style will
  1278. be activated when checking for which POI to activate. This is mainly
  1279. useful during development, to easily insert or change the order of teamed
  1280. POIs without needing to manually move the entity definitions around.
  1281. "team": if set, this will create a team of POIs. Teamed POIs act like
  1282. a single unit; activating any of them will do the same thing. When activated,
  1283. it will filter through all of the POIs on the team selecting the one that
  1284. best fits the current situation. This includes checking "count" and "style"
  1285. values. You can also set the NEAREST spawnflag on any of the teamed POIs,
  1286. which will additionally cause activation to prefer the nearest one to the player.
  1287. Killing a POI via killtarget will remove it from the chain, allowing you to
  1288. adjust valid POIs at runtime.
  1289. The DUMMY spawnflag is to allow you to use a single POI as a team member
  1290. that can be activated, if you're using killtargets to remove POIs.
  1291. The DYNAMIC spawnflag is for very specific circumstances where you want
  1292. to direct the player to the nearest teamed POI, but want the path to pick
  1293. the nearest at any given time rather than only when activated.
  1294. The DISABLED flag is mainly intended to work with DYNAMIC & teams; the POI
  1295. will be disabled until it is targeted, and afterwards will be enabled until
  1296. it is killed.
  1297. */
  1298. constexpr spawnflags_t SPAWNFLAG_POI_NEAREST = 1_spawnflag;
  1299. constexpr spawnflags_t SPAWNFLAG_POI_DUMMY = 2_spawnflag;
  1300. constexpr spawnflags_t SPAWNFLAG_POI_DYNAMIC = 4_spawnflag;
  1301. constexpr spawnflags_t SPAWNFLAG_POI_DISABLED = 8_spawnflag;
  1302. static float distance_to_poi(vec3_t start, vec3_t end)
  1303. {
  1304. PathRequest request;
  1305. request.start = start;
  1306. request.goal = end;
  1307. request.moveDist = 64.f;
  1308. request.pathFlags = PathFlags::All;
  1309. request.nodeSearch.ignoreNodeFlags = true;
  1310. request.nodeSearch.minHeight = 128.0f;
  1311. request.nodeSearch.maxHeight = 128.0f;
  1312. request.nodeSearch.radius = 1024.0f;
  1313. request.pathPoints.count = 0;
  1314. PathInfo info;
  1315. if (gi.GetPathToGoal(request, info))
  1316. return info.pathDistSqr;
  1317. if (info.returnCode == PathReturnCode::NoNavAvailable)
  1318. return (end - start).lengthSquared();
  1319. return std::numeric_limits<float>::infinity();
  1320. }
  1321. USE(target_poi_use) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  1322. {
  1323. // we were disabled, so remove the disable check
  1324. if (ent->spawnflags.has(SPAWNFLAG_POI_DISABLED))
  1325. ent->spawnflags &= ~SPAWNFLAG_POI_DISABLED;
  1326. // early stage check
  1327. if (ent->count && level.current_poi_stage > ent->count)
  1328. return;
  1329. // teamed POIs work a bit differently
  1330. if (ent->team)
  1331. {
  1332. edict_t *poi_master = ent->teammaster;
  1333. // unset ent, since we need to find one that matches
  1334. ent = nullptr;
  1335. float best_distance = std::numeric_limits<float>::infinity();
  1336. int32_t best_style = std::numeric_limits<int32_t>::max();
  1337. edict_t *dummy_fallback = nullptr;
  1338. for (edict_t *poi = poi_master; poi; poi = poi->teamchain)
  1339. {
  1340. // currently disabled
  1341. if (poi->spawnflags.has(SPAWNFLAG_POI_DISABLED))
  1342. continue;
  1343. // ignore dummy POI
  1344. if (poi->spawnflags.has(SPAWNFLAG_POI_DUMMY))
  1345. {
  1346. dummy_fallback = poi;
  1347. continue;
  1348. }
  1349. // POI is not part of current stage
  1350. else if (poi->count && level.current_poi_stage > poi->count)
  1351. continue;
  1352. // POI isn't the right style
  1353. else if (poi->style > best_style)
  1354. continue;
  1355. float dist = distance_to_poi(activator->s.origin, poi->s.origin);
  1356. // we have one already and it's farther away, don't bother
  1357. if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST) &&
  1358. ent &&
  1359. dist > best_distance)
  1360. continue;
  1361. // found a better style; overwrite dist
  1362. if (poi->style < best_style)
  1363. {
  1364. // unless we weren't reachable...
  1365. if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST) && std::isinf(dist))
  1366. continue;
  1367. best_style = poi->style;
  1368. if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST))
  1369. best_distance = dist;
  1370. ent = poi;
  1371. continue;
  1372. }
  1373. // if we're picking by nearest, check distance
  1374. if (poi_master->spawnflags.has(SPAWNFLAG_POI_NEAREST))
  1375. {
  1376. if (dist < best_distance)
  1377. {
  1378. best_distance = dist;
  1379. ent = poi;
  1380. continue;
  1381. }
  1382. }
  1383. else
  1384. {
  1385. // not picking by distance, so it's order of appearance
  1386. ent = poi;
  1387. }
  1388. }
  1389. // no valid POI found; this isn't always an error,
  1390. // some valid techniques may require this to happen.
  1391. if (!ent)
  1392. {
  1393. if (dummy_fallback && dummy_fallback->spawnflags.has(SPAWNFLAG_POI_DYNAMIC))
  1394. ent = dummy_fallback;
  1395. else
  1396. return;
  1397. }
  1398. // copy over POI stage value
  1399. if (ent->count)
  1400. {
  1401. if (level.current_poi_stage <= ent->count)
  1402. level.current_poi_stage = ent->count;
  1403. }
  1404. }
  1405. else
  1406. {
  1407. if (ent->count)
  1408. {
  1409. if (level.current_poi_stage <= ent->count)
  1410. level.current_poi_stage = ent->count;
  1411. else
  1412. return; // this POI is not part of our current stage
  1413. }
  1414. }
  1415. // dummy POI; not valid
  1416. if (!strcmp(ent->classname, "target_poi") && ent->spawnflags.has(SPAWNFLAG_POI_DUMMY) && !ent->spawnflags.has(SPAWNFLAG_POI_DYNAMIC))
  1417. return;
  1418. level.valid_poi = true;
  1419. level.current_poi = ent->s.origin;
  1420. level.current_poi_image = ent->noise_index;
  1421. if (!strcmp(ent->classname, "target_poi") && ent->spawnflags.has(SPAWNFLAG_POI_DYNAMIC))
  1422. {
  1423. level.current_dynamic_poi = nullptr;
  1424. // pick the dummy POI, since it isn't supposed to get freed
  1425. // FIXME maybe store the team string instead?
  1426. for (edict_t *m = ent->teammaster; m; m = m->teamchain)
  1427. if (m->spawnflags.has(SPAWNFLAG_POI_DUMMY))
  1428. {
  1429. level.current_dynamic_poi = m;
  1430. break;
  1431. }
  1432. if (!level.current_dynamic_poi)
  1433. gi.Com_PrintFmt("can't activate poi for {}; need DUMMY in chain\n", *ent);
  1434. }
  1435. else
  1436. level.current_dynamic_poi = nullptr;
  1437. }
  1438. THINK(target_poi_setup) (edict_t *self) -> void
  1439. {
  1440. if (self->team)
  1441. {
  1442. // copy dynamic/nearest over to all teammates
  1443. if (self->spawnflags.has((SPAWNFLAG_POI_NEAREST | SPAWNFLAG_POI_DYNAMIC)))
  1444. for (edict_t *m = self->teammaster; m; m = m->teamchain)
  1445. m->spawnflags |= self->spawnflags & (SPAWNFLAG_POI_NEAREST | SPAWNFLAG_POI_DYNAMIC);
  1446. for (edict_t *m = self->teammaster; m; m = m->teamchain)
  1447. {
  1448. if (strcmp(m->classname, "target_poi"))
  1449. gi.Com_PrintFmt("WARNING: {} is teamed with target_poi's; unintentional\n", *m);
  1450. }
  1451. }
  1452. }
  1453. void SP_target_poi(edict_t *self)
  1454. {
  1455. if (deathmatch->integer)
  1456. { // auto-remove for deathmatch
  1457. G_FreeEdict(self);
  1458. return;
  1459. }
  1460. if (st.image)
  1461. self->noise_index = gi.imageindex(st.image);
  1462. else
  1463. self->noise_index = gi.imageindex("friend");
  1464. self->use = target_poi_use;
  1465. self->svflags |= SVF_NOCLIENT;
  1466. self->think = target_poi_setup;
  1467. self->nextthink = level.time + 1_ms;
  1468. if (!self->team)
  1469. {
  1470. if (self->spawnflags.has(SPAWNFLAG_POI_NEAREST))
  1471. gi.Com_PrintFmt("{} has useless spawnflag 'NEAREST'\n", *self);
  1472. if (self->spawnflags.has(SPAWNFLAG_POI_DYNAMIC))
  1473. gi.Com_PrintFmt("{} has useless spawnflag 'DYNAMIC'\n", *self);
  1474. }
  1475. }
  1476. /*QUAKED target_music (1 0 0) (-8 -8 -8) (8 8 8)
  1477. Change music when used
  1478. */
  1479. USE(use_target_music) (edict_t* ent, edict_t* other, edict_t* activator) -> void
  1480. {
  1481. gi.configstring(CS_CDTRACK, G_Fmt("{}", ent->sounds).data());
  1482. }
  1483. void SP_target_music(edict_t* self)
  1484. {
  1485. self->use = use_target_music;
  1486. }
  1487. /*QUAKED target_healthbar (0 1 0) (-8 -8 -8) (8 8 8) PVS_ONLY
  1488. *
  1489. * Hook up health bars to monsters.
  1490. * "delay" is how long to show the health bar for after death.
  1491. * "message" is their name
  1492. */
  1493. USE(use_target_healthbar) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  1494. {
  1495. edict_t *target = G_PickTarget(ent->target);
  1496. if (!target || ent->health != target->spawn_count)
  1497. {
  1498. if (target)
  1499. gi.Com_PrintFmt("{}: target {} changed from what it used to be\n", *ent, *target);
  1500. else
  1501. gi.Com_PrintFmt("{}: no target\n", *ent);
  1502. G_FreeEdict(ent);
  1503. return;
  1504. }
  1505. for (size_t i = 0; i < MAX_HEALTH_BARS; i++)
  1506. {
  1507. if (level.health_bar_entities[i])
  1508. continue;
  1509. ent->enemy = target;
  1510. level.health_bar_entities[i] = ent;
  1511. gi.configstring(CONFIG_HEALTH_BAR_NAME, ent->message);
  1512. return;
  1513. }
  1514. gi.Com_PrintFmt("{}: too many health bars\n", *ent);
  1515. G_FreeEdict(ent);
  1516. }
  1517. THINK(check_target_healthbar) (edict_t *ent) -> void
  1518. {
  1519. edict_t *target = G_PickTarget(ent->target);
  1520. if (!target || !(target->svflags & SVF_MONSTER))
  1521. {
  1522. if ( target != nullptr ) {
  1523. gi.Com_PrintFmt( "{}: target {} does not appear to be a monster\n", *ent, *target );
  1524. }
  1525. G_FreeEdict(ent);
  1526. return;
  1527. }
  1528. // just for sanity check
  1529. ent->health = target->spawn_count;
  1530. }
  1531. void SP_target_healthbar(edict_t *self)
  1532. {
  1533. if (deathmatch->integer)
  1534. {
  1535. G_FreeEdict(self);
  1536. return;
  1537. }
  1538. if (!self->target || !*self->target)
  1539. {
  1540. gi.Com_PrintFmt("{}: missing target\n", *self);
  1541. G_FreeEdict(self);
  1542. return;
  1543. }
  1544. if (!self->message)
  1545. {
  1546. gi.Com_PrintFmt("{}: missing message\n", *self);
  1547. G_FreeEdict(self);
  1548. return;
  1549. }
  1550. self->use = use_target_healthbar;
  1551. self->think = check_target_healthbar;
  1552. self->nextthink = level.time + 25_ms;
  1553. }
  1554. /*QUAKED target_autosave (0 1 0) (-8 -8 -8) (8 8 8)
  1555. *
  1556. * Auto save on command.
  1557. */
  1558. USE(use_target_autosave) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  1559. {
  1560. gtime_t save_time = gtime_t::from_sec(gi.cvar("g_athena_auto_save_min_time", "60", CVAR_NOSET)->value);
  1561. if (level.time - level.next_auto_save > save_time)
  1562. {
  1563. gi.AddCommandString("autosave\n");
  1564. level.next_auto_save = level.time;
  1565. }
  1566. }
  1567. void SP_target_autosave(edict_t *self)
  1568. {
  1569. if (deathmatch->integer)
  1570. {
  1571. G_FreeEdict(self);
  1572. return;
  1573. }
  1574. self->use = use_target_autosave;
  1575. }
  1576. /*QUAKED target_sky (0 1 0) (-8 -8 -8) (8 8 8)
  1577. *
  1578. * Change sky parameters.
  1579. "sky" environment map name
  1580. "skyaxis" vector axis for rotating sky
  1581. "skyrotate" speed of rotation in degrees/second
  1582. */
  1583. USE(use_target_sky) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1584. {
  1585. if (self->map)
  1586. gi.configstring(CS_SKY, self->map);
  1587. if (self->count & 3)
  1588. {
  1589. float rotate;
  1590. int32_t autorotate;
  1591. sscanf(gi.get_configstring(CS_SKYROTATE), "%f %i", &rotate, &autorotate);
  1592. if (self->count & 1)
  1593. rotate = self->accel;
  1594. if (self->count & 2)
  1595. autorotate = self->style;
  1596. gi.configstring(CS_SKYROTATE, G_Fmt("{} {}", rotate, autorotate).data());
  1597. }
  1598. if (self->count & 4)
  1599. gi.configstring(CS_SKYAXIS, G_Fmt("{}", self->movedir).data());
  1600. }
  1601. void SP_target_sky(edict_t *self)
  1602. {
  1603. self->use = use_target_sky;
  1604. if (st.was_key_specified("sky"))
  1605. self->map = st.sky;
  1606. if (st.was_key_specified("skyaxis"))
  1607. {
  1608. self->count |= 4;
  1609. self->movedir = st.skyaxis;
  1610. }
  1611. if (st.was_key_specified("skyrotate"))
  1612. {
  1613. self->count |= 1;
  1614. self->accel = st.skyrotate;
  1615. }
  1616. if (st.was_key_specified("skyautorotate"))
  1617. {
  1618. self->count |= 2;
  1619. self->style = st.skyautorotate;
  1620. }
  1621. }
  1622. //==========================================================
  1623. /*QUAKED target_crossunit_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
  1624. Once this trigger is touched/used, any trigger_crossunit_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work.
  1625. */
  1626. USE(trigger_crossunit_trigger_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1627. {
  1628. game.cross_unit_flags |= self->spawnflags.value;
  1629. G_FreeEdict(self);
  1630. }
  1631. void SP_target_crossunit_trigger(edict_t *self)
  1632. {
  1633. if (deathmatch->integer)
  1634. {
  1635. G_FreeEdict(self);
  1636. return;
  1637. }
  1638. self->svflags = SVF_NOCLIENT;
  1639. self->use = trigger_crossunit_trigger_use;
  1640. }
  1641. /*QUAKED target_crossunit_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 - - - - - - - - trigger9 trigger10 trigger11 trigger12 trigger13 trigger14 trigger15 trigger16
  1642. Triggered by a trigger_crossunit elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and
  1643. killtarget also work.
  1644. "delay" delay before using targets if the trigger has been activated (default 1)
  1645. */
  1646. THINK(target_crossunit_target_think) (edict_t *self) -> void
  1647. {
  1648. if (self->spawnflags.value == (game.cross_unit_flags & SFL_CROSS_TRIGGER_MASK & self->spawnflags.value))
  1649. {
  1650. G_UseTargets(self, self);
  1651. G_FreeEdict(self);
  1652. }
  1653. }
  1654. void SP_target_crossunit_target(edict_t *self)
  1655. {
  1656. if (deathmatch->integer)
  1657. {
  1658. G_FreeEdict(self);
  1659. return;
  1660. }
  1661. if (!self->delay)
  1662. self->delay = 1;
  1663. self->svflags = SVF_NOCLIENT;
  1664. self->think = target_crossunit_target_think;
  1665. self->nextthink = level.time + gtime_t::from_sec(self->delay);
  1666. }
  1667. /*QUAKED target_achievement (.5 .5 .5) (-8 -8 -8) (8 8 8)
  1668. Give an achievement.
  1669. "achievement" cheevo to give
  1670. */
  1671. USE(use_target_achievement) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1672. {
  1673. gi.WriteByte(svc_achievement);
  1674. gi.WriteString(self->map);
  1675. gi.multicast(vec3_origin, MULTICAST_ALL, true);
  1676. }
  1677. void SP_target_achievement(edict_t *self)
  1678. {
  1679. if (deathmatch->integer)
  1680. {
  1681. G_FreeEdict(self);
  1682. return;
  1683. }
  1684. self->map = st.achievement;
  1685. self->use = use_target_achievement;
  1686. }
  1687. USE(use_target_story) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1688. {
  1689. if (self->message && *self->message)
  1690. level.story_active = true;
  1691. else
  1692. level.story_active = false;
  1693. gi.configstring(CONFIG_STORY, self->message ? self->message : "");
  1694. }
  1695. void SP_target_story(edict_t *self)
  1696. {
  1697. if (deathmatch->integer)
  1698. {
  1699. G_FreeEdict(self);
  1700. return;
  1701. }
  1702. self->use = use_target_story;
  1703. }