g_misc.cpp 63 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. // g_misc.c
  4. #include "g_local.h"
  5. /*QUAKED func_group (0 0 0) ?
  6. Used to group brushes together just for editor convenience.
  7. */
  8. //=====================================================
  9. USE(Use_Areaportal) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  10. {
  11. ent->count ^= 1; // toggle state
  12. gi.SetAreaPortalState(ent->style, ent->count);
  13. }
  14. /*QUAKED func_areaportal (0 0 0) ?
  15. This is a non-visible object that divides the world into
  16. areas that are seperated when this portal is not activated.
  17. Usually enclosed in the middle of a door.
  18. */
  19. void SP_func_areaportal(edict_t *ent)
  20. {
  21. ent->use = Use_Areaportal;
  22. ent->count = 0; // always start closed;
  23. }
  24. //=====================================================
  25. /*
  26. =================
  27. Misc functions
  28. =================
  29. */
  30. void VelocityForDamage(int damage, vec3_t &v)
  31. {
  32. v[0] = 100.0f * crandom();
  33. v[1] = 100.0f * crandom();
  34. v[2] = frandom(200.0f, 300.0f);
  35. if (damage < 50)
  36. v = v * 0.7f;
  37. else
  38. v = v * 1.2f;
  39. }
  40. void ClipGibVelocity(edict_t *ent)
  41. {
  42. if (ent->velocity[0] < -300)
  43. ent->velocity[0] = -300;
  44. else if (ent->velocity[0] > 300)
  45. ent->velocity[0] = 300;
  46. if (ent->velocity[1] < -300)
  47. ent->velocity[1] = -300;
  48. else if (ent->velocity[1] > 300)
  49. ent->velocity[1] = 300;
  50. if (ent->velocity[2] < 200)
  51. ent->velocity[2] = 200; // always some upwards
  52. else if (ent->velocity[2] > 500)
  53. ent->velocity[2] = 500;
  54. }
  55. /*
  56. =================
  57. gibs
  58. =================
  59. */
  60. DIE(gib_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  61. {
  62. if (mod.id == MOD_CRUSH)
  63. G_FreeEdict(self);
  64. }
  65. TOUCH(gib_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  66. {
  67. if (tr.plane.normal[2] > 0.7f)
  68. {
  69. self->s.angles[0] = clamp(self->s.angles[0], -5.0f, 5.0f);
  70. self->s.angles[2] = clamp(self->s.angles[2], -5.0f, 5.0f);
  71. }
  72. }
  73. edict_t *ThrowGib(edict_t *self, const char *gibname, int damage, gib_type_t type, float scale)
  74. {
  75. edict_t *gib;
  76. vec3_t vd;
  77. vec3_t origin;
  78. vec3_t size;
  79. float vscale;
  80. if (type & GIB_HEAD)
  81. {
  82. gib = self;
  83. gib->s.event = EV_OTHER_TELEPORT;
  84. // remove setskin so that it doesn't set the skin wrongly later
  85. self->monsterinfo.setskin = nullptr;
  86. }
  87. else
  88. gib = G_Spawn();
  89. size = self->size * 0.5f;
  90. // since absmin is bloated by 1, un-bloat it here
  91. origin = (self->absmin + vec3_t { 1, 1, 1 }) + size;
  92. int32_t i;
  93. for (i = 0; i < 3; i++)
  94. {
  95. gib->s.origin = origin + vec3_t { crandom(), crandom(), crandom() }.scaled(size);
  96. // try 3 times to get a good, non-solid position
  97. if (!(gi.pointcontents(gib->s.origin) & MASK_SOLID))
  98. break;
  99. }
  100. if (i == 3)
  101. {
  102. // only free us if we're not being turned into the gib, otherwise
  103. // just spawn inside a wall
  104. if (gib != self)
  105. {
  106. G_FreeEdict(gib);
  107. return nullptr;
  108. }
  109. }
  110. gib->s.modelindex = gi.modelindex(gibname);
  111. gib->s.modelindex2 = 0;
  112. gib->s.scale = scale;
  113. gib->solid = SOLID_NOT;
  114. gib->svflags |= SVF_DEADMONSTER;
  115. gib->svflags &= ~SVF_MONSTER;
  116. gib->clipmask = MASK_SOLID;
  117. gib->s.effects = EF_NONE;
  118. gib->s.renderfx = RF_LOW_PRIORITY;
  119. gib->s.renderfx |= RF_NOSHADOW;
  120. if (!(type & GIB_DEBRIS))
  121. {
  122. if (type & GIB_ACID)
  123. gib->s.effects |= EF_GREENGIB;
  124. else
  125. gib->s.effects |= EF_GIB;
  126. gib->s.renderfx |= RF_IR_VISIBLE;
  127. }
  128. gib->flags |= FL_NO_KNOCKBACK | FL_NO_DAMAGE_EFFECTS;
  129. gib->takedamage = true;
  130. gib->die = gib_die;
  131. gib->classname = "gib";
  132. if (type & GIB_SKINNED)
  133. gib->s.skinnum = self->s.skinnum;
  134. else
  135. gib->s.skinnum = 0;
  136. gib->s.frame = 0;
  137. gib->mins = gib->maxs = {};
  138. gib->s.sound = 0;
  139. gib->monsterinfo.engine_sound = 0;
  140. if (!(type & GIB_METALLIC))
  141. {
  142. gib->movetype = MOVETYPE_TOSS;
  143. vscale = (type & GIB_ACID) ? 3.0 : 0.5;
  144. }
  145. else
  146. {
  147. gib->movetype = MOVETYPE_BOUNCE;
  148. vscale = 1.0;
  149. }
  150. if (type & GIB_DEBRIS)
  151. {
  152. vec3_t v;
  153. v[0] = 100 * crandom();
  154. v[1] = 100 * crandom();
  155. v[2] = 100 + 100 * crandom();
  156. gib->velocity = self->velocity + (v * damage);
  157. }
  158. else
  159. {
  160. VelocityForDamage(damage, vd);
  161. gib->velocity = self->velocity + (vd * vscale);
  162. ClipGibVelocity(gib);
  163. }
  164. if (type & GIB_UPRIGHT)
  165. {
  166. gib->touch = gib_touch;
  167. gib->flags |= FL_ALWAYS_TOUCH;
  168. }
  169. gib->avelocity[0] = frandom(600);
  170. gib->avelocity[1] = frandom(600);
  171. gib->avelocity[2] = frandom(600);
  172. gib->s.angles[0] = frandom(359);
  173. gib->s.angles[1] = frandom(359);
  174. gib->s.angles[2] = frandom(359);
  175. gib->think = G_FreeEdict;
  176. if (g_instagib->integer)
  177. gib->nextthink = level.time + random_time(1_sec, 5_sec);
  178. else
  179. gib->nextthink = level.time + random_time(10_sec, 20_sec);
  180. gi.linkentity(gib);
  181. gib->watertype = gi.pointcontents(gib->s.origin);
  182. if (gib->watertype & MASK_WATER)
  183. gib->waterlevel = WATER_FEET;
  184. else
  185. gib->waterlevel = WATER_NONE;
  186. return gib;
  187. }
  188. void ThrowClientHead(edict_t *self, int damage)
  189. {
  190. vec3_t vd;
  191. const char *gibname;
  192. if (brandom())
  193. {
  194. gibname = "models/objects/gibs/head2/tris.md2";
  195. self->s.skinnum = 1; // second skin is player
  196. }
  197. else
  198. {
  199. gibname = "models/objects/gibs/skull/tris.md2";
  200. self->s.skinnum = 0;
  201. }
  202. self->s.origin[2] += 32;
  203. self->s.frame = 0;
  204. gi.setmodel(self, gibname);
  205. self->mins = { -16, -16, 0 };
  206. self->maxs = { 16, 16, 16 };
  207. self->takedamage = true; // [Paril-KEX] allow takedamage so we get crushed
  208. self->solid = SOLID_TRIGGER; // [Paril-KEX] make 'trigger' so we still move but don't block shots/explode
  209. self->svflags |= SVF_DEADMONSTER;
  210. self->s.effects = EF_GIB;
  211. // PGM
  212. self->s.renderfx |= RF_IR_VISIBLE;
  213. // PGM
  214. self->s.sound = 0;
  215. self->flags |= FL_NO_KNOCKBACK | FL_NO_DAMAGE_EFFECTS;
  216. self->movetype = MOVETYPE_BOUNCE;
  217. VelocityForDamage(damage, vd);
  218. self->velocity += vd;
  219. if (self->client) // bodies in the queue don't have a client anymore
  220. {
  221. self->client->anim_priority = ANIM_DEATH;
  222. self->client->anim_end = self->s.frame;
  223. }
  224. else
  225. {
  226. self->think = nullptr;
  227. self->nextthink = 0_ms;
  228. }
  229. gi.linkentity(self);
  230. }
  231. void BecomeExplosion1(edict_t *self)
  232. {
  233. gi.WriteByte(svc_temp_entity);
  234. gi.WriteByte(TE_EXPLOSION1);
  235. gi.WritePosition(self->s.origin);
  236. gi.multicast(self->s.origin, MULTICAST_PHS, false);
  237. G_FreeEdict(self);
  238. }
  239. void BecomeExplosion2(edict_t *self)
  240. {
  241. gi.WriteByte(svc_temp_entity);
  242. gi.WriteByte(TE_EXPLOSION2);
  243. gi.WritePosition(self->s.origin);
  244. gi.multicast(self->s.origin, MULTICAST_PHS, false);
  245. G_FreeEdict(self);
  246. }
  247. /*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
  248. Target: next path corner
  249. Pathtarget: gets used when an entity that has
  250. this path_corner targeted touches it
  251. */
  252. TOUCH(path_corner_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  253. {
  254. vec3_t v;
  255. edict_t *next;
  256. if (other->movetarget != self)
  257. return;
  258. if (other->enemy)
  259. return;
  260. if (self->pathtarget)
  261. {
  262. const char *savetarget;
  263. savetarget = self->target;
  264. self->target = self->pathtarget;
  265. G_UseTargets(self, other);
  266. self->target = savetarget;
  267. }
  268. // see m_move; this is just so we don't needlessly check it
  269. self->flags |= FL_PARTIALGROUND;
  270. if (self->target)
  271. next = G_PickTarget(self->target);
  272. else
  273. next = nullptr;
  274. // [Paril-KEX] don't teleport to a point_combat, it means HOLD for them.
  275. if ((next) && !strcmp(next->classname, "path_corner") && next->spawnflags.has(SPAWNFLAG_PATH_CORNER_TELEPORT))
  276. {
  277. v = next->s.origin;
  278. v[2] += next->mins[2];
  279. v[2] -= other->mins[2];
  280. other->s.origin = v;
  281. next = G_PickTarget(next->target);
  282. other->s.event = EV_OTHER_TELEPORT;
  283. }
  284. other->goalentity = other->movetarget = next;
  285. if (self->wait)
  286. {
  287. other->monsterinfo.pausetime = level.time + gtime_t::from_sec(self->wait);
  288. other->monsterinfo.stand(other);
  289. return;
  290. }
  291. if (!other->movetarget)
  292. {
  293. // N64 cutscene behavior
  294. if (other->hackflags & HACKFLAG_END_CUTSCENE)
  295. {
  296. G_FreeEdict(other);
  297. return;
  298. }
  299. other->monsterinfo.pausetime = HOLD_FOREVER;
  300. other->monsterinfo.stand(other);
  301. }
  302. else
  303. {
  304. v = other->goalentity->s.origin - other->s.origin;
  305. other->ideal_yaw = vectoyaw(v);
  306. }
  307. }
  308. void SP_path_corner(edict_t *self)
  309. {
  310. if (!self->targetname)
  311. {
  312. gi.Com_PrintFmt("{} with no targetname\n", *self);
  313. G_FreeEdict(self);
  314. return;
  315. }
  316. self->solid = SOLID_TRIGGER;
  317. self->touch = path_corner_touch;
  318. self->mins = { -8, -8, -8 };
  319. self->maxs = { 8, 8, 8 };
  320. self->svflags |= SVF_NOCLIENT;
  321. gi.linkentity(self);
  322. }
  323. /*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
  324. Makes this the target of a monster and it will head here
  325. when first activated before going after the activator. If
  326. hold is selected, it will stay here.
  327. */
  328. TOUCH(point_combat_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  329. {
  330. edict_t *activator;
  331. if (other->movetarget != self)
  332. return;
  333. if (self->target)
  334. {
  335. other->target = self->target;
  336. other->goalentity = other->movetarget = G_PickTarget(other->target);
  337. if (!other->goalentity)
  338. {
  339. gi.Com_PrintFmt("{} target {} does not exist\n", *self, self->target);
  340. other->movetarget = self;
  341. }
  342. // [Paril-KEX] allow them to be re-used
  343. //self->target = nullptr;
  344. }
  345. else if (self->spawnflags.has(SPAWNFLAG_POINT_COMBAT_HOLD) && !(other->flags & (FL_SWIM | FL_FLY)))
  346. {
  347. // already standing
  348. if (other->monsterinfo.aiflags & AI_STAND_GROUND)
  349. return;
  350. other->monsterinfo.pausetime = HOLD_FOREVER;
  351. other->monsterinfo.aiflags |= AI_STAND_GROUND | AI_REACHED_HOLD_COMBAT | AI_THIRD_EYE;
  352. other->monsterinfo.stand(other);
  353. }
  354. if (other->movetarget == self)
  355. {
  356. // [Paril-KEX] if we're holding, keep movetarget set; we will
  357. // use this to make sure we haven't moved too far from where
  358. // we want to "guard".
  359. if (!self->spawnflags.has(SPAWNFLAG_POINT_COMBAT_HOLD))
  360. {
  361. other->target = nullptr;
  362. other->movetarget = nullptr;
  363. }
  364. other->goalentity = other->enemy;
  365. other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
  366. }
  367. if (self->pathtarget)
  368. {
  369. const char *savetarget;
  370. savetarget = self->target;
  371. self->target = self->pathtarget;
  372. if (other->enemy && other->enemy->client)
  373. activator = other->enemy;
  374. else if (other->oldenemy && other->oldenemy->client)
  375. activator = other->oldenemy;
  376. else if (other->activator && other->activator->client)
  377. activator = other->activator;
  378. else
  379. activator = other;
  380. G_UseTargets(self, activator);
  381. self->target = savetarget;
  382. }
  383. }
  384. void SP_point_combat(edict_t *self)
  385. {
  386. if (deathmatch->integer)
  387. {
  388. G_FreeEdict(self);
  389. return;
  390. }
  391. self->solid = SOLID_TRIGGER;
  392. self->touch = point_combat_touch;
  393. self->mins = { -8, -8, -16 };
  394. self->maxs = { 8, 8, 16 };
  395. self->svflags = SVF_NOCLIENT;
  396. gi.linkentity(self);
  397. }
  398. /*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
  399. Used as a positional target for spotlights, etc.
  400. */
  401. void SP_info_null(edict_t *self)
  402. {
  403. G_FreeEdict(self);
  404. }
  405. /*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
  406. Used as a positional target for lightning.
  407. */
  408. void SP_info_notnull(edict_t *self)
  409. {
  410. self->absmin = self->s.origin;
  411. self->absmax = self->s.origin;
  412. }
  413. /*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF ALLOW_IN_DM
  414. Non-displayed light.
  415. Default light value is 300.
  416. Default style is 0.
  417. If targeted, will toggle between on and off.
  418. Default _cone value is 10 (used to set size of light for spotlights)
  419. */
  420. constexpr spawnflags_t SPAWNFLAG_LIGHT_START_OFF = 1_spawnflag;
  421. constexpr spawnflags_t SPAWNFLAG_LIGHT_ALLOW_IN_DM = 2_spawnflag;
  422. USE(light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  423. {
  424. if (self->spawnflags.has(SPAWNFLAG_LIGHT_START_OFF))
  425. {
  426. gi.configstring(CS_LIGHTS + self->style, self->style_on);
  427. self->spawnflags &= ~SPAWNFLAG_LIGHT_START_OFF;
  428. }
  429. else
  430. {
  431. gi.configstring(CS_LIGHTS + self->style, self->style_off);
  432. self->spawnflags |= SPAWNFLAG_LIGHT_START_OFF;
  433. }
  434. }
  435. // ---------------------------------------------------------------------------------
  436. // [Sam-KEX] For keeping track of shadow light parameters and setting them up on
  437. // the server side.
  438. // TODO move to level_locals_t
  439. struct shadow_light_info_t
  440. {
  441. int entity_number;
  442. shadow_light_data_t shadowlight;
  443. };
  444. static shadow_light_info_t shadowlightinfo[MAX_SHADOW_LIGHTS];
  445. const shadow_light_data_t *GetShadowLightData(int32_t entity_number)
  446. {
  447. for (int32_t i = 0; i < level.shadow_light_count; i++)
  448. {
  449. if (shadowlightinfo[i].entity_number == entity_number)
  450. return &shadowlightinfo[i].shadowlight;
  451. }
  452. return nullptr;
  453. }
  454. void setup_shadow_lights()
  455. {
  456. for(int i = 0; i < level.shadow_light_count; ++i)
  457. {
  458. edict_t *self = &g_edicts[shadowlightinfo[i].entity_number];
  459. shadowlightinfo[i].shadowlight.lighttype = shadow_light_type_t::point;
  460. shadowlightinfo[i].shadowlight.conedirection = {};
  461. if(self->target)
  462. {
  463. edict_t* target = G_FindByString<&edict_t::targetname>(nullptr, self->target);
  464. if(target)
  465. {
  466. shadowlightinfo[i].shadowlight.conedirection = (target->s.origin - self->s.origin).normalized();
  467. shadowlightinfo[i].shadowlight.lighttype = shadow_light_type_t::cone;
  468. }
  469. }
  470. if (self->itemtarget)
  471. {
  472. edict_t* target = G_FindByString<&edict_t::targetname>(nullptr, self->itemtarget);
  473. if(target)
  474. shadowlightinfo[i].shadowlight.lightstyle = target->style;
  475. }
  476. gi.configstring(CS_SHADOWLIGHTS + i, G_Fmt("{};{};{:1};{};{:1};{:1};{:1};{};{:1};{:1};{:1};{:1}",
  477. self->s.number,
  478. (int)shadowlightinfo[i].shadowlight.lighttype,
  479. shadowlightinfo[i].shadowlight.radius,
  480. shadowlightinfo[i].shadowlight.resolution,
  481. shadowlightinfo[i].shadowlight.intensity,
  482. shadowlightinfo[i].shadowlight.fade_start,
  483. shadowlightinfo[i].shadowlight.fade_end,
  484. shadowlightinfo[i].shadowlight.lightstyle,
  485. shadowlightinfo[i].shadowlight.coneangle,
  486. shadowlightinfo[i].shadowlight.conedirection[0],
  487. shadowlightinfo[i].shadowlight.conedirection[1],
  488. shadowlightinfo[i].shadowlight.conedirection[2]).data());
  489. }
  490. }
  491. // fix an oversight in shadow light code that causes
  492. // lights to be ordered wrong on return levels
  493. // if the spawn functions are changed.
  494. // this will work without changing the save/load code.
  495. void G_LoadShadowLights()
  496. {
  497. for (size_t i = 0; i < level.shadow_light_count; i++)
  498. {
  499. const char *cstr = gi.get_configstring(CS_SHADOWLIGHTS + i);
  500. const char *token = COM_ParseEx(&cstr, ";");
  501. if (token && *token)
  502. {
  503. shadowlightinfo[i].entity_number = atoi(token);
  504. token = COM_ParseEx(&cstr, ";");
  505. shadowlightinfo[i].shadowlight.lighttype = (shadow_light_type_t) atoi(token);
  506. token = COM_ParseEx(&cstr, ";");
  507. shadowlightinfo[i].shadowlight.radius = atof(token);
  508. token = COM_ParseEx(&cstr, ";");
  509. shadowlightinfo[i].shadowlight.resolution = atoi(token);
  510. token = COM_ParseEx(&cstr, ";");
  511. shadowlightinfo[i].shadowlight.intensity = atof(token);
  512. token = COM_ParseEx(&cstr, ";");
  513. shadowlightinfo[i].shadowlight.fade_start = atof(token);
  514. token = COM_ParseEx(&cstr, ";");
  515. shadowlightinfo[i].shadowlight.fade_end = atof(token);
  516. token = COM_ParseEx(&cstr, ";");
  517. shadowlightinfo[i].shadowlight.lightstyle = atoi(token);
  518. token = COM_ParseEx(&cstr, ";");
  519. shadowlightinfo[i].shadowlight.coneangle = atof(token);
  520. token = COM_ParseEx(&cstr, ";");
  521. shadowlightinfo[i].shadowlight.conedirection[0] = atof(token);
  522. token = COM_ParseEx(&cstr, ";");
  523. shadowlightinfo[i].shadowlight.conedirection[1] = atof(token);
  524. token = COM_ParseEx(&cstr, ";");
  525. shadowlightinfo[i].shadowlight.conedirection[2] = atof(token);
  526. }
  527. }
  528. }
  529. // ---------------------------------------------------------------------------------
  530. static void setup_dynamic_light(edict_t* self)
  531. {
  532. // [Sam-KEX] Shadow stuff
  533. if (st.sl.data.radius > 0)
  534. {
  535. self->s.renderfx = RF_CASTSHADOW;
  536. self->itemtarget = st.sl.lightstyletarget;
  537. shadowlightinfo[level.shadow_light_count].entity_number = self->s.number;
  538. shadowlightinfo[level.shadow_light_count].shadowlight = st.sl.data;
  539. level.shadow_light_count++;
  540. self->mins[0] = self->mins[1] = self->mins[2] = 0;
  541. self->maxs[0] = self->maxs[1] = self->maxs[2] = 0;
  542. gi.linkentity(self);
  543. }
  544. }
  545. USE(dynamic_light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  546. {
  547. self->svflags ^= SVF_NOCLIENT;
  548. }
  549. void SP_dynamic_light(edict_t* self)
  550. {
  551. setup_dynamic_light(self);
  552. if (self->targetname)
  553. {
  554. self->use = dynamic_light_use;
  555. }
  556. if (self->spawnflags.has(SPAWNFLAG_LIGHT_START_OFF))
  557. self->svflags ^= SVF_NOCLIENT;
  558. }
  559. void SP_light(edict_t *self)
  560. {
  561. // no targeted lights in deathmatch, because they cause global messages
  562. if((!self->targetname || (deathmatch->integer && !(self->spawnflags.has(SPAWNFLAG_LIGHT_ALLOW_IN_DM)))) && st.sl.data.radius == 0) // [Sam-KEX]
  563. {
  564. G_FreeEdict(self);
  565. return;
  566. }
  567. if (self->style >= 32)
  568. {
  569. self->use = light_use;
  570. if (!self->style_on || !*self->style_on)
  571. self->style_on = "m";
  572. else if (*self->style_on >= '0' && *self->style_on <= '9')
  573. self->style_on = gi.get_configstring(CS_LIGHTS + atoi(self->style_on));
  574. if (!self->style_off || !*self->style_off)
  575. self->style_off = "a";
  576. else if (*self->style_off >= '0' && *self->style_off <= '9')
  577. self->style_off = gi.get_configstring(CS_LIGHTS + atoi(self->style_off));
  578. if (self->spawnflags.has(SPAWNFLAG_LIGHT_START_OFF))
  579. gi.configstring(CS_LIGHTS + self->style, self->style_off);
  580. else
  581. gi.configstring(CS_LIGHTS + self->style, self->style_on);
  582. }
  583. setup_dynamic_light(self);
  584. }
  585. /*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
  586. This is just a solid wall if not inhibited
  587. TRIGGER_SPAWN the wall will not be present until triggered
  588. it will then blink in to existance; it will
  589. kill anything that was in it's way
  590. TOGGLE only valid for TRIGGER_SPAWN walls
  591. this allows the wall to be turned on and off
  592. START_ON only valid for TRIGGER_SPAWN walls
  593. the wall will initially be present
  594. */
  595. constexpr spawnflags_t SPAWNFLAG_WALL_TRIGGER_SPAWN = 1_spawnflag;
  596. constexpr spawnflags_t SPAWNFLAG_WALL_TOGGLE = 2_spawnflag;
  597. constexpr spawnflags_t SPAWNFLAG_WALL_START_ON = 4_spawnflag;
  598. constexpr spawnflags_t SPAWNFLAG_WALL_ANIMATED = 8_spawnflag;
  599. constexpr spawnflags_t SPAWNFLAG_WALL_ANIMATED_FAST = 16_spawnflag;
  600. USE(func_wall_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  601. {
  602. if (self->solid == SOLID_NOT)
  603. {
  604. self->solid = SOLID_BSP;
  605. self->svflags &= ~SVF_NOCLIENT;
  606. gi.linkentity(self);
  607. KillBox(self, false);
  608. }
  609. else
  610. {
  611. self->solid = SOLID_NOT;
  612. self->svflags |= SVF_NOCLIENT;
  613. gi.linkentity(self);
  614. }
  615. if (!self->spawnflags.has(SPAWNFLAG_WALL_TOGGLE))
  616. self->use = nullptr;
  617. }
  618. void SP_func_wall(edict_t *self)
  619. {
  620. self->movetype = MOVETYPE_PUSH;
  621. gi.setmodel(self, self->model);
  622. if (self->spawnflags.has(SPAWNFLAG_WALL_ANIMATED))
  623. self->s.effects |= EF_ANIM_ALL;
  624. if (self->spawnflags.has(SPAWNFLAG_WALL_ANIMATED_FAST))
  625. self->s.effects |= EF_ANIM_ALLFAST;
  626. // just a wall
  627. if (!self->spawnflags.has(SPAWNFLAG_WALL_TRIGGER_SPAWN | SPAWNFLAG_WALL_TOGGLE | SPAWNFLAG_WALL_START_ON))
  628. {
  629. self->solid = SOLID_BSP;
  630. gi.linkentity(self);
  631. return;
  632. }
  633. // it must be TRIGGER_SPAWN
  634. if (!(self->spawnflags & SPAWNFLAG_WALL_TRIGGER_SPAWN))
  635. self->spawnflags |= SPAWNFLAG_WALL_TRIGGER_SPAWN;
  636. // yell if the spawnflags are odd
  637. if (self->spawnflags.has(SPAWNFLAG_WALL_START_ON))
  638. {
  639. if (!self->spawnflags.has(SPAWNFLAG_WALL_TOGGLE))
  640. {
  641. gi.Com_Print("func_wall START_ON without TOGGLE\n");
  642. self->spawnflags |= SPAWNFLAG_WALL_TOGGLE;
  643. }
  644. }
  645. self->use = func_wall_use;
  646. if (self->spawnflags.has(SPAWNFLAG_WALL_START_ON))
  647. {
  648. self->solid = SOLID_BSP;
  649. }
  650. else
  651. {
  652. self->solid = SOLID_NOT;
  653. self->svflags |= SVF_NOCLIENT;
  654. }
  655. gi.linkentity(self);
  656. }
  657. // [Paril-KEX]
  658. /*QUAKED func_animation (0 .5 .8) ? START_ON
  659. Similar to func_wall, but triggering it will toggle animation
  660. state rather than going on/off.
  661. START_ON will start in alterate animation
  662. */
  663. constexpr spawnflags_t SPAWNFLAG_ANIMATION_START_ON = 1_spawnflag;
  664. USE(func_animation_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  665. {
  666. self->bmodel_anim.alternate = !self->bmodel_anim.alternate;
  667. }
  668. void SP_func_animation(edict_t *self)
  669. {
  670. if (!self->bmodel_anim.enabled)
  671. {
  672. gi.Com_PrintFmt("{} has no animation data\n", *self);
  673. G_FreeEdict(self);
  674. return;
  675. }
  676. self->movetype = MOVETYPE_PUSH;
  677. gi.setmodel(self, self->model);
  678. self->solid = SOLID_BSP;
  679. self->use = func_animation_use;
  680. self->bmodel_anim.alternate = self->spawnflags.has(SPAWNFLAG_ANIMATION_START_ON);
  681. if (self->bmodel_anim.alternate)
  682. self->s.frame = self->bmodel_anim.alt_start;
  683. else
  684. self->s.frame = self->bmodel_anim.start;
  685. gi.linkentity(self);
  686. }
  687. /*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
  688. This is solid bmodel that will fall if it's support it removed.
  689. */
  690. constexpr spawnflags_t SPAWNFLAGS_OBJECT_TRIGGER_SPAWN = 1_spawnflag;
  691. constexpr spawnflags_t SPAWNFLAGS_OBJECT_ANIMATED = 2_spawnflag;
  692. constexpr spawnflags_t SPAWNFLAGS_OBJECT_ANIMATED_FAST = 4_spawnflag;
  693. TOUCH(func_object_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  694. {
  695. // only squash thing we fall on top of
  696. if (other_touching_self)
  697. return;
  698. if (tr.plane.normal[2] < 1.0f)
  699. return;
  700. if (other->takedamage == false)
  701. return;
  702. if (other->damage_debounce_time > level.time)
  703. return;
  704. T_Damage(other, self, self, vec3_origin, closest_point_to_box(other->s.origin, self->absmin, self->absmax), tr.plane.normal, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
  705. other->damage_debounce_time = level.time + 10_hz;
  706. }
  707. THINK(func_object_release) (edict_t *self) -> void
  708. {
  709. self->movetype = MOVETYPE_TOSS;
  710. self->touch = func_object_touch;
  711. }
  712. USE(func_object_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  713. {
  714. self->solid = SOLID_BSP;
  715. self->svflags &= ~SVF_NOCLIENT;
  716. self->use = nullptr;
  717. func_object_release(self);
  718. KillBox(self, false);
  719. }
  720. void SP_func_object(edict_t *self)
  721. {
  722. gi.setmodel(self, self->model);
  723. self->mins[0] += 1;
  724. self->mins[1] += 1;
  725. self->mins[2] += 1;
  726. self->maxs[0] -= 1;
  727. self->maxs[1] -= 1;
  728. self->maxs[2] -= 1;
  729. if (!self->dmg)
  730. self->dmg = 100;
  731. if (!(self->spawnflags & SPAWNFLAGS_OBJECT_TRIGGER_SPAWN))
  732. {
  733. self->solid = SOLID_BSP;
  734. self->movetype = MOVETYPE_PUSH;
  735. self->think = func_object_release;
  736. self->nextthink = level.time + 20_hz;
  737. }
  738. else
  739. {
  740. self->solid = SOLID_NOT;
  741. self->movetype = MOVETYPE_PUSH;
  742. self->use = func_object_use;
  743. self->svflags |= SVF_NOCLIENT;
  744. }
  745. if (self->spawnflags.has(SPAWNFLAGS_OBJECT_ANIMATED))
  746. self->s.effects |= EF_ANIM_ALL;
  747. if (self->spawnflags.has(SPAWNFLAGS_OBJECT_ANIMATED_FAST))
  748. self->s.effects |= EF_ANIM_ALLFAST;
  749. self->clipmask = MASK_MONSTERSOLID;
  750. self->flags |= FL_NO_STANDING;
  751. gi.linkentity(self);
  752. }
  753. /*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE ALWAYS_SHOOTABLE
  754. Any brush that you want to explode or break apart. If you want an
  755. ex0plosion, set dmg and it will do a radius explosion of that amount
  756. at the center of the bursh.
  757. If targeted it will not be shootable.
  758. INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must
  759. target the entity you want to trigger it. This is the only entity approved to activate it.
  760. health defaults to 100.
  761. mass defaults to 75. This determines how much debris is emitted when
  762. it explodes. You get one large chunk per 100 of mass (up to 8) and
  763. one small chunk per 25 of mass (up to 16). So 800 gives the most.
  764. */
  765. constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_TRIGGER_SPAWN = 1_spawnflag;
  766. constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_ANIMATED = 2_spawnflag;
  767. constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_ANIMATED_FAST = 4_spawnflag;
  768. constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_INACTIVE = 8_spawnflag;
  769. constexpr spawnflags_t SPAWNFLAGS_EXPLOSIVE_ALWAYS_SHOOTABLE = 16_spawnflag;
  770. DIE(func_explosive_explode) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  771. {
  772. size_t count;
  773. int mass;
  774. edict_t *master;
  775. bool done = false;
  776. self->takedamage = false;
  777. if (self->dmg)
  778. T_RadiusDamage(self, attacker, (float) self->dmg, nullptr, (float) (self->dmg + 40), DAMAGE_NONE, MOD_EXPLOSIVE);
  779. self->velocity = inflictor->s.origin - self->s.origin;
  780. self->velocity.normalize();
  781. self->velocity *= 150;
  782. mass = self->mass;
  783. if (!mass)
  784. mass = 75;
  785. // big chunks
  786. if (mass >= 100)
  787. {
  788. count = mass / 100;
  789. if (count > 8)
  790. count = 8;
  791. ThrowGibs(self, 1, {
  792. { count, "models/objects/debris1/tris.md2", GIB_METALLIC | GIB_DEBRIS }
  793. });
  794. }
  795. // small chunks
  796. count = mass / 25;
  797. if (count > 16)
  798. count = 16;
  799. ThrowGibs(self, 2, {
  800. { count, "models/objects/debris2/tris.md2", GIB_METALLIC | GIB_DEBRIS }
  801. });
  802. // PMM - if we're part of a train, clean ourselves out of it
  803. if (self->flags & FL_TEAMSLAVE)
  804. {
  805. if (self->teammaster)
  806. {
  807. master = self->teammaster;
  808. if (master && master->inuse) // because mappers (other than jim (usually)) are stupid....
  809. {
  810. while (!done)
  811. {
  812. if (master->teamchain == self)
  813. {
  814. master->teamchain = self->teamchain;
  815. done = true;
  816. }
  817. master = master->teamchain;
  818. }
  819. }
  820. }
  821. }
  822. G_UseTargets(self, attacker);
  823. self->s.origin = (self->absmin + self->absmax) * 0.5f;
  824. if (self->noise_index)
  825. gi.positioned_sound(self->s.origin, self, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
  826. if (self->dmg)
  827. BecomeExplosion1(self);
  828. else
  829. G_FreeEdict(self);
  830. }
  831. USE(func_explosive_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  832. {
  833. // Paril: pass activator to explode as attacker. this fixes
  834. // "strike" trying to centerprint to the relay. Should be
  835. // a safe change.
  836. func_explosive_explode(self, self, activator, self->health, vec3_origin, MOD_EXPLOSIVE);
  837. }
  838. // PGM
  839. USE(func_explosive_activate) (edict_t *self, edict_t *other, edict_t *activator) -> void
  840. {
  841. int approved;
  842. approved = 0;
  843. // PMM - looked like target and targetname were flipped here
  844. if (other != nullptr && other->target)
  845. {
  846. if (!strcmp(other->target, self->targetname))
  847. approved = 1;
  848. }
  849. if (!approved && activator != nullptr && activator->target)
  850. {
  851. if (!strcmp(activator->target, self->targetname))
  852. approved = 1;
  853. }
  854. if (!approved)
  855. return;
  856. self->use = func_explosive_use;
  857. if (!self->health)
  858. self->health = 100;
  859. self->die = func_explosive_explode;
  860. self->takedamage = true;
  861. }
  862. // PGM
  863. USE(func_explosive_spawn) (edict_t *self, edict_t *other, edict_t *activator) -> void
  864. {
  865. self->solid = SOLID_BSP;
  866. self->svflags &= ~SVF_NOCLIENT;
  867. self->use = nullptr;
  868. gi.linkentity(self);
  869. KillBox(self, false);
  870. }
  871. void SP_func_explosive(edict_t *self)
  872. {
  873. if (deathmatch->integer)
  874. { // auto-remove for deathmatch
  875. G_FreeEdict(self);
  876. return;
  877. }
  878. self->movetype = MOVETYPE_PUSH;
  879. gi.modelindex("models/objects/debris1/tris.md2");
  880. gi.modelindex("models/objects/debris2/tris.md2");
  881. gi.setmodel(self, self->model);
  882. if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_TRIGGER_SPAWN))
  883. {
  884. self->svflags |= SVF_NOCLIENT;
  885. self->solid = SOLID_NOT;
  886. self->use = func_explosive_spawn;
  887. }
  888. // PGM
  889. else if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_INACTIVE))
  890. {
  891. self->solid = SOLID_BSP;
  892. if (self->targetname)
  893. self->use = func_explosive_activate;
  894. }
  895. // PGM
  896. else
  897. {
  898. self->solid = SOLID_BSP;
  899. if (self->targetname)
  900. self->use = func_explosive_use;
  901. }
  902. if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_ANIMATED))
  903. self->s.effects |= EF_ANIM_ALL;
  904. if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_ANIMATED_FAST))
  905. self->s.effects |= EF_ANIM_ALLFAST;
  906. // PGM
  907. if (self->spawnflags.has(SPAWNFLAGS_EXPLOSIVE_ALWAYS_SHOOTABLE) || ((self->use != func_explosive_use) && (self->use != func_explosive_activate)))
  908. // PGM
  909. {
  910. if (!self->health)
  911. self->health = 100;
  912. self->die = func_explosive_explode;
  913. self->takedamage = true;
  914. }
  915. if (self->sounds)
  916. {
  917. if (self->sounds == 1)
  918. self->noise_index = gi.soundindex("world/brkglas.wav");
  919. else
  920. gi.Com_PrintFmt("{}: invalid \"sounds\" {}\n", *self, self->sounds);
  921. }
  922. gi.linkentity(self);
  923. }
  924. /*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40)
  925. Large exploding box. You can override its mass (100),
  926. health (80), and dmg (150).
  927. */
  928. TOUCH(barrel_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  929. {
  930. float ratio;
  931. vec3_t v;
  932. if ((!other->groundentity) || (other->groundentity == self))
  933. return;
  934. else if (!other_touching_self)
  935. return;
  936. ratio = (float) other->mass / (float) self->mass;
  937. v = self->s.origin - other->s.origin;
  938. M_walkmove(self, vectoyaw(v), 20 * ratio * gi.frame_time_s);
  939. }
  940. THINK(barrel_explode) (edict_t *self) -> void
  941. {
  942. self->takedamage = false;
  943. T_RadiusDamage(self, self->activator, (float) self->dmg, nullptr, (float) (self->dmg + 40), DAMAGE_NONE, MOD_BARREL);
  944. ThrowGibs(self, (1.5f * self->dmg / 200.f), {
  945. { 2, "models/objects/debris1/tris.md2", GIB_METALLIC | GIB_DEBRIS },
  946. { 4, "models/objects/debris3/tris.md2", GIB_METALLIC | GIB_DEBRIS },
  947. { 8, "models/objects/debris2/tris.md2", GIB_METALLIC | GIB_DEBRIS }
  948. });
  949. if (self->groundentity)
  950. BecomeExplosion2(self);
  951. else
  952. BecomeExplosion1(self);
  953. }
  954. THINK(barrel_burn) (edict_t* self) -> void
  955. {
  956. if (level.time >= self->timestamp)
  957. self->think = barrel_explode;
  958. self->s.effects |= EF_BARREL_EXPLODING;
  959. self->s.sound = gi.soundindex("weapons/bfg__l1a.wav");
  960. self->nextthink = level.time + FRAME_TIME_S;
  961. }
  962. DIE(barrel_delay) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  963. {
  964. // allow "dead" barrels waiting to explode to still receive knockback
  965. if (self->think == barrel_burn || self->think == barrel_explode)
  966. return;
  967. // allow big booms to immediately blow up barrels (rockets, rail, other explosions) because it feels good and powerful
  968. if (damage >= 90)
  969. {
  970. self->think = barrel_explode;
  971. self->activator = attacker;
  972. }
  973. else
  974. {
  975. self->timestamp = level.time + 750_ms;
  976. self->think = barrel_burn;
  977. self->activator = attacker;
  978. }
  979. }
  980. //=========
  981. // PGM - change so barrels will think and hence, blow up
  982. THINK(barrel_think) (edict_t *self) -> void
  983. {
  984. // the think needs to be first since later stuff may override.
  985. self->think = barrel_think;
  986. self->nextthink = level.time + FRAME_TIME_S;
  987. M_CatagorizePosition(self, self->s.origin, self->waterlevel, self->watertype);
  988. self->flags |= FL_IMMUNE_SLIME;
  989. self->air_finished = level.time + 100_sec;
  990. M_WorldEffects(self);
  991. }
  992. THINK(barrel_start) (edict_t *self) -> void
  993. {
  994. M_droptofloor(self);
  995. self->think = barrel_think;
  996. self->nextthink = level.time + FRAME_TIME_S;
  997. }
  998. // PGM
  999. //=========
  1000. void SP_misc_explobox(edict_t *self)
  1001. {
  1002. if (deathmatch->integer)
  1003. { // auto-remove for deathmatch
  1004. G_FreeEdict(self);
  1005. return;
  1006. }
  1007. gi.modelindex("models/objects/debris1/tris.md2");
  1008. gi.modelindex("models/objects/debris2/tris.md2");
  1009. gi.modelindex("models/objects/debris3/tris.md2");
  1010. gi.soundindex("weapons/bfg__l1a.wav");
  1011. self->solid = SOLID_BBOX;
  1012. self->movetype = MOVETYPE_STEP;
  1013. self->model = "models/objects/barrels/tris.md2";
  1014. self->s.modelindex = gi.modelindex(self->model);
  1015. self->mins = { -16, -16, 0 };
  1016. self->maxs = { 16, 16, 40 };
  1017. if (!self->mass)
  1018. self->mass = 50;
  1019. if (!self->health)
  1020. self->health = 10;
  1021. if (!self->dmg)
  1022. self->dmg = 150;
  1023. self->die = barrel_delay;
  1024. self->takedamage = true;
  1025. self->flags |= FL_TRAP;
  1026. self->touch = barrel_touch;
  1027. // PGM - change so barrels will think and hence, blow up
  1028. self->think = barrel_start;
  1029. self->nextthink = level.time + 20_hz;
  1030. // PGM
  1031. gi.linkentity(self);
  1032. }
  1033. //
  1034. // miscellaneous specialty items
  1035. //
  1036. /*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) AUTO_NOISE
  1037. model="models/objects/black/tris.md2"
  1038. */
  1039. constexpr spawnflags_t SPAWNFLAG_BLACKHOLE_AUTO_NOISE = 1_spawnflag;
  1040. USE(misc_blackhole_use) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  1041. {
  1042. /*
  1043. gi.WriteByte (svc_temp_entity);
  1044. gi.WriteByte (TE_BOSSTPORT);
  1045. gi.WritePosition (ent->s.origin);
  1046. gi.multicast (ent->s.origin, MULTICAST_PVS);
  1047. */
  1048. G_FreeEdict(ent);
  1049. }
  1050. THINK(misc_blackhole_think) (edict_t *self) -> void
  1051. {
  1052. if (self->timestamp <= level.time)
  1053. {
  1054. if (++self->s.frame >= 19)
  1055. self->s.frame = 0;
  1056. self->timestamp = level.time + 10_hz;
  1057. }
  1058. if (self->spawnflags.has(SPAWNFLAG_BLACKHOLE_AUTO_NOISE))
  1059. {
  1060. self->s.angles[0] += 50.0f * gi.frame_time_s;
  1061. self->s.angles[1] += 50.0f * gi.frame_time_s;
  1062. }
  1063. self->nextthink = level.time + FRAME_TIME_MS;
  1064. }
  1065. void SP_misc_blackhole(edict_t *ent)
  1066. {
  1067. ent->movetype = MOVETYPE_NONE;
  1068. ent->solid = SOLID_NOT;
  1069. ent->mins = { -64, -64, 0 };
  1070. ent->maxs = { 64, 64, 8 };
  1071. ent->s.modelindex = gi.modelindex("models/objects/black/tris.md2");
  1072. ent->s.renderfx = RF_TRANSLUCENT;
  1073. ent->use = misc_blackhole_use;
  1074. ent->think = misc_blackhole_think;
  1075. ent->nextthink = level.time + 20_hz;
  1076. if (ent->spawnflags.has(SPAWNFLAG_BLACKHOLE_AUTO_NOISE))
  1077. {
  1078. ent->s.sound = gi.soundindex("world/blackhole.wav");
  1079. ent->s.loop_attenuation = ATTN_NORM;
  1080. }
  1081. gi.linkentity(ent);
  1082. }
  1083. /*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
  1084. */
  1085. THINK(misc_eastertank_think) (edict_t *self) -> void
  1086. {
  1087. if (++self->s.frame < 293)
  1088. self->nextthink = level.time + 10_hz;
  1089. else
  1090. {
  1091. self->s.frame = 254;
  1092. self->nextthink = level.time + 10_hz;
  1093. }
  1094. }
  1095. void SP_misc_eastertank(edict_t *ent)
  1096. {
  1097. ent->movetype = MOVETYPE_NONE;
  1098. ent->solid = SOLID_BBOX;
  1099. ent->mins = { -32, -32, -16 };
  1100. ent->maxs = { 32, 32, 32 };
  1101. ent->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2");
  1102. ent->s.frame = 254;
  1103. ent->think = misc_eastertank_think;
  1104. ent->nextthink = level.time + 20_hz;
  1105. gi.linkentity(ent);
  1106. }
  1107. /*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
  1108. */
  1109. THINK(misc_easterchick_think) (edict_t *self) -> void
  1110. {
  1111. if (++self->s.frame < 247)
  1112. self->nextthink = level.time + 10_hz;
  1113. else
  1114. {
  1115. self->s.frame = 208;
  1116. self->nextthink = level.time + 10_hz;
  1117. }
  1118. }
  1119. void SP_misc_easterchick(edict_t *ent)
  1120. {
  1121. ent->movetype = MOVETYPE_NONE;
  1122. ent->solid = SOLID_BBOX;
  1123. ent->mins = { -32, -32, 0 };
  1124. ent->maxs = { 32, 32, 32 };
  1125. ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
  1126. ent->s.frame = 208;
  1127. ent->think = misc_easterchick_think;
  1128. ent->nextthink = level.time + 20_hz;
  1129. gi.linkentity(ent);
  1130. }
  1131. /*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
  1132. */
  1133. THINK(misc_easterchick2_think) (edict_t *self) -> void
  1134. {
  1135. if (++self->s.frame < 287)
  1136. self->nextthink = level.time + 10_hz;
  1137. else
  1138. {
  1139. self->s.frame = 248;
  1140. self->nextthink = level.time + 10_hz;
  1141. }
  1142. }
  1143. void SP_misc_easterchick2(edict_t *ent)
  1144. {
  1145. ent->movetype = MOVETYPE_NONE;
  1146. ent->solid = SOLID_BBOX;
  1147. ent->mins = { -32, -32, 0 };
  1148. ent->maxs = { 32, 32, 32 };
  1149. ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
  1150. ent->s.frame = 248;
  1151. ent->think = misc_easterchick2_think;
  1152. ent->nextthink = level.time + 20_hz;
  1153. gi.linkentity(ent);
  1154. }
  1155. /*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48)
  1156. Not really a monster, this is the Tank Commander's decapitated body.
  1157. There should be a item_commander_head that has this as it's target.
  1158. */
  1159. THINK(commander_body_think) (edict_t *self) -> void
  1160. {
  1161. if (++self->s.frame < 24)
  1162. self->nextthink = level.time + 10_hz;
  1163. else
  1164. self->nextthink = 0_ms;
  1165. if (self->s.frame == 22)
  1166. gi.sound(self, CHAN_BODY, gi.soundindex("tank/thud.wav"), 1, ATTN_NORM, 0);
  1167. }
  1168. USE(commander_body_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1169. {
  1170. self->think = commander_body_think;
  1171. self->nextthink = level.time + 10_hz;
  1172. gi.sound(self, CHAN_BODY, gi.soundindex("tank/pain.wav"), 1, ATTN_NORM, 0);
  1173. }
  1174. THINK(commander_body_drop) (edict_t *self) -> void
  1175. {
  1176. self->movetype = MOVETYPE_TOSS;
  1177. self->s.origin[2] += 2;
  1178. }
  1179. void SP_monster_commander_body(edict_t *self)
  1180. {
  1181. self->movetype = MOVETYPE_NONE;
  1182. self->solid = SOLID_BBOX;
  1183. self->model = "models/monsters/commandr/tris.md2";
  1184. self->s.modelindex = gi.modelindex(self->model);
  1185. self->mins = { -32, -32, 0 };
  1186. self->maxs = { 32, 32, 48 };
  1187. self->use = commander_body_use;
  1188. self->takedamage = true;
  1189. self->flags = FL_GODMODE;
  1190. gi.linkentity(self);
  1191. gi.soundindex("tank/thud.wav");
  1192. gi.soundindex("tank/pain.wav");
  1193. self->think = commander_body_drop;
  1194. self->nextthink = level.time + 50_hz;
  1195. }
  1196. /*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4)
  1197. The origin is the bottom of the banner.
  1198. The banner is 128 tall.
  1199. model="models/objects/banner/tris.md2"
  1200. */
  1201. THINK(misc_banner_think) (edict_t *ent) -> void
  1202. {
  1203. ent->s.frame = (ent->s.frame + 1) % 16;
  1204. ent->nextthink = level.time + 10_hz;
  1205. }
  1206. void SP_misc_banner(edict_t *ent)
  1207. {
  1208. ent->movetype = MOVETYPE_NONE;
  1209. ent->solid = SOLID_NOT;
  1210. ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2");
  1211. ent->s.frame = irandom(16);
  1212. gi.linkentity(ent);
  1213. ent->think = misc_banner_think;
  1214. ent->nextthink = level.time + 10_hz;
  1215. }
  1216. /*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED
  1217. This is the dead player model. Comes in 6 exciting different poses!
  1218. */
  1219. constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_ON_BACK = 1_spawnflag;
  1220. constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_ON_STOMACH = 2_spawnflag;
  1221. constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_BACK_DECAP = 4_spawnflag;
  1222. constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_FETAL_POS = 8_spawnflag;
  1223. constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_SIT_DECAP = 16_spawnflag;
  1224. constexpr spawnflags_t SPAWNFLAGS_DEADSOLDIER_IMPALED = 32_spawnflag;
  1225. DIE(misc_deadsoldier_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
  1226. {
  1227. if (self->health > -30)
  1228. return;
  1229. gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
  1230. ThrowGibs(self, damage, {
  1231. { 4, "models/objects/gibs/sm_meat/tris.md2" },
  1232. { "models/objects/gibs/head2/tris.md2", GIB_HEAD }
  1233. });
  1234. }
  1235. void SP_misc_deadsoldier(edict_t *ent)
  1236. {
  1237. if (deathmatch->integer)
  1238. { // auto-remove for deathmatch
  1239. G_FreeEdict(ent);
  1240. return;
  1241. }
  1242. ent->movetype = MOVETYPE_NONE;
  1243. ent->solid = SOLID_BBOX;
  1244. ent->s.modelindex = gi.modelindex("models/deadbods/dude/tris.md2");
  1245. // Defaults to frame 0
  1246. if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_ON_STOMACH))
  1247. ent->s.frame = 1;
  1248. else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_BACK_DECAP))
  1249. ent->s.frame = 2;
  1250. else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_FETAL_POS))
  1251. ent->s.frame = 3;
  1252. else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_SIT_DECAP))
  1253. ent->s.frame = 4;
  1254. else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_IMPALED))
  1255. ent->s.frame = 5;
  1256. else if (ent->spawnflags.has(SPAWNFLAGS_DEADSOLDIER_ON_BACK))
  1257. ent->s.frame = 0;
  1258. else
  1259. ent->s.frame = 0;
  1260. ent->mins = { -16, -16, 0 };
  1261. ent->maxs = { 16, 16, 16 };
  1262. ent->deadflag = true;
  1263. ent->takedamage = true;
  1264. // nb: SVF_MONSTER is here so it bleeds
  1265. ent->svflags |= SVF_MONSTER | SVF_DEADMONSTER;
  1266. ent->die = misc_deadsoldier_die;
  1267. ent->monsterinfo.aiflags |= AI_GOOD_GUY | AI_DO_NOT_COUNT;
  1268. gi.linkentity(ent);
  1269. }
  1270. /*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32)
  1271. This is the Viper for the flyby bombing.
  1272. It is trigger_spawned, so you must have something use it for it to show up.
  1273. There must be a path for it to follow once it is activated.
  1274. "speed" How fast the Viper should fly
  1275. */
  1276. USE(misc_viper_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1277. {
  1278. self->svflags &= ~SVF_NOCLIENT;
  1279. self->use = train_use;
  1280. train_use(self, other, activator);
  1281. }
  1282. void SP_misc_viper(edict_t *ent)
  1283. {
  1284. if (!ent->target)
  1285. {
  1286. gi.Com_PrintFmt("{} without a target\n", *ent);
  1287. G_FreeEdict(ent);
  1288. return;
  1289. }
  1290. if (!ent->speed)
  1291. ent->speed = 300;
  1292. ent->movetype = MOVETYPE_PUSH;
  1293. ent->solid = SOLID_NOT;
  1294. ent->s.modelindex = gi.modelindex("models/ships/viper/tris.md2");
  1295. ent->mins = { -16, -16, 0 };
  1296. ent->maxs = { 16, 16, 32 };
  1297. ent->think = func_train_find;
  1298. ent->nextthink = level.time + 10_hz;
  1299. ent->use = misc_viper_use;
  1300. ent->svflags |= SVF_NOCLIENT;
  1301. ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
  1302. gi.linkentity(ent);
  1303. }
  1304. /*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72)
  1305. This is a large stationary viper as seen in Paul's intro
  1306. */
  1307. void SP_misc_bigviper(edict_t *ent)
  1308. {
  1309. ent->movetype = MOVETYPE_NONE;
  1310. ent->solid = SOLID_BBOX;
  1311. ent->mins = { -176, -120, -24 };
  1312. ent->maxs = { 176, 120, 72 };
  1313. ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2");
  1314. gi.linkentity(ent);
  1315. }
  1316. /*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8)
  1317. "dmg" how much boom should the bomb make?
  1318. */
  1319. TOUCH(misc_viper_bomb_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  1320. {
  1321. G_UseTargets(self, self->activator);
  1322. self->s.origin[2] = self->absmin[2] + 1;
  1323. T_RadiusDamage(self, self, (float) self->dmg, nullptr, (float) (self->dmg + 40), DAMAGE_NONE, MOD_BOMB);
  1324. BecomeExplosion2(self);
  1325. }
  1326. PRETHINK(misc_viper_bomb_prethink) (edict_t *self) -> void
  1327. {
  1328. self->groundentity = nullptr;
  1329. float diff = (self->timestamp - level.time).seconds();
  1330. if (diff < -1.0f)
  1331. diff = -1.0f;
  1332. vec3_t v = self->moveinfo.dir * (1.0f + diff);
  1333. v[2] = diff;
  1334. diff = self->s.angles[2];
  1335. self->s.angles = vectoangles(v);
  1336. self->s.angles[2] = diff + 10;
  1337. }
  1338. USE(misc_viper_bomb_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1339. {
  1340. edict_t *viper;
  1341. self->solid = SOLID_BBOX;
  1342. self->svflags &= ~SVF_NOCLIENT;
  1343. self->s.effects |= EF_ROCKET;
  1344. self->use = nullptr;
  1345. self->movetype = MOVETYPE_TOSS;
  1346. self->prethink = misc_viper_bomb_prethink;
  1347. self->touch = misc_viper_bomb_touch;
  1348. self->activator = activator;
  1349. viper = G_FindByString<&edict_t::classname>(nullptr, "misc_viper");
  1350. self->velocity = viper->moveinfo.dir * viper->moveinfo.speed;
  1351. self->timestamp = level.time;
  1352. self->moveinfo.dir = viper->moveinfo.dir;
  1353. }
  1354. void SP_misc_viper_bomb(edict_t *self)
  1355. {
  1356. self->movetype = MOVETYPE_NONE;
  1357. self->solid = SOLID_NOT;
  1358. self->mins = { -8, -8, -8 };
  1359. self->maxs = { 8, 8, 8 };
  1360. self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2");
  1361. if (!self->dmg)
  1362. self->dmg = 1000;
  1363. self->use = misc_viper_bomb_use;
  1364. self->svflags |= SVF_NOCLIENT;
  1365. gi.linkentity(self);
  1366. }
  1367. /*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32)
  1368. This is a Storgg ship for the flybys.
  1369. It is trigger_spawned, so you must have something use it for it to show up.
  1370. There must be a path for it to follow once it is activated.
  1371. "speed" How fast it should fly
  1372. */
  1373. USE(misc_strogg_ship_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1374. {
  1375. self->svflags &= ~SVF_NOCLIENT;
  1376. self->use = train_use;
  1377. train_use(self, other, activator);
  1378. }
  1379. void SP_misc_strogg_ship(edict_t *ent)
  1380. {
  1381. if (!ent->target)
  1382. {
  1383. gi.Com_PrintFmt("{} without a target\n", *ent);
  1384. G_FreeEdict(ent);
  1385. return;
  1386. }
  1387. if (!ent->speed)
  1388. ent->speed = 300;
  1389. ent->movetype = MOVETYPE_PUSH;
  1390. ent->solid = SOLID_NOT;
  1391. ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2");
  1392. ent->mins = { -16, -16, 0 };
  1393. ent->maxs = { 16, 16, 32 };
  1394. ent->think = func_train_find;
  1395. ent->nextthink = level.time + 10_hz;
  1396. ent->use = misc_strogg_ship_use;
  1397. ent->svflags |= SVF_NOCLIENT;
  1398. ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
  1399. gi.linkentity(ent);
  1400. }
  1401. /*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
  1402. model="models/objects/satellite/tris.md2"
  1403. */
  1404. THINK(misc_satellite_dish_think) (edict_t *self) -> void
  1405. {
  1406. self->s.frame++;
  1407. if (self->s.frame < 38)
  1408. self->nextthink = level.time + 10_hz;
  1409. }
  1410. USE(misc_satellite_dish_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1411. {
  1412. self->s.frame = 0;
  1413. self->think = misc_satellite_dish_think;
  1414. self->nextthink = level.time + 10_hz;
  1415. }
  1416. void SP_misc_satellite_dish(edict_t *ent)
  1417. {
  1418. ent->movetype = MOVETYPE_NONE;
  1419. ent->solid = SOLID_BBOX;
  1420. ent->mins = { -64, -64, 0 };
  1421. ent->maxs = { 64, 64, 128 };
  1422. ent->s.modelindex = gi.modelindex("models/objects/satellite/tris.md2");
  1423. ent->use = misc_satellite_dish_use;
  1424. gi.linkentity(ent);
  1425. }
  1426. /*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
  1427. */
  1428. void SP_light_mine1(edict_t *ent)
  1429. {
  1430. ent->movetype = MOVETYPE_NONE;
  1431. ent->solid = SOLID_NOT;
  1432. ent->svflags = SVF_DEADMONSTER;
  1433. ent->s.modelindex = gi.modelindex("models/objects/minelite/light1/tris.md2");
  1434. gi.linkentity(ent);
  1435. }
  1436. /*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
  1437. */
  1438. void SP_light_mine2(edict_t *ent)
  1439. {
  1440. ent->movetype = MOVETYPE_NONE;
  1441. ent->solid = SOLID_NOT;
  1442. ent->svflags = SVF_DEADMONSTER;
  1443. ent->s.modelindex = gi.modelindex("models/objects/minelite/light2/tris.md2");
  1444. gi.linkentity(ent);
  1445. }
  1446. /*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8)
  1447. Intended for use with the target_spawner
  1448. */
  1449. void SP_misc_gib_arm(edict_t *ent)
  1450. {
  1451. gi.setmodel(ent, "models/objects/gibs/arm/tris.md2");
  1452. ent->solid = SOLID_NOT;
  1453. ent->s.effects |= EF_GIB;
  1454. ent->takedamage = true;
  1455. ent->die = gib_die;
  1456. ent->movetype = MOVETYPE_TOSS;
  1457. ent->deadflag = true;
  1458. ent->avelocity[0] = frandom(200);
  1459. ent->avelocity[1] = frandom(200);
  1460. ent->avelocity[2] = frandom(200);
  1461. ent->think = G_FreeEdict;
  1462. ent->nextthink = level.time + 10_sec;
  1463. gi.linkentity(ent);
  1464. }
  1465. /*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8)
  1466. Intended for use with the target_spawner
  1467. */
  1468. void SP_misc_gib_leg(edict_t *ent)
  1469. {
  1470. gi.setmodel(ent, "models/objects/gibs/leg/tris.md2");
  1471. ent->solid = SOLID_NOT;
  1472. ent->s.effects |= EF_GIB;
  1473. ent->takedamage = true;
  1474. ent->die = gib_die;
  1475. ent->movetype = MOVETYPE_TOSS;
  1476. ent->deadflag = true;
  1477. ent->avelocity[0] = frandom(200);
  1478. ent->avelocity[1] = frandom(200);
  1479. ent->avelocity[2] = frandom(200);
  1480. ent->think = G_FreeEdict;
  1481. ent->nextthink = level.time + 10_sec;
  1482. gi.linkentity(ent);
  1483. }
  1484. /*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8)
  1485. Intended for use with the target_spawner
  1486. */
  1487. void SP_misc_gib_head(edict_t *ent)
  1488. {
  1489. gi.setmodel(ent, "models/objects/gibs/head/tris.md2");
  1490. ent->solid = SOLID_NOT;
  1491. ent->s.effects |= EF_GIB;
  1492. ent->takedamage = true;
  1493. ent->die = gib_die;
  1494. ent->movetype = MOVETYPE_TOSS;
  1495. ent->deadflag = true;
  1496. ent->avelocity[0] = frandom(200);
  1497. ent->avelocity[1] = frandom(200);
  1498. ent->avelocity[2] = frandom(200);
  1499. ent->think = G_FreeEdict;
  1500. ent->nextthink = level.time + 10_sec;
  1501. gi.linkentity(ent);
  1502. }
  1503. //=====================================================
  1504. /*QUAKED target_character (0 0 1) ?
  1505. used with target_string (must be on same "team")
  1506. "count" is position in the string (starts at 1)
  1507. */
  1508. void SP_target_character(edict_t *self)
  1509. {
  1510. self->movetype = MOVETYPE_PUSH;
  1511. gi.setmodel(self, self->model);
  1512. self->solid = SOLID_BSP;
  1513. self->s.frame = 12;
  1514. gi.linkentity(self);
  1515. return;
  1516. }
  1517. /*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
  1518. */
  1519. USE(target_string_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1520. {
  1521. edict_t *e;
  1522. int n;
  1523. size_t l;
  1524. char c;
  1525. l = strlen(self->message);
  1526. for (e = self->teammaster; e; e = e->teamchain)
  1527. {
  1528. if (!e->count)
  1529. continue;
  1530. n = e->count - 1;
  1531. if (n > l)
  1532. {
  1533. e->s.frame = 12;
  1534. continue;
  1535. }
  1536. c = self->message[n];
  1537. if (c >= '0' && c <= '9')
  1538. e->s.frame = c - '0';
  1539. else if (c == '-')
  1540. e->s.frame = 10;
  1541. else if (c == ':')
  1542. e->s.frame = 11;
  1543. else
  1544. e->s.frame = 12;
  1545. }
  1546. }
  1547. void SP_target_string(edict_t *self)
  1548. {
  1549. if (!self->message)
  1550. self->message = "";
  1551. self->use = target_string_use;
  1552. }
  1553. /*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE
  1554. target a target_string with this
  1555. The default is to be a time of day clock
  1556. TIMER_UP and TIMER_DOWN run for "count" seconds and then fire "pathtarget"
  1557. If START_OFF, this entity must be used before it starts
  1558. "style" 0 "xx"
  1559. 1 "xx:xx"
  1560. 2 "xx:xx:xx"
  1561. */
  1562. constexpr spawnflags_t SPAWNFLAG_TIMER_UP = 1_spawnflag;
  1563. constexpr spawnflags_t SPAWNFLAG_TIMER_DOWN = 2_spawnflag;
  1564. constexpr spawnflags_t SPAWNFLAG_TIMER_START_OFF = 4_spawnflag;
  1565. constexpr spawnflags_t SPAWNFLAG_TIMER_MULTI_USE = 8_spawnflag;
  1566. static void func_clock_reset(edict_t *self)
  1567. {
  1568. self->activator = nullptr;
  1569. if (self->spawnflags.has(SPAWNFLAG_TIMER_UP))
  1570. {
  1571. self->health = 0;
  1572. self->wait = (float) self->count;
  1573. }
  1574. else if (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN))
  1575. {
  1576. self->health = self->count;
  1577. self->wait = 0;
  1578. }
  1579. }
  1580. static void func_clock_format_countdown(edict_t *self)
  1581. {
  1582. if (self->style == 0)
  1583. {
  1584. G_FmtTo(self->clock_message, "{:2}", self->health);
  1585. return;
  1586. }
  1587. if (self->style == 1)
  1588. {
  1589. G_FmtTo(self->clock_message, "{:2}:{:02}", self->health / 60, self->health % 60);
  1590. return;
  1591. }
  1592. if (self->style == 2)
  1593. {
  1594. G_FmtTo(self->clock_message, "{:2}:{:02}:{:02}", self->health / 3600,
  1595. (self->health - (self->health / 3600) * 3600) / 60, self->health % 60);
  1596. return;
  1597. }
  1598. }
  1599. THINK(func_clock_think) (edict_t *self) -> void
  1600. {
  1601. if (!self->enemy)
  1602. {
  1603. self->enemy = G_FindByString<&edict_t::targetname>(nullptr, self->target);
  1604. if (!self->enemy)
  1605. return;
  1606. }
  1607. if (self->spawnflags.has(SPAWNFLAG_TIMER_UP))
  1608. {
  1609. func_clock_format_countdown(self);
  1610. self->health++;
  1611. }
  1612. else if (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN))
  1613. {
  1614. func_clock_format_countdown(self);
  1615. self->health--;
  1616. }
  1617. else
  1618. {
  1619. struct tm *ltime;
  1620. time_t gmtime;
  1621. time(&gmtime);
  1622. ltime = localtime(&gmtime);
  1623. G_FmtTo(self->clock_message, "{:2}:{:02}:{:02}", ltime->tm_hour, ltime->tm_min,
  1624. ltime->tm_sec);
  1625. }
  1626. self->enemy->message = self->clock_message;
  1627. self->enemy->use(self->enemy, self, self);
  1628. if ((self->spawnflags.has(SPAWNFLAG_TIMER_UP) && (self->health > self->wait)) ||
  1629. (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN) && (self->health < self->wait)))
  1630. {
  1631. if (self->pathtarget)
  1632. {
  1633. const char *savetarget;
  1634. savetarget = self->target;
  1635. self->target = self->pathtarget;
  1636. G_UseTargets(self, self->activator);
  1637. self->target = savetarget;
  1638. }
  1639. if (!self->spawnflags.has(SPAWNFLAG_TIMER_MULTI_USE))
  1640. return;
  1641. func_clock_reset(self);
  1642. if (self->spawnflags.has(SPAWNFLAG_TIMER_START_OFF))
  1643. return;
  1644. }
  1645. self->nextthink = level.time + 1_sec;
  1646. }
  1647. USE(func_clock_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
  1648. {
  1649. if (!self->spawnflags.has(SPAWNFLAG_TIMER_MULTI_USE))
  1650. self->use = nullptr;
  1651. if (self->activator)
  1652. return;
  1653. self->activator = activator;
  1654. self->think(self);
  1655. }
  1656. void SP_func_clock(edict_t *self)
  1657. {
  1658. if (!self->target)
  1659. {
  1660. gi.Com_PrintFmt("{} with no target\n", *self);
  1661. G_FreeEdict(self);
  1662. return;
  1663. }
  1664. if (self->spawnflags.has(SPAWNFLAG_TIMER_DOWN) && !self->count)
  1665. {
  1666. gi.Com_PrintFmt("{} with no count\n", *self);
  1667. G_FreeEdict(self);
  1668. return;
  1669. }
  1670. if (self->spawnflags.has(SPAWNFLAG_TIMER_UP) && (!self->count))
  1671. self->count = 60 * 60;
  1672. func_clock_reset(self);
  1673. self->think = func_clock_think;
  1674. if (self->spawnflags.has(SPAWNFLAG_TIMER_START_OFF))
  1675. self->use = func_clock_use;
  1676. else
  1677. self->nextthink = level.time + 1_sec;
  1678. }
  1679. //=================================================================================
  1680. constexpr spawnflags_t SPAWNFLAG_TELEPORTER_NO_SOUND = 1_spawnflag;
  1681. constexpr spawnflags_t SPAWNFLAG_TELEPORTER_NO_TELEPORT_EFFECT = 2_spawnflag;
  1682. TOUCH(teleporter_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  1683. {
  1684. edict_t *dest;
  1685. if (!other->client)
  1686. return;
  1687. dest = G_FindByString<&edict_t::targetname>(nullptr, self->target);
  1688. if (!dest)
  1689. {
  1690. gi.Com_Print("Couldn't find destination\n");
  1691. return;
  1692. }
  1693. // ZOID
  1694. CTFPlayerResetGrapple(other);
  1695. // ZOID
  1696. // unlink to make sure it can't possibly interfere with KillBox
  1697. gi.unlinkentity(other);
  1698. other->s.origin = dest->s.origin;
  1699. other->s.old_origin = dest->s.origin;
  1700. other->s.origin[2] += 10;
  1701. // clear the velocity and hold them in place briefly
  1702. other->velocity = {};
  1703. other->client->ps.pmove.pm_time = 160; // hold time
  1704. other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
  1705. // draw the teleport splash at source and on the player
  1706. if (!self->spawnflags.has(SPAWNFLAG_TELEPORTER_NO_TELEPORT_EFFECT))
  1707. {
  1708. self->owner->s.event = EV_PLAYER_TELEPORT;
  1709. other->s.event = EV_PLAYER_TELEPORT;
  1710. }
  1711. else
  1712. {
  1713. self->owner->s.event = EV_OTHER_TELEPORT;
  1714. other->s.event = EV_OTHER_TELEPORT;
  1715. }
  1716. // set angles
  1717. other->client->ps.pmove.delta_angles = dest->s.angles - other->client->resp.cmd_angles;
  1718. other->s.angles = {};
  1719. other->client->ps.viewangles = {};
  1720. other->client->v_angle = {};
  1721. AngleVectors(other->client->v_angle, other->client->v_forward, nullptr, nullptr);
  1722. gi.linkentity(other);
  1723. // kill anything at the destination
  1724. KillBox(other, !!other->client);
  1725. // [Paril-KEX] move sphere, if we own it
  1726. if (other->client->owned_sphere)
  1727. {
  1728. edict_t *sphere = other->client->owned_sphere;
  1729. sphere->s.origin = other->s.origin;
  1730. sphere->s.origin[2] = other->absmax[2];
  1731. sphere->s.angles[YAW] = other->s.angles[YAW];
  1732. gi.linkentity(sphere);
  1733. }
  1734. }
  1735. /*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) NO_SOUND NO_TELEPORT_EFFECT N64_EFFECT
  1736. Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
  1737. */
  1738. constexpr spawnflags_t SPAWNFLAG_TEMEPORTER_N64_EFFECT = 4_spawnflag;
  1739. void SP_misc_teleporter(edict_t *ent)
  1740. {
  1741. edict_t *trig;
  1742. gi.setmodel(ent, "models/objects/dmspot/tris.md2");
  1743. ent->s.skinnum = 1;
  1744. if (level.is_n64 || ent->spawnflags.has(SPAWNFLAG_TEMEPORTER_N64_EFFECT))
  1745. ent->s.effects = EF_TELEPORTER2;
  1746. else
  1747. ent->s.effects = EF_TELEPORTER;
  1748. if (!(ent->spawnflags & SPAWNFLAG_TELEPORTER_NO_SOUND))
  1749. ent->s.sound = gi.soundindex("world/amb10.wav");
  1750. ent->solid = SOLID_BBOX;
  1751. ent->mins = { -32, -32, -24 };
  1752. ent->maxs = { 32, 32, -16 };
  1753. gi.linkentity(ent);
  1754. // N64 has some of these for visual effects
  1755. if (!ent->target)
  1756. return;
  1757. trig = G_Spawn();
  1758. trig->touch = teleporter_touch;
  1759. trig->solid = SOLID_TRIGGER;
  1760. trig->target = ent->target;
  1761. trig->owner = ent;
  1762. trig->s.origin = ent->s.origin;
  1763. trig->mins = { -8, -8, 8 };
  1764. trig->maxs = { 8, 8, 24 };
  1765. gi.linkentity(trig);
  1766. }
  1767. /*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
  1768. Point teleporters at these.
  1769. */
  1770. void SP_misc_teleporter_dest(edict_t *ent)
  1771. {
  1772. // Paril-KEX N64 doesn't display these
  1773. if (level.is_n64)
  1774. return;
  1775. gi.setmodel(ent, "models/objects/dmspot/tris.md2");
  1776. ent->s.skinnum = 0;
  1777. ent->solid = SOLID_BBOX;
  1778. // ent->s.effects |= EF_FLIES;
  1779. ent->mins = { -32, -32, -24 };
  1780. ent->maxs = { 32, 32, -16 };
  1781. gi.linkentity(ent);
  1782. }
  1783. /*QUAKED misc_flare (1.0 1.0 0.0) (-32 -32 -32) (32 32 32) RED GREEN BLUE LOCK_ANGLE
  1784. Creates a flare seen in the N64 version.
  1785. */
  1786. static constexpr spawnflags_t SPAWNFLAG_FLARE_RED = 1_spawnflag;
  1787. static constexpr spawnflags_t SPAWNFLAG_FLARE_GREEN = 2_spawnflag;
  1788. static constexpr spawnflags_t SPAWNFLAG_FLARE_BLUE = 4_spawnflag;
  1789. static constexpr spawnflags_t SPAWNFLAG_FLARE_LOCK_ANGLE = 8_spawnflag;
  1790. USE(misc_flare_use) (edict_t *ent, edict_t *other, edict_t *activator) -> void
  1791. {
  1792. ent->svflags ^= SVF_NOCLIENT;
  1793. gi.linkentity(ent);
  1794. }
  1795. void SP_misc_flare(edict_t* ent)
  1796. {
  1797. ent->s.modelindex = 1;
  1798. ent->s.renderfx = RF_FLARE;
  1799. ent->solid = SOLID_NOT;
  1800. ent->s.scale = st.radius;
  1801. if (ent->spawnflags.has(SPAWNFLAG_FLARE_RED))
  1802. ent->s.renderfx |= RF_SHELL_RED;
  1803. if (ent->spawnflags.has(SPAWNFLAG_FLARE_GREEN))
  1804. ent->s.renderfx |= RF_SHELL_GREEN;
  1805. if (ent->spawnflags.has(SPAWNFLAG_FLARE_BLUE))
  1806. ent->s.renderfx |= RF_SHELL_BLUE;
  1807. if (ent->spawnflags.has(SPAWNFLAG_FLARE_LOCK_ANGLE))
  1808. ent->s.renderfx |= RF_FLARE_LOCK_ANGLE;
  1809. if (st.image && *st.image)
  1810. {
  1811. ent->s.renderfx |= RF_CUSTOMSKIN;
  1812. ent->s.frame = gi.imageindex(st.image);
  1813. }
  1814. ent->mins = { -32, -32, -32 };
  1815. ent->maxs = { 32, 32, 32 };
  1816. ent->s.modelindex2 = st.fade_start_dist;
  1817. ent->s.modelindex3 = st.fade_end_dist;
  1818. if (ent->targetname)
  1819. ent->use = misc_flare_use;
  1820. gi.linkentity(ent);
  1821. }
  1822. THINK(misc_hologram_think) (edict_t *ent) -> void
  1823. {
  1824. ent->s.angles[1] += 100 * gi.frame_time_s;
  1825. ent->nextthink = level.time + FRAME_TIME_MS;
  1826. ent->s.alpha = frandom(0.2f, 0.6f);
  1827. }
  1828. /*QUAKED misc_hologram (1.0 1.0 0.0) (-16 -16 0) (16 16 32)
  1829. Ship hologram seen in the N64 version.
  1830. */
  1831. void SP_misc_hologram(edict_t *ent)
  1832. {
  1833. ent->solid = SOLID_NOT;
  1834. ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2");
  1835. ent->mins = { -16, -16, 0 };
  1836. ent->maxs = { 16, 16, 32 };
  1837. ent->s.effects = EF_HOLOGRAM;
  1838. ent->think = misc_hologram_think;
  1839. ent->nextthink = level.time + FRAME_TIME_MS;
  1840. ent->s.alpha = frandom(0.2f, 0.6f);
  1841. ent->s.scale = 0.75f;
  1842. gi.linkentity(ent);
  1843. }
  1844. /*QUAKED misc_fireball (0 .5 .8) (-8 -8 -8) (8 8 8) NO_EXPLODE
  1845. Lava Balls. Shamelessly copied from Quake 1, like N64 guys
  1846. probably did too.
  1847. */
  1848. constexpr spawnflags_t SPAWNFLAG_LAVABALL_NO_EXPLODE = 1_spawnflag;
  1849. TOUCH(fire_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
  1850. {
  1851. if (self->spawnflags.has(SPAWNFLAG_LAVABALL_NO_EXPLODE))
  1852. {
  1853. G_FreeEdict(self);
  1854. return;
  1855. }
  1856. if (other->takedamage)
  1857. T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, 20, 0, DAMAGE_NONE, MOD_EXPLOSIVE);
  1858. if (gi.pointcontents(self->s.origin) & CONTENTS_LAVA)
  1859. G_FreeEdict(self);
  1860. else
  1861. BecomeExplosion1(self);
  1862. }
  1863. THINK(fire_fly) (edict_t *self) -> void
  1864. {
  1865. edict_t *fireball = G_Spawn();
  1866. fireball->s.effects = EF_FIREBALL;
  1867. fireball->s.renderfx = RF_MINLIGHT;
  1868. fireball->solid = SOLID_BBOX;
  1869. fireball->movetype = MOVETYPE_TOSS;
  1870. fireball->clipmask = MASK_SHOT;
  1871. fireball->velocity[0] = crandom() * 50;
  1872. fireball->velocity[1] = crandom() * 50;
  1873. fireball->avelocity = { crandom() * 360, crandom() * 360, crandom() * 360 };
  1874. fireball->velocity[2] = (self->speed * 1.75f) + (frandom() * 200);
  1875. fireball->classname = "fireball";
  1876. gi.setmodel(fireball, "models/objects/gibs/sm_meat/tris.md2");
  1877. fireball->s.origin = self->s.origin;
  1878. fireball->nextthink = level.time + 5_sec;
  1879. fireball->think = G_FreeEdict;
  1880. fireball->touch = fire_touch;
  1881. fireball->spawnflags = self->spawnflags;
  1882. gi.linkentity(fireball);
  1883. self->nextthink = level.time + random_time(5_sec);
  1884. }
  1885. void SP_misc_lavaball(edict_t *self)
  1886. {
  1887. self->classname = "fireball";
  1888. self->nextthink = level.time + random_time(5_sec);
  1889. self->think = fire_fly;
  1890. if (!self->speed)
  1891. self->speed = 185;
  1892. }
  1893. void SP_info_landmark(edict_t* self)
  1894. {
  1895. self->absmin = self->s.origin;
  1896. self->absmax = self->s.origin;
  1897. }
  1898. constexpr spawnflags_t SPAWNFLAG_WORLD_TEXT_START_OFF = 1_spawnflag;
  1899. constexpr spawnflags_t SPAWNFLAG_WORLD_TEXT_TRIGGER_ONCE = 2_spawnflag;
  1900. constexpr spawnflags_t SPAWNFLAG_WORLD_TEXT_REMOVE_ON_TRIGGER = 4_spawnflag;
  1901. USE( info_world_text_use ) ( edict_t * self, edict_t * other, edict_t * activator ) -> void {
  1902. if ( self->activator == nullptr ) {
  1903. self->activator = activator;
  1904. self->think( self );
  1905. } else {
  1906. self->nextthink = 0_ms;
  1907. self->activator = nullptr;
  1908. }
  1909. if (self->spawnflags.has(SPAWNFLAG_WORLD_TEXT_TRIGGER_ONCE)) {
  1910. self->use = nullptr;
  1911. }
  1912. if ( self->target != nullptr ) {
  1913. edict_t * target = G_PickTarget( self->target );
  1914. if ( target != nullptr && target->inuse ) {
  1915. if ( target->use ) {
  1916. target->use( target, self, self );
  1917. }
  1918. }
  1919. }
  1920. if (self->spawnflags.has(SPAWNFLAG_WORLD_TEXT_REMOVE_ON_TRIGGER)) {
  1921. G_FreeEdict( self );
  1922. }
  1923. }
  1924. THINK( info_world_text_think ) ( edict_t * self ) -> void {
  1925. rgba_t color = rgba_white;
  1926. switch ( self->sounds ) {
  1927. case 0:
  1928. color = rgba_white;
  1929. break;
  1930. case 1:
  1931. color = rgba_red;
  1932. break;
  1933. case 2:
  1934. color = rgba_blue;
  1935. break;
  1936. case 3:
  1937. color = rgba_green;
  1938. break;
  1939. case 4:
  1940. color = rgba_yellow;
  1941. break;
  1942. case 5:
  1943. color = rgba_black;
  1944. break;
  1945. case 6:
  1946. color = rgba_cyan;
  1947. break;
  1948. case 7:
  1949. color = rgba_orange;
  1950. break;
  1951. default:
  1952. color = rgba_white;
  1953. gi.Com_PrintFmt( "{}: invalid color\n", *self);
  1954. break;
  1955. }
  1956. if ( self->s.angles[ YAW ] == -3.0f ) {
  1957. gi.Draw_OrientedWorldText( self->s.origin, self->message, color, self->size[ 2 ], FRAME_TIME_MS.seconds(), true );
  1958. } else {
  1959. vec3_t textAngle = { 0.0f, 0.0f, 0.0f };
  1960. textAngle[ YAW ] = anglemod( self->s.angles[ YAW ] ) + 180;
  1961. if ( textAngle[ YAW ] > 360.0f ) {
  1962. textAngle[ YAW ] -= 360.0f;
  1963. }
  1964. gi.Draw_StaticWorldText( self->s.origin, textAngle, self->message, color, self->size[2], FRAME_TIME_MS.seconds(), true );
  1965. }
  1966. self->nextthink = level.time + FRAME_TIME_MS;
  1967. }
  1968. /*QUAKED info_world_text (1.0 1.0 0.0) (-16 -16 0) (16 16 32)
  1969. designer placed in world text for debugging.
  1970. */
  1971. void SP_info_world_text( edict_t * self ) {
  1972. if ( self->message == nullptr ) {
  1973. gi.Com_PrintFmt( "{}: no message\n", *self);
  1974. G_FreeEdict( self );
  1975. return;
  1976. } // not much point without something to print...
  1977. self->think = info_world_text_think;
  1978. self->use = info_world_text_use;
  1979. self->size[ 2 ] = st.radius ? st.radius : 0.2f;
  1980. if ( !self->spawnflags.has( SPAWNFLAG_WORLD_TEXT_START_OFF ) ) {
  1981. self->nextthink = level.time + FRAME_TIME_MS;
  1982. self->activator = self;
  1983. }
  1984. }
  1985. #include "m_player.h"
  1986. USE( misc_player_mannequin_use ) ( edict_t * self, edict_t * other, edict_t * activator ) -> void {
  1987. self->monsterinfo.aiflags |= AI_TARGET_ANGER;
  1988. self->enemy = activator;
  1989. switch ( self->count ) {
  1990. case GESTURE_FLIP_OFF:
  1991. self->s.frame = FRAME_flip01;
  1992. self->monsterinfo.nextframe = FRAME_flip12;
  1993. break;
  1994. case GESTURE_SALUTE:
  1995. self->s.frame = FRAME_salute01;
  1996. self->monsterinfo.nextframe = FRAME_salute11;
  1997. break;
  1998. case GESTURE_TAUNT:
  1999. self->s.frame = FRAME_taunt01;
  2000. self->monsterinfo.nextframe = FRAME_taunt17;
  2001. break;
  2002. case GESTURE_WAVE:
  2003. self->s.frame = FRAME_wave01;
  2004. self->monsterinfo.nextframe = FRAME_wave11;
  2005. break;
  2006. case GESTURE_POINT:
  2007. self->s.frame = FRAME_point01;
  2008. self->monsterinfo.nextframe = FRAME_point12;
  2009. break;
  2010. }
  2011. }
  2012. THINK( misc_player_mannequin_think ) ( edict_t * self ) -> void {
  2013. if ( self->teleport_time <= level.time ) {
  2014. self->s.frame++;
  2015. if ( ( self->monsterinfo.aiflags & AI_TARGET_ANGER ) == 0 ) {
  2016. if ( self->s.frame > FRAME_stand40 ) {
  2017. self->s.frame = FRAME_stand01;
  2018. }
  2019. } else {
  2020. if ( self->s.frame > self->monsterinfo.nextframe ) {
  2021. self->s.frame = FRAME_stand01;
  2022. self->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
  2023. self->enemy = nullptr;
  2024. }
  2025. }
  2026. self->teleport_time = level.time + 10_hz;
  2027. }
  2028. if ( self->enemy != nullptr ) {
  2029. const vec3_t vec = ( self->enemy->s.origin - self->s.origin );
  2030. self->ideal_yaw = vectoyaw( vec );
  2031. M_ChangeYaw( self );
  2032. }
  2033. self->nextthink = level.time + FRAME_TIME_MS;
  2034. }
  2035. void SetupMannequinModel( edict_t * self, const int32_t modelType, const char * weapon, const char * skin ) {
  2036. const char * modelName = nullptr;
  2037. const char * defaultSkin = nullptr;
  2038. switch ( modelType ) {
  2039. case 1: {
  2040. self->s.skinnum = ( MAX_CLIENTS - 1 );
  2041. modelName = "female";
  2042. defaultSkin = "venus";
  2043. break;
  2044. }
  2045. case 2: {
  2046. self->s.skinnum = ( MAX_CLIENTS - 2 );
  2047. modelName = "male";
  2048. defaultSkin = "rampage";
  2049. break;
  2050. }
  2051. case 3: {
  2052. self->s.skinnum = ( MAX_CLIENTS - 3 );
  2053. modelName = "cyborg";
  2054. defaultSkin = "oni911";
  2055. break;
  2056. }
  2057. default: {
  2058. self->s.skinnum = ( MAX_CLIENTS - 1 );
  2059. modelName = "female";
  2060. defaultSkin = "venus";
  2061. break;
  2062. }
  2063. }
  2064. if ( modelName != nullptr ) {
  2065. self->model = G_Fmt( "players/{}/tris.md2", modelName ).data();
  2066. const char * weaponName = nullptr;
  2067. if ( weapon != nullptr ) {
  2068. weaponName = G_Fmt( "players/{}/{}.md2", modelName, weapon ).data();
  2069. } else {
  2070. weaponName = G_Fmt( "players/{}/{}.md2", modelName, "w_hyperblaster" ).data();
  2071. }
  2072. self->s.modelindex2 = gi.modelindex( weaponName );
  2073. const char * skinName = nullptr;
  2074. if ( skin != nullptr ) {
  2075. skinName = G_Fmt( "mannequin\\{}/{}", modelName, skin ).data();
  2076. } else {
  2077. skinName = G_Fmt( "mannequin\\{}/{}", modelName, defaultSkin ).data();
  2078. }
  2079. gi.configstring( CS_PLAYERSKINS + self->s.skinnum, skinName );
  2080. }
  2081. }
  2082. /*QUAKED misc_player_mannequin (1.0 1.0 0.0) (-32 -32 -32) (32 32 32)
  2083. Creates a player mannequin that stands around.
  2084. NOTE: this is currently very limited, and only allows one unique model
  2085. from each of the three player model types.
  2086. "distance" - Sets the type of gesture mannequin when use when triggered
  2087. "height" - Sets the type of model to use ( valid numbers: 1 - 3 )
  2088. "goals" - Name of the weapon to use.
  2089. "image" - Name of the player skin to use.
  2090. "radius" - How much to scale the model in-game
  2091. */
  2092. void SP_misc_player_mannequin( edict_t * self ) {
  2093. self->movetype = MOVETYPE_NONE;
  2094. self->solid = SOLID_BBOX;
  2095. if (!st.was_key_specified("effects"))
  2096. self->s.effects = EF_NONE;
  2097. if (!st.was_key_specified("renderfx"))
  2098. self->s.renderfx = RF_MINLIGHT;
  2099. self->mins = { -16, -16, -24 };
  2100. self->maxs = { 16, 16, 32 };
  2101. self->yaw_speed = 30;
  2102. self->ideal_yaw = 0;
  2103. self->teleport_time = level.time + 10_hz;
  2104. self->s.modelindex = MODELINDEX_PLAYER;
  2105. self->count = st.distance;
  2106. SetupMannequinModel( self, st.height, st.goals, st.image );
  2107. self->s.scale = 1.0f;
  2108. if ( ai_model_scale->value > 0.0f ) {
  2109. self->s.scale = ai_model_scale->value;
  2110. } else if ( st.radius > 0.0f ) {
  2111. self->s.scale = st.radius;
  2112. }
  2113. self->mins *= self->s.scale;
  2114. self->maxs *= self->s.scale;
  2115. self->think = misc_player_mannequin_think;
  2116. self->nextthink = level.time + FRAME_TIME_MS;
  2117. if ( self->targetname ) {
  2118. self->use = misc_player_mannequin_use;
  2119. }
  2120. gi.linkentity( self );
  2121. }
  2122. /*QUAKED misc_model (1 0 0) (-8 -8 -8) (8 8 8)
  2123. */
  2124. void SP_misc_model(edict_t *ent)
  2125. {
  2126. gi.setmodel(ent, ent->model);
  2127. gi.linkentity(ent);
  2128. }