BuildAssistant.cpp 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604
  1. /*
  2. ** Command & Conquer Generals Zero Hour(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 constructor Object means a script built building so let it slide.
  284. if( (constructorObject != NULL) && !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. ObjectStatusMaskType startingStatus;
  313. if( what->isKindOf( KINDOF_STRUCTURE ) )
  314. startingStatus.set( MAKE_OBJECT_STATUS_MASK( 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. LegalBuildCode 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. Bool feedbackWithFailure = TRUE;
  551. Relationship rel = builderObject ? builderObject->getRelationship( them ) : NEUTRAL;
  552. //Kris: If the object is stealthed and we can't see it, pretend we can build there.
  553. if( BitTest( options, IGNORE_STEALTHED ) )
  554. {
  555. if( rel != ALLIES )
  556. {
  557. if( them->testStatus( OBJECT_STATUS_STEALTHED ) && !them->testStatus( OBJECT_STATUS_DETECTED ) && !them->testStatus( OBJECT_STATUS_DISGUISED ) )
  558. {
  559. if( BitTest( options, FAIL_STEALTHED_WITHOUT_FEEDBACK ) )
  560. {
  561. feedbackWithFailure = FALSE; //We want to fail now but without feedback
  562. }
  563. else
  564. {
  565. continue;
  566. }
  567. }
  568. }
  569. }
  570. //Kris: Patch 1.01 - November 5, 2003
  571. //Prevent busy units (black lotus hacking from being moved by trying to place a building -- exploit).
  572. if( rel == ALLIES )
  573. {
  574. if( them->testStatus( OBJECT_STATUS_IS_USING_ABILITY ) || them->getAI() && them->getAI()->isBusy() )
  575. {
  576. return LBC_OBJECTS_IN_THE_WAY;
  577. }
  578. }
  579. // ignore any kind of class of objects that we will "remove" for building
  580. if( isRemovableForConstruction( them ) == TRUE )
  581. continue;
  582. // ignore land mines, cluster mines and demo traps, since you can build on them
  583. // doing so will damage them during construction, by the way
  584. if (them->isKindOf( KINDOF_MINE ))
  585. continue;
  586. // same story for inert things: ie, radiation fields, pings, and projectile streams (!)...
  587. if (them->isKindOf(KINDOF_INERT))
  588. continue;
  589. // an immobile object may obstruct our building depending on flags.
  590. if( them->isKindOf( KINDOF_IMMOBILE ) )
  591. {
  592. if (onlyCheckEnemies && builderObject && rel != ENEMIES )
  593. {
  594. continue;
  595. }
  596. if( feedbackWithFailure )
  597. {
  598. TheTerrainVisual->addFactionBib( them, TRUE );
  599. return LBC_OBJECTS_IN_THE_WAY;
  600. }
  601. return LBC_GENERIC_FAILURE;
  602. }
  603. if( them->isDisabled() )
  604. {
  605. //Kris: If object is disabled, it can't move out of the way, thus you can't build there.
  606. if( feedbackWithFailure )
  607. {
  608. TheTerrainVisual->addFactionBib( them, TRUE );
  609. return LBC_OBJECTS_IN_THE_WAY;
  610. }
  611. return LBC_GENERIC_FAILURE;
  612. }
  613. //
  614. // if this is an enemy object of the builder object (and therefore the thing
  615. // that will be constructed) we can't build here
  616. //
  617. if( builderObject && rel == ENEMIES )
  618. {
  619. if( feedbackWithFailure )
  620. {
  621. TheTerrainVisual->addFactionBib( them, TRUE );
  622. return LBC_OBJECTS_IN_THE_WAY;
  623. }
  624. return LBC_GENERIC_FAILURE;
  625. }
  626. } // end for, them
  627. if (onlyCheckEnemies)
  628. {
  629. return LBC_OK;
  630. }
  631. // Check for overlapping exit areas.
  632. Real range = 2*(build->getTemplateGeometryInfo().getMajorRadius()+build->getTemplateGeometryInfo().getMinorRadius());
  633. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_STRUCTURE), KINDOFMASK_NONE);
  634. PartitionFilter *filters[] = { &f1, NULL };
  635. ObjectIterator *iter2 = ThePartitionManager->iterateObjectsInRange(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
  636. MemoryPoolObjectHolder hold2(iter2);
  637. Real myFactoryExitWidth = build->getFactoryExitWidth();
  638. Real myExtraWidth = build->getFactoryExtraBibWidth();
  639. if (thePlayer && thePlayer->isSkirmishAIPlayer()) {
  640. // Skirmish ai adds a little extra around the edges so it doesn't build itself into a corner.
  641. if (myExtraWidth < 3*PATHFIND_CELL_SIZE_F) {
  642. myExtraWidth = 3*PATHFIND_CELL_SIZE_F;
  643. myFactoryExitWidth -= myExtraWidth;
  644. if (myFactoryExitWidth<0) myFactoryExitWidth = 0;
  645. }
  646. }
  647. Bool checkMyExit = false;
  648. Coord3D myExitPos;
  649. GeometryInfo myBounds = build->getTemplateGeometryInfo();
  650. myBounds.setMajorRadius(myBounds.getMajorRadius()+myExtraWidth);
  651. if (myBounds.getGeomType() != GEOMETRY_BOX) {
  652. myBounds.set(GEOMETRY_BOX, false, 40, myBounds.getMajorRadius(), myBounds.getMajorRadius());
  653. } else {
  654. myBounds.setMinorRadius(myBounds.getMinorRadius()+myExtraWidth);
  655. }
  656. GeometryInfo myGeom = build->getTemplateGeometryInfo();
  657. if (myGeom.getGeomType() != GEOMETRY_BOX) {
  658. myGeom.setMinorRadius(myGeom.getMajorRadius());
  659. }
  660. myGeom.setMajorRadius(myFactoryExitWidth/2.0f);
  661. if (myFactoryExitWidth>0) {
  662. myExitPos = *worldPos;
  663. checkMyExit = true;
  664. Real c = (Real)cos(angle);
  665. Real s = (Real)sin(angle);
  666. Real offset = build->getTemplateGeometryInfo().getMajorRadius() + myFactoryExitWidth/2.0f;
  667. myExitPos.x += c*offset;
  668. myExitPos.y += s*offset;
  669. }
  670. for( them = iter2->first(); them; them = iter2->next() )
  671. {
  672. Relationship rel = builderObject ? builderObject->getRelationship( them ) : NEUTRAL;
  673. //Kris: If the building is stealthed and we can't see it, pretend we can build there.
  674. if( BitTest( options, IGNORE_STEALTHED ) )
  675. {
  676. if( rel != ALLIES )
  677. {
  678. if( them->testStatus( OBJECT_STATUS_STEALTHED ) && !them->testStatus( OBJECT_STATUS_DETECTED ) && !them->testStatus( OBJECT_STATUS_DISGUISED ) )
  679. {
  680. continue;
  681. }
  682. }
  683. }
  684. // ignore any kind of class of objects that we will "remove" for building
  685. if( isRemovableForConstruction( them ) == TRUE )
  686. continue;
  687. Real themFactoryExitWidth = them->getTemplate()->getFactoryExitWidth();
  688. Real hisExtraWidth = them->getTemplate()->getFactoryExtraBibWidth();
  689. Bool checkHisExit = false;
  690. Coord3D hisExitPos;
  691. GeometryInfo hisBounds = them->getGeometryInfo();
  692. hisBounds.setMajorRadius(hisBounds.getMajorRadius()+hisExtraWidth);
  693. if (hisBounds.getGeomType() != GEOMETRY_BOX) {
  694. hisBounds.set(GEOMETRY_BOX, false, 40, hisBounds.getMajorRadius(), hisBounds.getMajorRadius());
  695. } else {
  696. hisBounds.setMinorRadius(hisBounds.getMinorRadius()+myExtraWidth);
  697. }
  698. GeometryInfo hisGeom = them->getGeometryInfo();
  699. hisGeom.setMajorRadius(themFactoryExitWidth/2.0f);
  700. if (hisGeom.getGeomType() != GEOMETRY_BOX) {
  701. hisGeom.setMinorRadius(them->getGeometryInfo().getMajorRadius());
  702. }
  703. if (themFactoryExitWidth>0) {
  704. hisExitPos = *them->getPosition();
  705. checkHisExit = true;
  706. Real c = (Real)cos(them->getOrientation());
  707. Real s = (Real)sin(them->getOrientation());
  708. Real offset = them->getGeometryInfo().getMajorRadius() + themFactoryExitWidth/2.0f;
  709. hisExitPos.x += c*offset;
  710. hisExitPos.y += s*offset;
  711. }
  712. if (ThePartitionManager->geomCollidesWithGeom(them->getPosition(), hisBounds, them->getOrientation(),
  713. worldPos, myBounds, angle)) {
  714. TheTerrainVisual->addFactionBib(them, true);
  715. return LBC_OBJECTS_IN_THE_WAY;
  716. }
  717. if (!checkMyExit && !checkHisExit && !hisExtraWidth && !myExtraWidth)
  718. {
  719. continue; // neither has extra exit space.
  720. }
  721. // an immobile object will obstruct our building no matter what team it's on
  722. if ( them->isKindOf( KINDOF_IMMOBILE ) ) {
  723. /* Check for overlap of my exit rectangle to his geom info. */
  724. if (checkMyExit && ThePartitionManager->geomCollidesWithGeom(them->getPosition(), hisBounds, them->getOrientation(),
  725. &myExitPos, myGeom, angle)) {
  726. TheTerrainVisual->addFactionBib(them, true);
  727. return LBC_OBJECTS_IN_THE_WAY;
  728. }
  729. // Check for overlap of his exit rectangle with my geom info
  730. if (checkHisExit && ThePartitionManager->geomCollidesWithGeom(&hisExitPos, hisGeom, them->getOrientation(),
  731. worldPos, myBounds, angle)) {
  732. TheTerrainVisual->addFactionBib(them, true);
  733. return LBC_OBJECTS_IN_THE_WAY;
  734. }
  735. // Check both exit rectangles together.
  736. if (checkMyExit&&checkHisExit&&ThePartitionManager->geomCollidesWithGeom(&hisExitPos, hisGeom, them->getOrientation(),
  737. &myExitPos, myGeom, angle)) {
  738. TheTerrainVisual->addFactionBib(them, true);
  739. return LBC_OBJECTS_IN_THE_WAY;
  740. }
  741. }
  742. } // end for, them
  743. return LBC_OK;
  744. }
  745. //-------------------------------------------------------------------------------------------------
  746. /** Query if we can build at this location. Note that 'build' may be null and is NOT required
  747. * to be valid to know if a location is legal to build at. 'builderObject' is used
  748. * for queries that require a pathfind check and should be NULL if not required */
  749. //-------------------------------------------------------------------------------------------------
  750. LegalBuildCode BuildAssistant::isLocationLegalToBuild( const Coord3D *worldPos,
  751. const ThingTemplate *build,
  752. Real angle,
  753. UnsignedInt options,
  754. Object *builderObject,
  755. Player *player)
  756. {
  757. /* You just can't never build off the map, regardless of options. jba. */
  758. Region3D mapExtent;
  759. TheTerrainLogic->getMaximumPathfindExtent(&mapExtent);
  760. if (!mapExtent.isInRegionNoZ(worldPos)) {
  761. return LBC_RESTRICTED_TERRAIN;
  762. }
  763. // check shroud level
  764. // This should be the first check, since returning other errors for shrouded areas could be used to game the system
  765. if( BitTest( options, SHROUD_REVEALED ) )
  766. {
  767. {
  768. Int x, y;
  769. ThePartitionManager->worldToCell(worldPos->x, worldPos->y, &x, &y);
  770. Int playerIndex = -1;
  771. if (builderObject && builderObject->getControllingPlayer())
  772. playerIndex = builderObject->getControllingPlayer()->getPlayerIndex();
  773. DEBUG_ASSERTCRASH(playerIndex >= 0, ("isLocationLegalToBuild() needs a builderObject with a team to check for shroud"));
  774. if( ThePartitionManager->getShroudStatusForPlayer(playerIndex, x, y) != CELLSHROUD_CLEAR )
  775. {
  776. return LBC_SHROUD;
  777. }
  778. }
  779. }
  780. //
  781. // if NO_OBJECT_OVERLAP is set, we are not allowed to construct 'build' if it would overlap
  782. // any immobile objects, or an enemy object. Friendly objects should politely
  783. // "move out of the way" when you build something where they're standing
  784. //
  785. if( BitTest( options, NO_OBJECT_OVERLAP ) )
  786. {
  787. LegalBuildCode code = isLocationClearOfObjects(worldPos, build, angle, builderObject, options, player);
  788. if( code != LBC_OK )
  789. {
  790. return code;
  791. }
  792. } // end if
  793. //
  794. // if NO_ENEMY_OBJECT_OVERLAP is set, we are not allowed to construct 'build' if it would overlap
  795. // any enemy objects. Friendly objects are ignored.
  796. //
  797. if( BitTest( options, NO_ENEMY_OBJECT_OVERLAP ) )
  798. {
  799. LegalBuildCode code = isLocationClearOfObjects(worldPos, build, angle, builderObject, options, player);
  800. if( code != LBC_OK )
  801. {
  802. return code;
  803. }
  804. } // end if
  805. if (build->isKindOf(KINDOF_CANNOT_BUILD_NEAR_SUPPLIES) && TheGlobalData->m_SupplyBuildBorder > 0)
  806. {
  807. // special case for supply centers: can't build too close to supply sources
  808. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_SUPPLY_SOURCE), KINDOFMASK_NONE);
  809. PartitionFilter *filters[] = { &f1, NULL };
  810. // see if there are any reasonably close by
  811. Real range = build->getTemplateGeometryInfo().getBoundingCircleRadius() + TheGlobalData->m_SupplyBuildBorder*2;
  812. Object* tooClose = ThePartitionManager->getClosestObject(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
  813. if (tooClose != NULL)
  814. {
  815. // yep, see if we would collide with an expanded version
  816. GeometryInfo tooCloseGeom = tooClose->getGeometryInfo();
  817. tooCloseGeom.expandFootprint(TheGlobalData->m_SupplyBuildBorder);
  818. if (ThePartitionManager->geomCollidesWithGeom(
  819. worldPos,
  820. build->getTemplateGeometryInfo(),
  821. angle,
  822. tooClose->getPosition(),
  823. tooCloseGeom,
  824. tooClose->getOrientation()))
  825. {
  826. TheTerrainVisual->addFactionBib(tooClose, true, TheGlobalData->m_SupplyBuildBorder);
  827. return LBC_TOO_CLOSE_TO_SUPPLIES;
  828. }
  829. }
  830. }
  831. // if clear path is requested check to see if the builder object can get there (unless it's a structure)
  832. if( BitTest( options, CLEAR_PATH ) && builderObject && !builderObject->isKindOf( KINDOF_IMMOBILE ) )
  833. {
  834. AIUpdateInterface *ai = builderObject->getAIUpdateInterface();
  835. //
  836. // if there is no AI interface for this object, it cannot possible pass a clear path
  837. // check since it will never be able to move there
  838. //
  839. /**todo remove this if we need to change the semantics of this function of the builderObject
  840. // actually being able to get to the destination */
  841. //
  842. if( ai == NULL )
  843. return LBC_NO_CLEAR_PATH;
  844. if( ai->isQuickPathAvailable( worldPos ) == FALSE )
  845. return LBC_NO_CLEAR_PATH;
  846. } // end if
  847. // check basic terrain restrctions
  848. if( BitTest( options, TERRAIN_RESTRICTIONS ) )
  849. {
  850. //
  851. // we will take "samples" at this resolution across the footprint of where we are
  852. // going to build the structure
  853. //
  854. Real sampleResolution = MAP_XY_FACTOR;
  855. // get the terrain extents
  856. Region3D terrainExtent;
  857. TheTerrainLogic->getExtent( &terrainExtent );
  858. if (TheTerrainLogic->getLayerForDestination(worldPos) != LAYER_GROUND) {
  859. // we're on a bridge. This is somewhat restricted.
  860. return LBC_RESTRICTED_TERRAIN;
  861. }
  862. //
  863. // check the footprint of where the structure would go to be clear of any non-buildable
  864. // tiles and to make sure there isn't a restricted tile and to make sure it's "flat" enough
  865. //
  866. SampleBuildData sampleData;
  867. TheTerrainLogic->getExtent( &sampleData.mapRegion );
  868. sampleData.terrainRestricted = FALSE;
  869. sampleData.hiZ = terrainExtent.lo.z; // note we set hi point to lowest point
  870. sampleData.loZ = terrainExtent.hi.z; // note we set lo point to highest point
  871. // quick check at triple res.
  872. iterateFootprint( build, angle, worldPos, 3*sampleResolution,
  873. checkSampleBuildLocation, &sampleData );
  874. if( sampleData.terrainRestricted == TRUE )
  875. return LBC_RESTRICTED_TERRAIN;
  876. // check if the height across the whole footprint area is too varied (not flat enough)
  877. if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding )
  878. return LBC_NOT_FLAT_ENOUGH;
  879. // careful check at full res.
  880. iterateFootprint( build, angle, worldPos, sampleResolution,
  881. checkSampleBuildLocation, &sampleData );
  882. if( sampleData.terrainRestricted == TRUE )
  883. return LBC_RESTRICTED_TERRAIN;
  884. // check if the height across the whole footprint area is too varied (not flat enough)
  885. if( sampleData.hiZ - sampleData.loZ > TheGlobalData->m_allowedHeightVariationForBuilding )
  886. return LBC_NOT_FLAT_ENOUGH;
  887. } // end if
  888. // we passed all the checks
  889. return LBC_OK;
  890. } // end isLocationLegalToBuild
  891. //-------------------------------------------------------------------------------------------------
  892. /** Adds bibs to structures near to worldPos */
  893. //-------------------------------------------------------------------------------------------------
  894. void BuildAssistant::addBibs(const Coord3D *worldPos,
  895. const ThingTemplate *build )
  896. {
  897. Real range = build->friend_calcVisionRange();
  898. range += 3*build->getTemplateGeometryInfo().getMajorRadius();
  899. PartitionFilterAcceptByKindOf f1(MAKE_KINDOF_MASK(KINDOF_STRUCTURE), KINDOFMASK_NONE);
  900. PartitionFilter *filters[] = { &f1, NULL };
  901. ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange(worldPos, range, FROM_BOUNDINGSPHERE_2D, filters);
  902. MemoryPoolObjectHolder hold(iter);
  903. for( Object *them = iter->first(); them; them = iter->next() )
  904. {
  905. // ignore any kind of class of objects that we will "remove" for building
  906. if( isRemovableForConstruction( them ) == TRUE )
  907. continue;
  908. if( them->isKindOf( KINDOF_IMMOBILE ) ) {
  909. TheTerrainVisual->addFactionBib(them, true);
  910. }
  911. } // end for, them
  912. }
  913. //-------------------------------------------------------------------------------------------------
  914. /** Tiling wall object helper function, we can use this to "tile" walls when building.
  915. * This function will fill out the ARRAY world positions named 'positions' starting
  916. * at the 'start' point in the world and going to the 'end' point in the world. No
  917. * more than 'maxTiles' will be considered and if the position of any one of
  918. * the objects in the chain is illegal the chain stops there. The number of objects
  919. * actually used for the tiling is returned along with a pointer to the array
  920. * of positions in the tile build info.
  921. *
  922. * REQUIRES: Note that the array at 'postions' must be large enough to hold 'maxTiles'
  923. * entries of positions
  924. */
  925. //-------------------------------------------------------------------------------------------------
  926. BuildAssistant::TileBuildInfo *BuildAssistant::buildTiledLocations( const ThingTemplate *thingBeingTiled,
  927. Real angle,
  928. const Coord3D *start,
  929. const Coord3D *end,
  930. Real tilingSize,
  931. Int maxTiles,
  932. Object *builderObject )
  933. {
  934. // sanity
  935. if( start == NULL || end == NULL )
  936. return 0;
  937. //
  938. // we will fill out our own internal array of positions, it better be big enough to
  939. // accomodate max tiles, if it's not lets make it bigger!
  940. //
  941. if( maxTiles > m_buildPositionSize )
  942. {
  943. // delete the old array
  944. delete [] m_buildPositions;
  945. // create a new one
  946. m_buildPositions = NEW Coord3D[ maxTiles ];
  947. m_buildPositionSize = maxTiles;
  948. //
  949. // lets try to at least keep sanity here so that we don't have a completely unbounded
  950. // allocation spot in the code here
  951. //
  952. DEBUG_ASSERTCRASH( m_buildPositionSize < 200, ("Do you really need to tile this many objects!!!") );
  953. } // end if
  954. Coord3D *positions = m_buildPositions;
  955. // compute a vector from the start of the line in the world to the end
  956. Coord3D placementVector;
  957. placementVector.x = end->x - start->x;
  958. placementVector.y = end->y - start->y;
  959. placementVector.z = 0.0f; //end->z - start->z;
  960. //
  961. // get the lengh of the placement vector in the world, we'll use this to see how
  962. // many objects we'll need to fill the entire line
  963. //
  964. Real placementLength = placementVector.length();
  965. //
  966. // first see how many objects we're going to need to go from the placement
  967. // line start to the placement line end, we are guaranteed to have one thing (+1)
  968. //
  969. Int tilesNeeded = REAL_TO_INT(placementLength / tilingSize) + 1;
  970. // we have a max that we are allowed to build to
  971. if( tilesNeeded > maxTiles )
  972. tilesNeeded = maxTiles;
  973. // we're guaranteed to have at least one object so lets take care of that position now
  974. positions[ 0 ] = *start;
  975. //
  976. // march down each object and set the position based on its position in the line.
  977. // note that we're skipping the first one because we've already set it explicitly
  978. // and are guaranteed to have at least one object
  979. //
  980. Int tilesUsed = 1; // actual objects "tiled", we are guaranteed to have at least one
  981. Coord3D pos;
  982. Coord3D v = placementVector;
  983. v.normalize();
  984. for( Int i = 1; i < tilesNeeded; i++ )
  985. {
  986. // compute position of object
  987. pos.x = v.x * (tilingSize * i) + start->x;
  988. pos.y = v.y * (tilingSize * i) + start->y;
  989. pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y );
  990. // check for a legal position to be at and stop the tiling process if that becomes broken
  991. if( isLocationLegalToBuild( &pos, thingBeingTiled, angle,
  992. BuildAssistant::USE_QUICK_PATHFIND |
  993. BuildAssistant::TERRAIN_RESTRICTIONS |
  994. BuildAssistant::CLEAR_PATH |
  995. BuildAssistant::NO_OBJECT_OVERLAP |
  996. BuildAssistant::SHROUD_REVEALED,
  997. builderObject,
  998. NULL) != LBC_OK )
  999. break;
  1000. // save the position in the output array
  1001. positions[ i ] = pos;
  1002. // we have now actually used one more "tile"
  1003. tilesUsed++;
  1004. } // end for i
  1005. // return a struct filled out with the actual tiles used and the array of locations
  1006. static TileBuildInfo tileInfo;
  1007. tileInfo.tilesUsed = tilesUsed;
  1008. tileInfo.positions = positions;
  1009. return &tileInfo;
  1010. } // end buildTiledLocations
  1011. //-------------------------------------------------------------------------------------------------
  1012. /** Is the template passed in one of those wall type structures that we "build" in
  1013. * the world after drawing a line of where we want the object(s) placed. The objects
  1014. * are placed "tiled" in a line */
  1015. //-------------------------------------------------------------------------------------------------
  1016. Bool BuildAssistant::isLineBuildTemplate( const ThingTemplate *tTemplate )
  1017. {
  1018. // sanity
  1019. if( tTemplate == NULL )
  1020. return FALSE;
  1021. if( tTemplate->isKindOf(KINDOF_LINEBUILD))
  1022. return TRUE;
  1023. return FALSE; // not a line build object
  1024. } // end isLineBuildTemplate
  1025. //-------------------------------------------------------------------------------------------------
  1026. /** This method will check to make sure it is possible to build the requested unit. The
  1027. * builder object MUST be present since all construction comes comes from either a Dozer or
  1028. * a production building. The player must have satisfied the prereqs for 'whatToBuild'
  1029. * This does NOT check available money (see canMakeUnit) */
  1030. //-------------------------------------------------------------------------------------------------
  1031. Bool BuildAssistant::isPossibleToMakeUnit( Object *builder, const ThingTemplate *whatToBuild ) const
  1032. {
  1033. // sanity
  1034. if( builder == NULL || whatToBuild == NULL )
  1035. return FALSE;
  1036. // get the command set for the producer object
  1037. const CommandSet *commandSet = TheControlBar->findCommandSet( builder->getCommandSetString() );
  1038. // if no command set we cannot build anything
  1039. if( commandSet == NULL )
  1040. {
  1041. DEBUG_ASSERTLOG( 0, ("Can't build a '%s' from the builder '%s' because '%s' doesn't have any command set defined\n",
  1042. whatToBuild->getName().str(),
  1043. builder->getTemplate()->getName().str(),
  1044. builder->getTemplate()->getName().str()) );
  1045. return FALSE;
  1046. } // end if
  1047. //
  1048. // scan the command set, we must find whatToBuild as one of the "build" commands available
  1049. // in the command set. We want to have all players run this logic on all their machines
  1050. // so that nobody can hack one game and cheat to make stuff that they can't usually make
  1051. //
  1052. const CommandButton *commandButton;
  1053. const CommandButton *foundCommand = NULL;
  1054. Int i;
  1055. for( i = 0; i < MAX_COMMANDS_PER_SET; i++ )
  1056. {
  1057. // get this button
  1058. commandButton = commandSet->getCommandButton(i);
  1059. if( commandButton &&
  1060. (commandButton->getCommandType() == GUI_COMMAND_UNIT_BUILD ||
  1061. commandButton->getCommandType() == GUI_COMMAND_DOZER_CONSTRUCT) &&
  1062. commandButton->getThingTemplate()->isEquivalentTo(whatToBuild) )
  1063. foundCommand = commandButton;
  1064. } // end for i
  1065. if( foundCommand == NULL )
  1066. return FALSE;
  1067. // make sure that the player can actually make this unit by checking prereqs and such
  1068. Player *player = builder->getControllingPlayer();
  1069. if( player->canBuild( foundCommand->getThingTemplate() ) == FALSE )
  1070. return FALSE;
  1071. // all is well
  1072. return TRUE;
  1073. }
  1074. //-------------------------------------------------------------------------------------------------
  1075. /** This method will check to make sure it is possible to build the requested unit. and
  1076. * that the player has enough money for 'whatToBuild' */
  1077. //-------------------------------------------------------------------------------------------------
  1078. CanMakeType BuildAssistant::canMakeUnit( Object *builder, const ThingTemplate *whatToBuild ) const
  1079. {
  1080. // sanity
  1081. if( builder == NULL || whatToBuild == NULL )
  1082. return CANMAKE_NO_PREREQ;
  1083. if (builder->testScriptStatusBit(OBJECT_STATUS_SCRIPT_DISABLED) || builder->testScriptStatusBit(OBJECT_STATUS_SCRIPT_UNPOWERED))
  1084. return CANMAKE_FACTORY_IS_DISABLED;
  1085. ProductionUpdateInterface* pu = builder->getProductionUpdateInterface();
  1086. //If our builder is actually constructing an object via a special power, then allow it if the templates match.
  1087. //It's possible they won't match because a GLA command center could be in "place sneak attack" mode, and queue
  1088. //up a worker in the meantime.
  1089. if( pu && pu->getSpecialPowerConstructionCommandButton() && pu->getSpecialPowerConstructionCommandButton()->getThingTemplate() == whatToBuild )
  1090. {
  1091. return CANMAKE_OK;
  1092. }
  1093. Player *player = builder->getControllingPlayer();
  1094. // make sure we're not maxed out for this type of unit.
  1095. // Warning: isPossibleToMakeUnit() now implicitly calls
  1096. // canBuildMoreOfType(), so do this check first
  1097. if ( player && !player->canBuildMoreOfType( whatToBuild ) )
  1098. return CANMAKE_MAXED_OUT_FOR_PLAYER;
  1099. if (!isPossibleToMakeUnit(builder, whatToBuild))
  1100. return CANMAKE_NO_PREREQ;
  1101. if (pu != NULL)
  1102. {
  1103. CanMakeType cmt = pu->canQueueCreateUnit(whatToBuild);
  1104. if (cmt != CANMAKE_OK)
  1105. return cmt;
  1106. }
  1107. // make sure we have enough money to build this
  1108. Money *money = player->getMoney();
  1109. if( whatToBuild->calcCostToBuild( player ) > money->countMoney() )
  1110. return CANMAKE_NO_MONEY;
  1111. // get the command set for the producer object
  1112. return CANMAKE_OK;
  1113. }
  1114. // ------------------------------------------------------------------------------------------------
  1115. /** Some objects will be automatically removed when something is built on them so they
  1116. * aren't considered as obstacles when placing them */
  1117. // ------------------------------------------------------------------------------------------------
  1118. Bool BuildAssistant::isRemovableForConstruction( Object *obj )
  1119. {
  1120. // sanity
  1121. if( obj == NULL )
  1122. return FALSE;
  1123. if (obj->isKindOf(KINDOF_INERT))
  1124. {
  1125. DEBUG_CRASH(("should not have gotten here."));
  1126. return FALSE;
  1127. }
  1128. // all shrubbery can be removed
  1129. if( obj->isKindOf( KINDOF_SHRUBBERY ) )
  1130. return TRUE;
  1131. // anything with a kindof marked as cleared by build can be removed
  1132. if( obj->isKindOf( KINDOF_CLEARED_BY_BUILD ) )
  1133. return TRUE;
  1134. // Rubble, scrap & dead units can be cleared.
  1135. if( obj->isEffectivelyDead( ) )
  1136. return TRUE;
  1137. // not removable
  1138. return FALSE;
  1139. } // end isRemovableForConstruction
  1140. // ------------------------------------------------------------------------------------------------
  1141. /** Given that we are about to build 'whatToBuild' remove all the objects that are in the
  1142. * 'footprint' of where the build will take place that can be auto-removed for construction */
  1143. // ------------------------------------------------------------------------------------------------
  1144. void BuildAssistant::clearRemovableForConstruction( const ThingTemplate *whatToBuild,
  1145. const Coord3D *pos,
  1146. Real angle )
  1147. {
  1148. ObjectIterator *iter =
  1149. ThePartitionManager->iteratePotentialCollisions( pos,
  1150. whatToBuild->getTemplateGeometryInfo(),
  1151. angle );
  1152. MemoryPoolObjectHolder hold( iter );
  1153. for( Object *them = iter->first(); them; them = iter->next() )
  1154. {
  1155. // UI feedback objects (always selectable) never get destroyed by construction, but also should never
  1156. // trip any of the other side effects of placing them in isRemovableForConstruction()
  1157. if( isRemovableForConstruction( them ) == TRUE && !them->isKindOf( KINDOF_ALWAYS_SELECTABLE ) )
  1158. TheGameLogic->destroyObject( them );
  1159. } // end for, them
  1160. TheTerrainVisual->removeTreesAndPropsForConstruction(pos, whatToBuild->getTemplateGeometryInfo(), angle);
  1161. } // end clearRemovableForConstruction
  1162. // ------------------------------------------------------------------------------------------------
  1163. /** clearRemovable is set up to delete objects that should cease to exist (for instance, trees).
  1164. * moveObjects will move objects that are owned by the player. It will also return false if
  1165. the association with the object is enemy. (We can move neutral things) */
  1166. // ------------------------------------------------------------------------------------------------
  1167. Bool BuildAssistant::moveObjectsForConstruction( const ThingTemplate *whatToBuild,
  1168. const Coord3D *pos,
  1169. Real angle,
  1170. Player *playerToBuild )
  1171. {
  1172. GeometryInfo gi (GEOMETRY_BOX, false, 10, whatToBuild->getTemplateGeometryInfo().getMajorRadius(),
  1173. whatToBuild->getTemplateGeometryInfo().getMajorRadius());
  1174. if (whatToBuild->getTemplateGeometryInfo().getGeomType()==GEOMETRY_BOX) {
  1175. gi = whatToBuild->getTemplateGeometryInfo();
  1176. }
  1177. ObjectIterator *iter =
  1178. ThePartitionManager->iteratePotentialCollisions( pos,
  1179. gi,
  1180. angle );
  1181. Bool anyUnmovables = false;
  1182. MemoryPoolObjectHolder hold( iter );
  1183. Real radius = sqrt(pow(gi.getMajorRadius(), 2) + pow(gi.getMinorRadius(), 2));
  1184. radius *= 1.4f; // Fudge the distance,
  1185. for( Object *them = iter->first(); them; them = iter->next() )
  1186. {
  1187. //ignore land mines, cluster mines and demo traps, since you can build on them
  1188. // doing so will damage them during construction, by the way
  1189. if (them->isKindOf( KINDOF_MINE ))
  1190. continue;
  1191. // same story for inert things: ie, radiation fields, pings, and projectile streams (!)...
  1192. if (them->isKindOf(KINDOF_INERT))
  1193. continue;
  1194. // Skip KINDOF_ALWAYS_SELECTABLE and isRemovableForConstruction, because if it is
  1195. // RemovableForConstruction, it just got destroyed, and won't actually be gone until
  1196. // the end of the frame. jba.
  1197. if ( !them->isKindOf( KINDOF_ALWAYS_SELECTABLE ) && !isRemovableForConstruction(them) )
  1198. {
  1199. Relationship rel = playerToBuild->getRelationship(them->getTeam());
  1200. if (rel == NEUTRAL || rel == ALLIES)
  1201. {
  1202. // Pick a point outside of the construction and tell those fools to move there.
  1203. // Pick an arbitrary direction and tell the unit to move there, at a distance greater
  1204. // than the object's radius.
  1205. AIUpdateInterface *ai = them->getAIUpdateInterface();
  1206. if (ai)
  1207. {
  1208. // Vary the distance to move between one half the diameter of the building (roughly)
  1209. // and 1.5 times the diameter of the building
  1210. Real variedRadius = GameLogicRandomValueReal(0.5, 1.5) * radius;
  1211. Coord3D destPos;
  1212. Real dir = GameLogicRandomValueReal(-PI, PI);
  1213. Vector3 vec(variedRadius, 0, 0);
  1214. vec.Rotate_Z(dir);
  1215. destPos.x = pos->x + vec.X;
  1216. destPos.y = pos->y + vec.Y;
  1217. destPos.z = pos->z + vec.Z;
  1218. // note that this is an extra-special case... even if the unit's mood is "sleep"
  1219. // it still needs to move here. (units with an ai mood of "sleep" won't respond to
  1220. // any AI commands. this is a special case that ignores the "sleep" ai mood.) (srj)
  1221. ai->aiMoveToPositionEvenIfSleeping(&destPos, CMD_FROM_AI);
  1222. }
  1223. else
  1224. {
  1225. anyUnmovables = true;
  1226. }
  1227. }
  1228. else
  1229. {
  1230. anyUnmovables = true;
  1231. }
  1232. }
  1233. }
  1234. return !anyUnmovables;
  1235. }
  1236. // ------------------------------------------------------------------------------------------------
  1237. /** Start the process for selling the object in question */
  1238. // ------------------------------------------------------------------------------------------------
  1239. void BuildAssistant::sellObject( Object *obj )
  1240. {
  1241. // sanity
  1242. if( obj == NULL )
  1243. return;
  1244. // we can only sell structures ... sanity check this
  1245. if( obj->isKindOf( KINDOF_STRUCTURE ) == FALSE )
  1246. return;
  1247. // if object already has an entry in the sell list, we shouldn't try to sell it again
  1248. ObjectSellInfo *sellInfo = NULL;
  1249. ObjectSellListIterator it;
  1250. for( it = m_sellList.begin(); it != m_sellList.end(); ++it )
  1251. {
  1252. sellInfo = (*it);
  1253. if( sellInfo->m_id == obj->getID() )
  1254. break;
  1255. else
  1256. sellInfo = NULL;
  1257. } // end for
  1258. if( sellInfo != NULL )
  1259. return;
  1260. // set the construction percent of this object just below 100.0% so we can start counting down
  1261. obj->setConstructionPercent( 99.9f );
  1262. // add this object to the list of objects being sold
  1263. sellInfo = newInstance(ObjectSellInfo);
  1264. sellInfo->m_id = obj->getID();
  1265. sellInfo->m_sellFrame = TheGameLogic->getFrame();
  1266. m_sellList.push_front( sellInfo );
  1267. //
  1268. // set the model condition in the drawable for this object that will show the buildup
  1269. // scaffold and adjust the model height by construction percent
  1270. //
  1271. obj->setModelConditionFlags( MAKE_MODELCONDITION_MASK2( MODELCONDITION_PARTIALLY_CONSTRUCTED,
  1272. MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED) );
  1273. //
  1274. // set this object as under de-construction (sold). It is still a legal target, since you get the money at
  1275. // the completion of sale.
  1276. //
  1277. obj->setStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_SOLD, OBJECT_STATUS_UNSELECTABLE ) );
  1278. // for everybody, unselect them at this time. You can't just deselect a drawable. Selection is a logic property.
  1279. TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE);
  1280. //
  1281. // set the animation durations so that the regular build up loop animations can be
  1282. // done a bit faster for selling
  1283. //
  1284. Drawable *draw = obj->getDrawable();
  1285. if( draw )
  1286. draw->setAnimationLoopDuration( TOTAL_FRAMES_TO_SELL_OBJECT / 2 );
  1287. // We also need to refund all production for the object at start-of-sell time
  1288. ProductionUpdateInterface *production = obj->getProductionUpdateInterface();
  1289. if ( production )
  1290. {
  1291. production->cancelAndRefundAllProduction();
  1292. }
  1293. // Tell the contain module so it can decide what to do.
  1294. ContainModuleInterface* contain = obj->getContain();
  1295. if( contain )
  1296. {
  1297. contain->onSelling();
  1298. }
  1299. // Tell it to stop attacking or anything else it is doing
  1300. if( obj->getAI() )
  1301. obj->getAI()->aiIdle(CMD_FROM_AI);
  1302. // if it has parking places, kill anything parked there.
  1303. for (BehaviorModule** i = obj->getBehaviorModules(); *i; ++i)
  1304. {
  1305. ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface();
  1306. if (pp)
  1307. {
  1308. pp->killAllParkedUnits();
  1309. break;
  1310. }
  1311. }
  1312. // destroy any mines that are owned by this structure, right now.
  1313. // unfortunately, structures don't keep list of mines they own, so we must do
  1314. // this the hard way :-( [fortunately, this doens't happen very often, so this
  1315. // is probably an acceptable, if icky, solution.] (srj)
  1316. for (Object* mine = TheGameLogic->getFirstObject(); mine; mine = mine->getNextObject())
  1317. {
  1318. if (mine->isKindOf(KINDOF_MINE))
  1319. {
  1320. if (mine->getProducerID() == obj->getID())
  1321. {
  1322. TheGameLogic->destroyObject(mine);
  1323. }
  1324. }
  1325. }
  1326. } // end sellObject