marrub
/
Lithia
Archived
1
0
Fork 0
This repository has been archived on 2023-06-17. You can view files and clone it, but cannot push or open issues/pull-requests.
Lithia/source/Main/w_monster.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