| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579 |
- /*
- ** Command & Conquer Generals(tm)
- ** Copyright 2025 Electronic Arts Inc.
- **
- ** This program is free software: you can redistribute it and/or modify
- ** it under the terms of the GNU General Public License as published by
- ** the Free Software Foundation, either version 3 of the License, or
- ** (at your option) any later version.
- **
- ** This program is distributed in the hope that it will be useful,
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ** GNU General Public License for more details.
- **
- ** You should have received a copy of the GNU General Public License
- ** along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- ////////////////////////////////////////////////////////////////////////////////
- // //
- // (c) 2001-2003 Electronic Arts Inc. //
- // //
- ////////////////////////////////////////////////////////////////////////////////
- // FILE: BuildAssistant.cpp ///////////////////////////////////////////////////////////////////////
- // Author: Colin Day, February 2002
- // Desc: Singleton class to encapsulate some of the more common functions or rules
- // that apply to building structures and units
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
- #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
- #include "Common/BuildAssistant.h"
- #include "Common/GlobalData.h"
- #include "Common/Player.h"
- #include "Common/ThingTemplate.h"
- #include "Common/GameAudio.h"
- #include "Common/ThingFactory.h"
- #include "Common/Team.h"
- #include "Common/TerrainTypes.h"
- #include "Common/MapObject.h"
- #include "GameClient/ControlBar.h"
- #include "GameClient/Drawable.h"
- #include "GameClient/InGameUI.h"
- #include "GameClient/TerrainVisual.h"
- #include "GameLogic/AI.h"
- #include "GameLogic/PartitionManager.h"
- #include "GameLogic/TerrainLogic.h"
- #include "GameLogic/AIPathfind.h"
- #include "GameLogic/Module/AIUpdate.h"
- #include "GameLogic/Module/BehaviorModule.h"
- #include "GameLogic/Module/ContainModule.h"
- #include "GameLogic/Module/CreateModule.h"
- #include "GameLogic/Module/ProductionUpdate.h"
- #include "GameLogic/Module/ContainModule.h"
- #include "GameLogic/Module/ParkingPlaceBehavior.h"
- // PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
- BuildAssistant *TheBuildAssistant = NULL;
- #ifdef _INTERNAL
- // for occasional debugging...
- //#pragma optimize("", off)
- //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
- #endif
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- // ------------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------------------------------
- ObjectSellInfo::ObjectSellInfo( void )
- {
- m_id = INVALID_ID;
- m_sellFrame = 0;
-
- } // end ObjectSellInfo
- // ------------------------------------------------------------------------------------------------
- // ------------------------------------------------------------------------------------------------
- ObjectSellInfo::~ObjectSellInfo( void )
- {
- } // end ~ObjectSellInfo
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////////////////////////////////////////////////////////////////////////
- //-------------------------------------------------------------------------------------------------
- /** Is this object a dozer */
- //-------------------------------------------------------------------------------------------------
- static Bool isDozer( Object *obj )
- {
- // sanity
- if( obj == NULL )
- return FALSE;
- if( obj->isKindOf(KINDOF_DOZER))
- return TRUE;
- return FALSE;
- } // end isDozer
- // PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- BuildAssistant::BuildAssistant( void )
- {
- m_buildPositions = NULL;
- m_buildPositionSize = 0;
- m_sellList.clear();
- } // end BuildAssistant
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- BuildAssistant::~BuildAssistant( void )
- {
- // delete build position array if we used it
- if( m_buildPositions )
- {
-
- delete [] m_buildPositions;
- m_buildPositions = NULL;
- m_buildPositionSize = 0;
- } // end if
- } // end ~BuildAssistant
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- void BuildAssistant::init( void )
- {
- //
- // allocate our array of positions that we use to assist ourselves when constructing
- // a tiled array of locations to build things like walls
- //
- m_buildPositionSize = TheGlobalData->m_maxLineBuildObjects;
- m_buildPositions = NEW Coord3D[ m_buildPositionSize ];
- } // end init
- //-------------------------------------------------------------------------------------------------
- //-------------------------------------------------------------------------------------------------
- void BuildAssistant::reset( void )
- {
- // clear all our data from the sell list
- ObjectSellInfo *sellInfo;
- ObjectSellListIterator it;
- for( it = m_sellList.begin(); it != m_sellList.end(); ++it )
- {
- // get sell info
- sellInfo = (*it);
- // delete our data and erase this entry from the list
- sellInfo->deleteInstance();
- } // end for
- // clear the sell list
- m_sellList.clear();
- } // end reset
- static const Real FRAMES_TO_ALLOW_SCAFFOLD = LOGICFRAMES_PER_SECOND * 1.5f;
- static const Real TOTAL_FRAMES_TO_SELL_OBJECT = LOGICFRAMES_PER_SECOND * 3.0f;
- //-------------------------------------------------------------------------------------------------
- /** Update phase for the build assistant */
- //-------------------------------------------------------------------------------------------------
- void BuildAssistant::update( void )
- {
- ObjectSellInfo *sellInfo;
- Object *obj;
- // remove any objects from the sell list who's "sell time" is up
- ObjectSellListIterator it, thisIterator;
- for( it = m_sellList.begin(); it != m_sellList.end(); /*empty*/ )
- {
- // get this object info
- sellInfo = (*it);
- // increment the iterator to the next as we may remove it
- thisIterator = it;
- ++it;
- // find the object
- obj = TheGameLogic->findObjectByID( sellInfo->m_id );
- //
- // if object is not found, remove it from the list immediately ... this is valid as
- // the object maybe was destroyed by other means during the sell process
- //
- if( obj == NULL )
- {
- sellInfo->deleteInstance();
- m_sellList.erase( thisIterator );
- continue;
- } // end if
- // decrement the construction percent
- if( TheGameLogic->getFrame() - sellInfo->m_sellFrame >= FRAMES_TO_ALLOW_SCAFFOLD )
- {
- Real previousConstructionPercent = obj->getConstructionPercent();
- // do the constructoin
- obj->setConstructionPercent( previousConstructionPercent - (100.0f / TOTAL_FRAMES_TO_SELL_OBJECT) );
- //
- // did we cross the threshold from positive to negative ... if so, we will clear
- // visual construction indicators
- //
- if( previousConstructionPercent > 0.0f && obj->getConstructionPercent() <= 0.0f )
- {
- obj->clearAndSetModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_PARTIALLY_CONSTRUCTED,
- MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED ),
- MAKE_MODELCONDITION_MASK( MODELCONDITION_SOLD ) );
- //
- // set the animation durations so that the regular build up loop animations can be
- // done a bit faster for selling
- //
- Drawable *draw = obj->getDrawable();
- if( draw )
- draw->setAnimationLoopDuration( TOTAL_FRAMES_TO_SELL_OBJECT / 2 );
- } // end if
- } // end if
- //
- // after we've reached zero ... the object has "sunk" back down into the ground ... but
- // we need to keep the object around for a little while longer so that the scaffold
- // animation can finish up and sink itself back into the ground
- //
- if( obj->getConstructionPercent() <= -50.0f )
- {
- // refund the money to the controlling player
- Player *player = obj->getControllingPlayer();
- if( player )
- {
- UnsignedInt sellValue;
- // 0 is the init, and means you have no override. We would be marked unsellable if someone wanted us to not sell
- if( obj->getTemplate()->getRefundValue() != 0 )
- sellValue = obj->getTemplate()->getRefundValue();
- else
- sellValue = REAL_TO_UNSIGNEDINT( obj->getTemplate()->calcCostToBuild( player ) *
- TheGlobalData->m_sellPercentage );
- player->getMoney()->deposit( sellValue );
- // this money shouldn't be scored since it wasn't really "earned."
- // player->getScoreKeeper()->addMoneyEarned( sellValue );
- } // end if
- // cancel any of the production items and refund to the controlling player
- ProductionUpdateInterface *pui = obj->getProductionUpdateInterface();
- if( pui )
- pui->cancelAndRefundAllProduction();
- // for debugging
- // UnicodeString msg;
- // msg.format( L"'%S' has been sold\n", obj->getTemplate()->getName().str() );
- // TheInGameUI->message( msg );
- // destroy the object
- TheGameLogic->destroyObject( obj );
- // remove this object from the sell list
- sellInfo->deleteInstance();
- m_sellList.erase( thisIterator );
- } // end if
- } // end for
- } // end update
- //-------------------------------------------------------------------------------------------------
- /** Xfer the sell list. */
- //-------------------------------------------------------------------------------------------------
- void BuildAssistant::xferTheSellList( Xfer *xfer )
- {
- ObjectSellInfo *sellInfo;
- Int count=0;
- ObjectSellListIterator it, thisIterator;
- for( it = m_sellList.begin(); it != m_sellList.end(); ++it ) {
- count++;
- }
- xfer->xferInt(&count);
- if (xfer->getXferMode() == XFER_LOAD) {
- m_sellList.clear();
- Int i;
- for (i=0; i<count; i++) {
- // add this object to the list of objects being sold
- sellInfo = newInstance(ObjectSellInfo);
- xfer->xferObjectID(&sellInfo->m_id);
- xfer->xferUnsignedInt(&sellInfo->m_sellFrame);
- m_sellList.push_back( sellInfo );
- }
- } else {
- for( it = m_sellList.begin(); it != m_sellList.end(); ++it ) {
- // get this object info
- sellInfo = (*it);
- xfer->xferObjectID(&sellInfo->m_id);
- xfer->xferUnsignedInt(&sellInfo->m_sellFrame);
- count--;
- }
- DEBUG_ASSERTCRASH(count==0, ("Inconsistent list size counts."));
- }
- } // end xferTheSellList
- //-------------------------------------------------------------------------------------------------
- /** Nice little method to wrap up creating an object from a build */
- //-------------------------------------------------------------------------------------------------
- Object *BuildAssistant::buildObjectNow( Object *constructorObject, const ThingTemplate *what,
- const Coord3D *pos, Real angle, Player *owningPlayer )
- {
- // sanity
- if( what == NULL || pos == NULL )
- return NULL;
- if( owningPlayer == NULL )
- return NULL;// Invalid pointer. Won't happen.
- // sanity
- if( constructorObject )
- {
- DEBUG_ASSERTCRASH( constructorObject->getControllingPlayer() == owningPlayer,
- ("buildObjectNow: Constructor object player is not the same as the controlling player passed in\n") );
- } // end if
- // Need to validate that we can make this in case someone fakes their CommandSet
- // A Null constructorObject is used by the script engine to cheat, so let it slide
- if( constructorObject && !isPossibleToMakeUnit(constructorObject, what) )
- return NULL;
- // clear out any objects from the building area that are "auto-clearable" when building
- clearRemovableForConstruction( what, pos, angle );
- if ( moveObjectsForConstruction( what, pos, angle, owningPlayer ) == FALSE )
- {
- // totally bogus. We tried to move our units out of the way, but they wouldn't.
- // Chode-boys.
- if (owningPlayer->getPlayerType()==PLAYER_HUMAN) {
- return NULL; // ai gets to cheat. jba.
- }
- }
- // do the build
- if( isDozer( constructorObject ) && isLineBuildTemplate( what ) == FALSE )
- {
- AIUpdateInterface *ai = constructorObject->getAIUpdateInterface();
- if( ai )
- {
- ai->aiIdle(CMD_FROM_AI); // stop any current behavior.
- return ai->construct( what, pos, angle, owningPlayer, FALSE );
- }
- return NULL;
- } // end else if
- else
- {
- // create the new object. We need to construct it with UnderConstruction set, since we are going to insta build it,
- // but we don't want to send double construction type events like power creation. onStructureConstructionComplete
- // is called below, so we need to simulate the proper object creation flow from the start. Be like Dozer.
- ObjectStatusBits startingStatus = OBJECT_STATUS_NONE;
- if( what->isKindOf( KINDOF_STRUCTURE ) )
- startingStatus = OBJECT_STATUS_UNDER_CONSTRUCTION;
- Object *obj = TheThingFactory->newObject( what, owningPlayer->getDefaultTeam(), startingStatus );
- obj->setProducer(constructorObject);
- // place on terrain surface
- Coord3D groundPos;
- groundPos.x = pos->x;
- groundPos.y = pos->y;
- groundPos.z = TheTerrainLogic->getGroundHeight( groundPos.x, groundPos.y );
- obj->setPosition( &groundPos );
- obj->setOrientation( angle );
- TheAI->pathfinder()->addObjectToPathfindMap( obj );
- // notify the player that this thing has come into existence
- if( obj->isKindOf( KINDOF_STRUCTURE ) )
- {
- //
- // this is a cheat since this method is currently used by the solo AI to make
- // buildings out of thin air
- //
- owningPlayer->onStructureCreated( constructorObject, obj );
- owningPlayer->onStructureConstructionComplete( constructorObject, obj, FALSE );
- } // end if
- else
- {
- owningPlayer->onUnitCreated( constructorObject, obj );
- //Call the voice created for the 1st object -- because it's possible to create multiple objects like redguards!
- AudioEventRTS sound = *obj->getTemplate()->getPerUnitSound( "VoiceCreated" );
- sound.setObjectID( obj->getID() );
- TheAudio->addAudioEvent( &sound );
- }
- // Now onCreates were called at the constructor. This magically created
- // thing needs to be considered as Built for Game specific stuff.
- for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m)
- {
- CreateModuleInterface* create = (*m)->getCreate();
- if (!create)
- continue;
- create->onBuildComplete();
- }
-
- // Creation is another valid and essential time to call this. This building now Looks.
- obj->handlePartitionCellMaintenance();
- return obj;
- } // end else
- return NULL;
- } // end buildObjectNow
- //-------------------------------------------------------------------------------------------------
- /** This method will create a line of objects end to end along the line defined in 3D
- * space from start to end. This is especially useful in building walls */
- //-------------------------------------------------------------------------------------------------
- void BuildAssistant::buildObjectLineNow( Object *constructorObject, const ThingTemplate *what,
- const Coord3D *start, const Coord3D *end, Real angle,
- Player *owningPlayer )
- {
- TileBuildInfo *tileBuildInfo;
- // sanity
- if( what == NULL || start == NULL || end == NULL )
- return;
- // how big are each of our objects
- Real objectSize = what->getTemplateGeometryInfo().getMajorRadius() * 2.0f;
-
- // what is our max tiling length we can make
- Int maxObjects = TheGlobalData->m_maxLineBuildObjects;
- // build an array of locations that we want to build from start to end
- tileBuildInfo = buildTiledLocations( what, angle, start, end,
- objectSize, maxObjects, constructorObject );
- // create an object at each position
- for( Int i = 0; i < tileBuildInfo->tilesUsed; i++ )
- buildObjectNow( constructorObject, what, &tileBuildInfo->positions[ i ], angle, owningPlayer );
- } // end buildObjectLineNow
- //-------------------------------------------------------------------------------------------------
- /** This structure is passed along to the checkSampleBuildLocation while iterating the
- * footprint of an area in order to determine if the entire location is a legal place to
- * build */
- //-------------------------------------------------------------------------------------------------
- struct SampleBuildData
- {
- Region3D mapRegion;
- Bool terrainRestricted; ///< one of the terrain tiles prevents building
- Real hiZ; ///< highest sample point used
- Real loZ; ///< lowest sample point used
- };
- //-------------------------------------------------------------------------------------------------
- /** This will check the build conditions at the specified sample location point */
- //-------------------------------------------------------------------------------------------------
- static void checkSampleBuildLocation( const Coord3D *samplePoint, void *userData )
- {
- TerrainType *terrain;
- SampleBuildData *sampleData = (SampleBuildData *)userData;
- // get the terrain tile here
- terrain = TheTerrainVisual->getTerrainTile( samplePoint->x, samplePoint->y );
- if( terrain )
- {
- // check for the restricts building flag
- if( terrain->getRestrictConstruction() )
- sampleData->terrainRestricted = TRUE;
- } // end if
- Int cellX = REAL_TO_INT_FLOOR( samplePoint->x / PATHFIND_CELL_SIZE );
- Int cellY = REAL_TO_INT_FLOOR( samplePoint->y / PATHFIND_CELL_SIZE );
-
- PathfindCell* cell = TheAI->pathfinder()->getCell( LAYER_GROUND, cellX, cellY );
- if (!cell) {
- sampleData->terrainRestricted = TRUE;
- } else {
- enum PathfindCell::CellType type = cell->getType();
- if ( (type == PathfindCell::CELL_WATER) || (type == PathfindCell::CELL_CLIFF) ||
- (type == PathfindCell::CELL_IMPASSABLE)) {
- sampleData->terrainRestricted = true;
- }
- }
- //
- // record the highest and lowest Z points from all the samples and do not allow
- // building when the difference between them is too great
- //
- if( samplePoint->z < sampleData->loZ )
- sampleData->loZ = samplePoint->z;
- if( samplePoint->z > sampleData->hiZ )
- sampleData->hiZ = samplePoint->z;
- // too close to edge of map?
- if (TheGlobalData->m_MinDistFromEdgeOfMapForBuild > 0.0f)
- {
- if (samplePoint->x < sampleData->mapRegion.lo.x + TheGlobalData->m_MinDistFromEdgeOfMapForBuild
- || samplePoint->x > sampleData->mapRegion.hi.x - TheGlobalData->m_MinDistFromEdgeOfMapForBuild
- || samplePoint->y < sampleData->mapRegion.lo.y + TheGlobalData->m_MinDistFromEdgeOfMapForBuild
- || samplePoint->y > sampleData->mapRegion.hi.y - TheGlobalData->m_MinDistFromEdgeOfMapForBuild)
- {
- sampleData->terrainRestricted = TRUE;
- }
- }
- } // end checkSampleBuildLocation
- //-------------------------------------------------------------------------------------------------
- /** This function will call the user callback at each "sample point" across the footprint
- * of where this object would be in the world. The smaller the sample resolution param is
- * the more samples will be taken across the footprint area */
- //-------------------------------------------------------------------------------------------------
- void BuildAssistant::iterateFootprint( const ThingTemplate *build,
- Real buildOrientation,
- const Coord3D *worldPos,
- Real sampleResolution,
- IterateFootprintFunc func,
- void *funcUserData )
- {
- // sanity
- if( build == NULL || worldPos == NULL || func == NULL )
- return;
- //
- // create a transform matrix for the object position at the specified angle, we will
- // iterate the object footprints in "local object extent space" and transform those
- // points using this matrix into the world coords for the object at the real
- // location and specified angle
- //
- Matrix3D transform;
- transform.Make_Identity();
- transform.Adjust_Translation( Vector3( worldPos->x, worldPos->y, worldPos->z ) );
- transform.Rotate_Z( buildOrientation );
- // get the bounding footprint rectangle for the geometry we're looking at
- Real halfFootprintHeight,
- halfFootprintWidth;
- if( build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_BOX )
- {
- halfFootprintHeight = build->getTemplateGeometryInfo().getMinorRadius();
- halfFootprintWidth = build->getTemplateGeometryInfo().getMajorRadius();
- } // end if
- else if( build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_SPHERE ||
- build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_CYLINDER )
- {
- halfFootprintHeight = build->getTemplateGeometryInfo().getBoundingCircleRadius();
- halfFootprintWidth = build->getTemplateGeometryInfo().getBoundingCircleRadius();
- } // end else if
- else
- {
- DEBUG_ASSERTCRASH( 0, ("iterateFootprint: Undefined geometry '%d' for '%s'\n",
- build->getTemplateGeometryInfo().getGeomType(), build->getName().str()) );
- return;
- } // end else
- //
- // start at a corner of the extent ... box geometries have a major radius down
- // the X axis with the minor radius on the Y axis. Note that we are allowing
- // the sample process to go an "extra" sample resolution outside of the
- // real extent here. This is so that we can be sure to sample all of the
- // footprint area, if our sample point is outside of the actual extent footprint
- // area because of this we snap it back to sample on the exact edge
- //
- Real x, y;
- Vector3 v;
- for( y = -halfFootprintHeight;
- y < halfFootprintHeight + sampleResolution;
- y += sampleResolution )
- {
- // snap it to the actual extent since we can go over by one sample resolution
- if( y > halfFootprintHeight )
- y = halfFootprintHeight;
- for( x = -halfFootprintWidth;
- x < halfFootprintWidth + sampleResolution;
- x += sampleResolution )
- {
- // snap it to the actual extent since we can go over by one sample resolution
- if( x > halfFootprintWidth )
- x = halfFootprintWidth;
- // transform to world
- v.Set( x, y, TheTerrainLogic->getGroundHeight( x, y ) );
- transform.Transform_Vector( transform, v, &v );
- // for circular geometries we must actually be within the circle
- if( build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_SPHERE ||
- build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_CYLINDER )
- {
- Coord2D vector;
- vector.x = v.X - worldPos->x;
- vector.y = v.Y - worldPos->y;
- if( vector.length() > halfFootprintWidth ) // could be height too, radius is all the same for circles
- continue; // ignore this point
- } // end if
-
- // call the user callback
- Coord3D pos;
- pos.x = v.X;
- pos.y = v.Y;
- pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y );
- func( &pos, funcUserData );
- } // end for x
- } // end for y
- } // end iterateFootprint
- //-------------------------------------------------------------------------------------------------
- /** Check for objects preventing building at this location. */
- //-------------------------------------------------------------------------------------------------
- Bool BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos,
- const ThingTemplate *build,
- Real angle,
- Object *builderObject,
- UnsignedInt options,
- Player *thePlayer)
- {
- ObjectIterator *iter =
- ThePartitionManager->iteratePotentialCollisions( worldPos,
- build->getTemplateGeometryInfo(),
- angle );
- Object *them;
- Bool onlyCheckEnemies = (options == NO_ENEMY_OBJECT_OVERLAP);
- MemoryPoolObjectHolder hold(iter);
- for( them = iter->first(); them; them = iter->next() )
- {
- // ignore any kind of class of objects that we will "remove" for building
- if( isRemovableForConstruction( them ) == TRUE )
- continue;
- // ignore land mines, cluster mines and demo traps, since you can build on them
- // doing so will damage them during construction, by the way
- if (them->isKindOf( KINDOF_MINE ))
- continue;
- // same story for inert things: ie, radiation fields, pings, and projectile streams (!)...
- if (them->isKindOf(KINDOF_INERT))
- continue;
- // an immobile object may obstruct our building depending on flags.
- if( them->isKindOf( KINDOF_IMMOBILE ) ) {
- if (onlyCheckEnemies && builderObject && builderObject->getRelationship( them ) != ENEMIES ) {
- continue;
- }
- TheTerrainVisual->addFactionBib(them, true);
- return false;
- }
- //
- // if this is an enemy object of the builder object (and therefore the thing
- // that will be constructed) we can't build here
- //
- if( builderObject && builderObject->getRelationship( them ) == ENEMIES ) {
- TheTerrainVisual->addFactionBib(them, true);
- return false;
- }
- } // end for, them
- if (onlyCheckEnemies) {
- return true;
- }
- // Check for overlapping exit areas.
- Real range = 2*(build->getTemplateGeometryInfo().getMajorRadius()+build->getTemplateGeometryInfo().getMinorRadius());
- PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_STRUCTURE), KINDOFMASK_NONE);
- PartitionFilter *filters[] = { &f1, NULL };
- ObjectIterator *iter2 = ThePartitionManager->iterateObjectsInRange(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
- MemoryPoolObjectHolder hold2(iter2);
- Real myFactoryExitWidth = build->getFactoryExitWidth();
- Real myExtraWidth = build->getFactoryExtraBibWidth();
- if (thePlayer && thePlayer->isSkirmishAIPlayer()) {
- // Skirmish ai adds a little extra around the edges so it doesn't build itself into a corner.
- if (myExtraWidth < 3*PATHFIND_CELL_SIZE_F) {
- myExtraWidth = 3*PATHFIND_CELL_SIZE_F;
- myFactoryExitWidth -= myExtraWidth;
- if (myFactoryExitWidth<0) myFactoryExitWidth = 0;
- }
- }
- Bool checkMyExit = false;
- Coord3D myExitPos;
- GeometryInfo myBounds = build->getTemplateGeometryInfo();
- myBounds.setMajorRadius(myBounds.getMajorRadius()+myExtraWidth);
- if (myBounds.getGeomType() != GEOMETRY_BOX) {
- myBounds.set(GEOMETRY_BOX, false, 40, myBounds.getMajorRadius(), myBounds.getMajorRadius());
- } else {
- myBounds.setMinorRadius(myBounds.getMinorRadius()+myExtraWidth);
- }
- GeometryInfo myGeom = build->getTemplateGeometryInfo();
- if (myGeom.getGeomType() != GEOMETRY_BOX) {
- myGeom.setMinorRadius(myGeom.getMajorRadius());
- }
- myGeom.setMajorRadius(myFactoryExitWidth/2.0f);
- if (myFactoryExitWidth>0) {
- myExitPos = *worldPos;
- checkMyExit = true;
- Real c = (Real)cos(angle);
- Real s = (Real)sin(angle);
- Real offset = build->getTemplateGeometryInfo().getMajorRadius() + myFactoryExitWidth/2.0f;
- myExitPos.x += c*offset;
- myExitPos.y += s*offset;
- }
- for( them = iter2->first(); them; them = iter2->next() )
- {
- // ignore any kind of class of objects that we will "remove" for building
- if( isRemovableForConstruction( them ) == TRUE )
- continue;
- Real themFactoryExitWidth = them->getTemplate()->getFactoryExitWidth();
- Real hisExtraWidth = them->getTemplate()->getFactoryExtraBibWidth();
- Bool checkHisExit = false;
- Coord3D hisExitPos;
- GeometryInfo hisBounds = them->getGeometryInfo();
- hisBounds.setMajorRadius(hisBounds.getMajorRadius()+hisExtraWidth);
- if (hisBounds.getGeomType() != GEOMETRY_BOX) {
- hisBounds.set(GEOMETRY_BOX, false, 40, hisBounds.getMajorRadius(), hisBounds.getMajorRadius());
- } else {
- hisBounds.setMinorRadius(hisBounds.getMinorRadius()+myExtraWidth);
- }
- GeometryInfo hisGeom = them->getGeometryInfo();
- hisGeom.setMajorRadius(themFactoryExitWidth/2.0f);
- if (hisGeom.getGeomType() != GEOMETRY_BOX) {
- hisGeom.setMinorRadius(them->getGeometryInfo().getMajorRadius());
- }
- if (themFactoryExitWidth>0) {
- hisExitPos = *them->getPosition();
- checkHisExit = true;
- Real c = (Real)cos(them->getOrientation());
- Real s = (Real)sin(them->getOrientation());
- Real offset = them->getGeometryInfo().getMajorRadius() + themFactoryExitWidth/2.0f;
- hisExitPos.x += c*offset;
- hisExitPos.y += s*offset;
- }
- if (ThePartitionManager->geomCollidesWithGeom(them->getPosition(), hisBounds, them->getOrientation(),
- worldPos, myBounds, angle)) {
- TheTerrainVisual->addFactionBib(them, true);
- return false;
- }
- if (!checkMyExit && !checkHisExit && !hisExtraWidth && !myExtraWidth)
- {
- continue; // neither has extra exit space.
- }
- // an immobile object will obstruct our building no matter what team it's on
- if ( them->isKindOf( KINDOF_IMMOBILE ) ) {
- /* Check for overlap of my exit rectangle to his geom info. */
- if (checkMyExit && ThePartitionManager->geomCollidesWithGeom(them->getPosition(), hisBounds, them->getOrientation(),
- &myExitPos, myGeom, angle)) {
- TheTerrainVisual->addFactionBib(them, true);
- return false;
- }
- // Check for overlap of his exit rectangle with my geom info
- if (checkHisExit && ThePartitionManager->geomCollidesWithGeom(&hisExitPos, hisGeom, them->getOrientation(),
- worldPos, myBounds, angle)) {
- TheTerrainVisual->addFactionBib(them, true);
- return false;
- }
- // Check both exit rectangles together.
- if (checkMyExit&&checkHisExit&&ThePartitionManager->geomCollidesWithGeom(&hisExitPos, hisGeom, them->getOrientation(),
- &myExitPos, myGeom, angle)) {
- TheTerrainVisual->addFactionBib(them, true);
- return false;
- }
- }
- } // end for, them
- return true;
- }
- //-------------------------------------------------------------------------------------------------
- /** Query if we can build at this location. Note that 'build' may be null and is NOT required
- * to be valid to know if a location is legal to build at. 'builderObject' is used
- * for queries that require a pathfind check and should be NULL if not required */
- //-------------------------------------------------------------------------------------------------
- LegalBuildCode BuildAssistant::isLocationLegalToBuild( const Coord3D *worldPos,
- const ThingTemplate *build,
- Real angle,
- UnsignedInt options,
- Object *builderObject,
- Player *player)
- {
- /* You just can't never build off the map, regardless of options. jba. */
- Region3D mapExtent;
- TheTerrainLogic->getMaximumPathfindExtent(&mapExtent);
- if (!mapExtent.isInRegionNoZ(worldPos)) {
- return LBC_RESTRICTED_TERRAIN;
- }
- // check shroud level
- // This should be the first check, since returning other errors for shrouded areas could be used to game the system
- if( BitTest( options, SHROUD_REVEALED ) )
- {
- {
- Int x, y;
- ThePartitionManager->worldToCell(worldPos->x, worldPos->y, &x, &y);
- Int playerIndex = -1;
- if (builderObject && builderObject->getControllingPlayer())
- playerIndex = builderObject->getControllingPlayer()->getPlayerIndex();
- DEBUG_ASSERTCRASH(playerIndex >= 0, ("isLocationLegalToBuild() needs a builderObject with a team to check for shroud"));
- if( ThePartitionManager->getShroudStatusForPlayer(playerIndex, x, y) != CELLSHROUD_CLEAR )
- {
- return LBC_SHROUD;
- }
- }
- }
- //
- // if NO_OBJECT_OVERLAP is set, we are not allowed to construct 'build' if it would overlap
- // any immobile objects, or an enemy object. Friendly objects should politely
- // "move out of the way" when you build something where they're standing
- //
- if( BitTest( options, NO_OBJECT_OVERLAP ) )
- {
- if (!isLocationClearOfObjects(worldPos, build, angle, builderObject, NO_OBJECT_OVERLAP, player))
- {
- return LBC_OBJECTS_IN_THE_WAY;
- }
- } // end if
- //
- // if NO_ENEMY_OBJECT_OVERLAP is set, we are not allowed to construct 'build' if it would overlap
- // any enemy objects. Friendly objects are ignored.
- //
- if( BitTest( options, NO_ENEMY_OBJECT_OVERLAP ) )
- {
- if (!isLocationClearOfObjects(worldPos, build, angle, builderObject, NO_ENEMY_OBJECT_OVERLAP, player))
- {
- return LBC_OBJECTS_IN_THE_WAY;
- }
- } // end if
- if (build->isKindOf(KINDOF_CANNOT_BUILD_NEAR_SUPPLIES) && TheGlobalData->m_SupplyBuildBorder > 0)
- {
- // special case for supply centers: can't build too close to supply sources
- PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_SUPPLY_SOURCE), KINDOFMASK_NONE);
- PartitionFilter *filters[] = { &f1, NULL };
-
- // see if there are any reasonably close by
- Real range = build->getTemplateGeometryInfo().getBoundingCircleRadius() + TheGlobalData->m_SupplyBuildBorder*2;
- Object* tooClose = ThePartitionManager->getClosestObject(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
- if (tooClose != NULL)
- {
- // yep, see if we would collide with an expanded version
- GeometryInfo tooCloseGeom = tooClose->getGeometryInfo();
- tooCloseGeom.expandFootprint(TheGlobalData->m_SupplyBuildBorder);
- if (ThePartitionManager->geomCollidesWithGeom(
- worldPos,
- build->getTemplateGeometryInfo(),
- angle,
- tooClose->getPosition(),
- tooCloseGeom,
- tooClose->getOrientation()))
- {
- TheTerrainVisual->addFactionBib(tooClose, true, TheGlobalData->m_SupplyBuildBorder);
- return LBC_TOO_CLOSE_TO_SUPPLIES;
- }
- }
- }
- // if clear path is requestsed check to see if the builder object can get there
- if( BitTest( options, CLEAR_PATH ) && builderObject )
- {
- AIUpdateInterface *ai = builderObject->getAIUpdateInterface();
- //
- // if there is no AI interface for this object, it cannot possible pass a clear path
- // check since it will never be able to move there
- //
- /**todo remove this if we need to change the semantics of this function of the builderObject
- // actually being able to get to the destination */
- //
- if( ai == NULL )
- return LBC_NO_CLEAR_PATH;
- //
- // check for an available path using one of two methods (the quick less accurate one,
- // or the slow more accurate one)
- //
- if( BitTest( options, USE_QUICK_PATHFIND ) )
- {
- if( ai->isQuickPathAvailable( worldPos ) == FALSE )
- return LBC_NO_CLEAR_PATH;
- } // end if
- else
- {
- if( ai->isPathAvailable( worldPos ) == FALSE )
- return LBC_NO_CLEAR_PATH;
- } // end else
- } // end if
- // check basic terrain restrctions
- if( BitTest( options, TERRAIN_RESTRICTIONS ) )
- {
- //
- // we will take "samples" at this resolution across the footprint of where we are
- // going to build the structure
- //
- Real sampleResolution = MAP_XY_FACTOR;
- // get the terrain extents
- Region3D terrainExtent;
- TheTerrainLogic->getExtent( &terrainExtent );
- if (TheTerrainLogic->getLayerForDestination(worldPos) != LAYER_GROUND) {
- // we're on a bridge. This is somewhat restricted.
- return LBC_RESTRICTED_TERRAIN;
- }
- //
- // check the footprint of where the structure would go to be clear of any non-buildable
- // tiles and to make sure there isn't a restricted tile and to make sure it's "flat" enough
- //
- SampleBuildData sampleData;
- TheTerrainLogic->getExtent( &sampleData.mapRegion );
- sampleData.terrainRestricted = FALSE;
- sampleData.hiZ = terrainExtent.lo.z; // note we set hi point to lowest point
- sampleData.loZ = terrainExtent.hi.z; // note we set lo point to highest point
- // quick check at triple res.
- iterateFootprint( build, angle, worldPos, 3*sampleResolution,
- checkSampleBuildLocation, &sampleData );
- if( sampleData.terrainRestricted == TRUE )
- return LBC_RESTRICTED_TERRAIN;
- // check if the height across the whole footprint area is too varied (not flat enough)
- if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding )
- return LBC_NOT_FLAT_ENOUGH;
- // careful check at full res.
- iterateFootprint( build, angle, worldPos, sampleResolution,
- checkSampleBuildLocation, &sampleData );
- if( sampleData.terrainRestricted == TRUE )
- return LBC_RESTRICTED_TERRAIN;
- // check if the height across the whole footprint area is too varied (not flat enough)
- if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding )
- return LBC_NOT_FLAT_ENOUGH;
- } // end if
- // we passed all the checks
- return LBC_OK;
- } // end isLocationLegalToBuild
- //-------------------------------------------------------------------------------------------------
- /** Adds bibs to structures near to worldPos */
- //-------------------------------------------------------------------------------------------------
- void BuildAssistant::addBibs(const Coord3D *worldPos,
- const ThingTemplate *build )
- {
- Real range = build->friend_getVisionRange();
- range += 3*build->getTemplateGeometryInfo().getMajorRadius();
- PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_STRUCTURE), KINDOFMASK_NONE);
- PartitionFilter *filters[] = { &f1, NULL };
- ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
- MemoryPoolObjectHolder hold(iter);
- for( Object *them = iter->first(); them; them = iter->next() )
- {
- // ignore any kind of class of objects that we will "remove" for building
- if( isRemovableForConstruction( them ) == TRUE )
- continue;
- if( them->isKindOf( KINDOF_IMMOBILE ) ) {
- TheTerrainVisual->addFactionBib(them, true);
- }
- } // end for, them
- }
- //-------------------------------------------------------------------------------------------------
- /** Tiling wall object helper function, we can use this to "tile" walls when building.
- * This function will fill out the ARRAY world positions named 'positions' starting
- * at the 'start' point in the world and going to the 'end' point in the world. No
- * more than 'maxTiles' will be considered and if the position of any one of
- * the objects in the chain is illegal the chain stops there. The number of objects
- * actually used for the tiling is returned along with a pointer to the array
- * of positions in the tile build info.
- *
- * REQUIRES: Note that the array at 'postions' must be large enough to hold 'maxTiles'
- * entries of positions
- */
- //-------------------------------------------------------------------------------------------------
- BuildAssistant::TileBuildInfo *BuildAssistant::buildTiledLocations( const ThingTemplate *thingBeingTiled,
- Real angle,
- const Coord3D *start,
- const Coord3D *end,
- Real tilingSize,
- Int maxTiles,
- Object *builderObject )
- {
- // sanity
- if( start == NULL || end == NULL )
- return 0;
- //
- // we will fill out our own internal array of positions, it better be big enough to
- // accomodate max tiles, if it's not lets make it bigger!
- //
- if( maxTiles > m_buildPositionSize )
- {
- // delete the old array
- delete [] m_buildPositions;
- // create a new one
- m_buildPositions = NEW Coord3D[ maxTiles ];
- m_buildPositionSize = maxTiles;
- //
- // lets try to at least keep sanity here so that we don't have a completely unbounded
- // allocation spot in the code here
- //
- DEBUG_ASSERTCRASH( m_buildPositionSize < 200, ("Do you really need to tile this many objects!!!") );
- } // end if
- Coord3D *positions = m_buildPositions;
- // compute a vector from the start of the line in the world to the end
- Coord3D placementVector;
- placementVector.x = end->x - start->x;
- placementVector.y = end->y - start->y;
- placementVector.z = 0.0f; //end->z - start->z;
- //
- // get the lengh of the placement vector in the world, we'll use this to see how
- // many objects we'll need to fill the entire line
- //
- Real placementLength = placementVector.length();
- //
- // first see how many objects we're going to need to go from the placement
- // line start to the placement line end, we are guaranteed to have one thing (+1)
- //
- Int tilesNeeded = REAL_TO_INT(placementLength / tilingSize) + 1;
-
- // we have a max that we are allowed to build to
- if( tilesNeeded > maxTiles )
- tilesNeeded = maxTiles;
- // we're guaranteed to have at least one object so lets take care of that position now
- positions[ 0 ] = *start;
- //
- // march down each object and set the position based on its position in the line.
- // note that we're skipping the first one because we've already set it explicitly
- // and are guaranteed to have at least one object
- //
- Int tilesUsed = 1; // actual objects "tiled", we are guaranteed to have at least one
- Coord3D pos;
- Coord3D v = placementVector;
- v.normalize();
- for( Int i = 1; i < tilesNeeded; i++ )
- {
- // compute position of object
- pos.x = v.x * (tilingSize * i) + start->x;
- pos.y = v.y * (tilingSize * i) + start->y;
- pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y );
- // check for a legal position to be at and stop the tiling process if that becomes broken
- if( isLocationLegalToBuild( &pos, thingBeingTiled, angle,
- BuildAssistant::USE_QUICK_PATHFIND |
- BuildAssistant::TERRAIN_RESTRICTIONS |
- BuildAssistant::CLEAR_PATH |
- BuildAssistant::NO_OBJECT_OVERLAP |
- BuildAssistant::SHROUD_REVEALED,
- builderObject,
- NULL) != LBC_OK )
- break;
- // save the position in the output array
- positions[ i ] = pos;
- // we have now actually used one more "tile"
- tilesUsed++;
- } // end for i
- // return a struct filled out with the actual tiles used and the array of locations
- static TileBuildInfo tileInfo;
- tileInfo.tilesUsed = tilesUsed;
- tileInfo.positions = positions;
- return &tileInfo;
- } // end buildTiledLocations
- //-------------------------------------------------------------------------------------------------
- /** Is the template passed in one of those wall type structures that we "build" in
- * the world after drawing a line of where we want the object(s) placed. The objects
- * are placed "tiled" in a line */
- //-------------------------------------------------------------------------------------------------
- Bool BuildAssistant::isLineBuildTemplate( const ThingTemplate *tTemplate )
- {
- // sanity
- if( tTemplate == NULL )
- return FALSE;
- if( tTemplate->isKindOf(KINDOF_LINEBUILD))
- return TRUE;
- return FALSE; // not a line build object
- } // end isLineBuildTemplate
- //-------------------------------------------------------------------------------------------------
- /** This method will check to make sure it is possible to build the requested unit. The
- * builder object MUST be present since all construction comes comes from either a Dozer or
- * a production building. The player must have satisfied the prereqs for 'whatToBuild'
- * This does NOT check available money (see canMakeUnit) */
- //-------------------------------------------------------------------------------------------------
- Bool BuildAssistant::isPossibleToMakeUnit( Object *builder, const ThingTemplate *whatToBuild ) const
- {
- // sanity
- if( builder == NULL || whatToBuild == NULL )
- return FALSE;
- // get the command set for the producer object
- const CommandSet *commandSet = TheControlBar->findCommandSet( builder->getCommandSetString() );
-
- // if no command set we cannot build anything
- if( commandSet == NULL )
- {
- DEBUG_ASSERTLOG( 0, ("Can't build a '%s' from the builder '%s' because '%s' doesn't have any command set defined\n",
- whatToBuild->getName().str(),
- builder->getTemplate()->getName().str(),
- builder->getTemplate()->getName().str()) );
- return FALSE;
- } // end if
- //
- // scan the command set, we must find whatToBuild as one of the "build" commands available
- // in the command set. We want to have all players run this logic on all their machines
- // so that nobody can hack one game and cheat to make stuff that they can't usually make
- //
- const CommandButton *commandButton;
- const CommandButton *foundCommand = NULL;
- Int i;
- for( i = 0; i < MAX_COMMANDS_PER_SET; i++ )
- {
-
- // get this button
- commandButton = commandSet->getCommandButton(i);
- if( commandButton &&
- (commandButton->getCommandType() == GUI_COMMAND_UNIT_BUILD ||
- commandButton->getCommandType() == GUI_COMMAND_DOZER_CONSTRUCT) &&
- commandButton->getThingTemplate()->isEquivalentTo(whatToBuild) )
- foundCommand = commandButton;
- } // end for i
- if( foundCommand == NULL )
- return FALSE;
- // make sure that the player can actually make this unit by checking prereqs and such
- Player *player = builder->getControllingPlayer();
- if( player->canBuild( foundCommand->getThingTemplate() ) == FALSE )
- return FALSE;
- // all is well
- return TRUE;
- }
- // ------------------------------------------------------------------------------------------------
- struct ProductionCountData
- {
- UnsignedInt count;
- const ThingTemplate *type;
- };
- // ------------------------------------------------------------------------------------------------
- /** Count all the units of a given type that are in any production queues for a player */
- // ------------------------------------------------------------------------------------------------
- static void countInProduction( Object *obj, void *userData )
- {
- // only consider objects that have a production update interface
- ProductionUpdateInterface *pui = ProductionUpdate::getProductionUpdateInterfaceFromObject( obj );
- if( pui )
- {
- ProductionCountData *productionCountData = (ProductionCountData *)userData;
- // add the count of this type that are in the queue
- productionCountData->count += pui->countUnitTypeInQueue( productionCountData->type );
- } // end if
- } // end countInProduction
- //-------------------------------------------------------------------------------------------------
- /** This method will check to make sure it is possible to build the requested unit. and
- * that the player has enough money for 'whatToBuild' */
- //-------------------------------------------------------------------------------------------------
- CanMakeType BuildAssistant::canMakeUnit( Object *builder, const ThingTemplate *whatToBuild ) const
- {
- // sanity
- if( builder == NULL || whatToBuild == NULL )
- return CANMAKE_NO_PREREQ;
-
- if (builder->testScriptStatusBit(OBJECT_STATUS_SCRIPT_DISABLED) || builder->testScriptStatusBit(OBJECT_STATUS_SCRIPT_UNPOWERED))
- return CANMAKE_FACTORY_IS_DISABLED;
- if (!isPossibleToMakeUnit(builder, whatToBuild))
- return CANMAKE_NO_PREREQ;
- ProductionUpdateInterface* pu = builder->getProductionUpdateInterface();
- if (pu != NULL)
- {
- CanMakeType cmt = pu->canQueueCreateUnit(whatToBuild);
- if (cmt != CANMAKE_OK)
- return cmt;
- }
- // make sure we have enough money to build this
- Player *player = builder->getControllingPlayer();
- Money *money = player->getMoney();
- if( whatToBuild->calcCostToBuild( player ) > money->countMoney() )
- return CANMAKE_NO_MONEY;
- // make sure we're not maxed out for this type of unit.
- if (whatToBuild->getMaxSimultaneousOfType() != 0)
- {
- const Bool ignoreDead = true;
- const Bool ignoreUnderConstruction = FALSE;// Most people don't want to count under construction, but I totally do
- Int existingCount;
- player->countObjectsByThingTemplate(1, &whatToBuild, ignoreDead, &existingCount, ignoreUnderConstruction);
- if (existingCount >= whatToBuild->getMaxSimultaneousOfType())
- return CANMAKE_MAXED_OUT_FOR_PLAYER;
- // also check objects that are in production
- ProductionCountData productionCountData;
- productionCountData.count = 0;
- productionCountData.type = whatToBuild;
- player->iterateObjects( countInProduction, &productionCountData );
- if( productionCountData.count + existingCount >= whatToBuild->getMaxSimultaneousOfType() )
- return CANMAKE_MAXED_OUT_FOR_PLAYER;
- }
- // get the command set for the producer object
- return CANMAKE_OK;
- }
- // ------------------------------------------------------------------------------------------------
- /** Some objects will be automatically removed when something is built on them so they
- * aren't considered as obstacles when placing them */
- // ------------------------------------------------------------------------------------------------
- Bool BuildAssistant::isRemovableForConstruction( Object *obj )
- {
- // sanity
- if( obj == NULL )
- return FALSE;
- if (obj->isKindOf(KINDOF_INERT))
- {
- DEBUG_CRASH(("should not have gotten here."));
- return FALSE;
- }
- // all shrubbery can be removed
- if( obj->isKindOf( KINDOF_SHRUBBERY ) )
- return TRUE;
- // anything with a kindof marked as cleared by build can be removed
- if( obj->isKindOf( KINDOF_CLEARED_BY_BUILD ) )
- return TRUE;
- // Rubble, scrap & dead units can be cleared.
- if( obj->isEffectivelyDead( ) )
- return TRUE;
- // not removable
- return FALSE;
- } // end isRemovableForConstruction
- // ------------------------------------------------------------------------------------------------
- /** Given that we are about to build 'whatToBuild' remove all the objects that are in the
- * 'footprint' of where the build will take place that can be auto-removed for construction */
- // ------------------------------------------------------------------------------------------------
- void BuildAssistant::clearRemovableForConstruction( const ThingTemplate *whatToBuild,
- const Coord3D *pos,
- Real angle )
- {
- ObjectIterator *iter =
- ThePartitionManager->iteratePotentialCollisions( pos,
- whatToBuild->getTemplateGeometryInfo(),
- angle );
- MemoryPoolObjectHolder hold( iter );
- for( Object *them = iter->first(); them; them = iter->next() )
- {
- // UI feedback objects (always selectable) never get destroyed by construction, but also should never
- // trip any of the other side effects of placing them in isRemovableForConstruction()
- if( isRemovableForConstruction( them ) == TRUE && !them->isKindOf( KINDOF_ALWAYS_SELECTABLE ) )
- TheGameLogic->destroyObject( them );
- } // end for, them
- } // end clearRemovableForConstruction
- // ------------------------------------------------------------------------------------------------
- /** clearRemovable is set up to delete objects that should cease to exist (for instance, trees).
- * moveObjects will move objects that are owned by the player. It will also return false if
- the association with the object is enemy. (We can move neutral things) */
- // ------------------------------------------------------------------------------------------------
- Bool BuildAssistant::moveObjectsForConstruction( const ThingTemplate *whatToBuild,
- const Coord3D *pos,
- Real angle,
- Player *playerToBuild )
- {
- GeometryInfo gi (GEOMETRY_BOX, false, 10, whatToBuild->getTemplateGeometryInfo().getMajorRadius(),
- whatToBuild->getTemplateGeometryInfo().getMajorRadius());
- if (whatToBuild->getTemplateGeometryInfo().getGeomType()==GEOMETRY_BOX) {
- gi = whatToBuild->getTemplateGeometryInfo();
- }
- ObjectIterator *iter =
- ThePartitionManager->iteratePotentialCollisions( pos,
- gi,
- angle );
- Bool anyUnmovables = false;
- MemoryPoolObjectHolder hold( iter );
- Real radius = sqrt(pow(gi.getMajorRadius(), 2) + pow(gi.getMinorRadius(), 2));
- radius *= 1.4f; // Fudge the distance,
- for( Object *them = iter->first(); them; them = iter->next() )
- {
- //ignore land mines, cluster mines and demo traps, since you can build on them
- // doing so will damage them during construction, by the way
- if (them->isKindOf( KINDOF_MINE ))
- continue;
- // same story for inert things: ie, radiation fields, pings, and projectile streams (!)...
- if (them->isKindOf(KINDOF_INERT))
- continue;
- // Skip KINDOF_ALWAYS_SELECTABLE and isRemovableForConstruction, because if it is
- // RemovableForConstruction, it just got destroyed, and won't actually be gone until
- // the end of the frame. jba.
- if ( !them->isKindOf( KINDOF_ALWAYS_SELECTABLE ) && !isRemovableForConstruction(them) )
- {
- Relationship rel = playerToBuild->getRelationship(them->getTeam());
- if (rel == NEUTRAL || rel == ALLIES)
- {
- // Pick a point outside of the construction and tell those fools to move there.
- // Pick an arbitrary direction and tell the unit to move there, at a distance greater
- // than the object's radius.
- AIUpdateInterface *ai = them->getAIUpdateInterface();
- if (ai)
- {
- // Vary the distance to move between one half the diameter of the building (roughly)
- // and 1.5 times the diameter of the building
- Real variedRadius = GameLogicRandomValueReal(0.5, 1.5) * radius;
- Coord3D destPos;
- Real dir = GameLogicRandomValueReal(-PI, PI);
- Vector3 vec(variedRadius, 0, 0);
- vec.Rotate_Z(dir);
- destPos.x = pos->x + vec.X;
- destPos.y = pos->y + vec.Y;
- destPos.z = pos->z + vec.Z;
-
- // note that this is an extra-special case... even if the unit's mood is "sleep"
- // it still needs to move here. (units with an ai mood of "sleep" won't respond to
- // any AI commands. this is a special case that ignores the "sleep" ai mood.) (srj)
- ai->aiMoveToPositionEvenIfSleeping(&destPos, CMD_FROM_AI);
- }
- else
- {
- anyUnmovables = true;
- }
- }
- else
- {
- anyUnmovables = true;
- }
- }
- }
- return !anyUnmovables;
- }
- // ------------------------------------------------------------------------------------------------
- /** Start the process for selling the object in question */
- // ------------------------------------------------------------------------------------------------
- void BuildAssistant::sellObject( Object *obj )
- {
- // sanity
- if( obj == NULL )
- return;
- // we can only sell structures ... sanity check this
- if( obj->isKindOf( KINDOF_STRUCTURE ) == FALSE )
- return;
- // if object already has an entry in the sell list, we shouldn't try to sell it again
- ObjectSellInfo *sellInfo = NULL;
- ObjectSellListIterator it;
- for( it = m_sellList.begin(); it != m_sellList.end(); ++it )
- {
- sellInfo = (*it);
- if( sellInfo->m_id == obj->getID() )
- break;
- else
- sellInfo = NULL;
- } // end for
- if( sellInfo != NULL )
- return;
- // set the construction percent of this object just below 100.0% so we can start counting down
- obj->setConstructionPercent( 99.9f );
- // add this object to the list of objects being sold
- sellInfo = newInstance(ObjectSellInfo);
- sellInfo->m_id = obj->getID();
- sellInfo->m_sellFrame = TheGameLogic->getFrame();
- m_sellList.push_front( sellInfo );
- //
- // set the model condition in the drawable for this object that will show the buildup
- // scaffold and adjust the model height by construction percent
- //
- obj->setModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_PARTIALLY_CONSTRUCTED,
- MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED) );
- //
- // set this object as under de-construction (sold). It is still a legal target, since you get the money at
- // the completion of sale.
- //
- obj->setStatus( ObjectStatusBits( OBJECT_STATUS_SOLD | OBJECT_STATUS_UNSELECTABLE ) );
- // for everybody, unselect them at this time. You can't just deselect a drawable. Selection is a logic property.
- TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE);
- //
- // set the animation durations so that the regular build up loop animations can be
- // done a bit faster for selling
- //
- Drawable *draw = obj->getDrawable();
- if( draw )
- draw->setAnimationLoopDuration( (unsigned)(TOTAL_FRAMES_TO_SELL_OBJECT / 2.0f) );
- // We also need to refund all production for the object at start-of-sell time
- ProductionUpdateInterface *production = obj->getProductionUpdateInterface();
- if ( production )
- {
- production->cancelAndRefundAllProduction();
- }
- // Tell the contain module so it can decide what to do.
- ContainModuleInterface* contain = obj->getContain();
- if( contain )
- {
- contain->onSelling();
- }
-
- // Tell it to stop attacking or anything else it is doing
- if( obj->getAI() )
- obj->getAI()->aiIdle(CMD_FROM_AI);
- // if it has parking places, kill anything parked there.
- for (BehaviorModule** i = obj->getBehaviorModules(); *i; ++i)
- {
- ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface();
- if (pp)
- {
- pp->killAllParkedUnits();
- break;
- }
- }
-
- // destroy 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() == obj->getID())
- {
- TheGameLogic->destroyObject(mine);
- }
- }
- }
- } // end sellObject
|