/* ** 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 . */ //////////////////////////////////////////////////////////////////////////////// // // // (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)m_rollRate)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_frontLeftHeightOffsetm_wheelInfo.m_frontLeftHeightOffset=MAX_SUSPENSION_EXTENSION; } if (m_locoInfo->m_wheelInfo.m_frontRightHeightOffsetm_wheelInfo.m_frontRightHeightOffset=MAX_SUSPENSION_EXTENSION; } if (m_locoInfo->m_wheelInfo.m_rearLeftHeightOffsetm_wheelInfo.m_rearLeftHeightOffset=MAX_SUSPENSION_EXTENSION; } if (m_locoInfo->m_wheelInfo.m_rearRightHeightOffsetm_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