4894 lines
158 KiB
C++
Raw Permalink Normal View History

/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// Drawable.cpp ///////////////////////////////////////////////////////////////////////////////////
// "Drawables" - graphical GameClient entities bound to GameLogic objects
// Author: Michael S. Booth, March 2001
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventInfo.h"
#include "Common/AudioSettings.h"
#include "Common/BitFlagsIO.h"
#include "Common/BuildAssistant.h"
#include "Common/ClientUpdateModule.h"
#include "Common/DrawModule.h"
#include "Common/GameAudio.h"
#include "Common/GameEngine.h"
#include "Common/GameLOD.h"
#include "Common/GameState.h"
#include "Common/GlobalData.h"
#include "Common/ModuleFactory.h"
#include "Common/PerfTimer.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/GameLogic.h" // for logic frame count
#include "GameLogic/Object.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/StickyBombUpdate.h"
#include "GameLogic/Module/BattlePlanUpdate.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Anim2D.h"
#include "GameClient/Display.h"
#include "GameClient/DisplayStringManager.h"
#include "GameClient/Drawable.h"
#include "GameClient/DrawGroupInfo.h"
#include "GameClient/GameClient.h"
#include "GameClient/GlobalLanguage.h"
#include "GameClient/InGameUI.h"
#include "GameClient/Image.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/LanguageFilter.h"
#include "GameClient/Shadow.h"
#include "GameClient/GameText.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
#define VERY_TRANSPARENT_HEATVISION (0.001f)
#define HEATVISION_FADE_SCALAR (0.8f)
static const char *TheDrawableIconNames[] =
{
"DefaultHeal",
"StructureHeal",
"VehicleHeal",
#ifdef ALLOW_DEMORALIZE
"Demoralized",
#else
"Demoralized_OBSOLETE",
#endif
"BombTimed",
"BombRemote",
"Disabled",
"BattlePlanIcon_Bombard",
"BattlePlanIcon_HoldTheLine",
"BattlePlanIcon_SeekAndDestroy",
"Emoticon",
"Enthusiastic",//a red cross? // soon to replace?
"Subliminal", //with the gold border! replace?
"CarBomb",
NULL
};
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DrawableIconInfo::DrawableIconInfo()
{
for (int i = 0; i < MAX_ICONS; ++i)
{
m_icon[i] = NULL;
m_keepTillFrame[i] = 0;
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DrawableIconInfo::~DrawableIconInfo()
{
clear();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void DrawableIconInfo::clear()
{
for (int i = 0; i < MAX_ICONS; ++i)
{
if (m_icon[i])
m_icon[i]->deleteInstance();
m_icon[i] = NULL;
m_keepTillFrame[i] = 0;
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void DrawableIconInfo::killIcon(DrawableIconType t)
{
if (m_icon[t])
{
m_icon[t]->deleteInstance();
m_icon[t] = NULL;
m_keepTillFrame[t] = 0;
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DrawableLocoInfo::DrawableLocoInfo()
{
m_pitch = 0.0f;
m_pitchRate = 0.0f;
m_roll = 0.0f;
m_rollRate = 0.0f;
m_yaw = 0.0f;
m_accelerationPitch = 0.0f;
m_accelerationPitchRate = 0.0f;
m_accelerationRoll = 0.0f;
m_accelerationRollRate = 0.0f;
m_overlapZVel = 0.0f;
m_overlapZ = 0.0f;
m_wobble = 1.0f;
m_wheelInfo.m_frontLeftHeightOffset = 0;
m_wheelInfo.m_frontRightHeightOffset = 0;
m_wheelInfo.m_rearLeftHeightOffset = 0;
m_wheelInfo.m_rearRightHeightOffset = 0;
m_wheelInfo.m_framesAirborneCounter = 0;
m_wheelInfo.m_framesAirborne = 0;
m_wheelInfo.m_wheelAngle = 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DrawableLocoInfo::~DrawableLocoInfo()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
static const char *drawableIconIndexToName( DrawableIconType iconIndex )
{
DEBUG_ASSERTCRASH( iconIndex >= ICON_FIRST && iconIndex < MAX_ICONS,
("drawableIconIndexToName - Illegal index '%d'\n", iconIndex) );
return TheDrawableIconNames[ iconIndex ];
} // end drawableIconIndexToName
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
static DrawableIconType drawableIconNameToIndex( const char *iconName )
{
DEBUG_ASSERTCRASH( iconName != NULL, ("drawableIconNameToIndex - Illegal name\n") );
for( Int i = ICON_FIRST; i < MAX_ICONS; ++i )
if( stricmp( TheDrawableIconNames[ i ], iconName ) == 0 )
return (DrawableIconType)i;
return ICON_INVALID;
} // end drawableIconNameToIndex
// ------------------------------------------------------------------------------------------------
// constants
const UnsignedInt HEALING_ICON_DISPLAY_TIME = LOGICFRAMES_PER_SECOND * 3;
const UnsignedInt DEFAULT_HEAL_ICON_WIDTH = 32;
const UnsignedInt DEFAULT_HEAL_ICON_HEIGHT = 32;
const RGBColor SICKLY_GREEN_POISONED_COLOR = {-1.0f, 1.0f, -1.0f};
const RGBColor DARK_GRAY_DISABLED_COLOR = {-0.5f, -0.5f, -0.5f};
const RGBColor RED_IRRADIATED_COLOR = { 1.0f, -1.0f, -1.0f};
const Int MAX_ENABLED_MODULES = 16;
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ Bool Drawable::s_staticImagesInited = false;
/*static*/ const Image* Drawable::s_veterancyImage[LEVEL_COUNT] = { NULL };
/*static*/ const Image* Drawable::s_fullAmmo = NULL;
/*static*/ const Image* Drawable::s_emptyAmmo = NULL;
/*static*/ const Image* Drawable::s_fullContainer = NULL;
/*static*/ const Image* Drawable::s_emptyContainer = NULL;
/*static*/ Anim2DTemplate** Drawable::s_animationTemplates = NULL;
#ifdef DIRTY_CONDITION_FLAGS
/*static*/ Int Drawable::s_modelLockCount = 0;
#endif
// ------------------------------------------------------------------------------------------------
/*static*/ void Drawable::initStaticImages()
{
if (s_staticImagesInited)
return;
s_veterancyImage[0] = NULL;
s_veterancyImage[1] = TheMappedImageCollection->findImageByName("SCVeter1");
s_veterancyImage[2] = TheMappedImageCollection->findImageByName("SCVeter2");
s_veterancyImage[3] = TheMappedImageCollection->findImageByName("SCVeter3");
s_fullAmmo = TheMappedImageCollection->findImageByName("SCPAmmoFull");
s_emptyAmmo = TheMappedImageCollection->findImageByName("SCPAmmoEmpty");
s_fullContainer = TheMappedImageCollection->findImageByName("SCPPipFull");
s_emptyContainer = TheMappedImageCollection->findImageByName("SCPPipEmpty");
s_animationTemplates = NEW Anim2DTemplate* [ MAX_ICONS ];
s_animationTemplates[ICON_DEFAULT_HEAL] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_DEFAULT_HEAL]);
s_animationTemplates[ICON_STRUCTURE_HEAL] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_STRUCTURE_HEAL]);
s_animationTemplates[ICON_VEHICLE_HEAL] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_VEHICLE_HEAL]);
#ifdef ALLOW_DEMORALIZE
s_animationTemplates[ICON_DEMORALIZED] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_DEMORALIZED]);
#endif
s_animationTemplates[ICON_BOMB_TIMED] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_BOMB_TIMED]);
s_animationTemplates[ICON_BOMB_REMOTE] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_BOMB_REMOTE]);
s_animationTemplates[ICON_DISABLED] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_DISABLED]);
s_animationTemplates[ICON_BATTLEPLAN_BOMBARD] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_BATTLEPLAN_BOMBARD]);
s_animationTemplates[ICON_BATTLEPLAN_HOLDTHELINE] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_BATTLEPLAN_HOLDTHELINE]);
s_animationTemplates[ICON_BATTLEPLAN_SEARCHANDDESTROY] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_BATTLEPLAN_SEARCHANDDESTROY]);
s_animationTemplates[ICON_EMOTICON] = NULL; //Emoticons can be anything, so we'll need to handle it dynamically.
s_animationTemplates[ICON_ENTHUSIASTIC] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_ENTHUSIASTIC]);
s_animationTemplates[ICON_ENTHUSIASTIC_SUBLIMINAL] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_ENTHUSIASTIC_SUBLIMINAL]);
s_animationTemplates[ICON_CARBOMB] = TheAnim2DCollection->findTemplate(TheDrawableIconNames[ICON_CARBOMB]);
s_staticImagesInited = true;
}
//-------------------------------------------------------------------------------------------------
/*static*/ void Drawable::killStaticImages()
{
if( s_animationTemplates )
{
delete s_animationTemplates;
s_animationTemplates = NULL;
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::saturateRGB(RGBColor& color, Real factor)
{
color.red *= factor;
color.green *= factor;
color.blue *= factor;
Real halfFactor = factor * 0.5f;
color.red -= halfFactor;
color.green -= halfFactor;
color.blue -= halfFactor;
}
//--- A MACRO TO APPLY TO TheTacticalView->getZoom() ------ To Clamp the return to a visually pleasing size
//--- so that icons, emoticons, health bars, pips, etc, look reasonably solid and don't shimmer or tweed
//#define CLAMP_ICON_ZOOM_FACTOR(n) (MAX(0.80f, MIN(1.00f, n)))
#define CLAMP_ICON_ZOOM_FACTOR(n) (n)//nothing
//-------------------------------------------------------------------------------------------------
/** Drawables are lightweight, graphical entities which live on the GameClient,
* and are usually bound to GameLogic objects. In other words, they are the
* graphical side of a logical object, whereas GameLogic objects encapsulate
* behaviors and physics. */
//-------------------------------------------------------------------------------------------------
Drawable::Drawable( const ThingTemplate *thingTemplate, DrawableStatus statusBits )
: Thing( thingTemplate )
{
// assign status bits before anything else can be done
m_status = statusBits;
// Added By Sadullah Nader
// Initialization missing and needed
m_nextDrawable = NULL;
m_prevDrawable = NULL;
//
// register drawable with the GameClient ... do this first before we start doing anything
// complex that uses any of the drawable data so that we have and ID!! It's ok to initialize
// members of the drawable before this registration happens
//
TheGameClient->registerDrawable( this );
Int i;
// Added By Sadullah Nader
// Initialization missing and needed
m_flashColor = 0;
m_selected = '\0';
//
m_expirationDate = 0; // 0 == never expires
m_lastConstructDisplayed = -1.0f;
//Added By Sadullah Nader
//Fix for the building percent
m_constructDisplayString = TheDisplayStringManager->newDisplayString();
m_constructDisplayString->setFont(TheFontLibrary->getFont(TheInGameUI->getDrawableCaptionFontName(),
TheGlobalLanguageData->adjustFontSize(TheInGameUI->getDrawableCaptionPointSize()),
TheInGameUI->isDrawableCaptionBold() ));
m_ambientSound = NULL;
m_decalOpacityFadeTarget = 0;
m_decalOpacityFadeRate = 0;
m_decalOpacity = 0;
m_explicitOpacity = 1.0f;
m_stealthOpacity = 1.0f;
m_effectiveStealthOpacity = 1.0f;
m_terrainDecalType = TERRAIN_DECAL_NONE;
m_fadeMode = FADING_NONE;
m_timeElapsedFade = 0;
m_timeToFade = 0;
m_shroudClearFrame = 0;
for (i = 0; i < NUM_DRAWABLE_MODULE_TYPES; ++i)
m_modules[i] = NULL;
m_stealthLook = STEALTHLOOK_NONE;
m_flashCount = 0;
m_locoInfo = NULL;
// sanity
if( TheGameClient == NULL || thingTemplate == NULL )
{
assert( 0 );
return;
} // end if
m_instance.Make_Identity();
m_instanceIsIdentity = true;
//Real scaleFuzziness = thingTemplate->getInstanceScaleFuzziness();
//Real fuzzyScale = ( 1.0f + GameClientRandomValueReal( -scaleFuzziness, scaleFuzziness ));
m_instanceScale = thingTemplate->getAssetScale();// * fuzzyScale;
// initially not bound to an object
m_object = NULL;
m_particle = NULL;
// tintStatusTracking
m_tintStatus = 0;
m_prevTintStatus = 0;
#ifdef DIRTY_CONDITION_FLAGS
m_isModelDirty = true;
#endif
m_hidden = false;
m_hiddenByStealth = false;
m_heatVisionOpacity = 0.0f;
m_drawableFullyObscuredByShroud = false;
m_ambientSoundEnabled = TRUE;
//
// allocate any modules we need to, we should keep
// this at or near the end of the drawable construction so that we have
// all the valid data about the thing when we create the module
//
//Filter out drawable modules which have been disabled because of game LOD.
Int modIdx;
Module** m;
const ModuleInfo& drawMI = thingTemplate->getDrawModuleInfo();
m_modules[MODULETYPE_DRAW - FIRST_DRAWABLE_MODULE_TYPE] = MSGNEW("ModulePtrs") Module*[drawMI.getCount()+1]; // pool[]ify
m = m_modules[MODULETYPE_DRAW - FIRST_DRAWABLE_MODULE_TYPE];
for (modIdx = 0; modIdx < drawMI.getCount(); ++modIdx)
{
const ModuleData* newModData = drawMI.getNthData(modIdx);
if (TheGlobalData->m_useDrawModuleLOD &&
newModData->getMinimumRequiredGameLOD() > TheGameLODManager->getStaticLODLevel())
continue;
*m++ = TheModuleFactory->newModule(this, drawMI.getNthName(modIdx), newModData, MODULETYPE_DRAW);
}
*m = NULL;
const ModuleInfo& cuMI = thingTemplate->getClientUpdateModuleInfo();
if (cuMI.getCount())
{
// since most things don't have CU modules, we allow this to be null!
m_modules[MODULETYPE_CLIENT_UPDATE - FIRST_DRAWABLE_MODULE_TYPE] = MSGNEW("ModulePtrs") Module*[cuMI.getCount()+1]; // pool[]ify
m = m_modules[MODULETYPE_CLIENT_UPDATE - FIRST_DRAWABLE_MODULE_TYPE];
for (modIdx = 0; modIdx < cuMI.getCount(); ++modIdx)
{
const ModuleData* newModData = cuMI.getNthData(modIdx);
/// @todo srj -- this is evil, we shouldn't look at the module name directly!
if (thingTemplate->isKindOf(KINDOF_SHRUBBERY) &&
!TheGlobalData->m_useTreeSway &&
cuMI.getNthName(modIdx).compareNoCase("SwayClientUpdate") == 0)
continue;
*m++ = TheModuleFactory->newModule(this, cuMI.getNthName(modIdx), newModData, MODULETYPE_CLIENT_UPDATE);
}
*m = NULL;
}
/// allow for inter-Module resolution
for (i = 0; i < NUM_DRAWABLE_MODULE_TYPES; ++i)
{
for (Module** m = m_modules[i]; m && *m; ++m)
(*m)->onObjectCreated();
}
m_groupNumber = NULL;
m_captionDisplayString = NULL;
m_drawableInfo.m_drawable = this;
m_drawableInfo.m_ghostObject = NULL;
m_iconInfo = NULL; // lazily allocate!
m_selectionFlashEnvelope = NULL; // lazily allocate!
m_colorTintEnvelope = NULL; // lazily allocate!
initStaticImages();
startAmbientSound();
} // end Drawable
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Drawable::~Drawable()
{
Int i;
if( m_constructDisplayString )
TheDisplayStringManager->freeDisplayString( m_constructDisplayString );
m_constructDisplayString = NULL;
if ( m_captionDisplayString )
TheDisplayStringManager->freeDisplayString( m_captionDisplayString );
m_captionDisplayString = NULL;
m_groupNumber = NULL;
// delete any modules callbacks
for (i = 0; i < NUM_DRAWABLE_MODULE_TYPES; ++i)
{
for (Module** m = m_modules[i]; m && *m; ++m)
{
(*m)->deleteInstance();
*m = NULL; // in case other modules call findModule from their dtor!
}
delete [] m_modules[i];
m_modules[i] = NULL;
}
stopAmbientSound();
if (m_ambientSound)
{
m_ambientSound->deleteInstance();
m_ambientSound = NULL;
}
/// @todo this is nasty, we need a real general effects system
// remove any entries that might be present from the ray effect system
TheGameClient->removeFromRayEffects( this );
// reset object to NULL so we never mistaken grab "dead" objects
m_object = NULL;
m_particle = NULL;
// delete any icons present
if (m_iconInfo)
m_iconInfo->deleteInstance();
if (m_selectionFlashEnvelope)
m_selectionFlashEnvelope->deleteInstance();
if (m_colorTintEnvelope)
m_colorTintEnvelope->deleteInstance();
if (m_locoInfo)
{
m_locoInfo->deleteInstance();
m_locoInfo = NULL;
}
}
//-------------------------------------------------------------------------------------------------
/** Run from GameClient::destroyDrawable */
//-------------------------------------------------------------------------------------------------
void Drawable::onDestroy( void )
{
//
// run the onDelete on all modules present so they each have an opportunity to cleanup
// anything they need to ... including talking to any other modules
//
for( Int i = 0; i < NUM_DRAWABLE_MODULE_TYPES; i++ )
{
for( Module** m = m_modules[ i ]; m && *m; ++m )
(*m)->onDelete();
} // end for i
} // end onDestroy
//-------------------------------------------------------------------------------------------------
Bool Drawable::isVisible()
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
if ((*dm)->isVisible())
{
return TRUE;
}
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
Bool Drawable::getShouldAnimate( Bool considerPower ) const
{
const Object *obj = getObject();
if (obj)
{
if (considerPower && obj->testScriptStatusBit(OBJECT_STATUS_SCRIPT_UNPOWERED))
return FALSE;
if (obj->isDisabled())
{
if( obj->isDisabledByType( DISABLED_HACKED )
|| obj->isDisabledByType( DISABLED_PARALYZED )
|| obj->isDisabledByType( DISABLED_EMP )
// srj sez: unmanned things also should not animate. (eg, gattling tanks,
// which have a slight barrel animation even when at rest). if this causes
// a problem, we will need to fix gattling tanks in another way.
|| obj->isDisabledByType( DISABLED_UNMANNED )
)
return FALSE;
if (considerPower && obj->isDisabledByType(DISABLED_UNDERPOWERED))
{
// We only pause animations if this draw module says so
// By checking for the others first, we prevent underpower from allowing a True on an addition disable type
return FALSE;
}
}
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
// this method must ONLY be called from the client, NEVER From the logic, not even indirectly.
Bool Drawable::clientOnly_getFirstRenderObjInfo(Coord3D* pos, Real* boundingSphereRadius, Matrix3D* transform)
{
DrawModule** dm = getDrawModules();
const ObjectDrawInterface* di = (dm && *dm) ? (*dm)->getObjectDrawInterface() : NULL;
if (di)
{
return di->clientOnly_getRenderObjInfo(pos, boundingSphereRadius, transform);
}
return false;
}
//-------------------------------------------------------------------------------------------------
Bool Drawable::getProjectileLaunchOffset(WeaponSlotType wslot, Int specificBarrelToUse, Matrix3D* launchPos, WhichTurretType tur, Coord3D* turretRotPos, Coord3D* turretPitchPos) const
{
for (const DrawModule** dm = getDrawModules(); *dm; ++dm)
{
const ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di && di->getProjectileLaunchOffset(m_conditionState, wslot, specificBarrelToUse, launchPos, tur, turretRotPos, turretPitchPos))
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
void Drawable::setAnimationLoopDuration(UnsignedInt numFrames)
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->setAnimationLoopDuration(numFrames);
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::setAnimationCompletionTime(UnsignedInt numFrames)
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->setAnimationCompletionTime(numFrames);
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::updateSubObjects()
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->updateSubObjects();
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::showSubObject( const AsciiString& name, Bool show )
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
{
di->showSubObject( name, show );
}
}
}
#ifdef ALLOW_ANIM_INQUIRIES
// srj sez: not sure if this is a good idea, for net sync reasons...
//-------------------------------------------------------------------------------------------------
/**
This call asks, "In the current animation (if any) how far along are you, from 0.0f to 1.0f".
*/
Real Drawable::getAnimationScrubScalar( void ) const // lorenzen
{
for (const DrawModule** dm = getDrawModules(); *dm; ++dm)
{
const ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
{
return di->getAnimationScrubScalar();
}
}
return 0.0f;
}
#endif
//-------------------------------------------------------------------------------------------------
Int Drawable::getPristineBonePositions(const char* boneNamePrefix, Int startIndex, Coord3D* positions, Matrix3D* transforms, Int maxBones) const
{
Int count = 0;
for (const DrawModule** dm = getDrawModules(); *dm; ++dm)
{
if (maxBones <= 0)
break;
const ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
{
Int subcount =
di->getPristineBonePositionsForConditionState(m_conditionState, boneNamePrefix, startIndex, positions, transforms, maxBones);
if (subcount > 0)
{
count += subcount;
if (positions)
positions += subcount;
if (transforms)
transforms += subcount;
maxBones -= subcount;
}
}
}
return count;
}
//-------------------------------------------------------------------------------------------------
Int Drawable::getCurrentClientBonePositions(const char* boneNamePrefix, Int startIndex, Coord3D* positions, Matrix3D* transforms, Int maxBones) const
{
Int count = 0;
for (const DrawModule** dm = getDrawModules(); *dm; ++dm)
{
if (maxBones <= 0)
break;
const ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
{
Int subcount =
di->getCurrentBonePositions(boneNamePrefix, startIndex, positions, transforms, maxBones);
if (subcount > 0)
{
count += subcount;
if (positions)
positions += subcount;
if (transforms)
transforms += subcount;
maxBones -= subcount;
}
}
}
return count;
}
//-------------------------------------------------------------------------------------------------
Bool Drawable::getCurrentWorldspaceClientBonePositions(const char* boneName, Matrix3D& transform) const
{
for (const DrawModule** dm = getDrawModules(); *dm; ++dm)
{
const ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di && di->getCurrentWorldspaceClientBonePositions(boneName, transform))
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
/** Attach to a particle system */
//-------------------------------------------------------------------------------------------------
void Drawable::attachToParticleSystem( Particle *p )
{
m_particle = p;
}
//-------------------------------------------------------------------------------------------------
/** Detach from a particle system, if attached */
//-------------------------------------------------------------------------------------------------
void Drawable::detachFromParticleSystem( void )
{
if (m_particle)
{
m_particle->detachDrawable();
m_particle = NULL;
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::setTerrainDecal(TerrainDecalType type)
{
if (m_terrainDecalType == type)
return;
m_terrainDecalType=type;
DrawModule** dm = getDrawModules();
//Only the first draw module gets a decal to prevent stacking.
//Should be okay as long as we keep the primary object in the
//first module.
if (*dm)
(*dm)->setTerrainDecal(type);
}
//-------------------------------------------------------------------------------------------------
void Drawable::setTerrainDecalSize(Real x, Real y)
{
DrawModule** dm = getDrawModules();
if (*dm)
(*dm)->setTerrainDecalSize(x,y);
}
//-------------------------------------------------------------------------------------------------
void Drawable::setTerrainDecalFadeTarget(Real target, Real rate)
{
if (m_decalOpacityFadeTarget != target)
{
m_decalOpacityFadeTarget = target;
m_decalOpacityFadeRate = rate;
}
//else
// m_decalOpacityFadeRate = 0;
}
//-------------------------------------------------------------------------------------------------
void Drawable::setShadowsEnabled(Bool enable)
{
// set status bit
if( enable )
setDrawableStatus( DRAWABLE_STATUS_SHADOWS );
else
clearDrawableStatus( DRAWABLE_STATUS_SHADOWS );
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->setShadowsEnabled(enable);
}
}
//-------------------------------------------------------------------------------------------------
/**frees all shadow resources used by this module - used by Options screen.*/
void Drawable::releaseShadows(void)
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->releaseShadows();
}
}
//-------------------------------------------------------------------------------------------------
/**create shadow resources if not already present. Used by Options screen.*/
void Drawable::allocateShadows(void)
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->allocateShadows();
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::setFullyObscuredByShroud(Bool fullyObscured)
{
if (m_drawableFullyObscuredByShroud != fullyObscured)
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->setFullyObscuredByShroud(fullyObscured);
}
m_drawableFullyObscuredByShroud = fullyObscured;
}
}
//-------------------------------------------------------------------------------------------------
/** Set drawable's "selected" status, if not already set. Also update running
* total count of selected drawables. */
//-------------------------------------------------------------------------------------------------
void Drawable::friend_setSelected( void )
{
if(isSelected() == false)
{
m_selected = TRUE;
onSelected();
}
}
//-------------------------------------------------------------------------------------------------
/** Clear drawable's "selected" status, if not already clear. Also update running
* total count of selected drawables. */
//-------------------------------------------------------------------------------------------------
void Drawable::friend_clearSelected( void )
{
if(isSelected())
{
m_selected = FALSE;
onUnselected();
}
}
// ------------------------------------------------------------------------------------------------
/** Flash the drawable with the color */
// ------------------------------------------------------------------------------------------------
void Drawable::colorFlash( const RGBColor* color, UnsignedInt decayFrames, UnsignedInt attackFrames, UnsignedInt sustainAtPeak )
{
if (m_colorTintEnvelope == NULL)
m_colorTintEnvelope = newInstance(TintEnvelope);
if( color )
{
m_colorTintEnvelope->play( color, attackFrames, decayFrames, sustainAtPeak);
}
else
{
RGBColor white;
white.setFromInt(0xffffffff);
m_colorTintEnvelope->play( &white );
}
// make sure the tint color is unlocked so we "fade back down" to normal
clearDrawableStatus( DRAWABLE_STATUS_TINT_COLOR_LOCKED );
}
// ------------------------------------------------------------------------------------------------
/** Tint a drawable a specified color */
// ------------------------------------------------------------------------------------------------
void Drawable::colorTint( const RGBColor* color )
{
if( color )
{
// set the color via color flash
colorFlash( color, 0, 0, TRUE );
// lock the tint color so the flash never "fades back down"
setDrawableStatus( DRAWABLE_STATUS_TINT_COLOR_LOCKED );
}
else
{
if (m_colorTintEnvelope == NULL)
m_colorTintEnvelope = newInstance(TintEnvelope);
// remove the tint applied to the object
m_colorTintEnvelope->rest();
// set the tint as unlocked so we can flash and stuff again
clearDrawableStatus( DRAWABLE_STATUS_TINT_COLOR_LOCKED );
}
}
//-------------------------------------------------------------------------------------------------
/** Gathering point for all things besides actual selection that must happen on selection */
//-------------------------------------------------------------------------------------------------
void Drawable::onSelected()
{
flashAsSelected();//much simpler
Object* obj = getObject();
if ( obj )
{
ContainModuleInterface* contain = obj->getContain();
if ( contain )
{
contain->clientVisibleContainedFlashAsSelected();
}
}
} // end onSelected
//-------------------------------------------------------------------------------------------------
/** Gathering point for all things besides actual selection that must happen on deselection */
//-------------------------------------------------------------------------------------------------
void Drawable::onUnselected()
{
// nothing
}
//-------------------------------------------------------------------------------------------------
/** get FX color value to add to ALL LIGHTS when drawing */
//-------------------------------------------------------------------------------------------------
const Vector3 * Drawable::getTintColor( void ) const
{
if ( m_colorTintEnvelope )
{
if (m_colorTintEnvelope->isEffective())
{
return m_colorTintEnvelope->getColor();
}
}
return NULL;
}
//-------------------------------------------------------------------------------------------------
/** get SELECTION color value to add to ALL LIGHTS when drawing */
//-------------------------------------------------------------------------------------------------
const Vector3 * Drawable::getSelectionColor( void ) const
{
if (m_selectionFlashEnvelope)
{
if (m_selectionFlashEnvelope->isEffective())
{
return m_selectionFlashEnvelope->getColor();
}
}
return NULL;
}
//-------------------------------------------------------------------------------------------------
/** fades the object out gradually...how gradually is determined by number of frames */
//-------------------------------------------------------------------------------------------------
void Drawable::fadeOut( UnsignedInt frames ) ///< cloak object
{
setDrawableOpacity(1.0);
m_fadeMode = FADING_OUT;
m_timeToFade = frames;
m_timeElapsedFade = 0;
}
//-------------------------------------------------------------------------------------------------
/** fades the object in gradually...how gradually is determined by number of frames */
//-------------------------------------------------------------------------------------------------
void Drawable::fadeIn( UnsignedInt frames ) ///< decloak object
{
setDrawableOpacity(0.0);
m_fadeMode = FADING_IN;
m_timeToFade = frames;
m_timeElapsedFade = 0;
}
//-------------------------------------------------------------------------------------------------
const Real Drawable::getScale (void) const
{
return m_instanceScale;
// return getTemplate()->getAssetScale();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void Drawable::reactToBodyDamageStateChange(BodyDamageType newState)
{
static const ModelConditionFlagType TheDamageMap[BODYDAMAGETYPE_COUNT] =
{
MODELCONDITION_INVALID,
MODELCONDITION_DAMAGED,
MODELCONDITION_REALLY_DAMAGED,
MODELCONDITION_RUBBLE,
};
ModelConditionFlags newDamage;
if (TheDamageMap[newState] != MODELCONDITION_INVALID)
newDamage.set(TheDamageMap[newState]);
clearAndSetModelConditionFlags(
MAKE_MODELCONDITION_MASK3(MODELCONDITION_DAMAGED, MODELCONDITION_REALLY_DAMAGED, MODELCONDITION_RUBBLE),
newDamage);
startAmbientSound(newState, TheGlobalData->m_timeOfDay);
}
//-------------------------------------------------------------------------------------------------
void Drawable::setEffectiveOpacity( Real pulseFactor, Real explicitOpacity /* = -1.0f */)
{
if( explicitOpacity != -1.0f )
{
m_stealthOpacity = MIN( 1.0f, MAX( 0.0f, explicitOpacity ) );
}
Real pf = MIN(1.0f, MAX(0.0f, pulseFactor));
Real pulseMargin = (1.0f - m_stealthOpacity);
Real pulseAmount = pulseMargin * pf;
m_effectiveStealthOpacity = m_stealthOpacity + pulseAmount;
} ///< get alpha/opacity value used to override defaults when drawing.
//-------------------------------------------------------------------------------------------------
/** update is called once per frame */
//-------------------------------------------------------------------------------------------------
//DECLARE_PERF_TIMER(updateDrawable)
void Drawable::updateDrawable( void )
{
//USE_PERF_TIMER(updateDrawable)
UnsignedInt now = TheGameLogic->getFrame();
Object *obj = getObject();
{
for (ClientUpdateModule** cu = getClientUpdateModules(); cu && *cu; ++cu)
{
(*cu)->clientUpdate();
}
}
{
// handle fading in or out
if (m_fadeMode != FADING_NONE)
{
Real numer = (m_fadeMode == FADING_IN) ? (m_timeElapsedFade) : (m_timeToFade-m_timeElapsedFade);
setDrawableOpacity(numer/(Real)m_timeToFade);
++m_timeElapsedFade;
if (m_timeElapsedFade > m_timeToFade)
m_fadeMode = FADING_NONE;
}
}
if ( getTerrainDecalType() != TERRAIN_DECAL_NONE )
{
DrawModule** dm = getDrawModules();
if (*dm)
{
if (m_decalOpacityFadeRate != 0)
{
//LERP
(*dm)->setTerrainDecalOpacity(m_decalOpacity);
m_decalOpacity += m_decalOpacityFadeRate;
}
//---------------
if (m_decalOpacityFadeRate < 0 && m_decalOpacity <= 0 )
{
m_decalOpacityFadeRate = 0.0f;
m_decalOpacity = 0.0f;
this->setTerrainDecal(TERRAIN_DECAL_NONE);
}
else if (m_decalOpacityFadeRate > 0 && m_decalOpacity >= 1.0f)
{
m_decalOpacity = 1.0f;
m_decalOpacityFadeRate = 0.0f;
(*dm)->setTerrainDecalOpacity(m_decalOpacity);
}
}//end if (*dm)
}
else
m_decalOpacity = 0;
{
if (m_expirationDate != 0 && now >= m_expirationDate)
{
DEBUG_ASSERTCRASH(obj == NULL, ("Drawables with Objects should not have expiration dates!"));
TheGameClient->destroyDrawable(this);
return;
}
}
{
if (m_flashCount > 0 && (TheGameClient->getFrame() % DRAWABLE_FRAMES_PER_FLASH) == 0)
{
RGBColor tmp;
tmp.setFromInt(m_flashColor);
colorFlash(&tmp);
m_flashCount--;
}
}
//Lets figure out whether we should be changing colors right about now
// we'll use an ifelseif ladder since we are scanning bits
if( m_prevTintStatus != m_tintStatus )// edge test
{
if ( testTintStatus( TINT_STATUS_DISABLED ) )
{
if (m_colorTintEnvelope == NULL)
m_colorTintEnvelope = newInstance(TintEnvelope);
m_colorTintEnvelope->play( &DARK_GRAY_DISABLED_COLOR, 30, 30, SUSTAIN_INDEFINITELY);
}
// else if ( testTintStatus( TINT_STATUS_POISONED) )
// {
// if (m_colorTintEnvelope == NULL)
// m_colorTintEnvelope = newInstance(TintEnvelope);
// m_colorTintEnvelope->play( &SICKLY_GREEN_POISONED_COLOR, 30, 30, SUSTAIN_INDEFINITELY);
// }
// else if ( testTintStatus( TINT_STATUS_IRRADIATED) )
// {
// if (m_colorTintEnvelope == NULL)
// m_colorTintEnvelope = newInstance(TintEnvelope);
// m_colorTintEnvelope->play( &RED_IRRADIATED_COLOR, 30, 30, SUSTAIN_INDEFINITELY);
// }
else
{
// NO TINTING SHOULD BE PRESENT
if (m_colorTintEnvelope == NULL)
m_colorTintEnvelope = newInstance(TintEnvelope);
m_colorTintEnvelope->release(); // head on back to normal, now
}
}
m_prevTintStatus = m_tintStatus;//for next frame
if ( obj )
{
if ( ! obj->isEffectivelyDead() )
clearTintStatus( TINT_STATUS_IRRADIATED); // so the res glow stops when not exposed
}
if (m_colorTintEnvelope)
m_colorTintEnvelope->update(); // defector fx, disable fx, etc...
if (m_selectionFlashEnvelope)
m_selectionFlashEnvelope->update(); // selection flashing
//If we have an ambient sound, and we aren't currently playing it, attempt to play it now
if( m_ambientSound && m_ambientSoundEnabled && !m_ambientSound->m_event.getEventName().isEmpty() && !m_ambientSound->m_event.isCurrentlyPlaying() )
{
startAmbientSound();
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::flashAsSelected( const RGBColor *color ) ///< drawable takes care of the details if you spec no color
{
if (m_selectionFlashEnvelope == NULL)
m_selectionFlashEnvelope = newInstance(TintEnvelope);
if ( color )
{
m_selectionFlashEnvelope->play( color, 0, 4 );
}
else
{
Object *obj = getObject();
if (obj)
{
RGBColor tempColor;
if (TheGlobalData->m_selectionFlashHouseColor)
tempColor.setFromInt(obj->getIndicatorColor());
else
tempColor.setFromInt(0xffffffff);//white
Real saturation = TheGlobalData->m_selectionFlashSaturationFactor;
saturateRGB( tempColor, saturation );
m_selectionFlashEnvelope->play( &tempColor, 0, 4 );
}
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::applyPhysicsXform(Matrix3D* mtx)
{
const Object *obj = getObject();
if( !obj || obj->isDisabledByType( DISABLED_HELD ) || !TheGlobalData->m_showClientPhysics )
{
return;
}
Bool frozen = TheTacticalView->isTimeFrozen() && !TheTacticalView->isCameraMovementFinished();
frozen = frozen || TheScriptEngine->isTimeFrozenDebug() || TheScriptEngine->isTimeFrozenScript();
if (frozen)
return;
PhysicsXformInfo info;
if (calcPhysicsXform(info))
{
mtx->Translate(0.0f, 0.0f, info.m_totalZ);
mtx->Rotate_Y( info.m_totalPitch );
mtx->Rotate_X( -info.m_totalRoll );
mtx->Rotate_Z( info.m_totalYaw );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool Drawable::calcPhysicsXform(PhysicsXformInfo& info)
{
const Object* obj = getObject();
const AIUpdateInterface *ai = obj ? obj->getAIUpdateInterface() : NULL;
Bool hasPhysicsXform = false;
if (ai)
{
const Locomotor *locomotor = ai->getCurLocomotor();
if (locomotor)
{
switch (locomotor->getAppearance())
{
case LOCO_WHEELS_FOUR:
calcPhysicsXformWheels(locomotor, info);
hasPhysicsXform = true;
break;
case LOCO_TREADS:
calcPhysicsXformTreads(locomotor, info);
hasPhysicsXform = true;
break;
case LOCO_HOVER:
case LOCO_WINGS:
calcPhysicsXformHoverOrWings(locomotor, info);
hasPhysicsXform = true;
break;
case LOCO_THRUST:
calcPhysicsXformThrust(locomotor, info);
hasPhysicsXform = true;
break;
}
}
}
if (hasPhysicsXform)
{
// HOTFIX: Ensure that we are not passing denormalized values back to caller
// @todo remove hotfix
if (info.m_totalPitch>-1e-20f&&info.m_totalPitch<1e-20f)
info.m_totalPitch=0.f;
if (info.m_totalRoll>-1e-20f&&info.m_totalRoll<1e-20f)
info.m_totalRoll=0.f;
if (info.m_totalYaw>-1e-20f&&info.m_totalYaw<1e-20f)
info.m_totalYaw=0.f;
if (info.m_totalZ>-1e-20f&&info.m_totalZ<1e-20f)
info.m_totalZ=0.f;
}
return hasPhysicsXform;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::calcPhysicsXformThrust( const Locomotor *locomotor, PhysicsXformInfo& info )
{
if (m_locoInfo == NULL)
m_locoInfo = newInstance(DrawableLocoInfo);
Real THRUST_ROLL = locomotor->getThrustRoll();
Real WOBBLE_RATE = locomotor->getWobbleRate();
Real MAX_WOBBLE = locomotor->getMaxWobble();
Real MIN_WOBBLE = locomotor->getMinWobble();
//
// this is a kind of quick thrust implementation cause we need scud missiles to wobble *now*,
// we deal with just adjusting pitch, yaw, and roll just a little bit
//
if( WOBBLE_RATE )
{
if( m_locoInfo->m_wobble >= 1.0f )
{
if( m_locoInfo->m_pitch < MAX_WOBBLE - WOBBLE_RATE * 2 )
{
m_locoInfo->m_pitch += WOBBLE_RATE;
m_locoInfo->m_yaw += WOBBLE_RATE;
} // end if
else
{
m_locoInfo->m_pitch += (WOBBLE_RATE / 2.0f);
m_locoInfo->m_yaw += (WOBBLE_RATE / 2.0f);
} // end else
if( m_locoInfo->m_pitch >= MAX_WOBBLE )
m_locoInfo->m_wobble = -1.0f;
} // end if
else
{
if( m_locoInfo->m_pitch >= MIN_WOBBLE + WOBBLE_RATE * 2.0f )
{
m_locoInfo->m_pitch -= WOBBLE_RATE;
m_locoInfo->m_yaw -= WOBBLE_RATE;
} // end if
else
{
m_locoInfo->m_pitch -= (WOBBLE_RATE / 2.0f);
m_locoInfo->m_yaw -= (WOBBLE_RATE / 2.0f);
} // end else
if( m_locoInfo->m_pitch <= MIN_WOBBLE )
m_locoInfo->m_wobble = 1.0f;
} // end else
info.m_totalPitch = m_locoInfo->m_pitch;
info.m_totalYaw = m_locoInfo->m_yaw;
} // end if, wobble exists
if( THRUST_ROLL )
{
m_locoInfo->m_roll += THRUST_ROLL;
info.m_totalRoll = m_locoInfo->m_roll;
} // end if
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void Drawable::calcPhysicsXformHoverOrWings( const Locomotor *locomotor, PhysicsXformInfo& info )
{
if (m_locoInfo == NULL)
m_locoInfo = newInstance(DrawableLocoInfo);
const Real ACCEL_PITCH_LIMIT = locomotor->getAccelPitchLimit();
const Real PITCH_STIFFNESS = locomotor->getPitchStiffness();
const Real ROLL_STIFFNESS = locomotor->getRollStiffness();
const Real PITCH_DAMPING = locomotor->getPitchDamping();
const Real ROLL_DAMPING = locomotor->getRollDamping();
const Real Z_VEL_PITCH_COEFF = locomotor->getPitchByZVelCoef();
const Real FORWARD_VEL_COEFF = locomotor->getForwardVelCoef();
const Real LATERAL_VEL_COEFF = locomotor->getLateralVelCoef();
const Real FORWARD_ACCEL_COEFF = locomotor->getForwardAccelCoef();
const Real LATERAL_ACCEL_COEFF = locomotor->getLateralAccelCoef();
const Real UNIFORM_AXIAL_DAMPING = locomotor->getUniformAxialDamping();
// get object from logic
Object *obj = getObject();
if (obj == NULL)
return;
AIUpdateInterface *ai = obj->getAIUpdateInterface();
if (ai == NULL)
return;
// get object physics state
PhysicsBehavior *physics = obj->getPhysics();
if (physics == NULL)
return;
// get our position and direction vector
//const Coord3D *pos = getPosition();
const Coord3D* dir = getUnitDirectionVector2D();
const Coord3D* accel = physics->getAcceleration();
const Coord3D* vel = physics->getVelocity();
m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * m_locoInfo->m_pitch) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper
m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_roll) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper
m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING;
m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING;
// process chassis acceleration dynamics - damp back towards zero
m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper
m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate;
m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper
m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate;
// compute total pitch and roll of tank
info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch;
info.m_totalRoll = m_locoInfo->m_roll + m_locoInfo->m_accelerationRoll;
if (physics->isMotive())
{
if (Z_VEL_PITCH_COEFF != 0.0f)
{
const Real TINY_DZ = 0.001f;
if (fabs(vel->z) > TINY_DZ)
{
Real pitch = atan2(vel->z, sqrt(sqr(vel->x)+sqr(vel->y)));
m_locoInfo->m_pitch -= Z_VEL_PITCH_COEFF * pitch;
}
}
// cause the chassis to pitch & roll in reaction to current speed
Real forwardVel = dir->x * vel->x + dir->y * vel->y;
m_locoInfo->m_pitch += -(FORWARD_VEL_COEFF * forwardVel);
Real lateralVel = -dir->y * vel->x + dir->x * vel->y;
m_locoInfo->m_roll += -(LATERAL_VEL_COEFF * lateralVel);
// cause the chassis to pitch & roll in reaction to acceleration/deceleration
Real forwardAccel = dir->x * accel->x + dir->y * accel->y;
m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel);
Real lateralAccel = -dir->y * accel->x + dir->x * accel->y;
m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel);
}
// limit acceleration pitch and roll
if (m_locoInfo->m_accelerationPitch > ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationPitch = ACCEL_PITCH_LIMIT;
else if (m_locoInfo->m_accelerationPitch < -ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationPitch = -ACCEL_PITCH_LIMIT;
if (m_locoInfo->m_accelerationRoll > ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationRoll = ACCEL_PITCH_LIMIT;
else if (m_locoInfo->m_accelerationRoll < -ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationRoll = -ACCEL_PITCH_LIMIT;
info.m_totalZ = 0.0f;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void Drawable::calcPhysicsXformTreads( const Locomotor *locomotor, PhysicsXformInfo& info )
{
if (m_locoInfo == NULL)
m_locoInfo = newInstance(DrawableLocoInfo);
const Real OVERLAP_SHRINK_FACTOR = 0.8f;
const Real FLATTENED_OBJECT_HEIGHT = 0.5f;
const Real LEAVE_OVERLAP_PITCH_KICK = PI/128;
const Real OVERLAP_ROUGH_VIBRATION_FACTOR = 5.0f;
const Real MAX_ROUGH_VIBRATION = 0.5f;
const Real ACCEL_PITCH_LIMIT = locomotor->getAccelPitchLimit();
const Real PITCH_STIFFNESS = locomotor->getPitchStiffness();
const Real ROLL_STIFFNESS = locomotor->getRollStiffness();
const Real PITCH_DAMPING = locomotor->getPitchDamping();
const Real ROLL_DAMPING = locomotor->getRollDamping();
const Real FORWARD_ACCEL_COEFF = locomotor->getForwardAccelCoef();
const Real LATERAL_ACCEL_COEFF = locomotor->getLateralAccelCoef();
const Real UNIFORM_AXIAL_DAMPING = locomotor->getUniformAxialDamping();
// get object from logic
Object *obj = getObject();
if (obj == NULL)
return;
AIUpdateInterface *ai = obj->getAIUpdateInterface();
if (ai == NULL)
return ;
// get object physics state
PhysicsBehavior *physics = obj->getPhysics();
if (physics == NULL)
return;
// get our position and direction vector
const Coord3D *pos = getPosition();
const Coord3D *dir = getUnitDirectionVector2D();
const Coord3D *accel = physics->getAcceleration();
const Coord3D *vel = physics->getVelocity();
// compute perpendicular (2d)
Coord3D perp;
perp.x = -dir->y;
perp.y = dir->x;
perp.z = 0.0f;
// find pitch and roll of terrain under chassis
Coord3D normal;
/* Real hheight = */ TheTerrainLogic->getLayerHeight( pos->x, pos->y, obj->getLayer(), &normal );
// override surface normal if we are overlapping another object - crushing it
Real overlapZ = 0.0f;
// get object we are currently overlapping, if any
Object* overlapped = TheGameLogic->findObjectByID(physics->getCurrentOverlap());
if (overlapped && overlapped->isKindOf(KINDOF_SHRUBBERY)) {
overlapped = NULL; // We just smash through shrubbery. jba.
}
if (overlapped)
{
const Coord3D *overPos = overlapped->getPosition();
Real dx = overPos->x - pos->x;
Real dy = overPos->y - pos->y;
Real centerDistSqr = sqr(dx) + sqr(dy);
// compute maximum distance between objects, if their edges just touched
Real ourSize = getDrawableGeometryInfo().getBoundingCircleRadius();
Real otherSize = overlapped->getGeometryInfo().getBoundingCircleRadius();
Real maxCenterDist = otherSize + ourSize;
// shrink the overlap distance a bit to avoid floating
maxCenterDist *= OVERLAP_SHRINK_FACTOR;
if (centerDistSqr < sqr(maxCenterDist))
{
Real centerDist = sqrtf(centerDistSqr);
Real amount = 1.0f - centerDist/maxCenterDist;
if (amount < 0.0f)
amount = 0.0f;
else if (amount > 1.0f)
amount = 1.0f;
// rough vibrations proportional to speed when we drive over something
Real rough = (vel->x*vel->x + vel->y*vel->y) * OVERLAP_ROUGH_VIBRATION_FACTOR;
if (rough > MAX_ROUGH_VIBRATION)
rough = MAX_ROUGH_VIBRATION;
Real height = overlapped->getGeometryInfo().getMaxHeightAbovePosition();
// do not "go up" flattened crushed things
Bool flat = false;
if (overlapped->isKindOf(KINDOF_LOW_OVERLAPPABLE) ||
overlapped->isKindOf(KINDOF_INFANTRY) ||
(overlapped->getBodyModule()->getFrontCrushed() && overlapped->getBodyModule()->getBackCrushed()))
{
flat = true;
height = FLATTENED_OBJECT_HEIGHT;
}
if (amount < FLATTENED_OBJECT_HEIGHT && flat == false)
{
overlapZ = height * 2.0f * amount;
// compute vector along "surface"
// not proportional to actual geometry to avoid overlay steep inclines, etc
Coord3D v;
v.x = dx/centerDist;
v.y = dy/centerDist;
v.z = 0.2f; // 0.25
Coord3D up;
up.x = GameClientRandomValueReal( -rough, rough );
up.y = GameClientRandomValueReal( -rough, rough );
up.z = 1.0f;
up.normalize();
Coord3D prp;
prp.crossProduct( &v, &up, &prp );
normal.crossProduct( &prp, &v, &normal );
// compute unit normal
normal.normalize();
}
else
{
// sitting on top of object
overlapZ = height;
normal.x = GameClientRandomValueReal( -rough, rough );
normal.y = GameClientRandomValueReal( -rough, rough );
normal.z = 1.0f;
normal.normalize();
}
}
}
else // no overlap this frame
{
// if we had an overlap last frame, and we're now in the air, give a
// kick to the pitch for effect
if (physics->getPreviousOverlap() != INVALID_ID && m_locoInfo->m_overlapZ > 0.0f)
m_locoInfo->m_pitchRate += LEAVE_OVERLAP_PITCH_KICK;
}
Real dot = normal.x * dir->x + normal.y * dir->y;
Real groundPitch = dot * (PI/2.0f);
dot = normal.x * perp.x + normal.y * perp.y;
Real groundRoll = dot * (PI/2.0f);
// process chassis suspension dynamics - damp back towards groundPitch
// the ground can only push back if we're touching it
if (overlapped || m_locoInfo->m_overlapZ <= 0.0f)
{
m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper
if (m_locoInfo->m_pitchRate > 0.0f)
m_locoInfo->m_pitchRate *= 0.5f;
m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper
}
m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING;
m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING;
// process chassis recoil dynamics - damp back towards zero
m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper
m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate;
m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper
m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate;
// compute total pitch and roll of tank
info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch;
info.m_totalRoll = m_locoInfo->m_roll + m_locoInfo->m_accelerationRoll;
if (physics->isMotive())
{
// cause the chassis to pitch & roll in reaction to acceleration/deceleration
Real forwardAccel = dir->x * accel->x + dir->y * accel->y;
m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel);
Real lateralAccel = -dir->y * accel->x + dir->x * accel->y;
m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel);
}
#ifdef RECOIL_FROM_BEING_DAMAGED
// recoil from being hit
/// @todo Recoil needs to be based on sane damage amounts (MSB)
const DamageInfo *damageInfo = obj->getBodyModule()->getLastDamageInfo();
if (damageInfo)
{
if (obj->getBodyModule()->getLastDamageTimestamp() > m_lastDamageTimestamp && damageInfo->in.m_amount > RECOIL_DAMAGE)
{
Object *attacker = TheGameLogic->getObject( damageInfo->in.m_sourceID );
if (attacker)
{
Coord3D to;
ThePartitionManager->getVectorTo( obj, attacker, FROM_CENTER_2D, &to );
to.normalize();
Real forward = dir->x * to.x + dir->y * to.y;
Real lateral = perp.x * to.x + perp.y * to.y;
Real recoil = PI/16.0f * GameClientRandomValueReal( 0.5f, 1.0f );
m_locoInfo->m_accelerationPitchRate -= recoil * forward;
m_locoInfo->m_accelerationRollRate -= recoil * lateral;
}
m_lastDamageTimestamp = obj->getBodyModule()->getLastDamageTimestamp();
}
}
#endif
// limit recoil pitch and roll
if (m_locoInfo->m_accelerationPitch > ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationPitch = ACCEL_PITCH_LIMIT;
else if (m_locoInfo->m_accelerationPitch < -ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationPitch = -ACCEL_PITCH_LIMIT;
if (m_locoInfo->m_accelerationRoll > ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationRoll = ACCEL_PITCH_LIMIT;
else if (m_locoInfo->m_accelerationRoll < -ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationRoll = -ACCEL_PITCH_LIMIT;
// adjust z
if (overlapZ > m_locoInfo->m_overlapZ)
{
m_locoInfo->m_overlapZ = overlapZ;
/// @todo Z needs to accelerate/decelerate, not be directly set (MSB)
// m_locoInfo->m_overlapZ += 0.4f;
m_locoInfo->m_overlapZVel = 0.0f;
}
Real ztmp = m_locoInfo->m_overlapZ/2.0f;
// do fake Z physics
if (m_locoInfo->m_overlapZ > 0.0f)
{
m_locoInfo->m_overlapZVel -= 0.2f;
m_locoInfo->m_overlapZ += m_locoInfo->m_overlapZVel;
}
if (m_locoInfo->m_overlapZ <= 0.0f)
{
m_locoInfo->m_overlapZ = 0.0f;
m_locoInfo->m_overlapZVel = 0.0f;
}
info.m_totalZ = ztmp;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void Drawable::calcPhysicsXformWheels( const Locomotor *locomotor, PhysicsXformInfo& info )
{
if (m_locoInfo == NULL)
m_locoInfo = newInstance(DrawableLocoInfo);
const Real ACCEL_PITCH_LIMIT = locomotor->getAccelPitchLimit();
const Real BOUNCE_ANGLE_KICK = locomotor->getBounceKick();
const Real PITCH_STIFFNESS = locomotor->getPitchStiffness();
const Real ROLL_STIFFNESS = locomotor->getRollStiffness();
const Real PITCH_DAMPING = locomotor->getPitchDamping();
const Real ROLL_DAMPING = locomotor->getRollDamping();
const Real FORWARD_ACCEL_COEFF = locomotor->getForwardAccelCoef();
const Real LATERAL_ACCEL_COEFF = locomotor->getLateralAccelCoef();
const Real UNIFORM_AXIAL_DAMPING = locomotor->getUniformAxialDamping();
const Real MAX_SUSPENSION_EXTENSION = locomotor->getMaxWheelExtension(); //-2.3f;
// const Real MAX_SUSPENSION_COMPRESSION = locomotor->getMaxWheelCompression(); //1.4f;
const Real WHEEL_ANGLE = locomotor->getWheelTurnAngle(); //PI/8;
const Bool DO_WHEELS = locomotor->hasSuspension();
// get object from logic
Object *obj = getObject();
if (obj == NULL)
return;
AIUpdateInterface *ai = obj->getAIUpdateInterface();
if (ai == NULL)
return ;
// get object physics state
PhysicsBehavior *physics = obj->getPhysics();
if (physics == NULL)
return ;
// get our position and direction vector
const Coord3D *pos = getPosition();
const Coord3D *dir = getUnitDirectionVector2D();
const Coord3D *accel = physics->getAcceleration();
// compute perpendicular (2d)
Coord3D perp;
perp.x = -dir->y;
perp.y = dir->x;
perp.z = 0.0f;
// find pitch and roll of terrain under chassis
Coord3D normal;
Real hheight = TheTerrainLogic->getLayerHeight( pos->x, pos->y, obj->getLayer(), &normal );
Real dot = normal.x * dir->x + normal.y * dir->y;
Real groundPitch = dot * (PI/2.0f);
dot = normal.x * perp.x + normal.y * perp.y;
Real groundRoll = dot * (PI/2.0f);
Bool airborne = obj->isSignificantlyAboveTerrain();
if (airborne)
{
if (DO_WHEELS)
{
// Wheels extend when airborne.
m_locoInfo->m_wheelInfo.m_framesAirborne = 0;
m_locoInfo->m_wheelInfo.m_framesAirborneCounter++;
if (pos->z - hheight > -MAX_SUSPENSION_EXTENSION)
{
m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f;
m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (MAX_SUSPENSION_EXTENSION - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f;
}
else
{
m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f;
m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (0 - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f;
}
}
// Calculate suspension info.
Real length = obj->getGeometryInfo().getMajorRadius();
Real width = obj->getGeometryInfo().getMinorRadius();
Real pitchHeight = length*Sin(m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch - groundPitch);
Real rollHeight = width*Sin(m_locoInfo->m_roll + m_locoInfo->m_accelerationRoll - groundRoll);
info.m_totalZ = fabs(pitchHeight)/4 + fabs(rollHeight)/4;
return; // maintain the same orientation while we fly through the air.
}
// Bouncy.
Real curSpeed = physics->getVelocityMagnitude();
#if 1
Real maxSpeed = ai->getCurLocomotorSpeed();
if (!airborne && curSpeed > maxSpeed/10)
{
Real factor = curSpeed/maxSpeed;
if (fabs(m_locoInfo->m_pitchRate)<factor*BOUNCE_ANGLE_KICK/4 && fabs(m_locoInfo->m_rollRate)<factor*BOUNCE_ANGLE_KICK/8)
{
// do the bouncy.
switch (GameClientRandomValue(0,3))
{
case 0:
m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor;
m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2;
break;
case 1:
m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor;
m_locoInfo->m_rollRate -= BOUNCE_ANGLE_KICK*factor/2;
break;
case 2:
m_locoInfo->m_pitchRate -= BOUNCE_ANGLE_KICK*factor;
m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2;
break;
case 3:
m_locoInfo->m_pitchRate += BOUNCE_ANGLE_KICK*factor;
m_locoInfo->m_rollRate += BOUNCE_ANGLE_KICK*factor/2;
break;
}
}
}
#endif
// process chassis suspension dynamics - damp back towards groundPitch
// the ground can only push back if we're touching it
if (!airborne)
{
m_locoInfo->m_pitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_pitch - groundPitch)) + (-PITCH_DAMPING * m_locoInfo->m_pitchRate)); // spring/damper
if (m_locoInfo->m_pitchRate > 0.0f)
m_locoInfo->m_pitchRate *= 0.5f;
m_locoInfo->m_rollRate += ((-ROLL_STIFFNESS * (m_locoInfo->m_roll - groundRoll)) + (-ROLL_DAMPING * m_locoInfo->m_rollRate)); // spring/damper
}
m_locoInfo->m_pitch += m_locoInfo->m_pitchRate * UNIFORM_AXIAL_DAMPING;
m_locoInfo->m_roll += m_locoInfo->m_rollRate * UNIFORM_AXIAL_DAMPING;
// process chassis acceleration dynamics - damp back towards zero
m_locoInfo->m_accelerationPitchRate += ((-PITCH_STIFFNESS * (m_locoInfo->m_accelerationPitch)) + (-PITCH_DAMPING * m_locoInfo->m_accelerationPitchRate)); // spring/damper
m_locoInfo->m_accelerationPitch += m_locoInfo->m_accelerationPitchRate;
m_locoInfo->m_accelerationRollRate += ((-ROLL_STIFFNESS * m_locoInfo->m_accelerationRoll) + (-ROLL_DAMPING * m_locoInfo->m_accelerationRollRate)); // spring/damper
m_locoInfo->m_accelerationRoll += m_locoInfo->m_accelerationRollRate;
// compute total pitch and roll of tank
info.m_totalPitch = m_locoInfo->m_pitch + m_locoInfo->m_accelerationPitch;
info.m_totalRoll = m_locoInfo->m_roll + m_locoInfo->m_accelerationRoll;
if (physics->isMotive())
{
// cause the chassis to pitch & roll in reaction to acceleration/deceleration
Real forwardAccel = dir->x * accel->x + dir->y * accel->y;
m_locoInfo->m_accelerationPitchRate += -(FORWARD_ACCEL_COEFF * forwardAccel);
Real lateralAccel = -dir->y * accel->x + dir->x * accel->y;
m_locoInfo->m_accelerationRollRate += -(LATERAL_ACCEL_COEFF * lateralAccel);
}
// limit acceleration pitch and roll
if (m_locoInfo->m_accelerationPitch > ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationPitch = ACCEL_PITCH_LIMIT;
else if (m_locoInfo->m_accelerationPitch < -ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationPitch = -ACCEL_PITCH_LIMIT;
if (m_locoInfo->m_accelerationRoll > ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationRoll = ACCEL_PITCH_LIMIT;
else if (m_locoInfo->m_accelerationRoll < -ACCEL_PITCH_LIMIT)
m_locoInfo->m_accelerationRoll = -ACCEL_PITCH_LIMIT;
info.m_totalZ = 0;
// Calculate suspension info.
Real length = obj->getGeometryInfo().getMajorRadius();
Real width = obj->getGeometryInfo().getMinorRadius();
Real pitchHeight = length*Sin(info.m_totalPitch-groundPitch);
Real rollHeight = width*Sin(info.m_totalRoll-groundRoll);
if (DO_WHEELS)
{
// calculate each wheel position
m_locoInfo->m_wheelInfo.m_framesAirborne = m_locoInfo->m_wheelInfo.m_framesAirborneCounter;
m_locoInfo->m_wheelInfo.m_framesAirborneCounter = 0;
TWheelInfo newInfo = m_locoInfo->m_wheelInfo;
PhysicsTurningType rotation = physics->getTurning();
if (rotation == TURN_NEGATIVE) {
newInfo.m_wheelAngle = -WHEEL_ANGLE;
} else if (rotation == TURN_POSITIVE) {
newInfo.m_wheelAngle = WHEEL_ANGLE;
} else {
newInfo.m_wheelAngle = 0;
}
if (physics->getForwardSpeed2D() < 0.0f) {
// if we're moving backwards, the wheels rotate in the opposite direction.
newInfo.m_wheelAngle = -newInfo.m_wheelAngle;
}
//
///@todo Steven/John ... please review this and make sure it makes sense (CBD)
// we're going to add the angle to the current wheel rotation ... but we're going to
// divide that number to add small angles. This allows for "smoother" wheel turning
// transitions ... and when the AI has things move in a straight line, since it's
// constantly telling the object to go left, go straight, go right, go straight,
// etc, this smaller angle we'll be adding covers the constant wheel shifting
// left and right when moving in a relatively straight line
//
#define WHEEL_SMOOTHNESS 10.0f // higher numbers add smaller angles, make it more "smooth"
m_locoInfo->m_wheelInfo.m_wheelAngle += (newInfo.m_wheelAngle - m_locoInfo->m_wheelInfo.m_wheelAngle)/WHEEL_SMOOTHNESS;
const Real SPRING_FACTOR = 0.9f;
if (pitchHeight<0) { // Front raising up
newInfo.m_frontLeftHeightOffset = SPRING_FACTOR*(pitchHeight/3+pitchHeight/2);
newInfo.m_frontRightHeightOffset = SPRING_FACTOR*(pitchHeight/3+pitchHeight/2);
newInfo.m_rearLeftHeightOffset = -pitchHeight/2 + pitchHeight/4;
newInfo.m_rearRightHeightOffset = -pitchHeight/2 + pitchHeight/4;
} else { // Back rasing up.
newInfo.m_frontLeftHeightOffset = (-pitchHeight/4+pitchHeight/2);
newInfo.m_frontRightHeightOffset = (-pitchHeight/4+pitchHeight/2);
newInfo.m_rearLeftHeightOffset = SPRING_FACTOR*(-pitchHeight/2 + -pitchHeight/3);
newInfo.m_rearRightHeightOffset = SPRING_FACTOR*(-pitchHeight/2 + -pitchHeight/3);
}
if (rollHeight>0) { // Right raising up
newInfo.m_frontRightHeightOffset += -SPRING_FACTOR*(rollHeight/3+rollHeight/2);
newInfo.m_rearRightHeightOffset += -SPRING_FACTOR*(rollHeight/3+rollHeight/2);
newInfo.m_rearLeftHeightOffset += rollHeight/2 - rollHeight/4;
newInfo.m_frontLeftHeightOffset += rollHeight/2 - rollHeight/4;
} else { // Left rasing up.
newInfo.m_frontRightHeightOffset += -rollHeight/2 + rollHeight/4;
newInfo.m_rearRightHeightOffset += -rollHeight/2 + rollHeight/4;
newInfo.m_rearLeftHeightOffset += SPRING_FACTOR*(rollHeight/3+rollHeight/2);
newInfo.m_frontLeftHeightOffset += SPRING_FACTOR*(rollHeight/3+rollHeight/2);
}
if (newInfo.m_frontLeftHeightOffset < m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset) {
// If it's going down, dampen the movement a bit
m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset += (newInfo.m_frontLeftHeightOffset - m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset)/2.0f;
} else {
m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset = newInfo.m_frontLeftHeightOffset;
}
if (newInfo.m_frontRightHeightOffset < m_locoInfo->m_wheelInfo.m_frontRightHeightOffset) {
// If it's going down, dampen the movement a bit
m_locoInfo->m_wheelInfo.m_frontRightHeightOffset += (newInfo.m_frontRightHeightOffset - m_locoInfo->m_wheelInfo.m_frontRightHeightOffset)/2.0f;
} else {
m_locoInfo->m_wheelInfo.m_frontRightHeightOffset = newInfo.m_frontRightHeightOffset;
}
if (newInfo.m_rearLeftHeightOffset < m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset) {
// If it's going down, dampen the movement a bit
m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset += (newInfo.m_rearLeftHeightOffset - m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset)/2.0f;
} else {
m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset = newInfo.m_rearLeftHeightOffset;
}
if (newInfo.m_rearRightHeightOffset < m_locoInfo->m_wheelInfo.m_rearRightHeightOffset) {
// If it's going down, dampen the movement a bit
m_locoInfo->m_wheelInfo.m_rearRightHeightOffset += (newInfo.m_rearRightHeightOffset - m_locoInfo->m_wheelInfo.m_rearRightHeightOffset)/2.0f;
} else {
m_locoInfo->m_wheelInfo.m_rearRightHeightOffset = newInfo.m_rearRightHeightOffset;
}
//m_locoInfo->m_wheelInfo = newInfo;
if (m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset<MAX_SUSPENSION_EXTENSION) {
m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset=MAX_SUSPENSION_EXTENSION;
}
if (m_locoInfo->m_wheelInfo.m_frontRightHeightOffset<MAX_SUSPENSION_EXTENSION) {
m_locoInfo->m_wheelInfo.m_frontRightHeightOffset=MAX_SUSPENSION_EXTENSION;
}
if (m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset<MAX_SUSPENSION_EXTENSION) {
m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset=MAX_SUSPENSION_EXTENSION;
}
if (m_locoInfo->m_wheelInfo.m_rearRightHeightOffset<MAX_SUSPENSION_EXTENSION) {
m_locoInfo->m_wheelInfo.m_rearRightHeightOffset=MAX_SUSPENSION_EXTENSION;
}
/*
if (m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset>MAX_SUSPENSION_COMPRESSION) {
m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset=MAX_SUSPENSION_COMPRESSION;
}
if (m_locoInfo->m_wheelInfo.m_frontRightHeightOffset>MAX_SUSPENSION_COMPRESSION) {
m_locoInfo->m_wheelInfo.m_frontRightHeightOffset=MAX_SUSPENSION_COMPRESSION;
}
if (m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset>MAX_SUSPENSION_COMPRESSION) {
m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset=MAX_SUSPENSION_COMPRESSION;
}
if (m_locoInfo->m_wheelInfo.m_rearRightHeightOffset>MAX_SUSPENSION_COMPRESSION) {
m_locoInfo->m_wheelInfo.m_rearRightHeightOffset=MAX_SUSPENSION_COMPRESSION;
}
*/
}
// If we are > 22 degrees, need to raise height;
Real divisor = 4;
Real pitch = fabs(info.m_totalPitch-groundPitch);
if (pitch>PI/8) {
divisor = ((4*PI/8) + (1*(pitch-PI/8)))/pitch;
}
info.m_totalZ += fabs(pitchHeight)/divisor;
info.m_totalZ += fabs(rollHeight)/divisor;
}
//-------------------------------------------------------------------------------------------------
/** decodes the current previous damage type and sets the ambient sound set from that. */
//-------------------------------------------------------------------------------------------------
const AudioEventRTS& Drawable::getAmbientSoundByDamage(BodyDamageType dt)
{
switch (dt)
{
case BODY_DAMAGED:
return *getTemplate()->getSoundAmbientDamaged();
case BODY_REALLYDAMAGED:
return *getTemplate()->getSoundAmbientReallyDamaged();
case BODY_RUBBLE:
return *getTemplate()->getSoundAmbientRubble();
case BODY_PRISTINE:
default:
return *getTemplate()->getSoundAmbient();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
#ifdef _DEBUG
void Drawable::validatePos() const
{
const Coord3D* ourPos = getPosition();
if (_isnan(ourPos->x) || _isnan(ourPos->y) || _isnan(ourPos->z))
{
DEBUG_CRASH(("Drawable/Object position NAN! '%s'\n", getTemplate()->getName().str()));
}
if (getObject())
{
const Coord3D* objPos = getObject()->getPosition();
if (ourPos->x != objPos->x || ourPos->y != objPos->y || ourPos->z != objPos->z)
{
DEBUG_CRASH(("Drawable/Object position mismatch! '%s'\n", getTemplate()->getName().str()));
}
}
}
#endif
//=============================================================================
void Drawable::setStealthLook(StealthLookType look)
{
if (look != m_stealthLook)
{
m_stealthOpacity = 1.0f; //assume not transparent
switch (look)
{
case STEALTHLOOK_NONE:
m_hiddenByStealth = false;
m_heatVisionOpacity = 0.0f;
break;
case STEALTHLOOK_VISIBLE_FRIENDLY:
case STEALTHLOOK_VISIBLE_FRIENDLY_DETECTED:
{
Real opacity = TheGlobalData->m_stealthFriendlyOpacity;
Object *obj = getObject();
if( obj )
{
//Try to get the stealthupdate module and see if the opacity value is overriden.
static NameKeyType key_StealthUpdate = NAMEKEY("StealthUpdate");
StealthUpdate* stealth = (StealthUpdate *)obj->findUpdateModule(key_StealthUpdate);
if( stealth )
{
if( stealth->isDisguised() )
{
//Disguised objects drive the opacity level directly, hence the break.
m_hiddenByStealth = false;
break;
}
else
{
Real friendlyOpacity = stealth->getFriendlyOpacity();
if( friendlyOpacity != INVALID_OPACITY )
{
opacity = friendlyOpacity;
}
}
}
}
m_stealthOpacity = opacity; // make as partially transparent as this while pulsing
m_hiddenByStealth = false;
/** @todo srj -- evil hack here... this whole heat-vision thing is fucked.
don't want it on mines but no good way to do that. hack for now. */
if (look == STEALTHLOOK_VISIBLE_FRIENDLY_DETECTED && !isKindOf(KINDOF_MINE))
m_heatVisionOpacity = 1.0f;
else
m_heatVisionOpacity = 0.0f;
break;
}
case STEALTHLOOK_DISGUISED_ENEMY:
m_hiddenByStealth = false;
m_heatVisionOpacity = 0.0f;
break;
// this is for the non-controllingplayer that can see me anyway
case STEALTHLOOK_VISIBLE_DETECTED:
m_hiddenByStealth = false;// let the scene omit the first drawing pass
/** @todo srj -- evil hack here... this whole heat-vision thing is fucked.
don't want it on mines but no good way to do that. hack for now. */
if (isKindOf(KINDOF_MINE))
m_heatVisionOpacity = 0.0f;
else
m_heatVisionOpacity = 1.0f;// Draw() will fade until it is set to 1 again
break;
case STEALTHLOOK_INVISIBLE:
m_hiddenByStealth = true;
m_heatVisionOpacity = 0.0f;
break;
}
m_stealthLook = look;
updateHiddenStatus();
}
}
//-------------------------------------------------------------------------------------------------
/** default draw is to just call the database defined draw */
//-------------------------------------------------------------------------------------------------
void Drawable::draw( View *view )
{
if ( getObject() && getObject()->isEffectivelyDead() )
m_heatVisionOpacity = 0.0f;//dead folks don't stealth anyway
else if ( m_heatVisionOpacity > VERY_TRANSPARENT_HEATVISION )// keep fading any heatvision unless something has set it to zero
m_heatVisionOpacity *= HEATVISION_FADE_SCALAR;
else
m_heatVisionOpacity = 0.0f;
if (m_hidden || m_hiddenByStealth || getFullyObscuredByShroud())
return; // my, that was easy
if ( getObject() && !getObject()->isEffectivelyDead() )
setShadowsEnabled( m_stealthLook != STEALTHLOOK_VISIBLE_DETECTED );
#ifdef _DEBUG
validatePos();
#endif
// call the database defined draw action method
Matrix3D transformMtx = *getTransformMatrix();
if (!isInstanceIdentity())
{
#ifdef ALLOW_TEMPORARIES
transformMtx = transformMtx * (*getInstanceMatrix());
#else
transformMtx.postMul(*getInstanceMatrix());
#endif
}
applyPhysicsXform(&transformMtx);
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->doDrawModule(&transformMtx);
}
}
// ------------------------------------------------------------------------------------------------
/** Compute the health bar region based on the health of the object and the
* zoom level of the camera */
// ------------------------------------------------------------------------------------------------
static Bool computeHealthRegion( const Drawable *draw, IRegion2D& region )
{
// sanity
if( draw == NULL )
return FALSE;
const Object *obj = draw->getObject();
if( obj == NULL )
return FALSE;
Coord3D p;
obj->getHealthBoxPosition(p);
ICoord2D screenCenter;
if( !TheTacticalView->worldToScreen( &p, &screenCenter ) )
return FALSE;
Real healthBoxWidth, healthBoxHeight;
if (!obj->getHealthBoxDimensions(healthBoxHeight, healthBoxWidth))
return FALSE;
// scale the health bars according to the zoom
Real zoom = TheTacticalView->getZoom();
//Real widthScale = 1.3f / zoom;
Real widthScale = 1.0f / zoom;
//Real heightScale = 0.8f / zoom;
Real heightScale = 1.0f;
healthBoxWidth *= widthScale;
healthBoxHeight *= heightScale;
// do this so health bar doesn't get too skinny or fat after scaling
//healthBoxHeight = max(3.0f, healthBoxHeight);
healthBoxHeight = 3.0f;
// figure out the final region for the health box
region.lo.x = screenCenter.x - healthBoxWidth * 0.45f;
region.lo.y = screenCenter.y - healthBoxHeight * 0.5f;
region.hi.x = region.lo.x + healthBoxWidth;
region.hi.y = region.lo.y + healthBoxHeight;
return TRUE;
} // end computeHealthRegion
// ------------------------------------------------------------------------------------------------
Bool Drawable::drawsAnyUIText( void )
{
if (!isSelected())
return FALSE;
const Object *obj = getObject();
if ( !obj || !obj->isLocallyControlled() )
return FALSE;
Player *owner = obj->getControllingPlayer();
Int groupNum = owner->getSquadNumberForObject(obj);
if (groupNum > NO_HOTKEY_SQUAD && groupNum < NUM_HOTKEY_SQUADS )
return TRUE;
else
m_groupNumber = NULL;
if ( obj->getFormationID() != NO_FORMATION_ID )
return TRUE;
return FALSE;
}
// ------------------------------------------------------------------------------------------------
/** This is called as part of the "post draw" phase when drawable a drawable. It is there
* that we should overlay on the screen any 2D elements for purposes of user interface
* information (such as a heatlh bar, veterency levels, etc.) */
// ------------------------------------------------------------------------------------------------
void Drawable::drawIconUI( void )
{
if( TheGameLogic->getDrawIconUI() && (TheScriptEngine->getFade()==ScriptEngine::FADE_NONE) )
{
IRegion2D healthBarRegionStorage;
const IRegion2D* healthBarRegion = NULL;
if (computeHealthRegion(this, healthBarRegionStorage))
healthBarRegion = &healthBarRegionStorage; //both data and a PointerAsFlag for logic in the methods below
Object *obj = getObject();
// we only draw icons drawables with objects, so one bail here -------------------------
if ( ! obj )
return;
//Icons that can be drawn on dead things
drawHealthBar( healthBarRegion );
drawEmoticon( healthBarRegion );
drawCaption( healthBarRegion );
drawConstructPercent( healthBarRegion );
//All Icons Below only draw on ALIVE things, so bail here -------------------------
if( obj->isEffectivelyDead() || obj->isKindOf( KINDOF_IGNORED_IN_GUI )) // object explicitly wants nothing to do with these icons, so...
return;
drawHealing( healthBarRegion );//call so dead things can kill their healing icons
drawBombed( healthBarRegion );
//Disabled for multiplay!
//drawBattlePlans( healthBarRegion );
if ( drawsAnyUIText() )
TheGameClient->addTextBearingDrawable( this );
drawEnthusiastic( healthBarRegion );
#ifdef ALLOW_DEMORALIZE
drawDemoralized( healthBarRegion );
#endif
drawDisabled( healthBarRegion );
drawAmmo( healthBarRegion );
drawContained( healthBarRegion );
//Moved this to last so that it shows up over contained and ammo icons.
drawVeterancy( healthBarRegion );
}
}
//------------------------------------------------------------------------------------------------
DrawableIconInfo* Drawable::getIconInfo()
{
if (m_iconInfo == NULL)
m_iconInfo = newInstance(DrawableIconInfo);
return m_iconInfo;
}
//------------------------------------------------------------------------------------------------
void Drawable::clearEmoticon()
{
if (!hasIconInfo())
return;
killIcon(ICON_EMOTICON);
}
//------------------------------------------------------------------------------------------------
void Drawable::setEmoticon( const AsciiString &name, Int duration )
{
//A duration of -1 means FOREVER
clearEmoticon();
Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( name );
if( animTemplate )
{
DEBUG_ASSERTCRASH( getIconInfo()->m_icon[ ICON_EMOTICON ] == NULL, ("Drawable::setEmoticon - Emoticon isn't empty, need to refuse to set or destroy the old one in favor of the new one\n") );
if( getIconInfo()->m_icon[ ICON_EMOTICON ] == NULL )
{
getIconInfo()->m_icon[ ICON_EMOTICON ] = newInstance(Anim2D)( animTemplate, TheAnim2DCollection );
getIconInfo()->m_keepTillFrame[ ICON_EMOTICON ] = duration >= 0 ? TheGameLogic->getFrame() + duration : FOREVER;
}
}
}
//------------------------------------------------------------------------------------------------
void Drawable::drawEmoticon( const IRegion2D *healthBarRegion )
{
if( hasIconInfo() && getIconInfo()->m_icon[ ICON_EMOTICON ] )
{
UnsignedInt now = TheGameLogic->getFrame();
if( healthBarRegion && getIconInfo()->m_keepTillFrame[ ICON_EMOTICON ] >= now )
{
//Draw the emoticon.
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
//Int barHeight = healthBarRegion.hi.y - healthBarRegion.lo.y;
Int frameWidth = getIconInfo()->m_icon[ ICON_EMOTICON ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_EMOTICON ]->getCurrentFrameHeight();
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int size = REAL_TO_INT( barWidth * 0.3f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = (Int)(healthBarRegion->lo.x + (barWidth * 0.5f) - (frameWidth * 0.5f));
screen.y = healthBarRegion->hi.y - frameHeight;
getIconInfo()->m_icon[ ICON_EMOTICON ]->draw( screen.x, screen.y, frameWidth, frameHeight );
}
else
{
//Get rid of the emoticon.
clearEmoticon();
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::drawAmmo( const IRegion2D *healthBarRegion )
{
const Object *obj = getObject();
if (!(
TheGlobalData->m_showObjectHealth &&
(isSelected() || (TheInGameUI && (TheInGameUI->getMousedOverDrawableID() == getID()))) &&
obj->getControllingPlayer() == ThePlayerList->getLocalPlayer()
))
return;
Int numTotal;
Int numFull;
if (!obj->getAmmoPipShowingInfo(numTotal, numFull))
return;
if (!s_fullAmmo || !s_emptyAmmo)
return;
#ifdef SCALE_ICONS_WITH_ZOOM_ML
Real scale = TheGlobalData->m_ammoPipScaleFactor / CLAMP_ICON_ZOOM_FACTOR( TheTacticalView->getZoom() );
#else
Real scale = 1.0f;
#endif
Int boxWidth = REAL_TO_INT(s_emptyAmmo->getImageWidth() * scale);
Int boxHeight = REAL_TO_INT(s_emptyAmmo->getImageHeight() * scale);
const Int SPACING = 1;
//Int totalWidth = (boxWidth+SPACING)*numTotal;
ICoord2D screenCenter;
Coord3D pos = *obj->getPosition();
pos.x += TheGlobalData->m_ammoPipWorldOffset.x;
pos.y += TheGlobalData->m_ammoPipWorldOffset.y;
pos.z += TheGlobalData->m_ammoPipWorldOffset.z + obj->getGeometryInfo().getMaxHeightAbovePosition();
if( !TheTacticalView->worldToScreen( &pos, &screenCenter ) )
return;
Real bounding = obj->getGeometryInfo().getBoundingSphereRadius() * scale;
//Int posx = screenCenter.x + REAL_TO_INT(TheGlobalData->m_ammoPipScreenOffset.x*bounding) - totalWidth;
//**CHANGING CODE: Left justify with health bar min
Int posx = healthBarRegion->lo.x;
Int posy = screenCenter.y + REAL_TO_INT(TheGlobalData->m_ammoPipScreenOffset.y*bounding);
for (Int i = 0; i < numTotal; ++i)
{
TheDisplay->drawImage(i < numFull ? s_fullAmmo : s_emptyAmmo, posx, posy + 1, posx + boxWidth, posy + 1 + boxHeight);
posx += boxWidth + SPACING;
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::drawContained( const IRegion2D *healthBarRegion )
{
const Object *obj = getObject();
ContainModuleInterface* container = obj->getContain();
if (!container)
return;
if (!(
TheGlobalData->m_showObjectHealth &&
(isSelected() || (TheInGameUI && (TheInGameUI->getMousedOverDrawableID() == getID()))) &&
obj->getControllingPlayer() == ThePlayerList->getLocalPlayer()
))
return;
Int numTotal;
Int numFull;
if (!container->getContainerPipsToShow(numTotal, numFull))
return;
// if empty, don't show nothin'
if (numFull == 0)
return;
Int numInfantry = 0;
const ContainedItemsList* contained = container->getContainedItemsList();
if (contained)
{
for (ContainedItemsList::const_iterator it = contained->begin(); it != contained->end(); ++it)
{
if ((*it)->isKindOf(KINDOF_INFANTRY))
++numInfantry;
}
}
#ifdef SCALE_ICONS_WITH_ZOOM_ML
Real scale = TheGlobalData->m_ammoPipScaleFactor / CLAMP_ICON_ZOOM_FACTOR( TheTacticalView->getZoom() );
#else
Real scale = 1.0f;
#endif
Int boxWidth = REAL_TO_INT(s_emptyContainer->getImageWidth() * scale);
Int boxHeight = REAL_TO_INT(s_emptyContainer->getImageHeight() * scale);
const Int SPACING = 1;
//Int totalWidth = (boxWidth+SPACING)*numTotal;
ICoord2D screenCenter;
Coord3D pos = *obj->getPosition();
pos.x += TheGlobalData->m_containerPipWorldOffset.x;
pos.y += TheGlobalData->m_containerPipWorldOffset.y;
pos.z += TheGlobalData->m_containerPipWorldOffset.z + obj->getGeometryInfo().getMaxHeightAbovePosition();
if( !TheTacticalView->worldToScreen( &pos, &screenCenter ) )
return;
Real bounding = obj->getGeometryInfo().getBoundingSphereRadius() * scale;
//Int posx = screenCenter.x + REAL_TO_INT(TheGlobalData->m_containerPipScreenOffset.x*bounding) - totalWidth;
//**CHANGING CODE: Left justify with health bar min
Int posx = healthBarRegion->lo.x;
Int posy = screenCenter.y + REAL_TO_INT(TheGlobalData->m_containerPipScreenOffset.y*bounding);
for (Int i = 0; i < numTotal; ++i)
{
const Color INFANTRY_COLOR = GameMakeColor(0, 255, 0, 255);
const Color NON_INFANTRY_COLOR = GameMakeColor(0, 0, 255, 255);
if (i < numFull)
TheDisplay->drawImage(s_fullContainer, posx, posy, posx + boxWidth, posy + boxHeight,
(i < numInfantry) ? INFANTRY_COLOR : NON_INFANTRY_COLOR);
else
TheDisplay->drawImage(s_emptyContainer, posx, posy + 1, posx + boxWidth, posy + 1 + boxHeight);
posx += boxWidth + SPACING;
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::drawBattlePlans( const IRegion2D *healthBarRegion )
{
Object *obj = getObject();
if( !obj || !healthBarRegion )
{
return;
}
Player *player = obj->getControllingPlayer();
if( player && player->getNumBattlePlansActive() > 0 && player->doesObjectQualifyForBattlePlan( obj ) )
{
if( player->getBattlePlansActiveSpecific( PLANSTATUS_BOMBARDMENT ) )
{
if( !getIconInfo()->m_icon[ ICON_BATTLEPLAN_BOMBARD ] )
{
getIconInfo()->m_icon[ ICON_BATTLEPLAN_BOMBARD ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BATTLEPLAN_BOMBARD ], TheAnim2DCollection );
}
//Int barHeight = healthBarRegion.hi.y - healthBarRegion.lo.y;
Int frameWidth = getIconInfo()->m_icon[ ICON_BATTLEPLAN_BOMBARD ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_BATTLEPLAN_BOMBARD ]->getCurrentFrameHeight();
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int size = REAL_TO_INT( barWidth * 0.3f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = healthBarRegion->lo.x;
screen.y = healthBarRegion->lo.y + frameHeight;
getIconInfo()->m_icon[ ICON_BATTLEPLAN_BOMBARD ]->draw( screen.x, screen.y, frameWidth, frameHeight );
}
else
{
killIcon(ICON_BATTLEPLAN_BOMBARD);
}
if( player->getBattlePlansActiveSpecific( PLANSTATUS_HOLDTHELINE ) )
{
if( !getIconInfo()->m_icon[ ICON_BATTLEPLAN_HOLDTHELINE ] )
{
getIconInfo()->m_icon[ ICON_BATTLEPLAN_HOLDTHELINE ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BATTLEPLAN_HOLDTHELINE ], TheAnim2DCollection );
}
// draw the icon
Int frameWidth = getIconInfo()->m_icon[ ICON_BATTLEPLAN_HOLDTHELINE ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_BATTLEPLAN_HOLDTHELINE ]->getCurrentFrameHeight();
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int size = REAL_TO_INT( barWidth * 0.3f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = healthBarRegion->lo.x;
screen.y = healthBarRegion->lo.y + frameHeight;
getIconInfo()->m_icon[ ICON_BATTLEPLAN_HOLDTHELINE ]->draw( screen.x + frameWidth, screen.y, frameWidth, frameHeight );
}
else
{
killIcon(ICON_BATTLEPLAN_HOLDTHELINE);
}
if( player->getBattlePlansActiveSpecific( PLANSTATUS_SEARCHANDDESTROY ) )
{
if( !getIconInfo()->m_icon[ ICON_BATTLEPLAN_SEARCHANDDESTROY ] )
{
getIconInfo()->m_icon[ ICON_BATTLEPLAN_SEARCHANDDESTROY ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BATTLEPLAN_SEARCHANDDESTROY ], TheAnim2DCollection );
}
// draw the icon
Int frameWidth = getIconInfo()->m_icon[ ICON_BATTLEPLAN_SEARCHANDDESTROY ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_BATTLEPLAN_SEARCHANDDESTROY ]->getCurrentFrameHeight();
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int size = REAL_TO_INT( barWidth * 0.3f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = healthBarRegion->lo.x;
screen.y = healthBarRegion->lo.y + frameHeight;
getIconInfo()->m_icon[ ICON_BATTLEPLAN_SEARCHANDDESTROY ]->draw( screen.x + (frameWidth * 2), screen.y, frameWidth, frameHeight );
}
else
{
killIcon(ICON_BATTLEPLAN_SEARCHANDDESTROY);
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::drawUIText()
{
// This gets called by GameClient now
// GameClient caches a list of us drawables during Drawablepostdraw()
// then our group numbers get spit out last, so they draw in front
const IRegion2D* healthBarRegion = NULL;
IRegion2D healthBarRegionStorage;
if (computeHealthRegion(this, healthBarRegionStorage))
healthBarRegion = &healthBarRegionStorage; //both data and a PointerAsFlag for logic in the methods below
if (!healthBarRegion)
return;
const Object *obj = getObject();
Player *owner = obj->getControllingPlayer();
Int groupNum = owner->getSquadNumberForObject(obj);
Color color = TheDrawGroupInfo->m_usePlayerColor ? owner->getPlayerColor() : TheDrawGroupInfo->m_colorForText;
if (groupNum > NO_HOTKEY_SQUAD && groupNum < NUM_HOTKEY_SQUADS )
{
Int xPos = healthBarRegion->lo.x;
Int yPos = healthBarRegion->lo.y;
if (TheDrawGroupInfo->m_usingPixelOffsetX) {
xPos += TheDrawGroupInfo->m_pixelOffsetX;
} else {
xPos += (healthBarRegion->width() * TheDrawGroupInfo->m_percentOffsetX);
}
if (TheDrawGroupInfo->m_usingPixelOffsetY) {
yPos += TheDrawGroupInfo->m_pixelOffsetY;
} else {
yPos += (healthBarRegion->width() * TheDrawGroupInfo->m_percentOffsetY);
}
m_groupNumber = TheDisplayStringManager->getGroupNumeralString(groupNum);
m_groupNumber->draw(xPos, yPos, color,
TheDrawGroupInfo->m_colorForTextDropShadow,
TheDrawGroupInfo->m_dropShadowOffsetX,
TheDrawGroupInfo->m_dropShadowOffsetY);
}
if ( obj->getFormationID() != NO_FORMATION_ID )
{
//draw an F, here
Coord3D p;
ICoord2D screenCenter;
obj->getHealthBoxPosition(p);
if( ! TheTacticalView->worldToScreen( &p, &screenCenter ) )
return;
Real healthBoxWidth, healthBoxHeight;
if ( ! obj->getHealthBoxDimensions(healthBoxHeight, healthBoxWidth))
return;
Real scale = 1.3f/CLAMP_ICON_ZOOM_FACTOR( TheTacticalView->getZoom() );
screenCenter.x += (healthBoxWidth * scale * 0.5f) + 10 ;
DisplayString *formationMarker = TheDisplayStringManager->getFormationLetterString();
//static DisplayString *formationMarker = TheDisplayStringManager->getGroupNumeralString( 5 );
if ( formationMarker )
formationMarker->draw(screenCenter.x, screenCenter.y, color,
TheDrawGroupInfo->m_colorForTextDropShadow,
TheDrawGroupInfo->m_dropShadowOffsetX,
TheDrawGroupInfo->m_dropShadowOffsetY);
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::drawHealing(const IRegion2D* healthBarRegion)
{
const Object *obj = getObject();
// we do show show icons for things that explicitly forbid it
if( obj->isKindOf( KINDOF_NO_HEAL_ICON ) || BitTest( obj->getStatusBits(), OBJECT_STATUS_SOLD ))
return;
// see if healing has been done to us recently
Bool showHealing = FALSE;
BodyModuleInterface *body = obj->getBodyModule();
if( body->getHealth() != body->getMaxHealth() )
{
// const DamageInfo* lastDamage = body->getLastDamageInfo();
// if( lastDamage != NULL && lastDamage->in.m_damageType == DAMAGE_HEALING
// &&(TheGameLogic->getFrame() - body->getLastHealingTimestamp()) <= HEALING_ICON_DISPLAY_TIME
// )
if ( TheGameLogic->getFrame() > HEALING_ICON_DISPLAY_TIME && // because so many things init health early in game
(TheGameLogic->getFrame() - body->getLastHealingTimestamp() <= HEALING_ICON_DISPLAY_TIME) )
showHealing = TRUE;
}
// based on our own kind of we have certain icons to display at a size scale
Real scale;
DrawableIconType typeIndex;
if( isKindOf( KINDOF_STRUCTURE ) )
{
typeIndex = ICON_STRUCTURE_HEAL;
scale = 0.33f;
}
else if( isKindOf( KINDOF_VEHICLE ) )
{
typeIndex = ICON_VEHICLE_HEAL;
scale = 0.7f;
}
else
{
typeIndex = ICON_DEFAULT_HEAL;
scale = 0.7f;
}
//
// if we are to show healing make sure we have the animation for it allocated, otherwise
// free any animation we may have allocated back to the animation memory pool
//
if( showHealing ) /// @todo HERE, WE NEED TO LEAVE STUFF ALONE, IF WE ARE ALREADY SHOWING HEALING
{
if (healthBarRegion != NULL)
{
if( getIconInfo()->m_icon[ typeIndex ] == NULL )
getIconInfo()->m_icon[ typeIndex ] = newInstance(Anim2D)( s_animationTemplates[ typeIndex ], TheAnim2DCollection );
// draw the animation if present
if( getIconInfo()->m_icon[ typeIndex ] != NULL)
{
//
// we are going to draw the healing icon relative to the size of the health bar region
// since that region takes into account hit point size and zoom factor of the camera too
//
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int frameWidth = getIconInfo()->m_icon[ typeIndex ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ typeIndex ]->getCurrentFrameHeight();
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int size = REAL_TO_INT( barWidth * scale );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = REAL_TO_INT( healthBarRegion->lo.x + (barWidth * 0.75f) - (frameWidth * 0.5f) );
screen.y = REAL_TO_INT( healthBarRegion->lo.y - frameHeight );
getIconInfo()->m_icon[ typeIndex ]->draw( screen.x, screen.y, frameWidth, frameHeight );
}
}
}
else
{
killIcon(typeIndex);
}
}
// ------------------------------------------------------------------------------------------------
/** This enthusiastic effect is TEMPORARY for the multiplayer test */
// ------------------------------------------------------------------------------------------------
void Drawable::drawEnthusiastic(const IRegion2D* healthBarRegion)
{
const Object *obj = getObject();
//
// if we are to show effect make sure we have the animation for it allocated, otherwise
// free any animation we may have allocated back to the animation memory pool
//
// only display if have enthusiasm
if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ) == TRUE &&
healthBarRegion != NULL )
{
DrawableIconType iconIndex = ICON_ENTHUSIASTIC;
if (obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL ) == TRUE )// unless...
iconIndex = ICON_ENTHUSIASTIC_SUBLIMINAL;
if( getIconInfo()->m_icon[ iconIndex ] == NULL )
getIconInfo()->m_icon[ iconIndex ] = newInstance(Anim2D)( s_animationTemplates[ iconIndex ], TheAnim2DCollection );
// draw the animation if present
if( getIconInfo()->m_icon[ iconIndex ] != NULL)
{
//
// we are going to draw the healing icon relative to the size of the health bar region
// since that region takes into account hit point size and zoom factor of the camera too
//
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;// used for position
// based on our own kind of we have certain icons to display at a size scale
Real scale;
if( isKindOf( KINDOF_STRUCTURE ) || isKindOf( KINDOF_HUGE_VEHICLE ) )
scale = 1.00f;
else if( isKindOf( KINDOF_VEHICLE ) )
scale = 0.75f;
else
scale = 0.5f;
Int frameWidth = getIconInfo()->m_icon[ iconIndex ]->getCurrentFrameWidth() * scale;
Int frameHeight = getIconInfo()->m_icon[ iconIndex ]->getCurrentFrameHeight() * scale;
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int size = REAL_TO_INT( barWidth * scale );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the bottom left point to draw the image at
ICoord2D screen;
screen.x = REAL_TO_INT( healthBarRegion->lo.x + (barWidth * 0.25f) - (frameWidth * 0.5f) );
screen.y = healthBarRegion->hi.y;
getIconInfo()->m_icon[ iconIndex ]->draw( screen.x, screen.y, frameWidth, frameHeight );
}
}
else
{
killIcon(ICON_ENTHUSIASTIC);
killIcon(ICON_ENTHUSIASTIC_SUBLIMINAL);
}
}
#ifdef ALLOW_DEMORALIZE
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::drawDemoralized(const IRegion2D* healthBarRegion)
{
const Object *obj = getObject();
//
// Demoralized
//
const AIUpdateInterface *ai = obj->getAIUpdateInterface();
if (!ai)
return;
if( ai->isDemoralized() )
{
// draw the icon
if( healthBarRegion )
{
// create icon if necessary
if( getIconInfo()->m_icon[ ICON_DEMORALIZED ] == NULL )
getIconInfo()->m_icon[ ICON_DEMORALIZED ] = newInstance(Anim2D)( s_animationTemplates[ ICON_DEMORALIZED ], TheAnim2DCollection );
if (getIconInfo()->m_icon[ ICON_DEMORALIZED ])
{
Int frameWidth = getIconInfo()->m_icon[ ICON_DEMORALIZED ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_DEMORALIZED ]->getCurrentFrameHeight();
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int size = REAL_TO_INT( barWidth * 0.3f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = healthBarRegion->lo.x;
screen.y = healthBarRegion->hi.y;
getIconInfo()->m_icon[ ICON_DEMORALIZED ]->draw( screen.x, screen.y, frameWidth, frameHeight );
}
}
}
else
{
killIcon(ICON_DEMORALIZED);
}
}
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::drawBombed(const IRegion2D* healthBarRegion)
{
const Object *obj = getObject();
UnsignedInt now = TheGameLogic->getFrame();
if( obj->testWeaponSetFlag( WEAPONSET_CARBOMB ) &&
obj->getControllingPlayer() == ThePlayerList->getLocalPlayer())
{
if( !getIconInfo()->m_icon[ ICON_CARBOMB ] )
getIconInfo()->m_icon[ ICON_CARBOMB ] = newInstance(Anim2D)( s_animationTemplates[ ICON_CARBOMB ], TheAnim2DCollection );
if( getIconInfo()->m_icon[ ICON_CARBOMB ] )
{
//
// we are going to draw the healing icon relative to the size of the health bar region
// since that region takes into account hit point size and zoom factor of the camera too
//
if( healthBarRegion )
{
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int barHeight = healthBarRegion->hi.y - healthBarRegion->lo.y;
Int frameWidth = getIconInfo()->m_icon[ ICON_CARBOMB ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_CARBOMB ]->getCurrentFrameHeight();
// adjust the width to be a % of the health bar region size
Int size = REAL_TO_INT( barWidth * 0.5f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = REAL_TO_INT( healthBarRegion->lo.x + (barWidth * 0.5f) - (frameWidth * 0.5f) );
screen.y = REAL_TO_INT( healthBarRegion->lo.y + barHeight * 0.5f );
getIconInfo()->m_icon[ ICON_CARBOMB ]->draw( screen.x, screen.y, frameWidth, frameHeight );
getIconInfo()->m_keepTillFrame[ ICON_CARBOMB ] = FOREVER;
}
}
}
else
{
killIcon(ICON_CARBOMB);
}
//
// Bombed?
//
static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" );
StickyBombUpdate *update = (StickyBombUpdate*)obj->findUpdateModule( key_StickyBombUpdate );
if( update )
{
//This case is tricky. The object that is bombed doesn't know it... but the bomb itself does.
//So what we do is get it's target, then determine if the target has the icon or not.
Object *target = update->getTargetObject();
if( target )
{
if( update->isTimedBomb() )
{
//Timed bomb
if( !getIconInfo()->m_icon[ ICON_BOMB_TIMED ] )
{
getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_REMOTE ], TheAnim2DCollection );
getIconInfo()->m_icon[ ICON_BOMB_TIMED ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_TIMED ], TheAnim2DCollection );
//Because this is a counter icon that ranges from 0-60 seconds, we need to calculate which frame to
//start the animation from. Because timers are second based -- 1000 ms equal 1 frame. So we simply
//calculate the time via detonation frame.
//
// srj sez: this may sound familiar somehow, but let me reiterate, just in case you missed it:
//
// hardcoding is bad.
//
// the anim got changed and now is only 20 seconds max, so the previous code was wrong.
//
// hey, I've got an idea! why don't we ASK the anim how long it is?
//
UnsignedInt dieFrame = update->getDetonationFrame();
UnsignedInt seconds = REAL_TO_INT_CEIL( (dieFrame - now) * SECONDS_PER_LOGICFRAME_REAL);
UnsignedInt numFrames = getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->getAnimTemplate()->getNumFrames();
// this anim goes from "N" seconds down to zero, so the max seconds we can use is N-1.
if (seconds > numFrames - 1)
seconds = numFrames - 1;
getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->setMinFrame(numFrames - seconds - 1);
getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->reset();
}
if( getIconInfo()->m_icon[ ICON_BOMB_TIMED ] )
{
//
// we are going to draw the healing icon relative to the size of the health bar region
// since that region takes into account hit point size and zoom factor of the camera too
//
if( healthBarRegion )
{
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int barHeight = healthBarRegion->hi.y - healthBarRegion->lo.y;
Int frameWidth = getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->getCurrentFrameHeight();
// adjust the width to be a % of the health bar region size
Int size = REAL_TO_INT( barWidth * 0.65f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = REAL_TO_INT( healthBarRegion->lo.x + (barWidth * 0.5f) - (frameWidth * 0.5f) );
screen.y = REAL_TO_INT( healthBarRegion->lo.y + barHeight * 0.5f );
getIconInfo()->m_icon[ ICON_BOMB_REMOTE ]->draw( screen.x, screen.y, frameWidth, frameHeight );
getIconInfo()->m_keepTillFrame[ ICON_BOMB_REMOTE ] = now + 1;
getIconInfo()->m_icon[ ICON_BOMB_TIMED ]->draw( screen.x, screen.y, frameWidth, frameHeight );
getIconInfo()->m_keepTillFrame[ ICON_BOMB_TIMED ] = now + 1;
}
}
}
else
{
//Remote charge
//Timed bomb
if( !getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] )
{
getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_REMOTE ], TheAnim2DCollection );
}
if( getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] )
{
//
// we are going to draw the healing icon relative to the size of the health bar region
// since that region takes into account hit point size and zoom factor of the camera too
//
if( healthBarRegion )
{
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int barHeight = healthBarRegion->hi.y - healthBarRegion->lo.y;
Int frameWidth = getIconInfo()->m_icon[ ICON_BOMB_REMOTE ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_BOMB_REMOTE ]->getCurrentFrameHeight();
// adjust the width to be a % of the health bar region size
Int size = REAL_TO_INT( barWidth * 0.65f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = REAL_TO_INT( healthBarRegion->lo.x + (barWidth * 0.5f) - (frameWidth * 0.5f) );
screen.y = REAL_TO_INT( healthBarRegion->lo.y + barHeight * 0.5f );
getIconInfo()->m_icon[ ICON_BOMB_REMOTE ]->draw( screen.x, screen.y, frameWidth, frameHeight );
getIconInfo()->m_keepTillFrame[ ICON_BOMB_REMOTE ] = now + 1;
}
}
}
}
}
if (hasIconInfo())
{
if(getIconInfo()->m_keepTillFrame[ ICON_BOMB_TIMED ] <= now )
{
killIcon(ICON_BOMB_TIMED);
}
if(getIconInfo()->m_keepTillFrame[ ICON_BOMB_REMOTE ] <= now )
{
killIcon(ICON_BOMB_REMOTE);
}
}
}
// ------------------------------------------------------------------------------------------------
/** Draw any icon information that needs to be drawn */
// ------------------------------------------------------------------------------------------------
void Drawable::drawDisabled(const IRegion2D* healthBarRegion)
{
const Object *obj = getObject();
//
// Disabled Emoticon /Lightning
// 7/
if( obj->isDisabledByType( DISABLED_HACKED )
|| obj->isDisabledByType( DISABLED_PARALYZED )
|| obj->isDisabledByType( DISABLED_EMP )
|| obj->isDisabledByType( DISABLED_UNDERPOWERED )
)
{
// create icon if necessary
if( getIconInfo()->m_icon[ ICON_DISABLED ] == NULL )
{
getIconInfo()->m_icon[ ICON_DISABLED ] = newInstance(Anim2D)
( s_animationTemplates[ ICON_DISABLED ], TheAnim2DCollection );
}
// draw the icon
if( healthBarRegion )
{
Int barHeight = healthBarRegion->hi.y - healthBarRegion->lo.y;
Int frameWidth = getIconInfo()->m_icon[ ICON_DISABLED ]->getCurrentFrameWidth();
Int frameHeight = getIconInfo()->m_icon[ ICON_DISABLED ]->getCurrentFrameHeight();
#ifdef SCALE_ICONS_WITH_ZOOM_ML
// adjust the width to be a % of the health bar region size
Int barWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Int size = REAL_TO_INT( barWidth * 0.3f );
frameHeight = REAL_TO_INT((INT_TO_REAL(size) / INT_TO_REAL(frameWidth)) * frameHeight);
frameWidth = size;
#endif
// given our scaled width and height we need to find the top left point to draw the image at
ICoord2D screen;
screen.x = healthBarRegion->lo.x;
screen.y = healthBarRegion->hi.y - (frameHeight + barHeight);
getIconInfo()->m_icon[ ICON_DISABLED ]->draw( screen.x, screen.y, frameWidth, frameHeight );
} // end if
} // end if
else
{
// delete icon if necessary
killIcon(ICON_DISABLED);
} // end if
}
//-------------------------------------------------------------------------------------------------
/** Draw construction percent for drawables that have objects that are "under construction" */
//-------------------------------------------------------------------------------------------------
void Drawable::drawConstructPercent( const IRegion2D *healthBarRegion )
{
// this data is in an attached object
Object *obj = getObject();
if( obj == NULL || BitTest( obj->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) == FALSE ||
BitTest( obj->getStatusBits(), OBJECT_STATUS_SOLD ) == TRUE )
{
// no object, or we are now complete get rid of the string if we have one
if( m_constructDisplayString )
{
TheDisplayStringManager->freeDisplayString( m_constructDisplayString );
m_constructDisplayString = NULL;
}
return;
}
//if( obj->isEffectivelyDead() )
//{
//Don't render icons for dead things.
// return;
//}
// construction is partially complete, allocate a display string if we need one
if( m_constructDisplayString == NULL )
m_constructDisplayString = TheDisplayStringManager->newDisplayString();
// set the string if the value has changed
if( m_lastConstructDisplayed != obj->getConstructionPercent() )
{
UnicodeString buffer;
buffer.format( TheGameText->fetch("CONTROLBAR:UnderConstructionDesc"), obj->getConstructionPercent());
m_constructDisplayString->setText( buffer );
// record this percent as our last displayed so we don't un-necessarily rebuild the string
m_lastConstructDisplayed = obj->getConstructionPercent();
} // end if
// get center position in drawable
ICoord2D screen;
Coord3D pos;
getDrawableGeometryInfo().getCenterPosition(*getPosition(), pos);
// convert drawable center position to screen coords
TheTacticalView->worldToScreen( &pos, &screen );
// draw the text
Color color = GameMakeColor( 255, 255, 255, 255 );
Color dropColor = GameMakeColor( 0, 0, 0, 255 );
screen.x -= (m_constructDisplayString->getWidth() / 2);
m_constructDisplayString->draw( screen.x, screen.y, color, dropColor );
} // end drawConstructPercent
//-------------------------------------------------------------------------------------------------
/** Draw caption */
//-------------------------------------------------------------------------------------------------
void Drawable::drawCaption( const IRegion2D *healthBarRegion )
{
if (!m_captionDisplayString)
return;
// get center position in drawable
ICoord2D screen;
Coord3D pos;
getDrawableGeometryInfo().getCenterPosition(*getPosition(), pos);
// convert drawable center position to screen coords
TheTacticalView->worldToScreen( &pos, &screen );
screen.x -= (m_captionDisplayString->getWidth() / 2);
// draw background
{
Int width, xPos;
Int height, yPos;
m_captionDisplayString->getSize(&width,&height);
xPos = screen.x - 1;
yPos = screen.y - 1;
TheDisplay->drawFillRect(xPos, yPos, width + 2,height + 2, GameMakeColor(0,0,0,125));
TheDisplay->drawOpenRect(xPos, yPos, width + 2,height + 2, 1.0, GameMakeColor(20,20,20,255));
}
// draw the text
Color color = TheInGameUI->getDrawableCaptionColor();
Color dropColor = GameMakeColor( 0, 0, 0, 255 );
m_captionDisplayString->draw( screen.x, screen.y, color, dropColor );
} // end drawCaption
// ------------------------------------------------------------------------------------------------
/** Draw any veterency markers that should be displayed */
// ------------------------------------------------------------------------------------------------
void Drawable::drawVeterancy( const IRegion2D *healthBarRegion )
{
// get object from drawble
Object* obj = getObject();
if( obj->getExperienceTracker() == NULL )
{
//Only objects with experience trackers can possibly have veterancy.
return;
}
VeterancyLevel level = obj->getVeterancyLevel();
const Image* image = s_veterancyImage[level];
if (!image)
return;
Real scale = 1.3f/CLAMP_ICON_ZOOM_FACTOR( TheTacticalView->getZoom() );
#ifdef SCALE_ICONS_WITH_ZOOM_ML
Real objScale = scale * 1.55f;
#else
Real objScale = 1.0f;
#endif
Real vetBoxWidth = image->getImageWidth()*objScale;
Real vetBoxHeight = image->getImageHeight()*objScale;
//
// take the center position of the object, go down to it's bottom extent, and project
// that point to the screen, that will be the "center" of our veterancy box
//
Coord3D p;
ICoord2D screenCenter;
obj->getHealthBoxPosition(p);
if( !TheTacticalView->worldToScreen( &p, &screenCenter ) )
return;
Real healthBoxWidth, healthBoxHeight;
if (!obj->getHealthBoxDimensions(healthBoxHeight, healthBoxWidth))
return;
screenCenter.x += healthBoxWidth * scale * 0.5f;
// draw the image
TheDisplay->drawImage(image, screenCenter.x + 1, screenCenter.y + 1, screenCenter.x + 1 + vetBoxWidth, screenCenter.y + 1 + vetBoxHeight);
} // end drawVeterancy
// ------------------------------------------------------------------------------------------------
/** Draw health bar information for drawable */
// ------------------------------------------------------------------------------------------------
void Drawable::drawHealthBar(const IRegion2D* healthBarRegion)
{
if (!healthBarRegion)
return;
//
// only draw health for selected drawbles and drawables that have been moused over
// by the cursor
//
if( TheGlobalData->m_showObjectHealth &&
(isSelected() || (TheInGameUI && (TheInGameUI->getMousedOverDrawableID() == getID()))) )
{
Object *obj = getObject();
// if no object, nothing to do
if( obj == NULL )
return;
if( obj->isKindOf( KINDOF_FORCEATTACKABLE ) )
{
//Currently (Nov 2002), everything that is forceattackable are civ fences, and they all have a
//single hit point and they aren't selectable. However, a bug is when you force attack it, it shows
//the healthbar. Well, this stops it, however, should force attackable kindofs change, then this
//will require reevaluation.
return;
}
// get body module of object
BodyModuleInterface *body = obj->getBodyModule();
// get the health and max health
Real health = body->getHealth();
Real maxHealth = body->getMaxHealth();
// if no max health or health at all we will draw nothing
if( maxHealth == 0.0f || health == 0.0f )
return;
// what is our health ratio
Real healthRatio = health / maxHealth;
//
// what color will we use for the health bar based on our ratio, this makes it
// slowly go from green to red, (or from blue to cyan if under construction, or disabled)
//
Color color, outlineColor;
if( BitTest( obj->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) || (obj->isDisabled() && !obj->isDisabledByType(DISABLED_HELD)) )
{
color = GameMakeColor( 0, healthRatio * 255.0f, 255, 255 );//blue to cyan
outlineColor = GameMakeColor( 0, healthRatio * 128.0f, 128, 255 );//dark blue to dark cyan
}
else //red to green
{
RGBColor inColor, outColor;
inColor.blue = 0; // health bars do not display blue...
outColor.blue = 0; // health bars do not display blue...
if( healthRatio >= 0.5f )
{
inColor.red = 1.0f - ((healthRatio - 0.5f) / 0.5f);
inColor.green = 1.0f;
// color = GameMakeColor ( 255 - ((healthRatio - 0.5f) / 0.5f) * 255, 255, 0, 255 );
// outlineColor = GameMakeColor( (255 - ((healthRatio - 0.5f) / 0.5f) * 255) * 0.5, 255 * 0.5, 0, 255 );
}
else
{
inColor.red = 1.0f;
inColor.green = 1.0f - ((0.5f - healthRatio) / 0.5f);
// color = GameMakeColor( 255, 255 - ((0.5f - healthRatio) / 0.5f) * 255, 0, 255 );
// outlineColor = GameMakeColor( 255 * 0.5, (255 - ((0.5f - healthRatio) / 0.5f) * 255) * 0.5, 0, 255 );
}
outColor.red = inColor.red * 0.5f;
outColor.green =inColor.green * 0.5f;
if( m_conditionState.test( MODELCONDITION_REALLY_DAMAGED ) == TRUE )
{//average the above color with red
inColor.red = (1.0f + inColor.red) * 0.5f;
inColor.green *= 0.5f;
}
else if ( m_conditionState.test( MODELCONDITION_DAMAGED ) == FALSE )
{//average the above color with green
inColor.green = (1.0f + inColor.green) * 0.5f;
inColor.red *= 0.5f;
}
color = GameMakeColor( 255.0 * inColor.red, 255.0 * inColor.green, 255.0 * inColor.blue, 255);
outlineColor = GameMakeColor( 255.0 * outColor.red, 255.0 * outColor.green, 255.0 * outColor.blue, 255);
}
/// Real scale = 1.3f / TheTacticalView->getZoom();
Real healthBoxWidth = healthBarRegion->hi.x - healthBarRegion->lo.x;
Real healthBoxHeight = max(3, healthBarRegion->hi.y - healthBarRegion->lo.y);
Real healthBoxOutlineSize = 1.0f;
// draw the health box outline
TheDisplay->drawOpenRect( healthBarRegion->lo.x, healthBarRegion->lo.y, healthBoxWidth, healthBoxHeight,
healthBoxOutlineSize, outlineColor );
// draw a filled bar for the health
TheDisplay->drawFillRect( healthBarRegion->lo.x + 1, healthBarRegion->lo.y + 1,
(healthBoxWidth - 2) * healthRatio, healthBoxHeight - 2,
color );
} // end if
} // end drawHealthBar
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void Drawable::clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set )
{
ModelConditionFlags c, s;
if (clr != MODELCONDITION_INVALID)
c.set(clr);
if (set != MODELCONDITION_INVALID)
s.set(set);
clearAndSetModelConditionFlags(c, s);
}
//-------------------------------------------------------------------------------------------------
DrawModule** Drawable::getDrawModules()
{
DrawModule** dm = (DrawModule**)getModuleList(MODULETYPE_DRAW);
#ifdef DIRTY_CONDITION_FLAGS
if (m_isModelDirty)
{
if (s_modelLockCount > 0)
{
DEBUG_CRASH(("Should not need to update dirty stuff while locked-for-iteration. Ignoring."));
// this shouldn't happen, but if it does, just return the current (dirty) scenario.
// we must NOT update the condition state, as someone is relying on the current
// list of W3D render objects not being munged. (srj)
}
else
{
for (DrawModule** dm2 = dm; *dm2; ++dm2)
{
ObjectDrawInterface* di = (*dm2)->getObjectDrawInterface();
if (di)
di->replaceModelConditionState( m_conditionState );
}
m_isModelDirty = false;
}
}
#endif
return dm;
}
//-------------------------------------------------------------------------------------------------
DrawModule const** Drawable::getDrawModules() const
{
DrawModule const** dm = (DrawModule const**)getModuleList(MODULETYPE_DRAW);
#ifdef DIRTY_CONDITION_FLAGS
if (m_isModelDirty)
{
if (s_modelLockCount > 0)
{
DEBUG_CRASH(("Should not need to update dirty stuff while locked-for-iteration. Ignoring."));
// this shouldn't happen, but if it does, just return the current (dirty) scenario.
// we must NOT update the condition state, as someone is relying on the current
// list of W3D render objects not being munged. (srj)
}
else
{
// yeah, yeah, yeah... I know (srj)
for (DrawModule** dm2 = (DrawModule**)dm; *dm2; ++dm2)
{
ObjectDrawInterface* di = (*dm2)->getObjectDrawInterface();
if (di)
di->replaceModelConditionState( m_conditionState );
}
m_isModelDirty = false;
}
}
#endif
return dm;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void Drawable::clearAndSetModelConditionFlags(const ModelConditionFlags& clr, const ModelConditionFlags& setf)
{
ModelConditionFlags oldFlags = m_conditionState;
m_conditionState.clearAndSet(clr, setf);
if (m_conditionState == oldFlags)
return;
#ifdef DIRTY_CONDITION_FLAGS
m_isModelDirty = true;
#else
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->replaceModelConditionState( m_conditionState );
}
#endif
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void Drawable::replaceModelConditionFlags( const ModelConditionFlags &flags, Bool forceReplace )
{
//
// this is a no-op if the new flags are the same as our existing flags (unless we
// have the forceReplace parameter set, in which case we will force the setting of the
// new flags)
//
if( forceReplace == FALSE && m_conditionState == flags )
return;
m_conditionState = flags;
#ifdef DIRTY_CONDITION_FLAGS
// when forcing a replace we won't use dirty flags, we will immediately do an update now
if( forceReplace == TRUE )
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->replaceModelConditionState( m_conditionState );
}
m_isModelDirty = false;
}
else
m_isModelDirty = true;
#else
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->replaceModelConditionState( m_conditionState );
}
#endif
}
//-------------------------------------------------------------------------------------------------
void Drawable::setIndicatorColor(Color color)
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->replaceIndicatorColor(color);
}
}
// ------------------------------------------------------------------------------------------------
const GeometryInfo& Drawable::getDrawableGeometryInfo() const
{
return getObject() ? getObject()->getGeometryInfo() : getTemplate()->getTemplateGeometryInfo();
}
// ------------------------------------------------------------------------------------------------
/** Set the id for this drawable */
// ------------------------------------------------------------------------------------------------
void Drawable::setID( DrawableID id )
{
// if id hasn't changed do nothing
if( m_id == id )
return;
// remove this objects previous id from the lookup table
if( m_id != INVALID_DRAWABLE_ID )
TheGameClient->removeDrawableFromLookupTable( this );
// assign new id
m_id = id;
// add new id to lookup table
if( m_id != INVALID_DRAWABLE_ID )
{
TheGameClient->addDrawableToLookupTable( this );
if (m_ambientSound)
m_ambientSound->m_event.setDrawableID(m_id);
}
} // end setID
// ------------------------------------------------------------------------------------------------
/** Return drawable ID, this ID is only good on the client */
// ------------------------------------------------------------------------------------------------
DrawableID Drawable::getID( void ) const
{
// we should never be getting the ID of a drawable who doesn't yet have and ID assigned to it
DEBUG_ASSERTCRASH( m_id != 0, ("Drawable::getID - Using ID before it was assigned!!!!\n") );
return m_id;
} // end get ID
//-------------------------------------------------------------------------------------------------
void Drawable::friend_bindToObject( Object *obj ) ///< bind this drawable to an object ID
{
m_object = obj;
if (getObject())
{
if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
setIndicatorColor(getObject()->getNightIndicatorColor());
else
setIndicatorColor(getObject()->getIndicatorColor());
}
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->onDrawableBoundToObject();
}
}
//-------------------------------------------------------------------------------------------------
// when our Object changes teams, it calls us to let us know, so
// we can update our model, etc., if necessary. NOTE, we don't guarantee
// that the new team is different from the old team, nor do we guarantee
// that the team is nonnull.
void Drawable::changedTeam()
{
Object *object = getObject();
if( object )
{
if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
setIndicatorColor( object->getNightIndicatorColor() );
else
setIndicatorColor( object->getIndicatorColor() );
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::setPosition(const Coord3D *pos)
{
// extend
Thing::setPosition(pos);
}
//-------------------------------------------------------------------------------------------------
void Drawable::reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle)
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->reactToTransformChange(oldMtx, oldPos, oldAngle);
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::reactToGeometryChange()
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
(*dm)->reactToGeometryChange();
}
}
//-------------------------------------------------------------------------------------------------
Bool Drawable::handleWeaponFireFX(WeaponSlotType wslot, Int specificBarrelToUse, const FXList* fxl, Real weaponSpeed, Real recoilAmount, Real recoilAngle, const Coord3D* victimPos, Real damageRadius)
{
if (recoilAmount != 0.0f)
{
// adjust recoil from absolute to relative.
if (getObject())
recoilAngle -= getObject()->getOrientation();
// flip direction 180 degrees.
recoilAngle += PI;
if (m_locoInfo)
{
m_locoInfo->m_accelerationPitchRate += recoilAmount * Cos(recoilAngle);
m_locoInfo->m_accelerationRollRate += recoilAmount * Sin(recoilAngle);
}
}
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di && di->handleWeaponFireFX(wslot, specificBarrelToUse, fxl, weaponSpeed, victimPos, damageRadius))
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
Int Drawable::getBarrelCount(WeaponSlotType wslot) const
{
for (const DrawModule** dm = getDrawModules(); *dm; ++dm)
{
const ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
Int count = di ? di->getBarrelCount(wslot) : 0;
if (count != 0)
return count;
}
return 0;
}
//-------------------------------------------------------------------------------------------------
/** Set the Drawable's instance transform */
//-------------------------------------------------------------------------------------------------
void Drawable::setInstanceMatrix( const Matrix3D *instance )
{
if (instance)
{
m_instance = *instance;
m_instanceIsIdentity = false;
}
else
{
m_instance.Make_Identity();
m_instanceIsIdentity = true;
}
}
//-------------------------------------------------------------------------------------------------
/**
* Return the Drawable's world transform.
* If this Drawable is attached to an Object, return the Object's transform instead.
*/
//-------------------------------------------------------------------------------------------------
const Matrix3D *Drawable::getTransformMatrix( void ) const
{
const Object *obj = getObject();
if (obj)
return obj->getTransformMatrix();
else
return Thing::getTransformMatrix();
}
//-------------------------------------------------------------------------------------------------
/**
* Set and clear the drawable's caption text
*/
//-------------------------------------------------------------------------------------------------
void Drawable::setCaptionText( const UnicodeString& captionText )
{
if (captionText.isEmpty())
{
clearCaptionText();
return;
}
UnicodeString sanitizedString = captionText;
TheLanguageFilter->filterLine(sanitizedString);
if( m_captionDisplayString == NULL )
{
m_captionDisplayString = TheDisplayStringManager->newDisplayString();
GameFont *font = TheFontLibrary->getFont(
TheInGameUI->getDrawableCaptionFontName(),
TheGlobalLanguageData->adjustFontSize(TheInGameUI->getDrawableCaptionPointSize()),
TheInGameUI->isDrawableCaptionBold() );
m_captionDisplayString->setFont( font );
m_captionDisplayString->setText( sanitizedString );
}
else
{
// set the string if the value has changed
if( m_captionDisplayString->getText().compare(sanitizedString) != 0 )
{
m_captionDisplayString->setText( sanitizedString );
}
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::clearCaptionText( void )
{
if (m_captionDisplayString)
TheDisplayStringManager->freeDisplayString(m_captionDisplayString);
m_captionDisplayString = NULL;
}
//-------------------------------------------------------------------------------------------------
UnicodeString Drawable::getCaptionText( void )
{
if (m_captionDisplayString)
return m_captionDisplayString->getText();
return UnicodeString::TheEmptyString;
}
//-------------------------------------------------------------------------------------------------
/** Attach and start playing an ambient sound to this drawable */
//-------------------------------------------------------------------------------------------------
void Drawable::setTimeOfDay(TimeOfDay tod)
{
BodyDamageType dt = BODY_PRISTINE;
if (getObject() && getObject()->getBodyModule())
dt = getObject()->getBodyModule()->getDamageState();
startAmbientSound(dt, tod);
ModelConditionFlags c = m_conditionState;
c.set(MODELCONDITION_NIGHT, (tod == TIME_OF_DAY_NIGHT) ? 1 : 0);
replaceModelConditionFlags(c);
}
//-------------------------------------------------------------------------------------------------
/** Attach and start playing an ambient sound to this drawable */
//-------------------------------------------------------------------------------------------------
void Drawable::startAmbientSound(BodyDamageType dt, TimeOfDay tod)
{
stopAmbientSound();
//Get the specific ambient sound for the damage type.
const AudioEventRTS& audio = getAmbientSoundByDamage(dt);
Bool trySound = FALSE;
if( audio.getEventName().isNotEmpty() )
{
if (m_ambientSound == NULL)
m_ambientSound = newInstance(DynamicAudioEventRTS);
(m_ambientSound->m_event) = audio;
trySound = TRUE;
}
else if( dt != BODY_PRISTINE && dt != BODY_RUBBLE )
{
//If the ambient sound was absent in the case of non-pristine damage types,
//try getting the pristine one. Most of our cases actually specify just the
//pristine sound and want to use it for all states (except dead/rubble).
const AudioEventRTS& pristineAudio = getAmbientSoundByDamage( BODY_PRISTINE );
if( pristineAudio.getEventName().isNotEmpty() )
{
if (m_ambientSound == NULL)
m_ambientSound = newInstance(DynamicAudioEventRTS);
(m_ambientSound->m_event) = pristineAudio;
trySound = TRUE;
}
}
if( trySound && m_ambientSound )
{
const AudioEventInfo *info = m_ambientSound->m_event.getAudioEventInfo();
if( info )
{
if( BitTest( info->m_type, ST_GLOBAL) || info->m_priority == AP_CRITICAL )
{
//Play it anyways.
m_ambientSound->m_event.setDrawableID(getID());
m_ambientSound->m_event.setTimeOfDay(tod);
m_ambientSound->m_event.setPlayingHandle(TheAudio->addAudioEvent( &m_ambientSound->m_event ));
}
else
{
//Check if it's close enough to try playing (optimization)
Coord3D vector = *getPosition();
vector.sub( TheAudio->getListenerPosition() );
Real distSqr = vector.lengthSqr();
if( distSqr < sqr( info->m_maxDistance ) )
{
m_ambientSound->m_event.setDrawableID(getID());
m_ambientSound->m_event.setTimeOfDay(tod);
m_ambientSound->m_event.setPlayingHandle(TheAudio->addAudioEvent( &m_ambientSound->m_event ));
}
}
}
else
{
DEBUG_CRASH( ("Ambient sound %s missing! Skipping...", m_ambientSound->m_event.getEventName().str() ) );
m_ambientSound->deleteInstance();
m_ambientSound = NULL;
}
}
}
//-------------------------------------------------------------------------------------------------
// Attach and start playing an ambient sound to this drawable. Calculates states automatically.
//-------------------------------------------------------------------------------------------------
void Drawable::startAmbientSound()
{
stopAmbientSound();
BodyDamageType bodyCondition = BODY_PRISTINE;
Object *obj = getObject();
if( obj )
{
bodyCondition = obj->getBodyModule()->getDamageState();
}
startAmbientSound( bodyCondition, TheGlobalData->m_timeOfDay );
}
//-------------------------------------------------------------------------------------------------
/** Stop playing the drawables ambient sound if it has one */
//-------------------------------------------------------------------------------------------------
void Drawable::stopAmbientSound( void )
{
if (m_ambientSound)
TheAudio->removeAudioEvent(m_ambientSound->m_event.getPlayingHandle());
}
//-------------------------------------------------------------------------------------------------
void Drawable::enableAmbientSound( Bool enable )
{
if( m_ambientSoundEnabled == enable )
{
return;
}
m_ambientSoundEnabled = enable;
if( enable )
{
startAmbientSound();
}
else
{
stopAmbientSound();
}
}
//-------------------------------------------------------------------------------------------------
/** add self to the linked list */
//-------------------------------------------------------------------------------------------------
void Drawable::prependToList(Drawable **pListHead)
{
// add the object to the global list
m_prevDrawable = NULL;
m_nextDrawable = *pListHead;
if (*pListHead)
(*pListHead)->m_prevDrawable = this;
*pListHead = this;
}
//-------------------------------------------------------------------------------------------------
/** remove self from the linked list */
//-------------------------------------------------------------------------------------------------
void Drawable::removeFromList(Drawable **pListHead)
{
if (m_nextDrawable)
m_nextDrawable->m_prevDrawable = m_prevDrawable;
if (m_prevDrawable)
m_prevDrawable->m_nextDrawable = m_nextDrawable;
else
*pListHead = m_nextDrawable;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void Drawable::updateHiddenStatus()
{
Bool hidden = m_hidden || m_hiddenByStealth;
if( hidden )
TheInGameUI->deselectDrawable( this );
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->setHidden(hidden != 0);
}
}
//-------------------------------------------------------------------------------------------------
/** Hide or un-hide drawable */
//-------------------------------------------------------------------------------------------------
void Drawable::setDrawableHidden( Bool hidden )
{
if (hidden != m_hidden)
{
m_hidden = hidden;
updateHiddenStatus();
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::updateDrawableClipStatus( UnsignedInt shotsRemaining, UnsignedInt maxShots, WeaponSlotType slot )
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->updateProjectileClipStatus(shotsRemaining, maxShots, slot);
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::updateDrawableSupplyStatus( Int maxSupply, Int currentSupply )
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->updateDrawModuleSupplyStatus( maxSupply, currentSupply );
}
}
//-------------------------------------------------------------------------------------------------
void Drawable::notifyDrawableDependencyCleared()
{
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->notifyDrawModuleDependencyCleared();
}
}
//-------------------------------------------------------------------------------------------------
/** Set as selectable or not. */
//-------------------------------------------------------------------------------------------------
void Drawable::setSelectable( Bool selectable )
{
// unselct drawable if it is no longer selectable.
if( !selectable )
TheInGameUI->deselectDrawable( this );
for (DrawModule** dm = getDrawModules(); *dm; ++dm)
{
ObjectDrawInterface* di = (*dm)->getObjectDrawInterface();
if (di)
di->setSelectable(selectable);
}
}
//-------------------------------------------------------------------------------------------------
/** Return whether or not this Drawable is selectable. */
//-------------------------------------------------------------------------------------------------
Bool Drawable::isSelectable( void ) const
{
return getObject() && getObject()->isSelectable();
}
//-------------------------------------------------------------------------------------------------
/** Return whether or not this Drawable is selectable as part of a group. */
//-------------------------------------------------------------------------------------------------
Bool Drawable::isMassSelectable( void ) const
{
return getObject() && getObject()->isMassSelectable();
}
//-------------------------------------------------------------------------------------------------
/** Preload all our assets that we can for all our possible states in this time of day */
//-------------------------------------------------------------------------------------------------
void Drawable::preloadAssets( TimeOfDay timeOfDay )
{
/// walk all our modules and preload any assets we need to
for( Int i = 0; i < NUM_DRAWABLE_MODULE_TYPES; ++i )
for( Module** m = m_modules[i]; m && *m; ++m )
(*m)->preloadAssets( timeOfDay );
} // end preloadAssets
//-------------------------------------------------------------------------------------------------
// Simply searches for the first occurrence of a specified client update module.
//-------------------------------------------------------------------------------------------------
ClientUpdateModule* Drawable::findClientUpdateModule( NameKeyType key )
{
ClientUpdateModule **clientModules = getClientUpdateModules();
if( clientModules )
{
while( *clientModules )
{
if( (*clientModules)->getModuleNameKey() == key )
{
return *clientModules;
}
}
}
return NULL;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void Drawable::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer the drawable modules
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void Drawable::xferDrawableModules( Xfer *xfer )
{
// version
const XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
//
// when using dirty condition flags ... we want to make sure that the modules are updated to
// the current state of the drawable
//
#ifdef DIRTY_CONDITION_FLAGS
if( xfer->getXferMode() == XFER_SAVE )
getDrawModules(); // will re-evaluate modules that are dirty and update them
#endif
// xfer number of module types
UnsignedShort moduleTypes = NUM_DRAWABLE_MODULE_TYPES;
xfer->xferUnsignedShort( &moduleTypes );
// xfer each set of modules for each type
AsciiString moduleIdentifier;
for( UnsignedShort curModuleType = 0; curModuleType < moduleTypes; ++curModuleType )
{
// how many modules are here for this type
Module **m;
UnsignedShort moduleCount = 0;
for( m = m_modules[ curModuleType ]; m && *m; ++m )
moduleCount++;
xfer->xferUnsignedShort( &moduleCount );
// xfer each module data
if( xfer->getXferMode() == XFER_SAVE )
{
// save each module
for( m = m_modules[ curModuleType ]; m && *m; ++m )
{
// write module identifier
moduleIdentifier = TheNameKeyGenerator->keyToName( (*m)->getModuleTagNameKey() );
DEBUG_ASSERTCRASH( moduleIdentifier != AsciiString::TheEmptyString,
("Drawable::xferDrawableModules - module name key does not translate to a string!\n") );
xfer->xferAsciiString( &moduleIdentifier );
// begin data block
xfer->beginBlock();
// xfer data
xfer->xferSnapshot( *m );
// end data block
xfer->endBlock();
} // end for, m
} // end if, save
else
{
// read each module
for( UnsignedShort j = 0; j < moduleCount; ++j )
{
// read module identifier
xfer->xferAsciiString( &moduleIdentifier );
NameKeyType moduleIdentifierKey = TheNameKeyGenerator->nameToKey(moduleIdentifier);
// find module in the drawable module list
Module* module = NULL;
for( Module **m = m_modules[curModuleType]; m && *m; ++m )
{
if (moduleIdentifierKey == (*m)->getModuleTagNameKey())
{
module = *m;
break; // exit for m
} // end if
} // end for, m
// new block of data
Int dataSize = xfer->beginBlock();
//
// if we didn't find the module, it's quite possible that we have removed
// it from the object definition in a future patch, if that is so, we need to
// skip the module data in the file
//
if( module == NULL )
{
// for testing purposes, this module better be found
DEBUG_CRASH(( "Drawable::xferDrawableModules - Module '%s' was indicated in file, but not found on Drawable %s %d\n",
moduleIdentifier.str(), getTemplate()->getName().str(),getID() ));
// skip this data in the file
xfer->skip( dataSize );
} // end if
else
{
// xfer the data into this module
xfer->xferSnapshot( module );
} // end else
// end of data block
xfer->endBlock();
} // end for j
} // end else, load
} // end for curModuleType
} // end xferDrawableModules
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info;
* 1: Initial version
* 2: Moved condition state xfer before module xfer so we can restore anim frame
* during the module xfer (CBD)
* 4: Added m_ambientSoundEnabled flag
* 5: save full mtx, not pos+orient.
*/
// ------------------------------------------------------------------------------------------------
void Drawable::xfer( Xfer *xfer )
{
// version
const XferVersion currentVersion = 5;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
//Wow, because the constructor creates the ambient sound, the xfer can
//change the ID the sound points to, therefore, we must remove it now
//and restore it in loadPostProcess().
if( xfer->getXferMode() == XFER_LOAD && m_ambientSound )
{
TheAudio->killAudioEventImmediately( m_ambientSound->m_event.getPlayingHandle() );
m_ambientSound->deleteInstance();
m_ambientSound = NULL;
}
// drawable id
DrawableID id = getID();
xfer->xferDrawableID( &id );
setID( id );
// condition state, note that when we're loading we need to force a replace of these flags
if( version >= 2 )
{
m_conditionState.xfer( xfer );
if( xfer->getXferMode() == XFER_LOAD )
replaceModelConditionFlags( m_conditionState, TRUE );
} // end if
if( version >= 3 )
{
if (version >= 5)
{
Matrix3D mtx = *getTransformMatrix();
xfer->xferMatrix3D(&mtx);
setTransformMatrix(&mtx);
}
else
{
// position
Coord3D pos = *getPosition();
xfer->xferCoord3D( &pos );
setPosition( &pos );
// orientation
Real orientation = getOrientation();
xfer->xferReal( &orientation );
setOrientation( orientation );
}
}
// selection flash envelope
Bool selFlash = (m_selectionFlashEnvelope != NULL);
xfer->xferBool( &selFlash );
if( selFlash )
{
// allocate selection flash envelope if we need to
if( m_selectionFlashEnvelope == NULL )
m_selectionFlashEnvelope = newInstance( TintEnvelope );
// xfer
xfer->xferSnapshot( m_selectionFlashEnvelope );
} // end if
// color tint envelope
Bool colFlash = (m_colorTintEnvelope != NULL);
xfer->xferBool( &colFlash );
if( colFlash )
{
// allocate envelope if we need to
if( m_colorTintEnvelope == NULL )
m_colorTintEnvelope = newInstance( TintEnvelope );
// xfer
xfer->xferSnapshot( m_colorTintEnvelope );
} // end if
// terrain decal type
TerrainDecalType decal = getTerrainDecalType();
xfer->xferUser( &decal, sizeof( TerrainDecalType ) );
if( xfer->getXferMode() == XFER_LOAD )
setTerrainDecal( decal );
// explicit opacity
xfer->xferReal( &m_explicitOpacity );
// stealth opacity
xfer->xferReal( &m_stealthOpacity );
// effective stealth opacity
xfer->xferReal( &m_effectiveStealthOpacity );
// decalOpacityFadeTarget
xfer->xferReal( &m_decalOpacityFadeTarget );
// decalOpacityFadeRate
xfer->xferReal( &m_decalOpacityFadeRate );
// decalOpacityFadeRate
xfer->xferReal( &m_decalOpacity );
// object (if present)
ObjectID objectID = m_object ? m_object->getID() : INVALID_ID;
xfer->xferObjectID( &objectID );
// sanity
if( xfer->getXferMode() == XFER_LOAD )
{
if( m_object )
{
if( objectID != m_object->getID() )
{
DEBUG_CRASH(( "Drawable::xfer - Drawable '%s' is attached to wrong object '%s'\n",
getTemplate()->getName().str(), m_object->getTemplate()->getName().str() ));
throw SC_INVALID_DATA;
} // end if
} // end if
else
{
if( objectID != INVALID_ID )
{
#ifdef DEBUG_CRASHING
Object *obj = TheGameLogic->findObjectByID( objectID );
DEBUG_CRASH(( "Drawable::xfer - Drawable '%s' is not attached to an object but should be attached to object '%s' with id '%d'\n",
getTemplate()->getName().str(),
obj ? obj->getTemplate()->getName().str() : "Unknown",
objectID ));
#endif
throw SC_INVALID_DATA;
} // end if
} // end else
} // end if
// particle
// we don't need to worry about this, the particle itself will set it upon loading
// selected
// we won't worry about selection, we'll let TheInGameUI take care of it all
// status
xfer->xferUnsignedInt( &m_status );
// tint status
xfer->xferUnsignedInt( &m_tintStatus );
// prev tint status
xfer->xferUnsignedInt( &m_prevTintStatus );
// fading mode
xfer->xferUser( &m_fadeMode, sizeof( FadingMode ) );
// time elapsed fade
xfer->xferUnsignedInt( &m_timeElapsedFade );
// time to fade
xfer->xferUnsignedInt( &m_timeToFade );
Bool hasLocoInfo = (m_locoInfo != NULL);
xfer->xferBool( &hasLocoInfo );
if (hasLocoInfo)
{
if( xfer->getXferMode() == XFER_LOAD && m_locoInfo == NULL )
m_locoInfo = newInstance(DrawableLocoInfo);
// pitch
xfer->xferReal( &m_locoInfo->m_pitch );
// pitch rate
xfer->xferReal( &m_locoInfo->m_pitchRate );
// roll
xfer->xferReal( &m_locoInfo->m_roll );
// roll rate
xfer->xferReal( &m_locoInfo->m_rollRate );
// yaw
xfer->xferReal( &m_locoInfo->m_yaw );
// acceleration pitch
xfer->xferReal( &m_locoInfo->m_accelerationPitch );
// acceleration pitch rate
xfer->xferReal( &m_locoInfo->m_accelerationPitchRate );
// acceleration roll
xfer->xferReal( &m_locoInfo->m_accelerationRoll );
// acceleration roll rate
xfer->xferReal( &m_locoInfo->m_accelerationRollRate );
// overlap z vel
xfer->xferReal( &m_locoInfo->m_overlapZVel );
// overlap z
xfer->xferReal( &m_locoInfo->m_overlapZ );
// wobble
xfer->xferReal( &m_locoInfo->m_wobble );
// wheel info
xfer->xferReal( &m_locoInfo->m_wheelInfo.m_frontLeftHeightOffset );
xfer->xferReal( &m_locoInfo->m_wheelInfo.m_frontRightHeightOffset );
xfer->xferReal( &m_locoInfo->m_wheelInfo.m_rearLeftHeightOffset );
xfer->xferReal( &m_locoInfo->m_wheelInfo.m_rearRightHeightOffset );
xfer->xferReal( &m_locoInfo->m_wheelInfo.m_wheelAngle );
xfer->xferInt( &m_locoInfo->m_wheelInfo.m_framesAirborneCounter );
xfer->xferInt( &m_locoInfo->m_wheelInfo.m_framesAirborne );
}
// modules
xferDrawableModules( xfer );
// stealth look
xfer->xferUser( &m_stealthLook, sizeof( StealthLookType ) );
// flash count
xfer->xferInt( &m_flashCount );
// flash color
xfer->xferColor( &m_flashColor );
// hidden
xfer->xferBool( &m_hidden );
// hidden by stealth
xfer->xferBool( &m_hiddenByStealth );
// heat vision opacity
xfer->xferReal( &m_heatVisionOpacity );
// instance is identity
xfer->xferBool( &m_instanceIsIdentity );
// instance matrix
xfer->xferUser( &m_instance, sizeof( Matrix3D ) );
// instance scale
xfer->xferReal( &m_instanceScale );
// drawable Info - mostly hold FOW related data.
xfer->xferObjectID(&m_drawableInfo.m_shroudStatusObjectID);
// we do not need to save m_drawableInfo
// m_drawableInfo <--- do nothing with this
// condition state used to be here so we must keep it here for compatibility
if( version < 2 )
{
// sanity, we don't write old versions we can only read them
DEBUG_ASSERTCRASH( xfer->getXferMode() == XFER_LOAD,
("Drawable::xfer - Writing an old format!!!\n") );
// condition state, note that when we're loading we need to force a replace of these flags
m_conditionState.xfer( xfer );
if( xfer->getXferMode() == XFER_LOAD )
replaceModelConditionFlags( m_conditionState, TRUE );
} // end if
// expiration date
xfer->xferUnsignedInt( &m_expirationDate );
// icon count
UnsignedByte iconCount = 0;
if (hasIconInfo())
{
for( UnsignedByte i = 0; i < MAX_ICONS; ++i )
if( getIconInfo()->m_icon[ i ] )
iconCount++;
}
xfer->xferUnsignedByte( &iconCount );
// icon data
AsciiString iconIndexName;
AsciiString iconTemplateName;
UnsignedInt iconKeepFrame;
if( xfer->getXferMode() == XFER_SAVE )
{
for( UnsignedByte i = 0; i < MAX_ICONS; ++i )
{
// skip empty icon slots
if( !hasIconInfo() || getIconInfo()->m_icon[ i ] == NULL )
continue;
// icon index name
iconIndexName.set( drawableIconIndexToName( (DrawableIconType)i ) );
xfer->xferAsciiString( &iconIndexName );
// keep till frame
iconKeepFrame = getIconInfo()->m_keepTillFrame[ i ];
xfer->xferUnsignedInt( &iconKeepFrame );
// icon template name
iconTemplateName = getIconInfo()->m_icon[ i ]->getAnimTemplate()->getName();
xfer->xferAsciiString( &iconTemplateName );
// icon data
xfer->xferSnapshot( getIconInfo()->m_icon[ i ] );
} // end for, i
} // end if, save
else
{
Int i;
// destroy any icons that might be present right now in favor of what we'll load from the file
if (hasIconInfo())
getIconInfo()->clear();
// read each data segment from the file
DrawableIconType iconIndex;
Anim2DTemplate *animTemplate;
for( i = 0; i < iconCount; ++i )
{
// icon index name
xfer->xferAsciiString( &iconIndexName );
iconIndex = drawableIconNameToIndex( iconIndexName.str() );
// keep till frame
xfer->xferUnsignedInt( &iconKeepFrame );
getIconInfo()->m_keepTillFrame[ iconIndex ] = iconKeepFrame;
// icon template name
xfer->xferAsciiString( &iconTemplateName );
animTemplate = TheAnim2DCollection->findTemplate( iconTemplateName );
if( animTemplate == NULL )
{
DEBUG_CRASH(( "Drawable::xfer - Unknown icon template '%s'\n", iconTemplateName.str() ));
throw SC_INVALID_DATA;
} // end if
// create icon
getIconInfo()->m_icon[ iconIndex ] = newInstance(Anim2D)( animTemplate, TheAnim2DCollection );
// icon data
xfer->xferSnapshot( getIconInfo()->m_icon[ iconIndex ] );
} // end for, i
} // end else, load
if( xfer->getXferMode() == XFER_LOAD )
{
// On load, we want to set it to none, because stealthlook updates
// when it changes. So in the next stealth update, it will be set to
// it's correct value, and the drawable updated (hide shadows and such). jba.
m_stealthLook = STEALTHLOOK_NONE;
// Also, need to update the hidden status for all sub-modules.
if (m_hidden || m_hiddenByStealth) {
updateHiddenStatus();
}
}
//
// when saving we should never have dirty modules, but when loading we will force the modules
// to be dirty just to be sure that they get re-evaluated after the load
//
#ifdef DIRTY_CONDITION_FLAGS
if( xfer->getXferMode() == XFER_SAVE )
DEBUG_ASSERTCRASH( m_isModelDirty == FALSE, ("Drawble::xfer - m_isModelDirty is not FALSE!\n") );
else
m_isModelDirty = TRUE;
#endif
if( version >= 4 )
{
xfer->xferBool( &m_ambientSoundEnabled );
}
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void Drawable::loadPostProcess( void )
{
// if we have an object, we don't need to save/load the pos, just restore it.
// if we don't, we'd better save it!
if (m_object != NULL)
{
setTransformMatrix(m_object->getTransformMatrix());
}
if( m_ambientSoundEnabled )
{
startAmbientSound();
}
else
{
stopAmbientSound();
}
} // end loadPostProcess
//=================================================================================================
//=================================================================================================
#ifdef DIRTY_CONDITION_FLAGS
/*static*/ void Drawable::friend_lockDirtyStuffForIteration()
{
if (s_modelLockCount == 0)
{
if (TheGameClient) // WB has no GameClient!
{
for (Drawable* d = TheGameClient->firstDrawable(); d != NULL; d = d->getNextDrawable())
{
// this will force us to update stuff.
d->getDrawModules();
}
}
}
++s_modelLockCount;
}
#endif
//=================================================================================================
//=================================================================================================
#ifdef DIRTY_CONDITION_FLAGS
/*static*/ void Drawable::friend_unlockDirtyStuffForIteration()
{
if (s_modelLockCount > 0)
--s_modelLockCount;
}
#endif
//=================================================================================================
//=================================================================================================
TintEnvelope::TintEnvelope(void)
{
m_attackRate.Set(0,0,0);
m_decayRate.Set(0,0,0);
m_peakColor.Set(0,0,0);
m_currentColor.Set(0,0,0);
m_envState = ENVELOPE_STATE_REST;
m_sustainCounter = 0;
m_affect = FALSE;
}
//-------------------------------------------------------------------------------------------------
const Real FADE_RATE_EPSILON = (0.001f);
//-------------------------------------------------------------------------------------------------
void TintEnvelope::play(const RGBColor *peak, UnsignedInt atackFrames, UnsignedInt decayFrames, UnsignedInt sustainAtPeak )
{
setPeakColor( peak );
setAttackFrames( atackFrames );
setDecayFrames( decayFrames );
m_envState = ENVELOPE_STATE_ATTACK;
m_sustainCounter = sustainAtPeak;
m_affect = TRUE;
Vector3 delta;
Vector3::Subtract(m_currentColor, m_peakColor, &delta);
if ( delta.Length() <= FADE_RATE_EPSILON ) // we are practically already at this color
m_envState = ENVELOPE_STATE_SUSTAIN;
}
//-------------------------------------------------------------------------------------------------
void TintEnvelope::setAttackFrames(UnsignedInt frames)
{
Real recipFrames = 1.0f / (Real)MAX(1,frames);
m_attackRate.Set( m_currentColor );
Vector3::Subtract( m_peakColor, m_attackRate, &m_attackRate);
m_attackRate.Scale( Vector3(recipFrames, recipFrames, recipFrames) );
}
//-------------------------------------------------------------------------------------------------
void TintEnvelope::setDecayFrames( UnsignedInt frames )
{
Real recipFrames = ( -1.0f ) / (Real)MAX(1,frames);
m_decayRate.Set( m_peakColor );
m_decayRate.Scale( Vector3(recipFrames, recipFrames, recipFrames) );
}
//-------------------------------------------------------------------------------------------------
void TintEnvelope::update(void)
{
switch ( m_envState )
{
case ( ENVELOPE_STATE_REST ) : //most likely case
{
m_currentColor.Set(0,0,0);
m_affect = FALSE;
break;
}
case ( ENVELOPE_STATE_DECAY ) : // much more likely than attack
{
if (m_decayRate.Length() > m_currentColor.Length() || m_currentColor.Length() <= FADE_RATE_EPSILON) //we are at rest
{
m_envState = ENVELOPE_STATE_REST;
m_affect = FALSE;
}
else
{
Vector3::Add( m_decayRate, m_currentColor, &m_currentColor );//Add the decayRate to the current color;
m_affect = TRUE;
}
break;
}
case ( ENVELOPE_STATE_ATTACK ) :
{
Vector3 delta;
Vector3::Subtract(m_currentColor, m_peakColor, &delta);
if (m_attackRate.Length() > delta.Length() || delta.Length() <= FADE_RATE_EPSILON) //we are at the peak
{
if ( m_sustainCounter )
{
m_envState = ENVELOPE_STATE_SUSTAIN;
}
else
{
m_envState = ENVELOPE_STATE_DECAY;
}
}
else
{
Vector3::Add( m_attackRate, m_currentColor, &m_currentColor );//Add the attackRate to the current color;
m_affect = TRUE;
}
break;
}
case ( ENVELOPE_STATE_SUSTAIN ) :
{
if ( m_sustainCounter > 0 )
--m_sustainCounter;
else
release();
break;
}
default:
{
//do nothing, we are sustaining until externally triggered to release (decay)
break;
}
}
// here we transition the color from current to peak to release, according to
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void TintEnvelope::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info;
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void TintEnvelope::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// attack rate
xfer->xferUser( &m_attackRate, sizeof( Vector3 ) );
// decay rate
xfer->xferUser( &m_decayRate, sizeof( Vector3 ) );
// peak color
xfer->xferUser( &m_peakColor, sizeof( Vector3 ) );
// current color
xfer->xferUser( &m_currentColor, sizeof( Vector3 ) );
// sustain counter
xfer->xferUnsignedInt( &m_sustainCounter );
// affect
xfer->xferBool( &m_affect );
// state
xfer->xferByte( &m_envState );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load Post Process */
// ------------------------------------------------------------------------------------------------
void TintEnvelope::loadPostProcess( void )
{
} // end loadPostProcess