507 lines
16 KiB
C
507 lines
16 KiB
C
// Copyright © 2016-2017 Graham Sanderson, all rights reserved.
|
|
#include "lith_monster.h"
|
|
#include "lith_player.h"
|
|
#include "lith_world.h"
|
|
#include "lith_scorenums.h"
|
|
|
|
#include <math.h>
|
|
|
|
#define HasResistances(m) ((m)->rank >= 2)
|
|
|
|
#define GetInfo(m) \
|
|
do { \
|
|
(m)->ms->x = ACS_GetActorX(0); \
|
|
(m)->ms->y = ACS_GetActorY(0); \
|
|
(m)->ms->z = ACS_GetActorZ(0); \
|
|
\
|
|
(m)->ms->r = ACS_GetActorPropertyFixed(0, APROP_Radius); \
|
|
(m)->ms->h = ACS_GetActorPropertyFixed(0, APROP_Height); \
|
|
\
|
|
(m)->ms->health = ACS_GetActorProperty(0, APROP_Health); \
|
|
} while(0)
|
|
|
|
// Types ---------------------------------------------------------------------|
|
|
|
|
struct dmon_stat {
|
|
fixed x, y, z;
|
|
fixed r, h;
|
|
int health;
|
|
int painwait;
|
|
bool finalized;
|
|
};
|
|
|
|
struct monster_info {
|
|
u64 exp;
|
|
i96 score;
|
|
enum mtype type;
|
|
__str name;
|
|
int flags;
|
|
};
|
|
|
|
enum {
|
|
mif_full = 1 << 0
|
|
};
|
|
|
|
// Static Objects ------------------------------------------------------------|
|
|
|
|
#define M(name) Exp_##name, Score_##name
|
|
static struct monster_info const monsterinfo[] = {
|
|
#if 0
|
|
// Hexen
|
|
{9999, Score_ShotgunGuy, mtype_imp, "Ettin", mif_full},
|
|
{9999, Score_Imp, mtype_lostsoul, "FireDemon", mif_full},
|
|
{9999, Score_Arachnotron, mtype_arachnotron, "CentaurLeader", mif_full},
|
|
{9999, Score_Demon, mtype_demon, "Centaur", mif_full},
|
|
{9999, Score_Mancubus, mtype_hellknight, "IceGuy", mif_full},
|
|
{9999, Score_Mancubus, mtype_hellknight, "SerpentLeader", mif_full},
|
|
{9999, Score_Arachnotron, mtype_hellknight, "Serpent", mif_full},
|
|
{9999, Score_HellKnight, mtype_hellknight, "Demon1", mif_full},
|
|
{9999, Score_BaronOfHell, mtype_baron, "Demon2", mif_full},
|
|
{9999, Score_Cacodemon, mtype_mancubus, "Bishop", mif_full},
|
|
{9999, Score_HellKnight, mtype_lostsoul, "Wraith", mif_full},
|
|
{9999, Score_CyberDemon, mtype_cyberdemon, "Dragon", mif_full},
|
|
{9999, Score_CyberDemon, mtype_phantom, "ClericBoss", mif_full},
|
|
{9999, Score_CyberDemon, mtype_phantom, "FighterBoss", mif_full},
|
|
{9999, Score_CyberDemon, mtype_phantom, "MageBoss", mif_full},
|
|
{9999, Score_CyberDemon, mtype_cyberdemon, "Heresiarch", mif_full},
|
|
{9999, Score_DSparil * 2, mtype_cyberdemon, "Korax", mif_full},
|
|
#endif
|
|
|
|
// Doom 2
|
|
{M(ZombieMan), mtype_zombie, "ZombieMan" },
|
|
{M(ShotgunGuy), mtype_zombie, "ShotgunGuy" },
|
|
{M(ChaingunGuy), mtype_zombie, "ChaingunGuy" },
|
|
{M(Imp), mtype_imp, "Imp" },
|
|
{M(Demon), mtype_demon, "Demon" },
|
|
{M(Spectre), mtype_demon, "Spectre" },
|
|
{M(LostSoul), mtype_lostsoul, "LostSoul" },
|
|
{M(Mancubus), mtype_mancubus, "Fatso" },
|
|
{M(Mancubus), mtype_mancubus, "Mancubus" },
|
|
{M(Arachnotron), mtype_arachnotron, "Arachnotron" },
|
|
{M(Cacodemon), mtype_cacodemon, "Cacodemon" },
|
|
{M(HellKnight), mtype_hellknight, "Knight" },
|
|
{M(BaronOfHell), mtype_baron, "Baron" },
|
|
{M(Revenant), mtype_revenant, "Revenant" },
|
|
{M(PainElemental), mtype_painelemental, "PainElemental" },
|
|
{M(Archvile), mtype_archvile, "Archvile" },
|
|
{M(SpiderDemon), mtype_mastermind, "SpiderMastermind"},
|
|
{M(CyberDemon), mtype_cyberdemon, "Cyberdemon" },
|
|
|
|
// Heretic
|
|
{M(Imp), mtype_imp, "Gargoyle" },
|
|
{M(Demon), mtype_demon, "Golem" },
|
|
{M(Nitrogolem), mtype_demon, "Nitrogolem"},
|
|
{M(Spectre), mtype_demon, "Sabreclaw" },
|
|
{M(Cacodemon), mtype_mancubus, "Disciple" },
|
|
{M(Ophidian), mtype_arachnotron, "Ophidian" },
|
|
{M(HellKnight), mtype_hellknight, "Warrior" },
|
|
{M(BaronOfHell), mtype_cacodemon, "IronLich" },
|
|
{M(Maulotaur), mtype_mastermind, "Maulotaur" },
|
|
{M(DSparil), mtype_cyberdemon, "DSparil" },
|
|
|
|
// Lithium
|
|
{1000, 0, mtype_phantom, "James" },
|
|
{2000, 0, mtype_phantom, "Makarov" },
|
|
{3000, 0, mtype_phantom, "Isaac" },
|
|
{0, 0, mtype_cyberdemon, "Steggles"},
|
|
|
|
// Cheogsh.wad
|
|
{M(Imp), mtype_imp, "Howler", mif_full},
|
|
{M(Imp), mtype_imp, "SoulHarvester", mif_full},
|
|
{M(Demon), mtype_demon, "Satyr", mif_full},
|
|
{M(BaronOfHell), mtype_baron, "HellWarrior", mif_full},
|
|
{M(CyberDemon), mtype_cyberdemon, "Cheogsh", mif_full},
|
|
{M(Spectre), mtype_demon, "MaulerDemon", mif_full},
|
|
{M(Spectre), mtype_demon, "KDiZDNightmare", mif_full},
|
|
|
|
// ed4_rfo1.wad
|
|
{M(CyberDemon), mtype_cacodemon, "RealAbstractPain", mif_full},
|
|
{M(Arachnotron), mtype_arachnotron, "RailSpider", mif_full},
|
|
{M(PainElemental), mtype_painelemental, "EDPE", mif_full},
|
|
{M(Arachnotron), mtype_arachnotron, "Arachnophyte", mif_full},
|
|
{M(Imp), mtype_imp, "DragonFamiliar", mif_full},
|
|
{M(Imp), mtype_imp, "BatFamiliar", mif_full},
|
|
{M(Imp), mtype_imp, "SnakeImp", mif_full},
|
|
{M(DRLACaptain), mtype_zombie, "BazookaZombie", mif_full},
|
|
{M(Imp), mtype_imp, "DarkImp", mif_full},
|
|
{M(Imp), mtype_imp, "ImpWarlord", mif_full},
|
|
|
|
// dop.pk3
|
|
{M(LostSoul), mtype_lostsoul, "LostGhoul", mif_full},
|
|
|
|
// DoomRL Arsenal Monsters
|
|
{M(ZombieMan), mtype_zombie, "FormerHuman" },
|
|
{M(ShotgunGuy), mtype_zombie, "FormerSergeant"},
|
|
{M(ChaingunGuy), mtype_zombie, "FormerCommando"},
|
|
{M(DRLACaptain), mtype_zombie, "Former"}, // hue
|
|
|
|
// Colorful Hell
|
|
{M(ZombieMan), mtype_zombie, "Zombie"},
|
|
{M(ShotgunGuy), mtype_zombie, "SG" },
|
|
{M(ChaingunGuy), mtype_zombie, "CGuy" },
|
|
{M(LostSoul), mtype_lostsoul, "LSoul" },
|
|
{M(HellKnight), mtype_hellknight, "HK" },
|
|
{M(Arachnotron), mtype_arachnotron, "SP1" },
|
|
{M(Cacodemon), mtype_cacodemon, "Caco" },
|
|
{M(Archvile), mtype_archvile, "Arch" },
|
|
{M(PainElemental), mtype_painelemental, "PE" },
|
|
{M(SpiderDemon), mtype_mastermind, "Mind" },
|
|
{M(CyberDemon), mtype_cyberdemon, "Cybie" },
|
|
|
|
// Shut Up and Lithium
|
|
{M(ZombieMan), mtype_imp, "Roach", mif_full},
|
|
{M(ZombieMan), mtype_imp, "Remnant", mif_full},
|
|
{M(Imp), mtype_imp, "Turmoil", mif_full},
|
|
{M(ShotgunGuy), mtype_imp, "Pitkis", mif_full},
|
|
{M(ChaingunGuy), mtype_imp, "Helliate", mif_full},
|
|
{M(Demon), mtype_demon, "Satyr", mif_full},
|
|
{M(Spectre), mtype_demon, "Mush", mif_full},
|
|
{M(LostSoul), mtype_lostsoul, "Gsoul", mif_full},
|
|
{M(Cacodemon), mtype_cacodemon, "Accuser", mif_full},
|
|
{M(Revenant), mtype_revenant, "Famine", mif_full},
|
|
{M(PainElemental), mtype_arachnotron, "Writhe", mif_full},
|
|
{M(HellKnight), mtype_hellknight, "Sonnelion", mif_full},
|
|
{M(LostSoul), mtype_lostsoul, "Eotu", mif_full},
|
|
{M(Arachnotron), mtype_arachnotron, "Lolth", mif_full},
|
|
{M(Mancubus), mtype_mancubus, "Malbouge", mif_full},
|
|
{M(BaronOfHell), mtype_baron, "Abaddon", mif_full},
|
|
{M(Archvile), mtype_archvile, "Pestilence", mif_full},
|
|
{M(SpiderDemon), mtype_mastermind, "Granfalloon", mif_full},
|
|
{M(CyberDemon), mtype_cyberdemon, "SoF", mif_full},
|
|
{M(LostSoul), mtype_lostsoul, "Karkass"},
|
|
{M(HellKnight), mtype_imp, "Doorman", mif_full},
|
|
{M(LostSoul), mtype_lostsoul, "UnbodiedFury", mif_full},
|
|
{M(ShotgunGuy), mtype_imp, "Bloodfiend", mif_full},
|
|
{M(Demon), mtype_demon, "Golem", mif_full},
|
|
{M(Demon), mtype_demon, "Phantasm", mif_full},
|
|
{M(Demon), mtype_demon, "Mortuus", mif_full},
|
|
{M(ChaingunGuy), mtype_imp, "Malum", mif_full},
|
|
{M(ShotgunGuy), mtype_imp, "Phasma", mif_full},
|
|
{M(Spectre), mtype_demon, "Licho", mif_full},
|
|
{M(Cacodemon), mtype_cacodemon, "Mandingo", mif_full},
|
|
{M(Cacodemon), mtype_cacodemon, "Hierophant", mif_full},
|
|
{M(CyberDemon), mtype_cyberdemon, "Burrower", mif_full},
|
|
{M(Archvile), mtype_arachnotron, "Aranearum", mif_full},
|
|
{M(Cacodemon), mtype_cacodemon, "Lividus", mif_full},
|
|
{M(HellKnight), mtype_hellknight, "Deepone", mif_full},
|
|
{M(BaronOfHell), mtype_baron, "Deeptwo", mif_full},
|
|
};
|
|
#undef M
|
|
|
|
static __str const dmgtype_names[dmgtype_max] = {
|
|
"Bullets",
|
|
"Energy",
|
|
"Fire",
|
|
"Magic",
|
|
"Melee",
|
|
"Shrapnel"
|
|
};
|
|
|
|
// Static Functions ----------------------------------------------------------|
|
|
|
|
//
|
|
// ApplyLevels
|
|
//
|
|
static void ApplyLevels(dmon_t *m, int prev)
|
|
{
|
|
GetInfo(m);
|
|
|
|
for(int i = prev + 1; i <= m->level; i++)
|
|
{
|
|
if(i % 10 == 0)
|
|
{
|
|
// if we have resistances, randomly pick a resistance we already have
|
|
if(HasResistances(m))
|
|
{
|
|
int r;
|
|
do {r = ACS_Random(0, dmgtype_max-1);} while(m->resist[r] == 0);
|
|
m->resist[r] += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m->level >= 5)
|
|
{
|
|
float rn = m->rank / 10.f;
|
|
int hp10 = m->maxhealth / 10;
|
|
int newh = (m->level - prev) * hp10 * RandomFloat(rn - .1f, rn + .1f);
|
|
ACS_SetActorProperty(0, APROP_Health, m->ms->health + newh);
|
|
m->maxhealth += newh;
|
|
}
|
|
|
|
for(int i = 0; i < dmgtype_max; i++) {
|
|
ifauto(int, resist, m->resist[i] / 15.0) {
|
|
InvGive(StrParam("Lith_M_%S%i", dmgtype_names[i],
|
|
min(resist, MAXRANK)), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// ShowBarrier
|
|
//
|
|
stkcall static void ShowBarrier(dmon_t const *m, fixed alpha)
|
|
{
|
|
bool anyplayer = false;
|
|
|
|
// Optimization: Check for players nearby first.
|
|
int const xw1 = m->ms->x - 192, xw2 = m->ms->x + 192;
|
|
int const yw1 = m->ms->y - 192, yw2 = m->ms->y + 192;
|
|
|
|
Lith_ForPlayer() if(aabb(xw1, yw1, xw2, yw2, p->x, p->y))
|
|
{anyplayer = true; break;}
|
|
|
|
if(!anyplayer)
|
|
return;
|
|
|
|
world.begAngles(m->ms->x, m->ms->y);
|
|
ACS_GiveInventory("Lith_MonsterBarrierLook", 1);
|
|
|
|
for(int i = 0; i < world.a_cur; i++)
|
|
{
|
|
struct polar *a = &world.a_angles[i];
|
|
|
|
fixed dst = m->ms->r / 2 + a->dst / 4;
|
|
fixed x = m->ms->x + ACS_Cos(a->ang) * dst;
|
|
fixed y = m->ms->y + ACS_Sin(a->ang) * dst;
|
|
int tid = ACS_UniqueTID();
|
|
__str bar = m->rank >= 5 ? "Lith_MonsterHeptaura" : "Lith_MonsterBarrier";
|
|
|
|
ACS_SpawnForced(bar, x, y, m->ms->z + m->ms->h / 2, tid);
|
|
ACS_SetActorPropertyFixed(tid, APROP_Alpha,
|
|
(1 - a->dst / (256 * (m->rank - 1))) * alpha);
|
|
}
|
|
}
|
|
|
|
//
|
|
// BaseMonsterLevel
|
|
//
|
|
static void BaseMonsterLevel(dmon_t *m)
|
|
{
|
|
fixed rn1 = ACS_RandomFixed(1, MAXRANK);
|
|
fixed rn2 = ACS_RandomFixed(1, MAXLEVEL);
|
|
fixed bias;
|
|
|
|
switch(world.game) {
|
|
case Game_Episodic: bias = world.mapscleared / 10.0; break;
|
|
default: bias = world.mapscleared / 40.0; break;
|
|
}
|
|
|
|
Lith_ForPlayer() {
|
|
rn2 += p->attr.level / 2.0;
|
|
break;
|
|
}
|
|
|
|
bias *= bias;
|
|
bias += (ACS_GameSkill() / (fixed)skill_nightmare) * 0.1;
|
|
bias += world.difficulty / 100.0;
|
|
bias *= ACS_RandomFixed(1, 1.5);
|
|
|
|
m->rank = minmax(rn1 * bias * 2, 1, MAXRANK);
|
|
m->level = minmax(rn2 * bias * 1, 1, MAXLEVEL);
|
|
|
|
if(HasResistances(m)) {
|
|
for(int i = 0; i < m->rank; i++)
|
|
m->resist[ACS_Random(1, dmgtype_max)-1] += 5;
|
|
}
|
|
|
|
ApplyLevels(m, 0);
|
|
}
|
|
|
|
//
|
|
// ApplyPainResist
|
|
//
|
|
static void ApplyPainResist(dmon_t *m)
|
|
{
|
|
if(!m->ms->painwait) {
|
|
ACS_GiveInventory("Lith_MonsterUsePain", 1);
|
|
m->ms->painwait = m->painresist;
|
|
} else {
|
|
ACS_GiveInventory("Lith_MonsterNoPain", 1);
|
|
m->ms->painwait--;
|
|
}
|
|
}
|
|
|
|
//
|
|
// SoulCleave
|
|
//
|
|
// Spawn a Monster Soul and temporarily set the species of it until the
|
|
// actor is no longer solid, so it won't explode immediately.
|
|
//
|
|
script static void SoulCleave(dmon_t *m, struct player *p)
|
|
{
|
|
int tid = ACS_UniqueTID();
|
|
ACS_SpawnForced("Lith_MonsterSoul", m->ms->x, m->ms->y, m->ms->z + 16, tid);
|
|
ACS_SetActorProperty(tid, APROP_Damage, 7 * m->rank * ACS_Random(1, 8));
|
|
|
|
Lith_SetPointer(tid, AAPTR_DEFAULT, AAPTR_TARGET, p->tid);
|
|
ACS_SetActorPropertyString(tid, APROP_Species,
|
|
ACS_GetActorPropertyString(0, APROP_Species));
|
|
|
|
for(int i = 0; ACS_CheckFlag(0, "SOLID") && i < 15; i++)
|
|
ACS_Delay(1);
|
|
|
|
ACS_SetActorPropertyString(tid, APROP_Species, "Lith_Player");
|
|
}
|
|
|
|
//
|
|
// SpawnManaPickup
|
|
//
|
|
static void SpawnManaPickup(dmon_t *m, struct player *p)
|
|
{
|
|
int i = 0;
|
|
do {
|
|
int tid = ACS_UniqueTID();
|
|
int x = m->ms->x + ACS_Random(-8, 8);
|
|
int y = m->ms->y + ACS_Random(-8, 8);
|
|
ACS_Spawn("Lith_ManaPickup", x, y, m->ms->z + 4, tid);
|
|
Lith_SetPointer(tid, AAPTR_DEFAULT, AAPTR_TRACER, p->tid);
|
|
Lith_SetPointer(tid, AAPTR_DEFAULT, AAPTR_TARGET, p->tid);
|
|
i += 150;
|
|
} while(i < m->maxhealth);
|
|
}
|
|
|
|
//
|
|
// OnFinalize
|
|
//
|
|
static void OnFinalize(dmon_t *m)
|
|
{
|
|
withplayer(Lith_GetPlayer(0, AAPTR_TARGET))
|
|
{
|
|
if(p->sigil.acquired)
|
|
{
|
|
if(p->weapon.cur->info->type == weapon_c_starshot && rand() == 1)
|
|
ACS_Teleport_EndGame();
|
|
|
|
if(m->type == mtype_imp && m->level >= 50 && m->rank >= 4)
|
|
ACS_SpawnForced("Lith_ClawOfImp", m->ms->x, m->ms->y, m->ms->z);
|
|
}
|
|
|
|
if(!m->ms->finalized)
|
|
{
|
|
if(p->getUpgrActive(UPGR_Magic) && p->mana != p->manamax &&
|
|
(m->type != mtype_zombie || ACS_Random(0, 50) < 10))
|
|
{
|
|
SpawnManaPickup(m, p);
|
|
}
|
|
|
|
if(p->getUpgrActive(UPGR_SoulCleaver))
|
|
SoulCleave(m, p);
|
|
}
|
|
|
|
if(p->health < 5) p->giveEXP(50);
|
|
else if(p->health < 15) p->giveEXP(25);
|
|
else if(p->health < 25) p->giveEXP(10);
|
|
|
|
Lith_GiveAllEXP(m->exp + m->level + (m->rank - 1) * 10);
|
|
}
|
|
|
|
m->ms->finalized = true;
|
|
}
|
|
|
|
//
|
|
// OnDeath
|
|
//
|
|
static void OnDeath(dmon_t *m)
|
|
{
|
|
LogDebug(log_dmon, "monster %i is ded", m->id);
|
|
m->wasdead = true;
|
|
|
|
OnFinalize(m);
|
|
|
|
// If enemies emit score on death we only need to give extra rank score.
|
|
Lith_GiveAllScore((world.enemycompat ? 0 : m->score) + m->rank * 500, false);
|
|
}
|
|
|
|
// Extern Functions ----------------------------------------------------------|
|
|
|
|
//
|
|
// Lith_MonsterMain
|
|
//
|
|
script stksize(0x7f) void Lith_MonsterMain(dmon_t *m)
|
|
{
|
|
struct dmon_stat ms = {};
|
|
|
|
InvGive("Lith_MonsterID", m->id + 1);
|
|
|
|
m->ms = &ms;
|
|
GetInfo(m);
|
|
m->maxhealth = m->ms->health;
|
|
|
|
BaseMonsterLevel(m);
|
|
|
|
LogDebug(log_dmonV, "monster %-4i \Cdr%i \Cgl%-3i \C-running on %S",
|
|
m->id, m->rank, m->level, ACS_GetActorClass(0));
|
|
|
|
for(int tic = 0;; tic++)
|
|
{
|
|
GetInfo(m);
|
|
|
|
if(m->ms->health <= 0)
|
|
{
|
|
OnDeath(m);
|
|
|
|
do {ACS_Delay(3); GetInfo(m);} while(m->ms->health <= 0);
|
|
|
|
LogDebug(log_dmon, "monster %i resurrected", m->id);
|
|
}
|
|
|
|
if(HasResistances(m) && m->level >= 20)
|
|
ShowBarrier(m, m->level / (fixed)MAXLEVEL);
|
|
|
|
if(ACS_CheckInventory("Lith_Ionized") && tic % 5 == 0)
|
|
ACS_GiveInventory("Lith_IonizedFXSpawner", 1);
|
|
|
|
if(m->painresist) ApplyPainResist(m);
|
|
|
|
ACS_Delay(2);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Lith_MonsterInfo
|
|
//
|
|
script acs void Lith_MonsterInfo(int tid)
|
|
{
|
|
if(tid) ACS_SetActivator(tid);
|
|
while(!world.gsinit) ACS_Delay(1);
|
|
|
|
__str cname = ACS_GetActorClass(0);
|
|
|
|
for(int i = 0; i < countof(monsterinfo); i++)
|
|
{
|
|
struct monster_info const *mi = &monsterinfo[i];
|
|
bool init;
|
|
|
|
if(mi->flags & mif_full) init = cname == mi->name;
|
|
else init = strstr_str(cname, mi->name);
|
|
|
|
if(init)
|
|
{
|
|
ifauto(dmon_t *, m, AllocDmon()) {
|
|
m->type = mi->type;
|
|
m->score = mi->score;
|
|
m->exp = mi->exp;
|
|
Lith_MonsterMain(m);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
LogDebug(log_dmon, "no monster %S", cname);
|
|
|
|
// If the monster failed all checks, give them this so we don't need to recheck every tick.
|
|
InvGive("Lith_MonsterInvalid", 1);
|
|
}
|
|
|
|
//
|
|
// Lith_MonsterFinalized
|
|
//
|
|
script acs void Lith_MonsterFinalized()
|
|
{
|
|
ifauto(dmon_t *, m, DmonPtr())
|
|
OnFinalize(m);
|
|
}
|
|
|
|
// EOF
|