halflife-photomode/dlls/hornet.cpp

438 lines
11 KiB
C++

/***
*
* Copyright (c) 1996-2001, Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
* Use, distribution, and modification of this source code and/or resulting
* object code is restricted to non-commercial enhancements to products from
* Valve LLC. All other use, distribution, or modification is prohibited
* without written permission from Valve LLC.
*
****/
//=========================================================
// Hornets
//=========================================================
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "soundent.h"
#include "hornet.h"
#include "gamerules.h"
int iHornetTrail;
int iHornetPuff;
LINK_ENTITY_TO_CLASS(hornet, CHornet);
//=========================================================
// Save/Restore
//=========================================================
TYPEDESCRIPTION CHornet::m_SaveData[] =
{
DEFINE_FIELD(CHornet, m_flStopAttack, FIELD_TIME),
DEFINE_FIELD(CHornet, m_iHornetType, FIELD_INTEGER),
DEFINE_FIELD(CHornet, m_flFlySpeed, FIELD_FLOAT),
};
IMPLEMENT_SAVERESTORE(CHornet, CBaseMonster);
//=========================================================
// don't let hornets gib, ever.
//=========================================================
bool CHornet::TakeDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType)
{
// filter these bits a little.
bitsDamageType &= ~(DMG_ALWAYSGIB);
bitsDamageType |= DMG_NEVERGIB;
return CBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType);
}
//=========================================================
//=========================================================
void CHornet::Spawn()
{
Precache();
pev->movetype = MOVETYPE_FLY;
pev->solid = SOLID_BBOX;
pev->takedamage = DAMAGE_YES;
pev->flags |= FL_MONSTER;
pev->health = 1; // weak!
if (g_pGameRules->IsMultiplayer())
{
// hornets don't live as long in multiplayer
m_flStopAttack = gpGlobals->time + 3.5;
}
else
{
m_flStopAttack = gpGlobals->time + 5.0;
}
m_flFieldOfView = 0.9; // +- 25 degrees
if (RANDOM_LONG(1, 5) <= 2)
{
m_iHornetType = HORNET_TYPE_RED;
m_flFlySpeed = HORNET_RED_SPEED;
}
else
{
m_iHornetType = HORNET_TYPE_ORANGE;
m_flFlySpeed = HORNET_ORANGE_SPEED;
}
SET_MODEL(ENT(pev), "models/hornet.mdl");
UTIL_SetSize(pev, Vector(-4, -4, -4), Vector(4, 4, 4));
SetTouch(&CHornet::DieTouch);
SetThink(&CHornet::StartTrack);
edict_t* pSoundEnt = pev->owner;
if (!pSoundEnt)
pSoundEnt = edict();
if (!FNullEnt(pev->owner) && (pev->owner->v.flags & FL_CLIENT) != 0)
{
pev->dmg = gSkillData.plrDmgHornet;
}
else
{
// no real owner, or owner isn't a client.
pev->dmg = gSkillData.monDmgHornet;
}
pev->nextthink = gpGlobals->time + 0.1;
ResetSequenceInfo();
}
void CHornet::Precache()
{
PRECACHE_MODEL("models/hornet.mdl");
PRECACHE_SOUND("agrunt/ag_fire1.wav");
PRECACHE_SOUND("agrunt/ag_fire2.wav");
PRECACHE_SOUND("agrunt/ag_fire3.wav");
PRECACHE_SOUND("hornet/ag_buzz1.wav");
PRECACHE_SOUND("hornet/ag_buzz2.wav");
PRECACHE_SOUND("hornet/ag_buzz3.wav");
PRECACHE_SOUND("hornet/ag_hornethit1.wav");
PRECACHE_SOUND("hornet/ag_hornethit2.wav");
PRECACHE_SOUND("hornet/ag_hornethit3.wav");
iHornetPuff = PRECACHE_MODEL("sprites/muz1.spr");
iHornetTrail = PRECACHE_MODEL("sprites/laserbeam.spr");
}
//=========================================================
// hornets will never get mad at each other, no matter who the owner is.
//=========================================================
int CHornet::IRelationship(CBaseEntity* pTarget)
{
if (pTarget->pev->modelindex == pev->modelindex)
{
return R_NO;
}
return CBaseMonster::IRelationship(pTarget);
}
//=========================================================
// ID's Hornet as their owner
//=========================================================
int CHornet::Classify()
{
if (pev->owner && (pev->owner->v.flags & FL_CLIENT) != 0)
{
return CLASS_PLAYER_BIOWEAPON;
}
return CLASS_ALIEN_BIOWEAPON;
}
//=========================================================
// StartTrack - starts a hornet out tracking its target
//=========================================================
void CHornet::StartTrack()
{
IgniteTrail();
SetTouch(&CHornet::TrackTouch);
SetThink(&CHornet::TrackTarget);
pev->nextthink = gpGlobals->time + 0.1;
}
//=========================================================
// StartDart - starts a hornet out just flying straight.
//=========================================================
void CHornet::StartDart()
{
IgniteTrail();
SetTouch(&CHornet::DartTouch);
SetThink(&CHornet::SUB_Remove);
pev->nextthink = gpGlobals->time + 4;
}
void CHornet::IgniteTrail()
{
/*
ted's suggested trail colors:
r161
g25
b97
r173
g39
b14
old colors
case HORNET_TYPE_RED:
WRITE_BYTE( 255 ); // r, g, b
WRITE_BYTE( 128 ); // r, g, b
WRITE_BYTE( 0 ); // r, g, b
break;
case HORNET_TYPE_ORANGE:
WRITE_BYTE( 0 ); // r, g, b
WRITE_BYTE( 100 ); // r, g, b
WRITE_BYTE( 255 ); // r, g, b
break;
*/
// trail
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
WRITE_BYTE(TE_BEAMFOLLOW);
WRITE_SHORT(entindex()); // entity
WRITE_SHORT(iHornetTrail); // model
WRITE_BYTE(10); // life
WRITE_BYTE(2); // width
switch (m_iHornetType)
{
case HORNET_TYPE_RED:
WRITE_BYTE(179); // r, g, b
WRITE_BYTE(39); // r, g, b
WRITE_BYTE(14); // r, g, b
break;
case HORNET_TYPE_ORANGE:
WRITE_BYTE(255); // r, g, b
WRITE_BYTE(128); // r, g, b
WRITE_BYTE(0); // r, g, b
break;
}
WRITE_BYTE(128); // brightness
MESSAGE_END();
}
//=========================================================
// Hornet is flying, gently tracking target
//=========================================================
void CHornet::TrackTarget()
{
Vector vecFlightDir;
Vector vecDirToEnemy;
float flDelta;
StudioFrameAdvance();
if (gpGlobals->time > m_flStopAttack)
{
SetTouch(NULL);
SetThink(&CHornet::SUB_Remove);
pev->nextthink = gpGlobals->time + 0.1;
return;
}
// UNDONE: The player pointer should come back after returning from another level
if (m_hEnemy == NULL)
{ // enemy is dead.
Look(512);
m_hEnemy = BestVisibleEnemy();
}
if (m_hEnemy != NULL && FVisible(m_hEnemy))
{
m_vecEnemyLKP = m_hEnemy->BodyTarget(pev->origin);
}
else
{
m_vecEnemyLKP = m_vecEnemyLKP + pev->velocity * m_flFlySpeed * 0.1;
}
vecDirToEnemy = (m_vecEnemyLKP - pev->origin).Normalize();
if (pev->velocity.Length() < 0.1)
vecFlightDir = vecDirToEnemy;
else
vecFlightDir = pev->velocity.Normalize();
// measure how far the turn is, the wider the turn, the slow we'll go this time.
flDelta = DotProduct(vecFlightDir, vecDirToEnemy);
if (flDelta < 0.5)
{ // hafta turn wide again. play sound
switch (RANDOM_LONG(0, 2))
{
case 0:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM);
break;
case 1:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM);
break;
case 2:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM);
break;
}
}
if (flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED)
{ // no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far.
flDelta = 0.25;
}
pev->velocity = (vecFlightDir + vecDirToEnemy).Normalize();
if (pev->owner && (pev->owner->v.flags & FL_MONSTER) != 0)
{
// random pattern only applies to hornets fired by monsters, not players.
pev->velocity.x += RANDOM_FLOAT(-0.10, 0.10); // scramble the flight dir a bit.
pev->velocity.y += RANDOM_FLOAT(-0.10, 0.10);
pev->velocity.z += RANDOM_FLOAT(-0.10, 0.10);
}
switch (m_iHornetType)
{
case HORNET_TYPE_RED:
pev->velocity = pev->velocity * (m_flFlySpeed * flDelta); // scale the dir by the ( speed * width of turn )
pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.1, 0.3);
break;
case HORNET_TYPE_ORANGE:
pev->velocity = pev->velocity * m_flFlySpeed; // do not have to slow down to turn.
pev->nextthink = gpGlobals->time + 0.1; // fixed think time
break;
}
pev->angles = UTIL_VecToAngles(pev->velocity);
pev->solid = SOLID_BBOX;
// if hornet is close to the enemy, jet in a straight line for a half second.
// (only in the single player game)
if (m_hEnemy != NULL && !g_pGameRules->IsMultiplayer())
{
if (flDelta >= 0.4 && (pev->origin - m_vecEnemyLKP).Length() <= 300)
{
MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, pev->origin);
WRITE_BYTE(TE_SPRITE);
WRITE_COORD(pev->origin.x); // pos
WRITE_COORD(pev->origin.y);
WRITE_COORD(pev->origin.z);
WRITE_SHORT(iHornetPuff); // model
// WRITE_BYTE( 0 ); // life * 10
WRITE_BYTE(2); // size * 10
WRITE_BYTE(128); // brightness
MESSAGE_END();
switch (RANDOM_LONG(0, 2))
{
case 0:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM);
break;
case 1:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM);
break;
case 2:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM);
break;
}
pev->velocity = pev->velocity * 2;
pev->nextthink = gpGlobals->time + 1.0;
// don't attack again
m_flStopAttack = gpGlobals->time;
}
}
}
//=========================================================
// Tracking Hornet hit something
//=========================================================
void CHornet::TrackTouch(CBaseEntity* pOther)
{
if (pOther->edict() == pev->owner || pOther->pev->modelindex == pev->modelindex)
{ // bumped into the guy that shot it.
pev->solid = SOLID_NOT;
return;
}
if (IRelationship(pOther) <= R_NO)
{
// hit something we don't want to hurt, so turn around.
pev->velocity = pev->velocity.Normalize();
pev->velocity.x *= -1;
pev->velocity.y *= -1;
pev->origin = pev->origin + pev->velocity * 4; // bounce the hornet off a bit.
pev->velocity = pev->velocity * m_flFlySpeed;
return;
}
DieTouch(pOther);
}
void CHornet::DartTouch(CBaseEntity* pOther)
{
DieTouch(pOther);
}
void CHornet::DieTouch(CBaseEntity* pOther)
{
//Only deal damage if the owner exists in this map.
//Hornets that transition without their owner (e.g. Alien Grunt) will otherwise pass a null pointer down to TakeDamage.
if (pOther && 0 != pOther->pev->takedamage && nullptr != pev->owner)
{ // do the damage
switch (RANDOM_LONG(0, 2))
{ // buzz when you plug someone
case 0:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_hornethit1.wav", 1, ATTN_NORM);
break;
case 1:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_hornethit2.wav", 1, ATTN_NORM);
break;
case 2:
EMIT_SOUND(ENT(pev), CHAN_VOICE, "hornet/ag_hornethit3.wav", 1, ATTN_NORM);
break;
}
pOther->TakeDamage(pev, VARS(pev->owner), pev->dmg, DMG_BULLET);
}
pev->modelindex = 0; // so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid
pev->solid = SOLID_NOT;
SetThink(&CHornet::SUB_Remove);
pev->nextthink = gpGlobals->time + 1; // stick around long enough for the sound to finish!
}