Forráskód Böngészése

Merge branch 'Preview4_0' of https://github.com/TorqueGameEngines/Torque3D into UpdatedProjectImporter

JeffR 3 éve
szülő
commit
1952820ef2

+ 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);
          mLoadedState = BadFileReference;
+         mSFXProfile.setDescription(NULL);
+         mSFXProfile.setSoundFileName(StringTable->insert(StringTable->EmptyString()));
+         mSFXProfile.setPreload(false);
          return false;
       }
       else
@@ -257,7 +260,7 @@ StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName)
    if (fileName == StringTable->EmptyString())
       return StringTable->EmptyString();
 
-   StringTableEntry materialAssetId = "";
+   StringTableEntry soundAssetId = StringTable->EmptyString();
 
    AssetQuery query;
    U32 foundCount = AssetDatabase.findAssetType(&query, "SoundAsset");
@@ -268,7 +271,7 @@ StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName)
          SoundAsset* soundAsset = AssetDatabase.acquireAsset<SoundAsset>(query.mAssetList[i]);
          if (soundAsset && soundAsset->getSoundPath() == fileName)
          {
-            materialAssetId = soundAsset->getAssetId();
+            soundAssetId = soundAsset->getAssetId();
             AssetDatabase.releaseAsset(query.mAssetList[i]);
             break;
          }
@@ -276,7 +279,7 @@ StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName)
       }
    }
 
-   return materialAssetId;
+   return soundAssetId;
 }
 
 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),
-   "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())
    {

+ 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)
       return false;
 
-   if (!server && !getSoundProfile())
-      return false;
-
    if( !server )
    {
 
@@ -870,12 +867,18 @@ bool ExplosionData::preload(bool server, String &errorStr)
          _setSound(getSound());
 
          if (!getSoundProfile())
+         {
             Con::errorf(ConsoleLogEntry::General, "SplashData::preload: Cant get an sfxProfile for splash.");
+            return false;
+         }
       }
 
       if (!particleEmitter && particleEmitterId != 0)
          if (Sim::findObject(particleEmitterId, particleEmitter) == false)
+         {
             Con::errorf(ConsoleLogEntry::General, "Error, unable to load particle emitter for explosion datablock");
+            return false;
+         }
    }
 
    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 )
-      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()
@@ -2010,49 +2027,49 @@ DefineEngineMethod( GameConnection, isControlObjectRotDampedCamera, bool, (),,
    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"
 
-   "@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"
-   "function ServerPlay2D(%profile)\n"
+   "function ServerPlay2D(%assetId)\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"
    "   for(%idx = 0; %idx < ClientGroup.getCount(); %idx++)\n"
-   "      ClientGroup.getObject(%idx).play2D(%profile);\n"
+   "      ClientGroup.getObject(%idx).play2D(%assetId);\n"
    "}\n"
    "@endtsexample\n\n")
 {
-   if(!profile)
+   if(assetId == StringTable->EmptyString())
       return false;
 
-   object->play2D(profile);
+   object->play2D(assetId);
    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"
    
-   "@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"
 
    "@tsexample\n"
-   "function ServerPlay3D(%profile,%transform)\n"
+   "function ServerPlay3D(%assetId,%transform)\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"
    "   for(%idx = 0; %idx < ClientGroup.getCount(); %idx++)\n"
-   "      ClientGroup.getObject(%idx).play3D(%profile,%transform);\n"
+   "      ClientGroup.getObject(%idx).play3D(%assetID,%transform);\n"
    "}\n"
    "@endtsexample\n\n")
 {
-   if(!profile)
+   if(assetId == StringTable->EmptyString())
       return false;
 
    MatrixF mat = location.getMatrix();
-   object->play3D(profile,&mat);
+   object->play3D(assetId,&mat);
    return true;
 }
 

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

@@ -352,8 +352,8 @@ public:
    /// @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.

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

@@ -47,6 +47,7 @@
 
 //--------------------------------------------------------------------------
 IMPLEMENT_CO_CLIENTEVENT_V1(SimDataBlockEvent);
+IMPLEMENT_CO_CLIENTEVENT_V1(SimSoundAssetEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(Sim2DAudioEvent);
 IMPLEMENT_CO_CLIENTEVENT_V1(Sim3DAudioEvent);
 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)
 {
@@ -321,11 +420,6 @@ void Sim2DAudioEvent::process(NetConnection *)
       SFX->playOnce( mProfile );
 }
 
-//----------------------------------------------------------------------------
-
-static F32 SoundPosAccuracy = 0.5;
-static S32 SoundRotBits = 8;
-
 Sim3DAudioEvent::Sim3DAudioEvent(SFXProfile *profile,const MatrixF* mat)
 {
    mProfile = profile;

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

@@ -39,6 +39,9 @@
 #include "core/stream/bitStream.h"
 #endif
 
+#include "T3D/assets/SoundAsset.h"
+
+
 
 class QuitEvent : public SimEvent
 {
@@ -102,6 +105,23 @@ class SimDataBlockEvent : public NetEvent
       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
 {
   private:

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

@@ -1009,7 +1009,7 @@ ShapeBase::ShapeBase()
 
    for (i = 0; i < MaxSoundThreads; i++) {
       mSoundThread[i].play = false;
-      mSoundThread[i].profile = 0;
+      mSoundThread[i].asset = 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" );
-   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);
       st.play = true;
-      st.profile = profile;
+         st.asset = tempSoundAsset;
       updateAudioState(st);
    }
+   }
 }
 
 void ShapeBase::stopAudio(U32 slot)
 {
    AssertFatal( slot < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index" );
 
-   Sound& st = mSoundThread[slot];
+   SoundThread& st = mSoundThread[slot];
    if ( st.play ) 
    {
       st.play = false;
@@ -2263,7 +2269,7 @@ void ShapeBase::updateServerAudio()
 {
    // Timeout non-looping sounds
    for (S32 i = 0; i < MaxSoundThreads; i++) {
-      Sound& st = mSoundThread[i];
+      SoundThread& st = mSoundThread[i];
       if (st.play && st.timeout && st.timeout < Sim::getCurrentTime()) {
          clearMaskBits(SoundMaskN << i);
          st.play = false;
@@ -2271,17 +2277,18 @@ void ShapeBase::updateServerAudio()
    }
 }
 
-void ShapeBase::updateAudioState(Sound& st)
+void ShapeBase::updateAudioState(SoundThread& st)
 {
    SFX_DELETE( st.sound );
 
-   if ( st.play && st.profile ) 
+   if ( st.play && st.asset ) 
    {
       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 )
                st.sound->play();
          }
@@ -2292,12 +2299,17 @@ void ShapeBase::updateAudioState(Sound& st)
       {
          // Non-looping sounds timeout on the server
          st.timeout = 0;
-         if ( !st.profile->getDescription()->mIsLooping )
+         if ( !st.asset->getSfxDescription()->mIsLooping )
             st.timeout = Sim::getCurrentTime() + sAudioTimeout;
       }
    }
    else
+   {
+      // st.sound was not stopped before. If this causes issues remove.
       st.play = false;
+      if (st.sound)
+         st.sound->stop();
+   }
 }
 
 void ShapeBase::updateAudioPos()
@@ -3122,13 +3134,15 @@ U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
 
    if (stream->writeFlag(mask & SoundMask)) {
       for (S32 i = 0; i < MaxSoundThreads; i++) {
-         Sound& st = mSoundThread[i];
+         SoundThread& st = mSoundThread[i];
          if (stream->writeFlag(mask & (SoundMaskN << i)))
             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)) {
       for (S32 i = 0; i < MaxMountedImages; i++)
@@ -3242,12 +3256,18 @@ void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream)
       {
          if ( stream->readFlag() ) 
          {
-            Sound& st = mSoundThread[i];
+            SoundThread& st = mSoundThread[i];
             st.play = stream->readFlag();
             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() )
@@ -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"
 
    "@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")
 {
-   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 false;

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

@@ -265,6 +265,7 @@ struct ShapeBaseImageData: public GameBaseData {
       F32 emitterTime;              ///<
       S32 emitterNode[MaxShapes];   ///< Node ID on the shape to emit from
       SoundAsset* sound;
+      SFXTrack* soundTrack;         ///<Holdover for special, non-asset cases like SFXPlaylists
    };
    /// @name State Data
    /// Individual state data used to initialize struct array
@@ -744,13 +745,13 @@ protected:
 
    /// @name Scripted Sound
    /// @{
-   struct Sound {
+   struct SoundThread {
       bool play;                    ///< Are we 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
    };
-   Sound mSoundThread[MaxSoundThreads];
+   SoundThread mSoundThread[MaxSoundThreads];
    /// @}
 
    /// @name Scripted Animation Threads
@@ -1114,7 +1115,7 @@ protected:
 
    /// Updates the audio state of the supplied sound
    /// @param   st   Sound
-   void updateAudioState(Sound& st);
+   void updateAudioState(SoundThread& st);
 
    /// Recalculates the spacial sound based on the current position of the object
    /// emitting the sound.
@@ -1328,9 +1329,7 @@ public:
 
    /// Plays an audio sound from a mounted object
    /// @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
    /// @param   slot   Mount slot ID

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

@@ -133,6 +133,7 @@ ShapeBaseImageData::StateData::StateData()
    spin = IgnoreSpin;
    recoil = NoRecoil;
    sound = NULL;
+   soundTrack = NULL;
    emitter = NULL;
    shapeSequence = NULL;
    shapeSequenceScale = true;
@@ -372,6 +373,19 @@ bool ShapeBaseImageData::onAdd()
 
          //_setstateSound(getstateSound(i),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.emitter = stateEmitter[i];
          s.emitterTime = stateEmitterTime[i];
@@ -2580,7 +2594,7 @@ bool ShapeBase::hasImageState(U32 imageSlot, const char* state)
    return false;
 }
 
-void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
+void ShapeBase::setImageState(U32 imageSlot, U32 newState, bool force)
 {
    if (!mMountedImageList[imageSlot].dataBlock)
       return;
@@ -2611,12 +2625,12 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
    // Eject shell casing on every state change (client side only)
    ShapeBaseImageData::StateData& nextStateData = image.dataBlock->state[newState];
    if (isGhost() && nextStateData.ejectShell) {
-      ejectShellCasing( imageSlot );
+      ejectShellCasing(imageSlot);
    }
 
    // Shake camera on client.
    if (isGhost() && nextStateData.fire && image.dataBlock->shakeCamera) {
-      shakeCamera( imageSlot );
+      shakeCamera(imageSlot);
    }
 
    // 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]) {
       image.delayTime = image.state->timeoutValue;
       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
       //  animation if we're returning to that state...
       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))
             continue;
@@ -2665,7 +2679,7 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
 
    // Mount pending images
    if (image.nextImage != InvalidImagePtr && stateData.allowImageChange) {
-      setImage(imageSlot,image.nextImage,image.nextSkinNameHandle,image.nextLoaded);
+      setImage(imageSlot, image.nextImage, image.nextSkinNameHandle, image.nextLoaded);
       return;
    }
 
@@ -2673,16 +2687,16 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
    // (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
    // 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 (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]) {
-         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 ((ns = stateData.transition.loaded[image.loaded]) != -1) {
-         setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
          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) {
             setImageState(imageSlot, ns);
@@ -2707,7 +2721,7 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
       }
       //if (!imageData.usesEnergy)
          if ((ns = stateData.transition.ammo[image.ammo]) != -1) {
-            setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
             return;
          }
       if ((ns = stateData.transition.target[image.target]) != -1) {
@@ -2723,11 +2737,11 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
          return;
       }
       if ((ns = stateData.transition.trigger[image.triggerDown]) != -1) {
-         setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
          return;
       }
       if ((ns = stateData.transition.altTrigger[image.altTriggerDown]) != -1) {
-         setImageState(imageSlot,ns);
+         setImageState(imageSlot, ns);
          return;
       }
    }
@@ -2752,7 +2766,7 @@ void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force)
 
    // Apply recoil
    if (stateData.recoil != ShapeBaseImageData::StateData::NoRecoil)
-      onImageRecoil(imageSlot,stateData.recoil);
+      onImageRecoil(imageSlot, stateData.recoil);
 
    // Apply image state animation on mounting shape
    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.
    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));    
 
       image.mSoundSources.clear();  
    }  
 
    // Play sound
-   if( stateData.sound && isGhost() )
+   if (isGhost())
+   {
+      if (stateData.sound)
    {
       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

+ 208 - 96
Engine/source/persistence/taml/fsTinyXml.cpp

@@ -26,6 +26,41 @@
 
 #include "console/console.h"
 
+VfsXMLPrinter::VfsXMLPrinter(FileStream& stream, bool compact, int depth)
+   : XMLPrinter(NULL, compact, depth),
+     m_Stream(stream)
+{
+}
+
+VfsXMLPrinter::~VfsXMLPrinter()
+{
+   m_Stream.flush();
+   m_Stream.close();
+}
+
+
+// Add VFS friendly implementations of output functions
+
+void VfsXMLPrinter::Print(const char* format, ...)
+{
+   va_list     va;
+   va_start(va, format);
+
+   m_Stream.writeFormattedBuffer(format, va);
+
+   va_end(va);
+}
+
+void VfsXMLPrinter::Write(const char* data, size_t size)
+{
+   m_Stream.write(size, data);
+}
+
+void VfsXMLPrinter::Putc(char ch)
+{
+   m_Stream.write(static_cast<U8>(ch));
+}
+
 bool VfsXMLDocument::LoadFile(const char* pFilename)
 {
    // Expand the file-path.
@@ -61,96 +96,6 @@ bool VfsXMLDocument::LoadFile(const char* pFilename)
    return true;
 }
 
-bool VfsXMLDocument::SaveFile(const char* pFilename)
-{
-   // Expand the file-name into the file-path buffer.
-   char filenameBuffer[1024];
-   Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
-
-   FileStream stream;
-
-   // File opened?
-   if (!stream.open(filenameBuffer, Torque::FS::File::Write))
-   {
-      // No, so warn.
-      Con::warnf("Taml::writeFile() - Could not open filename '%s' for write.", filenameBuffer);
-      return false;
-   }
-
-   bool ret = SaveFile(stream);
-
-   stream.close();
-   return ret;
-}
-
-void VfsXMLDocument::ClearError()
-{
-   _errorID = tinyxml2::XML_SUCCESS;
-   _errorLineNum = 0;
-   _errorStr.Reset();
-
-   tinyxml2::XMLDocument::ClearError();
-}
-
-void VfsXMLDocument::SetError(tinyxml2::XMLError error, int lineNum, const char* format, ...)
-{
-   TIXMLASSERT(error >= 0 && error < tinyxml2::XML_ERROR_COUNT);
-   _errorID = error;
-   _errorLineNum = lineNum;
-   _errorStr.Reset();
-
-   const size_t BUFFER_SIZE = 1000;
-   char* buffer = new char[BUFFER_SIZE];
-
-   TIXMLASSERT(sizeof(error) <= sizeof(int));
-   dSprintf(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum);
-
-   if (format) {
-      size_t len = strlen(buffer);
-      dSprintf(buffer + len, BUFFER_SIZE - len, ": ");
-      len = strlen(buffer);
-
-      va_list va;
-      va_start(va, format);
-      dSprintf(buffer + len, BUFFER_SIZE - len, format, va);
-      va_end(va);
-   }
-   _errorStr.SetStr(buffer);
-   delete[] buffer;
-}
-
-VfsXMLPrinter::VfsXMLPrinter(FileStream& stream, bool compact, int depth)
-   : XMLPrinter(NULL, compact, depth),
-     m_Stream(stream)
-{
-}
-
-VfsXMLPrinter::~VfsXMLPrinter()
-{
-   m_Stream.flush();
-   m_Stream.close();
-}
-
-void VfsXMLPrinter::Print(const char* format, ...)
-{
-   va_list     va;
-   va_start(va, format);
-
-   m_Stream.writeFormattedBuffer(format, va);
-
-   va_end(va);
-}
-
-void VfsXMLPrinter::Write(const char* data, size_t size)
-{
-   m_Stream.write(size, data);
-}
-
-void VfsXMLPrinter::Putc(char ch)
-{
-   m_Stream.write(static_cast<U8>(ch));
-}
-
 bool VfsXMLDocument::LoadFile(FileStream& stream)
 {
    // Delete the existing data:
@@ -196,7 +141,7 @@ bool VfsXMLDocument::LoadFile(FileStream& stream)
 
    if (!stream.read(length, buf))
    {
-      delete [] buf;
+      delete[] buf;
       SetError(tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, 0);
       return false;
    }
@@ -220,8 +165,8 @@ bool VfsXMLDocument::LoadFile(FileStream& stream)
    buf[length] = 0;
    while (*p)
    {
-      assert(p < (buf+length));
-      assert(q <= (buf+length));
+      assert(p < (buf + length));
+      assert(q <= (buf + length));
       assert(q <= p);
 
       if (*p == CR)
@@ -239,12 +184,12 @@ bool VfsXMLDocument::LoadFile(FileStream& stream)
          *q++ = *p++;
       }
    }
-   assert(q <= (buf+length));
+   assert(q <= (buf + length));
    *q = 0;
 
    Parse(buf, length);
 
-   delete [] buf;
+   delete[] buf;
    return !Error();
 }
 
@@ -254,6 +199,173 @@ bool VfsXMLDocument::SaveFile(FileStream& stream)
    // for *this* call.
    ClearError();
    VfsXMLPrinter printer(stream, false, 0);
-   Print(&printer);
+   PrettyXMLPrinter prettyPrinter(printer);
+   Print(&prettyPrinter);
    return !Error();
 }
+
+bool VfsXMLDocument::SaveFile(const char* pFilename)
+{
+   // Expand the file-name into the file-path buffer.
+   char filenameBuffer[1024];
+   Con::expandScriptFilename(filenameBuffer, sizeof(filenameBuffer), pFilename);
+
+   FileStream stream;
+
+   // File opened?
+   if (!stream.open(filenameBuffer, Torque::FS::File::Write))
+   {
+      // No, so warn.
+      Con::warnf("Taml::writeFile() - Could not open filename '%s' for write.", filenameBuffer);
+      return false;
+   }
+
+   bool ret = SaveFile(stream);
+
+   stream.close();
+   return ret;
+}
+
+void VfsXMLDocument::ClearError()
+{
+   _errorID = tinyxml2::XML_SUCCESS;
+   _errorLineNum = 0;
+   _errorStr.Reset();
+
+   tinyxml2::XMLDocument::ClearError();
+}
+
+void VfsXMLDocument::SetError(tinyxml2::XMLError error, int lineNum, const char* format, ...)
+{
+   TIXMLASSERT(error >= 0 && error < tinyxml2::XML_ERROR_COUNT);
+   _errorID = error;
+   _errorLineNum = lineNum;
+   _errorStr.Reset();
+
+   const size_t BUFFER_SIZE = 1000;
+   char* buffer = new char[BUFFER_SIZE];
+
+   TIXMLASSERT(sizeof(error) <= sizeof(int));
+   dSprintf(buffer, BUFFER_SIZE, "Error=%s ErrorID=%d (0x%x) Line number=%d", ErrorIDToName(error), int(error), int(error), lineNum);
+
+   if (format) {
+      size_t len = strlen(buffer);
+      dSprintf(buffer + len, BUFFER_SIZE - len, ": ");
+      len = strlen(buffer);
+
+      va_list va;
+      va_start(va, format);
+      dSprintf(buffer + len, BUFFER_SIZE - len, format, va);
+      va_end(va);
+   }
+   _errorStr.SetStr(buffer);
+   delete[] buffer;
+}
+
+
+// Overwrite Visitation of elements to add newlines before attributes
+PrettyXMLPrinter::PrettyXMLPrinter(VfsXMLPrinter& innerPrinter, int depth)
+   : mInnerPrinter(innerPrinter),
+   mDepth(depth)
+{
+   for (int i = 0; i < ENTITY_RANGE; ++i) {
+      mEntityFlag[i] = false;
+      mRestrictedEntityFlag[i] = false;
+   }
+   for (int i = 0; i < NUM_ENTITIES; ++i) {
+      const char entityValue = entities[i].value;
+      const unsigned char flagIndex = static_cast<unsigned char>(entityValue);
+      TIXMLASSERT(flagIndex < ENTITY_RANGE);
+      mEntityFlag[flagIndex] = true;
+   }
+   mRestrictedEntityFlag[static_cast<unsigned char>('&')] = true;
+   mRestrictedEntityFlag[static_cast<unsigned char>('<')] = true;
+   mRestrictedEntityFlag[static_cast<unsigned char>('>')] = true;	// not required, but consistency is nice
+}
+
+void PrettyXMLPrinter::PrintString(const char* p, bool restricted)
+{
+   // Look for runs of bytes between entities to print.
+   const char* q = p;
+
+   if (mProcessEntities) {
+      const bool* flag = restricted ? mRestrictedEntityFlag : mEntityFlag;
+      while (*q) {
+         TIXMLASSERT(p <= q);
+         // Remember, char is sometimes signed. (How many times has that bitten me?)
+         if (*q > 0 && *q < ENTITY_RANGE) {
+            // Check for entities. If one is found, flush
+            // the stream up until the entity, write the
+            // entity, and keep looking.
+            if (flag[static_cast<unsigned char>(*q)]) {
+               while (p < q) {
+                  const size_t delta = q - p;
+                  const int toPrint = (INT_MAX < delta) ? INT_MAX : static_cast<int>(delta);
+                  mInnerPrinter.Write(p, toPrint);
+                  p += toPrint;
+               }
+               bool entityPatternPrinted = false;
+               for (int i = 0; i < NUM_ENTITIES; ++i) {
+                  if (entities[i].value == *q) {
+                     mInnerPrinter.Putc('&');
+                     mInnerPrinter.Write(entities[i].pattern, entities[i].length);
+                     mInnerPrinter.Putc(';');
+                     entityPatternPrinted = true;
+                     break;
+                  }
+               }
+               if (!entityPatternPrinted) {
+                  // TIXMLASSERT( entityPatternPrinted ) causes gcc -Wunused-but-set-variable in release
+                  TIXMLASSERT(false);
+               }
+               ++p;
+            }
+         }
+         ++q;
+         TIXMLASSERT(p <= q);
+      }
+      // Flush the remaining string. This will be the entire
+      // string if an entity wasn't found.
+      if (p < q) {
+         const size_t delta = q - p;
+         const int toPrint = (INT_MAX < delta) ? INT_MAX : static_cast<int>(delta);
+         mInnerPrinter.Write(p, toPrint);
+      }
+   }
+   else {
+      mInnerPrinter.Write(p);
+   }
+}
+
+bool PrettyXMLPrinter::VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* attribute)
+{
+   const tinyxml2::XMLElement* parentElem = 0;
+   if (element.Parent()) {
+      parentElem = element.Parent()->ToElement();
+   }
+   const bool compactMode = parentElem ? mInnerPrinter.CompactMode(*parentElem) : mInnerPrinter.CompactMode(element);
+   mInnerPrinter.OpenElement(element.Name(), compactMode);
+   mDepth++;
+   while (attribute) {
+      PushAttribute(attribute->Name(), attribute->Value(), compactMode);
+      attribute = attribute->Next();
+   }
+   return true;
+}
+
+void PrettyXMLPrinter::PushAttribute(const char* name, const char* value, bool compactMode)
+{
+   if (compactMode)
+   {
+      mInnerPrinter.Putc(' ');
+   }
+   else
+   {
+      mInnerPrinter.Putc('\n');
+      mInnerPrinter.PrintSpace(mDepth);
+   }
+   mInnerPrinter.Write(name);
+   mInnerPrinter.Write("=\"");
+   PrintString(value, false);
+   mInnerPrinter.Putc('\"');
+}

+ 108 - 0
Engine/source/persistence/taml/fsTinyXml.h

@@ -40,9 +40,18 @@ public:
    VfsXMLPrinter(FileStream& stream, bool compact = false, int depth = 0);
    ~VfsXMLPrinter() override;
 
+   // Re-implement protected functionality in TinyXML2 library, and make it public
+   // (This is a bit dirty, but it's necessary for the PrettyXMLPrinter)
+   bool CompactMode(const tinyxml2::XMLElement& element) override { return tinyxml2::XMLPrinter::CompactMode(element); }
+   void PrintSpace(int depth) override { tinyxml2::XMLPrinter::PrintSpace(depth); }
+   inline void Write(const char* data) { Write(data, strlen(data)); }
+
+   // Add VFS friendly implementations of output functions
    void Print(const char* format, ...) override;
    void Write(const char* data, size_t size) override;
    void Putc(char ch) override;
+
+   // Accept a virtual FileStream instead of a FILE pointer
    FileStream& m_Stream;
 };
 
@@ -127,4 +136,103 @@ public:
    }
 };
 
+class PrettyXMLPrinter : public tinyxml2::XMLPrinter
+{
+   // Re-implement private functionality in TinyXML2
+   static const char LINE_FEED = static_cast<char>(0x0a); // all line endings are normalized to LF
+   static const char LF = LINE_FEED;
+   static const char CARRIAGE_RETURN = static_cast<char>(0x0d); // CR gets filtered out
+   static const char CR = CARRIAGE_RETURN;
+   static const char SINGLE_QUOTE = '\'';
+   static const char DOUBLE_QUOTE = '\"';
+
+   struct Entity
+   {
+      const char* pattern;
+      int length;
+      char value;
+   };
+
+   static const int NUM_ENTITIES = 5;
+   static constexpr  Entity entities[NUM_ENTITIES] = {
+      {"quot", 4, DOUBLE_QUOTE},
+      {"amp", 3, '&'},
+      {"apos", 4, SINGLE_QUOTE},
+      {"lt", 2, '<'},
+      {"gt", 2, '>'}
+   };
+public:
+   PrettyXMLPrinter(VfsXMLPrinter& innerPrinter, int depth = 0);
+
+   /// Visit a document.
+   virtual bool VisitEnter(const tinyxml2::XMLDocument& doc)
+   {
+      mProcessEntities = doc.ProcessEntities();
+      return mInnerPrinter.VisitEnter(doc);
+   }
+
+   /// Visit a document.
+   virtual bool VisitExit(const tinyxml2::XMLDocument& doc)
+   {
+      return mInnerPrinter.VisitExit(doc);
+   }
+
+   /// Visit an element.
+   virtual bool VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* firstAttribute);
+   /// Visit an element.
+   virtual bool VisitExit(const tinyxml2::XMLElement& element)
+   {
+      mDepth--;
+      return mInnerPrinter.VisitExit(element);
+   }
+
+   /// Visit a declaration.
+   virtual bool Visit(const tinyxml2::XMLDeclaration& declaration)
+   {
+      return mInnerPrinter.Visit(declaration);
+   }
+
+   /// Visit a text node.
+   virtual bool Visit(const tinyxml2::XMLText& text)
+   {
+      return mInnerPrinter.Visit(text);
+   }
+
+   /// Visit a comment node.
+   virtual bool Visit(const tinyxml2::XMLComment& comment)
+   {
+      return mInnerPrinter.Visit(comment);
+   }
+
+   /// Visit an unknown node.
+   virtual bool Visit(const tinyxml2::XMLUnknown& unknown)
+   {
+      return mInnerPrinter.Visit(unknown);
+   }
+   
+   void PushAttribute(const char* name, const char* value, bool compactMode);
+
+   // Re-implement private functionality in TinyXML2 library, this is just a copy-paste job
+   void PrintString(const char*, bool restrictedEntitySet); // prints out, after detecting entities.
+
+   // The inner printer we are wrapping, we only support VfsXMLPrinter based classes because
+   // stock tinyxml printer is very closed
+   VfsXMLPrinter& mInnerPrinter;
+
+   // Track private fields that are necessary for private functionality in TinyXML2
+   int mDepth;
+   bool mProcessEntities;
+   bool mCompactMode;
+
+   enum
+   {
+      ENTITY_RANGE = 64,
+      BUF_SIZE = 200
+   };
+
+   bool mEntityFlag[ENTITY_RANGE];
+   bool mRestrictedEntityFlag[ENTITY_RANGE];
+};
+
+
 #endif //_FSTINYXML_H_

+ 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 )
       {
-         _setTrack(getTrack(i),i);
+         StringTableEntry track = getTrack(i);
+         if (track != StringTable->EmptyString())
+         {
+            _setTrack(getTrack(i), i);
          if (!getTrackProfile(i))
          {
             Con::errorf("SFXPlayList::Preload() - unable to find sfxProfile for asset %s", mTrackAssetId[i]);
             return false;
          }
             
-         if( !sfxResolve( &mSlots.mState[ i ], errorStr ) )
+            if (!sfxResolve(&mSlots.mState[i], errorStr))
             return false;
+         }
       }
    }
       

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

@@ -35,6 +35,7 @@
 #include "core/stream/bitStream.h"
 #include "core/resourceManager.h"
 #include "console/engineAPI.h"
+#include "core/stream/fileStream.h"
 
 using namespace Torque;
 
@@ -283,8 +284,13 @@ bool SFXProfile::_preloadBuffer()
 
 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;
 }

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

@@ -1581,133 +1581,70 @@ DefineEngineFunction( sfxPlay, S32, ( const char * trackName, const char * point
 //-----------------------------------------------------------------------------
 
 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"
    "@param track The sound datablock.\n"
    "@return A newly created temporary source in \"Playing\" state or 0 if the operation failed.\n\n"
    "@ref SFXSource_playonce\n\n"
    "@ingroup SFX",
    NULL,
-   "SFXSource sfxPlayOnce( SFXTrack track );"
+   "SFXSource sfxPlayOnce( StringTableEntry assetID );"
 );
 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"
-   "@param track The sound datablock.\n"
+   "@param assetId The sound asset.\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"
+   "@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"
    "@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"
    "@ref SFXSource_playonce\n\n"
    "@ingroup SFX",
    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 ] ) "
    "Create a new play-once source for the given profile or description+filename and start playback of the source.\n"
    "@hide" )
 {
    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;
       }
-   }
 
    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
       {
          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
       {
-         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 )