g_rogue_spawn.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "../g_local.h"
  4. //
  5. // ROGUE
  6. //
  7. //
  8. // Monster spawning code
  9. //
  10. // Used by the carrier, the medic_commander, and the black widow
  11. //
  12. // The sequence to create a flying monster is:
  13. //
  14. // FindSpawnPoint - tries to find suitable spot to spawn the monster in
  15. // CreateFlyMonster - this verifies the point as good and creates the monster
  16. // To create a ground walking monster:
  17. //
  18. // FindSpawnPoint - same thing
  19. // CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable
  20. //
  21. // FIXME - for the black widow, if we want the stalkers coming in on the roof, we'll have to tweak some things
  22. //
  23. // CreateMonster
  24. //
  25. edict_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname)
  26. {
  27. edict_t *newEnt;
  28. newEnt = G_Spawn();
  29. newEnt->s.origin = origin;
  30. newEnt->s.angles = angles;
  31. newEnt->classname = classname;
  32. newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
  33. newEnt->gravityVector = { 0, 0, -1 };
  34. ED_CallSpawn(newEnt);
  35. newEnt->s.renderfx |= RF_IR_VISIBLE;
  36. return newEnt;
  37. }
  38. edict_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, const char *classname)
  39. {
  40. if (!CheckSpawnPoint(origin, mins, maxs))
  41. return nullptr;
  42. return (CreateMonster(origin, angles, classname));
  43. }
  44. // This is just a wrapper for CreateMonster that looks down height # of CMUs and sees if there
  45. // are bad things down there or not
  46. edict_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &entMins, const vec3_t &entMaxs, const char *classname, float height)
  47. {
  48. edict_t *newEnt;
  49. // check the ground to make sure it's there, it's relatively flat, and it's not toxic
  50. if (!CheckGroundSpawnPoint(origin, entMins, entMaxs, height, -1.f))
  51. return nullptr;
  52. newEnt = CreateMonster(origin, angles, classname);
  53. if (!newEnt)
  54. return nullptr;
  55. return newEnt;
  56. }
  57. // FindSpawnPoint
  58. // PMM - this is used by the medic commander (possibly by the carrier) to find a good spawn point
  59. // if the startpoint is bad, try above the startpoint for a bit
  60. bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, float maxMoveUp, bool drop)
  61. {
  62. spawnpoint = startpoint;
  63. // drop first
  64. if (!drop || !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false))
  65. {
  66. spawnpoint = startpoint;
  67. // fix stuck if we couldn't drop initially
  68. if (G_FixStuckObject_Generic(spawnpoint, mins, maxs, [] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) {
  69. return gi.trace(start, mins, maxs, end, nullptr, MASK_MONSTERSOLID);
  70. }) == stuck_result_t::NO_GOOD_POSITION)
  71. return false;
  72. // fixed, so drop again
  73. if (drop && !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false))
  74. return false; // ???
  75. }
  76. return true;
  77. }
  78. // FIXME - all of this needs to be tweaked to handle the new gravity rules
  79. // if we ever want to spawn stuff on the roof
  80. //
  81. // CheckSpawnPoint
  82. //
  83. // PMM - checks volume to make sure we can spawn a monster there (is it solid?)
  84. //
  85. // This is all fliers should need
  86. bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs)
  87. {
  88. trace_t tr;
  89. if (!mins || !maxs)
  90. return false;
  91. tr = gi.trace(origin, mins, maxs, origin, nullptr, MASK_MONSTERSOLID);
  92. if (tr.startsolid || tr.allsolid)
  93. return false;
  94. if (tr.ent != world)
  95. return false;
  96. return true;
  97. }
  98. //
  99. // CheckGroundSpawnPoint
  100. //
  101. // PMM - used for walking monsters
  102. // checks:
  103. // 1) is there a ground within the specified height of the origin?
  104. // 2) is the ground non-water?
  105. // 3) is the ground flat enough to walk on?
  106. //
  107. bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, float gravity)
  108. {
  109. if (!CheckSpawnPoint(origin, entMins, entMaxs))
  110. return false;
  111. if (M_CheckBottom_Fast_Generic(origin + entMins, origin + entMaxs, false))
  112. return true;
  113. if (M_CheckBottom_Slow_Generic(origin, entMins, entMaxs, nullptr, MASK_MONSTERSOLID, false, false))
  114. return true;
  115. return false;
  116. }
  117. // ****************************
  118. // SPAWNGROW stuff
  119. // ****************************
  120. constexpr gtime_t SPAWNGROW_LIFESPAN = 1000_ms;
  121. THINK(spawngrow_think) (edict_t *self) -> void
  122. {
  123. if (level.time >= self->timestamp)
  124. {
  125. G_FreeEdict(self->target_ent);
  126. G_FreeEdict(self);
  127. return;
  128. }
  129. self->s.angles += self->avelocity * gi.frame_time_s;
  130. float t = 1.f - ((level.time - self->teleport_time).seconds() / self->wait);
  131. self->s.scale = clamp(lerp(self->decel, self->accel, t) / 16.f, 0.001f, 16.f);
  132. self->s.alpha = t * t;
  133. self->nextthink += FRAME_TIME_MS;
  134. }
  135. static vec3_t SpawnGro_laser_pos(edict_t *ent)
  136. {
  137. // pick random direction
  138. float theta = frandom(2 * PIf);
  139. float phi = acos(crandom());
  140. vec3_t d {
  141. sin(phi) * cos(theta),
  142. sin(phi) * sin(theta),
  143. cos(phi)
  144. };
  145. return ent->s.origin + (d * ent->owner->s.scale * 9.f);
  146. }
  147. THINK(SpawnGro_laser_think) (edict_t *self) -> void
  148. {
  149. self->s.old_origin = SpawnGro_laser_pos(self);
  150. gi.linkentity(self);
  151. self->nextthink = level.time + 1_ms;
  152. }
  153. void SpawnGrow_Spawn(const vec3_t &startpos, float start_size, float end_size)
  154. {
  155. edict_t *ent;
  156. ent = G_Spawn();
  157. ent->s.origin = startpos;
  158. ent->s.angles[0] = (float) irandom(360);
  159. ent->s.angles[1] = (float) irandom(360);
  160. ent->s.angles[2] = (float) irandom(360);
  161. ent->avelocity[0] = frandom(280.f, 360.f) * 2.f;
  162. ent->avelocity[1] = frandom(280.f, 360.f) * 2.f;
  163. ent->avelocity[2] = frandom(280.f, 360.f) * 2.f;
  164. ent->solid = SOLID_NOT;
  165. ent->s.renderfx |= RF_IR_VISIBLE;
  166. ent->movetype = MOVETYPE_NONE;
  167. ent->classname = "spawngro";
  168. ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2");
  169. ent->s.skinnum = 1;
  170. ent->accel = start_size;
  171. ent->decel = end_size;
  172. ent->think = spawngrow_think;
  173. ent->s.scale = clamp(start_size / 16.f, 0.001f, 8.f);
  174. ent->teleport_time = level.time;
  175. ent->wait = SPAWNGROW_LIFESPAN.seconds();
  176. ent->timestamp = level.time + SPAWNGROW_LIFESPAN;
  177. ent->nextthink = level.time + FRAME_TIME_MS;
  178. gi.linkentity(ent);
  179. // [Paril-KEX]
  180. edict_t *beam = ent->target_ent = G_Spawn();
  181. beam->s.modelindex = MODELINDEX_WORLD;
  182. beam->s.renderfx = RF_BEAM_LIGHTNING | RF_NO_ORIGIN_LERP;
  183. beam->s.frame = 1;
  184. beam->s.skinnum = 0x30303030;
  185. beam->classname = "spawngro_beam";
  186. beam->angle = end_size;
  187. beam->owner = ent;
  188. beam->s.origin = ent->s.origin;
  189. beam->think = SpawnGro_laser_think;
  190. beam->nextthink = level.time + 1_ms;
  191. beam->s.old_origin = SpawnGro_laser_pos(beam);
  192. gi.linkentity(beam);
  193. }
  194. // ****************************
  195. // WidowLeg stuff
  196. // ****************************
  197. constexpr int32_t MAX_LEGSFRAME = 23;
  198. constexpr gtime_t LEG_WAIT_TIME = 1_sec;
  199. void ThrowMoreStuff(edict_t *self, const vec3_t &point);
  200. void ThrowSmallStuff(edict_t *self, const vec3_t &point);
  201. void ThrowWidowGibLoc(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool fade);
  202. void ThrowWidowGibSized(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, int hitsound, bool fade);
  203. THINK(widowlegs_think) (edict_t *self) -> void
  204. {
  205. vec3_t offset;
  206. vec3_t point;
  207. vec3_t f, r, u;
  208. if (self->s.frame == 17)
  209. {
  210. offset = { 11.77f, -7.24f, 23.31f };
  211. AngleVectors(self->s.angles, f, r, u);
  212. point = G_ProjectSource2(self->s.origin, offset, f, r, u);
  213. gi.WriteByte(svc_temp_entity);
  214. gi.WriteByte(TE_EXPLOSION1);
  215. gi.WritePosition(point);
  216. gi.multicast(point, MULTICAST_ALL, false);
  217. ThrowSmallStuff(self, point);
  218. }
  219. if (self->s.frame < MAX_LEGSFRAME)
  220. {
  221. self->s.frame++;
  222. self->nextthink = level.time + 10_hz;
  223. return;
  224. }
  225. else if (self->wait == 0)
  226. {
  227. self->wait = (level.time + LEG_WAIT_TIME).seconds();
  228. }
  229. if (level.time > gtime_t::from_sec(self->wait))
  230. {
  231. AngleVectors(self->s.angles, f, r, u);
  232. offset = { -65.6f, -8.44f, 28.59f };
  233. point = G_ProjectSource2(self->s.origin, offset, f, r, u);
  234. gi.WriteByte(svc_temp_entity);
  235. gi.WriteByte(TE_EXPLOSION1);
  236. gi.WritePosition(point);
  237. gi.multicast(point, MULTICAST_ALL, false);
  238. ThrowSmallStuff(self, point);
  239. ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
  240. ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
  241. offset = { -1.04f, -51.18f, 7.04f };
  242. point = G_ProjectSource2(self->s.origin, offset, f, r, u);
  243. gi.WriteByte(svc_temp_entity);
  244. gi.WriteByte(TE_EXPLOSION1);
  245. gi.WritePosition(point);
  246. gi.multicast(point, MULTICAST_ALL, false);
  247. ThrowSmallStuff(self, point);
  248. ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
  249. ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
  250. ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
  251. G_FreeEdict(self);
  252. return;
  253. }
  254. if ((level.time > gtime_t::from_sec(self->wait - 0.5f)) && (self->count == 0))
  255. {
  256. self->count = 1;
  257. AngleVectors(self->s.angles, f, r, u);
  258. offset = { 31, -88.7f, 10.96f };
  259. point = G_ProjectSource2(self->s.origin, offset, f, r, u);
  260. gi.WriteByte(svc_temp_entity);
  261. gi.WriteByte(TE_EXPLOSION1);
  262. gi.WritePosition(point);
  263. gi.multicast(point, MULTICAST_ALL, false);
  264. // ThrowSmallStuff (self, point);
  265. offset = { -12.67f, -4.39f, 15.68f };
  266. point = G_ProjectSource2(self->s.origin, offset, f, r, u);
  267. gi.WriteByte(svc_temp_entity);
  268. gi.WriteByte(TE_EXPLOSION1);
  269. gi.WritePosition(point);
  270. gi.multicast(point, MULTICAST_ALL, false);
  271. // ThrowSmallStuff (self, point);
  272. self->nextthink = level.time + 10_hz;
  273. return;
  274. }
  275. self->nextthink = level.time + 10_hz;
  276. }
  277. void Widowlegs_Spawn(const vec3_t &startpos, const vec3_t &angles)
  278. {
  279. edict_t *ent;
  280. ent = G_Spawn();
  281. ent->s.origin = startpos;
  282. ent->s.angles = angles;
  283. ent->solid = SOLID_NOT;
  284. ent->s.renderfx = RF_IR_VISIBLE;
  285. ent->movetype = MOVETYPE_NONE;
  286. ent->classname = "widowlegs";
  287. ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2");
  288. ent->think = widowlegs_think;
  289. ent->nextthink = level.time + 10_hz;
  290. gi.linkentity(ent);
  291. }