123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platform/platform.h"
- #include "core/stream/bitStream.h"
- #include "console/console.h"
- #include "console/consoleTypes.h"
- #include "console/engineAPI.h"
- #include "sim/netConnection.h"
- #include "math/mMath.h"
- #include "math/mathIO.h"
- #include "math/mathUtils.h"
- #include "gfx/gfxTransformSaver.h"
- #include "gfx/gfxDrawUtil.h"
- #include "sfx/sfxTrack.h"
- #include "sfx/sfxTypes.h"
- #include "sfx/sfxSystem.h"
- #include "ts/tsShapeInstance.h"
- #include "T3D/fx/explosion.h"
- #include "T3D/proximityMine.h"
- #include "T3D/physics/physicsBody.h"
- const U32 sTriggerCollisionMask = ( VehicleObjectType | PlayerObjectType );
- static S32 gAutoDeleteTicks = 16; // Provides about half a second for all clients to be updated
- // before a mine is deleted on the server.
- //----------------------------------------------------------------------------
- IMPLEMENT_CO_DATABLOCK_V1( ProximityMineData );
- ConsoleDocClass( ProximityMineData,
- "@brief Stores common properties for a ProximityMine.\n\n"
- "@see ProximityMine\n"
- "@ingroup gameObjects\n"
- );
- IMPLEMENT_CALLBACK( ProximityMineData, onTriggered, void, ( ProximityMine* obj, SceneObject *target ),( obj, target ),
- "Callback invoked when an object triggers the ProximityMine.\n\n"
- "@param obj The ProximityMine object\n"
- "@param target The object that triggered the mine\n"
- "@note This callback is only invoked on the server.\n"
- "@see ProximityMine\n"
- );
- IMPLEMENT_CALLBACK( ProximityMineData, onExplode, void, ( ProximityMine* obj, Point3F pos ),( obj, pos ),
- "Callback invoked when a ProximityMine is about to explode.\n\n"
- "@param obj The ProximityMine object\n"
- "@param pos The position of the mine explosion\n"
- "@note This callback is only invoked on the server.\n"
- "@see ProximityMine\n"
- );
- ProximityMineData::ProximityMineData()
- : armingDelay( 0 ),
- armingSequence( -1 ),
- triggerRadius( 5.0f ),
- triggerSpeed( 1.0f ),
- autoTriggerDelay( 0 ),
- triggerOnOwner( false ),
- triggerDelay( 0 ),
- triggerSequence( -1 ),
- explosionOffset( 0.05f )
- {
- INIT_ASSET(ArmSound);
- INIT_ASSET(TriggerSound);
- }
- void ProximityMineData::initPersistFields()
- {
- docsURL;
- Parent::initPersistFields();
- addGroup("Sounds");
- INITPERSISTFIELD_SOUNDASSET(ArmSound, ProximityMineData, "Arming sound for this proximity mine.");
- INITPERSISTFIELD_SOUNDASSET(TriggerSound, ProximityMineData, "Arming sound for this proximity mine.");
- endGroup("Sounds");
- addGroup( "Arming" );
- addField( "armingDelay", TypeF32, Offset(armingDelay, ProximityMineData),
- "Delay (in seconds) from when the mine is placed to when it becomes active." );
- endGroup( "Arming" );
- addGroup( "Triggering" );
- addField( "autoTriggerDelay", TypeF32, Offset(autoTriggerDelay, ProximityMineData),
- "@brief Delay (in seconds) from arming until the mine automatically "
- "triggers and explodes, even if no object has entered the trigger area.\n\n"
- "Set to 0 to disable." );
- addField( "triggerOnOwner", TypeBool, Offset(triggerOnOwner, ProximityMineData),
- "@brief Controls whether the mine can be triggered by the object that owns it.\n\n"
- "For example, a player could deploy mines that are only dangerous to other "
- "players and not himself." );
- addField( "triggerRadius", TypeF32, Offset(triggerRadius, ProximityMineData),
- "Distance at which an activated mine will detect other objects and explode." );
- addField( "triggerSpeed", TypeF32, Offset(triggerSpeed, ProximityMineData),
- "Speed above which moving objects within the trigger radius will trigger the mine" );
- addField( "triggerDelay", TypeF32, Offset(triggerDelay, ProximityMineData),
- "Delay (in seconds) from when the mine is triggered until it explodes." );
- endGroup( "Triggering" );
- addGroup( "Explosion" );
- addField( "explosionOffset", TypeF32, Offset(explosionOffset, ProximityMineData),
- "@brief Offset from the mine's origin where the explosion emanates from."
- "Sometimes a thrown mine may be slightly sunk into the ground. This can be just "
- "enough to cause the explosion to occur under the ground, especially on flat "
- "ground, which can end up blocking the explosion. This offset along the mine's "
- "'up' normal allows you to raise the explosion origin to a better height.");
- endGroup( "Explosion" );
- }
- bool ProximityMineData::preload( bool server, String& errorStr )
- {
- if ( Parent::preload( server, errorStr ) == false )
- return false;
- if ( !server )
- {
- if( !getArmSound() )
- Con::errorf( ConsoleLogEntry::General, "ProximityMineData::preload: Invalid arming sound." );
- if( !getTriggerSound() )
- Con::errorf( ConsoleLogEntry::General, "ProximityMineData::preload: Invalid trigger sound." );
- }
- if ( mShape )
- {
- // Lookup animation sequences
- armingSequence = mShape->findSequence( "armed" );
- triggerSequence = mShape->findSequence( "triggered" );
- }
- return true;
- }
- void ProximityMineData::packData( BitStream* stream )
- {
- Parent::packData( stream );
- stream->write( armingDelay );
- PACKDATA_ASSET(ArmSound);
- stream->write( autoTriggerDelay );
- stream->writeFlag( triggerOnOwner );
- stream->write( triggerRadius );
- stream->write( triggerSpeed );
- stream->write( triggerDelay );
- PACKDATA_ASSET(TriggerSound);
- }
- void ProximityMineData::unpackData( BitStream* stream )
- {
- Parent::unpackData(stream);
- stream->read( &armingDelay );
- UNPACKDATA_ASSET(ArmSound);
- stream->read( &autoTriggerDelay );
- triggerOnOwner = stream->readFlag();
- stream->read( &triggerRadius );
- stream->read( &triggerSpeed );
- stream->read( &triggerDelay );
- UNPACKDATA_ASSET(TriggerSound);
- }
- //----------------------------------------------------------------------------
- IMPLEMENT_CO_NETOBJECT_V1( ProximityMine );
- ConsoleDocClass( ProximityMine,
- "@brief A simple proximity mine.\n\n"
- "Proximity mines can be deployed using the world editor or thrown by an "
- "in-game object. Once armed, any Player or Vehicle object that moves within "
- "the mine's trigger area will cause it to explode.\n\n"
- "Internally, the ProximityMine object transitions through the following states:\n"
- "<ol>\n"
- " <li><b>Thrown</b>: Mine has been thrown, but has not yet attached to a surface</li>\n"
- " <li><b>Deployed</b>: Mine has attached to a surface but is not yet armed. Start "
- "playing the #armingSound and <i>armed</i> sequence.</li>\n"
- " <li><b>Armed</b>: Mine is armed and will trigger if a Vehicle or Player object moves "
- "within the trigger area.</li>\n"
- " <li><b>Triggered</b>: Mine has been triggered and will explode soon. Invoke the "
- "onTriggered callback, and start playing the #triggerSound and <i>triggered</i> "
- "sequence.</li>\n"
- " <li><b>Exploded</b>: Mine has exploded and will be deleted on the server shortly. "
- "Invoke the onExplode callback on the server and generate the explosion effects "
- "on the client.</li>\n"
- "</ol>\n\n"
- "@note Proximity mines with the #static field set to true will start in the "
- "<b>Armed</b> state. Use this for mines placed with the World Editor.\n\n"
- "The shape used for the mine may optionally define the following sequences:\n"
- "<dl>\n"
- " <dt>armed</dt><dd>Sequence to play when the mine is deployed, but before "
- "it becomes active and triggerable (#armingDelay should be set appropriately).</dd>\n"
- " <dt>triggered</dt><dd>Sequence to play when the mine is triggered, just "
- "before it explodes (#triggerDelay should be set appropriately).<dd>\n"
- "</dl>\n\n"
- "@tsexample\n"
- "datablock ProximityMineData( SimpleMine )\n"
- "{\n"
- " // ShapeBaseData fields\n"
- " category = \"Weapon\";\n"
- " shapeFile = \"art/shapes/weapons/misc/proximityMine.dts\";\n\n"
- " // ItemData fields\n"
- " sticky = true;\n\n"
- " // ProximityMineData fields\n"
- " armingDelay = 0.5;\n"
- " armingSound = MineArmedSound;\n\n"
- " autoTriggerDelay = 0;\n"
- " triggerOnOwner = true;\n"
- " triggerRadius = 5.0;\n"
- " triggerSpeed = 1.0;\n"
- " triggerDelay = 0.5;\n"
- " triggerSound = MineTriggeredSound;\n"
- " explosion = RocketLauncherExplosion;\n\n"
- " // dynamic fields\n"
- " pickUpName = \"Proximity Mines\";\n"
- " maxInventory = 20;\n\n"
- " damageType = \"MineDamage\"; // type of damage applied to objects in radius\n"
- " radiusDamage = 30; // amount of damage to apply to objects in radius\n"
- " damageRadius = 8; // search radius to damage objects when exploding\n"
- " areaImpulse = 2000; // magnitude of impulse to apply to objects in radius\n"
- "};\n\n"
- "function ProximityMineData::onTriggered( %this, %obj, %target )\n"
- "{\n"
- " echo( %this.name SPC \"triggered by \" @ %target.getClassName() );\n"
- "}\n\n"
- "function ProximityMineData::onExplode( %this, %obj, %position )\n"
- "{\n"
- " // Damage objects within the mine's damage radius\n"
- " if ( %this.damageRadius > 0 )\n"
- " radiusDamage( %obj.sourceObject, %position, %this.damageRadius, %this.radiusDamage, %this.damageType, %this.areaImpulse );\n"
- "}\n\n"
- "function ProximityMineData::damage( %this, %obj, %position, %source, %amount, %damageType )\n"
- "{\n"
- " // Explode if any damage is applied to the mine\n"
- " %obj.schedule(50 + getRandom(50), explode);\n"
- "}\n\n"
- "%obj = new ProximityMine()\n"
- "{\n"
- " dataBlock = SimpleMine;\n"
- "};\n"
- "@endtsexample\n\n"
- "@see ProximityMineData\n"
- "@ingroup gameObjects\n"
- );
- ProximityMine::ProximityMine()
- {
- mTypeMask |= StaticShapeObjectType;
- mDataBlock = 0;
- mStickyCollisionPos.zero();
- mOwner = NULL;
- mState = Thrown;
- mStateTimeout = 0;
- mAnimThread = NULL;
- // For the Item class
- mSubclassItemHandlesScene = true;
- }
- ProximityMine::~ProximityMine()
- {
- }
- //----------------------------------------------------------------------------
- void ProximityMine::consoleInit()
- {
- Parent::consoleInit();
- Con::addVariable("$ProxMine::autoDeleteTicks", TypeS32, &gAutoDeleteTicks,
- "@brief Number of ticks until an exploded mine is deleted on the server.\n\n"
- "After a mine has exploded it remains in the server's scene graph for a time "
- "to allow its exploded state to be passed along to each client. This variable "
- "controls how long a mine remains before it is deleted. Any client that has not "
- "received the exploded state by then (perhaps due to lag) will not see any "
- "explosion produced by the mine.\n\n"
- "@ingroup GameObjects");
- }
- //----------------------------------------------------------------------------
- bool ProximityMine::onAdd()
- {
- if ( !Parent::onAdd() || !mDataBlock )
- return false;
- addToScene();
- if (isServerObject())
- scriptOnAdd();
- if ( mStatic )
- {
- // static mines are armed immediately
- mState = Deployed;
- mStateTimeout = 0;
- }
- return true;
- }
- bool ProximityMine::onNewDataBlock( GameBaseData* dptr, bool reload )
- {
- mDataBlock = dynamic_cast<ProximityMineData*>( dptr );
- if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
- return false;
- scriptOnNewDataBlock();
- return true;
- }
- void ProximityMine::onRemove()
- {
- scriptOnRemove();
- removeFromScene();
- Parent::onRemove();
- }
- //----------------------------------------------------------------------------
- void ProximityMine::setTransform( const MatrixF& mat )
- {
- ShapeBase::setTransform( mat ); // Skip Item::setTransform as it restricts rotation to the Z axis
- if ( !mStatic )
- {
- mAtRest = false;
- mAtRestCounter = 0;
- }
- if ( mPhysicsRep )
- mPhysicsRep->setTransform( getTransform() );
- setMaskBits( Item::RotationMask | Item::PositionMask | Item::NoWarpMask );
- }
- void ProximityMine::setDeployedPos( const Point3F& pos, const Point3F& normal )
- {
- // Align to deployed surface normal
- MatrixF mat( true );
- MathUtils::getMatrixFromUpVector( normal, &mat );
- mat.setPosition( pos + normal * mObjBox.minExtents.z );
- mDelta.pos = pos;
- mDelta.posVec.set(0, 0, 0);
- ShapeBase::setTransform( mat );
- if ( mPhysicsRep )
- mPhysicsRep->setTransform( getTransform() );
- setMaskBits( DeployedMask );
- }
- void ProximityMine::processTick( const Move* move )
- {
- Parent::processTick( move );
- // Process state machine
- mStateTimeout -= TickSec;
- State lastState = NumStates;;
- while ( mState != lastState )
- {
- lastState = mState;
- switch ( mState )
- {
- case Thrown:
- if ( mAtRest )
- {
- mState = Deployed;
- mStateTimeout = mDataBlock->armingDelay;
- // Get deployed position if mine was not stuck to another surface
- if ( mStickyCollisionPos.isZero() )
- {
- mObjToWorld.getColumn( 2, &mStickyCollisionNormal );
- mObjToWorld.getColumn( 3, &mStickyCollisionPos );
- }
- setDeployedPos( mStickyCollisionPos, mStickyCollisionNormal );
- if ( mDataBlock->armingSequence != -1 )
- {
- mAnimThread = mShapeInstance->addThread();
- mShapeInstance->setSequence( mAnimThread, mDataBlock->armingSequence, 0.0f );
- }
- if ( mDataBlock->getArmSoundProfile() )
- SFX->playOnce( mDataBlock->getArmSoundProfile(), &getRenderTransform() );
- }
- break;
- case Deployed:
- // Timeout into Armed state
- if ( mStateTimeout <= 0 )
- {
- mState = Armed;
- mStateTimeout = mDataBlock->autoTriggerDelay ? mDataBlock->autoTriggerDelay : F32_MAX;
- }
- break;
- case Armed:
- {
- // Check for objects within the trigger area
- Box3F triggerBox( mDataBlock->triggerRadius * 2 );
- triggerBox.setCenter( getTransform().getPosition() );
- SimpleQueryList sql;
- getContainer()->findObjects( triggerBox, sTriggerCollisionMask,
- SimpleQueryList::insertionCallback, &sql );
- for ( S32 i = 0; i < sql.mList.size(); i++ )
- {
- // Detect movement in the trigger area
- if ( ( sql.mList[i] == mOwner && !mDataBlock->triggerOnOwner ) ||
- sql.mList[i]->getVelocity().len() < mDataBlock->triggerSpeed )
- continue;
- // Mine has been triggered
- mShapeInstance->destroyThread( mAnimThread );
- mAnimThread = NULL;
- mState = Triggered;
- mStateTimeout = mDataBlock->triggerDelay;
- if ( mDataBlock->triggerSequence != -1 )
- {
- mAnimThread = mShapeInstance->addThread();
- mShapeInstance->setSequence( mAnimThread, mDataBlock->triggerSequence, 0.0f );
- }
- if ( mDataBlock->getTriggerSoundProfile() )
- SFX->playOnce( mDataBlock->getTriggerSoundProfile(), &getRenderTransform() );
- if ( isServerObject() )
- mDataBlock->onTriggered_callback( this, sql.mList[0] );
- }
- break;
- }
- case Triggered:
- // Timeout into exploded state
- if ( mStateTimeout <= 0 )
- {
- explode();
- }
- break;
- case Exploded:
- // Mine's delete themselves on the server after exploding
- if ( isServerObject() && ( mStateTimeout <= 0 ) )
- {
- deleteObject();
- return;
- }
- break;
- }
- }
- }
- //----------------------------------------------------------------------------
- void ProximityMine::explode()
- {
- // Make sure we don't explode twice
- if ( mState == Exploded )
- {
- return;
- }
- mState = Exploded;
- mStateTimeout = TickSec * gAutoDeleteTicks; // auto-delete on server N ticks after exploding
- // Move the explosion point slightly off the surface to avoid problems with radius damage
- Point3F normal = getTransform().getUpVector();
- Point3F explodePos = getTransform().getPosition() + normal * mDataBlock->explosionOffset;
- if ( isServerObject() )
- {
- // Do what the server needs to do, damage the surrounding objects, etc.
- mDataBlock->onExplode_callback( this, explodePos );
- setMaskBits( ExplosionMask );
- // Wait till the timeout to self delete. This gives the server object time
- // to get ghosted to the client
- }
- else
- {
- // Client just plays the explosion effect at the right place
- if ( mDataBlock->explosion )
- {
- Explosion *pExplosion = new Explosion;
- pExplosion->onNewDataBlock( mDataBlock->explosion, false );
- MatrixF xform( true );
- xform.setPosition( explodePos );
- pExplosion->setTransform( xform );
- pExplosion->setInitialState( explodePos, normal );
- pExplosion->setCollideType( sTriggerCollisionMask );
- if ( pExplosion->registerObject() == false )
- {
- Con::errorf( ConsoleLogEntry::General, "ProximityMine(%s)::explode: couldn't register explosion",
- mDataBlock->getName() );
- delete pExplosion;
- }
- }
- }
- }
- void ProximityMine::advanceTime( F32 dt )
- {
- Parent::advanceTime( dt );
- if ( mAnimThread )
- mShapeInstance->advancePos( dt, mAnimThread );
- }
- //----------------------------------------------------------------------------
- U32 ProximityMine::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
- {
- // Handle rotation ourselves (so it is not locked to the Z axis like for Items)
- U32 retMask = Parent::packUpdate( connection, mask & (~Item::RotationMask), stream );
- if ( stream->writeFlag( mask & Item::RotationMask ) )
- {
- QuatF rot( mObjToWorld );
- mathWrite( *stream, rot );
- }
- if ( stream->writeFlag( !mStatic && ( mask & DeployedMask ) && ( mState > Thrown ) ) )
- {
- mathWrite( *stream, mStickyCollisionPos );
- mathWrite( *stream, mStickyCollisionNormal );
- }
- stream->writeFlag( ( mask & ExplosionMask ) && ( mState == Exploded ) );
- return retMask;
- }
- void ProximityMine::unpackUpdate( NetConnection* connection, BitStream* stream )
- {
- Parent::unpackUpdate( connection, stream );
- // Item::RotationMask
- if ( stream->readFlag() )
- {
- QuatF rot;
- mathRead( *stream, &rot );
- Point3F pos = mObjToWorld.getPosition();
- rot.setMatrix( &mObjToWorld );
- mObjToWorld.setPosition( pos );
- }
- // !mStatic && ( mask & DeployedMask ) && ( mState > Thrown )
- if ( stream->readFlag() )
- {
- mathRead( *stream, &mStickyCollisionPos );
- mathRead( *stream, &mStickyCollisionNormal );
- mAtRest = true;
- setDeployedPos( mStickyCollisionPos, mStickyCollisionNormal );
- }
- // ( mask & ExplosionMask ) && ( mState == Exploded )
- if ( stream->readFlag() )
- {
- // start the explosion visuals on the client
- explode();
- }
- if ( mStatic && mState <= Deployed )
- {
- // static mines are armed immediately
- mState = Deployed;
- mStateTimeout = 0;
- }
- }
- //----------------------------------------------------------------------------
- void ProximityMine::prepRenderImage( SceneRenderState* state )
- {
- // Don't render the mine if exploded
- if ( mState == Exploded )
- return;
- // Use ShapeBase to render the 3D shape
- Parent::prepRenderImage( state );
- // Add a custom render instance to draw the trigger area
- if ( !state->isShadowPass() )
- {
- ObjectRenderInst* ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
- ri->renderDelegate.bind( this, &ProximityMine::renderObject );
- ri->type = RenderPassManager::RIT_ObjectTranslucent;
- ri->translucentSort = true;
- ri->defaultKey = 1;
- state->getRenderPass()->addInst( ri );
- }
- }
- void ProximityMine::renderObject( ObjectRenderInst* ri,
- SceneRenderState* state,
- BaseMatInstance* overrideMat )
- {
- if ( overrideMat )
- return;
- /*
- // Render the trigger area
- if ( mState == Armed || mState == Triggered )
- {
- const LinearColorF drawColor(1, 0, 0, 0.05f);
- if ( drawColor.alpha > 0 )
- {
- GFXStateBlockDesc desc;
- desc.setZReadWrite( true, false );
- desc.setBlend( true );
- GFXTransformSaver saver;
- MatrixF mat = getRenderTransform();
- mat.scale( getScale() );
- GFX->getDrawUtil()->drawSphere( desc, mDataBlock->triggerRadius, mat.getPosition(),
- drawColor, true, false, &mat );
- }
- }
- */
- }
- //----------------------------------------------------------------------------
- DefineEngineMethod( ProximityMine, explode, void, (),,
- "@brief Manually cause the mine to explode.\n\n")
- {
- object->explode();
- }
|