/* ** Command & Conquer Generals Zero Hour(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. // // // //////////////////////////////////////////////////////////////////////////////// // FILE Object.cpp //////////////////////////////////////////////////////////////////////////////// // Simple base object // Author: Michael S. Booth, October 2000 /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #define DEFINE_WEAPONCONDITIONMAP #include "Common/BitFlagsIO.h" #include "Common/BuildAssistant.h" #include "Common/Dict.h" #include "Common/GameEngine.h" #include "Common/GameState.h" #include "Common/ModuleFactory.h" #include "Common/Player.h" #include "Common/PlayerList.h" #include "Common/Radar.h" #include "Common/SpecialPower.h" #include "Common/Team.h" #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/Upgrade.h" #include "Common/WellKnownKeys.h" #include "Common/Xfer.h" #include "Common/XferCRC.h" #include "Common/PerfTimer.h" #include "GameClient/Anim2D.h" #include "GameClient/ControlBar.h" #include "GameClient/Drawable.h" #include "GameClient/Eva.h" #include "GameClient/GameClient.h" #include "GameClient/InGameUI.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/ExperienceTracker.h" #include "GameLogic/FiringTracker.h" #include "GameLogic/GameLogic.h" #include "GameLogic/Locomotor.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/AutoHealBehavior.h" #include "GameLogic/Module/BehaviorModule.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Module/CollideModule.h" #include "GameLogic/Module/ContainModule.h" #include "GameLogic/Module/CountermeasuresBehavior.h" #include "GameLogic/Module/CreateModule.h" #include "GameLogic/Module/DamageModule.h" #include "GameLogic/Module/DeletionUpdate.h" #include "GameLogic/Module/DestroyModule.h" #include "GameLogic/Module/DieModule.h" #include "GameLogic/Module/DozerAIUpdate.h" #include "GameLogic/Module/ObjectDefectionHelper.h" #include "GameLogic/Module/ObjectRepulsorHelper.h" #include "GameLogic/Module/ObjectSMCHelper.h" #include "GameLogic/Module/ObjectWeaponStatusHelper.h" #include "GameLogic/Module/OverchargeBehavior.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "GameLogic/Module/PowerPlantUpgrade.h" #include "GameLogic/Module/ProductionUpdate.h" #include "GameLogic/Module/RadarUpgrade.h" #include "GameLogic/Module/RebuildHoleBehavior.h" #include "GameLogic/Module/SpawnBehavior.h" #include "GameLogic/Module/SpecialPowerModule.h" #include "GameLogic/Module/SpecialAbilityUpdate.h" #include "GameLogic/Module/StatusDamageHelper.h" #include "GameLogic/Module/StickyBombUpdate.h" #include "GameLogic/Module/SubdualDamageHelper.h" #include "GameLogic/Module/TempWeaponBonusHelper.h" #include "GameLogic/Module/ToppleUpdate.h" #include "GameLogic/Module/UpdateModule.h" #include "GameLogic/Module/UpgradeModule.h" #include "GameLogic/Object.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/PolygonTrigger.h" #include "GameLogic/ScriptEngine.h" #include "GameLogic/Weapon.h" #include "GameLogic/WeaponSet.h" #include "GameLogic/Module/RadarUpdate.h" #include "GameLogic/Module/PowerPlantUpdate.h" #include "Common/CRCDebug.h" #include "Common/MiscAudio.h" #include "Common/AudioEventInfo.h" #include "Common/DynamicAudioEventInfo.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif #ifdef DEBUG_OBJECT_ID_EXISTS ObjectID TheObjectIDToDebug = INVALID_ID; #endif // ------------------------------------------------------------------------------------------------ static const ModelConditionFlags s_allWeaponFireFlags[WEAPONSLOT_COUNT] = { MAKE_MODELCONDITION_MASK5( MODELCONDITION_FIRING_A, MODELCONDITION_BETWEEN_FIRING_SHOTS_A, MODELCONDITION_RELOADING_A, MODELCONDITION_PREATTACK_A, MODELCONDITION_USING_WEAPON_A ), MAKE_MODELCONDITION_MASK5( MODELCONDITION_FIRING_B, MODELCONDITION_BETWEEN_FIRING_SHOTS_B, MODELCONDITION_RELOADING_B, MODELCONDITION_PREATTACK_B, MODELCONDITION_USING_WEAPON_B ), MAKE_MODELCONDITION_MASK5( MODELCONDITION_FIRING_C, MODELCONDITION_BETWEEN_FIRING_SHOTS_C, MODELCONDITION_RELOADING_C, MODELCONDITION_PREATTACK_C, MODELCONDITION_USING_WEAPON_C ) }; //------------------------------------------------------------------------------------------------- extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- #ifdef DEBUG_LOGGING AsciiString DescribeObject(const Object *obj) { if (!obj) return ""; AsciiString ret; if (obj->getName().isNotEmpty()) { ret.format("Object %d (%s) [%s, owned by player %d (%ls)]", obj->getID(), obj->getName().str(), obj->getTemplate()->getName().str(), obj->getControllingPlayer()->getPlayerIndex(), obj->getControllingPlayer()->getPlayerDisplayName().str()); } else { ret.format("Object %d [%s, owned by player %d (%ls)]", obj->getID(), obj->getTemplate()->getName().str(), obj->getControllingPlayer()->getPlayerIndex(), obj->getControllingPlayer()->getPlayerDisplayName().str()); } return ret; } #endif // DEBUG_LOGGING //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatusMask, Team *team ) : Thing(tt), m_indicatorColor(0), m_ai(NULL), m_physics(NULL), m_geometryInfo(tt->getTemplateGeometryInfo()), m_containedBy(NULL), m_xferContainedByID(INVALID_ID), m_containedByFrame(0), m_behaviors(NULL), m_body(NULL), m_contain(NULL), m_stealth(NULL), m_partitionData(NULL), m_radarData(NULL), m_drawable(NULL), m_next(NULL), m_prev(NULL), m_team(NULL), m_experienceTracker(NULL), m_firingTracker(NULL), m_repulsorHelper(NULL), m_statusDamageHelper(NULL), m_tempWeaponBonusHelper(NULL), m_subdualDamageHelper(NULL), m_smcHelper(NULL), m_wsHelper(NULL), m_defectionHelper(NULL), m_partitionLastLook(NULL), m_partitionRevealAllLastLook(NULL), m_partitionLastShroud(NULL), m_partitionLastThreat(NULL), m_partitionLastValue(NULL), m_smcUntil(NEVER), m_privateStatus(0), m_formationID(NO_FORMATION_ID), m_isReceivingDifficultyBonus(FALSE), m_singleUseCommandUsed(FALSE), m_scriptStatus(0), m_enteredOrExitedFrame(0), m_visionSpiedMask (PLAYERMASK_NONE), m_numTriggerAreasActive(0) { #if defined(_DEBUG) || defined(_INTERNAL) m_hasDiedAlready = false; #endif //Modules have not been created yet! m_modulesReady = false; // Force the thing template to use the most overridden version of itself - jkmcd // Note that after this, the object will be using m_template, which forces the usage of the // most overridden version of tt, so this is okay. tt = (const ThingTemplate *) tt->getFinalOverride(); Int i, modIdx; AsciiString modName; //Added By Sadullah Nader //Initializations inserted m_formationOffset.x = m_formationOffset.y = 0.0f; m_iPos.zero(); // for (i = 0; i < MAX_PLAYER_COUNT; ++i) { m_visionSpiedBy[i] = 0; } for( i = 0; i < DISABLED_COUNT; i++ ) { m_disabledTillFrame[ i ] = NEVER; } m_weaponBonusCondition = 0; m_curWeaponSetFlags.clear(); // sanity if( TheGameLogic == NULL || tt == NULL ) { assert( 0 ); return; } // end if // Object's set of these persist for the life of the object. m_partitionLastLook = newInstance(SightingInfo); m_partitionLastLook->reset(); m_partitionRevealAllLastLook = newInstance(SightingInfo); m_partitionRevealAllLastLook->reset(); m_partitionLastShroud = newInstance(SightingInfo); m_partitionLastShroud->reset(); m_partitionLastThreat = newInstance(SightingInfo); m_partitionLastThreat->reset(); m_partitionLastValue = newInstance(SightingInfo); m_partitionLastValue->reset(); // must set ID to zero, since some of these set methods // will cause network messages to be sent // which use this ID. m_id = INVALID_ID; m_producerID = INVALID_ID; m_builderID = INVALID_ID; m_status = objectStatusMask; m_layer = LAYER_GROUND; m_group = NULL; m_constructionPercent = CONSTRUCTION_COMPLETE; // complete by default m_visionRange = tt->friend_calcVisionRange(); m_shroudClearingRange = tt->friend_calcShroudClearingRange(); if( m_shroudClearingRange == -1.0f ) m_shroudClearingRange = m_visionRange;// Backwards compatible, and perfectly logical default to assign m_shroudRange = 0.0f; m_singleUseCommandUsed = false; // assign unique object id setID( TheGameLogic->allocateObjectID() ); // // 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 // Int totalModules = tt->getBehaviorModuleInfo().getCount() + NUM_SLEEP_HELPERS; // need to take into account all the helper modules // allocate the publicModule arrays // pool[]ify m_behaviors = MSGNEW("ModulePtrs") BehaviorModule*[totalModules + 1]; BehaviorModule** curB = m_behaviors; const ModuleInfo& mi = tt->getBehaviorModuleInfo(); // set m_team to null before the first call, to avoid naughtiness... // If no team is specified in the constructor, then assign the object // to the neutral team. setTeam(team ? team : ThePlayerList->getNeutralPlayer()->getDefaultTeam()); // the helpers are done first -- even before Behaviors! -- in case a module needs // to call something that uses them. static const NameKeyType smcHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SMCHelper" ); static ObjectSMCHelperModuleData smcModuleData; smcModuleData.setModuleTagNameKey( smcHelperModuleDataTagNameKey ); m_smcHelper = newInstance(ObjectSMCHelper)(this, &smcModuleData); *curB++ = m_smcHelper; //Inactive bodies can't take special damage since they can't take damage Bool isInactiveBody = FALSE; for( Int infoIndex = 0; infoIndex < mi.getCount(); ++infoIndex ) { modName = mi.getNthName(infoIndex); if (modName.isEmpty()) continue; if( modName.compare("InactiveBody") == 0 ) { isInactiveBody = TRUE; break; } } if( !isInactiveBody ) { static const NameKeyType statusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_StatusDamageHelper" ); static StatusDamageHelperModuleData statusModuleData; statusModuleData.setModuleTagNameKey( statusHelperModuleDataTagNameKey ); m_statusDamageHelper = newInstance(StatusDamageHelper)(this, &statusModuleData); *curB++ = m_statusDamageHelper; static const NameKeyType subdualHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SubdualDamageHelper" ); static SubdualDamageHelperModuleData subdualModuleData; subdualModuleData.setModuleTagNameKey( subdualHelperModuleDataTagNameKey ); m_subdualDamageHelper = newInstance(SubdualDamageHelper)(this, &subdualModuleData); *curB++ = m_subdualDamageHelper; } if (TheAI != NULL && TheAI->getAiData()->m_enableRepulsors && isKindOf(KINDOF_CAN_BE_REPULSED)) { // if we can ever be a temporary-repulsor, make a repulsor helper. (srj) static const NameKeyType repulsorHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_RepulsorHelper" ); static ObjectRepulsorHelperModuleData repulsorModuleData; repulsorModuleData.setModuleTagNameKey( repulsorHelperModuleDataTagNameKey ); m_repulsorHelper = newInstance(ObjectRepulsorHelper)(this, &repulsorModuleData); *curB++ = m_repulsorHelper; } /** @todo srj -- figure out how to create this only on demand. currently we don't have a good way to add/remove update modules from an object on-the-fly, so we fake it here, and just skip the creation if it is impossible for this object to ever defect... */ // shrubbery cannot defect. no, really. if (!tt->isKindOf(KINDOF_SHRUBBERY)) { static const NameKeyType defectionModuleDataTagNameKey = NAMEKEY( "ModuleTag_DefectionHelper" ); static ObjectDefectionHelperModuleData defectionModuleData; defectionModuleData.setModuleTagNameKey( defectionModuleDataTagNameKey ); m_defectionHelper = newInstance(ObjectDefectionHelper)(this, &defectionModuleData); *curB++ = m_defectionHelper; } if (tt->canPossiblyHaveAnyWeapon()) { // we only need a firingtracker and wshelper if we can possibly have a weapon. static const NameKeyType weaponStatusModuleDataTagNameKey = NAMEKEY( "ModuleTag_WeaponStatusHelper" ); static ObjectWeaponStatusHelperModuleData weaponStatusModuleData; weaponStatusModuleData.setModuleTagNameKey( weaponStatusModuleDataTagNameKey ); m_wsHelper = newInstance(ObjectWeaponStatusHelper)(this, &weaponStatusModuleData); *curB++ = m_wsHelper; static const NameKeyType firingTrackerModuleDataTagNameKey = NAMEKEY( "ModuleTag_FiringTrackerHelper" ); static FiringTrackerModuleData firingTrackerModuleData; firingTrackerModuleData.setModuleTagNameKey( firingTrackerModuleDataTagNameKey ); m_firingTracker = newInstance(FiringTracker)(this, &firingTrackerModuleData); *curB++ = m_firingTracker; static const NameKeyType tempWeaponBonusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_TempWeaponBonusHelper" ); static TempWeaponBonusHelperModuleData tempWeaponBonusModuleData; tempWeaponBonusModuleData.setModuleTagNameKey( tempWeaponBonusHelperModuleDataTagNameKey ); m_tempWeaponBonusHelper = newInstance(TempWeaponBonusHelper)(this, &tempWeaponBonusModuleData); *curB++ = m_tempWeaponBonusHelper; } // behaviors are always done first, so they get into the publicModule arrays // before anything else. for (modIdx = 0; modIdx < mi.getCount(); ++modIdx) { modName = mi.getNthName(modIdx); if (modName.isEmpty()) continue; BehaviorModule* newMod = (BehaviorModule*)TheModuleFactory->newModule(this, modName, mi.getNthData(modIdx), MODULETYPE_BEHAVIOR); *curB++ = newMod; BodyModuleInterface* body = newMod->getBody(); if (body) { DEBUG_ASSERTCRASH(m_body == NULL, ("Duplicate bodies")); m_body = body; } ContainModuleInterface* contain = newMod->getContain(); if (contain) { DEBUG_ASSERTCRASH(m_contain == NULL, ("Duplicate containers")); m_contain = contain; } StealthUpdate* stealth = (StealthUpdate*)newMod->getStealth(); if ( stealth ) { DEBUG_ASSERTCRASH( m_stealth == NULL, ("DuplicateStealthUpdates!") ); m_stealth = stealth; } AIUpdateInterface* ai = newMod->getAIUpdateInterface(); if (ai) { if( m_ai ) { DEBUG_ASSERTCRASH( m_ai == NULL, ("%s has more than one AI module. This is illegal!\n", getTemplate()->getName().str()) ); } m_ai = ai; } static NameKeyType key_PhysicsUpdate = NAMEKEY("PhysicsBehavior"); if (newMod->getModuleNameKey() == key_PhysicsUpdate) { DEBUG_ASSERTCRASH(m_physics == NULL, ("You should never have more than one Physics module (%s)\n",getTemplate()->getName().str())); m_physics = (PhysicsBehavior*)newMod; } } *curB = NULL; AIUpdateInterface *ai = getAIUpdateInterface(); if (ai) { ai->setAttitude(getTeam()->getPrototype()->getTemplateInfo()->m_initialTeamAttitude); if (m_team && m_team->getPrototype() && m_team->getPrototype()->getAttackPriorityName().isNotEmpty()) { AsciiString name = m_team->getPrototype()->getAttackPriorityName(); const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); if (info && info->getName().isNotEmpty()) { ai->setAttackInfo(info); } } } // allocate experience tracker m_experienceTracker = newInstance(ExperienceTracker)(this); // If a valid team has been assigned me, then I have a Player I can ask about my starting level const Player* controller = getControllingPlayer(); m_experienceTracker->setVeterancyLevel( controller->getProductionVeterancyLevel( getTemplate()->getName() ) ); /// allow for inter-Module resolution for (BehaviorModule** b = m_behaviors; *b; ++b) { (*b)->onObjectCreated(); } m_numTriggerAreasActive = 0; m_enteredOrExitedFrame = 0; m_isSelectable = tt->isKindOf(KINDOF_SELECTABLE); m_healthBoxOffset.zero();// this is used for units that are amorphous, like angry mob //Modules have now been completely created! m_modulesReady = true; TheRadar->addObject( this ); // register the object with the GameLogic TheGameLogic->registerObject( this ); //disable occlusion for some time after object is created to allow them to exit the factory/building. m_safeOcclusionFrame = TheGameLogic->getFrame()+tt->getOcclusionDelay(); m_soleHealingBenefactorID = INVALID_ID; ///< who is the only other object that can give me this non-stacking heal benefit? m_soleHealingBenefactorExpirationFrame = 0; ///< on what frame can I accept healing (thus to switch) from a new benefactor } // end Object //------------------------------------------------------------------------------------------------- /** Emit message announcing object's creation * Note: Have to do this in virtual init() method because virtual methods * don't become virtual until AFTER the constructor has completed, and we * need to send our type in this message via virtual getType(). */ //------------------------------------------------------------------------------------------------- void Object::initObject() { // Weapons & Damage ------------------------------------------------------------------------------------------------- // Force the initial weapon set to be instantiated & reloaded. //GS No Bad Wrong // The flags are constructed to empty, and between then and now they may be set in valid ways by onCreate modules. // We don't want to blow that away. updateWeaponSet is safe to call on its own, so I will move that to the end. // m_curWeaponSetFlags.clear(); // m_weaponSet.updateWeaponSet(this); // m_weaponBonusCondition = 0; for (int i = 0; i < WEAPONSLOT_COUNT; ++i) m_lastWeaponCondition[i] = WSF_INVALID; // emit message announcing object's creation TheGameLogic->sendObjectCreated( this ); // If I have a valid team assigned, I can run through my Upgrade modules with his flags updateUpgradeModules(); //If the player has battle plans (America Strategy Center), then apply those bonuses //to this object if applicable. Internally it validates certain kinds of objects. const Player* controller = getControllingPlayer(); if (controller) { if (!getReceivingDifficultyBonus() && TheScriptEngine->getObjectsShouldReceiveDifficultyBonus()) { setReceivingDifficultyBonus(TRUE); } if (controller->getNumBattlePlansActive() > 0) { controller->applyBattlePlanBonusesForObject( this ); } } //For each special power module that we have, add it's type to the specialpower bits. This is //for optimal access later. for (BehaviorModule** m = m_behaviors; *m; ++m) { SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); if (!sp) continue; const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); if( spTemplate ) { SET_SPECIALPOWERMASK( m_specialPowerBits, spTemplate->getSpecialPowerType() ); } } // Kris -- All missiles must be projectiles! This is the perfect place to assert them! // srj: yes, but only in debug... #if defined(_DEBUG) || defined(_INTERNAL) if( !isKindOf( KINDOF_PROJECTILE ) ) { if( isKindOf( KINDOF_SMALL_MISSILE ) || isKindOf( KINDOF_BALLISTIC_MISSILE ) ) { //Warning only... DEBUG_CRASH( ("Missile %s must also be a KindOf = PROJECTILE in addition to being either a SMALL_MISSILE or PROJECTILE_MISSILE -- call Kris (36844) for questions!", getTemplate()->getName().str() ) ); } } #endif if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { // Notify script conditions to update conditions that consider unit counts. // We ignore projectiles cause they are frequently created & destroyed, and are not // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. TheScriptEngine->notifyOfObjectCreationOrDestruction(); TheGameLogic->updateObjectsChangedTriggerAreas(); } // Everything (like weaponSet flags) is inited, so check if the WeaponSet needs to change. m_weaponSet.updateWeaponSet(this); if( isKindOf( KINDOF_MINE ) || isKindOf( KINDOF_BOOBY_TRAP ) || isKindOf( KINDOF_DEMOTRAP ) ) { ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordMine(); } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Object::~Object() { // tell the AI the building is gone /// @todo Generalize the notion of objects entering and leaving the world, so we don't have to special case this TheAI->pathfinder()->removeObjectFromPathfindMap( this ); if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { // Notify script conditions to update conditions that consider unit counts. // We ignore projectiles cause they are frequently created & destroyed, and are not // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. TheGameLogic->updateObjectsChangedTriggerAreas(); TheScriptEngine->notifyOfObjectCreationOrDestruction(); } // // remove from radar before we NULL out the team ... the order of ops are critical here // because the radar code will sometimes look at the team info and it is assumed through // the team and player code that the team is valid // if( m_radarData ) TheRadar->removeObject( this ); // emit message announcing object's destruction. Again, order is important; we must do this // before wiping out the team. TheGameLogic->sendObjectDestroyed( this ); // empty the team setTeam( NULL ); // Object's set of these persist for the life of the object. m_partitionLastLook->deleteInstance(); m_partitionLastLook = NULL; m_partitionRevealAllLastLook->deleteInstance(); m_partitionRevealAllLastLook = NULL; m_partitionLastShroud->deleteInstance(); m_partitionLastShroud = NULL; m_partitionLastThreat->deleteInstance(); m_partitionLastThreat = NULL; m_partitionLastValue->deleteInstance(); m_partitionLastValue = NULL; // remove the object from the partition system if present if( m_partitionData ) ThePartitionManager->unRegisterObject( this ); // if we are in a group, remove us if (m_group) m_group->remove( this ); // note, do NOT free these, there are just a shadow copy! m_ai = NULL; m_physics = NULL; // delete any modules present for (BehaviorModule** b = m_behaviors; *b; ++b) { (*b)->deleteInstance(); *b = NULL; // in case other modules call findModule from their dtor! } delete [] m_behaviors; m_behaviors = NULL; if( m_experienceTracker ) m_experienceTracker->deleteInstance(); m_experienceTracker = NULL; // we don't need to delete these, there were deleted on the m_behaviors list m_firingTracker = NULL; m_repulsorHelper = NULL; m_statusDamageHelper = NULL; m_tempWeaponBonusHelper = NULL; m_subdualDamageHelper = NULL; m_smcHelper = NULL; m_wsHelper = NULL; m_defectionHelper = NULL; // reset id to zero so we never mistaken grab "dead" objects m_id = INVALID_ID; // Instead of removing it from the named cache, notify the script engine that it has died. // The script engine will remove it from the cache if necessary. The script engine needs to take // a crack at this in case it is the current "This Object" pointer. TheScriptEngine->notifyOfObjectDestruction(this); } //------------------------------------------------------------------------------------------------- /// this object now contained in "containedBy" //------------------------------------------------------------------------------------------------- void Object::onContainedBy( Object *containedBy ) { setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); if (containedBy && containedBy->getContain()->isEnclosingContainerFor(this)) setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); else clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); m_containedBy = containedBy; m_containedByFrame = TheGameLogic->getFrame(); handlePartitionCellMaintenance(); // which should unlook me now that I am contained } //------------------------------------------------------------------------------------------------- /// this object no longer contained in "containedBy" //------------------------------------------------------------------------------------------------- void Object::onRemovedFrom( Object *removedFrom ) { clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) ); m_containedBy = NULL; m_containedByFrame = 0; handlePartitionCellMaintenance(); // get a clean look, now that I am outdoors, again } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Int Object::getTransportSlotCount() const { Int count = getTemplate()->getRawTransportSlotCount(); ContainModuleInterface* contain = getContain(); if ( contain && contain->isSpecialZeroSlotContainer() ) { count = 0; const ContainedItemsList* items = contain->getContainedItemsList(); if (items) { for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it) { count += (*it)->getTransportSlotCount(); } } } return count; } //------------------------------------------------------------------------------------------------- /** Run from GameLogic::destroyObject */ //------------------------------------------------------------------------------------------------- void Object::onDestroy() { // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. if( m_containedBy && m_containedBy->getContain() ) { m_containedBy->getContain()->removeFromContain( this ); } // // 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 (BehaviorModule** b = m_behaviors; *b; ++b) { (*b)->onDelete(); } //Have to remove ourself from looking as well. RebuildHoleWorkers definately hit here. handlePartitionCellMaintenance(); } // end onDestroy //============================================================================= //============================================================================= void Object::setGeometryInfo(const GeometryInfo& geom) { m_geometryInfo = geom; if( m_partitionData ) { // if our geometry changes, we unregister and re-register with the partitionmgr // so that our size gets updated appropriately. this shouldn't be a problem // unless setGeometryInfo gets called frequently. (srj) ThePartitionManager->unRegisterObject( this ); ThePartitionManager->registerObject( this ); } if (m_drawable) m_drawable->reactToGeometryChange(); } //============================================================================= //============================================================================= void Object::setGeometryInfoZ( Real newZ ) { // A Z change only does not need to un/register with the PartitionManager m_geometryInfo.setMaxHeightAbovePosition( newZ ); if (m_drawable) m_drawable->reactToGeometryChange(); } //============================================================================= void Object::friend_setUndetectedDefector( Bool status ) { if (status) m_privateStatus |= UNDETECTED_DEFECTOR; else m_privateStatus &= ~UNDETECTED_DEFECTOR; } //============================================================================= void Object::restoreOriginalTeam() { if( m_team == NULL || m_originalTeamName.isEmpty() ) return; Team* origTeam = TheTeamFactory->findTeam(m_originalTeamName); if (origTeam == NULL) { DEBUG_CRASH(("Object original team (%s) could not be found or created! (srj)\n",m_originalTeamName.str())); return; } if (m_team == origTeam) { DEBUG_CRASH(("Object appears to still be on its original team, so why are we attempting to restore it? (srj)\n")); return; } setTeam(origTeam); } //============================================================================= //============================================================================= void Object::setTeam( Team *team ) { // In order to prevent spawning useful units for a player after he dies, we // just assign objects to the neutral player if we try to misbehave. if (team && !team->getControllingPlayer()->isPlayerActive()) team = ThePlayerList->getNeutralPlayer()->getDefaultTeam(); setTemporaryTeam(team); m_originalTeamName = m_team ? m_team->getName() : AsciiString::TheEmptyString; } //============================================================================= //============================================================================= void Object::setTemporaryTeam( Team *team ) { const Bool restoring = false; setOrRestoreTeam(team, restoring); } //============================================================================= //============================================================================= void Object::setOrRestoreTeam( Team* team, Bool restoring ) { // don't do anything if the team hasn't changed if( m_team == team ) return; Team* oldTeam = m_team; // Before Switch ////////////////////////// if (m_team) { if (m_team->isInList_TeamMemberList(this)) { m_team->removeFrom_TeamMemberList(this); m_team->getControllingPlayer()->becomingTeamMember(this, false); } } // Switch ////////////////////////// m_team = team; // After Switch ////////////////////////// if (m_team) { if (!m_team->isInList_TeamMemberList(this)) { m_team->prependTo_TeamMemberList(this); m_team->getControllingPlayer()->becomingTeamMember(this, true); } // now, adjust the attitude of the unit to its new team. const TeamPrototype* proto = m_team->getPrototype(); if (proto && proto->getTemplateInfo()) { AIUpdateInterface *ai = getAIUpdateInterface(); if (ai) { ai->setAttitude(proto->getTemplateInfo()->m_initialTeamAttitude); if (proto->getAttackPriorityName().isNotEmpty()) { AsciiString name = proto->getAttackPriorityName(); const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); if (info && info->getName().isNotEmpty()) { ai->setAttackInfo(info); } } } } // emit message announcing object's new alliance Drawable *draw = getDrawable(); if (draw) draw->changedTeam(); } // This can't just go in ::defect, because some things just do setTeam. The act of // setting a new team needs to tell the modules and do other important stuff. // And it needs to happen after the switch. if( oldTeam && team && !restoring ) onCapture( oldTeam->getControllingPlayer(), team->getControllingPlayer() ); // // the team changed we have a change in priorities on the radar if we are // a candidate for the radar as it is // if( m_radarData ) { // removing it and adding it will cause a resort to happen TheRadar->removeObject( this ); TheRadar->addObject( this ); } // Tell TheInGameUI that the object has changed hands Int oldPlayerIndex = (oldTeam)?(oldTeam->getControllingPlayer()->getPlayerIndex()):-1; Int newPlayerIndex = (m_team)?(m_team->getControllingPlayer()->getPlayerIndex()):-1; if (oldPlayerIndex != newPlayerIndex) TheInGameUI->objectChangedTeam(this, oldPlayerIndex, newPlayerIndex); } //============================================================================= enum { BOOBY_TRAP_SCAN_RANGE = 25 }; Bool Object::checkAndDetonateBoobyTrap(const Object *victim) { if( !testStatus(OBJECT_STATUS_BOOBY_TRAPPED) ) return FALSE; PartitionFilterAcceptByKindOf kindFilter(MAKE_KINDOF_MASK(KINDOF_BOOBY_TRAP), KINDOFMASK_NONE); PartitionFilterSameMapStatus filterMapStatus(this); PartitionFilter *filters[3]; filters[0] = &kindFilter; filters[1] = &filterMapStatus; filters[2] = NULL; ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( getPosition(), BOOBY_TRAP_SCAN_RANGE + getGeometryInfo().getBoundingCircleRadius(), FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR ); MemoryPoolObjectHolder hold(iter);// This is the magic thing that frees the dynamically made iter in its destructor Object *ourBoobyTrap = NULL; for( Object *other = iter->first(); other; other = iter->next() ) { if( other->getProducerID() == getID() )// Sticky bombs call the thing they are on their producer for just such an occasion { ourBoobyTrap = other; break; } } if( ourBoobyTrap ) { static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" ); StickyBombUpdate *update = (StickyBombUpdate*)ourBoobyTrap->findUpdateModule( key_StickyBombUpdate ); if( update ) { if( victim && ourBoobyTrap->getControllingPlayer()->getRelationship(victim->getTeam()) == ALLIES ) return FALSE;// Friends don't touch friends boobies. update->detonate(); return TRUE;// Booby Trapped status will be cleared by stickybomb, as they set it } } return FALSE; } //============================================================================= void Object::setStatus( ObjectStatusMaskType objectStatus, Bool set ) { ObjectStatusMaskType oldStatus = m_status; if (set) m_status.set( objectStatus ); else m_status.clear( objectStatus ); if (m_status != oldStatus) { if( set && objectStatus.test( OBJECT_STATUS_REPULSOR ) && m_repulsorHelper != NULL ) { // Damaged repulsable civilians scare (repulse) other civs, but only // for a short amount of time... use the repulsor helper to turn off repulsion shortly. m_repulsorHelper->sleepUntil(TheGameLogic->getFrame() + 2*LOGICFRAMES_PER_SECOND); } if( objectStatus.test( OBJECT_STATUS_STEALTHED ) || objectStatus.test( OBJECT_STATUS_DETECTED ) || objectStatus.test( OBJECT_STATUS_DISGUISED ) ) { //Kris: Aug 20, 2003 //When any of the three key status bits for stealth go on or off, then handle partition updates for vision. if( getTemplate()->getShroudRevealToAllRange() > 0.0f ) { handlePartitionCellMaintenance(); } } // when an object's construction status changes, it needs to have its partition data updated, // in order to maintain the shroud correctly. if( m_status.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) != oldStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) { // CHECK FOR MINES, AND DETONATE THEM NOW ObjectIterator *iter = ThePartitionManager->iteratePotentialCollisions( getPosition(), getGeometryInfo(), getOrientation() ); MemoryPoolObjectHolder hold( iter ); Object *them; for( them = iter->first(); them; them = iter->next() ) { if (them->isKindOf( KINDOF_MINE )) { //DETONATE ANY ENEMY MINES, OR DELETE FRIENDLY ONES Relationship r = getRelationship(them); if (r == ENEMIES) { them->kill(); // detonate mine } else { TheGameLogic->destroyObject(them); } } }// next object if (m_partitionData) m_partitionData->makeDirty(true); } } } //============================================================================= void Object::setScriptStatus( ObjectScriptStatusBit bit, Bool set ) { UnsignedInt oldScriptStatus = m_scriptStatus; if( set ) { m_scriptStatus |= bit; } else { m_scriptStatus &= ~bit; } if( m_scriptStatus != oldScriptStatus ) { if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) ) { if( m_partitionData ) { // if an object becomes disabled or unpowered, then you have to update its partition data because it will // change how far it can see. m_partitionData->makeDirty(true); } if( m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED ) { //I am now disabled, so tell the main game engine! setDisabled( DISABLED_SCRIPT_DISABLED ); } else { //I am no longer disabled, so tell the main game engine! clearDisabled( DISABLED_SCRIPT_DISABLED ); } } if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) ) { if( m_partitionData ) { // if an object becomes disabled or unpowered, then you have to update its partition data because it will // change how far it can see. m_partitionData->makeDirty(true); } if( m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED ) { //I am now underpowered, so tell the main game engine! setDisabled( DISABLED_SCRIPT_UNDERPOWERED ); } else { //I am no longer undperpowered, so tell the main game engine! clearDisabled( DISABLED_SCRIPT_UNDERPOWERED ); } } } } //============================================================================= Bool Object::canCrushOrSquish(Object *otherObj, CrushSquishTestType testType ) const { DEBUG_ASSERTCRASH(this, ("null this in canCrushOrSquish")); if( !otherObj ) { //Can't crush anything. return false; } if( isDisabledByType( DISABLED_UNMANNED ) ) { //Unmanned vehicles cannot crush troops. This was happening when Jarmen Kell sniped //the vehicle and booted the guys out while still moving, as the vehicle is now //on a different team. return false; } UnsignedByte crusherLevel = getCrusherLevel(); // order matters: we want to know if I consider it to be an ally, not vice versa if( getRelationship( otherObj ) == ALLIES ) { //Friends don't let friends crush friends. return false; } if( !crusherLevel ) { //Can't crush anything! return false; } //Test this case for generic infantry getting squished by vehicles! if( testType == TEST_SQUISH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) { //**************************************************************************************** //NOTE: This section of code is used by the pathfinder to determine if the object should // move to the target. I don't think it's the right place to check for this because // the semantics check to see if we can squish something -- not approach it. However // I'm not moving it for fear of some major breakage! -- KM //Bool squisher = crusherLevel > 0; //if( !squisher ) //{ // Weapon *weapon = getCurrentWeapon(); // if( weapon && weapon->isContactWeapon() ) // { // squisher = true; // } //} //if( squisher ) //NOTE2: *** IF YOU REENABLE THIS CODE -- Move the "if( !crusherLevel ) return false" below // this squish section. //**************************************************************************************** { // See if other is squishable static NameKeyType key_squish = NAMEKEY( "SquishCollide" ); if( otherObj->findModule( key_squish ) ) { return true; // squishable. } } } UnsignedByte crushableLevel = otherObj->getCrushableLevel(); if( testType == TEST_CRUSH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) { if( crusherLevel > crushableLevel ) { return true; } } return false; } //------------------------------------------------------------------------------------------------- UnsignedByte Object::getCrusherLevel() const { return getTemplate()->getCrusherLevel(); } //------------------------------------------------------------------------------------------------- UnsignedByte Object::getCrushableLevel() const { return getTemplate()->getCrushableLevel(); } // ------------------------------------------------------------------------------------------------ /** Topple an object, if possible */ // ------------------------------------------------------------------------------------------------ void Object::topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ) { static NameKeyType key_ToppleUpdate = NAMEKEY("ToppleUpdate"); ToppleUpdate* toppleUpdate = (ToppleUpdate*)findModule(key_ToppleUpdate); if( toppleUpdate && toppleUpdate->isAbleToBeToppled() ) { // apply the topple force toppleUpdate->applyTopplingForce( toppleDirection, toppleSpeed, options ); } // end if } // end topple //============================================================================= void Object::setArmorSetFlag(ArmorSetType ast) { m_body->setArmorSetFlag(ast); } //============================================================================= void Object::clearArmorSetFlag(ArmorSetType ast) { m_body->clearArmorSetFlag(ast); } //============================================================================= Bool Object::testArmorSetFlag(ArmorSetType ast) const { return m_body->testArmorSetFlag(ast); } //============================================================================= void Object::reloadAllAmmo(Bool now) { m_weaponSet.reloadAllAmmo(this, now); } //============================================================================= Bool Object::isOutOfAmmo() const { return m_weaponSet.isOutOfAmmo(); } //============================================================================= Bool Object::hasAnyWeapon() const { return m_weaponSet.hasAnyWeapon(); } //============================================================================= Bool Object::hasAnyDamageWeapon() const { //First check to see if we have any weapons -- if not return false. if( !m_weaponSet.hasAnyDamageWeapon() ) { return FALSE; } return TRUE; } //============================================================================= UnsignedInt Object::getMostPercentReadyToFireAnyWeapon() const { return m_weaponSet.getMostPercentReadyToFireAnyWeapon(); } //============================================================================= Bool Object::hasWeaponToDealDamageType(DamageType typeToDeal) const { return m_weaponSet.hasWeaponToDealDamageType(typeToDeal); } //============================================================================= Real Object::getLargestWeaponRange() const { Real retVal = -1; for (Int i = PRIMARY_WEAPON; i < WEAPONSLOT_COUNT; ++i) { Weapon* weapon = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); if (!weapon) { continue; } Real tmpVal = weapon->getAttackRange(this); if (tmpVal > retVal) { retVal = tmpVal; } } return retVal; } //============================================================================= void Object::setFiringConditionForCurrentWeapon() const { if (m_drawable) { WeaponSlotType wslot = m_weaponSet.getCurWeaponSlot(); ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot(wslot, WSF_FIRING); m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[wslot], c); } } //============================================================================= void Object::setModelConditionState( ModelConditionFlagType a ) { if (m_drawable) { m_drawable->setModelConditionState(a); } } //============================================================================= void Object::clearModelConditionState( ModelConditionFlagType a ) { if (m_drawable) { m_drawable->clearModelConditionState(a); } } //============================================================================= void Object::clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ) { if (m_drawable) { m_drawable->clearAndSetModelConditionState(clr, set); } } //============================================================================= void Object::clearModelConditionFlags( const ModelConditionFlags& clr ) { if (m_drawable) { m_drawable->clearModelConditionFlags(clr); } } //============================================================================= void Object::setModelConditionFlags( const ModelConditionFlags& set ) { if (m_drawable) { m_drawable->setModelConditionFlags(set); } } //============================================================================= void Object::clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ) { if (m_drawable) { m_drawable->clearAndSetModelConditionFlags(clr, set); } } //============================================================================= // Special model states are states that are turned on for a period of time, and // turned off automatically -- used for cheer, and scripted special moment // animations. Setting a special state will automatically clear any other // special states that may be turned on so you can only have one at a time. //============================================================================= void Object::setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames ) { clearSpecialModelConditionStates(); setModelConditionState( set ); if( frames == 0 ) { frames = 1; } m_smcUntil = TheGameLogic->getFrame() + frames; m_smcHelper->sleepUntil(m_smcUntil); } //============================================================================= void Object::clearSpecialModelConditionStates() { clearModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_SPECIAL_CHEERING ) ); m_smcUntil = NEVER; } // Lorenzen has some interest in this, ask before deleting //============================================================================= //const ModelConditionFlags& Object::getModelConditionFlags() const //{ // if (m_drawable) // { // return m_drawable->getModelConditionFlags(); // } // else // { // DEBUG_CRASH(("NULL Drawable at this point, you can't get modelconditionflags now.")); // static ModelConditionFlags noFlags; // return noFlags; // } //} //============================================================================= Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) { if (!m_weaponSet.hasAnyWeapon()) return NULL; if (wslot) *wslot = m_weaponSet.getCurWeaponSlot(); return m_weaponSet.getCurWeapon(); } //============================================================================= const Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) const { if (!m_weaponSet.hasAnyWeapon()) return NULL; if (wslot) *wslot = m_weaponSet.getCurWeaponSlot(); return m_weaponSet.getCurWeapon(); } //============================================================================= Weapon* Object::findWaypointFollowingCapableWeapon() { return m_weaponSet.findWaypointFollowingCapableWeapon(); } //============================================================================= Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const { /// @todo srj -- may need to cache this inside weaponset. const Weapon* w = m_weaponSet.findAmmoPipShowingWeapon(); if (w) { numTotal = w->getClipSize(); numFull = w->getRemainingAmmo(); return true; } else { return false; } } //============================================================================= /* NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), since that isn't an incredibly fast call, and this is called repeatedly in some inner loops where we already know that isAbleToAttack() == true. so you should always call isAbleToAttack prior to calling this! (srj) */ CanAttackResult Object::getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot ) const { // NO! BAD! WRONG! // If we can't attack at all, then we cannot attack this //if (!isAbleToAttack()) // return FALSE; // Otherwise leave it up to our weapons. return m_weaponSet.getAbleToAttackSpecificObject( t, this, target, commandSource, specificSlot ); } //============================================================================= //Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. CanAttackResult Object::getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot ) const { return m_weaponSet.getAbleToUseWeaponAgainstTarget( attackType, this, victim, pos, commandSource, specificSlot ); } //============================================================================= Bool Object::chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource ) { return m_weaponSet.chooseBestWeaponForTarget(this, target, criteria, cmdSource ); } //DECLARE_PERF_TIMER(fireCurrentWeapon) //============================================================================= void Object::fireCurrentWeapon(Object *target) { //USE_PERF_TIMER(fireCurrentWeapon) // victim may have already been destroyed if (target == NULL) return; Weapon* weapon = m_weaponSet.getCurWeapon(); if (weapon && (weapon->getStatus() == READY_TO_FIRE)) { Bool reloaded = weapon->fireWeapon(this, target); DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); if (m_firingTracker) m_firingTracker->shotFired(weapon, target->getID()); if (reloaded) releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. friend_setUndetectedDefector( FALSE );// My secret is out } } //============================================================================= void Object::fireCurrentWeapon(const Coord3D* pos) { //USE_PERF_TIMER(fireCurrentWeapon) if (pos == NULL) return; Weapon* weapon = m_weaponSet.getCurWeapon(); if (weapon && (weapon->getStatus() == READY_TO_FIRE)) { Bool reloaded = weapon->fireWeapon(this, pos); DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); if (m_firingTracker) m_firingTracker->shotFired(weapon, INVALID_ID); if (reloaded) releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. friend_setUndetectedDefector( FALSE );// My secret is out } } //============================================================================== void Object::notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) { if ( m_firingTracker ) m_firingTracker->shotFired( weaponFired, victimID ); } //============================================================================= void Object::preFireCurrentWeapon( const Object *victim ) { Weapon* weapon = m_weaponSet.getCurWeapon(); //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens //next frame. if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame() ) { weapon->preFireWeapon( this, victim ); friend_setUndetectedDefector( FALSE );// My secret is out } } // ============================================================================ /** Using the firing tracker, return the frame a shot was last fired on */ // ============================================================================ UnsignedInt Object::getLastShotFiredFrame() const { UnsignedInt recent = 0; for (int i = 0; i < WEAPONSLOT_COUNT; ++i) { const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); if (w) { UnsignedInt when = w->getLastShotFrame(); if (when > recent) recent = when; } } return recent; } // ============================================================================ /** Get the victim ID we last shot at */ // ============================================================================ ObjectID Object::getLastVictimID() const { return m_firingTracker ? m_firingTracker->getLastShotVictim() : INVALID_ID; } //============================================================================= // Object::getRelationship //============================================================================= Relationship Object::getRelationship(const Object *that) const { const Team *myTeam = getTeam(); if (myTeam && that) { if (getIsUndetectedDefector()) { return NEUTRAL; // so my AI does not give away my position by auto acquire } else if (that->getIsUndetectedDefector()) { return ALLIES; // so I treat undetecteddefectors like they were my very own } else { return myTeam->getRelationship( that->getTeam() ); } } return NEUTRAL; } //============================================================================= // Object::getControllingPlayer //============================================================================= Player * Object::getControllingPlayer() const { const Team* myTeam = this->getTeam(); if (myTeam) return myTeam->getControllingPlayer(); return NULL; } //============================================================================= void Object::setProducer(const Object* obj) { m_producerID = obj ? obj->getID() : INVALID_ID; // seems like a good idea, but is not. (srj) // if (obj) // m_indicatorColor = obj->m_indicatorColor; } //============================================================================= void Object::setBuilder( const Object *obj ) { m_builderID = obj ? obj->getID() : INVALID_ID; } //============================================================================= void Object::setCustomIndicatorColor(Color c) { if (m_indicatorColor != c) { m_indicatorColor = c; if (m_drawable) m_drawable->changedTeam(); } } //============================================================================= void Object::removeCustomIndicatorColor() { setCustomIndicatorColor(0); } //============================================================================= // Object::getIndicatorColor //============================================================================= Color Object::getIndicatorColor() const { if (m_indicatorColor == 0) { const Team *myTeam = getTeam(); if (myTeam) { const Player* p = myTeam->getControllingPlayer(); if (p) { return p->getPlayerColor(); } } return GameMakeColor(0, 0, 0, 255); } else { return m_indicatorColor; } } //============================================================================= // Object::getNightIndicatorColor - used to make blue/purple easier to see on night models. //============================================================================= Color Object::getNightIndicatorColor() const { if (m_indicatorColor == 0) { const Team *myTeam = getTeam(); if (myTeam) { const Player* p = myTeam->getControllingPlayer(); if (p) { return p->getPlayerNightColor(); } } return GameMakeColor(0, 0, 0, 255); } else { return m_indicatorColor; } } //============================================================================= // Object::isLocallyControlled //============================================================================= Bool Object::isLocallyControlled() const { return getControllingPlayer() == ThePlayerList->getLocalPlayer(); } //============================================================================= // Object::isLocallyControlled //============================================================================= Bool Object::isNeutralControlled() const { return getControllingPlayer() == ThePlayerList->getNeutralPlayer(); } //------------------------------------------------------------------------------------------------- inline Bool isPosDifferent(const Coord3D* a, const Coord3D* b) { // this is necessary because PhysicsBehavior may generate tiny changes even when // "standing still", due to roundoff errors. It's important that we only invalidate // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) // so we must put in some cleverness... const Real THRESH = 0.01f; if (fabs(a->x - b->x) > THRESH) return true; if (fabs(a->y - b->y) > THRESH) return true; if (fabs(a->z - b->z) > THRESH) return true; return false; } //------------------------------------------------------------------------------------------------- inline Bool isAngleDifferent(Real a, Real b) { // this is necessary because PhysicsBehavior may generate tiny changes even when // "standing still", due to roundoff errors. It's important that we only invalidate // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) // so we must put in some cleverness... const Real THRESH = 0.01f; // in radians, this is approx 1/2 degree. if (fabs(a - b) > THRESH) return true; return false; } //------------------------------------------------------------------------------------------------- void Object::reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ) { Real currentRotation = 0.0f; Real currentPitch = 0.0f; if( getAI() ) { getAI()->getTurretRotAndPitch( turret, ¤tRotation, ¤tPitch ); } Bool rotationChange = (currentRotation != oldRotation); // Bool pitchChange = (currentPitch != oldPitch); if( rotationChange ) { if (getContain()) getContain()->containReactToTransformChange(); } } //------------------------------------------------------------------------------------------------- //DECLARE_PERF_TIMER(Object_reactToTransformChange) void Object::reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) { //USE_PERF_TIMER(Object_reactToTransformChange) if(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)) { DEBUG_CRASH(("Object pos is nan.")); TheGameLogic->destroyObject(this); } if (m_drawable) { m_drawable->setTransformMatrix( this->getTransformMatrix() ); } Bool posDiff = isPosDifferent(oldPos, getPosition()); Bool angDiff = isAngleDifferent(oldAngle, getOrientation()); if (posDiff || angDiff) { if (m_partitionData) m_partitionData->makeDirty(true); if (getContain()) getContain()->containReactToTransformChange(); } if (posDiff) { setTriggerAreaFlagsForChangeInPosition(); // Update for entered/exited Region3D mapExtent; TheTerrainLogic->getExtent(&mapExtent); if (mapExtent.isInRegionNoZ(getPosition())) m_privateStatus &= ~OFF_MAP; else m_privateStatus |= OFF_MAP; } } //------------------------------------------------------------------------------------------------- ObjectShroudStatus Object::getShroudedStatus(Int playerIndex) const { if (getTemplate()->isKindOf( KINDOF_ALWAYS_VISIBLE )) return OBJECTSHROUD_CLEAR; if (m_partitionData) return m_partitionData->getShroudedStatus(playerIndex); // This can happen for objects removed from the partition system (e.g., // for soldiers that are garrisoned inside a building). return OBJECTSHROUD_CLEAR; } //------------------------------------------------------------------------------------------------- /** Something is attempting to damage this object */ //------------------------------------------------------------------------------------------------- void Object::attemptDamage( DamageInfo *damageInfo ) { BodyModuleInterface* body = getBodyModule(); if (body) body->attemptDamage( damageInfo ); // Process any shockwave forces that might affect this object due to the incurred damage if (damageInfo->in.m_shockWaveAmount > 0.0f && damageInfo->in.m_shockWaveRadius > 0.0f) { //KindOfMaskType immuneToShockwaveKindofs; //NEW RESTRICTIONS ADDED //immuneToShockwaveKindofs.set(KINDOF_PROJECTILE);// projectiles go idle in midair when they get sw'd //NEW RESTRICTIONS ADDED //immuneToShockwaveKindofs.set(KINDOF_PRODUCED_AT_HELIPAD);//helicopters go all wonky when they get shockwaved //NEW RESTRICTIONS ADDED PhysicsBehavior *behavior = getPhysics(); if ( behavior && (isAirborneTarget() == FALSE) && (! isKindOf(KINDOF_PROJECTILE) ) ) // if (behavior && isAnyKindOf( immuneToShockwaveKindofs ) == FALSE )//NEW RESTRICTIONS ADDED { // Calculate the shockwave taperoff amount due to distance from ground zero Real shockWaveScalar = damageInfo->in.m_shockWaveVector.length(); Real distanceFromCenter = min(1.0f, shockWaveScalar / damageInfo->in.m_shockWaveRadius); Real distanceTaper = (distanceFromCenter) * (1.0f - damageInfo->in.m_shockWaveTaperOff); Real shockTaperMult = 1.0f - distanceTaper; // Set up the shockwave force to use apply on object Coord3D shockWaveForce; shockWaveForce.set( &damageInfo->in.m_shockWaveVector ); shockWaveForce.normalize(); shockWaveForce.scale( damageInfo->in.m_shockWaveAmount * shockTaperMult ); shockWaveForce.z = shockWaveForce.length(); // Apply up force equal to the lateral force for dramatic effect // Apply the shock to the object behavior->applyShock(&shockWaveForce); // Add random rotation to the object for drama behavior->applyRandomRotation(); // Set stunned state due to the shock for the object behavior->setStunned(true); setModelConditionState(MODELCONDITION_STUNNED_FLAILING); } } /// @todo track damage dealt/attempted // // if actual damage occurred, and this is an object owned by the local player we // might do a radar event for under attack. Note that we do not even try // to do radar events for DAMAGE_PENALTY as that damage type is a type of damage // that occurs with explicit player knowledge // if( damageInfo->out.m_actualDamageDealt > 0.0f && damageInfo->in.m_damageType != DAMAGE_PENALTY && damageInfo->in.m_damageType != DAMAGE_HEALING && getControllingPlayer() && !BitTest(damageInfo->in.m_sourcePlayerMask, getControllingPlayer()->getPlayerMask()) && m_radarData != NULL && getControllingPlayer() == ThePlayerList->getLocalPlayer() ) TheRadar->tryUnderAttackEvent( this ); } //------------------------------------------------------------------------------------------------- void Object::attemptHealing(Real amount, const Object* source) { BodyModuleInterface* body = getBodyModule(); if (body) { DamageInfo damageInfo; damageInfo.in.m_damageType = DAMAGE_HEALING; damageInfo.in.m_deathType = DEATH_NONE; damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; damageInfo.in.m_amount = amount; body->attemptHealing( &damageInfo ); } } ObjectID Object::getSoleHealingBenefactor( void ) const { UnsignedInt now = TheGameLogic->getFrame(); if( now > m_soleHealingBenefactorExpirationFrame ) return INVALID_ID; return m_soleHealingBenefactorID; } Bool Object::attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration ) {///< for the non-stacking healers like ambulance and propaganda if( ! source ) // sanity return FALSE; UnsignedInt now = TheGameLogic->getFrame(); ObjectID id = source->getID(); // Either it is ok to accept healing from any who offer or this is my guy, calling again if( now > m_soleHealingBenefactorExpirationFrame || m_soleHealingBenefactorID == id ) { m_soleHealingBenefactorID = id; m_soleHealingBenefactorExpirationFrame = now + duration; BodyModuleInterface* body = getBodyModule(); if (body) { DamageInfo damageInfo; damageInfo.in.m_damageType = DAMAGE_HEALING; damageInfo.in.m_deathType = DEATH_NONE; damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; damageInfo.in.m_amount = amount; body->attemptHealing( &damageInfo ); } return TRUE; } return FALSE; } //------------------------------------------------------------------------------------------------- Real Object::estimateDamage( DamageInfoInput& damageInfo ) const { BodyModuleInterface* body = getBodyModule(); if (body) return body->estimateDamage( damageInfo ); return 0.0f; } //------------------------------------------------------------------------------------------------- /** Do so much damage to an object that it will certainly die */ //------------------------------------------------------------------------------------------------- void Object::kill( DamageType damageType, DeathType deathType ) { DamageInfo damageInfo; // Do unmodifiable damage equal to their max health to kill. damageInfo.in.m_damageType = damageType; damageInfo.in.m_deathType = deathType; damageInfo.in.m_sourceID = INVALID_ID; damageInfo.in.m_amount = getBodyModule()->getMaxHealth(); damageInfo.in.m_kill = TRUE; // Triggers object to die no matter what. attemptDamage( &damageInfo ); DEBUG_ASSERTCRASH(!damageInfo.out.m_noEffect, ("Attempting to kill an unKillable object (InactiveBody?)\n")); } // end kill //------------------------------------------------------------------------------------------------- /** Restore max health to this Object */ //------------------------------------------------------------------------------------------------- void Object::healCompletely() { attemptHealing(HUGE_DAMAGE_AMOUNT, NULL); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::setEffectivelyDead(Bool dead) { if (dead) BitSet(m_privateStatus, EFFECTIVELY_DEAD); else BitClear(m_privateStatus, EFFECTIVELY_DEAD); if (dead) { if( m_radarData ) TheRadar->removeObject( this ); } } //------------------------------------------------------------------------------------------------- void Object::setCaptured(Bool isCaptured) { if (isCaptured) BitSet(m_privateStatus, CAPTURED); else { DEBUG_LOG(("Clearing Captured Status. This should never happen. jkmcd")); BitClear(m_privateStatus, CAPTURED); } // No need to see if we should skip updates, this flag has no effect on skipping updates. } //------------------------------------------------------------------------------------------------- Bool Object::isStructure(void) const { return isKindOf(KINDOF_STRUCTURE); } //------------------------------------------------------------------------------------------------- Bool Object::isFactionStructure(void) const { return isAnyKindOf( KINDOFMASK_FS ); } //------------------------------------------------------------------------------------------------- Bool Object::isNonFactionStructure(void) const { return isStructure() && !isFactionStructure(); } void localIsHero( Object *obj, void* userData ) { Bool *hero = (Bool*)userData; if( obj && obj->isKindOf( KINDOF_HERO ) ) { *hero = TRUE; } } //------------------------------------------------------------------------------------------------- Bool Object::isHero(void) const { ContainModuleInterface *contain = getContain(); if( contain ) { Bool heroInside = FALSE; contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); if( heroInside ) { return TRUE; } } return isKindOf( KINDOF_HERO ); } //------------------------------------------------------------------------------------------------- void Object::setReceivingDifficultyBonus(Bool receive) { if (receive == m_isReceivingDifficultyBonus) { return; } m_isReceivingDifficultyBonus = receive; getControllingPlayer()->friend_applyDifficultyBonusesForObject(this, m_isReceivingDifficultyBonus); } //------------------------------------------------------------------------------------------------- //- DISABLEDNESS STUFF ---------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::setDisabled( DisabledType type ) { setDisabledUntil(type, FOREVER); } //------------------------------------------------------------------------------------------------- void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) { Bool edgeCase = !isDisabled(); if( type < 0 || type >= DISABLED_COUNT ) { DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); return; } //Handle audio events! AudioEventRTS sound; if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) { //We've been sniped! Play a splatter sound for the pilot losing his face. sound = TheAudio->getMiscAudio()->m_splatterVehiclePilotsBrain; sound.setPosition( getPosition() ); TheAudio->addAudioEvent( &sound ); } else if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) { //We've lost power -- make sure we aren't already out of power as the sounds shouldn't happen //if you were already disabled. if( !isDisabledByType( DISABLED_UNDERPOWERED ) && !isDisabledByType( DISABLED_EMP ) && !isDisabledByType( DISABLED_SUBDUED ) && !isDisabledByType( DISABLED_HACKED ) ) { if( isKindOf( KINDOF_STRUCTURE ) ) { sound = TheAudio->getMiscAudio()->m_buildingDisabled; sound.setPosition( getPosition() ); TheAudio->addAudioEvent( &sound ); } else if( isKindOf( KINDOF_VEHICLE ) ) { sound = TheAudio->getMiscAudio()->m_vehicleDisabled; sound.setPosition( getPosition() ); TheAudio->addAudioEvent( &sound ); } } } if( m_disabledTillFrame[ type ] != frame ) { // an edge-test for disabledness, for type. This INCREMENTS m_pauseCount // srj sez: HELD nevers disables special powers. if ( type != DISABLED_HELD && !isDisabledByType( type ) ) pauseAllSpecialPowers( TRUE ); m_disabledTillFrame[ type ] = frame; m_disabledMask.set( type, frame > TheGameLogic->getFrame() ); if( m_drawable ) { if( isDisabled() ) { // Held does not tint anybody. If we are multiply disabled, the other setting will hit the tint, // and in clear, only-held and not-disabled are both causes to untint. // Doh. Also shouldn't be tinting when disabled by scripting. // Doh^2. Also shouldn't be CLEARING tinting if we're disabling by held or script disabledness // Doh^3. Unmanned is no tint too if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED ) { m_drawable->setTintStatus( TINT_STATUS_DISABLED ); } } } ContainModuleInterface *contain = getContain(); if ( contain ) { Object *rider = (Object*)contain->friend_getRider(); if ( rider ) { rider->setDisabledUntil(type, frame); } } if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) { SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); if ( sbi ) { //Kris: Patch 1.01 - November 12, 2003 //Actually, we want to disable the slaves, not order them to go idle! This fix was made to //stinger sites getting hit by an EMP to prevent the soldiers from attacking. //sbi->orderSlavesToGoIdle( CMD_FROM_AI ); // the canattack() will take care of any future attempts to fire sbi->orderSlavesDisabledUntil( type, frame ); } } } if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) { //strange but true: If I am a carbomb, //my driver actually has a dead-man's //trigger for my dynamite... //If he gets sniped, I blow up! Wheeee! WeaponSetFlags flags; flags.set( WEAPONSET_CARBOMB ); const WeaponTemplateSet* set = getTemplate()->findWeaponTemplateSet( flags ); if( set && set->testWeaponSetFlag( WEAPONSET_CARBOMB ) ) { Object* sniper = TheGameLogic->findObjectByID( getBodyModule()->getLastDamageInfo()->in.m_sourceID ); if ( sniper ) sniper->scoreTheKill( this ); kill(); } else { //This vehicle's pilot has been sniped, so we want to clear the veterancy rating (if any) ExperienceTracker *xpTracker = getExperienceTracker(); if( xpTracker ) { xpTracker->setExperienceAndLevel( 0, FALSE ); } //Not only that, but it also loses any healing bonuses it may have earned in its prior life { static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior"); AutoHealBehavior* autoHeal = (AutoHealBehavior*)(findUpdateModule( key_AutoHealBehavior )); if (autoHeal) autoHeal->undoUpgrade(); } } } // This will only be called if we were NOT disabled before coming into this function. if (edgeCase) { onDisabledEdge(true); } } //------------------------------------------------------------------------------------------------- UnsignedInt Object::getDisabledUntil( DisabledType type ) const { if( type == DISABLED_ANY ) { UnsignedInt highestFrame = 0; //Iterate through each disabled type and return the one with the highest frame. for( Int i = 0; i < DISABLED_COUNT; i++ ) { if( m_disabledMask.test( i ) && m_disabledTillFrame[ i ] > highestFrame ) { highestFrame = m_disabledTillFrame[ i ]; } } return highestFrame; } else if( m_disabledMask.test( type ) ) { //Specific query. return m_disabledTillFrame[ type ]; } //Not disabled. return 0; } //------------------------------------------------------------------------------------------------- Bool Object::clearDisabled( DisabledType type ) { if( type < 0 || type >= DISABLED_COUNT ) { DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); return FALSE; } if (!isDisabledByType(type)) { return FALSE; } if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) { //We've regained power-- make sure we aren't still disabled by another type. AudioEventRTS sound; if( (!isDisabledByType( DISABLED_UNDERPOWERED ) || type == DISABLED_UNDERPOWERED ) && (!isDisabledByType( DISABLED_EMP ) || type == DISABLED_EMP ) && (!isDisabledByType( DISABLED_SUBDUED ) || type == DISABLED_SUBDUED ) && (!isDisabledByType( DISABLED_HACKED ) || type == DISABLED_HACKED ) ) { if( isKindOf( KINDOF_STRUCTURE ) ) { sound = TheAudio->getMiscAudio()->m_buildingReenabled; sound.setPosition( getPosition() ); TheAudio->addAudioEvent( &sound ); } else if( isKindOf( KINDOF_VEHICLE ) ) { sound = TheAudio->getMiscAudio()->m_vehicleReenabled; sound.setPosition( getPosition() ); TheAudio->addAudioEvent( &sound ); } } } // an edge-test for disabledness, for type. This DECREMENTS m_pauseCount // srj sez: HELD nevers disables special powers. if ( type != DISABLED_HELD && isDisabledByType( type ) ) pauseAllSpecialPowers( FALSE ); ContainModuleInterface *contain = getContain(); if ( contain ) { // We explicitly pass stuff in up in the set, so we need to turn it off if it is a forever type Object *rider = (Object*)contain->friend_getRider(); if( rider && (m_disabledTillFrame[ type ] == FOREVER) ) { rider->clearDisabled(type); } } if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) { SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); if ( sbi ) { //Kris: Patch 1.02 - December 17, 2003 //Make sure slaves can recover from being disabled by subdual (stinger site soldier case) sbi->orderSlavesToClearDisabled( type ); } } m_disabledTillFrame[ type ] = NEVER; m_disabledMask.set( type, 0 ); DisabledMaskType exceptions; exceptions.set(DISABLED_HELD); exceptions.set(DISABLED_SCRIPT_DISABLED); exceptions.set(DISABLED_UNMANNED); DisabledMaskType myFlagsMinusExceptions = getDisabledFlags(); myFlagsMinusExceptions.clearAndSet(exceptions, DISABLEDMASK_NONE); // to clarify, if I am NOT disabled by anything other than DISABLED_HELD, or DISABLED_SCRIPT_DISABLED // to clarify, count inverse intersection gives you the number of exceptions you don't have, // and has nothing to do with checking other disabled types // if( !isDisabled() || getDisabledFlags().countInverseIntersection( exceptions ) == 0 ) if( myFlagsMinusExceptions.count() == 0 ) { // I have no disabled flag that is not one of the exceptions above. if (m_drawable) m_drawable->clearTintStatus( TINT_STATUS_DISABLED ); } checkDisabledStatus();// in case we just edged // if we're no longer disabled by anything, then call the edge function. if (!isDisabled()) { onDisabledEdge(false); } return TRUE; } //------------------------------------------------------------------------------------------------- //Checks any timers and clears disabled statii that have expired. //------------------------------------------------------------------------------------------------- void Object::checkDisabledStatus() { UnsignedInt now = TheGameLogic->getFrame(); for( int i = 0; i < DISABLED_COUNT; i++ ) { DisabledType type = (DisabledType)i; if( isDisabledByType( type ) ) { if ( now >= m_disabledTillFrame[ i ] ) { clearDisabled( type ); // This will also DECREMENT m_pauseCount in all specialpowers m_disabledMask.set( type, 0 ); } } } } //------------------------------------------------------------------------------------------------- void Object::pauseAllSpecialPowers( const Bool disabling ) const { for (BehaviorModule** m = m_behaviors; *m; ++m) { SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); if (!sp) continue; sp->pauseCountdown( disabling );// So it will pause if we are disabling. } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- /** Clear the previous entered/exited flags. */ //------------------------------------------------------------------------------------------------- void Object::updateTriggerAreaFlags() { Int j = 0; // Update the flags, and remove any trigger areas that this object isn't inside. for (Int i=0; igetCollide(); if (!collide) continue; // check each time thru the loop, in case a collide module sets it if( getStatusBits().test( OBJECT_STATUS_NO_COLLISIONS ) ) { #ifdef DEBUG_CRC //DEBUG_LOG(("Object::onCollide() - OBJECT_STATUS_NO_COLLISIONS set\n")); #endif break; } #ifdef DEBUG_CRC //DEBUG_LOG(("Object::onCollide() - calling collide module\n")); #endif collide->onCollide(other, loc, normal); } } //------------------------------------------------------------------------------------------------- Bool Object::isSalvageCrate() const { for( BehaviorModule** m = m_behaviors; *m; ++m ) { CollideModuleInterface* collide = (*m)->getCollide(); if( collide && collide->isSalvageCrateCollide() ) { return true; } } return false; } //------------------------------------------------------------------------------------------------- /** Our owning player is telling us to recheck our UpgradeModules, as an upgrade has completed */ void Object::updateUpgradeModules() { if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) return; // No upgrade can run if we are under construction. The three places that clear UnderConstruction will re-update us. if( testStatus( OBJECT_STATUS_DESTROYED ) ) return; // Patch 1.03 -- Fixes crash when you upgrade a fake GLA command center to a real one if (toxic or demo). if( getControllingPlayer() == NULL ) return; // This can only happen in game teardown. No upgrades for you without a player. Weird crashes are bad. UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); UpgradeMaskType maskToCheck = playerMask; maskToCheck.set( objectMask ); // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. // We combine all the masks in case someone has a Object AND Player combination for (BehaviorModule** module = m_behaviors; *module; ++module) { UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); if (!upgrade) continue; if( !upgrade->isAlreadyUpgraded() ) { upgrade->attemptUpgrade( maskToCheck ); } } } //------------------------------------------------------------------------------------------------- //This function sucks. //It was added for objects that can disguise as other objects and contain upgraded subobject overrides. //A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been //made. When the bomb truck disguises as something else, these subobjects are lost because the vector is //stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to //recalculate those upgraded subobjects. //------------------------------------------------------------------------------------------------- void Object::forceRefreshSubObjectUpgradeStatus() { for (BehaviorModule** module = m_behaviors; *module; ++module) { UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); if (!upgrade) continue; if( upgrade->isSubObjectsUpgrade() ) { upgrade->forceRefreshUpgrade(); } } } //------------------------------------------------------------------------------------------------- /** Returns whether an object entered or exited an area. */ //------------------------------------------------------------------------------------------------- Bool Object::didEnterOrExit() const { if (isKindOf(KINDOF_INERT)) { return FALSE; } // note that this needs to return true if we // entered or exited on the current frame OR // the previous frame... since the current execution // order is ScriptEngine, then ObjectUpdates, // enter/exits detected in ObjectUpdate on frame N // won't be noticed by the ScriptEngine till frame N+1. UnsignedInt now = TheGameLogic->getFrame(); return m_enteredOrExitedFrame == now || m_enteredOrExitedFrame == now - 1; } //------------------------------------------------------------------------------------------------- /** Returns whether an object entered an area. */ //------------------------------------------------------------------------------------------------- Bool Object::didEnter(const PolygonTrigger *pTrigger) const { if (!didEnterOrExit()) return false; DEBUG_ASSERTCRASH(!isKindOf(KINDOF_INERT), ("Asking whether an inert object entered or exited. This is invalid.\n")); for (Int i=0; igetUpdateExitInterface()) != NULL ) break; } // If you don't have a fancy one, you may have one from your contain module, // since if you can contain something, they will need to get out. if( exitInterface == NULL ) { ContainModuleInterface *cmod = getContain(); if( cmod ) { exitInterface = cmod->getContainExitInterface(); } } return exitInterface; } // end getObjectExitInterface //------------------------------------------------------------------------------------------------- /** Checks the object against trigger areas when the position changes. */ //------------------------------------------------------------------------------------------------- void Object::setTriggerAreaFlagsForChangeInPosition() { // projectiles cannot trigger areas. (jkmcd) // neither can inert objects, like the radar ping, etc. (jkmcd) if (isKindOf(KINDOF_PROJECTILE) || isKindOf(KINDOF_INERT)) return; ICoord3D iPos; Coord3D pos = *getPosition(); iPos.x = REAL_TO_INT(pos.x); iPos.y = REAL_TO_INT(pos.y); iPos.z = 0; // Trigger areas compare on xy only. if (m_iPos.x == iPos.x && m_iPos.y == iPos.y) { return; // didn't move enough to change integer position. } if (!isKindOf(KINDOF_IMMOBILE)) { if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE) ) { TheGameClient->notifyTerrainObjectMoved(this); } } if (getAIUpdateInterface()) { TheAI->pathfinder()->updatePos(this, getPosition()); } UnsignedInt now = TheGameLogic->getFrame(); if (m_enteredOrExitedFrame != 0 && m_enteredOrExitedFrame != now) updateTriggerAreaFlags(); // Check for exited. Int i; for (i=0; ipointInTrigger(m_iPos)) { m_triggerInfo[i].isInside = false; m_triggerInfo[i].exited = true; m_enteredOrExitedFrame = now; if (m_team) m_team->setEnteredExited(); TheGameLogic->updateObjectsChangedTriggerAreas(); #ifdef _DEBUG //TheScriptEngine->AppendDebugMessage("Object exited.", false); #endif } } m_iPos = iPos; for (const PolygonTrigger *pTrig = PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) { Bool skip = false; for (i = 0; i < m_numTriggerAreasActive; i++) { if (m_triggerInfo[i].pTrigger == pTrig) { // Already handled this one in the check for exited above. skip = true; break; } } if (skip) continue; if (pTrig->pointInTrigger(m_iPos)) { if (m_numTriggerAreasActive < MAX_TRIGGER_AREA_INFOS) { m_triggerInfo[m_numTriggerAreasActive].isInside = true; m_triggerInfo[m_numTriggerAreasActive].entered = true; m_triggerInfo[m_numTriggerAreasActive].exited = false; m_triggerInfo[m_numTriggerAreasActive].pTrigger = pTrig; m_enteredOrExitedFrame = now; if (m_team) m_team->setEnteredExited(); TheGameLogic->updateObjectsChangedTriggerAreas(); ++m_numTriggerAreasActive; #ifdef _DEBUG //TheScriptEngine->AppendDebugMessage("Object entered.", false); #endif } else { // Shouldn't happen. static Bool didWarn = false; if (!didWarn) { didWarn = true; TheScriptEngine->AppendDebugMessage("***WARNING - Too many nested trigger areas. ***", true); } } } } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Bool Object::isInList(Object **pListHead) const { Bool result = m_prev || m_next || *pListHead == this; #ifdef INTENSE_DEBUG Bool found = false; for (Object* o = *pListHead; o; o = o->m_next) { if (o == this) { found = true; break; } } DEBUG_ASSERTCRASH(found==result,("inconsistent links in Object::isInList")); #endif return result; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::prependToList(Object **pListHead) { DEBUG_ASSERTCRASH(!isInList(pListHead), ("obj is already in a list")); m_prev = NULL; m_next = *pListHead; if (*pListHead) (*pListHead)->m_prev = this; *pListHead = this; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::setLayer(PathfindLayerEnum layer) { if (layer!=m_layer) { #define no_SET_LAYER_INTENSE_DEBUG #ifdef SET_LAYER_INTENSE_DEBUG DEBUG_LOG(("Changing layer from %d to %d\n", m_layer, layer)); if (m_layer != LAYER_GROUND) { if (TheTerrainLogic->objectInteractsWithBridgeLayer(this, m_layer)) { DEBUG_CRASH(("Probably shouldn't be chaging layer. jba.")); } } #endif TheAI->pathfinder()->removePos(this); m_layer = layer; TheAI->pathfinder()->updatePos(this, getPosition()); } } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::setDestinationLayer(PathfindLayerEnum layer) { if (layer!=m_destinationLayer) { m_destinationLayer = layer; } } // ------------------------------------------------------------------------------------------------ /** Set unique ID */ // ------------------------------------------------------------------------------------------------ void Object::setID( ObjectID id ) { // sanity DEBUG_ASSERTCRASH( id != INVALID_ID, ("Object::setID - Invalid id\n") ); // if id hasn't changed do nothing if( m_id == id ) return; // remove this objects previous id from the lookup table TheGameLogic->removeObjectFromLookupTable( this ); // assign new id m_id = id; // add new id to lookup table TheGameLogic->addObjectToLookupTable( this ); } // end setID // ------------------------------------------------------------------------------------------------ Real Object::calculateHeightAboveTerrain(void) const { const Coord3D* pos = getPosition(); Real terrainZ = TheTerrainLogic->getLayerHeight( pos->x, pos->y, m_layer ); Real myZ = pos->z; return myZ - terrainZ; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::removeFromList(Object **pListHead) { if (m_next) m_next->m_prev = m_prev; if (m_prev) m_prev->m_next = m_next; else *pListHead = m_next; m_prev = NULL; m_next = NULL; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::friend_prepareForMapBoundaryAdjust(void) { // NOTE - DO NOT remove from pathfind map. jba. // NO NO. jba. TheAI->pathfinder()->removeObjectFromPathfindMap( this ); // remove from the radar, remove from the partition manager TheRadar->removeObject(this); ThePartitionManager->unRegisterObject(this); // The whole PartitionManager and all of the Looker data is about to be blown away, // so forget what I think I have done m_partitionLastLook->reset(); m_partitionRevealAllLastLook->reset(); m_partitionLastShroud->reset(); m_partitionLastThreat->reset(); m_partitionLastValue->reset(); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::friend_notifyOfNewMapBoundary(void) { ThePartitionManager->registerObject(this); TheRadar->addObject(this); TheAI->pathfinder()->addObjectToPathfindMap( this ); // Now that the PartitionManager has finished its reset, we need to relook handlePartitionCellMaintenance(); Region3D mapExtent; TheTerrainLogic->getExtent(&mapExtent); if (mapExtent.isInRegionNoZ(getPosition())) m_privateStatus &= ~OFF_MAP; else m_privateStatus |= OFF_MAP; } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void Object::calcNaturalRallyPoint(Coord2D *pt) { const Matrix3D *transform = getTransformMatrix(); Vector3 v; // // get the natural rally point from the template, this coord is in model space relative // to the model (0,0,0) // /* const Coord3D *naturalRallyPoint; naturalRallyPoint = m_template->getNaturalRallyPoint(); v.X = naturalRallyPoint->x; v.Y = naturalRallyPoint->y; v.Z = naturalRallyPoint->z; */ v.Set( 0, 0, 0 ); // transform the point into world space transform->Transform_Vector( *transform, v, &v ); // we're only concerned with the 2D elements for now pt->x = v.X; pt->y = v.Y; } //------------------------------------------------------------------------------------------------- Module* Object::findModule(NameKeyType key) const { Module* m = NULL; for (BehaviorModule** b = m_behaviors; *b; ++b) { if ((*b)->getModuleNameKey() == key) { #ifdef INTENSE_DEBUG if (m == NULL) { m = *b; } else { DEBUG_CRASH(("Duplicate modules found for name %s!\n",TheNameKeyGenerator->keyToName(key).str())); } #else m = *b; break; #endif } } return m; } //------------------------------------------------------------------------------------------------- /** * Returns true if object is currently able to move. */ Bool Object::isMobile() const { if (isKindOf(KINDOF_IMMOBILE)) return false; if( isDisabled() ) return false; return true; } //------------------------------------------------------------------------------------------------- void Object::scoreTheKill( const Object *victim ) { // Do stuff that has nothing to do with experience points here, like tell our Player we killed something /// @todo Multiplayer score hook location? Player* victimController = victim->getControllingPlayer(); // if the other player is not a playable side (i.e. they are civilian, observer, whatever) // we shouldn't count the kill. if (victimController->isPlayableSide() == FALSE) { return; } if ( victim->isKindOf( KINDOF_IGNORED_IN_GUI ) ) return; Player* controller = getControllingPlayer(); if (victimController) { victimController->getScoreKeeper()->addObjectLost(victim); } Relationship r = getRelationship(victim); if (r != ENEMIES) return; // Don't count kills that I do on my own buildings or units, cause thats just silly. if (controller == victimController) { return; } if (controller) { controller->getScoreKeeper()->addObjectDestroyed(victim); controller->addSkillPointsForKill(this, victim); controller->doBountyForKill(this, victim); } // Now handle experience, if we can gain any if (m_experienceTracker && m_experienceTracker->isAcceptingExperiencePoints()) { // srj sez: per dustin, no experience (et al) for killing things under construction. if (!victim->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)) { Int experienceValue = victim->getExperienceTracker()->getExperienceValue( this ); getExperienceTracker()->addExperiencePoints( experienceValue ); } } } //------------------------------------------------------------------------------------------------- VeterancyLevel Object::getVeterancyLevel() const { return m_experienceTracker ? m_experienceTracker->getVeterancyLevel() : LEVEL_REGULAR; } //------------------------------------------------------------------------------------------------- void Object::friend_bindToDrawable( Drawable *draw ) { m_drawable = draw; if (m_drawable) { ModelConditionFlags set; ModelConditionFlags clr; for (int i = 0; i < WEAPONSET_COUNT; ++i) { ModelConditionFlagType mcs = TheWeaponSetTypeToModelConditionTypeMap[i]; if( mcs != MODELCONDITION_INVALID ) { if (m_curWeaponSetFlags.test(i)) set.set(mcs); else clr.set(mcs); } } if (TheGlobalData) { if (TheGlobalData->m_forceModelsToFollowTimeOfDay) { set.set(MODELCONDITION_NIGHT, (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) ? 1 : 0); } if (TheGlobalData->m_forceModelsToFollowWeather) { set.set(MODELCONDITION_SNOW, (TheGlobalData->m_weather == WEATHER_SNOWY) ? 1 : 0); } } m_drawable->clearAndSetModelConditionFlags(clr, set); } for (BehaviorModule** b = m_behaviors; *b; ++b) { (*b)->onDrawableBoundToObject(); } } //------------------------------------------------------------------------------------------------- void Object::setSelectable(Bool selectable) { m_isSelectable = selectable; if (m_drawable) { m_drawable->setSelectable(selectable); } } //------------------------------------------------------------------------------------------------- Bool Object::isSelectable() const { // return getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE) // || (m_isSelectable // && !testStatus(OBJECT_STATUS_UNSELECTABLE) // && !isEffectivelyDead() // && !getTemplate()->isKindOf(KINDOF_DRONE)//Most drones are unselectable from being slaved, but the SpyDrone needs help // ); if (getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE)) return TRUE; if ( m_isSelectable ) if ( !testStatus(OBJECT_STATUS_UNSELECTABLE) ) if ( !isEffectivelyDead() ) //if ( !getTemplate()->isKindOf(KINDOF_DRONE) )//Most drones are unselectable from being slaved, but the SpyDrone needs help return TRUE; return FALSE; } //------------------------------------------------------------------------------------------------- Bool Object::isMassSelectable() const { return isSelectable() && !isKindOf(KINDOF_STRUCTURE); } //------------------------------------------------------------------------------------------------- void Object::setWeaponSetFlag(WeaponSetType wst) { m_curWeaponSetFlags.set(wst); m_weaponSet.updateWeaponSet(this); if (m_drawable) { m_drawable->setModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); } } //------------------------------------------------------------------------------------------------- void Object::clearWeaponSetFlag(WeaponSetType wst) { m_curWeaponSetFlags.set(wst, 0); m_weaponSet.updateWeaponSet(this); if (m_drawable) { m_drawable->clearModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); } } //------------------------------------------------------------------------------------------------- Bool Object::hasSpecialPower( SpecialPowerType type ) const { return TEST_SPECIALPOWERMASK( m_specialPowerBits, type ); } //------------------------------------------------------------------------------------------------- Bool Object::hasAnySpecialPower() const { return SPECIALPOWERMASK_ANY_SET( m_specialPowerBits ); } //------------------------------------------------------------------------------------------------- void Object::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) { updateUpgradeModules(); const UpgradeTemplate* up = TheUpgradeCenter->findVeterancyUpgrade(newLevel); if (up) giveUpgrade(up); BodyModuleInterface* body = getBodyModule(); if (body) body->onVeterancyLevelChanged( oldLevel, newLevel, provideFeedback ); Bool hideAnimationForStealth = FALSE; if( !isLocallyControlled() && testStatus( OBJECT_STATUS_STEALTHED ) && !testStatus( OBJECT_STATUS_DETECTED ) && !testStatus( OBJECT_STATUS_DISGUISED ) ) { hideAnimationForStealth = TRUE; } Bool doAnimation = ( ! hideAnimationForStealth && (newLevel > oldLevel) && ( ! isKindOf(KINDOF_IGNORED_IN_GUI))); //First, we plan to do the animation if the level went up switch (newLevel) { case LEVEL_REGULAR: clearWeaponSetFlag(WEAPONSET_VETERAN); clearWeaponSetFlag(WEAPONSET_ELITE); clearWeaponSetFlag(WEAPONSET_HERO); clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); doAnimation = FALSE;//... but not if somehow up to Regular break; case LEVEL_VETERAN: setWeaponSetFlag(WEAPONSET_VETERAN); clearWeaponSetFlag(WEAPONSET_ELITE); clearWeaponSetFlag(WEAPONSET_HERO); setWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); break; case LEVEL_ELITE: clearWeaponSetFlag(WEAPONSET_VETERAN); setWeaponSetFlag(WEAPONSET_ELITE); clearWeaponSetFlag(WEAPONSET_HERO); clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); setWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); break; case LEVEL_HEROIC: clearWeaponSetFlag(WEAPONSET_VETERAN); clearWeaponSetFlag(WEAPONSET_ELITE); setWeaponSetFlag(WEAPONSET_HERO); clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); setWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); break; } if( doAnimation && TheGameLogic->getDrawIconUI() && provideFeedback ) { if( TheAnim2DCollection && TheGlobalData->m_levelGainAnimationName.isEmpty() == FALSE ) { Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( TheGlobalData->m_levelGainAnimationName ); Coord3D pos = *getPosition(); pos.add(&m_healthBoxOffset); TheInGameUI->addWorldAnimation( animTemplate, &pos, WORLD_ANIM_FADE_ON_EXPIRE, TheGlobalData->m_levelGainAnimationDisplayTimeInSeconds, TheGlobalData->m_levelGainAnimationZRisePerSecond); } AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_unitPromoted; soundToPlay.setObjectID( getID() ); TheAudio->addAudioEvent( &soundToPlay ); } } //------------------------------------------------------------------------------------------------- /** * Returns true if object currently has some kind of attack capability */ Bool Object::isAbleToAttack() const { //****************************************************** //********* AUTOMATICALLY FALSE CONDITIONS ************* //****************************************************** // For things that may or may not be able to normally attack, but are under a status condition if( getStatusBits().test( OBJECT_STATUS_NO_ATTACK ) ) return false; // if we're contained within a transport we cannot attack unless it specifically allows us const Object *containedBy = getContainedBy(); DEBUG_ASSERTCRASH( (containedBy == NULL) || (containedBy->getContain() != NULL), ("A %s thinks they are contained by something with no contain module!", getTemplate()->getName().str() ) ); if( containedBy && containedBy->getContain() && !containedBy->getContain()->isPassengerAllowedToFire( getID() ) ) return false; // We can't fire if under construction if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) return false; // or being sold if( testStatus(OBJECT_STATUS_SOLD) ) return false; if ( isDisabledByType( DISABLED_SUBDUED ) ) return FALSE; // A Microwave Tank is cooking me //We can't fire if we, as a portable structure, are aptly disabled if ( isKindOf( KINDOF_PORTABLE_STRUCTURE ) || isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )) { if( isDisabledByType( DISABLED_HACKED ) || isDisabledByType( DISABLED_EMP ) ) return false; if ( isKindOf( KINDOF_INFANTRY ) ) // I must be a stinger soldier or similar { for (BehaviorModule** update = getBehaviorModules(); *update; ++update)//expensive search, limited only to stinger soldiers { SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface(); if ( sdu ) { ObjectID slaverID = sdu->getSlaverID(); if ( slaverID != INVALID_ID ) { Object *slaver = TheGameLogic->findObjectByID( slaverID ); if ( slaver && slaver->isDisabledByType( DISABLED_SUBDUED )) return FALSE;// if my stinger site is subdued, so am I } break;//only expect one slavedupdate, so stop searching } } } } //We can't fire if all our weapons are disabled! //Currently, only turreted weapons can be disabled. //ONLY DO THIS CHECK IF OUR UNIT DOESN'T HAVE THE //KINDOF_CAN_ATTACK flag... nuke cannons have disabled //turrets when not deployed, and need to be able to attack to deploy! //Strategy centers can't attack when bombardment isn't active! Bool anyEnabled = FALSE; Bool anyWeapon = FALSE; const AIUpdateInterface *ai = getAI(); if( ai && !isKindOf( KINDOF_CAN_ATTACK ) ) { for( Int i = 0; i < WEAPONSLOT_COUNT; i++ ) { //Find the weapon in this slot. Weapon* weapon = getWeaponInWeaponSlot( (WeaponSlotType)i ); if( !weapon ) continue; anyWeapon = TRUE; //We found a weapon, is it a turret? Real dummy; WhichTurretType tur = ai->getWhichTurretForWeaponSlot( (WeaponSlotType)i, &dummy ); if( tur == TURRET_INVALID ) { //Currently impossible to disable a non-turreted weapon, so we //have a non turreted weapon that is enabled. Quit. anyEnabled = TRUE; break; } if( ai->isTurretEnabled( tur ) ) { //The turret is enable, meaning we have an enabled weapon. Quit. anyEnabled = TRUE; break;; } } if( anyWeapon && !anyEnabled ) { //We failed to find any active weapons. return FALSE; } } //*************************************** //********* TRUE CONDITIONS ************* //*************************************** // for certain buildings if (isKindOf(KINDOF_CAN_ATTACK)) return true; // for garrisonned buildings that can attack sometimes if( getStatusBits().test( OBJECT_STATUS_CAN_ATTACK ) ) return true; // for weaponless transports. This will make me think I can, but I will check if I literally can by looking // at passenger weapons in CanAttack. const ContainModuleInterface* contain = getContain(); if( contain && contain->isPassengerAllowedToFire( getID() ) && contain->getContainCount() > 0 ) return true; // if we have AI and a weapon, assume we know how to use it if (getAIUpdateInterface() != NULL && m_weaponSet.hasAnyWeapon()) { // actually, we don't want to do this; we want the troop crawler to be considered "able to attack" // even if empty, so sayeth Dustin. (srj) // // special case: if the only damage we do is DEPLOY, we must have some guys contained. // if (m_weaponSet.hasSingleDamageType(DAMAGE_DEPLOY)) // { // return contain->getContainCount() > 0; // } // else { return true; } } SpawnBehaviorInterface *spawnInterface = getSpawnBehaviorInterface(); if( spawnInterface ) { if( spawnInterface->canAnySlavesAttack() ) { return TRUE; } } if (getTemplate()->isEnterGuard()) return TRUE; //Default is no return false; } //------------------------------------------------------------------------------------------------- /** * Mask/Un-Mask an object */ void Object::maskObject( Bool mask ) { // set or clear the mask bit setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ), mask ); // // when masking objects they become unselected ... we do this in any situation for // any player cause you aren't allowed to select masked objects, if the object is not // selected (ie, belongs to another player) it's no big deal cause it won't be selected // anyway // if (mask) TheGameLogic->deselectObject(this, ~getControllingPlayer()->getPlayerMask(), TRUE); } // end maskObject //------------------------------------------------------------------------------------------------- /* * returns true if the current locomotor is an airborne one */ Bool Object::isUsingAirborneLocomotor( void ) const { return ( m_ai && m_ai->getCurLocomotor() && ((m_ai->getCurLocomotor()->getLegalSurfaces() & LOCOMOTORSURFACE_AIR) != 0) ); } //------------------------------------------------------------------------------------------------- //THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT //(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... //IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW //INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. void Object::getHealthBoxPosition(Coord3D& pos) const { pos = *getPosition(); pos.z += getGeometryInfo().getMaxHeightAbovePosition() + 10; pos.add(&m_healthBoxOffset); // this needs to get moved to the mobspawnerupdate if (isKindOf(KINDOF_MOB_NEXUS)) // quicker idiot test { pos.z += 20;// dear God, I confess my kluge, and repent. } } //------------------------------------------------------------------------------------------------- //THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT //(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... //IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW //INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. Bool Object::getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const { #ifdef CALC_HEALTHBAR_FROM_HITPOINTS Real maxHP = getBodyModule()->getMaxHealth(); if( isKindOf( KINDOF_STRUCTURE ) ) { //enforce healthBoxHeightMinimum/Maximum healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); //enforce healthBoxWidthMinimum/Maximum healthBoxWidth = min(150.0f, max(100.0f, maxHP/10)); return true; } else if ( isKindOf(KINDOF_MOB_NEXUS) ) { //enforce healthBoxHeightMinimum/Maximum healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); //enforce healthBoxWidthMinimum/Maximum healthBoxWidth = min(100.0f, max(66.0f, maxHP/10)); return true; } else if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) { healthBoxHeight = 0; healthBoxWidth = 0; return false; } else { //enforce healthBoxHeightMinimum/Maximum healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); //enforce healthBoxWidthMinimum/Maximum healthBoxWidth = min(150.0f, max(35.0f, maxHP/10)); return true; } #else if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) { healthBoxHeight = 0; healthBoxWidth = 0; return false; } //just add the major and minor axes Real size = MAX(20.0f, MIN(150.0f, (getGeometryInfo().getMajorRadius() + getGeometryInfo().getMinorRadius())) ); healthBoxHeight = 3.0f; healthBoxWidth = MAX(20.0f, size * 2.0f); return TRUE; #endif } //------------------------------------------------------------------------------------------------- /** * Update this object instance with properties from the map object * */ void Object::updateObjValuesFromMapProperties(Dict* properties) { Bool exists; AsciiString valStr; Bool valBool = false; Int valInt = 0; Real valReal = 0.0f; valStr = properties->getAsciiString(TheKey_objectName, &exists); if (exists) { setName(valStr); } valInt = properties->getInt(TheKey_objectMaxHPs, &exists); if (exists && valInt >= 0) { BodyModuleInterface* body = getBodyModule(); if (body) { body->setMaxHealth(valInt); } } valInt = properties->getInt(TheKey_objectInitialHealth, &exists); if (exists) { BodyModuleInterface* body = getBodyModule(); if (body) { body->setInitialHealth(valInt); } } // set the veterancy level valInt = properties->getInt(TheKey_objectVeterancy, &exists); if (exists) { if (m_experienceTracker && m_experienceTracker->isTrainable()) { m_experienceTracker->setVeterancyLevel((VeterancyLevel)valInt); } } // set the aggressiveness/mood valInt = properties->getInt(TheKey_objectAggressiveness, &exists); if (exists) { AIUpdateInterface *ai = getAIUpdateInterface(); if (ai) { ai->setAttitude((AttitudeType)valInt); } } // set recruitable valBool = properties->getBool(TheKey_objectRecruitableAI, &exists); if (exists) { if (getAIUpdateInterface()) { getAIUpdateInterface()->setIsRecruitable(valBool); } } // set selectable valBool = properties->getBool(TheKey_objectSelectable, &exists); if (exists) { if (valBool != isSelectable()) { setSelectable(valBool); } } // set the stopping distance valReal = properties->getReal(TheKey_objectStoppingDistance, &exists); if (exists && valReal >= 0.5f) { if (getAIUpdateInterface() && getAIUpdateInterface()->getCurLocomotor()) { Locomotor *loco = getAIUpdateInterface()->getCurLocomotor(); loco->setCloseEnoughDist(valReal); } } // set the disabledness of this object valBool = properties->getBool(TheKey_objectEnabled, &exists); if (exists) { setScriptStatus(OBJECT_STATUS_SCRIPT_DISABLED, !valBool); } // set the disabledness of this object valBool = properties->getBool(TheKey_objectPowered, &exists); if (exists) { setScriptStatus(OBJECT_STATUS_SCRIPT_UNPOWERED, !valBool); } // set the invulnerability of the object valBool = properties->getBool(TheKey_objectIndestructible, &exists); if (exists) { BodyModuleInterface* body = getBodyModule(); if (body) { body->setIndestructible(valBool); } } // set the sellability of the object valBool = properties->getBool(TheKey_objectUnsellable, &exists); if (exists) { setScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE, valBool); } //Set the player targetable setting of the object valBool = properties->getBool( TheKey_objectTargetable, &exists ); if( exists ) { setScriptStatus(OBJECT_STATUS_SCRIPT_TARGETABLE, valBool); } // adjust the vision distance of this object, overriding its default vision distance valInt = properties->getInt(TheKey_objectVisualRange, &exists); if (exists) { if (valInt < 0) valInt = 0; m_visionRange = INT_TO_REAL(valInt); } // adjust the shroud clearing distance of this object, overriding its default distance valInt = properties->getInt(TheKey_objectShroudClearingDistance, &exists); if (exists) { if (valInt < 0) valInt = 0.0f; m_shroudClearingRange = INT_TO_REAL(valInt); } Int upgradeNum = 0; do { AsciiString keyName; keyName.format("%s%d", TheNameKeyGenerator->keyToName(TheKey_objectGrantUpgrade).str(), upgradeNum); valStr = properties->getAsciiString(NAMEKEY(keyName), &exists); if (exists) { const UpgradeTemplate *ut = TheUpgradeCenter->findUpgrade(valStr); if (ut) giveUpgrade(ut); } else { valStr.clear(); } ++upgradeNum; } while (!valStr.isEmpty()); Drawable *drawable = getDrawable(); if ( drawable ) { valInt = properties->getInt(TheKey_objectTime, &exists); if (exists) { switch (valInt) { case 1: drawable->clearModelConditionState(MODELCONDITION_NIGHT); break; case 2: drawable->setModelConditionState(MODELCONDITION_NIGHT); break; default: break; } } valInt = properties->getInt(TheKey_objectWeather, &exists); if (exists) { switch (valInt) { case 1: drawable->clearModelConditionState(MODELCONDITION_SNOW); break; case 2: drawable->setModelConditionState(MODELCONDITION_SNOW); break; default: break; } } // See if we are supposed to playing the ambient sound Bool soundEnabledExists; Bool soundEnabled = properties->getBool( TheKey_objectSoundAmbientEnabled, &soundEnabledExists ); DynamicAudioEventInfo * audioToModify = NULL; Bool infoModified = false; valStr = properties->getAsciiString( TheKey_objectSoundAmbient, &exists ); if ( exists ) { if ( valStr.isEmpty() ) { drawable->setCustomSoundAmbientOff(); soundEnabledExists = true; soundEnabled = false; // Don't bother trying to enable later } else { const AudioEventInfo * baseInfo = TheAudio->findAudioEventInfo( valStr ); DEBUG_ASSERTCRASH( baseInfo != NULL, ("Cannot find customized ambient sound '%s'", valStr.str() ) ); if ( baseInfo != NULL ) { audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); infoModified = true; } } } // Don't do anything more to audio if we forced the ambient sound off if ( !( exists && valStr.isEmpty() ) ) { valBool = properties->getBool( TheKey_objectSoundAmbientCustomized, &exists ); if ( exists && valBool ) { if ( audioToModify == NULL ) { const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); DEBUG_ASSERTCRASH( baseInfo != NULL, ("getBaseSoundAmbientInfo() return NULL" ) ); if ( baseInfo != NULL ) { audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); } } if ( audioToModify != NULL ) { valBool = properties->getBool( TheKey_objectSoundAmbientLooping, &exists ); if ( exists ) { audioToModify->overrideLoopFlag( valBool ); infoModified = true; } valInt = properties->getInt( TheKey_objectSoundAmbientLoopCount, &exists ); if ( exists && BitTest( audioToModify->m_control, AC_LOOP ) ) { audioToModify->overrideLoopCount( valInt ); infoModified = true; } valReal = properties->getReal( TheKey_objectSoundAmbientMinVolume, &exists ); if ( exists ) { audioToModify->overrideMinVolume( valReal ); infoModified = true; } valReal = properties->getReal( TheKey_objectSoundAmbientVolume, &exists ); if ( exists ) { audioToModify->overrideVolume( valReal ); infoModified = true; } valReal = properties->getReal( TheKey_objectSoundAmbientMinRange, &exists ); if ( exists ) { audioToModify->overrideMinRange( valReal ); infoModified = true; } valReal = properties->getReal( TheKey_objectSoundAmbientMaxRange, &exists ); if ( exists ) { audioToModify->overrideMaxRange( valReal ); infoModified = true; } valInt = properties->getInt( TheKey_objectSoundAmbientPriority, &exists ); if ( exists ) { audioToModify->overridePriority ( (AudioPriority)valInt ); infoModified = true; } } } } if ( !soundEnabledExists ) { // Decide if the sound should start enabled or not, since the map maker didn't record // a preference. Enable permanently looping sounds, disable one-shot sounds by default // NOTE: This test should match the tests done in MapObjectProps::mapObjectPageSound::dictToEnabled() // when it decided whether or not to show a customized sound as enabled if ( audioToModify != NULL ) { soundEnabled = audioToModify->isPermanentSound(); soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. } else { // Use default audio const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); if ( baseInfo != NULL ) { soundEnabled = baseInfo->isPermanentSound(); soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. } } } if ( soundEnabledExists && !soundEnabled ) { // Make sure sound doesn't start playing when we set it // ...FromScript because this is also controlled by the map designer not the game logic drawable->enableAmbientSoundFromScript( false ); } if ( infoModified && audioToModify != NULL ) { // Give a custom, level-specific name drawable->mangleCustomAudioName( audioToModify ); // Pass to TheAudio TheAudio->addAudioEventInfo( audioToModify ); drawable->setCustomSoundAmbientInfo( audioToModify ); audioToModify = NULL; // Belongs to TheAudio now } if ( audioToModify != NULL ) { audioToModify->deleteInstance(); audioToModify = NULL; } if ( soundEnabledExists && soundEnabled ) { // Play sound now that it is set up, if needed. Don't call if already enabled because that // can cause sound to play twice // ...FromScript because this is also controlled by the map designer not the game logic if ( !drawable->getAmbientSoundEnabledFromScript() ) { drawable->enableAmbientSoundFromScript( true ); } } } } //------------------------------------------------------------------------------------------------- void Object::friend_adjustPowerForPlayer( Bool incoming ) { if (isDisabled() && getTemplate()->getEnergyProduction() > 0) { // Disabledness only affects Producers, not Consumers. return; } if (incoming) { getControllingPlayer()->getEnergy()->objectEnteringInfluence(this); } else { getControllingPlayer()->getEnergy()->objectLeavingInfluence(this); } } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// //------------------------------------------------------------------------------------------------- void Object::onDisabledEdge(Bool becomingDisabled) { // rip through the behavior modules and call the onDisabledEdge for any modules that care for( BehaviorModule **module = m_behaviors; *module; ++module ) (*module)->onDisabledEdge( becomingDisabled ); DozerAIInterface *dozerAI = getAI() ? getAI()->getDozerAIInterface() : NULL; if( becomingDisabled && dozerAI ) { // Have to say goodbye to the thing we might be building or repairing so someone else can do it. if( dozerAI->getCurrentTask() != DOZER_TASK_INVALID ) dozerAI->cancelTask( dozerAI->getCurrentTask() ); } Player* controller = getControllingPlayer(); // can be called during game teardown, thus controller can be null if (controller) { //@todo jkmcd - Colin suggested we rewrite this to use the interface stuff. I agree, but need // to get some more bugs fixed today. static NameKeyType radar = NAMEKEY("RadarUpgrade"); Module *mod = mod = findModule(radar); if (mod) { RadarUpgrade *radarMod = (RadarUpgrade*) mod; if (radarMod->isAlreadyUpgraded()) { // Need to decrement the count here, because we own a radar upgrade if (becomingDisabled) { controller->removeRadar(radarMod->getIsDisableProof()); } else { controller->addRadar(radarMod->getIsDisableProof()); } } } } // We will need to adjust power ... somehow ... Int powerToAdjust = getTemplate()->getEnergyProduction(); if( powerToAdjust > 0 ) { // We can't affect something that consumes, or else we go low power which removes the consumption // which makes us not low power so we add the consumption so we go low power... // This check also guaards the IsDisabled in friend_adjustPower above static NameKeyType powerPlant = NAMEKEY("PowerPlantUpgrade"); static NameKeyType overCharge = NAMEKEY("OverchargeBehavior"); Module* mod = findModule(powerPlant); if (mod) { PowerPlantUpgrade *powerPlantMod = (PowerPlantUpgrade*) mod; if (powerPlantMod->isAlreadyUpgraded()) { powerToAdjust += getTemplate()->getEnergyBonus(); } } mod = findModule(overCharge); if (mod) { OverchargeBehavior *overChargeMod = (OverchargeBehavior*) mod; if (overChargeMod->isOverchargeActive()) { powerToAdjust += getTemplate()->getEnergyBonus(); } } // Now, adjust the power for the player. if (controller) controller->getEnergy()->adjustPower(powerToAdjust, !becomingDisabled); } } //------------------------------------------------------------------------------------------------- /** Object CRC implemtation */ //------------------------------------------------------------------------------------------------- void Object::crc( Xfer *xfer ) { // This is evil - we cast the const Matrix3D * to a Matrix3D * because the XferCRC class must use // the same interface as the XferLoad class for save game restore. This only works because // XferCRC does not modify its data. #ifdef DEBUG_CRC // g_logObjectCRCs = TRUE; // Bool g_logAllObjects = TRUE; AsciiString logString; AsciiString tmp; Bool doLogging = g_logObjectCRCs /* && getControllingPlayer()->getPlayerType() == PLAYER_HUMAN */; if (doLogging) { tmp.format("CRC of Object %d (%s), owned by player %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex()); logString.concat(tmp); } #endif DEBUG_CRC xfer->xferUnsignedByte(&m_privateStatus); #ifdef DEBUG_CRC if (doLogging) { tmp.format("m_privateStatus: %X, ", (UnsignedInt)m_privateStatus); logString.concat(tmp); } #endif // DEBUG_CRC xfer->xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); #ifdef DEBUG_CRC if (doLogging) { XferCRC tmpXfer; tmpXfer.open("tmp"); tmpXfer.xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); tmp.format("getTransformMatrix(): %8.8X, ", tmpXfer.getCRC()); tmpXfer.close(); logString.concat(tmp); } #endif DEBUG_CRC #ifdef DEBUG_CRC if (doLogging) { const Matrix3D *mtx = getTransformMatrix(); CRCDEBUG_LOG(("CRC of Object %d (%s), owned by player %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex())); DUMPMATRIX3D(mtx); } #endif DEBUG_CRC xfer->xferUser(&m_id, sizeof(m_id)); #ifdef DEBUG_CRC if (doLogging) { tmp.format("m_id: %d, ", m_id); logString.concat(tmp); } #endif DEBUG_CRC xfer->xferUser(&m_objectUpgradesCompleted, sizeof(Int64)); #ifdef DEBUG_CRC if (doLogging) { tmp.format("m_objectUpgradesCompleted: %I64X, ", m_objectUpgradesCompleted); logString.concat(tmp); } #endif DEBUG_CRC if (m_experienceTracker) xfer->xferSnapshot( m_experienceTracker ); #ifdef DEBUG_CRC if (doLogging) { XferCRC tmpXfer; tmpXfer.open("tmp"); tmpXfer.xferSnapshot(m_experienceTracker); tmp.format("m_experienceTracker: %8.8X, ", tmpXfer.getCRC()); tmpXfer.close(); logString.concat(tmp); } #endif DEBUG_CRC Real health = getBodyModule()->getHealth(); xfer->xferUser(&health, sizeof(health)); #ifdef DEBUG_CRC if (doLogging) { tmp.format("health: %g/%8.8X, ", health, AS_INT(health)); logString.concat(tmp); } #endif DEBUG_CRC xfer->xferUnsignedInt(&m_weaponBonusCondition); #ifdef DEBUG_CRC if (doLogging) { tmp.format("m_weaponBonusCondition: %8.8X, ", m_weaponBonusCondition); logString.concat(tmp); } #endif DEBUG_CRC Real scalar = getBodyModule()->getDamageScalar(); xfer->xferUser(&scalar, sizeof(scalar)); #ifdef DEBUG_CRC if (doLogging) { tmp.format("damage scalar: %g/%8.8X\n", scalar, AS_INT(scalar)); logString.concat(tmp); CRCDEBUG_LOG(("%s", logString.str())); } #endif DEBUG_CRC for (Int i=0; ixferSnapshot( thisWeapon ); } } } // end crc //------------------------------------------------------------------------------------------------- /** Object xfer implemtation * Version Info: * 1: Initial version * 2: Xfers m_singleUseCommandUsed... determines if the single use command button has been used or not. * 3: Xfers the solehealingbenefactor ID and expiration frame * 4: misc stuff that got missed somehow * 5: m_isReceivingDifficultyBonus * 6: We do indeed need to save m_containedBy. The comment misrepresents what the contain module will do. * 7: save full mtx, not pos+orient. * 8: Kris: Conversion of object status bits from UnsignedInt to BitFlags<> * 9: Extra sighting for reveal to all with different range units */ //------------------------------------------------------------------------------------------------- void Object::xfer( Xfer *xfer ) { // version const XferVersion currentVersion = 9; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // object ID ObjectID id = getID(); xfer->xferObjectID( &id ); setID( id ); DEBUG_LOG(("Xfer Object %s id=%d\n",getTemplate()->getName().str(),id)); if (version >= 7) { Matrix3D mtx = *getTransformMatrix(); xfer->xferMatrix3D(&mtx); setTransformMatrix(&mtx); } else { // object position Coord3D pos = *getPosition(); xfer->xferCoord3D( &pos ); setPosition( &pos ); // orientation Real orientation = getOrientation(); xfer->xferReal( &orientation ); setOrientation( orientation ); } // team TeamID teamID = m_team ? m_team->getID() : TEAM_ID_INVALID; xfer->xferUser( &teamID, sizeof( TeamID ) ); // DON'T set the team yet; must wait till we read our status bits, // since setTeam can affect the player's power usage, but that could // be done incorrectly if our status bits aren't accurate yet... (srj) // producer id xfer->xferObjectID( &m_producerID ); // builder id xfer->xferObjectID( &m_builderID ); // drawable id Drawable *draw = getDrawable(); DrawableID drawableID = draw ? draw->getID() : INVALID_DRAWABLE_ID; xfer->xferDrawableID( &drawableID ); if( xfer->getXferMode() == XFER_LOAD ) { // change the ID of the drawable attached to be the same ID as it was when it was saved draw->setID( drawableID ); } // end if // internal name xfer->xferAsciiString( &m_name ); // status if( version >= 8 ) { m_status.xfer( xfer ); } else { //We are loading an old version, so we must convert it from a 32-bit int to a bitflag UnsignedInt oldStatus; xfer->xferUnsignedInt( &oldStatus ); //Clear our status m_status.clear(); for( int i = 0; i < 32; i++ ) { UnsignedInt bit = 1<xferUnsignedByte( &m_scriptStatus ); // private status xfer->xferUnsignedByte( &m_privateStatus ); // OK, now that we have xferred our status bits, it's safe to set the team... if( xfer->getXferMode() == XFER_LOAD ) { Team *team = TheTeamFactory->findTeamByID( teamID ); if( team == NULL ) { DEBUG_CRASH(( "Object::xfer - Unable to load team\n" )); throw SC_INVALID_DATA; } const Bool restoring = true; setOrRestoreTeam( team, restoring ); } // geometry info xfer->xferSnapshot( &m_geometryInfo ); // sighting info, last look - must be saved cause we save PartitionCell::m_shroudLevel xfer->xferSnapshot( m_partitionLastLook ); if( version >= 9 ) xfer->xferSnapshot( m_partitionRevealAllLastLook ); // sighting info, last shroud - must be saved cause we save PartitionCell::m_shroudLevel xfer->xferSnapshot( m_partitionLastShroud ); // vision spied by xfer->xferUser( m_visionSpiedBy, sizeof( Int ) * MAX_PLAYER_COUNT ); // vision spied by mask xfer->xferUser( &m_visionSpiedMask, sizeof( PlayerMaskType ) ); // sighting info, last threat // John M says we don't need to save this (CBD) // xfer->xferSnapshot( &m_partitionLastThreat ); // sighting info, last value // John M says we don't need to save this (CBD) // xfer->xferSnapshot( &m_partitionLastValue ); // vision range xfer->xferReal( &m_visionRange ); // shroud clearing range xfer->xferReal( &m_shroudClearingRange ); // shroud range xfer->xferReal( &m_shroudRange ); // disabled mask m_disabledMask.xfer( xfer ); //New var added for version 2. Determines if the single use command button has been used or not. if( xfer->getXferMode() == XFER_SAVE || version >= 2 ) { xfer->xferBool( &m_singleUseCommandUsed ); } else { m_singleUseCommandUsed = false; } // disabled till frame xfer->xferUser( m_disabledTillFrame, sizeof( UnsignedInt ) * DISABLED_COUNT ); // special model condition until xfer->xferUnsignedInt( &m_smcUntil ); // // radar data ... when loading, we will remove all objects from the radar and let // the radar system load itself as a separate chunk of data from the save file // if( xfer->getXferMode() == XFER_LOAD && m_radarData ) TheRadar->removeObject( this ); // experience tracker xfer->xferSnapshot( m_experienceTracker ); // // we do not need to do anything with our m_containedBy pointer, the post process // of that objects contain module will actually re-do the contain process again // // m_containedBy <-- do nothing with this right now if( version >= 6 ) { // No, the contain module is just going to friend_ reach in and set this for us. // Containers more complicated than Open (like Tunnel) can't do that. Our variable, // our responsibility. if( xfer->getXferMode() == XFER_SAVE ) { if( m_containedBy != NULL ) m_xferContainedByID = m_containedBy->getID(); else m_xferContainedByID = INVALID_ID; } xfer->xferObjectID( &m_xferContainedByID ); } // contained by frame xfer->xferUnsignedInt( &m_containedByFrame ); // construction percent xfer->xferReal( &m_constructionPercent ); // upgrades completed xfer->xferUpgradeMask( &m_objectUpgradesCompleted ); // original team name xfer->xferAsciiString( &m_originalTeamName ); // indicator color xfer->xferColor( &m_indicatorColor ); // health box offset xfer->xferCoord3D( &m_healthBoxOffset ); // Entered & exited housekeeping. Int i; xfer->xferByte(&m_numTriggerAreasActive); xfer->xferUnsignedInt(&m_enteredOrExitedFrame); xfer->xferICoord3D(&m_iPos); if (m_numTriggerAreasActive<0 || m_numTriggerAreasActive>MAX_TRIGGER_AREA_INFOS) { DEBUG_CRASH(("Invalid m_numTriggerAreasActive = %d, max is %d", m_numTriggerAreasActive, MAX_TRIGGER_AREA_INFOS)); throw SC_INVALID_DATA; } for (i=0; igetTriggerName(); } xfer->xferAsciiString(&triggerName); if (xfer->getXferMode() == XFER_LOAD) { // // CBD (11-13-2002) I'm disabling this because it appears there might be some areas with // empty names, see John A. for more info // //if (triggerName.isNotEmpty()) m_triggerInfo[i].pTrigger = TheTerrainLogic->getTriggerAreaByName(triggerName); } xfer->xferByte(&m_triggerInfo[i].entered); xfer->xferByte(&m_triggerInfo[i].exited); xfer->xferByte(&m_triggerInfo[i].isInside); } // Layer object is pathing on. xfer->xferUser(&m_layer, sizeof(m_layer)); // Layer of current path goal. xfer->xferUser(&m_destinationLayer, sizeof(m_destinationLayer)); // Object selectability. xfer->xferBool(&m_isSelectable); xfer->xferUnsignedInt(&m_safeOcclusionFrame); // User formations. xfer->xferUser(&m_formationID, sizeof(m_formationID)); if (m_formationID!=NO_FORMATION_ID) { xfer->xferCoord2D(&m_formationOffset); } // module count UnsignedShort moduleCount = 0; for (BehaviorModule** b = m_behaviors; *b; ++b) ++moduleCount; xfer->xferUnsignedShort( &moduleCount ); AsciiString moduleIdentifier; BehaviorModule *module; if( xfer->getXferMode() == XFER_SAVE ) { // go through all modules for (BehaviorModule** b = m_behaviors; *b; ++b) { // get module module = *b; // write module identifier moduleIdentifier = TheNameKeyGenerator->keyToName( module->getModuleTagNameKey() ); DEBUG_ASSERTCRASH( moduleIdentifier != AsciiString::TheEmptyString, ("Object::xfer - Module tag key does not translate to a string!\n") ); xfer->xferAsciiString( &moduleIdentifier ); // begin a data block xfer->beginBlock(); // xfer data xfer->xferSnapshot( module ); // end data block xfer->endBlock(); } // end for, it } // end if, save else { AsciiString otherModuleIdentifier; // read all module data for( UnsignedShort i = 0; i < moduleCount; ++i ) { // read module name xfer->xferAsciiString( &moduleIdentifier ); NameKeyType moduleIdentifierKey = TheNameKeyGenerator->nameToKey(moduleIdentifier); // find the module with this identifier in the module list module = NULL; for (BehaviorModule** b = m_behaviors; b && *b; ++b) { if (moduleIdentifierKey == (*b)->getModuleTagNameKey()) { module = *b; break; } } // end for, moduleIt // start of a new block 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(( "Object::xfer - Module '%s' was indicated in file, but not found on object '%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 block xfer->endBlock(); } // end for, i module count recorded in file } // end else, load if ( version >= 3 ) { xfer->xferObjectID( &m_soleHealingBenefactorID ); xfer->xferUnsignedInt( &m_soleHealingBenefactorExpirationFrame ); } else if ( xfer->getXferMode() == XFER_LOAD ) { m_soleHealingBenefactorID = INVALID_ID; m_soleHealingBenefactorExpirationFrame = 0; } // Doesn't need to be saved. These are created as needed. jba. //AIGroup* m_group; ///< if non-NULL, we are part of this group of agents // don't need to save m_partitionData. DEBUG_ASSERTCRASH(!(xfer->getXferMode() == XFER_LOAD && m_partitionData == NULL), ("should not be in partitionmgr yet")); // don't need to be saved or loaded; are inited & cached for runtime only by our ctor (srj) //m_repulsorHelper; //m_smcHelper; //m_wsHelper; //m_defectionHelper; //m_firingTracker; //m_contain; //m_body; //m_ai; //m_physics; #if defined(_DEBUG) || defined(_INTERNAL) //m_hasDiedAlready; #endif if (version >= 4) { // xfer the weaponSetFlags FIRST, since we need 'em to restore the weaponSet properly. (srj) m_curWeaponSetFlags.xfer( xfer ); xfer->xferUnsignedInt(&m_weaponBonusCondition); xfer->xferUser(&m_lastWeaponCondition, sizeof(m_lastWeaponCondition)); // do the weaponSet itself after all the weapon-related stuff, just in case xfer->xferSnapshot(&m_weaponSet); m_specialPowerBits.xfer( xfer ); xfer->xferAsciiString(&m_commandSetStringOverride); xfer->xferBool(&m_modulesReady); } if (version >= 5) { xfer->xferBool(&m_isReceivingDifficultyBonus); } else m_isReceivingDifficultyBonus = FALSE; } // end xfer //------------------------------------------------------------------------------------------------- /** Object load game post process phase */ //------------------------------------------------------------------------------------------------- void Object::loadPostProcess() { if( m_xferContainedByID != INVALID_ID ) m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); else m_containedBy = NULL; } // end loadPostProcess //------------------------------------------------------------------------------------------------- /** Does this object have this upgrade */ //------------------------------------------------------------------------------------------------- Bool Object::hasUpgrade( const UpgradeTemplate *upgradeT ) const { if( m_objectUpgradesCompleted.testForAll( upgradeT->getUpgradeMask() ) ) { return TRUE; } return FALSE; } // end hasUpgrade //------------------------------------------------------------------------------------------------- /** Is this object capable of having this upgrade */ //------------------------------------------------------------------------------------------------- Bool Object::affectedByUpgrade( const UpgradeTemplate *upgradeT ) const { UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); UpgradeMaskType maskToCheck = playerMask; maskToCheck.set( objectMask ); maskToCheck.set( upgradeT->getUpgradeMask() ); // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. // We combine all the masks in case someone has a Object AND Player combination for (BehaviorModule** module = m_behaviors; *module; ++module) { UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); if (!upgrade) continue; if( upgrade->wouldUpgrade( maskToCheck ) ) { // if any of my many upgrade modules would execute in response to this flag, say yes. return TRUE; } } return FALSE; } // end affectedByUpgrade //------------------------------------------------------------------------------------------------- /** Give this upgrade to this object */ //------------------------------------------------------------------------------------------------- void Object::giveUpgrade( const UpgradeTemplate *upgradeT ) { if (upgradeT) { m_objectUpgradesCompleted.set( upgradeT->getUpgradeMask() ); // // iterate through all the upgrade modules of this object and call the method to // grant a new upgrade // updateUpgradeModules(); } } // end giveUpgrade //------------------------------------------------------------------------------------------------- /** Remove this upgrade from this object */ //------------------------------------------------------------------------------------------------- void Object::removeUpgrade( const UpgradeTemplate *upgradeT ) { m_objectUpgradesCompleted.clear( upgradeT->getUpgradeMask() ); for (BehaviorModule** module = m_behaviors; *module; ++module) { UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); if (!upgrade) continue; // Whoa, please note that while the function is called Object::RemoveUpgrade, it is not removing anything // in the sense of undoing the effects. It is just resetting the upgrade so it may be run again. upgrade->resetUpgrade( upgradeT->getUpgradeMask() ); } } //------------------------------------------------------------------------------------------------- /** Central point for onCapture logic */ //------------------------------------------------------------------------------------------------- void Object::onCapture( Player *oldOwner, Player *newOwner ) { // Everybody dhills when they captured so they don't keep doing something the new player might not want him to be doing if( getAIUpdateInterface() && (oldOwner != newOwner) ) getAIUpdateInterface()->aiIdle(CMD_FROM_AI); // this gets the new owner some points newOwner->getScoreKeeper()->addObjectCaptured(this); // rip through the behavior modules and call the onCapture for any modules that care for( BehaviorModule **module = m_behaviors; *module; ++module ) (*module)->onCapture( oldOwner, newOwner ); // // We have to undo our look for the old team and redo it for the new. // onCapture is used now, so it better be called after ownership changes and not before. // handlePartitionCellMaintenance(); // Design needs the player to be able to sell buildings he steals from the AI's build list, and this is the // easiest fix. The only snafu would be a key building build listed by the AI that the player can capture // and the AI tries to capture back but needs to not sell. In that case, a Cinematic Unsellable version // of the building needs to be made. This fix has been okayed as the most non-lethal in November. clearScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE); // mark the command bar to redraw TheControlBar->markUIDirty(); if (oldOwner!=newOwner && newOwner->isSkirmishAIPlayer()) { // The skirmish ai doesn't know what to do with captured faction buildings except sell them. if (isFactionStructure()) { TheBuildAssistant->sellObject( this ); } } } // end onCapture //------------------------------------------------------------------------------------------------- /// Object level events that need to happen upon game death void Object::onDie( DamageInfo *damageInfo ) { checkAndDetonateBoobyTrap(NULL);// Already dying, so no need to handle death case of explosion #if defined(_DEBUG) || defined(_INTERNAL) DEBUG_ASSERTCRASH(m_hasDiedAlready == false, ("Object::onDie has been called multiple times. This is invalid. jkmcd")); m_hasDiedAlready = true; #endif Bool selfInflicted = (damageInfo->in.m_sourceID == getID()); // FIRST, call our die modules. for (BehaviorModule** d = m_behaviors; *d; ++d) { DieModuleInterface* die = (*d)->getDie(); if (die) die->onDie(damageInfo); } // When objects die we remove from the radar as they're really not interesting anymore if( m_radarData ) TheRadar->removeObject( this ); // Just in case I have been sporting one of thise fancy Terrain Decals, //I naturally lose it now, because I'm dead. Drawable *draw = getDrawable(); if (draw) draw->setTerrainDecalFadeTarget(0.0f, -0.03f);//fade... //if (draw) draw->setTerrainDecal(TERRAIN_DECAL_NONE);//pop! // objects that were spawned from something, need to tell their spawner that they have died Object* spawner = TheGameLogic->findObjectByID( getProducerID() ); if( spawner ) { // get the spawn behavior interface of the spawner SpawnBehaviorInterface *spawnerBehavior = spawner->getSpawnBehaviorInterface(); if( spawnerBehavior ) spawnerBehavior->onSpawnDeath( getID(), damageInfo ); } handlePartitionCellMaintenance(); if(m_team) m_team->notifyTeamOfObjectDeath(); if (isLocallyControlled() && !selfInflicted) // wasLocallyControlled? :-) { if (isKindOf(KINDOF_STRUCTURE) && isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)) { TheEva->setShouldPlay(EVA_BuldingLost); } else if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE)) { TheEva->setShouldPlay(EVA_UnitLost); //Create a fake radar event so the user can use the spacebar to quickly jump to this! TheRadar->tryEvent( RADAR_EVENT_FAKE, getPosition() ); } } // This call won't do anything if we aren't actually in the list. //Kris: Added NULL check to prevent crash with combat bikes & their riders getting deleted on exit. if( getControllingPlayer() ) { TheInGameUI->removeIdleWorker( this, getControllingPlayer()->getPlayerIndex() ); } //When a GLA hole is in the process of rebuilding, and that rebuild is lost, we need to //tell anyone attacking it to transfer the attack to the hole that still exists. if( testStatus( OBJECT_STATUS_RECONSTRUCTING ) ) { Object *hole = TheGameLogic->findObjectByID( getProducerID() ); if( hole ) { // set the information in the hole about what to build RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( hole ); // sanity DEBUG_ASSERTCRASH( rhbi, ("Object::onDie() - No Rebuild Hole Behavior interface on hole\n") ); // start the rebuild process if( rhbi ) { rhbi->startRebuildProcess( getTemplate(), getID() ); } //Transfer any attackers from the destroyed building to the hole. for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) { AIUpdateInterface* ai = obj->getAI(); if (!ai) continue; ai->transferAttack( getID(), hole->getID() ); } } } } //------------------------------------------------------------------------------------------------- void Object::setWeaponBonusCondition(WeaponBonusConditionType wst) { WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; m_weaponBonusCondition |= (1 << wst); if( oldCondition != m_weaponBonusCondition ) { // Our weapon bonus just changed, so we need to immediately update our weapons m_weaponSet.weaponSetOnWeaponBonusChange(this); } } //------------------------------------------------------------------------------------------------- void Object::clearWeaponBonusCondition(WeaponBonusConditionType wst) { WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; m_weaponBonusCondition &= ~(1 << wst); if( oldCondition != m_weaponBonusCondition ) { // Our weapon bonus just changed, so we need to immediately update our weapons m_weaponSet.weaponSetOnWeaponBonusChange(this); } } //------------------------------------------------------------------------------------------------- /** A weapon cannot be in charge of maintaining condition flags as it is all event driven. I will maintain my ModelCondition myself if it should change. Firing is set by firing logic, so I don't include it here. It is only the states that expire on timers that noone watches that I am concerned with. */ //------------------------------------------------------------------------------------------------- void Object::adjustModelConditionForWeaponStatus() { UnsignedInt now = TheGameLogic->getFrame(); for (int i = 0; i < WEAPONSLOT_COUNT; ++i) { const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); if (!w) { m_lastWeaponCondition[i] = WSF_NONE; continue; } WeaponSetConditionType conditionToSet = WSF_INVALID; if (i != m_weaponSet.getCurWeaponSlot()) { // if this isn't the current weapon, then we never set ANYTHING for it. conditionToSet = WSF_NONE; } else if (w->getLastShotFrame() == now) { // yep, this overrides any weapon-status condition! conditionToSet = WSF_FIRING; } else if (!testStatus( OBJECT_STATUS_IS_ATTACKING )) { // srj sez: not 100% sure about this one, but the problem is: say we were attacking, // then issue a move command. if we didn't do this here, we might still have a 'firing' // pose, because his weapon might be in 'reloading' mode. since we're not attacking, however, // we really don't care, so we just force the issue here. (This might still need tweaking for the pursue state.) conditionToSet = WSF_NONE; } else { WeaponStatus newStatus = w->getStatus(); const static WeaponSetConditionType s_wsfLookup[WEAPON_STATUS_COUNT] = { WSF_NONE, // READY_TO_FIRE, WSF_NONE, // OUT_OF_AMMO, WSF_BETWEEN, // BETWEEN_FIRING_SHOTS, WSF_RELOADING, // RELOADING_CLIP, WSF_PREATTACK // PRE_ATTACK, }; conditionToSet = s_wsfLookup[newStatus]; // special case this: say we are firing in bursts: pow-pow-pow-pause, etc. // then we might have a frame where we have reloaded and are ready-to-fire, // but haven't fired yet this frame. in that case, use 'between' so we still have // a firing pose, 'cuz if we use 'none' we will 'pop' back to idle for a frame. (srj) // additional note: only do if aiming or firing, since we could also be in this state if // we are approaching or pursuing a target! (srj) if (newStatus == READY_TO_FIRE && conditionToSet == WSF_NONE && testStatus( OBJECT_STATUS_IS_ATTACKING ) && (testStatus( OBJECT_STATUS_IS_AIMING_WEAPON ) || testStatus( OBJECT_STATUS_IS_FIRING_WEAPON ))) { conditionToSet = WSF_BETWEEN; } } if (m_drawable) { m_drawable->updateDrawableClipStatus( w->getRemainingAmmo(), w->getClipSize(), w->getWeaponSlot() ); if (conditionToSet != WSF_INVALID && conditionToSet != m_lastWeaponCondition[i]) { m_lastWeaponCondition[i] = conditionToSet; ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot((WeaponSlotType)i, conditionToSet); m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[i], c); if (conditionToSet == WSF_PREATTACK) { // in the preattack state, adjust the speed of the preattack anim to match the actual time it will take UnsignedInt preAttackDone = w->getPreAttackFinishedFrame(); if (preAttackDone > now) m_drawable->setAnimationLoopDuration(preAttackDone - now); } } } } } //------------------------------------------------------------------------------------------------- /// We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' void Object::onPartitionCellChange() { handlePartitionCellMaintenance(); } //------------------------------------------------------------------------------------------------- void Object::handlePartitionCellMaintenance() { handleShroud(); handleValueMap(); handleThreatMap(); } //------------------------------------------------------------------------------------------------- void Object::handleShroud() { // Undo last looking unlook(); // and shrouding unshroud(); // redo shrouding shroud(); // Redo looking look(); } //------------------------------------------------------------------------------------------------- void Object::handleValueMap() { removeValue(); addValue(); } //------------------------------------------------------------------------------------------------- void Object::handleThreatMap() { removeThreat(); addThreat(); } //------------------------------------------------------------------------------------------------- void Object::addValue() { if( !m_partitionLastValue->isInvalid() ) { DEBUG_CRASH( ("An Object is adding value, but hasn't removed his previous value.") ); return; } if (!getControllingPlayer()) return; if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) return; m_partitionLastValue->m_where = *getPosition(); m_partitionLastValue->m_data = getTemplate()->friend_getBuildCost(); m_partitionLastValue->m_forWhom = getControllingPlayer()->getPlayerMask(); m_partitionLastValue->m_howFar = getVisionRange(); // we are valuable all the way to where we can target. ThePartitionManager->doValueAffect(m_partitionLastValue->m_where.x, m_partitionLastValue->m_where.y, m_partitionLastValue->m_howFar, m_partitionLastValue->m_data, m_partitionLastValue->m_forWhom ); } //------------------------------------------------------------------------------------------------- void Object::removeValue() { if( m_partitionLastValue->isInvalid() ) { // removing before adding is valid, cause we always remove before adding. (So the first remove // will occur before the first add) return; } ThePartitionManager->undoValueAffect(m_partitionLastValue->m_where.x, m_partitionLastValue->m_where.y, m_partitionLastValue->m_howFar, m_partitionLastValue->m_data, m_partitionLastValue->m_forWhom ); m_partitionLastValue->reset(); } //------------------------------------------------------------------------------------------------- void Object::addThreat() { if( !m_partitionLastThreat->isInvalid() ) { DEBUG_CRASH( ("An Object is adding threat, but hasn't removed his previous threat. (He hasn't finished the threat?)") ); return; } if (!getControllingPlayer()) return; if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) return; m_partitionLastThreat->m_where = *getPosition(); m_partitionLastThreat->m_data = getTemplate()->getThreatValue(); m_partitionLastThreat->m_forWhom = getControllingPlayer()->getPlayerMask(); m_partitionLastThreat->m_howFar = getVisionRange(); // we are threatening all the way to where we can target. ThePartitionManager->doThreatAffect(m_partitionLastThreat->m_where.x, m_partitionLastThreat->m_where.y, m_partitionLastThreat->m_howFar, m_partitionLastThreat->m_data, m_partitionLastThreat->m_forWhom ); } //------------------------------------------------------------------------------------------------- void Object::removeThreat() { if( m_partitionLastThreat->isInvalid() ) { // removing before adding is valid, cause we always remove before adding. (So the first remove // will occur before the first add) return; } ThePartitionManager->undoThreatAffect(m_partitionLastThreat->m_where.x, m_partitionLastThreat->m_where.y, m_partitionLastThreat->m_howFar, m_partitionLastThreat->m_data, m_partitionLastThreat->m_forWhom ); m_partitionLastThreat->reset(); } //------------------------------------------------------------------------------------------------- void Object::look() { if( ! m_partitionLastLook->isInvalid() ) { DEBUG_CRASH( ("An Object is looking, but hasn't unlooked the last one.") ); return; } Player* controller = getControllingPlayer(); if ( controller ) { // I removed the check for objects under construction by request of designers since // they want constructing objects to have a reduced sight range now. -MW // dead or blind things don't reveal shroud // Some things get Destroyed directly without hitting Death. if( !isDestroyed() && !isEffectivelyDead() ) { ContainModuleInterface * contain = (getContainedBy() ? getContainedBy()->getContain() : NULL); if ( contain && !contain->isGarrisonable() ) return;// dont look, 'cause you are in a tunnel, now // GS 10-20 Need to expand that exception to all transports or else you get a perma reveal where // you entered the transport. Remember, this hackiness is caused by the fact that we never realized that // garrisoned buildings weren't looking, we were just seeing the leftover last look of the guy inside. // Otherwise we'd just have enclosingContainer control looking which is the 'correct' answer. Real shroudClearingRange = getShroudClearingRange(); if( shroudClearingRange > 0.0f ) { PlayerMaskType lookingMask = 0; if ( isKindOf(KINDOF_REVEAL_TO_ALL) ) { lookingMask = PLAYERMASK_ALL; } else { for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) { const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); // Build mask of of allies who can see me. // This is the Object-centric game level that cares if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) == ALLIES ) { lookingMask |= currentPlayer->getPlayerMask(); } } // Other players can also be looking through our eyes. lookingMask |= m_visionSpiedMask; } Coord3D pos = *getPosition(); ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudClearingRange, lookingMask ); m_partitionLastLook->m_where = pos; m_partitionLastLook->m_forWhom = lookingMask; m_partitionLastLook->m_howFar = getShroudClearingRange(); // DEBUG_LOG(( "A %s looks at %f, %f for %x at range %f\n", // getTemplate()->getName().str(), // pos.x, // pos.y, // lookingMask, // getShroudClearingRange() // )); } //Now reveal to everyone if we're special. Note this works differently than KINDOF_REVEAL_TO_ALL because //the kindof uses the same range as allies, spies, and owners would see. This template based shroud //reveal to all range can specify a different value so we can get a much smaller reveal distance. // And don't reveal while under construction. When finished, a refresh occurs, so don't worry. Real shroudRevealToAllRange = getTemplate()->getShroudRevealToAllRange(); if( shroudRevealToAllRange > 0.0f && !testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) { //Kris: August 20, 2003 //Seeing I added this concept, I'm changing it now to only reveal to all when the unit is visible. If it's stealthed, //we won't reveal it anymore (stealth general scudstorm). Bool stealthedAndNotDetected = testStatus( OBJECT_STATUS_STEALTHED ) && !testStatus( OBJECT_STATUS_DETECTED ) && !testStatus( OBJECT_STATUS_DISGUISED ); if( !stealthedAndNotDetected ) { Coord3D pos = *getPosition(); PlayerMaskType thePlayersMask = ThePlayerList->getPlayersWithRelationship( getControllingPlayer()->getPlayerIndex(), ALLOW_ENEMIES | ALLOW_NEUTRAL ); ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudRevealToAllRange, thePlayersMask ); m_partitionRevealAllLastLook->m_where = pos; m_partitionRevealAllLastLook->m_forWhom = thePlayersMask; m_partitionRevealAllLastLook->m_howFar = shroudRevealToAllRange; } } } } } //------------------------------------------------------------------------------------------------- void Object::unlook() { if( m_partitionLastLook->isInvalid() ) { // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error // This early return prevents an extra unlook if you never looked. Like you have 0 vision. return; } ThePartitionManager->queueUndoShroudReveal(m_partitionLastLook->m_where.x, m_partitionLastLook->m_where.y, m_partitionLastLook->m_howFar, m_partitionLastLook->m_forWhom ); // DEBUG_LOG(( "A %s queues an unlook at %f, %f for %x at range %f\n", // getTemplate()->getName().str(), // m_partitionLastLook.m_where.x, // m_partitionLastLook.m_where.y, // m_partitionLastLook.m_forWhom, // m_partitionLastLook.m_howFar // )); m_partitionLastLook->reset(); if( !m_partitionRevealAllLastLook->isInvalid() ) { ThePartitionManager->queueUndoShroudReveal(m_partitionRevealAllLastLook->m_where.x, m_partitionRevealAllLastLook->m_where.y, m_partitionRevealAllLastLook->m_howFar, m_partitionRevealAllLastLook->m_forWhom ); m_partitionRevealAllLastLook->reset(); } } //------------------------------------------------------------------------------------------------- void Object::shroud() { if( ! m_partitionLastShroud->isInvalid() ) { DEBUG_CRASH( ("An Object is shrouding, but hasn't unshrouded the last one.") ); return; } Player* controller = getControllingPlayer(); if ( controller ) { // things under construction don't shroud. (srj), nor do dead or blind things if( !getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) && !isEffectivelyDead() && getShroudRange() > 0.0f ) { PlayerMaskType shroudingMask = 0; for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) { const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); //Build mask of NON-allies. This is the Object-centric game level that cares if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) != ALLIES ) { shroudingMask |= currentPlayer->getPlayerMask(); } } Coord3D pos = *getPosition(); ThePartitionManager->doShroudCover(pos.x, pos.y, getShroudRange(), shroudingMask); m_partitionLastShroud->m_where = pos; m_partitionLastShroud->m_forWhom = shroudingMask; m_partitionLastShroud->m_howFar = getShroudRange(); } } } //------------------------------------------------------------------------------------------------- void Object::unshroud() { if( m_partitionLastShroud->isInvalid() ) { // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error // This early return prevents an extra unlook if you never looked. Like you have 0 shroud generation. return; } ThePartitionManager->undoShroudCover(m_partitionLastShroud->m_where.x, m_partitionLastShroud->m_where.y, m_partitionLastShroud->m_howFar, m_partitionLastShroud->m_forWhom); m_partitionLastShroud->reset(); } //------------------------------------------------------------------------------------------------- Real Object::getVisionRange() const { #if defined(_DEBUG) || defined(_INTERNAL) if (TheGlobalData->m_debugVisibility) { Vector3 pos(m_visionRange, 0, 0); for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) { pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, TheGlobalData->m_debugVisibilityTileDuration, TheGlobalData->m_debugVisibilityTargettableColor); } } #endif return m_visionRange; } //------------------------------------------------------------------------------------------------- void Object::setVisionRange( Real newVisionRange ) { m_visionRange = newVisionRange; } //------------------------------------------------------------------------------------------------- Real Object::getShroudClearingRange() const { Real shroudClearingRange=m_shroudClearingRange; if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) { //structures under construction have limited vision range. For now, base it //on the geometry extents so the structure can only see itself. shroudClearingRange = getGeometryInfo().getBoundingCircleRadius(); } #if defined(_DEBUG) || defined(_INTERNAL) if (TheGlobalData->m_debugVisibility) { Vector3 pos(shroudClearingRange, 0, 0); for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) { pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, TheGlobalData->m_debugVisibilityTileDuration, TheGlobalData->m_debugVisibilityDeshroudColor); } } #endif return shroudClearingRange; } //------------------------------------------------------------------------------------------------- void Object::setShroudClearingRange( Real newShroudClearingRange ) { if( newShroudClearingRange != m_shroudClearingRange ) { // The partition cell refresh is a slow operation, so only do it if you really have to. // Range change is a valid reason to relook. m_shroudClearingRange = newShroudClearingRange; /* Complete and total monkey hack fix. The problem: newObject doesn't get an initial pos, so all objects start at 0,0,0. Most code paths instantly move 'em to a good pos, but in some cases, that is too late: If we have search-and-destroy battle plan, we will apply it at that point, and clear out a vision range based on our current (wrong) location. Doh! So, this just sez: if you are at 0,0,0, don't call handlePartitionCellMaintenance()... since you will either (1) be moved elsewhere immediately, thus forcing it to be called via another route anyway, or (2) not be moved, which means you are a very naughty and worthless object anyway and we should just ignore you. Proper fix for next version is to require initial pos to be passed in to newObject so that all objects can start at their proper initial position from the start of the ctor. (srj) */ const Coord3D* pos = getPosition(); if (pos->x || pos->y || pos->z) { handlePartitionCellMaintenance(); } } } //------------------------------------------------------------------------------------------------- Real Object::getShroudRange() const { #if defined(_DEBUG) || defined(_INTERNAL) if (TheGlobalData->m_debugVisibility) { Vector3 pos(m_shroudRange, 0, 0); for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) { pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, TheGlobalData->m_debugVisibilityTileDuration, TheGlobalData->m_debugVisibilityGapColor); } } #endif return m_shroudRange; } //------------------------------------------------------------------------------------------------- void Object::setShroudRange( Real newShroudRange ) { m_shroudRange = newShroudRange; } //------------------------------------------------------------------------------------------------- void Object::setVisionSpied(Bool setting, Int byWhom) { Bool needRefresh = FALSE; // If this setting is an edge trigger on the reference count, I need to refresh if( setting ) { m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] + 1; if( m_visionSpiedBy[ byWhom ] == 1 ) needRefresh = TRUE; } else { m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] - 1; if( m_visionSpiedBy[ byWhom ] == 0 ) needRefresh = TRUE; } if( needRefresh ) { PlayerMaskType workingMask = 0; for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) { if( m_visionSpiedBy[i] > 0 ) BitSet( workingMask, ( 1 << i ) ); else BitClear( workingMask, ( 1 << i ) ); } m_visionSpiedMask = workingMask; handlePartitionCellMaintenance(); } } //------------------------------------------------------------------------------------------------- void Object::doStatusDamage( ObjectStatusTypes status, Real duration ) { if(m_statusDamageHelper) m_statusDamageHelper->doStatusDamage(status, duration); } //------------------------------------------------------------------------------------------------- void Object::doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration ) { if(m_tempWeaponBonusHelper) m_tempWeaponBonusHelper->doTempWeaponBonus(status, duration); } //------------------------------------------------------------------------------------------------- void Object::notifySubdualDamage( Real amount ) { if(m_subdualDamageHelper) m_subdualDamageHelper->notifySubdualDamage( amount ); // If we are gaining subdual damage, we are slowly tinting if( getDrawable() ) { if( amount > 0 ) getDrawable()->setTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); else getDrawable()->clearTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); } } //------------------------------------------------------------------------------------------------- /** Given a special power template, find the module in the object that can implement it. * There can be at most one */ //------------------------------------------------------------------------------------------------- SpecialPowerModuleInterface *Object::getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const { // sanity if( specialPowerTemplate == NULL ) return NULL; // search the modules for the one with the matching template for( BehaviorModule** m = m_behaviors; *m; ++m ) { SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); if (!sp) continue; if( sp->isModuleForPower( specialPowerTemplate ) ) return sp; } return NULL; } //------------------------------------------------------------------------------------------------- /** Execute special power */ //------------------------------------------------------------------------------------------------- void Object::doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced ) { if (isDisabled()) return; // sanity if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) return; // get the module and execute SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); if( mod ) mod->doSpecialPower( commandOptions ); } //------------------------------------------------------------------------------------------------- /** Execute special power */ //------------------------------------------------------------------------------------------------- void Object::doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced ) { if (isDisabled()) return; // sanity if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) return; // get the module and execute SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); if( mod ) mod->doSpecialPowerAtObject( obj, commandOptions ); } //------------------------------------------------------------------------------------------------- /** Execute special power */ //------------------------------------------------------------------------------------------------- void Object::doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced ) { if (isDisabled()) return; // sanity if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) return; // get the module and execute SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); if( mod ) mod->doSpecialPowerAtLocation( loc, angle, commandOptions ); } //------------------------------------------------------------------------------------------------- /** Execute special power */ //------------------------------------------------------------------------------------------------- void Object::doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced ) { if (isDisabled()) return; // sanity if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) return; // get the module and execute SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); if( mod ) mod->doSpecialPowerUsingWaypoints( way, commandOptions ); } //------------------------------------------------------------------------------------------------- /** Execute command button ability */ //------------------------------------------------------------------------------------------------- void Object::doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ) { if (isDisabled()) return; AIUpdateInterface *ai = getAIUpdateInterface(); if( commandButton ) { switch( commandButton->getCommandType() ) { case GUI_COMMAND_SPECIAL_POWER: if( commandButton->getSpecialPowerTemplate() ) { CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); doSpecialPower( commandButton->getSpecialPowerTemplate(), commandOptions, cmdSource == CMD_FROM_SCRIPT ); return; } break; case GUI_COMMAND_STOP: if( ai ) { ai->aiIdle( cmdSource ); return; } break; case GUI_COMMAND_SWITCH_WEAPON: { WeaponSlotType weaponSlot = commandButton->getWeaponSlot(); // GUI_COMMAND_SWITCH_WEAPON switches until un-switched, or switched to something else. setWeaponLock( weaponSlot, LOCKED_PERMANENTLY ); return; } case GUI_COMMAND_FIRE_WEAPON: if( ai ) { if( !BitTest( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) && !BitTest( commandButton->getOptions(), NEED_TARGET_POS ) ) { setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); //LOCATION BASED FIRE WEAPON ai->aiAttackPosition( NULL, commandButton->getMaxShotsToFire(), cmdSource ); } else { DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s cannot fire weapon with NO POSITION. Skipping.", commandButton->getName().str()) ); } return; } break; case GUI_COMMAND_OBJECT_UPGRADE: case GUI_COMMAND_PLAYER_UPGRADE: { const UpgradeTemplate *upgradeT = commandButton->getUpgradeTemplate(); DEBUG_ASSERTCRASH( upgradeT, ("Undefined upgrade '%s' in player upgrade command\n", "UNKNOWN") ); // sanity if( upgradeT == NULL ) break; if( upgradeT->getUpgradeType() == UPGRADE_TYPE_OBJECT ) { if( hasUpgrade( upgradeT ) || !affectedByUpgrade( upgradeT ) ) break; } // producer must have a production update ProductionUpdateInterface *pu = getProductionUpdateInterface(); if( pu == NULL ) break; // queue the upgrade "research" pu->queueUpgrade( upgradeT ); } return; case GUI_COMMAND_UNIT_BUILD: case GUI_COMMAND_DOZER_CONSTRUCT: { const ThingTemplate *tt = commandButton->getThingTemplate(); ProductionUpdateInterface *pu = this->getProductionUpdateInterface(); if (pu && tt) { pu->queueCreateUnit( tt, pu->requestUniqueUnitID()); return; } break; } case GUI_COMMAND_HACK_INTERNET:{ if( ai ) { ai->aiHackInternet( cmdSource ); return; } break; } case GUI_COMMAND_SELL: TheBuildAssistant->sellObject( this ); return; //Feel free to implement object based command buttons. case GUI_COMMAND_COMBATDROP: case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: case GUI_COMMAND_CANCEL_UNIT_BUILD: case GUI_COMMAND_CANCEL_UPGRADE: case GUI_COMMAND_ATTACK_MOVE: case GUI_COMMAND_GUARD: case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: case GUI_COMMAND_WAYPOINTS: case GUI_COMMAND_EXIT_CONTAINER: case GUI_COMMAND_EVACUATE: case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: case GUI_COMMAND_BEACON_DELETE: case GUI_COMMAND_SET_RALLY_POINT: case GUI_COMMAND_TOGGLE_OVERCHARGE: #ifdef ALLOW_SURRENDER case GUI_COMMAND_POW_RETURN_TO_PRISON: #endif case GUICOMMANDMODE_HIJACK_VEHICLE: case GUICOMMANDMODE_CONVERT_TO_CARBOMB: #ifdef ALLOW_SURRENDER case GUICOMMANDMODE_PICK_UP_PRISONER: #endif default: break; } DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); } } //------------------------------------------------------------------------------------------------- /** Execute command button ability directed at an object target */ //------------------------------------------------------------------------------------------------- void Object::doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ) { if (isDisabled()) return; AIUpdateInterface *ai = getAIUpdateInterface(); if( commandButton ) { switch( commandButton->getCommandType() ) { case GUI_COMMAND_COMBATDROP: if( ai ) { ai->aiCombatDrop( obj, *(obj->getPosition()), cmdSource ); } return; case GUI_COMMAND_SPECIAL_POWER: { if( commandButton->getSpecialPowerTemplate() ) { CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); doSpecialPowerAtObject( commandButton->getSpecialPowerTemplate(), obj, commandOptions, cmdSource == CMD_FROM_SCRIPT ); } return; } case GUI_COMMAND_STOP: if( ai ) { ai->aiIdle( cmdSource ); } return; case GUI_COMMAND_FIRE_WEAPON: if( ai ) { if( BitTest( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) ) { //OBJECT BASED FIRE WEAPON if( !obj ) { break; } if( !commandButton->isValidObjectTarget( this, obj ) ) { break; } setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); if( BitTest( commandButton->getOptions(), ATTACK_OBJECTS_POSITION ) ) { //Actually, you know what.... we want to attack the object's location instead. ai->aiAttackPosition( obj->getPosition(), commandButton->getMaxShotsToFire(), cmdSource ); } else { ai->aiAttackObject( obj, commandButton->getMaxShotsToFire(), cmdSource ); } } else { DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s cannot fire weapon at AN OBJECT. Skipping.", commandButton->getName().str()) ); } return; } break; case GUICOMMANDMODE_HIJACK_VEHICLE: case GUICOMMANDMODE_CONVERT_TO_CARBOMB: case GUICOMMANDMODE_SABOTAGE_BUILDING: if( ai ) { ai->aiEnter( obj, cmdSource ); } return; //Feel free to implement object based command buttons. case GUI_COMMAND_DOZER_CONSTRUCT: case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: case GUI_COMMAND_UNIT_BUILD: case GUI_COMMAND_CANCEL_UNIT_BUILD: case GUI_COMMAND_PLAYER_UPGRADE: case GUI_COMMAND_OBJECT_UPGRADE: case GUI_COMMAND_CANCEL_UPGRADE: case GUI_COMMAND_ATTACK_MOVE: case GUI_COMMAND_GUARD: case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: case GUI_COMMAND_WAYPOINTS: case GUI_COMMAND_EXIT_CONTAINER: case GUI_COMMAND_EVACUATE: case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: case GUI_COMMAND_BEACON_DELETE: case GUI_COMMAND_SET_RALLY_POINT: case GUI_COMMAND_SELL: case GUI_COMMAND_HACK_INTERNET: case GUI_COMMAND_TOGGLE_OVERCHARGE: case GUI_COMMAND_SWITCH_WEAPON: #ifdef ALLOW_SURRENDER case GUI_COMMAND_POW_RETURN_TO_PRISON: case GUICOMMANDMODE_PICK_UP_PRISONER: #endif default: break; } DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); } } //------------------------------------------------------------------------------------------------- /** Execute command button ability directed at a location */ //------------------------------------------------------------------------------------------------- void Object::doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ) { if (isDisabled()) return; AIUpdateInterface *ai = getAIUpdateInterface(); if( commandButton ) { switch( commandButton->getCommandType() ) { case GUI_COMMAND_SPECIAL_POWER: { if( commandButton->getSpecialPowerTemplate() ) { CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); doSpecialPowerAtLocation( commandButton->getSpecialPowerTemplate(), pos, INVALID_ANGLE, commandOptions, cmdSource == CMD_FROM_SCRIPT ); return; } break; } case GUI_COMMAND_ATTACK_MOVE: if( ai ) { ai->aiAttackMoveToPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); return; } break; case GUI_COMMAND_STOP: if( ai ) { ai->aiIdle( cmdSource ); return; } break; case GUI_COMMAND_DOZER_CONSTRUCT: TheBuildAssistant->buildObjectNow( this, commandButton->getThingTemplate(), pos, 0.0f, getControllingPlayer() ); return; case GUI_COMMAND_FIRE_WEAPON: if( ai ) { if( BitTest( commandButton->getOptions(), NEED_TARGET_POS ) ) { //LOCATION BASED FIRE WEAPON if( !pos ) { break; } setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); ai->aiAttackPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); } else { DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s cannot fire weapon at A POSITION. Skipping.", commandButton->getName().str()) ); } return; } break; case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: case GUI_COMMAND_UNIT_BUILD: case GUI_COMMAND_CANCEL_UNIT_BUILD: case GUI_COMMAND_PLAYER_UPGRADE: case GUI_COMMAND_OBJECT_UPGRADE: case GUI_COMMAND_CANCEL_UPGRADE: case GUI_COMMAND_GUARD: case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: case GUI_COMMAND_WAYPOINTS: case GUI_COMMAND_EXIT_CONTAINER: case GUI_COMMAND_EVACUATE: case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: case GUI_COMMAND_BEACON_DELETE: case GUI_COMMAND_SET_RALLY_POINT: case GUI_COMMAND_SELL: case GUI_COMMAND_HACK_INTERNET: case GUI_COMMAND_TOGGLE_OVERCHARGE: #ifdef ALLOW_SURRENDER case GUI_COMMAND_POW_RETURN_TO_PRISON: #endif case GUI_COMMAND_COMBATDROP: case GUI_COMMAND_SWITCH_WEAPON: case GUICOMMANDMODE_HIJACK_VEHICLE: case GUICOMMANDMODE_CONVERT_TO_CARBOMB: #ifdef ALLOW_SURRENDER case GUICOMMANDMODE_PICK_UP_PRISONER: #endif default: break; } DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); } } //------------------------------------------------------------------------------------------------- /** Execute command button ability directed at a location */ //------------------------------------------------------------------------------------------------- void Object::doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ) { if (isDisabled()) return; if( commandButton ) { if( !BitTest( commandButton->getOptions(), CAN_USE_WAYPOINTS ) ) { //Our button doesn't support waypoints. DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s lacks CAN_USE_WAYPOINTS option. Doing nothing.", commandButton->getName().str()) ); return; } switch( commandButton->getCommandType() ) { case GUI_COMMAND_SPECIAL_POWER: { if( commandButton->getSpecialPowerTemplate() ) { CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); doSpecialPowerUsingWaypoints( commandButton->getSpecialPowerTemplate(), way, commandOptions, cmdSource == CMD_FROM_SCRIPT ); return; } break; } case GUI_COMMAND_ATTACK_MOVE: case GUI_COMMAND_STOP: case GUI_COMMAND_DOZER_CONSTRUCT: case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: case GUI_COMMAND_UNIT_BUILD: case GUI_COMMAND_CANCEL_UNIT_BUILD: case GUI_COMMAND_PLAYER_UPGRADE: case GUI_COMMAND_OBJECT_UPGRADE: case GUI_COMMAND_CANCEL_UPGRADE: case GUI_COMMAND_GUARD: case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: case GUI_COMMAND_WAYPOINTS: case GUI_COMMAND_EXIT_CONTAINER: case GUI_COMMAND_EVACUATE: case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: case GUI_COMMAND_BEACON_DELETE: case GUI_COMMAND_SET_RALLY_POINT: case GUI_COMMAND_SELL: case GUI_COMMAND_FIRE_WEAPON: case GUI_COMMAND_HACK_INTERNET: case GUI_COMMAND_TOGGLE_OVERCHARGE: #ifdef ALLOW_SURRENDER case GUI_COMMAND_POW_RETURN_TO_PRISON: #endif case GUI_COMMAND_COMBATDROP: case GUI_COMMAND_SWITCH_WEAPON: case GUICOMMANDMODE_HIJACK_VEHICLE: case GUICOMMANDMODE_CONVERT_TO_CARBOMB: #ifdef ALLOW_SURRENDER case GUICOMMANDMODE_PICK_UP_PRISONER: #endif default: break; } DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); } } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void Object::clearLeechRangeModeForAllWeapons() { m_weaponSet.clearLeechRangeModeForAllWeapons(); } // ------------------------------------------------------------------------------------------------ /** Search our update modules for a production update interface and return it if one is found */ // ------------------------------------------------------------------------------------------------ ProductionUpdateInterface* Object::getProductionUpdateInterface( void ) { ProductionUpdateInterface *pui; // tell our update modules that we intend to do this special power. for( BehaviorModule** u = m_behaviors; *u; ++u ) { pui = (*u)->getProductionUpdateInterface(); if( pui ) return pui; } // end for return NULL; } // end getProductionUpdateInterface // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ DockUpdateInterface *Object::getDockUpdateInterface( void ) { DockUpdateInterface *dock = NULL; for( BehaviorModule **u = m_behaviors; *u; ++u ) { if( (dock = (*u)->getDockUpdateInterface()) != NULL ) return dock; } return NULL; } // end getDockUpdateInterface // ------------------------------------------------------------------------------------------------ // Search our special power modules for a specific one. // ------------------------------------------------------------------------------------------------ SpecialPowerModuleInterface* Object::findSpecialPowerModuleInterface( SpecialPowerType type ) const { for (BehaviorModule** m = m_behaviors; *m; ++m) { SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); if (!sp) continue; const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); if (spTemplate && spTemplate->getSpecialPowerType() == type || type == SPECIAL_INVALID ) { return sp; } } return NULL; } // ------------------------------------------------------------------------------------------------ // Search our special power modules for the first occurrence of a shortcut special. // ------------------------------------------------------------------------------------------------ SpecialPowerModuleInterface* Object::findAnyShortcutSpecialPowerModuleInterface() const { for( BehaviorModule** m = m_behaviors; *m; ++m ) { SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); if (!sp) continue; const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); if( spTemplate && spTemplate->isShortcutPower() ) { return sp; } } return NULL; } // ------------------------------------------------------------------------------------------------ /** Get spawn behavior interface from object */ // ------------------------------------------------------------------------------------------------ SpawnBehaviorInterface* Object::getSpawnBehaviorInterface() const { for (BehaviorModule** m = m_behaviors; *m; ++m) { SpawnBehaviorInterface *sbi = (*m)->getSpawnBehaviorInterface(); if( sbi ) { return sbi; } } return NULL; } // end getSpawnBehaviorInterfaceFromObject // ------------------------------------------------------------------------------------------------ ProjectileUpdateInterface* Object::getProjectileUpdateInterface() const { for (BehaviorModule** m = m_behaviors; *m; ++m) { ProjectileUpdateInterface *pui = (*m)->getProjectileUpdateInterface(); if( pui ) { return pui; } } return NULL; } // ------------------------------------------------------------------------------------------------ // Simply find the special power module that is currently allowing plotting of positions to target. // ------------------------------------------------------------------------------------------------ SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type ) const { for( BehaviorModule** u = m_behaviors; *u; ++u ) { SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); if( spInterface ) { if( spInterface->doesSpecialPowerHaveOverridableDestinationActive() ) { return spInterface; } } } // end for return NULL; } // ------------------------------------------------------------------------------------------------ // Simply find the special power module that is potentially allowed to plot positions to target. // ------------------------------------------------------------------------------------------------ SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestination( SpecialPowerType type ) const { for( BehaviorModule** u = m_behaviors; *u; ++u ) { SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); if( spInterface ) { if( spInterface->doesSpecialPowerHaveOverridableDestination() ) { return spInterface; } } } // end for return NULL; } // ------------------------------------------------------------------------------------------------ // Search our special ability updates for a specific one. // ------------------------------------------------------------------------------------------------ SpecialAbilityUpdate* Object::findSpecialAbilityUpdate( SpecialPowerType type ) const { for( BehaviorModule** u = m_behaviors; *u; ++u ) { SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); if( spInterface && spInterface->isSpecialAbility() ) { SpecialAbilityUpdate *spUpdate = (SpecialAbilityUpdate*)spInterface; if( spUpdate->getSpecialPowerType() == type ) { return spUpdate; } } } // end for return NULL; } // ------------------------------------------------------------------------------------------------ SpecialPowerCompletionDie* Object::findSpecialPowerCompletionDie() const { static NameKeyType key_SpecialPowerCompletionDie = NAMEKEY("SpecialPowerCompletionDie"); return (SpecialPowerCompletionDie*)findModule(key_SpecialPowerCompletionDie); } // ------------------------------------------------------------------------------------------------ Int Object::getNumConsecutiveShotsFiredAtTarget( const Object *victim ) const { return m_firingTracker ? m_firingTracker->getNumConsecutiveShotsAtVictim( victim ) : 0; } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Bool Object::getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const { if (m_drawable && m_drawable->getPristineBonePositions( boneName, 0, position, transform, 1 ) == 1 ) { m_drawable->convertBonePosToWorldPos( position, transform, position, transform ); return true; } else { if (position) *position = *getPosition(); if (transform) *transform = *getTransformMatrix(); return false; } } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Bool Object::getSingleLogicalBonePositionOnTurret( WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform ) const { Coord3D turretPosition; Coord3D bonePosition; if( getDrawable() == NULL || getAI() == NULL ) return FALSE; // We need to find the TurretBone's pristine position. getDrawable()->getProjectileLaunchOffset( PRIMARY_WEAPON, 1, NULL, whichTurret, &turretPosition, NULL ); // And the required bone's pristine position if( getDrawable()->getPristineBonePositions(boneName, 0, &bonePosition, NULL, 1) != 1 ) return FALSE; //Then we mojo the Logic position of the required bone like Missile firing does. Using the logic twist of the turret Real turretRotation; getAI()->getTurretRotAndPitch( whichTurret, &turretRotation, NULL ); Matrix3D boneOffset(TRUE);// This will be from the turret to the requested bone // Vector3 bonePositionVector( bonePosition.x - turretPosition.x, // bonePosition.y - turretPosition.y, // bonePosition.z - turretPosition.z ); Vector3 bonePositionVector( bonePosition.x, bonePosition.y, bonePosition.z ); boneOffset.Translate(bonePositionVector); Matrix3D turnAdjustment(TRUE);// this is the turret twist to be applied to the final answer turnAdjustment.Translate( turretPosition.x, turretPosition.y, turretPosition.z ); turnAdjustment.In_Place_Pre_Rotate_Z(turretRotation); turnAdjustment.Translate( -turretPosition.x, -turretPosition.y, -turretPosition.z ); Matrix3D boneLogicTransform; boneLogicTransform.mul( turnAdjustment, boneOffset ); Matrix3D worldTransform; convertBonePosToWorldPos(NULL, &boneLogicTransform, NULL, &worldTransform); Vector3 tmp = worldTransform.Get_Translation(); Coord3D worldPos; worldPos.x = tmp.X; worldPos.y = tmp.Y; worldPos.z = tmp.Z; if( position ) *position = worldPos; if( transform ) *transform = worldTransform; return TRUE; } // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ Int Object::getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, Coord3D* positions, Matrix3D* transforms, Bool convertToWorld ) const { Int count; if (m_drawable && (count = m_drawable->getPristineBonePositions( boneNamePrefix, 1, positions, transforms, maxBones )) > 0 ) { if( convertToWorld ) { for (Int i = 0; i < count; ++i) m_drawable->convertBonePosToWorldPos( positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL, positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL ); } return count; } else { return 0; } } //============================================================================= const AsciiString& Object::getCommandSetString() const { if (m_commandSetStringOverride.isNotEmpty()) return m_commandSetStringOverride; return getTemplate()->friend_getCommandSetString(); } //============================================================================= Bool Object::canProduceUpgrade( const UpgradeTemplate *upgrade ) { // We need to have the button to make the upgrade. CommandSets are a weird Logic/Client hybrid. const CommandSet *set = TheControlBar->findCommandSet(getCommandSetString()); for( Int buttonIndex = 0; buttonIndex < MAX_COMMANDS_PER_SET; buttonIndex++ ) { const CommandButton *button = set->getCommandButton(buttonIndex); if( button && button->getUpgradeTemplate() && (button->getUpgradeTemplate() == upgrade) ) return TRUE; // getUpgradeTemplate only returns something if it is actually an upgrade } return FALSE;// Cheatin' punk. } //============================================================================= // Object::defect, and related methods = //============================================================================= void Object::defect( Team* newTeam, UnsignedInt detectionTime ) { if ( isContained() ) //@todo (KRIS?) make contained units unselectable, until then... lorenzen { return; } Player *player = getControllingPlayer(); if ( !player ) return; Team* myTeam = player->getDefaultTeam(); if ( myTeam == newTeam ) // can't defect from my own team, that would be silly return; // things that are under construction, or sold, cannot defect. if (testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) || testStatus(OBJECT_STATUS_SOLD)) { return; } // Before switch //////////////////////////////////////// //Design says: ProductionUpdateInterface *production = getProductionUpdateInterface(); if ( production ) { production->cancelAndRefundAllProduction(); } // pop it up on the radar, so as to warn those who care // do this first, since after setTeam() the infiltrator // becomes the controllingplayer, not me // But don't do this is if the new team is not a real team. "'Enemy' infiltration" wouldn't make // sense, and we are probably just reverting a cave or something. if( friend_getRadarData() && newTeam->getControllingPlayer()->isPlayableSide() && myTeam->getControllingPlayer()->isPlayableSide()) { TheRadar->tryInfiltrationEvent( this ); } friend_setUndetectedDefector( detectionTime > 0 ); if (m_defectionHelper) m_defectionHelper->startDefectionTimer(detectionTime); // Switch //////////////////////////////////////// setTeam( newTeam ); // After switch //////////////////////////////////////// AIUpdateInterface *ai = getAI(); handlePartitionCellMaintenance();// to clear the shoud for my new master if ( ai ) { ai->aiIdle( CMD_FROM_AI ); } // Play our sound indicating we've been defected. (weird verbage, but true.) AudioEventRTS voiceDefect = *getTemplate()->getVoiceDefect(); voiceDefect.setObjectID(getID()); TheAudio->addAudioEvent(&voiceDefect); //make the new recruit the only selected thing, awaiting new command to move, attack, etc... Drawable *dr = getDrawable(); if (dr) { dr->flashAsSelected(); //This is the first of several flashes which get cue'd by doDefectorUpdateStuff() AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound; defectorTimerSound.setObjectID( getID() ); TheAudio->addAudioEvent(&defectorTimerSound); } ContainModuleInterface *ct = getContain(); if( ct && ct->isKickOutOnCapture() ) { // Caves really really don't want to do this. ct->removeAllContained( TRUE ); } // if it has parking places, defect anything parked there. for (BehaviorModule** i = getBehaviorModules(); *i; ++i) { ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); if (pp) { pp->defectAllParkedUnits(newTeam, detectionTime); break; } } // defect any mines that are owned by this structure, right now. // unfortunately, structures don't keep list of mines they own, so we must do // this the hard way :-( [fortunately, this doens't happen very often, so this // is probably an acceptable, if icky, solution.] (srj) for (Object* mine = TheGameLogic->getFirstObject(); mine; mine = mine->getNextObject()) { if (mine->isKindOf(KINDOF_MINE)) { if (mine->getProducerID() == this->getID()) { mine->setTeam(newTeam); } } } } //============================================================================= // Object::goInvulnerable //============================================================================= void Object::goInvulnerable( UnsignedInt time ) { const Bool WITHOUT_DEFECTOR_FX = FALSE; friend_setUndetectedDefector( time > 0 ); if (m_defectionHelper) m_defectionHelper->startDefectionTimer(time, WITHOUT_DEFECTOR_FX); } // ------------------------------------------------------------------------------------------------ /** Return the radar priority for this object type */ // ------------------------------------------------------------------------------------------------ RadarPriorityType Object::getRadarPriority( void ) const { RadarPriorityType priority = RADAR_PRIORITY_INVALID; // first, get the priority at the thing template level priority = getTemplate()->getDefaultRadarPriority(); // // there are some objects that we want to show up on the radar when they have // certain properties ... here we will check for those properties unless the INI // setting of "not on radar" has been manually entered which explicitly forbids an // object from being on the radar ... by default objects get an "invalid" priority // on the radar and this means that we are free to decide one here if we want // if( priority == RADAR_PRIORITY_INVALID ) { // objects that are "garrisonable" show up on the radar ContainModuleInterface *cmi = getContain(); if( cmi && cmi->isGarrisonable() ) priority = RADAR_PRIORITY_STRUCTURE; // objects that are "capturable" show up on the radar if( isKindOf( KINDOF_CAPTURABLE ) ) priority = RADAR_PRIORITY_STRUCTURE; } // end if // Carbombs will show up as units regardless of their default priority if ( testStatus( OBJECT_STATUS_IS_CARBOMB ) ) priority = RADAR_PRIORITY_UNIT; // return the priority we're going to use return priority; } // end getRadarPriority // ------------------------------------------------------------------------------------------------ AIGroup *Object::getGroup(void) { return m_group; } //------------------------------------------------------------------------------------------------- void Object::enterGroup( AIGroup *group ) { // DEBUG_LOG(("***AIGROUP %x involved in enterGroup on %x\n", group, this)); // if we are in another group, remove ourselves from it first leaveGroup(); m_group = group; } //------------------------------------------------------------------------------------------------- void Object::leaveGroup( void ) { // DEBUG_LOG(("***AIGROUP %x involved in leaveGroup on %x\n", m_group, this)); // if we are in a group, remove ourselves from it if (m_group) { // to avoid recursion, set m_group to NULL before removing AIGroup *group = m_group; m_group = NULL; group->remove( this ); } } //------------------------------------------------------------------------------------------------- Real Object::getCarrierDeckHeight() const { Object *producer = TheGameLogic->findObjectByID( getProducerID() ); if( producer ) { // Find a parking place behavior. for( BehaviorModule** i = producer->getBehaviorModules(); *i; ++i ) { ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); if( pp ) { return pp->getLandingDeckHeightOffset(); } } } return 0.0f; } //------------------------------------------------------------------------------------------------- CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() { for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) { CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); if( cbi ) { return cbi; } } return NULL; } //------------------------------------------------------------------------------------------------- const CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() const { for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) { const CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); if( cbi ) { return cbi; } } return NULL; } //------------------------------------------------------------------------------------------------- Bool Object::hasCountermeasures() const { const CountermeasuresBehaviorInterface* cbi = getCountermeasuresBehaviorInterface(); if( cbi && cbi->isActive() ) { return TRUE; } return FALSE; } //------------------------------------------------------------------------------------------------- void Object::reportMissileForCountermeasures( Object *missile ) { for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) { CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); if( cbi ) { cbi->reportMissileForCountermeasures( missile ); } } } //------------------------------------------------------------------------------------------------- ObjectID Object::calculateCountermeasureToDivertTo( const Object& victim ) { AIUpdateInterface *ai = getAI(); if( ai ) { for( BehaviorModule** i = victim.getBehaviorModules(); *i; ++i ) { CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); if( cbi ) { ObjectID decoyID = cbi->calculateCountermeasureToDivertTo( victim ); return decoyID; } } } return INVALID_ID; }