Browse Source

Cleaned up implementation of #712
Also updates other game classes like the shapebase to utilize the sound asset hooks properly.

JeffR 3 years ago
parent
commit
15ef8b4fbe

+ 8 - 5
Engine/source/T3D/assets/SoundAsset.cpp

@@ -218,6 +218,9 @@ bool SoundAsset::loadSound()
       {
       {
          Con::errorf("SoundAsset::initializeAsset: Attempted to load file %s but it was not valid!", mSoundFile);
          Con::errorf("SoundAsset::initializeAsset: Attempted to load file %s but it was not valid!", mSoundFile);
          mLoadedState = BadFileReference;
          mLoadedState = BadFileReference;
+         mSFXProfile.setDescription(NULL);
+         mSFXProfile.setSoundFileName(StringTable->insert(StringTable->EmptyString()));
+         mSFXProfile.setPreload(false);
          return false;
          return false;
       }
       }
       else
       else
@@ -257,7 +260,7 @@ StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName)
    if (fileName == StringTable->EmptyString())
    if (fileName == StringTable->EmptyString())
       return StringTable->EmptyString();
       return StringTable->EmptyString();
 
 
-   StringTableEntry materialAssetId = "";
+   StringTableEntry soundAssetId = StringTable->EmptyString();
 
 
    AssetQuery query;
    AssetQuery query;
    U32 foundCount = AssetDatabase.findAssetType(&query, "SoundAsset");
    U32 foundCount = AssetDatabase.findAssetType(&query, "SoundAsset");
@@ -268,7 +271,7 @@ StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName)
          SoundAsset* soundAsset = AssetDatabase.acquireAsset<SoundAsset>(query.mAssetList[i]);
          SoundAsset* soundAsset = AssetDatabase.acquireAsset<SoundAsset>(query.mAssetList[i]);
          if (soundAsset && soundAsset->getSoundPath() == fileName)
          if (soundAsset && soundAsset->getSoundPath() == fileName)
          {
          {
-            materialAssetId = soundAsset->getAssetId();
+            soundAssetId = soundAsset->getAssetId();
             AssetDatabase.releaseAsset(query.mAssetList[i]);
             AssetDatabase.releaseAsset(query.mAssetList[i]);
             break;
             break;
          }
          }
@@ -276,7 +279,7 @@ StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName)
       }
       }
    }
    }
 
 
-   return materialAssetId;
+   return soundAssetId;
 }
 }
 
 
 U32 SoundAsset::getAssetById(StringTableEntry assetId, AssetPtr<SoundAsset>* soundAsset)
 U32 SoundAsset::getAssetById(StringTableEntry assetId, AssetPtr<SoundAsset>* soundAsset)
@@ -330,8 +333,8 @@ DefineEngineMethod(SoundAsset, getSoundPath, const char*, (), , "")
 }
 }
 
 
 DefineEngineMethod(SoundAsset, playSound, S32, (Point3F position), (Point3F::Zero),
 DefineEngineMethod(SoundAsset, playSound, S32, (Point3F position), (Point3F::Zero),
-   "Gets the number of materials for this shape asset.\n"
-   "@return Material count.\n")
+   "Plays the sound for this asset.\n"
+   "@return (sound plays).\n")
 {
 {
    if (object->getSfxProfile())
    if (object->getSfxProfile())
    {
    {

+ 6 - 3
Engine/source/T3D/fx/explosion.cpp

@@ -859,9 +859,6 @@ bool ExplosionData::preload(bool server, String &errorStr)
    if (Parent::preload(server, errorStr) == false)
    if (Parent::preload(server, errorStr) == false)
       return false;
       return false;
 
 
-   if (!server && !getSoundProfile())
-      return false;
-
    if( !server )
    if( !server )
    {
    {
 
 
@@ -870,12 +867,18 @@ bool ExplosionData::preload(bool server, String &errorStr)
          _setSound(getSound());
          _setSound(getSound());
 
 
          if (!getSoundProfile())
          if (!getSoundProfile())
+         {
             Con::errorf(ConsoleLogEntry::General, "SplashData::preload: Cant get an sfxProfile for splash.");
             Con::errorf(ConsoleLogEntry::General, "SplashData::preload: Cant get an sfxProfile for splash.");
+            return false;
+         }
       }
       }
 
 
       if (!particleEmitter && particleEmitterId != 0)
       if (!particleEmitter && particleEmitterId != 0)
          if (Sim::findObject(particleEmitterId, particleEmitter) == false)
          if (Sim::findObject(particleEmitterId, particleEmitter) == false)
+         {
             Con::errorf(ConsoleLogEntry::General, "Error, unable to load particle emitter for explosion datablock");
             Con::errorf(ConsoleLogEntry::General, "Error, unable to load particle emitter for explosion datablock");
+            return false;
+         }
    }
    }
 
 
    if (mExplosionShapeAsset.notNull()) {
    if (mExplosionShapeAsset.notNull()) {

+ 50 - 33
Engine/source/T3D/gameBase/gameConnection.cpp

@@ -1562,33 +1562,50 @@ void GameConnection::packetDropped(PacketNotify *note)
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 
 
-void GameConnection::play2D(SFXProfile* profile)
+void GameConnection::play2D(StringTableEntry assetId)
 {
 {
-   postNetEvent(new Sim2DAudioEvent(profile));
+   if (AssetDatabase.isDeclaredAsset(assetId))
+   {
+
+      AssetPtr<SoundAsset> tempSoundAsset;
+      tempSoundAsset = assetId;
+
+      postNetEvent(new SimSoundAssetEvent(tempSoundAsset));
+
+   }
 }
 }
 
 
-void GameConnection::play3D(SFXProfile* profile, const MatrixF *transform)
+void GameConnection::play3D(StringTableEntry assetId, const MatrixF *transform)
 {
 {
    if ( !transform )
    if ( !transform )
-      play2D(profile);
-
-   else if ( !mControlObject )
-      postNetEvent(new Sim3DAudioEvent(profile,transform));
+      play2D(assetId);
 
 
-   else
+   if (AssetDatabase.isDeclaredAsset(assetId))
    {
    {
-      // TODO: Maybe improve this to account for the duration
-      // of the sound effect and if the control object can get
-      // into hearing range within time?
 
 
-      // Only post the event if it's within audible range
-      // of the control object.
-      Point3F ear,pos;
-      transform->getColumn(3,&pos);
-      mControlObject->getTransform().getColumn(3,&ear);
-      if ((ear - pos).len() < profile->getDescription()->mMaxDistance)
-         postNetEvent(new Sim3DAudioEvent(profile,transform));
-   } 
+      AssetPtr<SoundAsset> tempSoundAsset;
+      tempSoundAsset = assetId;
+
+      if (!mControlObject)
+         postNetEvent(new SimSoundAssetEvent(tempSoundAsset, transform));
+      else
+      {
+         // TODO: Maybe improve this to account for the duration
+         // of the sound effect and if the control object can get
+         // into hearing range within time?
+
+         // Only post the event if it's within audible range
+         // of the control object.
+         tempSoundAsset->getSfxDescription();
+         Point3F ear, pos;
+         transform->getColumn(3, &pos);
+         mControlObject->getTransform().getColumn(3, &ear);
+         if ((ear - pos).len() < tempSoundAsset->getSfxDescription()->mMaxDistance)
+            postNetEvent(new SimSoundAssetEvent(tempSoundAsset, transform));
+      }
+
+   }
+
 }
 }
 
 
 void GameConnection::doneScopingScene()
 void GameConnection::doneScopingScene()
@@ -2010,49 +2027,49 @@ DefineEngineMethod( GameConnection, isControlObjectRotDampedCamera, bool, (),,
    return object->isControlObjectRotDampedCamera();
    return object->isControlObjectRotDampedCamera();
 }
 }
 
 
-DefineEngineMethod( GameConnection, play2D, bool, (SFXProfile* profile),,
+DefineEngineMethod( GameConnection, play2D, bool, (StringTableEntry assetId),,
    "@brief Used on the server to play a 2D sound that is not attached to any object.\n\n"
    "@brief Used on the server to play a 2D sound that is not attached to any object.\n\n"
 
 
-   "@param profile The SFXProfile that defines the sound to play.\n\n"
+   "@param assetID The SoundAsset ID that defines the sound to play.\n"
 
 
    "@tsexample\n"
    "@tsexample\n"
-   "function ServerPlay2D(%profile)\n"
+   "function ServerPlay2D(%assetId)\n"
    "{\n"
    "{\n"
-   "   // Play the given sound profile on every client.\n"
+   "   // Play the given sound asset on every client.\n"
    "   // The sounds will be transmitted as an event, not attached to any object.\n"
    "   // The sounds will be transmitted as an event, not attached to any object.\n"
    "   for(%idx = 0; %idx < ClientGroup.getCount(); %idx++)\n"
    "   for(%idx = 0; %idx < ClientGroup.getCount(); %idx++)\n"
-   "      ClientGroup.getObject(%idx).play2D(%profile);\n"
+   "      ClientGroup.getObject(%idx).play2D(%assetId);\n"
    "}\n"
    "}\n"
    "@endtsexample\n\n")
    "@endtsexample\n\n")
 {
 {
-   if(!profile)
+   if(assetId == StringTable->EmptyString())
       return false;
       return false;
 
 
-   object->play2D(profile);
+   object->play2D(assetId);
    return true;
    return true;
 }
 }
 
 
-DefineEngineMethod( GameConnection, play3D, bool, (SFXProfile* profile, TransformF location),,
+DefineEngineMethod( GameConnection, play3D, bool, (StringTableEntry assetId, TransformF location),,
    "@brief Used on the server to play a 3D sound that is not attached to any object.\n\n"
    "@brief Used on the server to play a 3D sound that is not attached to any object.\n\n"
    
    
-   "@param profile The SFXProfile that defines the sound to play.\n"
+   "@param assetID The SoundAsset ID that defines the sound to play.\n"
    "@param location The position and orientation of the 3D sound given in the form of \"x y z ax ay az aa\".\n\n"
    "@param location The position and orientation of the 3D sound given in the form of \"x y z ax ay az aa\".\n\n"
 
 
    "@tsexample\n"
    "@tsexample\n"
-   "function ServerPlay3D(%profile,%transform)\n"
+   "function ServerPlay3D(%assetId,%transform)\n"
    "{\n"
    "{\n"
-   "   // Play the given sound profile at the given position on every client\n"
+   "   // Play the given sound asset at the given position on every client\n"
    "   // The sound will be transmitted as an event, not attached to any object.\n"
    "   // The sound will be transmitted as an event, not attached to any object.\n"
    "   for(%idx = 0; %idx < ClientGroup.getCount(); %idx++)\n"
    "   for(%idx = 0; %idx < ClientGroup.getCount(); %idx++)\n"
-   "      ClientGroup.getObject(%idx).play3D(%profile,%transform);\n"
+   "      ClientGroup.getObject(%idx).play3D(%assetID,%transform);\n"
    "}\n"
    "}\n"
    "@endtsexample\n\n")
    "@endtsexample\n\n")
 {
 {
-   if(!profile)
+   if(assetId == StringTable->EmptyString())
       return false;
       return false;
 
 
    MatrixF mat = location.getMatrix();
    MatrixF mat = location.getMatrix();
-   object->play3D(profile,&mat);
+   object->play3D(assetId,&mat);
    return true;
    return true;
 }
 }
 
 

+ 2 - 2
Engine/source/T3D/gameBase/gameConnection.h

@@ -352,8 +352,8 @@ public:
    /// @name Sound
    /// @name Sound
    /// @{
    /// @{
 
 
-   void play2D(SFXProfile *profile);
-   void play3D(SFXProfile *profile, const MatrixF *transform);
+   void play2D(StringTableEntry assetId);
+   void play3D(StringTableEntry assetId, const MatrixF *transform);
    /// @}
    /// @}
 
 
    /// @name Misc.
    /// @name Misc.

+ 99 - 5
Engine/source/T3D/gameBase/gameConnectionEvents.cpp

@@ -47,6 +47,7 @@
 
 
 //--------------------------------------------------------------------------
 //--------------------------------------------------------------------------
 IMPLEMENT_CO_CLIENTEVENT_V1(SimDataBlockEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(SimDataBlockEvent);
+IMPLEMENT_CO_CLIENTEVENT_V1(SimSoundAssetEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(Sim2DAudioEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(Sim2DAudioEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(Sim3DAudioEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(Sim3DAudioEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(SetMissionCRCEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(SetMissionCRCEvent);
@@ -293,6 +294,104 @@ void SimDataBlockEvent::process(NetConnection *cptr)
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 
 
+static F32 SoundPosAccuracy = 0.5;
+static S32 SoundRotBits = 8;
+
+SimSoundAssetEvent::SimSoundAssetEvent(AssetPtr<SoundAsset> asset, const MatrixF* mat)
+{
+   // cant get here unless the asset is declared.
+   mAsset = asset;
+
+   if (mat)
+      mTransform = *mat;
+}
+
+void SimSoundAssetEvent::pack(NetConnection* con, BitStream* stream)
+{
+   NetStringHandle assetIdStr = mAsset->getAssetId();
+   con->packNetStringHandleU(stream, assetIdStr);
+
+   // only stream if this is a 3d sound asset.
+   if (mAsset->is3D())
+   {
+      SFXDescription* ad = mAsset->getSfxDescription();
+      if (stream->writeFlag(ad->mConeInsideAngle || ad->mConeOutsideAngle))
+      {
+         QuatF q(mTransform);
+         q.normalize();
+
+         // LH - we can get a valid quat that's very slightly over 1 in and so
+         // this fails (barely) check against zero.  So use some error-
+         AssertFatal((1.0 - ((q.x * q.x) + (q.y * q.y) + (q.z * q.z))) >= (0.0 - 0.001),
+            "QuatF::normalize() is broken in Sim3DAudioEvent");
+
+         stream->writeSignedFloat(q.x, SoundRotBits);
+         stream->writeSignedFloat(q.y, SoundRotBits);
+         stream->writeSignedFloat(q.z, SoundRotBits);
+         stream->writeFlag(q.w < 0.0);
+      }
+
+      Point3F pos;
+      mTransform.getColumn(3, &pos);
+      stream->writeCompressedPoint(pos, SoundPosAccuracy);
+   }
+
+}
+
+void SimSoundAssetEvent::write(NetConnection* con, BitStream* stream)
+{
+   // Just do the normal pack...
+   pack(con, stream);
+}
+
+void SimSoundAssetEvent::unpack(NetConnection* con, BitStream* stream)
+{
+
+   StringTableEntry temp = StringTable->insert(con->unpackNetStringHandleU(stream).getString());
+   if (AssetDatabase.isDeclaredAsset(temp))
+   {
+      AssetPtr<SoundAsset> tempSoundAsset;
+      tempSoundAsset = temp;
+
+      mAsset = temp;
+   }
+
+   if (mAsset->is3D())
+   {
+      if (stream->readFlag()) {
+         QuatF q;
+         q.x = stream->readSignedFloat(SoundRotBits);
+         q.y = stream->readSignedFloat(SoundRotBits);
+         q.z = stream->readSignedFloat(SoundRotBits);
+         F32 value = ((q.x * q.x) + (q.y * q.y) + (q.z * q.z));
+         // #ifdef __linux
+               // Hmm, this should never happen, but it does...
+         if (value > 1.f)
+            value = 1.f;
+         // #endif
+         q.w = mSqrt(1.f - value);
+         if (stream->readFlag())
+            q.w = -q.w;
+         q.setMatrix(&mTransform);
+      }
+      else
+         mTransform.identity();
+
+      Point3F pos;
+      stream->readCompressedPoint(&pos, SoundPosAccuracy);
+      mTransform.setColumn(3, pos);
+   }
+}
+
+void SimSoundAssetEvent::process(NetConnection* con)
+{
+
+   if (mAsset->is3D())
+      SFX->playOnce(mAsset->getSfxProfile(), &mTransform);
+   else
+      SFX->playOnce(mAsset->getSfxProfile());
+
+}
 
 
 Sim2DAudioEvent::Sim2DAudioEvent(SFXProfile *profile)
 Sim2DAudioEvent::Sim2DAudioEvent(SFXProfile *profile)
 {
 {
@@ -321,11 +420,6 @@ void Sim2DAudioEvent::process(NetConnection *)
       SFX->playOnce( mProfile );
       SFX->playOnce( mProfile );
 }
 }
 
 
-//----------------------------------------------------------------------------
-
-static F32 SoundPosAccuracy = 0.5;
-static S32 SoundRotBits = 8;
-
 Sim3DAudioEvent::Sim3DAudioEvent(SFXProfile *profile,const MatrixF* mat)
 Sim3DAudioEvent::Sim3DAudioEvent(SFXProfile *profile,const MatrixF* mat)
 {
 {
    mProfile = profile;
    mProfile = profile;

+ 20 - 0
Engine/source/T3D/gameBase/gameConnectionEvents.h

@@ -39,6 +39,9 @@
 #include "core/stream/bitStream.h"
 #include "core/stream/bitStream.h"
 #endif
 #endif
 
 
+#include "T3D/assets/SoundAsset.h"
+
+
 
 
 class QuitEvent : public SimEvent
 class QuitEvent : public SimEvent
 {
 {
@@ -102,6 +105,23 @@ class SimDataBlockEvent : public NetEvent
       DECLARE_CATEGORY( "Game Networking" );
       DECLARE_CATEGORY( "Game Networking" );
 };
 };
 
 
+class SimSoundAssetEvent : public NetEvent
+{
+private:
+   AssetPtr<SoundAsset> mAsset;
+   MatrixF mTransform;
+
+public:
+   typedef NetEvent Parent;
+   
+   SimSoundAssetEvent(AssetPtr<SoundAsset> asset = NULL, const MatrixF* mat = NULL);
+   void pack(NetConnection*, BitStream* bstream);
+   void write(NetConnection*, BitStream* bstream);
+   void unpack(NetConnection*, BitStream* bstream);
+   void process(NetConnection*);
+   DECLARE_CONOBJECT(SimSoundAssetEvent);
+};
+
 class Sim2DAudioEvent: public NetEvent
 class Sim2DAudioEvent: public NetEvent
 {
 {
   private:
   private:

+ 41 - 21
Engine/source/T3D/shapeBase.cpp

@@ -1009,7 +1009,7 @@ ShapeBase::ShapeBase()
 
 
    for (i = 0; i < MaxSoundThreads; i++) {
    for (i = 0; i < MaxSoundThreads; i++) {
       mSoundThread[i].play = false;
       mSoundThread[i].play = false;
-      mSoundThread[i].profile = 0;
+      mSoundThread[i].asset = 0;
       mSoundThread[i].sound = 0;
       mSoundThread[i].sound = 0;
    }
    }
 
 
@@ -2233,24 +2233,30 @@ void ShapeBase::applyImpulse(const Point3F&,const VectorF&)
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
 
 
-void ShapeBase::playAudio(U32 slot,SFXTrack* profile)
+void ShapeBase::playAudio(U32 slot, StringTableEntry assetId)
 {
 {
    AssertFatal( slot < MaxSoundThreads, "ShapeBase::playAudio() bad slot index" );
    AssertFatal( slot < MaxSoundThreads, "ShapeBase::playAudio() bad slot index" );
-   Sound& st = mSoundThread[slot];
-   if( profile && ( !st.play || st.profile != profile ) ) 
+   if (AssetDatabase.isDeclaredAsset(assetId))
+   {
+      AssetPtr<SoundAsset> tempSoundAsset;
+      tempSoundAsset = assetId;
+
+      SoundThread& st = mSoundThread[slot];
+      if (tempSoundAsset && (!st.play || st.asset != tempSoundAsset))
    {
    {
       setMaskBits(SoundMaskN << slot);
       setMaskBits(SoundMaskN << slot);
       st.play = true;
       st.play = true;
-      st.profile = profile;
+         st.asset = tempSoundAsset;
       updateAudioState(st);
       updateAudioState(st);
    }
    }
+   }
 }
 }
 
 
 void ShapeBase::stopAudio(U32 slot)
 void ShapeBase::stopAudio(U32 slot)
 {
 {
    AssertFatal( slot < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index" );
    AssertFatal( slot < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index" );
 
 
-   Sound& st = mSoundThread[slot];
+   SoundThread& st = mSoundThread[slot];
    if ( st.play ) 
    if ( st.play ) 
    {
    {
       st.play = false;
       st.play = false;
@@ -2263,7 +2269,7 @@ void ShapeBase::updateServerAudio()
 {
 {
    // Timeout non-looping sounds
    // Timeout non-looping sounds
    for (S32 i = 0; i < MaxSoundThreads; i++) {
    for (S32 i = 0; i < MaxSoundThreads; i++) {
-      Sound& st = mSoundThread[i];
+      SoundThread& st = mSoundThread[i];
       if (st.play && st.timeout && st.timeout < Sim::getCurrentTime()) {
       if (st.play && st.timeout && st.timeout < Sim::getCurrentTime()) {
          clearMaskBits(SoundMaskN << i);
          clearMaskBits(SoundMaskN << i);
          st.play = false;
          st.play = false;
@@ -2271,17 +2277,18 @@ void ShapeBase::updateServerAudio()
    }
    }
 }
 }
 
 
-void ShapeBase::updateAudioState(Sound& st)
+void ShapeBase::updateAudioState(SoundThread& st)
 {
 {
    SFX_DELETE( st.sound );
    SFX_DELETE( st.sound );
 
 
-   if ( st.play && st.profile ) 
+   if ( st.play && st.asset ) 
    {
    {
       if ( isGhost() ) 
       if ( isGhost() ) 
       {
       {
-         if ( Sim::findObject( SimObjectId((uintptr_t)st.profile), st.profile ) )
+         // if asset is valid, play
+         if (st.asset->isAssetValid() )
          {
          {
-            st.sound = SFX->createSource( st.profile, &getTransform() );
+            st.sound = SFX->createSource( st.asset->getSfxProfile() , &getTransform() );
             if ( st.sound )
             if ( st.sound )
                st.sound->play();
                st.sound->play();
          }
          }
@@ -2292,12 +2299,17 @@ void ShapeBase::updateAudioState(Sound& st)
       {
       {
          // Non-looping sounds timeout on the server
          // Non-looping sounds timeout on the server
          st.timeout = 0;
          st.timeout = 0;
-         if ( !st.profile->getDescription()->mIsLooping )
+         if ( !st.asset->getSfxDescription()->mIsLooping )
             st.timeout = Sim::getCurrentTime() + sAudioTimeout;
             st.timeout = Sim::getCurrentTime() + sAudioTimeout;
       }
       }
    }
    }
    else
    else
+   {
+      // st.sound was not stopped before. If this causes issues remove.
       st.play = false;
       st.play = false;
+      if (st.sound)
+         st.sound->stop();
+   }
 }
 }
 
 
 void ShapeBase::updateAudioPos()
 void ShapeBase::updateAudioPos()
@@ -3122,13 +3134,15 @@ U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
 
 
    if (stream->writeFlag(mask & SoundMask)) {
    if (stream->writeFlag(mask & SoundMask)) {
       for (S32 i = 0; i < MaxSoundThreads; i++) {
       for (S32 i = 0; i < MaxSoundThreads; i++) {
-         Sound& st = mSoundThread[i];
+         SoundThread& st = mSoundThread[i];
          if (stream->writeFlag(mask & (SoundMaskN << i)))
          if (stream->writeFlag(mask & (SoundMaskN << i)))
             if (stream->writeFlag(st.play))
             if (stream->writeFlag(st.play))
-               stream->writeRangedU32(st.profile->getId(),DataBlockObjectIdFirst,
-                                      DataBlockObjectIdLast);
+            {
+               NetStringHandle assetIdStr = st.asset->getAssetId();
+               con->packNetStringHandleU(stream, assetIdStr);
       }
       }
    }
    }
+   }
 
 
    if (stream->writeFlag(mask & ImageMask)) {
    if (stream->writeFlag(mask & ImageMask)) {
       for (S32 i = 0; i < MaxMountedImages; i++)
       for (S32 i = 0; i < MaxMountedImages; i++)
@@ -3242,12 +3256,18 @@ void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)
       {
       {
          if ( stream->readFlag() ) 
          if ( stream->readFlag() ) 
          {
          {
-            Sound& st = mSoundThread[i];
+            SoundThread& st = mSoundThread[i];
             st.play = stream->readFlag();
             st.play = stream->readFlag();
             if ( st.play ) 
             if ( st.play ) 
             {
             {
-               st.profile = (SFXTrack*)(uintptr_t)stream->readRangedU32(  DataBlockObjectIdFirst,
-                                                                DataBlockObjectIdLast );
+               StringTableEntry temp = StringTable->insert(con->unpackNetStringHandleU(stream).getString());
+               if (AssetDatabase.isDeclaredAsset(temp))
+               {
+                  AssetPtr<SoundAsset> tempSoundAsset;
+                  tempSoundAsset = temp;
+
+                  st.asset = temp;
+               }
             }
             }
 
 
             if ( isProperlyAdded() )
             if ( isProperlyAdded() )
@@ -3777,7 +3797,7 @@ DefineEngineMethod( ShapeBase, isHidden, bool, (),,
 }
 }
 
 
 //----------------------------------------------------------------------------
 //----------------------------------------------------------------------------
-DefineEngineMethod( ShapeBase, playAudio, bool, ( S32 slot, SFXTrack* track ),,
+DefineEngineMethod( ShapeBase, playAudio, bool, ( S32 slot, StringTableEntry assetId),,
    "@brief Attach a sound to this shape and start playing it.\n\n"
    "@brief Attach a sound to this shape and start playing it.\n\n"
 
 
    "@param slot Audio slot index for the sound (valid range is 0 - 3)\n" // 3 = ShapeBase::MaxSoundThreads-1
    "@param slot Audio slot index for the sound (valid range is 0 - 3)\n" // 3 = ShapeBase::MaxSoundThreads-1
@@ -3786,8 +3806,8 @@ DefineEngineMethod( ShapeBase, playAudio, bool, ( S32 slot, SFXTrack* track ),,
    
    
    "@see stopAudio()\n")
    "@see stopAudio()\n")
 {
 {
-   if (track && slot >= 0 && slot < ShapeBase::MaxSoundThreads) {
-      object->playAudio(slot,track);
+   if (assetId && slot >= 0 && slot < ShapeBase::MaxSoundThreads) {
+      object->playAudio(slot, assetId);
       return true;
       return true;
    }
    }
    return false;
    return false;

+ 6 - 7
Engine/source/T3D/shapeBase.h

@@ -265,6 +265,7 @@ struct ShapeBaseImageData: public GameBaseData {
       F32 emitterTime;              ///<
       F32 emitterTime;              ///<
       S32 emitterNode[MaxShapes];   ///< Node ID on the shape to emit from
       S32 emitterNode[MaxShapes];   ///< Node ID on the shape to emit from
       SoundAsset* sound;
       SoundAsset* sound;
+      SFXTrack* soundTrack;         ///<Holdover for special, non-asset cases like SFXPlaylists
    };
    };
    /// @name State Data
    /// @name State Data
    /// Individual state data used to initialize struct array
    /// Individual state data used to initialize struct array
@@ -744,13 +745,13 @@ protected:
 
 
    /// @name Scripted Sound
    /// @name Scripted Sound
    /// @{
    /// @{
-   struct Sound {
+   struct SoundThread {
       bool play;                    ///< Are we playing this sound?
       bool play;                    ///< Are we playing this sound?
       SimTime timeout;              ///< Time until we stop playing this sound.
       SimTime timeout;              ///< Time until we stop playing this sound.
-      SFXTrack* profile;            ///< Profile on server
+      AssetPtr<SoundAsset> asset; ///< Asset on server
       SFXSource* sound;             ///< Sound on client
       SFXSource* sound;             ///< Sound on client
    };
    };
-   Sound mSoundThread[MaxSoundThreads];
+   SoundThread mSoundThread[MaxSoundThreads];
    /// @}
    /// @}
 
 
    /// @name Scripted Animation Threads
    /// @name Scripted Animation Threads
@@ -1114,7 +1115,7 @@ protected:
 
 
    /// Updates the audio state of the supplied sound
    /// Updates the audio state of the supplied sound
    /// @param   st   Sound
    /// @param   st   Sound
-   void updateAudioState(Sound& st);
+   void updateAudioState(SoundThread& st);
 
 
    /// Recalculates the spacial sound based on the current position of the object
    /// Recalculates the spacial sound based on the current position of the object
    /// emitting the sound.
    /// emitting the sound.
@@ -1328,9 +1329,7 @@ public:
 
 
    /// Plays an audio sound from a mounted object
    /// Plays an audio sound from a mounted object
    /// @param   slot    Mount slot ID
    /// @param   slot    Mount slot ID
-   /// @param   track   Audio track to play
-   void playAudio(U32 slot,SFXTrack* track);
-   void playAudio( U32 slot, SFXProfile* profile ) { playAudio( slot, ( SFXTrack* ) profile ); }
+   void playAudio(U32 slot, StringTableEntry assetId);
 
 
    /// Stops audio from a mounted object
    /// Stops audio from a mounted object
    /// @param   slot   Mount slot ID
    /// @param   slot   Mount slot ID

+ 42 - 20
Engine/source/T3D/shapeImage.cpp

@@ -133,6 +133,7 @@ ShapeBaseImageData::StateData::StateData()
    spin = IgnoreSpin;
    spin = IgnoreSpin;
    recoil = NoRecoil;
    recoil = NoRecoil;
    sound = NULL;
    sound = NULL;
+   soundTrack = NULL;
    emitter = NULL;
    emitter = NULL;
    shapeSequence = NULL;
    shapeSequence = NULL;
    shapeSequenceScale = true;
    shapeSequenceScale = true;
@@ -372,6 +373,19 @@ bool ShapeBaseImageData::onAdd()
 
 
          //_setstateSound(getstateSound(i),i);
          //_setstateSound(getstateSound(i),i);
          s.sound = getstateSoundAsset(i);
          s.sound = getstateSoundAsset(i);
+         if (s.sound == NULL && mstateSoundName[i] != StringTable->EmptyString())
+         {
+            //ok, so we've got some sort of special-case here like a fallback or SFXPlaylist. So do the hook-up now
+            SFXTrack* sndTrack;
+            if (!Sim::findObject(mstateSoundName[i], sndTrack))
+            {
+               Con::errorf("ShapeBaseImageData::onAdd() - attempted to find sound %s but failed!", mstateSoundName[i]);
+            }
+            else
+            {
+               s.soundTrack = sndTrack;
+            }
+         }
          s.script = stateScript[i];
          s.script = stateScript[i];
          s.emitter = stateEmitter[i];
          s.emitter = stateEmitter[i];
          s.emitterTime = stateEmitterTime[i];
          s.emitterTime = stateEmitterTime[i];
@@ -2580,7 +2594,7 @@ bool ShapeBase::hasImageState(U32 imageSlot, const char* state)
    return false;
    return false;
 }
 }
 
 
-void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
+void ShapeBase::setImageState(U32 imageSlot, U32 newState, bool force)
 {
 {
    if (!mMountedImageList[imageSlot].dataBlock)
    if (!mMountedImageList[imageSlot].dataBlock)
       return;
       return;
@@ -2611,12 +2625,12 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
    // Eject shell casing on every state change (client side only)
    // Eject shell casing on every state change (client side only)
    ShapeBaseImageData::StateData& nextStateData = image.dataBlock->state[newState];
    ShapeBaseImageData::StateData& nextStateData = image.dataBlock->state[newState];
    if (isGhost() && nextStateData.ejectShell) {
    if (isGhost() && nextStateData.ejectShell) {
-      ejectShellCasing( imageSlot );
+      ejectShellCasing(imageSlot);
    }
    }
 
 
    // Shake camera on client.
    // Shake camera on client.
    if (isGhost() && nextStateData.fire && image.dataBlock->shakeCamera) {
    if (isGhost() && nextStateData.fire && image.dataBlock->shakeCamera) {
-      shakeCamera( imageSlot );
+      shakeCamera(imageSlot);
    }
    }
 
 
    // Server must animate the shape if it is a firestate...
    // Server must animate the shape if it is a firestate...
@@ -2632,12 +2646,12 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
    if (!force && image.state == &image.dataBlock->state[newState]) {
    if (!force && image.state == &image.dataBlock->state[newState]) {
       image.delayTime = image.state->timeoutValue;
       image.delayTime = image.state->timeoutValue;
       if (image.state->script && !isGhost())
       if (image.state->script && !isGhost())
-         scriptCallback(imageSlot,image.state->script);
+         scriptCallback(imageSlot, image.state->script);
 
 
       // If this is a flash sequence, we need to select a new position for the
       // If this is a flash sequence, we need to select a new position for the
       //  animation if we're returning to that state...
       //  animation if we're returning to that state...
       F32 randomPos = Platform::getRandom();
       F32 randomPos = Platform::getRandom();
-      for (U32 i=0; i<ShapeBaseImageData::MaxShapes; ++i)
+      for (U32 i = 0; i < ShapeBaseImageData::MaxShapes; ++i)
       {
       {
          if (!image.dataBlock->shapeIsValid[i] || (i != imageShapeIndex && !image.doAnimateAllShapes))
          if (!image.dataBlock->shapeIsValid[i] || (i != imageShapeIndex && !image.doAnimateAllShapes))
             continue;
             continue;
@@ -2665,7 +2679,7 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
 
 
    // Mount pending images
    // Mount pending images
    if (image.nextImage != InvalidImagePtr && stateData.allowImageChange) {
    if (image.nextImage != InvalidImagePtr && stateData.allowImageChange) {
-      setImage(imageSlot,image.nextImage,image.nextSkinNameHandle,image.nextLoaded);
+      setImage(imageSlot, image.nextImage, image.nextSkinNameHandle, image.nextLoaded);
       return;
       return;
    }
    }
 
 
@@ -2673,16 +2687,16 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
    // (the first key frame should be it's off state).
    // (the first key frame should be it's off state).
    // We need to do this across all image shapes to make sure we have no hold overs when switching
    // We need to do this across all image shapes to make sure we have no hold overs when switching
    // rendering shapes while in the middle of a state change.
    // rendering shapes while in the middle of a state change.
-   for (U32 i=0; i<ShapeBaseImageData::MaxShapes; ++i)
+   for (U32 i = 0; i < ShapeBaseImageData::MaxShapes; ++i)
    {
    {
       // If we are to do a sequence transition then we need to keep the previous animThread active
       // If we are to do a sequence transition then we need to keep the previous animThread active
       if (image.animThread[i] && image.animThread[i]->getSequence()->isCyclic() && (stateData.sequenceNeverTransition || !(stateData.sequenceTransitionIn || lastState->sequenceTransitionOut))) {
       if (image.animThread[i] && image.animThread[i]->getSequence()->isCyclic() && (stateData.sequenceNeverTransition || !(stateData.sequenceTransitionIn || lastState->sequenceTransitionOut))) {
-         image.shapeInstance[i]->setPos(image.animThread[i],0);
-         image.shapeInstance[i]->setTimeScale(image.animThread[i],0);
+         image.shapeInstance[i]->setPos(image.animThread[i], 0);
+         image.shapeInstance[i]->setTimeScale(image.animThread[i], 0);
       }
       }
       if (image.flashThread[i]) {
       if (image.flashThread[i]) {
-         image.shapeInstance[i]->setPos(image.flashThread[i],0);
-         image.shapeInstance[i]->setTimeScale(image.flashThread[i],0);
+         image.shapeInstance[i]->setPos(image.flashThread[i], 0);
+         image.shapeInstance[i]->setTimeScale(image.flashThread[i], 0);
       }
       }
    }
    }
 
 
@@ -2695,10 +2709,10 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
    if (image.delayTime <= 0 || !stateData.waitForTimeout) 
    if (image.delayTime <= 0 || !stateData.waitForTimeout) 
    {
    {
       if ((ns = stateData.transition.loaded[image.loaded]) != -1) {
       if ((ns = stateData.transition.loaded[image.loaded]) != -1) {
-         setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
          return;
          return;
       }
       }
-      for (U32 i=0; i<ShapeBaseImageData::MaxGenericTriggers; ++i)
+      for (U32 i = 0; i < ShapeBaseImageData::MaxGenericTriggers; ++i)
       {
       {
          if ((ns = stateData.transition.genericTrigger[i][image.genericTrigger[i]]) != -1) {
          if ((ns = stateData.transition.genericTrigger[i][image.genericTrigger[i]]) != -1) {
             setImageState(imageSlot, ns);
             setImageState(imageSlot, ns);
@@ -2707,7 +2721,7 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
       }
       }
       //if (!imageData.usesEnergy)
       //if (!imageData.usesEnergy)
          if ((ns = stateData.transition.ammo[image.ammo]) != -1) {
          if ((ns = stateData.transition.ammo[image.ammo]) != -1) {
-            setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
             return;
             return;
          }
          }
       if ((ns = stateData.transition.target[image.target]) != -1) {
       if ((ns = stateData.transition.target[image.target]) != -1) {
@@ -2723,11 +2737,11 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
          return;
          return;
       }
       }
       if ((ns = stateData.transition.trigger[image.triggerDown]) != -1) {
       if ((ns = stateData.transition.trigger[image.triggerDown]) != -1) {
-         setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
          return;
          return;
       }
       }
       if ((ns = stateData.transition.altTrigger[image.altTriggerDown]) != -1) {
       if ((ns = stateData.transition.altTrigger[image.altTriggerDown]) != -1) {
-         setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
          return;
          return;
       }
       }
    }
    }
@@ -2752,7 +2766,7 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
 
 
    // Apply recoil
    // Apply recoil
    if (stateData.recoil != ShapeBaseImageData::StateData::NoRecoil)
    if (stateData.recoil != ShapeBaseImageData::StateData::NoRecoil)
-      onImageRecoil(imageSlot,stateData.recoil);
+      onImageRecoil(imageSlot, stateData.recoil);
 
 
    // Apply image state animation on mounting shape
    // Apply image state animation on mounting shape
    if (stateData.shapeSequence && stateData.shapeSequence[0])
    if (stateData.shapeSequence && stateData.shapeSequence[0])
@@ -2764,17 +2778,25 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
    // lastState does not return an id for the prev state so we keep track of it.
    // lastState does not return an id for the prev state so we keep track of it.
    if (lastState->sound && lastState->sound->getSfxProfile()->getDescription()->mIsLooping)
    if (lastState->sound && lastState->sound->getSfxProfile()->getDescription()->mIsLooping)
    {  
    {  
-      for(Vector<SFXSource*>::iterator i = image.mSoundSources.begin(); i != image.mSoundSources.end(); i++)      
+      for (Vector<SFXSource*>::iterator i = image.mSoundSources.begin(); i != image.mSoundSources.end(); i++)
          SFX_DELETE((*i));    
          SFX_DELETE((*i));    
 
 
       image.mSoundSources.clear();  
       image.mSoundSources.clear();  
    }  
    }  
 
 
    // Play sound
    // Play sound
-   if( stateData.sound && isGhost() )
+   if (isGhost())
+   {
+      if (stateData.sound)
    {
    {
       const Point3F& velocity         = getVelocity();
       const Point3F& velocity         = getVelocity();
-      image.addSoundSource(SFX->createSource(stateData.sound->getSfxProfile(), &getRenderTransform(), &velocity ));
+         image.addSoundSource(SFX->createSource(stateData.sound->getSfxProfile(), &getRenderTransform(), &velocity));
+      }
+      if (stateData.soundTrack)
+      {
+         const Point3F& velocity = getVelocity();
+         image.addSoundSource(SFX->createSource(stateData.soundTrack, &getRenderTransform(), &velocity));
+      }
    }
    }
 
 
    // Play animation
    // Play animation

+ 6 - 2
Engine/source/sfx/sfxPlayList.cpp

@@ -352,15 +352,19 @@ bool SFXPlayList::preload( bool server, String& errorStr )
    {
    {
       for( U32 i = 0; i < NUM_SLOTS; ++ i )
       for( U32 i = 0; i < NUM_SLOTS; ++ i )
       {
       {
-         _setTrack(getTrack(i),i);
+         StringTableEntry track = getTrack(i);
+         if (track != StringTable->EmptyString())
+         {
+            _setTrack(getTrack(i), i);
          if (!getTrackProfile(i))
          if (!getTrackProfile(i))
          {
          {
             Con::errorf("SFXPlayList::Preload() - unable to find sfxProfile for asset %s", mTrackAssetId[i]);
             Con::errorf("SFXPlayList::Preload() - unable to find sfxProfile for asset %s", mTrackAssetId[i]);
             return false;
             return false;
          }
          }
             
             
-         if( !sfxResolve( &mSlots.mState[ i ], errorStr ) )
+            if (!sfxResolve(&mSlots.mState[i], errorStr))
             return false;
             return false;
+         }
       }
       }
    }
    }
       
       

+ 8 - 2
Engine/source/sfx/sfxProfile.cpp

@@ -35,6 +35,7 @@
 #include "core/stream/bitStream.h"
 #include "core/stream/bitStream.h"
 #include "core/resourceManager.h"
 #include "core/resourceManager.h"
 #include "console/engineAPI.h"
 #include "console/engineAPI.h"
+#include "core/stream/fileStream.h"
 
 
 using namespace Torque;
 using namespace Torque;
 
 
@@ -283,8 +284,13 @@ bool SFXProfile::_preloadBuffer()
 
 
 Resource<SFXResource>& SFXProfile::getResource()
 Resource<SFXResource>& SFXProfile::getResource()
 {
 {
-   if( !mResource && mFilename != StringTable->EmptyString())
-      mResource = SFXResource::load( mFilename );
+   char buf[1024];
+   FileName fullFilename = String(Platform::makeFullPathName(mFilename, buf, sizeof(buf)));
+
+   if (!mResource && SFXResource::exists(fullFilename))
+      mResource = SFXResource::load(mFilename);
+   else
+      mResource = NULL;
 
 
    return mResource;
    return mResource;
 }
 }

+ 24 - 87
Engine/source/sfx/sfxSystem.cpp

@@ -1581,133 +1581,70 @@ DefineEngineFunction( sfxPlay, S32, ( const char * trackName, const char * point
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
 static ConsoleDocFragment _sPlayOnce1(
 static ConsoleDocFragment _sPlayOnce1(
-   "@brief Create a play-once source for the given @a track.\n\n"
+   "@brief Create a play-once source for the given @a asset.\n\n"
    "Once playback has finished, the source will be automatically deleted in the next sound system update.\n"
    "Once playback has finished, the source will be automatically deleted in the next sound system update.\n"
    "@param track The sound datablock.\n"
    "@param track The sound datablock.\n"
    "@return A newly created temporary source in \"Playing\" state or 0 if the operation failed.\n\n"
    "@return A newly created temporary source in \"Playing\" state or 0 if the operation failed.\n\n"
    "@ref SFXSource_playonce\n\n"
    "@ref SFXSource_playonce\n\n"
    "@ingroup SFX",
    "@ingroup SFX",
    NULL,
    NULL,
-   "SFXSource sfxPlayOnce( SFXTrack track );"
+   "SFXSource sfxPlayOnce( StringTableEntry assetID );"
 );
 );
 static ConsoleDocFragment _sPlayOnce2(
 static ConsoleDocFragment _sPlayOnce2(
-   "@brief Create a play-once source for the given given @a track and position the source's 3D sound at the given coordinates "
-      "only if the track's description is set up for 3D sound).\n\n"
+   "@brief Create a play-once source for the given given @a asset and position the source's 3D sound at the given coordinates "
+      "only if the asset is set up for 3D sound).\n\n"
    "Once playback has finished, the source will be automatically deleted in the next sound system update.\n"
    "Once playback has finished, the source will be automatically deleted in the next sound system update.\n"
-   "@param track The sound datablock.\n"
+   "@param assetId The sound asset.\n"
    "@param x The X coordinate of the 3D sound position.\n"
    "@param x The X coordinate of the 3D sound position.\n"
    "@param y The Y coordinate of the 3D sound position.\n"
    "@param y The Y coordinate of the 3D sound position.\n"
    "@param z The Z coordinate of the 3D sound position.\n"
    "@param z The Z coordinate of the 3D sound position.\n"
-   "@param fadeInTime If >=0, this overrides the SFXDescription::fadeInTime value on the track's description.\n"
+   "@param fadeInTime If >=0, this overrides the SFXDescription::fadeInTime value on the asset's definition.\n"
    "@return A newly created temporary source in \"Playing\" state or 0 if the operation failed.\n\n"
    "@return A newly created temporary source in \"Playing\" state or 0 if the operation failed.\n\n"
    "@tsexample\n"
    "@tsexample\n"
-      "// Immediately start playing the given track.  Fade it in to full volume over 5 seconds.\n"
-      "sfxPlayOnce( MusicTrack, 0, 0, 0, 5.f );\n"
+      "// Immediately start playing the given asset.  Fade it in to full volume over 5 seconds.\n"
+      "sfxPlayOnce( ExampleModule:MusicTrack, 0, 0, 0, 5.f );\n"
    "@endtsexample\n\n"
    "@endtsexample\n\n"
    "@ref SFXSource_playonce\n\n"
    "@ref SFXSource_playonce\n\n"
    "@ingroup SFX",
    "@ingroup SFX",
    NULL,
    NULL,
-   "SFXSource sfxPlayOnce( SFXTrack track, float x, float y, float z, float fadeInTime=-1 );"
-);
-static ConsoleDocFragment _sPlayOnce3(
-   "@brief Create a new temporary SFXProfile from the given @a description and @a filename, then create a play-once source "
-      "for it and start playback.\n\n"
-   "Once playback has finished, the source will be automatically deleted in the next sound system update.  If not referenced "
-      "otherwise by then, the temporary SFXProfile will also be deleted.\n"
-   "@param description The description to use for playback.\n"
-   "@param filename Path to the sound file to play.\n"
-   "@return A newly created temporary source in \"Playing\" state or 0 if the operation failed.\n\n"
-   "@tsexample\n"
-   "// Play a sound effect file once.\n"
-   "sfxPlayOnce( AudioEffects, \"art/sound/weapons/Weapon_pickup\" );\n"
-   "@endtsexample\n\n"
-   "@ref SFXSource_playonce\n\n"
-   "@ingroup SFX",
-   NULL,
-   "SFXSource sfxPlayOnce( SFXDescription description, string filename );"
-);
-static ConsoleDocFragment _sPlayOnce4(
-   "@brief Create a new temporary SFXProfile from the given @a description and @a filename, then create a play-once source "
-      "for it and start playback.  Position the source's 3D sound at the given coordinates (only if the description "
-      "is set up for 3D sound).\n\n"
-   "Once playback has finished, the source will be automatically deleted in the next sound system update.  If not referenced "
-      "otherwise by then, the temporary SFXProfile will also be deleted.\n"
-   "@param description The description to use for playback.\n"
-   "@param filename Path to the sound file to play.\n"
-   "@param x The X coordinate of the 3D sound position.\n"
-   "@param y The Y coordinate of the 3D sound position.\n"
-   "@param z The Z coordinate of the 3D sound position.\n"
-   "@param fadeInTime If >=0, this overrides the SFXDescription::fadeInTime value on the track's description.\n"
-   "@return A newly created temporary source in \"Playing\" state or 0 if the operation failed.\n\n"
-   "@tsexample\n"
-   "// Play a sound effect file once using a 3D sound with a default falloff placed at the origin.\n"
-   "sfxPlayOnce( AudioDefault3D, \"art/sound/weapons/Weapon_pickup\", 0, 0, 0 );\n"
-   "@endtsexample\n\n"
-   "@ref SFXSource_playonce\n\n"
-   "@ingroup SFX",
-   NULL,
-   "SFXSource sfxPlayOnce( SFXDescription description, string filename, float x, float y, float z, float fadeInTime=-1 );"
+   "SFXSource sfxPlayOnce( StringTableEntry assetID, float x, float y, float z, float fadeInTime=-1 );"
 );
 );
 
 
-DefineEngineFunction( sfxPlayOnce, S32, ( const char * sfxType, const char * arg0, const char * arg1, const char * arg2, const char * arg3, const char* arg4 ), ("", "", "", "", "-1.0f"),
+DefineEngineFunction( sfxPlayOnce, S32, (StringTableEntry assetId, const char* arg0, const char * arg1, const char * arg2, const char * arg3 ), (StringTable->EmptyString(), "", "", "", "-1.0f"),
    "SFXSource sfxPlayOnce( ( SFXTrack track | SFXDescription description, string filename ) [, float x, float y, float z, float fadeInTime=-1 ] ) "
    "SFXSource sfxPlayOnce( ( SFXTrack track | SFXDescription description, string filename ) [, float x, float y, float z, float fadeInTime=-1 ] ) "
    "Create a new play-once source for the given profile or description+filename and start playback of the source.\n"
    "Create a new play-once source for the given profile or description+filename and start playback of the source.\n"
    "@hide" )
    "@hide" )
 {
 {
    SFXDescription* description = NULL;
    SFXDescription* description = NULL;
-   SFXTrack* track = dynamic_cast< SFXTrack* >( Sim::findObject( sfxType ) );
-   if( !track )
-   {
-      description = dynamic_cast< SFXDescription* >( Sim::findObject( sfxType ) );
-      if( !description )
+   if (assetId == StringTable->EmptyString())
       {
       {
-         Con::errorf( "sfxPlayOnce - Unable to locate sound track/description '%s'", sfxType );
+         Con::errorf( "sfxPlayOnce - Must Define a sound asset");
          return 0;
          return 0;
       }
       }
-   }
 
 
    SFXSource* source = NULL;
    SFXSource* source = NULL;
-   if( track )
+
+   if (AssetDatabase.isDeclaredAsset(assetId))
    {
    {
-      // In this overloaded use, arg0..arg2 are x, y, z, and arg3 is the fadeInTime.
-      if (String::isEmpty(arg0))
+
+      AssetPtr<SoundAsset> tempSoundAsset;
+      tempSoundAsset = assetId;
+
+      if (String::isEmpty(arg0) || !tempSoundAsset->is3D())
       {
       {
-         source = SFX->playOnce( track );
+         source = SFX->playOnce(tempSoundAsset->getSfxProfile());
       }
       }
       else
       else
       {
       {
          MatrixF transform;
          MatrixF transform;
-         transform.set( EulerF( 0, 0, 0 ), Point3F( dAtof( arg0 ), dAtof( arg1 ),dAtof( arg2 ) ) );
-         source = SFX->playOnce( track, &transform, NULL, dAtof( arg3 ) );
-      }
+         transform.set(EulerF(0, 0, 0), Point3F(dAtof(arg0), dAtof(arg1), dAtof(arg2)));
+         source = SFX->playOnce(tempSoundAsset->getSfxProfile(), &transform, NULL, dAtof(arg3));
    }
    }
-   else if( description )
-   {
-      // In this overload, arg0 is the filename, arg1..arg3 are x, y, z, and arg4 is fadeInTime.
-      SFXProfile* tempProfile = new SFXProfile( description, StringTable->insert( arg0 ), true );
-      if( !tempProfile->registerObject() )
-      {
-         Con::errorf( "sfxPlayOnce - unable to create profile" );
-         delete tempProfile;
       }
       }
       else
       else
       {
       {
-         if (String::isEmpty(arg1))
-            source = SFX->playOnce( tempProfile );
-         else
-         {
-            MatrixF transform;
-            transform.set( EulerF( 0, 0, 0 ), Point3F( dAtof( arg1 ), dAtof( arg2 ),dAtof( arg3 ) ) );
-            source = SFX->playOnce( tempProfile, &transform, NULL, dAtof( arg4 ) );
-         }
-         
-         // Set profile to auto-delete when SFXSource releases its reference.
-         // Also add to root group so the profile will get deleted when the
-         // Sim system is shut down before the SFXSource has played out.
-
-         tempProfile->setAutoDelete( true );
-         Sim::getRootGroup()->addObject( tempProfile );
-      }
+      Con::errorf("sfxPlayOnce - Could not locate assetId '%s'", assetId);
+      return 0;
    }
    }
 
 
    if( !source )
    if( !source )