ToppleUpdate.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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: ToppleUpdate.cpp ///////////////////////////////////////////////////////////////////////////
  24. // Author: Graham Smallwood, December 2001
  25. // Desc: Update for something that sits around until hit, then topples and stops
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  29. #include "Common/ThingTemplate.h"
  30. #include "Common/ThingFactory.h"
  31. #include "Common/PlayerList.h"
  32. #include "Common/Player.h"
  33. #include "Common/RandomValue.h"
  34. #include "Common/Xfer.h"
  35. #include "GameClient/FXList.h"
  36. #include "GameLogic/AI.h"
  37. #include "GameLogic/AIPathfind.h"
  38. #include "GameLogic/Module/ToppleUpdate.h"
  39. #include "GameLogic/GameLogic.h"
  40. #include "GameLogic/Object.h"
  41. #include "GameClient/Drawable.h"
  42. #include "GameClient/Module/SwayClientUpdate.h"
  43. #include "GameLogic/Module/PhysicsUpdate.h"
  44. #include "GameLogic/ScriptEngine.h"
  45. #include "GameLogic/Damage.h"
  46. //-------------------------------------------------------------------------------------------------
  47. // this is our "bounce" limit -- slightly less that 90 degrees, to account for slop.
  48. static const Real ANGULAR_LIMIT = PI/2 - PI/64;
  49. //-------------------------------------------------------------------------------------------------
  50. //-------------------------------------------------------------------------------------------------
  51. ToppleUpdateModuleData::ToppleUpdateModuleData()
  52. {
  53. const Real START_VELOCITY_PERCENT = 0.2f;
  54. const Real START_ACCEL_PERCENT = 0.01f;
  55. const Real VELOCITY_BOUNCE_PERCENT = 0.3f; // multiply the velocity by this when you bounce
  56. m_toppleFX = NULL;
  57. m_bounceFX = NULL;
  58. m_stumpName.clear();
  59. m_killWhenToppled = true;
  60. m_killWhenStartToppled = false;
  61. m_killStumpWhenToppled = false;
  62. m_toppleLeftOrRightOnly = false;
  63. m_reorientToppledRubble = false;
  64. m_initialVelocityPercent = START_VELOCITY_PERCENT;
  65. m_initialAccelPercent = START_ACCEL_PERCENT;
  66. m_bounceVelocityPercent = VELOCITY_BOUNCE_PERCENT;
  67. }
  68. //-------------------------------------------------------------------------------------------------
  69. //-------------------------------------------------------------------------------------------------
  70. void ToppleUpdateModuleData::buildFieldParse(MultiIniFieldParse& p)
  71. {
  72. UpdateModuleData::buildFieldParse(p);
  73. static const FieldParse dataFieldParse[] =
  74. {
  75. { "ToppleFX", INI::parseFXList, NULL, offsetof( ToppleUpdateModuleData, m_toppleFX ) },
  76. { "BounceFX", INI::parseFXList, NULL, offsetof( ToppleUpdateModuleData, m_bounceFX ) },
  77. { "StumpName", INI::parseAsciiString, NULL, offsetof( ToppleUpdateModuleData, m_stumpName ) },
  78. { "KillWhenStartToppling", INI::parseBool, NULL, offsetof( ToppleUpdateModuleData, m_killWhenStartToppled ) },
  79. { "KillWhenFinishedToppling", INI::parseBool, NULL, offsetof( ToppleUpdateModuleData, m_killWhenToppled ) },
  80. { "KillStumpWhenToppled", INI::parseBool, NULL, offsetof( ToppleUpdateModuleData, m_killStumpWhenToppled ) },
  81. { "ToppleLeftOrRightOnly", INI::parseBool, NULL, offsetof( ToppleUpdateModuleData, m_toppleLeftOrRightOnly ) },
  82. { "ReorientToppledRubble", INI::parseBool, NULL, offsetof( ToppleUpdateModuleData, m_reorientToppledRubble ) },
  83. { "InitialVelocityPercent", INI::parsePercentToReal, NULL, offsetof( ToppleUpdateModuleData, m_initialVelocityPercent ) },
  84. { "InitialAccelPercent", INI::parsePercentToReal, NULL, offsetof( ToppleUpdateModuleData, m_initialAccelPercent ) },
  85. { "BounceVelocityPercent", INI::parsePercentToReal, NULL, offsetof( ToppleUpdateModuleData, m_bounceVelocityPercent ) },
  86. { 0, 0, 0, 0 }
  87. };
  88. p.add(dataFieldParse);
  89. }
  90. //-------------------------------------------------------------------------------------------------
  91. //-------------------------------------------------------------------------------------------------
  92. //-------------------------------------------------------------------------------------------------
  93. //-------------------------------------------------------------------------------------------------
  94. ToppleUpdate::ToppleUpdate( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
  95. {
  96. //Added By Sadullah Nader
  97. //Initialization(s) inserted
  98. m_angleDeltaX = 0.0f;
  99. m_doBounceFX = FALSE;
  100. m_numAngleDeltaX = 0;
  101. //
  102. m_angularVelocity = 0;
  103. m_angularAccumulation = 0;
  104. m_angularAcceleration = 0;
  105. m_toppleDirection.x = 0;
  106. m_toppleDirection.y = 0;
  107. m_toppleDirection.z = 0;
  108. m_toppleState = TOPPLE_UPRIGHT;
  109. m_options = TOPPLE_OPTIONS_NONE;
  110. m_stumpID = INVALID_ID;
  111. setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
  112. }
  113. //-------------------------------------------------------------------------------------------------
  114. //-------------------------------------------------------------------------------------------------
  115. ToppleUpdate::~ToppleUpdate( void )
  116. {
  117. }
  118. //-------------------------------------------------------------------------------------------------
  119. static Real angleClosestTo(Real a1, Real a2, Real desired)
  120. {
  121. a1 = normalizeAngle(a1);
  122. a2 = normalizeAngle(a2);
  123. return (fabs(stdAngleDiff(desired, a1)) < fabs(stdAngleDiff(desired, a2))) ? a1 : a2;
  124. }
  125. //-------------------------------------------------------------------------------------------------
  126. ///< Start the toppling process by giving a force vector
  127. //-------------------------------------------------------------------------------------------------
  128. void ToppleUpdate::applyTopplingForce( const Coord3D* toppleDirection, Real toppleSpeed,
  129. UnsignedInt options )
  130. {
  131. if (getObject()->isEffectivelyDead())
  132. return;
  133. //DEBUG_LOG(("awaking ToppleUpdate %08lx\n",this));
  134. setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
  135. const ToppleUpdateModuleData* d = getToppleUpdateModuleData();
  136. if (d->m_killWhenStartToppled)
  137. {
  138. setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
  139. getObject()->kill();
  140. return;
  141. }
  142. m_toppleDirection = *toppleDirection;
  143. m_toppleDirection.normalize();
  144. TheScriptEngine->adjustToppleDirection(getObject(), &m_toppleDirection);
  145. m_angularVelocity = toppleSpeed * d->m_initialVelocityPercent;
  146. m_angularAcceleration = toppleSpeed * d->m_initialAccelPercent;
  147. m_toppleState = TOPPLE_FALLING;
  148. m_options = options;
  149. // tell the drawable to stop swaying
  150. Drawable * draw = getObject()->getDrawable();
  151. static NameKeyType nameKeySwayUpdate = NAMEKEY("SwayClientUpdate");
  152. ClientUpdateModule ** clientModules = draw->getClientUpdateModules();
  153. if (clientModules)
  154. {
  155. while (*clientModules)
  156. {
  157. if ((*clientModules)->getModuleNameKey() == nameKeySwayUpdate)
  158. (*(SwayClientUpdate **)clientModules)->stopSway();
  159. ++clientModules;
  160. }
  161. }
  162. // rotate around the z-axis so that our x-axis is perpendicular to the topple direction.
  163. // this is really a trick to ensure that relatively planar things (eg, streetlights)
  164. // fall parallel to the ground, so that they don't up sticking thru the ground.
  165. // yeah, it assumes the models are constructed appropriately, but is a cheap way
  166. // of minimizing the problem. (srj)
  167. Real curAngleX = normalizeAngle(getObject()->getOrientation());
  168. Real toppleAngle = normalizeAngle(atan2(m_toppleDirection.y, m_toppleDirection.x));
  169. if (d->m_toppleLeftOrRightOnly)
  170. {
  171. // it's a fence or such, and can only topple left or right, so pick the closest
  172. toppleAngle = angleClosestTo(curAngleX + PI/2, curAngleX - PI/2, toppleAngle);
  173. m_toppleDirection.x = Cos(toppleAngle);
  174. m_toppleDirection.y = Sin(toppleAngle);
  175. // go ahead and remove it from the pathfinder now, rather than waiting for the topple to
  176. // finish.... since we might be in a slightly different position when toppled, which can
  177. // confuse the pathfinder and not de-obstacle everything correctly
  178. TheAI->pathfinder()->removeObjectFromPathfindMap(getObject());
  179. }
  180. // desired angle is toppleAngle +/- pi/2, whichever is closer to curangle
  181. Real desiredAngleX = angleClosestTo(toppleAngle + PI/2, toppleAngle - PI/2, curAngleX);
  182. m_numAngleDeltaX = REAL_TO_INT_FLOOR(ANGULAR_LIMIT / (m_angularVelocity * 2));
  183. if (m_numAngleDeltaX < 1)
  184. m_numAngleDeltaX = 1;
  185. m_angleDeltaX = (desiredAngleX - curAngleX) / m_numAngleDeltaX;
  186. getObject()->getDrawable()->setModelConditionState(MODELCONDITION_TOPPLED);
  187. FXList::doFXObj(d->m_toppleFX, getObject());
  188. // if this is a tree, create a stump
  189. if (!d->m_stumpName.isEmpty())
  190. {
  191. const ThingTemplate* ttn = TheThingFactory->findTemplate(d->m_stumpName);
  192. Object *stump = TheThingFactory->newObject( ttn, NULL );
  193. if (stump)
  194. {
  195. stump->setPosition( getObject()->getPosition() );
  196. stump->setOrientation( getObject()->getOrientation() );
  197. m_stumpID = stump->getID();
  198. // if we are "burned", then we will burn our stump
  199. const Drawable* draw = getObject()->getDrawable();
  200. if( draw )
  201. {
  202. if( draw->getModelConditionFlags().test( MODELCONDITION_BURNED ) == TRUE )
  203. {
  204. Drawable* stumpDraw = stump->getDrawable();
  205. if( stumpDraw )
  206. stumpDraw->setModelConditionState( MODELCONDITION_BURNED );
  207. }
  208. }
  209. }
  210. }
  211. }
  212. //-------------------------------------------------------------------------------------------------
  213. ///< Ask if this module is able to be toppled
  214. //-------------------------------------------------------------------------------------------------
  215. Bool ToppleUpdate::isAbleToBeToppled() const
  216. {
  217. return m_toppleState == TOPPLE_UPRIGHT;
  218. }
  219. //-------------------------------------------------------------------------------------------------
  220. static void deathByToppling(Object* obj)
  221. {
  222. // use a special "topppling" damage type here so that
  223. // toppled stuff can have different damage/die modules
  224. // for toppled-death vs other-death
  225. DamageInfo damageInfo;
  226. damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
  227. damageInfo.in.m_deathType = DEATH_TOPPLED;
  228. damageInfo.in.m_sourceID = INVALID_ID;
  229. damageInfo.in.m_amount = HUGE_DAMAGE_AMOUNT;
  230. obj->attemptDamage(&damageInfo);
  231. }
  232. //-------------------------------------------------------------------------------------------------
  233. ///< Keep track of rotational fall distance, bounce and/or stop when needed.
  234. //-------------------------------------------------------------------------------------------------
  235. UpdateSleepTime ToppleUpdate::update()
  236. {
  237. //DEBUG_LOG(("updating ToppleUpdate %08lx\n",this));
  238. DEBUG_ASSERTCRASH(m_toppleState != TOPPLE_UPRIGHT, ("hmm, we should be sleeping here"));
  239. if ( (m_toppleState == TOPPLE_UPRIGHT) || (m_toppleState == TOPPLE_DOWN) )
  240. return UPDATE_SLEEP_FOREVER;
  241. const ToppleUpdateModuleData* d = getToppleUpdateModuleData();
  242. const Real VELOCITY_BOUNCE_LIMIT = 0.01f; // if the velocity after a bounce will be this or lower, just stop at zero
  243. const Real VELOCITY_BOUNCE_SOUND_LIMIT = 0.03f; // and if this low, then skip the bounce sound
  244. Object* obj = getObject();
  245. if (m_numAngleDeltaX)
  246. {
  247. Matrix3D xfrm = *obj->getTransformMatrix();
  248. xfrm.In_Place_Pre_Rotate_Z(m_angleDeltaX);
  249. obj->setTransformMatrix(&xfrm);
  250. --m_numAngleDeltaX;
  251. }
  252. Real curVelToUse = m_angularVelocity;
  253. if (m_angularAccumulation + curVelToUse > ANGULAR_LIMIT)
  254. curVelToUse = ANGULAR_LIMIT - m_angularAccumulation;
  255. Matrix3D xfrm = *obj->getTransformMatrix();
  256. xfrm.In_Place_Pre_Rotate_X(-curVelToUse * m_toppleDirection.y);
  257. xfrm.In_Place_Pre_Rotate_Y(curVelToUse * m_toppleDirection.x);
  258. obj->setTransformMatrix(&xfrm);
  259. m_angularAccumulation += curVelToUse;
  260. if ((m_angularAccumulation >= ANGULAR_LIMIT) && (m_angularVelocity > 0))
  261. {
  262. // Hit so either bounce or stop if too little remaining velocity.
  263. m_angularVelocity *= -d->m_bounceVelocityPercent;
  264. if( BitTest( m_options, TOPPLE_OPTIONS_NO_BOUNCE ) == TRUE ||
  265. fabs(m_angularVelocity) < VELOCITY_BOUNCE_LIMIT )
  266. {
  267. // too slow, just stop
  268. m_angularVelocity = 0;
  269. m_toppleState = TOPPLE_DOWN;
  270. if (d->m_killWhenToppled)
  271. {
  272. deathByToppling(obj);
  273. if (d->m_reorientToppledRubble)
  274. {
  275. // we have a separate rubble state that needs to be upright, and centered
  276. // on the new "center" pos...
  277. Vector3 pos;
  278. pos.X = 0;
  279. pos.Y = 0;
  280. pos.Z = obj->getGeometryInfo().getMaxHeightAbovePosition();
  281. Matrix3D::Transform_Vector(*obj->getTransformMatrix(), pos, &pos);
  282. Coord3D tmp;
  283. tmp.x = pos.X;
  284. tmp.y = pos.Y;
  285. tmp.z = pos.Z;
  286. obj->setPosition(&tmp);
  287. // this relies on the fact that setOrientation always forces us straight up in the Z axis!
  288. obj->setOrientation(obj->getOrientation());
  289. }
  290. } // if kill when toppled
  291. if (d->m_killStumpWhenToppled)
  292. {
  293. Object* stump = TheGameLogic->findObjectByID(m_stumpID);
  294. if (stump)
  295. {
  296. deathByToppling(stump);
  297. }
  298. }
  299. }
  300. else if( fabs(m_angularVelocity) >= VELOCITY_BOUNCE_SOUND_LIMIT )
  301. {
  302. // fast enough bounce to warrant the bounce fx
  303. if( BitTest( m_options, TOPPLE_OPTIONS_NO_FX ) == FALSE )
  304. FXList::doFXObj(d->m_bounceFX, obj);
  305. }
  306. }
  307. else
  308. {
  309. m_angularVelocity += m_angularAcceleration;
  310. }
  311. Drawable *draw=obj->getDrawable();
  312. if (draw)
  313. draw->setShadowsEnabled(false);
  314. return UPDATE_SLEEP_NONE;
  315. }
  316. //-------------------------------------------------------------------------------------------------
  317. /** Do the collision */
  318. //-------------------------------------------------------------------------------------------------
  319. void ToppleUpdate::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
  320. {
  321. // Note that other == null means "collide with ground"
  322. //
  323. if (other == NULL)
  324. return;
  325. //@todo JohnA -- Should you get around to adding trees to avoidance pathfinding, then you'll
  326. //want to change this code:
  327. //if( other->getCrusherLevel() > getObject()->getCrushableLevel() ) //<----proper tree method
  328. if( other->getCrusherLevel() > 1 )
  329. {
  330. // Give a vector with direction to thing and my speed.
  331. Coord3D toppleVector = *getObject()->getPosition();
  332. toppleVector.x -= other->getPosition()->x;
  333. toppleVector.y -= other->getPosition()->y;
  334. toppleVector.z = 0;
  335. Coord3D vel;
  336. PhysicsBehavior* phys = other->getPhysics();
  337. if (phys)
  338. vel = *phys->getVelocity();
  339. else
  340. vel.zero();
  341. getObject()->topple( &toppleVector, vel.length(), TOPPLE_OPTIONS_NONE );
  342. }
  343. }
  344. // ------------------------------------------------------------------------------------------------
  345. /** CRC */
  346. // ------------------------------------------------------------------------------------------------
  347. void ToppleUpdate::crc( Xfer *xfer )
  348. {
  349. // extend base class
  350. UpdateModule::crc( xfer );
  351. } // end crc
  352. // ------------------------------------------------------------------------------------------------
  353. /** Xfer method
  354. * Version Info:
  355. * 1: Initial version */
  356. // ------------------------------------------------------------------------------------------------
  357. void ToppleUpdate::xfer( Xfer *xfer )
  358. {
  359. // version
  360. XferVersion currentVersion = 1;
  361. XferVersion version = currentVersion;
  362. xfer->xferVersion( &version, currentVersion );
  363. // extend base class
  364. UpdateModule::xfer( xfer );
  365. // angular velocity
  366. xfer->xferReal( &m_angularVelocity );
  367. // angular acceleration
  368. xfer->xferReal( &m_angularAcceleration );
  369. // topple direction
  370. xfer->xferCoord3D( &m_toppleDirection );
  371. // topple state
  372. xfer->xferUser( &m_toppleState, sizeof( ToppleState ) );
  373. // angluar accumulation
  374. xfer->xferReal( &m_angularAccumulation );
  375. // angle delta X
  376. xfer->xferReal( &m_angleDeltaX );
  377. // num angle delta X
  378. xfer->xferInt( &m_numAngleDeltaX );
  379. // do bounce FX
  380. xfer->xferBool( &m_doBounceFX );
  381. // options
  382. xfer->xferUnsignedInt( &m_options );
  383. // stump id
  384. xfer->xferObjectID( &m_stumpID );
  385. } // end xfer
  386. // ------------------------------------------------------------------------------------------------
  387. /** Load post process */
  388. // ------------------------------------------------------------------------------------------------
  389. void ToppleUpdate::loadPostProcess( void )
  390. {
  391. // extend base class
  392. UpdateModule::loadPostProcess();
  393. } // end loadPostProcess