qzdoom/src/g_doom/a_scriptedmarine.cpp
Randy Heit 7fb0d37d99 - Added a flag to A_M_Refire to ignore the actor's missile state, so the rocket marine will
not continue to punch air if it starts attacking while its target is in melee range and
  then moves out of it.

SVN r4021 (trunk)
2013-01-06 04:15:28 +00:00

614 lines
15 KiB
C++

/*
#include "actor.h"
#include "p_enemy.h"
#include "a_action.h"
#include "m_random.h"
#include "p_local.h"
#include "a_doomglobal.h"
#include "s_sound.h"
#include "r_data/r_translate.h"
#include "thingdef/thingdef.h"
#include "g_level.h"
*/
#define MARINE_PAIN_CHANCE 160
static FRandom pr_m_refire ("SMarineRefire");
static FRandom pr_m_punch ("SMarinePunch");
static FRandom pr_m_gunshot ("SMarineGunshot");
static FRandom pr_m_saw ("SMarineSaw");
static FRandom pr_m_fireshotgun2 ("SMarineFireSSG");
IMPLEMENT_CLASS (AScriptedMarine)
void AScriptedMarine::Serialize (FArchive &arc)
{
Super::Serialize (arc);
if (arc.IsStoring ())
{
arc.WriteSprite (SpriteOverride);
}
else
{
SpriteOverride = arc.ReadSprite ();
}
arc << CurrentWeapon;
}
void AScriptedMarine::Activate (AActor *activator)
{
if (flags2 & MF2_DORMANT)
{
flags2 &= ~MF2_DORMANT;
tics = 1;
}
}
void AScriptedMarine::Deactivate (AActor *activator)
{
if (!(flags2 & MF2_DORMANT))
{
flags2 |= MF2_DORMANT;
tics = -1;
}
}
bool AScriptedMarine::GetWeaponStates(int weap, FState *&melee, FState *&missile)
{
static ENamedName WeaponNames[] =
{
NAME_None,
NAME_Fist,
NAME_Berserk,
NAME_Chainsaw,
NAME_Pistol,
NAME_Shotgun,
NAME_SSG,
NAME_Chaingun,
NAME_Rocket,
NAME_Plasma,
NAME_Railgun,
NAME_BFG
};
if (weap < WEAPON_Dummy || weap > WEAPON_BFG) weap = WEAPON_Dummy;
melee = FindState(NAME_Melee, WeaponNames[weap], true);
missile = FindState(NAME_Missile, WeaponNames[weap], true);
return melee != NULL || missile != NULL;
}
void AScriptedMarine::BeginPlay ()
{
Super::BeginPlay ();
// Set the current weapon
for(int i=WEAPON_Dummy; i<=WEAPON_BFG; i++)
{
FState *melee, *missile;
if (GetWeaponStates(i, melee, missile))
{
if (melee == MeleeState && missile == MissileState)
{
CurrentWeapon = i;
}
}
}
}
void AScriptedMarine::Tick ()
{
Super::Tick ();
// Override the standard sprite, if desired
if (SpriteOverride != 0 && sprite == SpawnState->sprite)
{
sprite = SpriteOverride;
}
if (special1 != 0)
{
if (CurrentWeapon == WEAPON_SuperShotgun)
{ // Play SSG reload sounds
int ticks = level.maptime - special1;
if (ticks < 47)
{
switch (ticks)
{
case 14:
S_Sound (this, CHAN_WEAPON, "weapons/sshoto", 1, ATTN_NORM);
break;
case 28:
S_Sound (this, CHAN_WEAPON, "weapons/sshotl", 1, ATTN_NORM);
break;
case 41:
S_Sound (this, CHAN_WEAPON, "weapons/sshotc", 1, ATTN_NORM);
break;
}
}
else
{
special1 = 0;
}
}
else
{ // Wait for a long refire time
if (level.maptime >= special1)
{
special1 = 0;
}
else
{
flags |= MF_JUSTATTACKED;
}
}
}
}
//============================================================================
//
// A_M_Refire
//
//============================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Refire)
{
ACTION_PARAM_START(1);
ACTION_PARAM_BOOL(ignoremissile, 0);
if (self->target == NULL || self->target->health <= 0)
{
if (self->MissileState && pr_m_refire() < 160)
{ // Look for a new target most of the time
if (P_LookForPlayers (self, true, NULL) && P_CheckMissileRange (self))
{ // Found somebody new and in range, so don't stop shooting
return;
}
}
self->SetState (self->state + 1);
return;
}
if (((ignoremissile || self->MissileState == NULL) && !self->CheckMeleeRange ()) ||
!P_CheckSight (self, self->target) ||
pr_m_refire() < 4) // Small chance of stopping even when target not dead
{
self->SetState (self->state + 1);
}
}
//============================================================================
//
// A_M_SawRefire
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_SawRefire)
{
if (self->target == NULL || self->target->health <= 0)
{
self->SetState (self->state + 1);
return;
}
if (!self->CheckMeleeRange ())
{
self->SetState (self->state + 1);
}
}
//============================================================================
//
// A_MarineNoise
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_MarineNoise)
{
if (static_cast<AScriptedMarine *>(self)->CurrentWeapon == AScriptedMarine::WEAPON_Chainsaw)
{
S_Sound (self, CHAN_WEAPON, "weapons/sawidle", 1, ATTN_NORM);
}
}
//============================================================================
//
// A_MarineChase
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_MarineChase)
{
CALL_ACTION(A_MarineNoise, self);
A_Chase (self);
}
//============================================================================
//
// A_MarineLook
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_MarineLook)
{
CALL_ACTION(A_MarineNoise, self);
CALL_ACTION(A_Look, self);
}
//============================================================================
//
// A_M_Saw
//
//============================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Saw)
{
ACTION_PARAM_START(4);
ACTION_PARAM_SOUND(fullsound, 0);
ACTION_PARAM_SOUND(hitsound, 1);
ACTION_PARAM_INT(damage, 2);
ACTION_PARAM_CLASS(pufftype, 3);
if (self->target == NULL)
return;
if (pufftype == NULL) pufftype = PClass::FindClass(NAME_BulletPuff);
if (damage == 0) damage = 2;
A_FaceTarget (self);
if (self->CheckMeleeRange ())
{
angle_t angle;
AActor *linetarget;
damage *= (pr_m_saw()%10+1);
angle = self->angle + (pr_m_saw.Random2() << 18);
P_LineAttack (self, angle, MELEERANGE+1,
P_AimLineAttack (self, angle, MELEERANGE+1, &linetarget), damage,
NAME_Melee, pufftype, false, &linetarget);
if (!linetarget)
{
S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM);
return;
}
S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM);
// turn to face target
angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y);
if (angle - self->angle > ANG180)
{
if (angle - self->angle < (angle_t)(-ANG90/20))
self->angle = angle + ANG90/21;
else
self->angle -= ANG90/20;
}
else
{
if (angle - self->angle > ANG90/20)
self->angle = angle - ANG90/21;
else
self->angle += ANG90/20;
}
}
else
{
S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM);
}
//A_Chase (self);
}
//============================================================================
//
// A_M_Punch
//
//============================================================================
static void MarinePunch(AActor *self, int damagemul)
{
angle_t angle;
int damage;
int pitch;
AActor *linetarget;
if (self->target == NULL)
return;
damage = ((pr_m_punch()%10+1) << 1) * damagemul;
A_FaceTarget (self);
angle = self->angle + (pr_m_punch.Random2() << 18);
pitch = P_AimLineAttack (self, angle, MELEERANGE, &linetarget);
P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true, &linetarget);
// turn to face target
if (linetarget)
{
S_Sound (self, CHAN_WEAPON, "*fist", 1, ATTN_NORM);
self->angle = R_PointToAngle2 (self->x, self->y, linetarget->x, linetarget->y);
}
}
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Punch)
{
ACTION_PARAM_START(1);
ACTION_PARAM_INT(mult, 0);
MarinePunch(self, mult);
}
//============================================================================
//
// P_GunShot2
//
//============================================================================
void P_GunShot2 (AActor *mo, bool accurate, int pitch, const PClass *pufftype)
{
angle_t angle;
int damage;
damage = 5*(pr_m_gunshot()%3+1);
angle = mo->angle;
if (!accurate)
{
angle += pr_m_gunshot.Random2 () << 18;
}
P_LineAttack (mo, angle, MISSILERANGE, pitch, damage, NAME_Hitscan, pufftype);
}
//============================================================================
//
// A_M_FirePistol
//
//============================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_FirePistol)
{
if (self->target == NULL)
return;
ACTION_PARAM_START(1);
ACTION_PARAM_BOOL(accurate, 0);
S_Sound (self, CHAN_WEAPON, "weapons/pistol", 1, ATTN_NORM);
A_FaceTarget (self);
P_GunShot2 (self, accurate, P_AimLineAttack (self, self->angle, MISSILERANGE),
PClass::FindClass(NAME_BulletPuff));
}
//============================================================================
//
// A_M_FireShotgun
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun)
{
int pitch;
if (self->target == NULL)
return;
S_Sound (self, CHAN_WEAPON, "weapons/shotgf", 1, ATTN_NORM);
A_FaceTarget (self);
pitch = P_AimLineAttack (self, self->angle, MISSILERANGE);
for (int i = 0; i < 7; ++i)
{
P_GunShot2 (self, false, pitch, PClass::FindClass(NAME_BulletPuff));
}
self->special1 = level.maptime + 27;
}
//============================================================================
//
// A_M_CheckAttack
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_CheckAttack)
{
if (self->special1 != 0 || self->target == NULL)
{
self->SetState (self->FindState("SkipAttack"));
}
else
{
A_FaceTarget (self);
}
}
//============================================================================
//
// A_M_FireShotgun2
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun2)
{
int pitch;
if (self->target == NULL)
return;
S_Sound (self, CHAN_WEAPON, "weapons/sshotf", 1, ATTN_NORM);
A_FaceTarget (self);
pitch = P_AimLineAttack (self, self->angle, MISSILERANGE);
for (int i = 0; i < 20; ++i)
{
int damage = 5*(pr_m_fireshotgun2()%3+1);
angle_t angle = self->angle + (pr_m_fireshotgun2.Random2() << 19);
P_LineAttack (self, angle, MISSILERANGE,
pitch + (pr_m_fireshotgun2.Random2() * 332063), damage,
NAME_Hitscan, NAME_BulletPuff);
}
self->special1 = level.maptime;
}
//============================================================================
//
// A_M_FireCGun
//
//============================================================================
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_FireCGun)
{
if (self->target == NULL)
return;
ACTION_PARAM_START(1);
ACTION_PARAM_BOOL(accurate, 0);
S_Sound (self, CHAN_WEAPON, "weapons/chngun", 1, ATTN_NORM);
A_FaceTarget (self);
P_GunShot2 (self, accurate, P_AimLineAttack (self, self->angle, MISSILERANGE),
PClass::FindClass(NAME_BulletPuff));
}
//============================================================================
//
// A_M_FireMissile
//
// Giving a marine a rocket launcher is probably a bad idea unless you pump
// up his health, because he's just as likely to kill himself as he is to
// kill anything else with it.
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireMissile)
{
if (self->target == NULL)
return;
if (self->CheckMeleeRange ())
{ // If too close, punch it
MarinePunch(self, 1);
}
else
{
A_FaceTarget (self);
P_SpawnMissile (self, self->target, PClass::FindClass("Rocket"));
}
}
//============================================================================
//
// A_M_FireRailgun
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireRailgun)
{
if (self->target == NULL)
return;
CALL_ACTION(A_MonsterRail, self);
self->special1 = level.maptime + 50;
}
//============================================================================
//
// A_M_FirePlasma
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FirePlasma)
{
if (self->target == NULL)
return;
A_FaceTarget (self);
P_SpawnMissile (self, self->target, PClass::FindClass("PlasmaBall"));
self->special1 = level.maptime + 20;
}
//============================================================================
//
// A_M_BFGsound
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_BFGsound)
{
if (self->target == NULL)
return;
if (self->special1 != 0)
{
self->SetState (self->SeeState);
}
else
{
A_FaceTarget (self);
S_Sound (self, CHAN_WEAPON, "weapons/bfgf", 1, ATTN_NORM);
// Don't interrupt the firing sequence
self->PainChance = 0;
}
}
//============================================================================
//
// A_M_FireBFG
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, A_M_FireBFG)
{
if (self->target == NULL)
return;
A_FaceTarget (self);
P_SpawnMissile (self, self->target, PClass::FindClass("BFGBall"));
self->special1 = level.maptime + 30;
self->PainChance = MARINE_PAIN_CHANCE;
}
//---------------------------------------------------------------------------
void AScriptedMarine::SetWeapon (EMarineWeapon type)
{
if (GetWeaponStates(type, MeleeState, MissileState))
{
static const char *classes[] = {
"ScriptedMarine",
"MarineFist",
"MarineBerserk",
"MarineChainsaw",
"MarinePistol",
"MarineShotgun",
"MarineSSG",
"MarineChaingun",
"MarineRocket",
"MarinePlasma",
"MarineRailgun",
"MarineBFG"
};
const PClass *cls = PClass::FindClass(classes[type]);
if (cls != NULL)
DecalGenerator = GetDefaultByType(cls)->DecalGenerator;
else
DecalGenerator = NULL;
}
}
void AScriptedMarine::SetSprite (const PClass *source)
{
if (source == NULL || source->ActorInfo == NULL)
{ // A valid actor class wasn't passed, so use the standard sprite
SpriteOverride = sprite = GetClass()->ActorInfo->OwnedStates[0].sprite;
// Copy the standard scaling
scaleX = GetDefault()->scaleX;
scaleY = GetDefault()->scaleY;
}
else
{ // Use the same sprite and scaling the passed class spawns with
SpriteOverride = sprite = GetDefaultByType (source)->SpawnState->sprite;
scaleX = GetDefaultByType(source)->scaleX;
scaleY = GetDefaultByType(source)->scaleY;
}
}