mirror of
https://github.com/ENSL/NS.git
synced 2024-11-23 04:52:15 +00:00
af3128ec1a
- Removed really strict anti exploit detection (that was never used) - This should be the latest released version of NS!
1512 lines
42 KiB
C++
1512 lines
42 KiB
C++
//======== (C) Copyright 2001 Charles G. Cleveland All rights reserved. =========
|
|
//
|
|
// The copyright to the contents herein is the property of Charles G. Cleveland.
|
|
// The contents may be used and/or copied only with the written permission of
|
|
// Charles G. Cleveland, or in accordance with the terms and conditions stipulated in
|
|
// the agreement/contract under which the contents have been supplied.
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $Workfile: AvHBaseBuildable.cpp$
|
|
// $Date: 2002/11/22 21:28:15 $
|
|
//
|
|
//-------------------------------------------------------------------------------
|
|
// $Log: AvHBaseBuildable.cpp,v $
|
|
// Revision 1.19 2002/11/22 21:28:15 Flayra
|
|
// - mp_consistency changes
|
|
//
|
|
// Revision 1.18 2002/11/15 04:47:11 Flayra
|
|
// - Regenerate now returns bool if healed or not, for def chamber tweak
|
|
//
|
|
// Revision 1.17 2002/11/12 02:22:35 Flayra
|
|
// - Removed draw damage from public build
|
|
// - Don't allow +use to speed research
|
|
//
|
|
// Revision 1.16 2002/11/06 01:38:24 Flayra
|
|
// - Added ability for buildings to be enabled and disabled, for turrets to be shut down
|
|
// - Damage refactoring (TakeDamage assumes caller has already adjusted for friendly fire, etc.)
|
|
//
|
|
// Revision 1.15 2002/10/24 21:21:49 Flayra
|
|
// - Added code to award attacker a frag when destroying a building but thought better of it
|
|
//
|
|
// Revision 1.14 2002/10/16 00:49:35 Flayra
|
|
// - Reworked build times so they are real numbers
|
|
//
|
|
// Revision 1.13 2002/10/03 18:38:56 Flayra
|
|
// - Fixed problem where regenerating builders via healing spray wasn't updating health ring
|
|
// - Allow buildings to play custom damage alerts (for towers)
|
|
//
|
|
// Revision 1.12 2002/09/23 22:10:14 Flayra
|
|
// - Removed progress bar when building
|
|
// - Removed vestiges of fading building as building
|
|
// - Changed point costs
|
|
//
|
|
// Revision 1.11 2002/09/09 19:49:09 Flayra
|
|
// - Buildables now play animations better, without interrupting previous anims
|
|
//
|
|
// Revision 1.10 2002/08/31 18:01:00 Flayra
|
|
// - Work at VALVe
|
|
//
|
|
// Revision 1.9 2002/08/16 02:32:45 Flayra
|
|
// - Added damage types
|
|
// - Added visual health for commander and marines
|
|
//
|
|
// Revision 1.8 2002/08/02 22:02:09 Flayra
|
|
// - New alert system
|
|
//
|
|
// Revision 1.7 2002/07/26 23:03:56 Flayra
|
|
// - Don't play "hurt/wound" sounds when we don't actually take damage (GetCanEntityDoDamageTo)
|
|
// - Generate numerical feedback for damage events
|
|
//
|
|
// Revision 1.6 2002/07/23 16:58:38 Flayra
|
|
// - Auto-build can't happen before game starts
|
|
//
|
|
// Revision 1.5 2002/07/01 21:15:46 Flayra
|
|
// - Added auto-build capability
|
|
//
|
|
// Revision 1.4 2002/06/25 17:31:24 Flayra
|
|
// - Regular update, don't assume anything about player building, renamed arsenal to armory
|
|
//
|
|
// Revision 1.3 2002/06/03 16:27:59 Flayra
|
|
// - Allow alien buildings to regenerate, renamed weapons factory and armory
|
|
//
|
|
// Revision 1.2 2002/05/28 17:37:27 Flayra
|
|
// - Added building recycling, mark mapper placed buildables so they aren't destroyed at the end of the round
|
|
//
|
|
// Revision 1.1 2002/05/23 02:34:00 Flayra
|
|
// - Post-crash checkin. Restored @Backup from around 4/16. Contains changes for last four weeks of development.
|
|
//
|
|
//===============================================================================
|
|
#include "mod/AvHBaseBuildable.h"
|
|
#include "mod/AvHGamerules.h"
|
|
#include "mod/AvHSharedUtil.h"
|
|
#include "mod/AvHServerUtil.h"
|
|
#include "mod/AvHServerVariables.h"
|
|
#include "mod/AvHParticleConstants.h"
|
|
#include "mod/AvHMarineEquipmentConstants.h"
|
|
#include "mod/AvHSoundListManager.h"
|
|
#include "mod/AvHAlienEquipmentConstants.h"
|
|
#include "mod/AvHPlayerUpgrade.h"
|
|
#include "dlls/animation.h"
|
|
#include "mod/AvHMovementUtil.h"
|
|
|
|
const int kBaseBuildableSpawnAnimation = 0;
|
|
const int kBaseBuildableDeployAnimation = 1;
|
|
const int kBaseBuildableIdle1Animation = 2;
|
|
const int kBaseBuildableIdle2Animation = 3;
|
|
const int kBaseBuildableResearchingAnimation = 4;
|
|
const int kBaseBuildableActiveAnimation = 5;
|
|
const int kBaseBuildableFireAnimation = 6;
|
|
const int kBaseBuildableTakeDamageAnimation = 7;
|
|
const int kBaseBuildableDieForwardAnimation = 8;
|
|
const int kBaseBuildableDieLeftAnimation = 9;
|
|
const int kBaseBuildableDieBackwardAnimation = 10;
|
|
const int kBaseBuildableDieRightAnimation = 11;
|
|
const int kBaseBuildableSpecialAnimation = 12;
|
|
|
|
extern int gRegenerationEventID;
|
|
extern AvHSoundListManager gSoundListManager;
|
|
|
|
AvHBaseBuildable::AvHBaseBuildable(AvHTechID inTechID, AvHMessageID inMessageID, char* inClassName, int inUser3) : AvHBuildable(inTechID), kStartAlpha(128), mAverageUseSoundLength(.5f)
|
|
{
|
|
this->mClassName = inClassName;
|
|
this->mMessageID = inMessageID;
|
|
|
|
this->mBaseHealth = GetGameRules()->GetBaseHealthForMessageID(inMessageID);
|
|
|
|
char* theModelName = AvHSHUGetBuildTechModelName(inMessageID);
|
|
ASSERT(theModelName);
|
|
this->mModelName = theModelName;
|
|
|
|
this->mSelectID = inUser3;
|
|
this->mTimeToConstruct = GetGameRules()->GetBuildTimeForMessageID(inMessageID);
|
|
|
|
// Very important that this doesn't go in Init(), else mapper-placed structures disappear on map-reset
|
|
this->mPersistent = false;
|
|
|
|
this->Init();
|
|
}
|
|
|
|
void AvHBaseBuildable::Init()
|
|
{
|
|
if(this->pev)
|
|
{
|
|
InitializeBuildable(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, this->mSelectID);
|
|
}
|
|
|
|
this->mTimeAnimationDone = 0;
|
|
this->mLastAnimationPlayed = -1;
|
|
this->mIsResearching = false;
|
|
this->mTimeOfLastAutoHeal = -1;
|
|
this->mInternalSetConstructionComplete = false;
|
|
this->mKilled = false;
|
|
this->mTimeOfLastDamageEffect = -1;
|
|
this->mTimeOfLastDamageUpdate = -1;
|
|
this->mTimeRecycleStarted = -1;
|
|
this->mTimeRecycleDone = -1;
|
|
this->mTimeOfLastDCRegeneration = -1;
|
|
SetThink(NULL);
|
|
}
|
|
|
|
const float kAnimateThinkTime = .1f;
|
|
|
|
void AvHBaseBuildable::AnimateThink()
|
|
{
|
|
int theSequence = this->GetResearchAnimation();
|
|
if(!this->mIsResearching)
|
|
{
|
|
// Play a random idle animation
|
|
theSequence = this->GetIdleAnimation();
|
|
}
|
|
else
|
|
{
|
|
int a = 0;
|
|
}
|
|
|
|
this->PlayAnimationAtIndex(theSequence);
|
|
|
|
// Set our next think
|
|
float theUpdateTime = this->GetTimeForAnimation(theSequence);
|
|
this->pev->nextthink = gpGlobals->time + theUpdateTime;
|
|
}
|
|
|
|
int AvHBaseBuildable::BloodColor( void )
|
|
{
|
|
int theBloodColor = DONT_BLEED;
|
|
|
|
if(this->GetIsOrganic())
|
|
{
|
|
theBloodColor = BLOOD_COLOR_GREEN;
|
|
}
|
|
|
|
return theBloodColor;
|
|
}
|
|
|
|
void AvHBaseBuildable::BuildableTouch(CBaseEntity* inEntity)
|
|
{
|
|
if(inEntity->pev->team != this->pev->team)
|
|
{
|
|
this->Uncloak();
|
|
|
|
// GHOSTBUILDING: Destroy and return res.
|
|
if (this->mGhost && inEntity->IsAlive() && inEntity->IsPlayer())
|
|
{
|
|
this->TakeDamage(inEntity->pev, this->pev, 80000, DMG_GENERIC);
|
|
|
|
AvHTeam* theTeam = GetGameRules()->GetTeam(AvHTeamNumber(this->pev->team));
|
|
|
|
if (theTeam)
|
|
{
|
|
float thePercentage = .8f;
|
|
float thePointsBack = GetGameRules()->GetCostForMessageID(this->mMessageID) * thePercentage;
|
|
theTeam->SetTeamResources(theTeam->GetTeamResources() + thePointsBack);
|
|
|
|
AvHSUPlayNumericEventAboveStructure(thePointsBack, this);
|
|
}
|
|
|
|
// Uncloak the player
|
|
AvHCloakable *theCloakable=dynamic_cast<AvHCloakable *>(inEntity);
|
|
if ( theCloakable ) {
|
|
theCloakable->Uncloak();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::CheckEnabledState()
|
|
{
|
|
}
|
|
|
|
void AvHBaseBuildable::ConstructUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
bool theSuccess = false;
|
|
bool theIsBuilding = false;
|
|
bool theIsResearching = false;
|
|
float thePercentage = 0.0f;
|
|
|
|
AvHSHUGetBuildResearchState(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, theIsBuilding, theIsResearching, thePercentage);
|
|
|
|
// Only allow players to help along building, not researching
|
|
if(theIsBuilding)
|
|
{
|
|
// Only allow users from same team as turret deployer
|
|
float thePercentage = this->GetNormalizedBuildPercentage();
|
|
if(pActivator->pev->team == this->pev->team && (thePercentage < 1.0f))
|
|
{
|
|
AvHPlayer* thePlayer = dynamic_cast<AvHPlayer*>(pActivator);
|
|
ASSERT(thePlayer);
|
|
|
|
// Only soldiers and builders can build
|
|
if(thePlayer->GetIsAbleToAct() && ((thePlayer->pev->iuser3 == AVH_USER3_MARINE_PLAYER) || (thePlayer->pev->iuser3 == AVH_USER3_ALIEN_PLAYER2)))
|
|
{
|
|
AvHBasePlayerWeapon* theWeapon = dynamic_cast<AvHBasePlayerWeapon*>(thePlayer->m_pActiveItem);
|
|
if(!theWeapon || theWeapon->CanHolster())
|
|
{
|
|
thePlayer->PlayerConstructUse();
|
|
|
|
bool thePlaySound = false;
|
|
|
|
// Ensure that buildings are never absolutely painful to create
|
|
int theBuildTime = max(GetGameRules()->GetBuildTimeForMessageID(this->mMessageID), 1);
|
|
|
|
if((GetGameRules()->GetIsTesting() || GetGameRules()->GetCheatsEnabled()) && !GetGameRules()->GetIsCheatEnabled(kcSlowResearch))
|
|
{
|
|
theBuildTime = 2;
|
|
}
|
|
|
|
// Make non-frame-rate dependent
|
|
const float kDefaultInterval = .1f;
|
|
float theTimeOfLastConstructUse = thePlayer->GetTimeOfLastConstructUse();
|
|
|
|
float theInterval = min(max(gpGlobals->time - theTimeOfLastConstructUse, 0.0f), kDefaultInterval);
|
|
thePercentage += (theInterval/(float)theBuildTime);
|
|
|
|
thePlayer->SetTimeOfLastConstructUse(gpGlobals->time);
|
|
|
|
if(gpGlobals->time > (this->mLastTimePlayedSound + this->mAverageUseSoundLength))
|
|
{
|
|
AvHSUPlayRandomConstructionEffect(thePlayer, this);
|
|
this->mLastTimePlayedSound = gpGlobals->time;
|
|
}
|
|
|
|
// Given the number of constructors, what's chance of starting a new sound?
|
|
float theChanceForNewSound = (gpGlobals->frametime/(this->mAverageUseSoundLength));// /2.0f));
|
|
float theRandomFloat = RANDOM_FLOAT(0.0f, 1.0f);
|
|
if(theRandomFloat < theChanceForNewSound)
|
|
{
|
|
AvHSUPlayRandomConstructionEffect(thePlayer, this);
|
|
}
|
|
|
|
//if(RANDOM_LONG(0, 20) == 0)
|
|
//{
|
|
// char theMessage[128];
|
|
// sprintf(theMessage, "Time passed: %f, ticks: %d, rate: %f\n", theTimePassed, this->mPreThinkTicks, this->mPreThinkFrameRate);
|
|
// UTIL_SayText(theMessage, this);
|
|
//}
|
|
|
|
this->SetNormalizedBuildPercentage(thePercentage);
|
|
|
|
theSuccess = true;
|
|
|
|
// GHOSTBUILD: Manifest structure.
|
|
pev->renderamt = 255;
|
|
pev->rendermode = kRenderNormal;
|
|
pev->solid = SOLID_BBOX;
|
|
this->mGhost = false;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear out +use sound when ineffective
|
|
if(!theSuccess)
|
|
{
|
|
EMIT_SOUND(pActivator->edict(), CHAN_ITEM, "common/null.wav", 1.0, ATTN_NORM);
|
|
}
|
|
}
|
|
|
|
bool AvHBaseBuildable::Energize(float inEnergyAmount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetBaseHealth() const
|
|
{
|
|
return this->mBaseHealth;
|
|
}
|
|
|
|
char* AvHBaseBuildable::GetClassName() const
|
|
{
|
|
return this->mClassName;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetIdleAnimation() const
|
|
{
|
|
int theAnimation = this->GetIdle1Animation();
|
|
|
|
if(RANDOM_LONG(0, 1))
|
|
{
|
|
theAnimation = this->GetIdle2Animation();
|
|
}
|
|
|
|
return theAnimation;
|
|
}
|
|
|
|
char* AvHBaseBuildable::GetDeploySound() const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetIsBuilt() const
|
|
{
|
|
return this->mInternalSetConstructionComplete;
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetIsOrganic() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
char* AvHBaseBuildable::GetKilledSound() const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
float AvHBaseBuildable::GetNormalizedBuildPercentage() const
|
|
{
|
|
//return this->pev->fuser1/kNormalizationNetworkFactor;
|
|
|
|
bool theIsBuilding;
|
|
bool theIsResearching;
|
|
float thePercentage;
|
|
AvHSHUGetBuildResearchState(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, theIsBuilding, theIsResearching, thePercentage);
|
|
|
|
// Check for energy special case
|
|
if(theIsBuilding && theIsResearching)
|
|
{
|
|
thePercentage = 1.0f;
|
|
}
|
|
|
|
return thePercentage;
|
|
}
|
|
|
|
float AvHBaseBuildable::GetTimeForAnimation(int inIndex) const
|
|
{
|
|
return GetSequenceDuration(GET_MODEL_PTR(ENT(pev)), this->pev);
|
|
}
|
|
|
|
int AvHBaseBuildable::GetStartAlpha() const
|
|
{
|
|
return kStartAlpha;
|
|
}
|
|
|
|
void AvHBaseBuildable::FireDeathTarget() const
|
|
{
|
|
if(this->mTargetOnDeath != "")
|
|
{
|
|
FireTargets(this->mTargetOnDeath.c_str(), NULL, NULL, USE_TOGGLE, 0.0f);
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::FireSpawnTarget() const
|
|
{
|
|
if(this->mTargetOnSpawn != "")
|
|
{
|
|
FireTargets(this->mTargetOnSpawn.c_str(), NULL, NULL, USE_TOGGLE, 0.0f);
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::KeyValue(KeyValueData* pkvd)
|
|
{
|
|
// Any entity placed by the mapper is persistent
|
|
this->SetPersistent();
|
|
|
|
if(FStrEq(pkvd->szKeyName, "targetonspawn"))
|
|
{
|
|
this->mTargetOnSpawn = pkvd->szValue;
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if(FStrEq(pkvd->szKeyName, "targetondeath"))
|
|
{
|
|
this->mTargetOnDeath = pkvd->szValue;
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if(FStrEq(pkvd->szKeyName, "teamchoice"))
|
|
{
|
|
//this->mTeam = (AvHTeamNumber)(atoi(pkvd->szValue));
|
|
this->pev->team = (AvHTeamNumber)(atoi(pkvd->szValue));
|
|
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if(FStrEq(pkvd->szKeyName, "angles"))
|
|
{
|
|
// TODO: Insert code here
|
|
//pkvd->fHandled = TRUE;
|
|
int a = 0;
|
|
}
|
|
else
|
|
{
|
|
CBaseAnimating::KeyValue(pkvd);
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::PlayAnimationAtIndex(int inIndex, bool inForce, float inFrameRate)
|
|
{
|
|
// Only play animations on buildings that we have artwork for
|
|
bool thePlayAnim = false;
|
|
|
|
if(inIndex >= 0)
|
|
{
|
|
switch(this->mMessageID)
|
|
{
|
|
case BUILD_RESOURCES:
|
|
case BUILD_ARMSLAB:
|
|
case BUILD_COMMANDSTATION:
|
|
case BUILD_INFANTRYPORTAL:
|
|
case BUILD_TURRET_FACTORY:
|
|
case TURRET_FACTORY_UPGRADE:
|
|
case BUILD_ARMORY:
|
|
case ARMORY_UPGRADE:
|
|
case BUILD_OBSERVATORY:
|
|
case BUILD_TURRET:
|
|
case BUILD_SIEGE:
|
|
case BUILD_PROTOTYPE_LAB:
|
|
case ALIEN_BUILD_HIVE:
|
|
case ALIEN_BUILD_RESOURCES:
|
|
case ALIEN_BUILD_OFFENSE_CHAMBER:
|
|
case ALIEN_BUILD_DEFENSE_CHAMBER:
|
|
case ALIEN_BUILD_SENSORY_CHAMBER:
|
|
case ALIEN_BUILD_MOVEMENT_CHAMBER:
|
|
thePlayAnim = true;
|
|
}
|
|
}
|
|
|
|
// Make sure we're not interrupting another animation
|
|
if(thePlayAnim)
|
|
{
|
|
// Allow forcing of new animation, but it's better to complete current animation then interrupt it and play it again
|
|
float theCurrentTime = gpGlobals->time;
|
|
if((theCurrentTime >= this->mTimeAnimationDone) || (inForce && (inIndex != this->mLastAnimationPlayed)))
|
|
{
|
|
this->pev->sequence = inIndex;
|
|
this->pev->frame = 0;
|
|
ResetSequenceInfo();
|
|
|
|
this->pev->framerate = inFrameRate;
|
|
|
|
// Set to last frame to play backwards
|
|
if(this->pev->framerate < 0)
|
|
{
|
|
this->pev->frame = 255;
|
|
}
|
|
|
|
this->mLastAnimationPlayed = inIndex;
|
|
float theTimeForAnim = this->GetTimeForAnimation(inIndex);
|
|
this->mTimeAnimationDone = theCurrentTime + theTimeForAnim;
|
|
|
|
// Recalculate size
|
|
//Vector theMinSize, theMaxSize;
|
|
//this->ExtractBbox(this->pev->sequence, (float*)&theMinSize, (float*)&theMaxSize);
|
|
//UTIL_SetSize(this->pev, theMinSize, theMaxSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::SetNormalizedBuildPercentage(float inPercentage, bool inForceIfComplete)
|
|
{
|
|
// Get previous build percentage so we can add hitpoints as structure is building. This means that structures that are hurt while building finish hurt.
|
|
bool theIsBuilding, theIsResearching;
|
|
float theNormalizedBuildPercentage = 0.0f;
|
|
AvHSHUGetBuildResearchState(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, theIsBuilding, theIsResearching, theNormalizedBuildPercentage);
|
|
|
|
float theDiff = inPercentage - theNormalizedBuildPercentage;
|
|
if(theDiff > 0)
|
|
{
|
|
this->pev->health += theDiff*(1.0f - kBaseHealthPercentage)*this->mBaseHealth;
|
|
this->pev->health = min(max(0.0f, this->pev->health), (float)this->mBaseHealth);
|
|
}
|
|
else
|
|
{
|
|
int a = 0;
|
|
}
|
|
|
|
// Set new build state
|
|
AvHSHUSetBuildResearchState(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, true, inPercentage);
|
|
|
|
if(inPercentage >= 1.0f)
|
|
{
|
|
this->InternalSetConstructionComplete(inForceIfComplete);
|
|
}
|
|
|
|
this->HealthChanged();
|
|
}
|
|
|
|
void AvHBaseBuildable::UpdateOnRecycle()
|
|
{
|
|
// empty, override to add events on recycle for buildings
|
|
}
|
|
|
|
Vector AvHBaseBuildable::EyePosition( ) {
|
|
|
|
if ( this->pev->iuser3 == AVH_USER3_HIVE )
|
|
return CBaseEntity::EyePosition();
|
|
|
|
vec3_t position=AvHSHUGetRealLocation(this->pev->origin, this->pev->mins, this->pev->maxs);
|
|
position[2]+=10;
|
|
return position;
|
|
}
|
|
void AvHBaseBuildable::StartRecycle()
|
|
{
|
|
if(!GetHasUpgrade(this->pev->iuser4, MASK_RECYCLING))
|
|
{
|
|
int theRecycleTime = (GetGameRules()->GetCheatsEnabled() && !GetGameRules()->GetIsCheatEnabled(kcSlowResearch)) ? 2 : BALANCE_VAR(kRecycleTime);
|
|
|
|
// Play recycle animation in reverse (would like to play them slower according to recycle time, but it doesn't work for all structures, seems dependent on # of keyframes)
|
|
int theAnimation = this->GetRecycleAnimation();
|
|
float theTimeForAnim = this->GetTimeForAnimation(theAnimation);
|
|
float theFrameRate = -1;//-theTimeForAnim/theRecycleTime;
|
|
this->PlayAnimationAtIndex(theAnimation, true, theFrameRate);
|
|
|
|
// Schedule time to give points back
|
|
SetThink(&AvHBaseBuildable::RecycleComplete);
|
|
|
|
this->mTimeRecycleStarted = gpGlobals->time;
|
|
|
|
this->mTimeRecycleDone = gpGlobals->time + theRecycleTime;
|
|
|
|
this->pev->nextthink = this->mTimeRecycleDone;
|
|
|
|
float theVolume = .5f;
|
|
EMIT_SOUND(this->edict(), CHAN_AUTO, kBuildableRecycleSound, theVolume, ATTN_NORM);
|
|
|
|
SetUpgradeMask(&this->pev->iuser4, MASK_RECYCLING);
|
|
|
|
// run any events for this class on recycling the structure
|
|
this->UpdateOnRecycle();
|
|
|
|
// Remove tech immediately, so research or building isn't started using this tech
|
|
this->TriggerRemoveTech();
|
|
}
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetIsRecycling() const
|
|
{
|
|
return GetHasUpgrade(this->pev->iuser4, MASK_RECYCLING);
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetIsTechActive() const
|
|
{
|
|
bool theIsActive = false;
|
|
|
|
if(this->GetIsBuilt() && (this->pev->health > 0) && !GetHasUpgrade(this->pev->iuser4, MASK_RECYCLING))
|
|
{
|
|
theIsActive = true;
|
|
}
|
|
|
|
return theIsActive;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetActiveAnimation() const
|
|
{
|
|
return kBaseBuildableActiveAnimation;
|
|
}
|
|
|
|
CBaseEntity* AvHBaseBuildable::GetAttacker()
|
|
{
|
|
CBaseEntity* theAttacker = this;
|
|
|
|
AvHBuildable* theBuildable = dynamic_cast<AvHBuildable*>(this);
|
|
if(theBuildable)
|
|
{
|
|
int theBuilderIndex = theBuildable->GetBuilder();
|
|
CBaseEntity* theBuilderEntity = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(theBuilderIndex));
|
|
if(theBuilderEntity)
|
|
{
|
|
theAttacker = theBuilderEntity;
|
|
}
|
|
}
|
|
|
|
return theAttacker;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetDeployAnimation() const
|
|
{
|
|
return kBaseBuildableDeployAnimation;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetIdle1Animation() const
|
|
{
|
|
return kBaseBuildableIdle1Animation;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetIdle2Animation() const
|
|
{
|
|
return kBaseBuildableIdle2Animation;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetKilledAnimation() const
|
|
{
|
|
return kBaseBuildableDieForwardAnimation;
|
|
}
|
|
|
|
AvHMessageID AvHBaseBuildable::GetMessageID() const
|
|
{
|
|
return this->mMessageID;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetMoveType() const
|
|
{
|
|
return MOVETYPE_TOSS;
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetTriggerAlertOnDamage() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
float AvHBaseBuildable::GetTimeAnimationDone() const
|
|
{
|
|
return this->mTimeAnimationDone;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetResearchAnimation() const
|
|
{
|
|
return kBaseBuildableResearchingAnimation;
|
|
}
|
|
|
|
// Play deploy animation backwards
|
|
int AvHBaseBuildable::GetRecycleAnimation() const
|
|
{
|
|
int theAnimation = -1;
|
|
|
|
if(this->GetIsBuilt())
|
|
{
|
|
theAnimation = this->GetDeployAnimation();
|
|
}
|
|
|
|
return theAnimation;
|
|
}
|
|
|
|
char* AvHBaseBuildable::GetModelName() const
|
|
{
|
|
return this->mModelName;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetSpawnAnimation() const
|
|
{
|
|
return kBaseBuildableSpawnAnimation;
|
|
}
|
|
|
|
int AvHBaseBuildable::GetTakeDamageAnimation() const
|
|
{
|
|
int theAnimation = -1;
|
|
|
|
if(this->GetIsBuilt())
|
|
{
|
|
theAnimation = kBaseBuildableTakeDamageAnimation;
|
|
}
|
|
|
|
return theAnimation;
|
|
}
|
|
|
|
|
|
AvHTeamNumber AvHBaseBuildable::GetTeamNumber() const
|
|
{
|
|
AvHTeamNumber ret=TEAM_IND;
|
|
if ( this->pev )
|
|
ret=(AvHTeamNumber)this->pev->team;
|
|
return ret;
|
|
}
|
|
|
|
void AvHBaseBuildable::Killed(entvars_t* pevAttacker, int iGib)
|
|
{
|
|
bool theInReset = GetGameRules()->GetIsGameInReset();
|
|
|
|
AvHBaseBuildable::SetHasBeenKilled();
|
|
GetGameRules()->RemoveEntityUnderAttack( this->entindex() );
|
|
|
|
this->mKilled = true;
|
|
this->mInternalSetConstructionComplete = false;
|
|
this->mTimeOfLastAutoHeal = -1;
|
|
|
|
if (!theInReset)
|
|
{
|
|
// : 980
|
|
// Less smoke for recycled buildings
|
|
this->TriggerDeathAudioVisuals(iGib == GIB_RECYCLED);
|
|
|
|
if(!this->GetIsOrganic())
|
|
{
|
|
// More sparks for recycled buildings
|
|
int numSparks = ( iGib == GIB_RECYCLED ) ? 7 : 3;
|
|
for ( int i=0; i < numSparks; i++ ) {
|
|
Vector vecSrc = Vector( (float)RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ), (float)RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ), (float)0 );
|
|
vecSrc = vecSrc + Vector( (float)0, (float)0, (float)RANDOM_FLOAT( pev->origin.z, pev->absmax.z ) );
|
|
UTIL_Sparks(vecSrc);
|
|
}
|
|
}
|
|
// :
|
|
}
|
|
this->TriggerRemoveTech();
|
|
|
|
AvHSURemoveEntityFromHotgroupsAndSelection(this->entindex());
|
|
|
|
if(pevAttacker)
|
|
{
|
|
const char* theClassName = STRING(this->pev->classname);
|
|
AvHPlayer* inPlayer = dynamic_cast<AvHPlayer*>(CBaseEntity::Instance(ENT(pevAttacker)));
|
|
if(inPlayer && theClassName)
|
|
{
|
|
inPlayer->LogPlayerAction("structure_destroyed", theClassName);
|
|
GetGameRules()->RewardPlayerForKill(inPlayer, this);
|
|
}
|
|
}
|
|
|
|
if(this->GetIsPersistent())
|
|
{
|
|
this->SetInactive();
|
|
}
|
|
else
|
|
{
|
|
CBaseAnimating::Killed(pevAttacker, iGib);
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::SetActive()
|
|
{
|
|
this->pev->effects &= ~EF_NODRAW;
|
|
}
|
|
|
|
void AvHBaseBuildable::SetInactive()
|
|
{
|
|
this->pev->health = 0;
|
|
this->pev->effects |= EF_NODRAW;
|
|
this->pev->solid = SOLID_NOT;
|
|
this->pev->takedamage = DAMAGE_NO;
|
|
SetUpgradeMask(&this->pev->iuser4, MASK_PARASITED, false);//: remove parasite flag to prevent phantom parasites.
|
|
//this->pev->deadflag = DEAD_DEAD;
|
|
SetThink(NULL);
|
|
}
|
|
|
|
int AvHBaseBuildable::ObjectCaps(void)
|
|
{
|
|
return FCAP_CONTINUOUS_USE;
|
|
}
|
|
|
|
void AvHBaseBuildable::Precache(void)
|
|
{
|
|
CBaseAnimating::Precache();
|
|
|
|
char* theDeploySound = this->GetDeploySound();
|
|
if(theDeploySound)
|
|
{
|
|
PRECACHE_UNMODIFIED_SOUND(theDeploySound);
|
|
}
|
|
char* theKilledSound = this->GetKilledSound();
|
|
if(theKilledSound)
|
|
{
|
|
PRECACHE_UNMODIFIED_SOUND(theKilledSound);
|
|
}
|
|
|
|
PRECACHE_UNMODIFIED_MODEL(this->mModelName);
|
|
|
|
PRECACHE_UNMODIFIED_SOUND(kBuildableRecycleSound);
|
|
//PRECACHE_UNMODIFIED_SOUND(kBuildableHurt1Sound);
|
|
//PRECACHE_UNMODIFIED_SOUND(kBuildableHurt2Sound);
|
|
|
|
this->mElectricalSprite = PRECACHE_UNMODIFIED_MODEL(kElectricalSprite);
|
|
}
|
|
|
|
void AvHBaseBuildable::RecycleComplete()
|
|
{
|
|
// Look at whether it has been built and health to determine how many points to give back
|
|
float thePercentage = BALANCE_VAR(kRecycleResourcePercentage);
|
|
|
|
if(!this->GetIsBuilt())
|
|
{
|
|
thePercentage = .8f;
|
|
}
|
|
|
|
// Make sure the building is still alive, can't get points back if it's dead
|
|
if(this->pev->health <= 0)
|
|
{
|
|
thePercentage = 0.0f;
|
|
}
|
|
|
|
// Look up team
|
|
AvHTeam* theTeam = GetGameRules()->GetTeam((AvHTeamNumber)this->pev->team);
|
|
if(theTeam)
|
|
{
|
|
bool theIsEnergyTech = AvHSHUGetDoesTechCostEnergy(this->mMessageID);
|
|
ASSERT(!theIsEnergyTech);
|
|
|
|
float thePointsBack = GetGameRules()->GetCostForMessageID(this->mMessageID)*thePercentage;
|
|
theTeam->SetTeamResources(theTeam->GetTeamResources() + thePointsBack);
|
|
|
|
// Play "+ resources" event
|
|
AvHSUPlayNumericEventAboveStructure(thePointsBack, this);
|
|
|
|
// : 980
|
|
// Less smoke and more sparks for recycled buildings
|
|
this->Killed(this->pev, GIB_RECYCLED);
|
|
// :
|
|
}
|
|
}
|
|
|
|
// Sets the template iuser3 for this buildable. This is stored outside of the actual iuser3 because sometimes the pev isn't allocated yet.
|
|
void AvHBaseBuildable::SetSelectID(int inSelectID)
|
|
{
|
|
this->mSelectID = inSelectID;
|
|
}
|
|
|
|
bool AvHBaseBuildable::Regenerate(float inRegenerationAmount, bool inPlaySound, bool dcHealing)
|
|
{
|
|
bool theDidHeal = false;
|
|
if ( gpGlobals->time > this->mTimeOfLastDCRegeneration + BALANCE_VAR(kDefenseChamberThinkInterval) - 0.05f || (dcHealing == false)) {
|
|
if ( dcHealing )
|
|
this->mTimeOfLastDCRegeneration = gpGlobals->time;
|
|
float theMaxHealth = this->mBaseHealth;
|
|
|
|
if(!this->GetIsBuilt())
|
|
{
|
|
float theNormalizedBuildPercentage = this->GetNormalizedBuildPercentage();
|
|
|
|
theMaxHealth = (kBaseHealthPercentage + theNormalizedBuildPercentage*(1.0f - kBaseHealthPercentage))*this->mBaseHealth;
|
|
}
|
|
|
|
// If we aren't at full health, heal health
|
|
if(this->pev->health < theMaxHealth)
|
|
{
|
|
this->pev->health = min(theMaxHealth, this->pev->health + inRegenerationAmount);
|
|
this->HealthChanged();
|
|
theDidHeal = true;
|
|
}
|
|
|
|
// Play regen event
|
|
if(theDidHeal)
|
|
{
|
|
if(inPlaySound)
|
|
{
|
|
// Play regeneration event
|
|
PLAYBACK_EVENT_FULL(0, this->edict(), gRegenerationEventID, 0, this->pev->origin, (float *)&g_vecZero, 1.0f, 0.0, /*theWeaponIndex*/ 0, 0, 0, 0 );
|
|
}
|
|
}
|
|
}
|
|
return theDidHeal;
|
|
}
|
|
|
|
void AvHBaseBuildable::ResetEntity()
|
|
{
|
|
CBaseAnimating::ResetEntity();
|
|
|
|
this->Init();
|
|
|
|
this->Materialize();
|
|
|
|
this->pev->effects = 0;
|
|
|
|
// Build it if marked as starting built
|
|
if(this->pev->spawnflags & 1)
|
|
this->SetConstructionComplete(true);
|
|
|
|
this->mKilled = false;
|
|
}
|
|
|
|
void AvHBaseBuildable::InternalSetConstructionComplete(bool inForce)
|
|
{
|
|
if(!this->mInternalSetConstructionComplete || inForce)
|
|
{
|
|
// Fully built items are no longer marked as buildable
|
|
SetUpgradeMask(&this->pev->iuser4, MASK_BUILDABLE, false);
|
|
|
|
this->pev->rendermode = kRenderNormal;
|
|
this->pev->renderamt = 255;
|
|
|
|
// GHOSTBUILD: Ensure that finished buildings aren't ghosted.
|
|
this->mGhost = false;
|
|
this->pev->solid = SOLID_BBOX;
|
|
|
|
this->SetHasBeenBuilt();
|
|
|
|
this->SetActive();
|
|
|
|
this->mInternalSetConstructionComplete = true;
|
|
|
|
this->TriggerAddTech();
|
|
|
|
char* theDeploySound = this->GetDeploySound();
|
|
if(theDeploySound)
|
|
{
|
|
EMIT_SOUND(ENT(this->pev), CHAN_WEAPON, theDeploySound, 1, ATTN_NORM);
|
|
}
|
|
|
|
int theDeployAnimation = this->GetDeployAnimation();
|
|
|
|
this->PlayAnimationAtIndex(theDeployAnimation, true);
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::SetConstructionComplete(bool inForce)
|
|
{
|
|
this->SetNormalizedBuildPercentage(1.0f, inForce);
|
|
}
|
|
|
|
void AvHBaseBuildable::SetAverageUseSoundLength(float inLength)
|
|
{
|
|
this->mAverageUseSoundLength = inLength;
|
|
}
|
|
|
|
void AvHBaseBuildable::SetResearching(bool inState)
|
|
{
|
|
int theSequence = this->GetResearchAnimation();
|
|
|
|
if(!inState)
|
|
{
|
|
theSequence = this->GetIdleAnimation();
|
|
}
|
|
|
|
this->PlayAnimationAtIndex(theSequence, true);
|
|
|
|
this->mIsResearching = inState;
|
|
}
|
|
|
|
// Requires mSelectID and mMessageID to be set
|
|
// Sets the pev user variables, mBaseHealth, pev->health and pev->armorvalue
|
|
void AvHBaseBuildable::InternalInitializeBuildable()
|
|
{
|
|
// Always buildable
|
|
InitializeBuildable(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, this->mSelectID);
|
|
this->mBaseHealth = GetGameRules()->GetBaseHealthForMessageID(this->mMessageID);
|
|
this->pev->health = this->mBaseHealth*kBaseHealthPercentage;
|
|
this->pev->max_health = this->mBaseHealth;
|
|
|
|
// Store max health in armorvalue
|
|
//this->pev->armorvalue = GetGameRules()->GetBaseHealthForMessageID(this->mMessageID);
|
|
}
|
|
|
|
const float kFallThinkInterval = .1f;
|
|
|
|
void AvHBaseBuildable::Spawn()
|
|
{
|
|
this->Precache();
|
|
|
|
CBaseAnimating::Spawn();
|
|
|
|
// Get building size in standard way
|
|
SET_MODEL(ENT(this->pev), this->mModelName);
|
|
|
|
pev->movetype = this->GetMoveType();
|
|
pev->solid = SOLID_BBOX;
|
|
|
|
UTIL_SetOrigin( pev, pev->origin );
|
|
|
|
this->Materialize();
|
|
|
|
SetTouch(&AvHBaseBuildable::BuildableTouch);
|
|
|
|
if(this->pev->spawnflags & 1)
|
|
this->SetConstructionComplete(true);
|
|
|
|
// GHOSTBUILD: Mark as unmanifested if it's a marine structure.
|
|
if (!this->GetIsOrganic())
|
|
{
|
|
pev->renderamt = 170;
|
|
pev->rendermode = kRenderTransTexture;
|
|
this->mGhost = true;
|
|
}
|
|
}
|
|
|
|
|
|
void AvHBaseBuildable::FallThink()
|
|
{
|
|
pev->nextthink = gpGlobals->time + kFallThinkInterval;
|
|
|
|
if ( pev->flags & FL_ONGROUND )
|
|
{
|
|
this->Materialize();
|
|
|
|
// Start animating
|
|
SetThink(&AvHBaseBuildable::AnimateThink);
|
|
this->pev->nextthink = gpGlobals->time + kAnimateThinkTime;
|
|
}
|
|
}
|
|
|
|
int AvHBaseBuildable::GetSequenceForBoundingBox() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void AvHBaseBuildable::Materialize()
|
|
{
|
|
this->pev->solid = SOLID_BBOX;
|
|
|
|
this->pev->movetype = this->GetMoveType();
|
|
|
|
this->pev->classname = MAKE_STRING(this->mClassName);
|
|
|
|
this->pev->takedamage = DAMAGE_YES;
|
|
SetBits(this->pev->flags, FL_MONSTER);
|
|
|
|
// Always buildable
|
|
this->InternalInitializeBuildable();
|
|
|
|
this->SetNormalizedBuildPercentage(0.0f);
|
|
|
|
// NOTE: fuser2 is used for repairing structures
|
|
|
|
Vector theMinSize, theMaxSize;
|
|
//int theSequence = this->GetSequenceForBoundingBox();
|
|
|
|
// Get height needed for model
|
|
//this->ExtractBbox(theSequence, (float*)&theMinSize, (float*)&theMaxSize);
|
|
//float theHeight = theMaxSize.z - theMinSize.z;
|
|
|
|
AvHSHUGetSizeForTech(this->GetMessageID(), theMinSize, theMaxSize);
|
|
|
|
UTIL_SetSize(pev, theMinSize, theMaxSize);
|
|
|
|
this->PlayAnimationAtIndex(this->GetSpawnAnimation(), true);
|
|
|
|
SetUse(&AvHBaseBuildable::ConstructUse);
|
|
}
|
|
|
|
int AvHBaseBuildable::TakeDamage(entvars_t* inInflictor, entvars_t* inAttacker, float inDamage, int inBitsDamageType)
|
|
{
|
|
if(GetGameRules()->GetIsCheatEnabled(kcHighDamage))
|
|
{
|
|
inDamage *= 50;
|
|
}
|
|
|
|
if(!inAttacker)
|
|
{
|
|
inAttacker = inInflictor;
|
|
}
|
|
|
|
if(!inInflictor)
|
|
{
|
|
inInflictor = inAttacker;
|
|
}
|
|
|
|
// Take into account handicap
|
|
AvHTeam* theTeam = GetGameRules()->GetTeam(AvHTeamNumber(inAttacker->team));
|
|
if(theTeam)
|
|
{
|
|
float theHandicap = theTeam->GetHandicap();
|
|
inDamage *= theHandicap;
|
|
}
|
|
|
|
CBaseEntity* inInflictorEntity = CBaseEntity::Instance(inInflictor);
|
|
float theDamage = 0;
|
|
|
|
// Take half damage from piercing
|
|
if(inBitsDamageType & NS_DMG_PIERCING)
|
|
{
|
|
inDamage /= 2.0f;
|
|
}
|
|
|
|
// Take double damage from blast
|
|
if(inBitsDamageType & NS_DMG_BLAST)
|
|
{
|
|
inDamage *= 2.0f;
|
|
}
|
|
|
|
if((inBitsDamageType & NS_DMG_ORGANIC) && !this->GetIsOrganic())
|
|
{
|
|
inDamage = 0.0f;
|
|
}
|
|
|
|
theDamage = AvHPlayerUpgrade::CalculateDamageLessArmor((AvHUser3)this->pev->iuser3, this->pev->iuser4, inDamage, this->pev->armorvalue, inBitsDamageType, GetGameRules()->GetNumActiveHives((AvHTeamNumber)this->pev->team));
|
|
if(theDamage > 0)
|
|
{
|
|
int theAnimationIndex = this->GetTakeDamageAnimation();
|
|
if(theAnimationIndex >= 0)
|
|
{
|
|
this->PlayAnimationAtIndex(theAnimationIndex, true);
|
|
}
|
|
|
|
// Award experience to attacker
|
|
CBaseEntity* theEntity = CBaseEntity::Instance(ENT(inAttacker));
|
|
AvHPlayer* inAttacker = dynamic_cast<AvHPlayer*>(theEntity);
|
|
if(inAttacker && (inAttacker->pev->team != this->pev->team))
|
|
{
|
|
inAttacker->AwardExperienceForObjective(theDamage, this->GetMessageID());
|
|
}
|
|
}
|
|
|
|
int theReturnValue = 0;
|
|
|
|
if(theDamage > 0.0f)
|
|
{
|
|
if(this->GetTriggerAlertOnDamage())
|
|
GetGameRules()->TriggerAlert((AvHTeamNumber)this->pev->team, ALERT_UNDER_ATTACK, this->entindex());
|
|
|
|
theDamage = CBaseAnimating::TakeDamage(inInflictor, inAttacker, inDamage, inBitsDamageType);
|
|
|
|
bool theDrawDamage = (ns_cvar_float(&avh_drawdamage) > 0);
|
|
|
|
if(theDrawDamage)
|
|
{
|
|
Vector theMinSize;
|
|
Vector theMaxSize;
|
|
AvHSHUGetSizeForTech(this->GetMessageID(), theMinSize, theMaxSize);
|
|
|
|
Vector theStartPos = this->pev->origin;
|
|
theStartPos.z += theMaxSize.z;
|
|
|
|
// Draw for everyone (team is 0 after inDamage parameter)
|
|
AvHSUPlayNumericEvent(-inDamage, this->edict(), theStartPos, 0, kNumericalInfoHealthEvent, 0);
|
|
}
|
|
}
|
|
|
|
// Structures uncloak when damaged
|
|
this->Uncloak();
|
|
|
|
this->HealthChanged();
|
|
|
|
return theDamage;
|
|
}
|
|
|
|
void AvHBaseBuildable::TechnologyBuilt(AvHMessageID inMessageID)
|
|
{
|
|
}
|
|
|
|
void AvHBaseBuildable::WorldUpdate()
|
|
{
|
|
this->UpdateTechSlots();
|
|
|
|
// Organic buildings heal themselves
|
|
if(this->GetIsOrganic())
|
|
{
|
|
this->UpdateAutoHeal();
|
|
}
|
|
else
|
|
{
|
|
//this->UpdateDamageEffects();
|
|
}
|
|
|
|
// If we're electrified, set render mode
|
|
if(GetHasUpgrade(this->pev->iuser4, MASK_UPGRADE_11))
|
|
{
|
|
// Base marine building
|
|
const int kElectrifyRenderMode = kRenderFxGlowShell;
|
|
const int kElectrifyRenderAmount = 40;
|
|
|
|
this->pev->renderfx = kElectrifyRenderMode;
|
|
this->pev->renderamt = kElectrifyRenderAmount;
|
|
this->pev->rendercolor.x = kTeamColors[this->pev->team][0];
|
|
this->pev->rendercolor.y = kTeamColors[this->pev->team][1];
|
|
this->pev->rendercolor.z = kTeamColors[this->pev->team][2];
|
|
|
|
// Check for enemy players/structures nearby
|
|
CBaseEntity* theBaseEntity = NULL;
|
|
int theNumEntsDamaged = 0;
|
|
|
|
while(((theBaseEntity = UTIL_FindEntityInSphere(theBaseEntity, this->pev->origin, BALANCE_VAR(kElectricalRange))) != NULL) && (theNumEntsDamaged < BALANCE_VAR(kElectricalMaxTargets)))
|
|
{
|
|
// When "electric" cheat is enabled, shock all non-self entities, else shock enemies
|
|
if((GetGameRules()->GetIsCheatEnabled(kcElectric) && (theBaseEntity != this)) || ((theBaseEntity->pev->team != this->pev->team) && theBaseEntity->IsAlive()))
|
|
{
|
|
// Make sure it's not blocked
|
|
TraceResult theTraceResult;
|
|
UTIL_TraceLine(this->pev->origin, theBaseEntity->pev->origin, ignore_monsters, dont_ignore_glass, this->edict(), &theTraceResult);
|
|
if(theTraceResult.flFraction == 1.0f)
|
|
{
|
|
CBaseEntity* theAttacker = this->GetAttacker();
|
|
ASSERT(theAttacker);
|
|
|
|
if(theBaseEntity->TakeDamage(this->pev, theAttacker->pev, BALANCE_VAR(kElectricalDamage), DMG_GENERIC) > 0)
|
|
{
|
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
|
|
WRITE_BYTE(TE_BEAMENTPOINT);
|
|
WRITE_SHORT(theBaseEntity->entindex());
|
|
WRITE_COORD( this->pev->origin.x);
|
|
WRITE_COORD( this->pev->origin.y);
|
|
WRITE_COORD( this->pev->origin.z);
|
|
|
|
WRITE_SHORT( this->mElectricalSprite );
|
|
WRITE_BYTE( 0 ); // framestart
|
|
WRITE_BYTE( (int)15); // framerate
|
|
WRITE_BYTE( (int)(2) ); // life
|
|
WRITE_BYTE( 60 ); // width
|
|
WRITE_BYTE( 15 ); // noise
|
|
WRITE_BYTE( (int)this->pev->rendercolor.x ); // r, g, b
|
|
WRITE_BYTE( (int)this->pev->rendercolor.y ); // r, g, b
|
|
WRITE_BYTE( (int)this->pev->rendercolor.z ); // r, g, b
|
|
WRITE_BYTE( 200 ); // brightness
|
|
WRITE_BYTE( 10 ); // speed
|
|
MESSAGE_END();
|
|
|
|
gSoundListManager.PlaySoundInList(kElectricSparkSoundList, this, CHAN_AUTO, .7f);
|
|
|
|
UTIL_Sparks(theBaseEntity->pev->origin);
|
|
|
|
theNumEntsDamaged++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetHasBeenKilled() const
|
|
{
|
|
return this->mKilled;
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetIsTechnologyAvailable(AvHMessageID inMessageID) const
|
|
{
|
|
bool theTechnologyAvailable = false;
|
|
|
|
const AvHTeam* theTeam = GetGameRules()->GetTeam((AvHTeamNumber)this->pev->team);
|
|
if(theTeam)
|
|
{
|
|
// Don't allow electrical upgrade if we're already electrified
|
|
if((inMessageID != RESEARCH_ELECTRICAL) || !GetHasUpgrade(this->pev->iuser4, MASK_UPGRADE_11))
|
|
{
|
|
theTechnologyAvailable = (theTeam->GetIsTechnologyAvailable(inMessageID) && this->GetIsBuilt() && !GetHasUpgrade(this->pev->iuser4, MASK_RECYCLING));
|
|
|
|
// Enable recycle button for unbuilt structures
|
|
if(!this->GetIsBuilt() && (inMessageID == BUILD_RECYCLE))
|
|
{
|
|
theTechnologyAvailable = true;
|
|
}
|
|
}
|
|
}
|
|
return theTechnologyAvailable;
|
|
}
|
|
|
|
|
|
void AvHBaseBuildable::UpdateTechSlots()
|
|
{
|
|
// Get tech slot for this structure
|
|
AvHGamerules* theGameRules = GetGameRules();
|
|
const AvHTeam* theTeam = theGameRules->GetTeam((AvHTeamNumber)this->pev->team);
|
|
if(theTeam)
|
|
{
|
|
// Update tech slots
|
|
AvHTechSlots theTechSlots;
|
|
if(theTeam->GetTechSlotManager().GetTechSlotList((AvHUser3)this->pev->iuser3, theTechSlots))
|
|
{
|
|
// Clear the existing slots
|
|
int theMasks[kNumTechSlots] = {MASK_UPGRADE_1, MASK_UPGRADE_2, MASK_UPGRADE_3, MASK_UPGRADE_4, MASK_UPGRADE_5, MASK_UPGRADE_6, MASK_UPGRADE_7, MASK_UPGRADE_8};
|
|
|
|
// Each slot if we technology is available
|
|
for(int i = 0; i < kNumTechSlots; i++)
|
|
{
|
|
int theCurrentMask = theMasks[i];
|
|
this->pev->iuser4 &= ~theCurrentMask;
|
|
|
|
AvHMessageID theMessage = theTechSlots.mTechSlots[i];
|
|
if(theMessage != MESSAGE_NULL)
|
|
{
|
|
if(this->GetIsTechnologyAvailable(theMessage))
|
|
{
|
|
this->pev->iuser4 |= theCurrentMask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update recycling status bar
|
|
if(GetHasUpgrade(this->pev->iuser4, MASK_RECYCLING))
|
|
{
|
|
float theNormalizedRecyclingFactor = (gpGlobals->time - this->mTimeRecycleStarted)/(this->mTimeRecycleDone - this->mTimeRecycleStarted);
|
|
theNormalizedRecyclingFactor = min(max(theNormalizedRecyclingFactor, 0.0f), 1.0f);
|
|
|
|
//theResearchEntity->pev->fuser1 = (kResearchFuser1Base + theNormalizedResearchFactor)*kNormalizationNetworkFactor;
|
|
AvHSHUSetBuildResearchState(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, false, theNormalizedRecyclingFactor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::TriggerDeathAudioVisuals(bool isRecycled)
|
|
{
|
|
AvHClassType theTeamType = AVH_CLASS_TYPE_UNDEFINED;
|
|
AvHTeam* theTeam = GetGameRules()->GetTeam((AvHTeamNumber)this->pev->team);
|
|
if(theTeam)
|
|
{
|
|
theTeamType = theTeam->GetTeamType();
|
|
}
|
|
|
|
switch(theTeamType)
|
|
{
|
|
case AVH_CLASS_TYPE_ALIEN:
|
|
AvHSUPlayParticleEvent(kpsChamberDeath, this->edict(), this->pev->origin);
|
|
break;
|
|
|
|
case AVH_CLASS_TYPE_MARINE:
|
|
// lots of smoke
|
|
// : 980
|
|
// Less smoke for recycled buildings
|
|
int smokeScale = isRecycled ? 15 : 25;
|
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
|
|
WRITE_BYTE( TE_SMOKE );
|
|
WRITE_COORD( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ) );
|
|
WRITE_COORD( RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ) );
|
|
WRITE_COORD( RANDOM_FLOAT( pev->absmin.z, pev->absmax.z ) );
|
|
WRITE_SHORT( g_sModelIndexSmoke );
|
|
WRITE_BYTE( smokeScale ); // scale * 10
|
|
WRITE_BYTE( 10 ); // framerate
|
|
MESSAGE_END();
|
|
break;
|
|
}
|
|
|
|
char* theKilledSound = this->GetKilledSound();
|
|
if(theKilledSound)
|
|
{
|
|
EMIT_SOUND(ENT(this->pev), CHAN_AUTO, theKilledSound, 1.0, ATTN_IDLE);
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::UpdateAutoBuild(float inTimePassed)
|
|
{
|
|
if(GetGameRules()->GetGameStarted())
|
|
{
|
|
// TF2 snippet for making sure players don't get stuck in buildings
|
|
if(this->pev->solid == SOLID_NOT)
|
|
{
|
|
//trace_t tr;
|
|
//UTIL_TraceHull(this->pev->origin, this->pev->origin, this->pev->mins, this->pev->maxs, this->pev, &tr);
|
|
//if(!tr.startsolid && !tr.allsolid )
|
|
//if(AvHSHUGetIsAreaFree(this->pev->origin, this->pev->mins, this->pev->maxs, this->edict()))
|
|
|
|
// Check point contents for corner points
|
|
float theMinX = this->pev->origin.x + this->pev->mins.x;
|
|
float theMinY = this->pev->origin.y + this->pev->mins.y;
|
|
float theMinZ = this->pev->origin.z + this->pev->mins.z;
|
|
float theMaxX = this->pev->origin.x + this->pev->maxs.x;
|
|
float theMaxY = this->pev->origin.y + this->pev->maxs.y;
|
|
float theMaxZ = this->pev->origin.z + this->pev->maxs.z;
|
|
|
|
// Do tracelines between the corners, to make sure there's no geometry inside the box
|
|
Vector theMinVector(theMinX, theMinY, theMinZ);
|
|
Vector theMaxVector(theMaxX, theMaxY, theMaxZ);
|
|
if(AvHSHUTraceLineIsAreaFree(theMinVector, theMaxVector, this->edict()))
|
|
{
|
|
theMinVector = Vector(theMaxX, theMinY, theMinZ);
|
|
theMaxVector = Vector(theMinX, theMaxY, theMaxZ);
|
|
if(AvHSHUTraceLineIsAreaFree(theMinVector, theMaxVector, this->edict()))
|
|
{
|
|
theMinVector = Vector(theMaxX, theMaxY, theMinZ);
|
|
theMaxVector = Vector(theMinX, theMinY, theMaxZ);
|
|
if(AvHSHUTraceLineIsAreaFree(theMinVector, theMaxVector, this->edict()))
|
|
{
|
|
this->pev->solid = SOLID_BBOX;
|
|
|
|
// Relink into world (not sure if this is necessary)
|
|
UTIL_SetOrigin(this->pev, this->pev->origin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If it's not fully built, build more
|
|
bool theIsBuilding, theIsResearching;
|
|
float thePercentage;
|
|
AvHSHUGetBuildResearchState(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, theIsBuilding, theIsResearching, thePercentage);
|
|
|
|
float theBuildTime = GetGameRules()->GetBuildTimeForMessageID(this->GetMessageID());
|
|
float theBuildPercentage = inTimePassed/theBuildTime;
|
|
|
|
float theNewPercentage = min(thePercentage + theBuildPercentage, 1.0f);
|
|
this->SetNormalizedBuildPercentage(theNewPercentage);
|
|
|
|
// // Increase built time if not fully built
|
|
// if(!this->GetHasBeenBuilt() && (theNewPercentage >= 1.0f))
|
|
// {
|
|
// this->SetConstructionComplete();
|
|
// }
|
|
//// else
|
|
//// {
|
|
//// this->pev->rendermode = kRenderTransTexture;
|
|
//// int theStartAlpha = this->GetStartAlpha();
|
|
//// this->pev->renderamt = theStartAlpha + theNewPercentage*(255 - theStartAlpha);
|
|
//// }
|
|
//
|
|
// AvHSHUSetBuildResearchState(this->pev->iuser3, this->pev->iuser4, this->pev->fuser1, true, theNewPercentage);
|
|
|
|
// TODO: Heal self?
|
|
// TODO: Play ambient sounds?
|
|
}
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::UpdateAutoHeal()
|
|
{
|
|
if(GetGameRules()->GetGameStarted() && this->GetIsBuilt())
|
|
{
|
|
if((this->mTimeOfLastAutoHeal != -1) && (gpGlobals->time > this->mTimeOfLastAutoHeal))
|
|
{
|
|
float theMaxHealth = GetGameRules()->GetBaseHealthForMessageID(this->GetMessageID());
|
|
if(this->pev->health < theMaxHealth)
|
|
{
|
|
float theTimePassed = (gpGlobals->time - this->mTimeOfLastAutoHeal);
|
|
float theHitPointsToGain = theTimePassed*BALANCE_VAR(kOrganicStructureHealRate);
|
|
|
|
this->pev->health += theHitPointsToGain;
|
|
this->pev->health = min(this->pev->health, theMaxHealth);
|
|
|
|
this->HealthChanged();
|
|
}
|
|
}
|
|
|
|
this->mTimeOfLastAutoHeal = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
void AvHBaseBuildable::UpdateDamageEffects()
|
|
{
|
|
if(GetGameRules()->GetGameStarted() && this->GetIsBuilt())
|
|
{
|
|
// Add special effects for structures that are hurt or almost dead
|
|
float theMaxHealth = GetGameRules()->GetBaseHealthForMessageID(this->GetMessageID());
|
|
float theHealthScalar = this->pev->health/theMaxHealth;
|
|
float theTimeInterval = max(gpGlobals->time - this->mTimeOfLastDamageUpdate, .1f);
|
|
|
|
const float kParticleSystemLifetime = 5.0f;
|
|
int theAverageSoundInterval = -1;
|
|
|
|
// If we're at 25% health or less, emit black smoke
|
|
if(gpGlobals->time > (this->mTimeOfLastDamageEffect + kParticleSystemLifetime))
|
|
{
|
|
if(theHealthScalar < .25f)
|
|
{
|
|
AvHSUPlayParticleEvent(kpsBuildableLightDamage, this->edict(), this->pev->origin);
|
|
this->mTimeOfLastDamageEffect = gpGlobals->time;
|
|
theAverageSoundInterval = 3;
|
|
}
|
|
// If we're at 50% health or less, emit light smoke
|
|
else if(theHealthScalar < .5f)
|
|
{
|
|
AvHSUPlayParticleEvent(kpsBuildableLightDamage, this->edict(), this->pev->origin);
|
|
this->mTimeOfLastDamageEffect = gpGlobals->time;
|
|
theAverageSoundInterval = 5;
|
|
}
|
|
}
|
|
|
|
// If we're at less then 75% health, spark occasionally
|
|
if(theHealthScalar < .75f)
|
|
{
|
|
int theRandomChance = RANDOM_LONG(0, (float)8/theTimeInterval);
|
|
if(theRandomChance == 0)
|
|
{
|
|
UTIL_Sparks(this->pev->origin);
|
|
UTIL_Sparks(this->pev->origin);
|
|
|
|
const char* theHurtSoundToPlay = kBuildableHurt1Sound;
|
|
if(RANDOM_LONG(0, 1) == 1)
|
|
{
|
|
theHurtSoundToPlay = kBuildableHurt2Sound;
|
|
}
|
|
|
|
float theVolume = .3f;
|
|
EMIT_SOUND(this->edict(), CHAN_AUTO, theHurtSoundToPlay, theVolume, ATTN_NORM);
|
|
}
|
|
}
|
|
|
|
this->mTimeOfLastDamageUpdate = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
|
|
void AvHBaseBuildable::HealthChanged()
|
|
{
|
|
int theMaxHealth = this->mBaseHealth;//this->pev->armorvalue;
|
|
int theCurrentHealth = this->pev->health;
|
|
|
|
float theNewHealthPercentage = (float)theCurrentHealth/theMaxHealth;
|
|
this->pev->fuser2 = theNewHealthPercentage*kNormalizationNetworkFactor;
|
|
}
|
|
|
|
bool AvHBaseBuildable::GetIsPersistent() const
|
|
{
|
|
return this->mPersistent;
|
|
}
|
|
|
|
void AvHBaseBuildable::SetPersistent()
|
|
{
|
|
this->mPersistent = true;
|
|
}
|
|
|