BuildAssistant.cpp 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: BuildAssistant.cpp ///////////////////////////////////////////////////////////////////////
  24. // Author: Colin Day, February 2002
  25. // Desc: Singleton class to encapsulate some of the more common functions or rules
  26. // that apply to building structures and units
  27. ///////////////////////////////////////////////////////////////////////////////////////////////////
  28. // USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
  29. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  30. #include "Common/BuildAssistant.h"
  31. #include "Common/GlobalData.h"
  32. #include "Common/Player.h"
  33. #include "Common/ThingTemplate.h"
  34. #include "Common/GameAudio.h"
  35. #include "Common/ThingFactory.h"
  36. #include "Common/Team.h"
  37. #include "Common/TerrainTypes.h"
  38. #include "Common/MapObject.h"
  39. #include "GameClient/ControlBar.h"
  40. #include "GameClient/Drawable.h"
  41. #include "GameClient/InGameUI.h"
  42. #include "GameClient/TerrainVisual.h"
  43. #include "GameLogic/AI.h"
  44. #include "GameLogic/PartitionManager.h"
  45. #include "GameLogic/TerrainLogic.h"
  46. #include "GameLogic/AIPathfind.h"
  47. #include "GameLogic/Module/AIUpdate.h"
  48. #include "GameLogic/Module/BehaviorModule.h"
  49. #include "GameLogic/Module/ContainModule.h"
  50. #include "GameLogic/Module/CreateModule.h"
  51. #include "GameLogic/Module/ProductionUpdate.h"
  52. #include "GameLogic/Module/ContainModule.h"
  53. #include "GameLogic/Module/ParkingPlaceBehavior.h"
  54. // PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
  55. BuildAssistant *TheBuildAssistant = NULL;
  56. #ifdef _INTERNAL
  57. // for occasional debugging...
  58. //#pragma optimize("", off)
  59. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  60. #endif
  61. ///////////////////////////////////////////////////////////////////////////////////////////////////
  62. ///////////////////////////////////////////////////////////////////////////////////////////////////
  63. ///////////////////////////////////////////////////////////////////////////////////////////////////
  64. // ------------------------------------------------------------------------------------------------
  65. // ------------------------------------------------------------------------------------------------
  66. ObjectSellInfo::ObjectSellInfo( void )
  67. {
  68. m_id = INVALID_ID;
  69. m_sellFrame = 0;
  70. } // end ObjectSellInfo
  71. // ------------------------------------------------------------------------------------------------
  72. // ------------------------------------------------------------------------------------------------
  73. ObjectSellInfo::~ObjectSellInfo( void )
  74. {
  75. } // end ~ObjectSellInfo
  76. ///////////////////////////////////////////////////////////////////////////////////////////////////
  77. ///////////////////////////////////////////////////////////////////////////////////////////////////
  78. ///////////////////////////////////////////////////////////////////////////////////////////////////
  79. //-------------------------------------------------------------------------------------------------
  80. /** Is this object a dozer */
  81. //-------------------------------------------------------------------------------------------------
  82. static Bool isDozer( Object *obj )
  83. {
  84. // sanity
  85. if( obj == NULL )
  86. return FALSE;
  87. if( obj->isKindOf(KINDOF_DOZER))
  88. return TRUE;
  89. return FALSE;
  90. } // end isDozer
  91. // PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
  92. //-------------------------------------------------------------------------------------------------
  93. //-------------------------------------------------------------------------------------------------
  94. BuildAssistant::BuildAssistant( void )
  95. {
  96. m_buildPositions = NULL;
  97. m_buildPositionSize = 0;
  98. m_sellList.clear();
  99. } // end BuildAssistant
  100. //-------------------------------------------------------------------------------------------------
  101. //-------------------------------------------------------------------------------------------------
  102. BuildAssistant::~BuildAssistant( void )
  103. {
  104. // delete build position array if we used it
  105. if( m_buildPositions )
  106. {
  107. delete [] m_buildPositions;
  108. m_buildPositions = NULL;
  109. m_buildPositionSize = 0;
  110. } // end if
  111. } // end ~BuildAssistant
  112. //-------------------------------------------------------------------------------------------------
  113. //-------------------------------------------------------------------------------------------------
  114. void BuildAssistant::init( void )
  115. {
  116. //
  117. // allocate our array of positions that we use to assist ourselves when constructing
  118. // a tiled array of locations to build things like walls
  119. //
  120. m_buildPositionSize = TheGlobalData->m_maxLineBuildObjects;
  121. m_buildPositions = NEW Coord3D[ m_buildPositionSize ];
  122. } // end init
  123. //-------------------------------------------------------------------------------------------------
  124. //-------------------------------------------------------------------------------------------------
  125. void BuildAssistant::reset( void )
  126. {
  127. // clear all our data from the sell list
  128. ObjectSellInfo *sellInfo;
  129. ObjectSellListIterator it;
  130. for( it = m_sellList.begin(); it != m_sellList.end(); ++it )
  131. {
  132. // get sell info
  133. sellInfo = (*it);
  134. // delete our data and erase this entry from the list
  135. sellInfo->deleteInstance();
  136. } // end for
  137. // clear the sell list
  138. m_sellList.clear();
  139. } // end reset
  140. static const Real FRAMES_TO_ALLOW_SCAFFOLD = LOGICFRAMES_PER_SECOND * 1.5f;
  141. static const Real TOTAL_FRAMES_TO_SELL_OBJECT = LOGICFRAMES_PER_SECOND * 3.0f;
  142. //-------------------------------------------------------------------------------------------------
  143. /** Update phase for the build assistant */
  144. //-------------------------------------------------------------------------------------------------
  145. void BuildAssistant::update( void )
  146. {
  147. ObjectSellInfo *sellInfo;
  148. Object *obj;
  149. // remove any objects from the sell list who's "sell time" is up
  150. ObjectSellListIterator it, thisIterator;
  151. for( it = m_sellList.begin(); it != m_sellList.end(); /*empty*/ )
  152. {
  153. // get this object info
  154. sellInfo = (*it);
  155. // increment the iterator to the next as we may remove it
  156. thisIterator = it;
  157. ++it;
  158. // find the object
  159. obj = TheGameLogic->findObjectByID( sellInfo->m_id );
  160. //
  161. // if object is not found, remove it from the list immediately ... this is valid as
  162. // the object maybe was destroyed by other means during the sell process
  163. //
  164. if( obj == NULL )
  165. {
  166. sellInfo->deleteInstance();
  167. m_sellList.erase( thisIterator );
  168. continue;
  169. } // end if
  170. // decrement the construction percent
  171. if( TheGameLogic->getFrame() - sellInfo->m_sellFrame >= FRAMES_TO_ALLOW_SCAFFOLD )
  172. {
  173. Real previousConstructionPercent = obj->getConstructionPercent();
  174. // do the constructoin
  175. obj->setConstructionPercent( previousConstructionPercent - (100.0f / TOTAL_FRAMES_TO_SELL_OBJECT) );
  176. //
  177. // did we cross the threshold from positive to negative ... if so, we will clear
  178. // visual construction indicators
  179. //
  180. if( previousConstructionPercent > 0.0f && obj->getConstructionPercent() <= 0.0f )
  181. {
  182. obj->clearAndSetModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_PARTIALLY_CONSTRUCTED,
  183. MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED ),
  184. MAKE_MODELCONDITION_MASK( MODELCONDITION_SOLD ) );
  185. //
  186. // set the animation durations so that the regular build up loop animations can be
  187. // done a bit faster for selling
  188. //
  189. Drawable *draw = obj->getDrawable();
  190. if( draw )
  191. draw->setAnimationLoopDuration( TOTAL_FRAMES_TO_SELL_OBJECT / 2 );
  192. } // end if
  193. } // end if
  194. //
  195. // after we've reached zero ... the object has "sunk" back down into the ground ... but
  196. // we need to keep the object around for a little while longer so that the scaffold
  197. // animation can finish up and sink itself back into the ground
  198. //
  199. if( obj->getConstructionPercent() <= -50.0f )
  200. {
  201. // refund the money to the controlling player
  202. Player *player = obj->getControllingPlayer();
  203. if( player )
  204. {
  205. UnsignedInt sellValue;
  206. // 0 is the init, and means you have no override. We would be marked unsellable if someone wanted us to not sell
  207. if( obj->getTemplate()->getRefundValue() != 0 )
  208. sellValue = obj->getTemplate()->getRefundValue();
  209. else
  210. sellValue = REAL_TO_UNSIGNEDINT( obj->getTemplate()->calcCostToBuild( player ) *
  211. TheGlobalData->m_sellPercentage );
  212. player->getMoney()->deposit( sellValue );
  213. // this money shouldn't be scored since it wasn't really "earned."
  214. // player->getScoreKeeper()->addMoneyEarned( sellValue );
  215. } // end if
  216. // cancel any of the production items and refund to the controlling player
  217. ProductionUpdateInterface *pui = obj->getProductionUpdateInterface();
  218. if( pui )
  219. pui->cancelAndRefundAllProduction();
  220. // for debugging
  221. // UnicodeString msg;
  222. // msg.format( L"'%S' has been sold\n", obj->getTemplate()->getName().str() );
  223. // TheInGameUI->message( msg );
  224. // destroy the object
  225. TheGameLogic->destroyObject( obj );
  226. // remove this object from the sell list
  227. sellInfo->deleteInstance();
  228. m_sellList.erase( thisIterator );
  229. } // end if
  230. } // end for
  231. } // end update
  232. //-------------------------------------------------------------------------------------------------
  233. /** Xfer the sell list. */
  234. //-------------------------------------------------------------------------------------------------
  235. void BuildAssistant::xferTheSellList( Xfer *xfer )
  236. {
  237. ObjectSellInfo *sellInfo;
  238. Int count=0;
  239. ObjectSellListIterator it, thisIterator;
  240. for( it = m_sellList.begin(); it != m_sellList.end(); ++it ) {
  241. count++;
  242. }
  243. xfer->xferInt(&count);
  244. if (xfer->getXferMode() == XFER_LOAD) {
  245. m_sellList.clear();
  246. Int i;
  247. for (i=0; i<count; i++) {
  248. // add this object to the list of objects being sold
  249. sellInfo = newInstance(ObjectSellInfo);
  250. xfer->xferObjectID(&sellInfo->m_id);
  251. xfer->xferUnsignedInt(&sellInfo->m_sellFrame);
  252. m_sellList.push_back( sellInfo );
  253. }
  254. } else {
  255. for( it = m_sellList.begin(); it != m_sellList.end(); ++it ) {
  256. // get this object info
  257. sellInfo = (*it);
  258. xfer->xferObjectID(&sellInfo->m_id);
  259. xfer->xferUnsignedInt(&sellInfo->m_sellFrame);
  260. count--;
  261. }
  262. DEBUG_ASSERTCRASH(count==0, ("Inconsistent list size counts."));
  263. }
  264. } // end xferTheSellList
  265. //-------------------------------------------------------------------------------------------------
  266. /** Nice little method to wrap up creating an object from a build */
  267. //-------------------------------------------------------------------------------------------------
  268. Object *BuildAssistant::buildObjectNow( Object *constructorObject, const ThingTemplate *what,
  269. const Coord3D *pos, Real angle, Player *owningPlayer )
  270. {
  271. // sanity
  272. if( what == NULL || pos == NULL )
  273. return NULL;
  274. if( owningPlayer == NULL )
  275. return NULL;// Invalid pointer. Won't happen.
  276. // sanity
  277. if( constructorObject )
  278. {
  279. DEBUG_ASSERTCRASH( constructorObject->getControllingPlayer() == owningPlayer,
  280. ("buildObjectNow: Constructor object player is not the same as the controlling player passed in\n") );
  281. } // end if
  282. // Need to validate that we can make this in case someone fakes their CommandSet
  283. // A Null constructorObject is used by the script engine to cheat, so let it slide
  284. if( constructorObject && !isPossibleToMakeUnit(constructorObject, what) )
  285. return NULL;
  286. // clear out any objects from the building area that are "auto-clearable" when building
  287. clearRemovableForConstruction( what, pos, angle );
  288. if ( moveObjectsForConstruction( what, pos, angle, owningPlayer ) == FALSE )
  289. {
  290. // totally bogus. We tried to move our units out of the way, but they wouldn't.
  291. // Chode-boys.
  292. if (owningPlayer->getPlayerType()==PLAYER_HUMAN) {
  293. return NULL; // ai gets to cheat. jba.
  294. }
  295. }
  296. // do the build
  297. if( isDozer( constructorObject ) && isLineBuildTemplate( what ) == FALSE )
  298. {
  299. AIUpdateInterface *ai = constructorObject->getAIUpdateInterface();
  300. if( ai )
  301. {
  302. ai->aiIdle(CMD_FROM_AI); // stop any current behavior.
  303. return ai->construct( what, pos, angle, owningPlayer, FALSE );
  304. }
  305. return NULL;
  306. } // end else if
  307. else
  308. {
  309. // create the new object. We need to construct it with UnderConstruction set, since we are going to insta build it,
  310. // but we don't want to send double construction type events like power creation. onStructureConstructionComplete
  311. // is called below, so we need to simulate the proper object creation flow from the start. Be like Dozer.
  312. ObjectStatusBits startingStatus = OBJECT_STATUS_NONE;
  313. if( what->isKindOf( KINDOF_STRUCTURE ) )
  314. startingStatus = OBJECT_STATUS_UNDER_CONSTRUCTION;
  315. Object *obj = TheThingFactory->newObject( what, owningPlayer->getDefaultTeam(), startingStatus );
  316. obj->setProducer(constructorObject);
  317. // place on terrain surface
  318. Coord3D groundPos;
  319. groundPos.x = pos->x;
  320. groundPos.y = pos->y;
  321. groundPos.z = TheTerrainLogic->getGroundHeight( groundPos.x, groundPos.y );
  322. obj->setPosition( &groundPos );
  323. obj->setOrientation( angle );
  324. TheAI->pathfinder()->addObjectToPathfindMap( obj );
  325. // notify the player that this thing has come into existence
  326. if( obj->isKindOf( KINDOF_STRUCTURE ) )
  327. {
  328. //
  329. // this is a cheat since this method is currently used by the solo AI to make
  330. // buildings out of thin air
  331. //
  332. owningPlayer->onStructureCreated( constructorObject, obj );
  333. owningPlayer->onStructureConstructionComplete( constructorObject, obj, FALSE );
  334. } // end if
  335. else
  336. {
  337. owningPlayer->onUnitCreated( constructorObject, obj );
  338. //Call the voice created for the 1st object -- because it's possible to create multiple objects like redguards!
  339. AudioEventRTS sound = *obj->getTemplate()->getPerUnitSound( "VoiceCreated" );
  340. sound.setObjectID( obj->getID() );
  341. TheAudio->addAudioEvent( &sound );
  342. }
  343. // Now onCreates were called at the constructor. This magically created
  344. // thing needs to be considered as Built for Game specific stuff.
  345. for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m)
  346. {
  347. CreateModuleInterface* create = (*m)->getCreate();
  348. if (!create)
  349. continue;
  350. create->onBuildComplete();
  351. }
  352. // Creation is another valid and essential time to call this. This building now Looks.
  353. obj->handlePartitionCellMaintenance();
  354. return obj;
  355. } // end else
  356. return NULL;
  357. } // end buildObjectNow
  358. //-------------------------------------------------------------------------------------------------
  359. /** This method will create a line of objects end to end along the line defined in 3D
  360. * space from start to end. This is especially useful in building walls */
  361. //-------------------------------------------------------------------------------------------------
  362. void BuildAssistant::buildObjectLineNow( Object *constructorObject, const ThingTemplate *what,
  363. const Coord3D *start, const Coord3D *end, Real angle,
  364. Player *owningPlayer )
  365. {
  366. TileBuildInfo *tileBuildInfo;
  367. // sanity
  368. if( what == NULL || start == NULL || end == NULL )
  369. return;
  370. // how big are each of our objects
  371. Real objectSize = what->getTemplateGeometryInfo().getMajorRadius() * 2.0f;
  372. // what is our max tiling length we can make
  373. Int maxObjects = TheGlobalData->m_maxLineBuildObjects;
  374. // build an array of locations that we want to build from start to end
  375. tileBuildInfo = buildTiledLocations( what, angle, start, end,
  376. objectSize, maxObjects, constructorObject );
  377. // create an object at each position
  378. for( Int i = 0; i < tileBuildInfo->tilesUsed; i++ )
  379. buildObjectNow( constructorObject, what, &tileBuildInfo->positions[ i ], angle, owningPlayer );
  380. } // end buildObjectLineNow
  381. //-------------------------------------------------------------------------------------------------
  382. /** This structure is passed along to the checkSampleBuildLocation while iterating the
  383. * footprint of an area in order to determine if the entire location is a legal place to
  384. * build */
  385. //-------------------------------------------------------------------------------------------------
  386. struct SampleBuildData
  387. {
  388. Region3D mapRegion;
  389. Bool terrainRestricted; ///< one of the terrain tiles prevents building
  390. Real hiZ; ///< highest sample point used
  391. Real loZ; ///< lowest sample point used
  392. };
  393. //-------------------------------------------------------------------------------------------------
  394. /** This will check the build conditions at the specified sample location point */
  395. //-------------------------------------------------------------------------------------------------
  396. static void checkSampleBuildLocation( const Coord3D *samplePoint, void *userData )
  397. {
  398. TerrainType *terrain;
  399. SampleBuildData *sampleData = (SampleBuildData *)userData;
  400. // get the terrain tile here
  401. terrain = TheTerrainVisual->getTerrainTile( samplePoint->x, samplePoint->y );
  402. if( terrain )
  403. {
  404. // check for the restricts building flag
  405. if( terrain->getRestrictConstruction() )
  406. sampleData->terrainRestricted = TRUE;
  407. } // end if
  408. Int cellX = REAL_TO_INT_FLOOR( samplePoint->x / PATHFIND_CELL_SIZE );
  409. Int cellY = REAL_TO_INT_FLOOR( samplePoint->y / PATHFIND_CELL_SIZE );
  410. PathfindCell* cell = TheAI->pathfinder()->getCell( LAYER_GROUND, cellX, cellY );
  411. if (!cell) {
  412. sampleData->terrainRestricted = TRUE;
  413. } else {
  414. enum PathfindCell::CellType type = cell->getType();
  415. if ( (type == PathfindCell::CELL_WATER) || (type == PathfindCell::CELL_CLIFF) ||
  416. (type == PathfindCell::CELL_IMPASSABLE)) {
  417. sampleData->terrainRestricted = true;
  418. }
  419. }
  420. //
  421. // record the highest and lowest Z points from all the samples and do not allow
  422. // building when the difference between them is too great
  423. //
  424. if( samplePoint->z < sampleData->loZ )
  425. sampleData->loZ = samplePoint->z;
  426. if( samplePoint->z > sampleData->hiZ )
  427. sampleData->hiZ = samplePoint->z;
  428. // too close to edge of map?
  429. if (TheGlobalData->m_MinDistFromEdgeOfMapForBuild > 0.0f)
  430. {
  431. if (samplePoint->x < sampleData->mapRegion.lo.x + TheGlobalData->m_MinDistFromEdgeOfMapForBuild
  432. || samplePoint->x > sampleData->mapRegion.hi.x - TheGlobalData->m_MinDistFromEdgeOfMapForBuild
  433. || samplePoint->y < sampleData->mapRegion.lo.y + TheGlobalData->m_MinDistFromEdgeOfMapForBuild
  434. || samplePoint->y > sampleData->mapRegion.hi.y - TheGlobalData->m_MinDistFromEdgeOfMapForBuild)
  435. {
  436. sampleData->terrainRestricted = TRUE;
  437. }
  438. }
  439. } // end checkSampleBuildLocation
  440. //-------------------------------------------------------------------------------------------------
  441. /** This function will call the user callback at each "sample point" across the footprint
  442. * of where this object would be in the world. The smaller the sample resolution param is
  443. * the more samples will be taken across the footprint area */
  444. //-------------------------------------------------------------------------------------------------
  445. void BuildAssistant::iterateFootprint( const ThingTemplate *build,
  446. Real buildOrientation,
  447. const Coord3D *worldPos,
  448. Real sampleResolution,
  449. IterateFootprintFunc func,
  450. void *funcUserData )
  451. {
  452. // sanity
  453. if( build == NULL || worldPos == NULL || func == NULL )
  454. return;
  455. //
  456. // create a transform matrix for the object position at the specified angle, we will
  457. // iterate the object footprints in "local object extent space" and transform those
  458. // points using this matrix into the world coords for the object at the real
  459. // location and specified angle
  460. //
  461. Matrix3D transform;
  462. transform.Make_Identity();
  463. transform.Adjust_Translation( Vector3( worldPos->x, worldPos->y, worldPos->z ) );
  464. transform.Rotate_Z( buildOrientation );
  465. // get the bounding footprint rectangle for the geometry we're looking at
  466. Real halfFootprintHeight,
  467. halfFootprintWidth;
  468. if( build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_BOX )
  469. {
  470. halfFootprintHeight = build->getTemplateGeometryInfo().getMinorRadius();
  471. halfFootprintWidth = build->getTemplateGeometryInfo().getMajorRadius();
  472. } // end if
  473. else if( build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_SPHERE ||
  474. build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_CYLINDER )
  475. {
  476. halfFootprintHeight = build->getTemplateGeometryInfo().getBoundingCircleRadius();
  477. halfFootprintWidth = build->getTemplateGeometryInfo().getBoundingCircleRadius();
  478. } // end else if
  479. else
  480. {
  481. DEBUG_ASSERTCRASH( 0, ("iterateFootprint: Undefined geometry '%d' for '%s'\n",
  482. build->getTemplateGeometryInfo().getGeomType(), build->getName().str()) );
  483. return;
  484. } // end else
  485. //
  486. // start at a corner of the extent ... box geometries have a major radius down
  487. // the X axis with the minor radius on the Y axis. Note that we are allowing
  488. // the sample process to go an "extra" sample resolution outside of the
  489. // real extent here. This is so that we can be sure to sample all of the
  490. // footprint area, if our sample point is outside of the actual extent footprint
  491. // area because of this we snap it back to sample on the exact edge
  492. //
  493. Real x, y;
  494. Vector3 v;
  495. for( y = -halfFootprintHeight;
  496. y < halfFootprintHeight + sampleResolution;
  497. y += sampleResolution )
  498. {
  499. // snap it to the actual extent since we can go over by one sample resolution
  500. if( y > halfFootprintHeight )
  501. y = halfFootprintHeight;
  502. for( x = -halfFootprintWidth;
  503. x < halfFootprintWidth + sampleResolution;
  504. x += sampleResolution )
  505. {
  506. // snap it to the actual extent since we can go over by one sample resolution
  507. if( x > halfFootprintWidth )
  508. x = halfFootprintWidth;
  509. // transform to world
  510. v.Set( x, y, TheTerrainLogic->getGroundHeight( x, y ) );
  511. transform.Transform_Vector( transform, v, &v );
  512. // for circular geometries we must actually be within the circle
  513. if( build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_SPHERE ||
  514. build->getTemplateGeometryInfo().getGeomType() == GEOMETRY_CYLINDER )
  515. {
  516. Coord2D vector;
  517. vector.x = v.X - worldPos->x;
  518. vector.y = v.Y - worldPos->y;
  519. if( vector.length() > halfFootprintWidth ) // could be height too, radius is all the same for circles
  520. continue; // ignore this point
  521. } // end if
  522. // call the user callback
  523. Coord3D pos;
  524. pos.x = v.X;
  525. pos.y = v.Y;
  526. pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y );
  527. func( &pos, funcUserData );
  528. } // end for x
  529. } // end for y
  530. } // end iterateFootprint
  531. //-------------------------------------------------------------------------------------------------
  532. /** Check for objects preventing building at this location. */
  533. //-------------------------------------------------------------------------------------------------
  534. Bool BuildAssistant::isLocationClearOfObjects( const Coord3D *worldPos,
  535. const ThingTemplate *build,
  536. Real angle,
  537. Object *builderObject,
  538. UnsignedInt options,
  539. Player *thePlayer)
  540. {
  541. ObjectIterator *iter =
  542. ThePartitionManager->iteratePotentialCollisions( worldPos,
  543. build->getTemplateGeometryInfo(),
  544. angle );
  545. Object *them;
  546. Bool onlyCheckEnemies = (options == NO_ENEMY_OBJECT_OVERLAP);
  547. MemoryPoolObjectHolder hold(iter);
  548. for( them = iter->first(); them; them = iter->next() )
  549. {
  550. // ignore any kind of class of objects that we will "remove" for building
  551. if( isRemovableForConstruction( them ) == TRUE )
  552. continue;
  553. // ignore land mines, cluster mines and demo traps, since you can build on them
  554. // doing so will damage them during construction, by the way
  555. if (them->isKindOf( KINDOF_MINE ))
  556. continue;
  557. // same story for inert things: ie, radiation fields, pings, and projectile streams (!)...
  558. if (them->isKindOf(KINDOF_INERT))
  559. continue;
  560. // an immobile object may obstruct our building depending on flags.
  561. if( them->isKindOf( KINDOF_IMMOBILE ) ) {
  562. if (onlyCheckEnemies && builderObject && builderObject->getRelationship( them ) != ENEMIES ) {
  563. continue;
  564. }
  565. TheTerrainVisual->addFactionBib(them, true);
  566. return false;
  567. }
  568. //
  569. // if this is an enemy object of the builder object (and therefore the thing
  570. // that will be constructed) we can't build here
  571. //
  572. if( builderObject && builderObject->getRelationship( them ) == ENEMIES ) {
  573. TheTerrainVisual->addFactionBib(them, true);
  574. return false;
  575. }
  576. } // end for, them
  577. if (onlyCheckEnemies) {
  578. return true;
  579. }
  580. // Check for overlapping exit areas.
  581. Real range = 2*(build->getTemplateGeometryInfo().getMajorRadius()+build->getTemplateGeometryInfo().getMinorRadius());
  582. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_STRUCTURE), KINDOFMASK_NONE);
  583. PartitionFilter *filters[] = { &f1, NULL };
  584. ObjectIterator *iter2 = ThePartitionManager->iterateObjectsInRange(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
  585. MemoryPoolObjectHolder hold2(iter2);
  586. Real myFactoryExitWidth = build->getFactoryExitWidth();
  587. Real myExtraWidth = build->getFactoryExtraBibWidth();
  588. if (thePlayer && thePlayer->isSkirmishAIPlayer()) {
  589. // Skirmish ai adds a little extra around the edges so it doesn't build itself into a corner.
  590. if (myExtraWidth < 3*PATHFIND_CELL_SIZE_F) {
  591. myExtraWidth = 3*PATHFIND_CELL_SIZE_F;
  592. myFactoryExitWidth -= myExtraWidth;
  593. if (myFactoryExitWidth<0) myFactoryExitWidth = 0;
  594. }
  595. }
  596. Bool checkMyExit = false;
  597. Coord3D myExitPos;
  598. GeometryInfo myBounds = build->getTemplateGeometryInfo();
  599. myBounds.setMajorRadius(myBounds.getMajorRadius()+myExtraWidth);
  600. if (myBounds.getGeomType() != GEOMETRY_BOX) {
  601. myBounds.set(GEOMETRY_BOX, false, 40, myBounds.getMajorRadius(), myBounds.getMajorRadius());
  602. } else {
  603. myBounds.setMinorRadius(myBounds.getMinorRadius()+myExtraWidth);
  604. }
  605. GeometryInfo myGeom = build->getTemplateGeometryInfo();
  606. if (myGeom.getGeomType() != GEOMETRY_BOX) {
  607. myGeom.setMinorRadius(myGeom.getMajorRadius());
  608. }
  609. myGeom.setMajorRadius(myFactoryExitWidth/2.0f);
  610. if (myFactoryExitWidth>0) {
  611. myExitPos = *worldPos;
  612. checkMyExit = true;
  613. Real c = (Real)cos(angle);
  614. Real s = (Real)sin(angle);
  615. Real offset = build->getTemplateGeometryInfo().getMajorRadius() + myFactoryExitWidth/2.0f;
  616. myExitPos.x += c*offset;
  617. myExitPos.y += s*offset;
  618. }
  619. for( them = iter2->first(); them; them = iter2->next() )
  620. {
  621. // ignore any kind of class of objects that we will "remove" for building
  622. if( isRemovableForConstruction( them ) == TRUE )
  623. continue;
  624. Real themFactoryExitWidth = them->getTemplate()->getFactoryExitWidth();
  625. Real hisExtraWidth = them->getTemplate()->getFactoryExtraBibWidth();
  626. Bool checkHisExit = false;
  627. Coord3D hisExitPos;
  628. GeometryInfo hisBounds = them->getGeometryInfo();
  629. hisBounds.setMajorRadius(hisBounds.getMajorRadius()+hisExtraWidth);
  630. if (hisBounds.getGeomType() != GEOMETRY_BOX) {
  631. hisBounds.set(GEOMETRY_BOX, false, 40, hisBounds.getMajorRadius(), hisBounds.getMajorRadius());
  632. } else {
  633. hisBounds.setMinorRadius(hisBounds.getMinorRadius()+myExtraWidth);
  634. }
  635. GeometryInfo hisGeom = them->getGeometryInfo();
  636. hisGeom.setMajorRadius(themFactoryExitWidth/2.0f);
  637. if (hisGeom.getGeomType() != GEOMETRY_BOX) {
  638. hisGeom.setMinorRadius(them->getGeometryInfo().getMajorRadius());
  639. }
  640. if (themFactoryExitWidth>0) {
  641. hisExitPos = *them->getPosition();
  642. checkHisExit = true;
  643. Real c = (Real)cos(them->getOrientation());
  644. Real s = (Real)sin(them->getOrientation());
  645. Real offset = them->getGeometryInfo().getMajorRadius() + themFactoryExitWidth/2.0f;
  646. hisExitPos.x += c*offset;
  647. hisExitPos.y += s*offset;
  648. }
  649. if (ThePartitionManager->geomCollidesWithGeom(them->getPosition(), hisBounds, them->getOrientation(),
  650. worldPos, myBounds, angle)) {
  651. TheTerrainVisual->addFactionBib(them, true);
  652. return false;
  653. }
  654. if (!checkMyExit && !checkHisExit && !hisExtraWidth && !myExtraWidth)
  655. {
  656. continue; // neither has extra exit space.
  657. }
  658. // an immobile object will obstruct our building no matter what team it's on
  659. if ( them->isKindOf( KINDOF_IMMOBILE ) ) {
  660. /* Check for overlap of my exit rectangle to his geom info. */
  661. if (checkMyExit && ThePartitionManager->geomCollidesWithGeom(them->getPosition(), hisBounds, them->getOrientation(),
  662. &myExitPos, myGeom, angle)) {
  663. TheTerrainVisual->addFactionBib(them, true);
  664. return false;
  665. }
  666. // Check for overlap of his exit rectangle with my geom info
  667. if (checkHisExit && ThePartitionManager->geomCollidesWithGeom(&hisExitPos, hisGeom, them->getOrientation(),
  668. worldPos, myBounds, angle)) {
  669. TheTerrainVisual->addFactionBib(them, true);
  670. return false;
  671. }
  672. // Check both exit rectangles together.
  673. if (checkMyExit&&checkHisExit&&ThePartitionManager->geomCollidesWithGeom(&hisExitPos, hisGeom, them->getOrientation(),
  674. &myExitPos, myGeom, angle)) {
  675. TheTerrainVisual->addFactionBib(them, true);
  676. return false;
  677. }
  678. }
  679. } // end for, them
  680. return true;
  681. }
  682. //-------------------------------------------------------------------------------------------------
  683. /** Query if we can build at this location. Note that 'build' may be null and is NOT required
  684. * to be valid to know if a location is legal to build at. 'builderObject' is used
  685. * for queries that require a pathfind check and should be NULL if not required */
  686. //-------------------------------------------------------------------------------------------------
  687. LegalBuildCode BuildAssistant::isLocationLegalToBuild( const Coord3D *worldPos,
  688. const ThingTemplate *build,
  689. Real angle,
  690. UnsignedInt options,
  691. Object *builderObject,
  692. Player *player)
  693. {
  694. /* You just can't never build off the map, regardless of options. jba. */
  695. Region3D mapExtent;
  696. TheTerrainLogic->getMaximumPathfindExtent(&mapExtent);
  697. if (!mapExtent.isInRegionNoZ(worldPos)) {
  698. return LBC_RESTRICTED_TERRAIN;
  699. }
  700. // check shroud level
  701. // This should be the first check, since returning other errors for shrouded areas could be used to game the system
  702. if( BitTest( options, SHROUD_REVEALED ) )
  703. {
  704. {
  705. Int x, y;
  706. ThePartitionManager->worldToCell(worldPos->x, worldPos->y, &x, &y);
  707. Int playerIndex = -1;
  708. if (builderObject && builderObject->getControllingPlayer())
  709. playerIndex = builderObject->getControllingPlayer()->getPlayerIndex();
  710. DEBUG_ASSERTCRASH(playerIndex >= 0, ("isLocationLegalToBuild() needs a builderObject with a team to check for shroud"));
  711. if( ThePartitionManager->getShroudStatusForPlayer(playerIndex, x, y) != CELLSHROUD_CLEAR )
  712. {
  713. return LBC_SHROUD;
  714. }
  715. }
  716. }
  717. //
  718. // if NO_OBJECT_OVERLAP is set, we are not allowed to construct 'build' if it would overlap
  719. // any immobile objects, or an enemy object. Friendly objects should politely
  720. // "move out of the way" when you build something where they're standing
  721. //
  722. if( BitTest( options, NO_OBJECT_OVERLAP ) )
  723. {
  724. if (!isLocationClearOfObjects(worldPos, build, angle, builderObject, NO_OBJECT_OVERLAP, player))
  725. {
  726. return LBC_OBJECTS_IN_THE_WAY;
  727. }
  728. } // end if
  729. //
  730. // if NO_ENEMY_OBJECT_OVERLAP is set, we are not allowed to construct 'build' if it would overlap
  731. // any enemy objects. Friendly objects are ignored.
  732. //
  733. if( BitTest( options, NO_ENEMY_OBJECT_OVERLAP ) )
  734. {
  735. if (!isLocationClearOfObjects(worldPos, build, angle, builderObject, NO_ENEMY_OBJECT_OVERLAP, player))
  736. {
  737. return LBC_OBJECTS_IN_THE_WAY;
  738. }
  739. } // end if
  740. if (build->isKindOf(KINDOF_CANNOT_BUILD_NEAR_SUPPLIES) && TheGlobalData->m_SupplyBuildBorder > 0)
  741. {
  742. // special case for supply centers: can't build too close to supply sources
  743. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_SUPPLY_SOURCE), KINDOFMASK_NONE);
  744. PartitionFilter *filters[] = { &f1, NULL };
  745. // see if there are any reasonably close by
  746. Real range = build->getTemplateGeometryInfo().getBoundingCircleRadius() + TheGlobalData->m_SupplyBuildBorder*2;
  747. Object* tooClose = ThePartitionManager->getClosestObject(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
  748. if (tooClose != NULL)
  749. {
  750. // yep, see if we would collide with an expanded version
  751. GeometryInfo tooCloseGeom = tooClose->getGeometryInfo();
  752. tooCloseGeom.expandFootprint(TheGlobalData->m_SupplyBuildBorder);
  753. if (ThePartitionManager->geomCollidesWithGeom(
  754. worldPos,
  755. build->getTemplateGeometryInfo(),
  756. angle,
  757. tooClose->getPosition(),
  758. tooCloseGeom,
  759. tooClose->getOrientation()))
  760. {
  761. TheTerrainVisual->addFactionBib(tooClose, true, TheGlobalData->m_SupplyBuildBorder);
  762. return LBC_TOO_CLOSE_TO_SUPPLIES;
  763. }
  764. }
  765. }
  766. // if clear path is requestsed check to see if the builder object can get there
  767. if( BitTest( options, CLEAR_PATH ) && builderObject )
  768. {
  769. AIUpdateInterface *ai = builderObject->getAIUpdateInterface();
  770. //
  771. // if there is no AI interface for this object, it cannot possible pass a clear path
  772. // check since it will never be able to move there
  773. //
  774. /**todo remove this if we need to change the semantics of this function of the builderObject
  775. // actually being able to get to the destination */
  776. //
  777. if( ai == NULL )
  778. return LBC_NO_CLEAR_PATH;
  779. //
  780. // check for an available path using one of two methods (the quick less accurate one,
  781. // or the slow more accurate one)
  782. //
  783. if( BitTest( options, USE_QUICK_PATHFIND ) )
  784. {
  785. if( ai->isQuickPathAvailable( worldPos ) == FALSE )
  786. return LBC_NO_CLEAR_PATH;
  787. } // end if
  788. else
  789. {
  790. if( ai->isPathAvailable( worldPos ) == FALSE )
  791. return LBC_NO_CLEAR_PATH;
  792. } // end else
  793. } // end if
  794. // check basic terrain restrctions
  795. if( BitTest( options, TERRAIN_RESTRICTIONS ) )
  796. {
  797. //
  798. // we will take "samples" at this resolution across the footprint of where we are
  799. // going to build the structure
  800. //
  801. Real sampleResolution = MAP_XY_FACTOR;
  802. // get the terrain extents
  803. Region3D terrainExtent;
  804. TheTerrainLogic->getExtent( &terrainExtent );
  805. if (TheTerrainLogic->getLayerForDestination(worldPos) != LAYER_GROUND) {
  806. // we're on a bridge. This is somewhat restricted.
  807. return LBC_RESTRICTED_TERRAIN;
  808. }
  809. //
  810. // check the footprint of where the structure would go to be clear of any non-buildable
  811. // tiles and to make sure there isn't a restricted tile and to make sure it's "flat" enough
  812. //
  813. SampleBuildData sampleData;
  814. TheTerrainLogic->getExtent( &sampleData.mapRegion );
  815. sampleData.terrainRestricted = FALSE;
  816. sampleData.hiZ = terrainExtent.lo.z; // note we set hi point to lowest point
  817. sampleData.loZ = terrainExtent.hi.z; // note we set lo point to highest point
  818. // quick check at triple res.
  819. iterateFootprint( build, angle, worldPos, 3*sampleResolution,
  820. checkSampleBuildLocation, &sampleData );
  821. if( sampleData.terrainRestricted == TRUE )
  822. return LBC_RESTRICTED_TERRAIN;
  823. // check if the height across the whole footprint area is too varied (not flat enough)
  824. if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding )
  825. return LBC_NOT_FLAT_ENOUGH;
  826. // careful check at full res.
  827. iterateFootprint( build, angle, worldPos, sampleResolution,
  828. checkSampleBuildLocation, &sampleData );
  829. if( sampleData.terrainRestricted == TRUE )
  830. return LBC_RESTRICTED_TERRAIN;
  831. // check if the height across the whole footprint area is too varied (not flat enough)
  832. if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding )
  833. return LBC_NOT_FLAT_ENOUGH;
  834. } // end if
  835. // we passed all the checks
  836. return LBC_OK;
  837. } // end isLocationLegalToBuild
  838. //-------------------------------------------------------------------------------------------------
  839. /** Adds bibs to structures near to worldPos */
  840. //-------------------------------------------------------------------------------------------------
  841. void BuildAssistant::addBibs(const Coord3D *worldPos,
  842. const ThingTemplate *build )
  843. {
  844. Real range = build->friend_getVisionRange();
  845. range += 3*build->getTemplateGeometryInfo().getMajorRadius();
  846. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_STRUCTURE), KINDOFMASK_NONE);
  847. PartitionFilter *filters[] = { &f1, NULL };
  848. ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
  849. MemoryPoolObjectHolder hold(iter);
  850. for( Object *them = iter->first(); them; them = iter->next() )
  851. {
  852. // ignore any kind of class of objects that we will "remove" for building
  853. if( isRemovableForConstruction( them ) == TRUE )
  854. continue;
  855. if( them->isKindOf( KINDOF_IMMOBILE ) ) {
  856. TheTerrainVisual->addFactionBib(them, true);
  857. }
  858. } // end for, them
  859. }
  860. //-------------------------------------------------------------------------------------------------
  861. /** Tiling wall object helper function, we can use this to "tile" walls when building.
  862. * This function will fill out the ARRAY world positions named 'positions' starting
  863. * at the 'start' point in the world and going to the 'end' point in the world. No
  864. * more than 'maxTiles' will be considered and if the position of any one of
  865. * the objects in the chain is illegal the chain stops there. The number of objects
  866. * actually used for the tiling is returned along with a pointer to the array
  867. * of positions in the tile build info.
  868. *
  869. * REQUIRES: Note that the array at 'postions' must be large enough to hold 'maxTiles'
  870. * entries of positions
  871. */
  872. //-------------------------------------------------------------------------------------------------
  873. BuildAssistant::TileBuildInfo *BuildAssistant::buildTiledLocations( const ThingTemplate *thingBeingTiled,
  874. Real angle,
  875. const Coord3D *start,
  876. const Coord3D *end,
  877. Real tilingSize,
  878. Int maxTiles,
  879. Object *builderObject )
  880. {
  881. // sanity
  882. if( start == NULL || end == NULL )
  883. return 0;
  884. //
  885. // we will fill out our own internal array of positions, it better be big enough to
  886. // accomodate max tiles, if it's not lets make it bigger!
  887. //
  888. if( maxTiles > m_buildPositionSize )
  889. {
  890. // delete the old array
  891. delete [] m_buildPositions;
  892. // create a new one
  893. m_buildPositions = NEW Coord3D[ maxTiles ];
  894. m_buildPositionSize = maxTiles;
  895. //
  896. // lets try to at least keep sanity here so that we don't have a completely unbounded
  897. // allocation spot in the code here
  898. //
  899. DEBUG_ASSERTCRASH( m_buildPositionSize < 200, ("Do you really need to tile this many objects!!!") );
  900. } // end if
  901. Coord3D *positions = m_buildPositions;
  902. // compute a vector from the start of the line in the world to the end
  903. Coord3D placementVector;
  904. placementVector.x = end->x - start->x;
  905. placementVector.y = end->y - start->y;
  906. placementVector.z = 0.0f; //end->z - start->z;
  907. //
  908. // get the lengh of the placement vector in the world, we'll use this to see how
  909. // many objects we'll need to fill the entire line
  910. //
  911. Real placementLength = placementVector.length();
  912. //
  913. // first see how many objects we're going to need to go from the placement
  914. // line start to the placement line end, we are guaranteed to have one thing (+1)
  915. //
  916. Int tilesNeeded = REAL_TO_INT(placementLength / tilingSize) + 1;
  917. // we have a max that we are allowed to build to
  918. if( tilesNeeded > maxTiles )
  919. tilesNeeded = maxTiles;
  920. // we're guaranteed to have at least one object so lets take care of that position now
  921. positions[ 0 ] = *start;
  922. //
  923. // march down each object and set the position based on its position in the line.
  924. // note that we're skipping the first one because we've already set it explicitly
  925. // and are guaranteed to have at least one object
  926. //
  927. Int tilesUsed = 1; // actual objects "tiled", we are guaranteed to have at least one
  928. Coord3D pos;
  929. Coord3D v = placementVector;
  930. v.normalize();
  931. for( Int i = 1; i < tilesNeeded; i++ )
  932. {
  933. // compute position of object
  934. pos.x = v.x * (tilingSize * i) + start->x;
  935. pos.y = v.y * (tilingSize * i) + start->y;
  936. pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y );
  937. // check for a legal position to be at and stop the tiling process if that becomes broken
  938. if( isLocationLegalToBuild( &pos, thingBeingTiled, angle,
  939. BuildAssistant::USE_QUICK_PATHFIND |
  940. BuildAssistant::TERRAIN_RESTRICTIONS |
  941. BuildAssistant::CLEAR_PATH |
  942. BuildAssistant::NO_OBJECT_OVERLAP |
  943. BuildAssistant::SHROUD_REVEALED,
  944. builderObject,
  945. NULL) != LBC_OK )
  946. break;
  947. // save the position in the output array
  948. positions[ i ] = pos;
  949. // we have now actually used one more "tile"
  950. tilesUsed++;
  951. } // end for i
  952. // return a struct filled out with the actual tiles used and the array of locations
  953. static TileBuildInfo tileInfo;
  954. tileInfo.tilesUsed = tilesUsed;
  955. tileInfo.positions = positions;
  956. return &tileInfo;
  957. } // end buildTiledLocations
  958. //-------------------------------------------------------------------------------------------------
  959. /** Is the template passed in one of those wall type structures that we "build" in
  960. * the world after drawing a line of where we want the object(s) placed. The objects
  961. * are placed "tiled" in a line */
  962. //-------------------------------------------------------------------------------------------------
  963. Bool BuildAssistant::isLineBuildTemplate( const ThingTemplate *tTemplate )
  964. {
  965. // sanity
  966. if( tTemplate == NULL )
  967. return FALSE;
  968. if( tTemplate->isKindOf(KINDOF_LINEBUILD))
  969. return TRUE;
  970. return FALSE; // not a line build object
  971. } // end isLineBuildTemplate
  972. //-------------------------------------------------------------------------------------------------
  973. /** This method will check to make sure it is possible to build the requested unit. The
  974. * builder object MUST be present since all construction comes comes from either a Dozer or
  975. * a production building. The player must have satisfied the prereqs for 'whatToBuild'
  976. * This does NOT check available money (see canMakeUnit) */
  977. //-------------------------------------------------------------------------------------------------
  978. Bool BuildAssistant::isPossibleToMakeUnit( Object *builder, const ThingTemplate *whatToBuild ) const
  979. {
  980. // sanity
  981. if( builder == NULL || whatToBuild == NULL )
  982. return FALSE;
  983. // get the command set for the producer object
  984. const CommandSet *commandSet = TheControlBar->findCommandSet( builder->getCommandSetString() );
  985. // if no command set we cannot build anything
  986. if( commandSet == NULL )
  987. {
  988. DEBUG_ASSERTLOG( 0, ("Can't build a '%s' from the builder '%s' because '%s' doesn't have any command set defined\n",
  989. whatToBuild->getName().str(),
  990. builder->getTemplate()->getName().str(),
  991. builder->getTemplate()->getName().str()) );
  992. return FALSE;
  993. } // end if
  994. //
  995. // scan the command set, we must find whatToBuild as one of the "build" commands available
  996. // in the command set. We want to have all players run this logic on all their machines
  997. // so that nobody can hack one game and cheat to make stuff that they can't usually make
  998. //
  999. const CommandButton *commandButton;
  1000. const CommandButton *foundCommand = NULL;
  1001. Int i;
  1002. for( i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  1003. {
  1004. // get this button
  1005. commandButton = commandSet->getCommandButton(i);
  1006. if( commandButton &&
  1007. (commandButton->getCommandType() == GUI_COMMAND_UNIT_BUILD ||
  1008. commandButton->getCommandType() == GUI_COMMAND_DOZER_CONSTRUCT) &&
  1009. commandButton->getThingTemplate()->isEquivalentTo(whatToBuild) )
  1010. foundCommand = commandButton;
  1011. } // end for i
  1012. if( foundCommand == NULL )
  1013. return FALSE;
  1014. // make sure that the player can actually make this unit by checking prereqs and such
  1015. Player *player = builder->getControllingPlayer();
  1016. if( player->canBuild( foundCommand->getThingTemplate() ) == FALSE )
  1017. return FALSE;
  1018. // all is well
  1019. return TRUE;
  1020. }
  1021. // ------------------------------------------------------------------------------------------------
  1022. struct ProductionCountData
  1023. {
  1024. UnsignedInt count;
  1025. const ThingTemplate *type;
  1026. };
  1027. // ------------------------------------------------------------------------------------------------
  1028. /** Count all the units of a given type that are in any production queues for a player */
  1029. // ------------------------------------------------------------------------------------------------
  1030. static void countInProduction( Object *obj, void *userData )
  1031. {
  1032. // only consider objects that have a production update interface
  1033. ProductionUpdateInterface *pui = ProductionUpdate::getProductionUpdateInterfaceFromObject( obj );
  1034. if( pui )
  1035. {
  1036. ProductionCountData *productionCountData = (ProductionCountData *)userData;
  1037. // add the count of this type that are in the queue
  1038. productionCountData->count += pui->countUnitTypeInQueue( productionCountData->type );
  1039. } // end if
  1040. } // end countInProduction
  1041. //-------------------------------------------------------------------------------------------------
  1042. /** This method will check to make sure it is possible to build the requested unit. and
  1043. * that the player has enough money for 'whatToBuild' */
  1044. //-------------------------------------------------------------------------------------------------
  1045. CanMakeType BuildAssistant::canMakeUnit( Object *builder, const ThingTemplate *whatToBuild ) const
  1046. {
  1047. // sanity
  1048. if( builder == NULL || whatToBuild == NULL )
  1049. return CANMAKE_NO_PREREQ;
  1050. if (builder->testScriptStatusBit(OBJECT_STATUS_SCRIPT_DISABLED) || builder->testScriptStatusBit(OBJECT_STATUS_SCRIPT_UNPOWERED))
  1051. return CANMAKE_FACTORY_IS_DISABLED;
  1052. if (!isPossibleToMakeUnit(builder, whatToBuild))
  1053. return CANMAKE_NO_PREREQ;
  1054. ProductionUpdateInterface* pu = builder->getProductionUpdateInterface();
  1055. if (pu != NULL)
  1056. {
  1057. CanMakeType cmt = pu->canQueueCreateUnit(whatToBuild);
  1058. if (cmt != CANMAKE_OK)
  1059. return cmt;
  1060. }
  1061. // make sure we have enough money to build this
  1062. Player *player = builder->getControllingPlayer();
  1063. Money *money = player->getMoney();
  1064. if( whatToBuild->calcCostToBuild( player ) > money->countMoney() )
  1065. return CANMAKE_NO_MONEY;
  1066. // make sure we're not maxed out for this type of unit.
  1067. if (whatToBuild->getMaxSimultaneousOfType() != 0)
  1068. {
  1069. const Bool ignoreDead = true;
  1070. const Bool ignoreUnderConstruction = FALSE;// Most people don't want to count under construction, but I totally do
  1071. Int existingCount;
  1072. player->countObjectsByThingTemplate(1, &whatToBuild, ignoreDead, &existingCount, ignoreUnderConstruction);
  1073. if (existingCount >= whatToBuild->getMaxSimultaneousOfType())
  1074. return CANMAKE_MAXED_OUT_FOR_PLAYER;
  1075. // also check objects that are in production
  1076. ProductionCountData productionCountData;
  1077. productionCountData.count = 0;
  1078. productionCountData.type = whatToBuild;
  1079. player->iterateObjects( countInProduction, &productionCountData );
  1080. if( productionCountData.count + existingCount >= whatToBuild->getMaxSimultaneousOfType() )
  1081. return CANMAKE_MAXED_OUT_FOR_PLAYER;
  1082. }
  1083. // get the command set for the producer object
  1084. return CANMAKE_OK;
  1085. }
  1086. // ------------------------------------------------------------------------------------------------
  1087. /** Some objects will be automatically removed when something is built on them so they
  1088. * aren't considered as obstacles when placing them */
  1089. // ------------------------------------------------------------------------------------------------
  1090. Bool BuildAssistant::isRemovableForConstruction( Object *obj )
  1091. {
  1092. // sanity
  1093. if( obj == NULL )
  1094. return FALSE;
  1095. if (obj->isKindOf(KINDOF_INERT))
  1096. {
  1097. DEBUG_CRASH(("should not have gotten here."));
  1098. return FALSE;
  1099. }
  1100. // all shrubbery can be removed
  1101. if( obj->isKindOf( KINDOF_SHRUBBERY ) )
  1102. return TRUE;
  1103. // anything with a kindof marked as cleared by build can be removed
  1104. if( obj->isKindOf( KINDOF_CLEARED_BY_BUILD ) )
  1105. return TRUE;
  1106. // Rubble, scrap & dead units can be cleared.
  1107. if( obj->isEffectivelyDead( ) )
  1108. return TRUE;
  1109. // not removable
  1110. return FALSE;
  1111. } // end isRemovableForConstruction
  1112. // ------------------------------------------------------------------------------------------------
  1113. /** Given that we are about to build 'whatToBuild' remove all the objects that are in the
  1114. * 'footprint' of where the build will take place that can be auto-removed for construction */
  1115. // ------------------------------------------------------------------------------------------------
  1116. void BuildAssistant::clearRemovableForConstruction( const ThingTemplate *whatToBuild,
  1117. const Coord3D *pos,
  1118. Real angle )
  1119. {
  1120. ObjectIterator *iter =
  1121. ThePartitionManager->iteratePotentialCollisions( pos,
  1122. whatToBuild->getTemplateGeometryInfo(),
  1123. angle );
  1124. MemoryPoolObjectHolder hold( iter );
  1125. for( Object *them = iter->first(); them; them = iter->next() )
  1126. {
  1127. // UI feedback objects (always selectable) never get destroyed by construction, but also should never
  1128. // trip any of the other side effects of placing them in isRemovableForConstruction()
  1129. if( isRemovableForConstruction( them ) == TRUE && !them->isKindOf( KINDOF_ALWAYS_SELECTABLE ) )
  1130. TheGameLogic->destroyObject( them );
  1131. } // end for, them
  1132. } // end clearRemovableForConstruction
  1133. // ------------------------------------------------------------------------------------------------
  1134. /** clearRemovable is set up to delete objects that should cease to exist (for instance, trees).
  1135. * moveObjects will move objects that are owned by the player. It will also return false if
  1136. the association with the object is enemy. (We can move neutral things) */
  1137. // ------------------------------------------------------------------------------------------------
  1138. Bool BuildAssistant::moveObjectsForConstruction( const ThingTemplate *whatToBuild,
  1139. const Coord3D *pos,
  1140. Real angle,
  1141. Player *playerToBuild )
  1142. {
  1143. GeometryInfo gi (GEOMETRY_BOX, false, 10, whatToBuild->getTemplateGeometryInfo().getMajorRadius(),
  1144. whatToBuild->getTemplateGeometryInfo().getMajorRadius());
  1145. if (whatToBuild->getTemplateGeometryInfo().getGeomType()==GEOMETRY_BOX) {
  1146. gi = whatToBuild->getTemplateGeometryInfo();
  1147. }
  1148. ObjectIterator *iter =
  1149. ThePartitionManager->iteratePotentialCollisions( pos,
  1150. gi,
  1151. angle );
  1152. Bool anyUnmovables = false;
  1153. MemoryPoolObjectHolder hold( iter );
  1154. Real radius = sqrt(pow(gi.getMajorRadius(), 2) + pow(gi.getMinorRadius(), 2));
  1155. radius *= 1.4f; // Fudge the distance,
  1156. for( Object *them = iter->first(); them; them = iter->next() )
  1157. {
  1158. //ignore land mines, cluster mines and demo traps, since you can build on them
  1159. // doing so will damage them during construction, by the way
  1160. if (them->isKindOf( KINDOF_MINE ))
  1161. continue;
  1162. // same story for inert things: ie, radiation fields, pings, and projectile streams (!)...
  1163. if (them->isKindOf(KINDOF_INERT))
  1164. continue;
  1165. // Skip KINDOF_ALWAYS_SELECTABLE and isRemovableForConstruction, because if it is
  1166. // RemovableForConstruction, it just got destroyed, and won't actually be gone until
  1167. // the end of the frame. jba.
  1168. if ( !them->isKindOf( KINDOF_ALWAYS_SELECTABLE ) && !isRemovableForConstruction(them) )
  1169. {
  1170. Relationship rel = playerToBuild->getRelationship(them->getTeam());
  1171. if (rel == NEUTRAL || rel == ALLIES)
  1172. {
  1173. // Pick a point outside of the construction and tell those fools to move there.
  1174. // Pick an arbitrary direction and tell the unit to move there, at a distance greater
  1175. // than the object's radius.
  1176. AIUpdateInterface *ai = them->getAIUpdateInterface();
  1177. if (ai)
  1178. {
  1179. // Vary the distance to move between one half the diameter of the building (roughly)
  1180. // and 1.5 times the diameter of the building
  1181. Real variedRadius = GameLogicRandomValueReal(0.5, 1.5) * radius;
  1182. Coord3D destPos;
  1183. Real dir = GameLogicRandomValueReal(-PI, PI);
  1184. Vector3 vec(variedRadius, 0, 0);
  1185. vec.Rotate_Z(dir);
  1186. destPos.x = pos->x + vec.X;
  1187. destPos.y = pos->y + vec.Y;
  1188. destPos.z = pos->z + vec.Z;
  1189. // note that this is an extra-special case... even if the unit's mood is "sleep"
  1190. // it still needs to move here. (units with an ai mood of "sleep" won't respond to
  1191. // any AI commands. this is a special case that ignores the "sleep" ai mood.) (srj)
  1192. ai->aiMoveToPositionEvenIfSleeping(&destPos, CMD_FROM_AI);
  1193. }
  1194. else
  1195. {
  1196. anyUnmovables = true;
  1197. }
  1198. }
  1199. else
  1200. {
  1201. anyUnmovables = true;
  1202. }
  1203. }
  1204. }
  1205. return !anyUnmovables;
  1206. }
  1207. // ------------------------------------------------------------------------------------------------
  1208. /** Start the process for selling the object in question */
  1209. // ------------------------------------------------------------------------------------------------
  1210. void BuildAssistant::sellObject( Object *obj )
  1211. {
  1212. // sanity
  1213. if( obj == NULL )
  1214. return;
  1215. // we can only sell structures ... sanity check this
  1216. if( obj->isKindOf( KINDOF_STRUCTURE ) == FALSE )
  1217. return;
  1218. // if object already has an entry in the sell list, we shouldn't try to sell it again
  1219. ObjectSellInfo *sellInfo = NULL;
  1220. ObjectSellListIterator it;
  1221. for( it = m_sellList.begin(); it != m_sellList.end(); ++it )
  1222. {
  1223. sellInfo = (*it);
  1224. if( sellInfo->m_id == obj->getID() )
  1225. break;
  1226. else
  1227. sellInfo = NULL;
  1228. } // end for
  1229. if( sellInfo != NULL )
  1230. return;
  1231. // set the construction percent of this object just below 100.0% so we can start counting down
  1232. obj->setConstructionPercent( 99.9f );
  1233. // add this object to the list of objects being sold
  1234. sellInfo = newInstance(ObjectSellInfo);
  1235. sellInfo->m_id = obj->getID();
  1236. sellInfo->m_sellFrame = TheGameLogic->getFrame();
  1237. m_sellList.push_front( sellInfo );
  1238. //
  1239. // set the model condition in the drawable for this object that will show the buildup
  1240. // scaffold and adjust the model height by construction percent
  1241. //
  1242. obj->setModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_PARTIALLY_CONSTRUCTED,
  1243. MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED) );
  1244. //
  1245. // set this object as under de-construction (sold). It is still a legal target, since you get the money at
  1246. // the completion of sale.
  1247. //
  1248. obj->setStatus( ObjectStatusBits( OBJECT_STATUS_SOLD | OBJECT_STATUS_UNSELECTABLE ) );
  1249. // for everybody, unselect them at this time. You can't just deselect a drawable. Selection is a logic property.
  1250. TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE);
  1251. //
  1252. // set the animation durations so that the regular build up loop animations can be
  1253. // done a bit faster for selling
  1254. //
  1255. Drawable *draw = obj->getDrawable();
  1256. if( draw )
  1257. draw->setAnimationLoopDuration( (unsigned)(TOTAL_FRAMES_TO_SELL_OBJECT / 2.0f) );
  1258. // We also need to refund all production for the object at start-of-sell time
  1259. ProductionUpdateInterface *production = obj->getProductionUpdateInterface();
  1260. if ( production )
  1261. {
  1262. production->cancelAndRefundAllProduction();
  1263. }
  1264. // Tell the contain module so it can decide what to do.
  1265. ContainModuleInterface* contain = obj->getContain();
  1266. if( contain )
  1267. {
  1268. contain->onSelling();
  1269. }
  1270. // Tell it to stop attacking or anything else it is doing
  1271. if( obj->getAI() )
  1272. obj->getAI()->aiIdle(CMD_FROM_AI);
  1273. // if it has parking places, kill anything parked there.
  1274. for (BehaviorModule** i = obj->getBehaviorModules(); *i; ++i)
  1275. {
  1276. ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface();
  1277. if (pp)
  1278. {
  1279. pp->killAllParkedUnits();
  1280. break;
  1281. }
  1282. }
  1283. // destroy any mines that are owned by this structure, right now.
  1284. // unfortunately, structures don't keep list of mines they own, so we must do
  1285. // this the hard way :-( [fortunately, this doens't happen very often, so this
  1286. // is probably an acceptable, if icky, solution.] (srj)
  1287. for (Object* mine = TheGameLogic->getFirstObject(); mine; mine = mine->getNextObject())
  1288. {
  1289. if (mine->isKindOf(KINDOF_MINE))
  1290. {
  1291. if (mine->getProducerID() == obj->getID())
  1292. {
  1293. TheGameLogic->destroyObject(mine);
  1294. }
  1295. }
  1296. }
  1297. } // end sellObject