You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

409 lines
11KB

  1. /* ---------------------------------------------------------------------------|
  2. *
  3. * Distributed under the CC0 public domain license.
  4. * By Alison Sanderson. Attribution is encouraged, though not required.
  5. * See licenses/cc0.txt for more information.
  6. *
  7. * ---------------------------------------------------------------------------|
  8. *
  9. * Monster entry points.
  10. *
  11. * ---------------------------------------------------------------------------|
  12. */
  13. #include "w_monster.h"
  14. #include "p_player.h"
  15. #include "w_world.h"
  16. #include "w_scorenums.h"
  17. #define HasResistances(m) ((m)->rank >= 2)
  18. #define GetInfo(m) \
  19. do { \
  20. (m)->ms->x = GetX(0); \
  21. (m)->ms->y = GetY(0); \
  22. (m)->ms->z = GetZ(0); \
  23. \
  24. (m)->ms->r = GetPropK(0, APROP_Radius); \
  25. (m)->ms->h = GetPropK(0, APROP_Height); \
  26. \
  27. (m)->ms->health = GetPropI(0, APROP_Health); \
  28. } while(0)
  29. /* Types ------------------------------------------------------------------- */
  30. enum {
  31. _max_rank = 5,
  32. _max_level = 150,
  33. };
  34. struct dmon_stat {
  35. k32 x, y, z;
  36. k32 r, h;
  37. i32 health;
  38. i32 painwait;
  39. bool finalized;
  40. bool resurrect;
  41. };
  42. /* Static Objects ---------------------------------------------------------- */
  43. #include "w_moninfo.h"
  44. StrAry(dmgtype_names,
  45. s"Bullets",
  46. s"Energy",
  47. s"Fire",
  48. s"Magic",
  49. s"Melee",
  50. s"Shrapnel");
  51. /* Static Functions -------------------------------------------------------- */
  52. static void ApplyLevels(dmon_t *m, i32 prev)
  53. {
  54. GetInfo(m);
  55. for(i32 i = prev + 1; i <= m->level; i++)
  56. {
  57. if(i % 10 == 0)
  58. {
  59. /* if we have resistances, randomly pick a resistance we already have */
  60. if(HasResistances(m))
  61. {
  62. i32 r;
  63. do {r = ACS_Random(0, dmgtype_max-1);} while(m->resist[r] == 0);
  64. m->resist[r] += 2;
  65. }
  66. }
  67. }
  68. if(m->level >= 5)
  69. {
  70. k32 rn = m->rank / 10.0;
  71. i96 delt = m->level - prev;
  72. i96 hp10 = m->spawnhealth / 10;
  73. i32 newh = delt * hp10 * (i96)(ACS_RandomFixed(rn - 0.1, rn + 0.1) * 0xfff) / 0xfff;
  74. Dbg_Log(log_dmonV, "monster %i: newh %i", m->id, newh);
  75. SetPropI(0, APROP_Health, m->ms->health + newh);
  76. m->maxhealth += newh;
  77. }
  78. for(i32 i = 0; i < dmgtype_max; i++) {
  79. ifauto(i32, resist, m->resist[i] / 15.0) {
  80. InvGive(StrParam(OBJ "M_%S%i", dmgtype_names[i], min(resist, _max_rank)), 1);
  81. }
  82. }
  83. }
  84. stkcall
  85. static void ShowBarrier(dmon_t const *m, k32 alpha)
  86. {
  87. bool anyplayer = false;
  88. /* Optimization: Check for players nearby first. */
  89. i32 const xw1 = m->ms->x - 192, xw2 = m->ms->x + 192;
  90. i32 const yw1 = m->ms->y - 192, yw2 = m->ms->y + 192;
  91. for_player() if(aabb(xw1, yw1, xw2, yw2, p->x, p->y))
  92. {anyplayer = true; break;}
  93. if(!anyplayer)
  94. return;
  95. BeginAngles(m->ms->x, m->ms->y);
  96. ServCallI(sm_MonsterBarrierLook);
  97. for(i32 i = 0; i < a_cur; i++)
  98. {
  99. struct polar *a = &a_angles[i];
  100. k32 dst = m->ms->r / 2 + a->dst / 4;
  101. k32 x = m->ms->x + ACS_Cos(a->ang) * dst;
  102. k32 y = m->ms->y + ACS_Sin(a->ang) * dst;
  103. i32 tid = ACS_UniqueTID();
  104. str bar = m->rank >= 5 ? so_MonsterHeptaura : so_MonsterBarrier;
  105. ACS_SpawnForced(bar, x, y, m->ms->z + m->ms->h / 2, tid);
  106. SetPropK(tid, APROP_Alpha, (1 - a->dst / (256 * (m->rank - 1))) * alpha);
  107. }
  108. }
  109. static void BaseMonsterLevel(dmon_t *m)
  110. {
  111. k32 rlv = ACS_RandomFixed(1, _max_level);
  112. k32 bias;
  113. bias = mapscleared / 40.0;
  114. bias *= bias;
  115. bias += ACS_GameSkill() / (k32)skill_nightmare * 0.1;
  116. bias += ACS_GetCVar(sc_sv_difficulty) / 100.0;
  117. bias *= ACS_RandomFixed(1, 1.5);
  118. if(m->mi->flags & mif_angelic) {
  119. m->rank = 7;
  120. m->level = 7 + rlv * bias;
  121. } else if(m->mi->flags & mif_dark) {
  122. m->rank = 6;
  123. m->level = 6 + rlv * bias;
  124. } else if(GetFun() & lfun_ragnarok) {
  125. m->rank = _max_rank;
  126. m->level = _max_level + rlv * bias;
  127. } else {
  128. k32 rrn = ACS_RandomFixed(1, _max_rank);
  129. for_player() {rlv += p->attr.level / 2.0; break;}
  130. m->rank = minmax(rrn * bias * 2, 1, _max_rank);
  131. m->level = minmax(rlv * bias , 1, _max_level);
  132. }
  133. switch(m->rank) {
  134. case 5: ServCallI(sm_SetTeleFogTo, so_TeleFog5); break;
  135. case 6: ServCallI(sm_SetTeleFogTo, so_TeleFog6); break;
  136. case 7: ServCallI(sm_SetTeleFogTo, so_TeleFog7); break;
  137. }
  138. if(HasResistances(m)) for(i32 i = 0; i < m->rank; i++)
  139. m->resist[ACS_Random(1, dmgtype_max) - 1] += 5;
  140. ApplyLevels(m, 0);
  141. }
  142. /* Spawn a Monster Soul and temporarily set the species of it until the */
  143. /* actor is no longer solid, so it won't explode immediately. */
  144. script
  145. static void SoulCleave(dmon_t *m, struct player *p)
  146. {
  147. Str(solid_s, s"SOLID");
  148. i32 tid = ACS_UniqueTID();
  149. ACS_SpawnForced(so_MonsterSoul, m->ms->x, m->ms->y, m->ms->z + 16, tid);
  150. SetPropI(tid, APROP_Damage, 7 * m->rank * ACS_Random(1, 8));
  151. PtrSet(tid, AAPTR_DEFAULT, AAPTR_TARGET, p->tid);
  152. SetPropS(tid, APROP_Species, GetPropS(0, APROP_Species));
  153. for(i32 i = 0; ACS_CheckFlag(0, solid_s) && i < 15; i++) ACS_Delay(1);
  154. SetPropS(tid, APROP_Species, so_Player);
  155. }
  156. static void SpawnManaPickup(dmon_t *m, struct player *p)
  157. {
  158. i32 i = 0;
  159. do {
  160. i32 tid = ACS_UniqueTID();
  161. i32 x = m->ms->x + ACS_Random(-16, 16);
  162. i32 y = m->ms->y + ACS_Random(-16, 16);
  163. ACS_Spawn(so_ManaPickup, x, y, m->ms->z + 4, tid);
  164. PtrSet(tid, AAPTR_DEFAULT, AAPTR_TRACER, p->tid);
  165. PtrSet(tid, AAPTR_DEFAULT, AAPTR_TARGET, p->tid);
  166. i += 150;
  167. } while(i < m->maxhealth);
  168. }
  169. static void OnFinalize(dmon_t *m) {
  170. with_player(P_PtrFind(0, AAPTR_TARGET)) {
  171. if(p->sgacquired) {
  172. bool high_level_imp =
  173. m->mi->type == mtype_imp && m->level >= 70 && m->rank >= 4;
  174. if(high_level_imp && ACS_Random(0, 100) < 4)
  175. ACS_SpawnForced(so_ClawOfImp, m->ms->x, m->ms->y, m->ms->z);
  176. }
  177. if(!m->ms->finalized) {
  178. if(p->getUpgrActive(UPGR_Magic) && p->mana != p->manamax &&
  179. (m->mi->type != mtype_zombie || ACS_Random(0, 50) < 10)) {
  180. SpawnManaPickup(m, p);
  181. }
  182. if(ACS_GetCVar(sc_sv_wepdrop)) {
  183. Str(sgun, s"Shotgun");
  184. Str(cgun, s"Chaingun");
  185. str sp = snil;
  186. switch(m->mi->type) {
  187. case mtype_zombiesg: if(!p->weapon.slot[3]) sp = sgun; break;
  188. case mtype_zombiecg: if(!p->weapon.slot[4]) sp = cgun; break;
  189. }
  190. if(sp) {
  191. Str(dropped_s, s"DROPPED");
  192. i32 tid = ACS_UniqueTID();
  193. ACS_SpawnForced(sp, m->ms->x, m->ms->y, m->ms->z, tid);
  194. ACS_SetActorFlag(tid, dropped_s, false);
  195. }
  196. }
  197. if(p->getUpgrActive(UPGR_SoulCleaver))
  198. SoulCleave(m, p);
  199. }
  200. if(p->health < 5) P_Lv_GiveEXP(p, 50);
  201. else if(p->health < 15) P_Lv_GiveEXP(p, 25);
  202. else if(p->health < 25) P_Lv_GiveEXP(p, 10);
  203. P_GiveAllEXP(m->mi->exp + m->level + (m->rank - 1) * 10);
  204. }
  205. m->ms->finalized = true;
  206. }
  207. static void OnDeath(dmon_t *m)
  208. {
  209. Dbg_Log(log_dmon, "monster %i is ded", m->id);
  210. m->wasdead = true;
  211. OnFinalize(m);
  212. P_GiveAllScore(m->mi->score + m->rank * 500, false);
  213. }
  214. script
  215. static void MonsterMain(dmon_t *m)
  216. {
  217. struct dmon_stat ms = {};
  218. InvGive(so_MonsterID, m->id + 1);
  219. m->ms = &ms;
  220. GetInfo(m);
  221. m->spawnhealth = m->maxhealth = m->ms->health;
  222. BaseMonsterLevel(m);
  223. Dbg_Log(log_dmonV, "monster %-4i \Cdr%i \Cgl%-3i \C-running on %S",
  224. m->id, m->rank, m->level, ACS_GetActorClass(0));
  225. for(i32 tic = 0;; tic++)
  226. {
  227. GetInfo(m);
  228. if(m->ms->health <= 0)
  229. {
  230. OnDeath(m);
  231. do {ACS_Delay(15);} while(!m->ms->resurrect);
  232. m->ms->resurrect = false;
  233. Dbg_Log(log_dmon, "monster %i resurrected", m->id);
  234. }
  235. if(m->exp > 500)
  236. {
  237. i32 prev = m->level;
  238. div_t d = __div(m->exp, 500);
  239. m->level += d.quot;
  240. m->exp = d.rem;
  241. ACS_SpawnForced(so_MonsterLevelUp, m->ms->x, m->ms->y, m->ms->z);
  242. ApplyLevels(m, prev);
  243. Dbg_Log(log_dmon, "monster %i leveled up (%i -> %i)", m->id, prev, m->level);
  244. }
  245. if(HasResistances(m) && m->level >= 20)
  246. ShowBarrier(m, m->level / 100.);
  247. if(InvNum(so_Ionized) && tic % 5 == 0)
  248. ServCallI(sm_IonizeFX);
  249. ACS_Delay(2);
  250. }
  251. }
  252. /* Extern Functions -------------------------------------------------------- */
  253. void PrintMonsterInfo(void)
  254. {
  255. ifauto(dmon_t *, m, DmonPtr(0, AAPTR_PLAYER_GETTARGET))
  256. {
  257. Log("%p (%p %p) %S active: %u id: %.3u\n"
  258. "wasdead: %u finalized: %u painwait: %i\n"
  259. "level: %.3i rank: %i exp: %i\n"
  260. "health: %i/%i\n"
  261. "x: %k y: %k z: %k\n"
  262. "r: %k h: %k\n"
  263. "mi->exp: %lu mi->score: %lli\n"
  264. "mi->flags: %i mi->type: %i",
  265. m, m->ms, m->mi, m->mi->name, m->active, m->id,
  266. m->wasdead, m->ms->finalized, m->ms->painwait,
  267. m->level, m->rank, m->exp,
  268. m->ms->health, m->maxhealth,
  269. m->ms->x, m->ms->y, m->ms->z,
  270. m->ms->r, m->ms->h,
  271. m->mi->exp, m->mi->score,
  272. m->mi->flags, m->mi->type);
  273. for(i32 i = 0; i < countof(m->resist); i++)
  274. Log("resist %S: %i", dmgtype_names[i], m->resist[i]);
  275. }
  276. else
  277. Log("no active monster");
  278. }
  279. /* Scripts ----------------------------------------------------------------- */
  280. script ext("ACS") addr(lsc_monstertype)
  281. i32 Sc_GetMonsterType()
  282. {
  283. ifauto(dmon_t *, m, DmonSelf()) return m->mi->type;
  284. else return mtype_unknown;
  285. }
  286. script_str ext("ACS") addr("Lith_GiveMonsterEXP")
  287. void Sc_GiveMonsterEXP(i32 amt)
  288. {
  289. ifauto(dmon_t *, m, DmonSelf()) m->exp += amt;
  290. }
  291. script_str ext("ACS") addr("Lith_ResurrectMonster")
  292. void Sc_ResurrectMonster(i32 amt)
  293. {
  294. ifauto(dmon_t *, m, DmonSelf()) m->ms->resurrect = true;
  295. }
  296. script ext("ACS") addr(lsc_monsterinfo)
  297. void Sc_MonsterInfo(void)
  298. {
  299. Str(rladaptive, s"RLAdaptive");
  300. Str(rlhax, s"RLCyberdemonMkII");
  301. str cname = ACS_GetActorClass(0);
  302. if(strstr_str(cname, rladaptive) || strstr_str(cname, rlhax))
  303. return;
  304. for(i32 i = 0; i < countof(monsterinfo); i++) {
  305. struct monster_info const *mi = &monsterinfo[i];
  306. bool init;
  307. if(mi->flags & mif_full) init = cname == mi->name;
  308. else init = strstr_str(cname, mi->name);
  309. if(init) {
  310. ifauto(dmon_t *, m, AllocDmon()) {
  311. m->mi = mi;
  312. MonsterMain(m);
  313. }
  314. return;
  315. }
  316. }
  317. Dbg_Log(log_dmon, "no monster %S", cname);
  318. /* If the monster failed all checks, give them this so we don't
  319. need to recheck every tick.
  320. Edit: This isn't necessary anymore, but what the hell, keep it. */
  321. InvGive(so_MonsterInvalid, 1);
  322. }
  323. script_str ext("ACS") addr("Lith_MonsterFinalized")
  324. void Sc_MonsterFinalized(void)
  325. {
  326. ifauto(dmon_t *, m, DmonSelf())
  327. OnFinalize(m);
  328. }
  329. /* EOF */