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.

395 line
10KB

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