diff --git a/dlls/h_battery.cpp b/dlls/h_battery.cpp index fdb7c645..80cc9ed8 100644 --- a/dlls/h_battery.cpp +++ b/dlls/h_battery.cpp @@ -26,6 +26,7 @@ #include "saverestore.h" #include "skill.h" #include "gamerules.h" +#include "effects.h" class CRecharge : public CBaseToggle { @@ -193,3 +194,392 @@ void CRecharge::Off( void ) else SetThink( &CBaseEntity::SUB_DoNothing ); } + +//------------------------------------------------------------- +// Wall mounted health kit (PS2 && Decay) +//------------------------------------------------------------- + +class CRechargeGlassDecay : public CBaseAnimating +{ +public: + void Spawn(); +}; + +void CRechargeGlassDecay::Spawn() +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_FLY; + + SET_MODEL(ENT(pev), "models/hev_glass.mdl"); + pev->renderamt = 150; + pev->rendermode = kRenderTransTexture; +} + +class CRechargeDecay : public CBaseAnimating +{ +public: + void Spawn(); + void Precache(void); + void EXPORT SearchForPlayer(); + void EXPORT Off( void ); + void EXPORT Recharge( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return ( CBaseAnimating::ObjectCaps() | FCAP_CONTINUOUS_USE ) & ~FCAP_ACROSS_TRANSITION; } + void TurnChargeToPlayer(const Vector player); + void SetChargeState(int state); + void SetChargeController(float yaw); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + enum { + Still, + Deploy, + Idle, + GiveShot, + Healing, + RetractShot, + RetractArm, + Inactive + }; + + float m_flNextCharge; + int m_iJuice; + int m_iState; + float m_flSoundTime; + CRechargeGlassDecay* m_glass; + BOOL m_playingChargeSound; + CBeam* m_beam; + +protected: + void SetMySequence(const char* sequence); + void CreateBeam(); +}; + +TYPEDESCRIPTION CRechargeDecay::m_SaveData[] = +{ + DEFINE_FIELD( CRechargeDecay, m_flNextCharge, FIELD_TIME ), + DEFINE_FIELD( CRechargeDecay, m_iJuice, FIELD_INTEGER ), + DEFINE_FIELD( CRechargeDecay, m_iState, FIELD_INTEGER ), + DEFINE_FIELD( CRechargeDecay, m_flSoundTime, FIELD_TIME ), + DEFINE_FIELD( CRechargeDecay, m_glass, FIELD_CLASSPTR), + DEFINE_FIELD( CRechargeDecay, m_beam, FIELD_CLASSPTR), + DEFINE_FIELD( CRechargeDecay, m_playingChargeSound, FIELD_BOOLEAN), +}; + +IMPLEMENT_SAVERESTORE( CRechargeDecay, CBaseAnimating ) + +void CRechargeDecay::Spawn() +{ + Precache(); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + + SET_MODEL(ENT(pev), "models/hev.mdl"); + UTIL_SetSize(pev, Vector(-12, -16, 0), Vector(12, 16, 48)); + UTIL_SetOrigin(pev, pev->origin); + m_iJuice = gSkillData.suitchargerCapacity; + pev->skin = 0; + + m_glass = GetClassPtr( (CRechargeGlassDecay *)NULL ); + m_glass->Spawn(); + UTIL_SetOrigin( m_glass->pev, pev->origin ); + m_glass->pev->owner = ENT( pev ); + m_glass->pev->angles = pev->angles; + + InitBoneControllers(); + SetBoneController(1, 360); + + CreateBeam(); + if (m_iJuice > 0) + { + m_iState = Still; + SetThink(&CRechargeDecay::SearchForPlayer); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + m_iState = Inactive; + } +} + +LINK_ENTITY_TO_CLASS(item_recharge, CRechargeDecay) + +void CRechargeDecay::Precache(void) +{ + PRECACHE_MODEL("models/hev.mdl"); + PRECACHE_MODEL("models/hev_glass.mdl"); + PRECACHE_SOUND( "items/suitcharge1.wav" ); + PRECACHE_SOUND( "items/suitchargeno1.wav" ); + PRECACHE_SOUND( "items/suitchargeok1.wav" ); + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CRechargeDecay::SearchForPlayer() +{ + CBaseEntity* pEntity = 0; + float delay = 0.05; + UTIL_MakeVectors( pev->angles ); + while((pEntity = UTIL_FindEntityInSphere(pEntity, Center(), 64)) != 0) { // this must be in sync with PLAYER_SEARCH_RADIUS from player.cpp + if (pEntity->IsPlayer()) { + if (DotProduct(pEntity->pev->origin - pev->origin, gpGlobals->v_forward) < 0) { + continue; + } + TurnChargeToPlayer(pEntity->pev->origin); + switch (m_iState) { + case RetractShot: + SetChargeState(Idle); + break; + case RetractArm: + SetChargeState(Deploy); + break; + case Still: + SetChargeState(Deploy); + delay = 0.1; + break; + case Deploy: + SetChargeState(Idle); + break; + case Idle: + break; + default: + break; + } + } + break; + } + if (!pEntity || !pEntity->IsPlayer()) { + switch (m_iState) { + case Deploy: + case Idle: + case RetractShot: + SetChargeState(RetractArm); + delay = 0.2; + break; + case RetractArm: + SetChargeState(Still); + break; + case Still: + break; + default: + break; + } + } + pev->nextthink = gpGlobals->time + delay; +} + +void CRechargeDecay::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +{ + // Make sure that we have a caller + if( !pActivator ) + return; + // if it's not a player, ignore + if( !pActivator->IsPlayer() ) + return; + + if (m_iState != Idle && m_iState != GiveShot && m_iState != Healing && m_iState != Inactive) + return; + + // if there is no juice left, turn it off + if( (m_iState == Healing || m_iState == GiveShot) && m_iJuice <= 0 ) + { + pev->skin = 1; + SetThink(&CRechargeDecay::Off); + pev->nextthink = gpGlobals->time; + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if( ( m_iJuice <= 0 ) || ( !( pActivator->pev->weapons & ( 1 << WEAPON_SUIT ) ) ) ) + { + if( m_flSoundTime <= gpGlobals->time ) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/suitchargeno1.wav", 1.0, ATTN_NORM ); + } + return; + } + + SetThink(&CRechargeDecay::Off); + pev->nextthink = gpGlobals->time + 0.25; + + // Time to recharge yet? + if( m_flNextCharge >= gpGlobals->time ) + return; + + TurnChargeToPlayer(pActivator->pev->origin); + switch (m_iState) { + case Idle: + m_flSoundTime = 0.56 + gpGlobals->time; + SetChargeState(GiveShot); + EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/suitchargeok1.wav", 1.0, ATTN_NORM ); + break; + case GiveShot: + SetChargeState(Healing); + break; + case Healing: + if (!m_playingChargeSound && m_flSoundTime <= gpGlobals->time) + { + m_playingChargeSound = TRUE; + EMIT_SOUND( ENT( pev ), CHAN_STATIC, "items/suitcharge1.wav", 1.0, ATTN_NORM ); + } + // We need to keep playing animation even though it's 1 frame only for controllers smoothing + SetChargeState(Healing); + break; + default: + ALERT(at_console, "Unexpected recharger state on use: %d\n", m_iState); + break; + } + + // charge the player + if( pActivator->pev->armorvalue < 100 ) + { + m_iJuice--; + pActivator->pev->armorvalue += 1; + const float boneControllerValue = (m_iJuice / gSkillData.suitchargerCapacity) * 360; + SetBoneController(1, 360 - boneControllerValue); + SetBoneController(2, boneControllerValue); + + if( pActivator->pev->armorvalue > 100 ) + pActivator->pev->armorvalue = 100; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CRechargeDecay::Recharge( void ) +{ +// /EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/suitcharge1.wav", 1.0, ATTN_NORM ); + m_iJuice = gSkillData.healthchargerCapacity; + SetBoneController(1, 360); + SetBoneController(2, 0); + if (m_beam) + m_beam->SetBrightness( 225 ); + pev->skin = 0; + SetChargeState(Still); + SetThink( &CRechargeDecay::SearchForPlayer ); + pev->nextthink = gpGlobals->time; +} + +void CRechargeDecay::Off( void ) +{ + switch (m_iState) { + case GiveShot: + case Healing: + if (m_playingChargeSound) { + STOP_SOUND( ENT( pev ), CHAN_STATIC, "items/suitcharge1.wav" ); + m_playingChargeSound = FALSE; + } + SetChargeState(RetractShot); + pev->nextthink = gpGlobals->time + 0.1; + break; + case RetractShot: + if (m_iJuice > 0) { + SetChargeState(Idle); + SetThink( &CRechargeDecay::SearchForPlayer ); + pev->nextthink = gpGlobals->time; + } else { + SetChargeState(RetractArm); + pev->nextthink = gpGlobals->time + 0.2; + } + break; + case RetractArm: + { + if( ( m_iJuice <= 0 ) ) + { + if (m_beam) + m_beam->SetBrightness(0); + SetChargeState(Inactive); + const float rechargeTime = g_pGameRules->FlHEVChargerRechargeTime(); + if (rechargeTime > 0 ) { + pev->nextthink = gpGlobals->time + rechargeTime; + SetThink( &CRechargeDecay::Recharge ); + } + } + break; + } + default: + break; + } +} + +void CRechargeDecay::SetMySequence(const char *sequence) +{ + pev->sequence = LookupSequence( sequence ); + if (pev->sequence == -1) { + ALERT(at_error, "unknown sequence: %s\n", sequence); + pev->sequence = 0; + } + pev->frame = 0; + ResetSequenceInfo( ); +} + +void CRechargeDecay::SetChargeState(int state) +{ + m_iState = state; + if (state == RetractArm) + SetChargeController(0); + switch (state) { + case Still: + SetMySequence("rest"); + break; + case Deploy: + SetMySequence("deploy"); + break; + case Idle: + SetMySequence("prep_charge"); + break; + case GiveShot: + SetMySequence("give_charge"); + break; + case Healing: + SetMySequence("charge_idle"); + break; + case RetractShot: + SetMySequence("retract_charge"); + break; + case RetractArm: + SetMySequence("retract_arm"); + break; + case Inactive: + SetMySequence("rest"); + default: + break; + } +} + +void CRechargeDecay::TurnChargeToPlayer(const Vector player) +{ + float yaw = UTIL_VecToYaw( player - pev->origin ) - pev->angles.y; + + if( yaw > 180 ) + yaw -= 360; + if( yaw < -180 ) + yaw += 360; + + SetChargeController( yaw ); +} + +void CRechargeDecay::SetChargeController(float yaw) +{ + SetBoneController(3, yaw); +} + +void CRechargeDecay::CreateBeam() +{ + CBeam* beam = CBeam::BeamCreate( "sprites/lgtning.spr", 5 ); + if( !beam ) + return; + beam->EntsInit(entindex(), entindex()); + beam->SetStartAttachment(3); + beam->SetEndAttachment(4); + beam->SetColor( 0, 225, 0 ); + beam->SetBrightness( 225 ); + beam->SetNoise( 10 ); + beam->RelinkBeam(); + + m_beam = beam; +} diff --git a/dlls/healthkit.cpp b/dlls/healthkit.cpp index e1b22467..0d83c2b8 100644 --- a/dlls/healthkit.cpp +++ b/dlls/healthkit.cpp @@ -251,3 +251,359 @@ void CWallHealth::Off( void ) else SetThink( &CBaseEntity::SUB_DoNothing ); } + +//------------------------------------------------------------- +// Wall mounted health kit (PS2 && Decay) +//------------------------------------------------------------- + +class CWallHealthJarDecay : public CBaseAnimating +{ +public: + void Spawn(); +}; + +void CWallHealthJarDecay::Spawn() +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_FLY; + + SET_MODEL(ENT(pev), "models/health_charger_both.mdl"); + pev->renderamt = 180; + pev->rendermode = kRenderTransTexture; + InitBoneControllers(); +} + +class CWallHealthDecay : public CBaseAnimating +{ +public: + void Spawn(); + void Precache(void); + void EXPORT SearchForPlayer(); + void EXPORT Off( void ); + void EXPORT Recharge( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return ( CBaseAnimating::ObjectCaps() | FCAP_CONTINUOUS_USE ) & ~FCAP_ACROSS_TRANSITION; } + void TurnNeedleToPlayer(const Vector player); + void SetNeedleState(int state); + void SetNeedleController(float yaw); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + enum { + Still, + Deploy, + Idle, + GiveShot, + Healing, + RetractShot, + RetractArm, + Inactive + }; + + float m_flNextCharge; + int m_iJuice; + int m_iState; + float m_flSoundTime; + CWallHealthJarDecay* m_jar; + BOOL m_playingChargeSound; + +protected: + void SetMySequence(const char* sequence); +}; + +TYPEDESCRIPTION CWallHealthDecay::m_SaveData[] = +{ + DEFINE_FIELD( CWallHealthDecay, m_flNextCharge, FIELD_TIME ), + DEFINE_FIELD( CWallHealthDecay, m_iJuice, FIELD_INTEGER ), + DEFINE_FIELD( CWallHealthDecay, m_iState, FIELD_INTEGER ), + DEFINE_FIELD( CWallHealthDecay, m_flSoundTime, FIELD_TIME ), + DEFINE_FIELD( CWallHealthDecay, m_jar, FIELD_CLASSPTR), + DEFINE_FIELD( CWallHealthDecay, m_playingChargeSound, FIELD_BOOLEAN), +}; + +IMPLEMENT_SAVERESTORE( CWallHealthDecay, CBaseAnimating ) + +void CWallHealthDecay::Spawn() +{ + Precache(); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + + SET_MODEL(ENT(pev), "models/health_charger_body.mdl"); + UTIL_SetSize(pev, Vector(-12, -16, 0), Vector(12, 16, 48)); + UTIL_SetOrigin(pev, pev->origin); + m_iJuice = gSkillData.healthchargerCapacity; + pev->skin = 0; + + m_jar = GetClassPtr( (CWallHealthJarDecay *)NULL ); + m_jar->Spawn(); + UTIL_SetOrigin( m_jar->pev, pev->origin ); + m_jar->pev->owner = ENT( pev ); + m_jar->pev->angles = pev->angles; + + InitBoneControllers(); + + if (m_iJuice > 0) + { + m_iState = Still; + SetThink(&CWallHealthDecay::SearchForPlayer); + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + m_iState = Inactive; + } +} + +LINK_ENTITY_TO_CLASS(item_healthcharger, CWallHealthDecay) + +void CWallHealthDecay::Precache(void) +{ + PRECACHE_MODEL("models/health_charger_body.mdl"); + PRECACHE_MODEL("models/health_charger_both.mdl"); + PRECACHE_SOUND( "items/medshot4.wav" ); + PRECACHE_SOUND( "items/medshotno1.wav" ); + PRECACHE_SOUND( "items/medcharge4.wav" ); +} + +void CWallHealthDecay::SearchForPlayer() +{ + CBaseEntity* pEntity = 0; + float delay = 0.05; + UTIL_MakeVectors( pev->angles ); + while((pEntity = UTIL_FindEntityInSphere(pEntity, Center(), 64)) != 0) { // this must be in sync with PLAYER_SEARCH_RADIUS from player.cpp + if (pEntity->IsPlayer()) { + if (DotProduct(pEntity->pev->origin - pev->origin, gpGlobals->v_forward) < 0) { + continue; + } + TurnNeedleToPlayer(pEntity->pev->origin); + switch (m_iState) { + case RetractShot: + SetNeedleState(Idle); + break; + case RetractArm: + SetNeedleState(Deploy); + break; + case Still: + SetNeedleState(Deploy); + delay = 0.1; + break; + case Deploy: + SetNeedleState(Idle); + break; + case Idle: + break; + default: + break; + } + } + break; + } + if (!pEntity || !pEntity->IsPlayer()) { + switch (m_iState) { + case Deploy: + case Idle: + case RetractShot: + SetNeedleState(RetractArm); + delay = 0.2; + break; + case RetractArm: + SetNeedleState(Still); + break; + case Still: + break; + default: + break; + } + } + pev->nextthink = gpGlobals->time + delay; +} + +void CWallHealthDecay::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +{ + // Make sure that we have a caller + if( !pActivator ) + return; + // if it's not a player, ignore + if( !pActivator->IsPlayer() ) + return; + + if (m_iState != Idle && m_iState != GiveShot && m_iState != Healing && m_iState != Inactive) + return; + + // if there is no juice left, turn it off + if( (m_iState == Healing || m_iState == GiveShot) && m_iJuice <= 0 ) + { + pev->skin = 1; + SetThink(&CWallHealthDecay::Off); + pev->nextthink = gpGlobals->time; + } + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if( ( m_iJuice <= 0 ) || ( !( pActivator->pev->weapons & ( 1 << WEAPON_SUIT ) ) ) ) + { + if( m_flSoundTime <= gpGlobals->time ) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/medshotno1.wav", 1.0, ATTN_NORM ); + } + return; + } + + SetThink(&CWallHealthDecay::Off); + pev->nextthink = gpGlobals->time + 0.25; + + // Time to recharge yet? + if( m_flNextCharge >= gpGlobals->time ) + return; + + TurnNeedleToPlayer(pActivator->pev->origin); + switch (m_iState) { + case Idle: + m_flSoundTime = 0.56 + gpGlobals->time; + SetNeedleState(GiveShot); + EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + break; + case GiveShot: + SetNeedleState(Healing); + break; + case Healing: + if (!m_playingChargeSound && m_flSoundTime <= gpGlobals->time) + { + EMIT_SOUND( ENT( pev ), CHAN_STATIC, "items/medcharge4.wav", 1.0, ATTN_NORM ); + m_playingChargeSound = TRUE; + } + break; + default: + ALERT(at_console, "Unexpected healthcharger state on use: %d\n", m_iState); + break; + } + + // charge the player + if( pActivator->TakeHealth( 1, DMG_GENERIC ) ) + { + m_iJuice--; + const float jarBoneControllerValue = (m_iJuice / gSkillData.healthchargerCapacity) * 11 - 11; + m_jar->SetBoneController(0, jarBoneControllerValue ); + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CWallHealthDecay::Recharge( void ) +{ + EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_iJuice = gSkillData.healthchargerCapacity; + m_jar->SetBoneController(0, 0); + pev->skin = 0; + SetNeedleState(Still); + SetThink( &CWallHealthDecay::SearchForPlayer ); + pev->nextthink = gpGlobals->time; +} + +void CWallHealthDecay::Off( void ) +{ + switch (m_iState) { + case GiveShot: + case Healing: + if (m_playingChargeSound) { + STOP_SOUND( ENT( pev ), CHAN_STATIC, "items/medcharge4.wav" ); + m_playingChargeSound = FALSE; + } + SetNeedleState(RetractShot); + pev->nextthink = gpGlobals->time + 0.1; + break; + case RetractShot: + if (m_iJuice > 0) { + SetNeedleState(Idle); + SetThink( &CWallHealthDecay::SearchForPlayer ); + pev->nextthink = gpGlobals->time; + } else { + SetNeedleState(RetractArm); + pev->nextthink = gpGlobals->time + 0.2; + } + break; + case RetractArm: + { + if( ( m_iJuice <= 0 ) ) + { + SetNeedleState(Inactive); + const float rechargeTime = g_pGameRules->FlHealthChargerRechargeTime(); + if (rechargeTime > 0 ) { + pev->nextthink = gpGlobals->time + rechargeTime; + SetThink( &CWallHealthDecay::Recharge ); + } + } + break; + } + default: + break; + } +} + +void CWallHealthDecay::SetMySequence(const char *sequence) +{ + pev->sequence = LookupSequence( sequence ); + if (pev->sequence == -1) { + ALERT(at_error, "unknown sequence: %s\n", sequence); + pev->sequence = 0; + } + pev->frame = 0; + ResetSequenceInfo( ); +} + +void CWallHealthDecay::SetNeedleState(int state) +{ + m_iState = state; + if (state == RetractArm) + SetNeedleController(0); + switch (state) { + case Still: + SetMySequence("still"); + break; + case Deploy: + SetMySequence("deploy"); + break; + case Idle: + SetMySequence("prep_shot"); + break; + case GiveShot: + SetMySequence("give_shot"); + break; + case Healing: + SetMySequence("shot_idle"); + break; + case RetractShot: + SetMySequence("retract_shot"); + break; + case RetractArm: + SetMySequence("retract_arm"); + break; + case Inactive: + SetMySequence("inactive"); + default: + break; + } +} + +void CWallHealthDecay::TurnNeedleToPlayer(const Vector player) +{ + float yaw = UTIL_VecToYaw( player - pev->origin ) - pev->angles.y; + + if( yaw > 180 ) + yaw -= 360; + if( yaw < -180 ) + yaw += 360; + + SetNeedleController( yaw ); +} + +void CWallHealthDecay::SetNeedleController(float yaw) +{ + SetBoneController(0, yaw); +}