diff --git a/Engine/source/T3D/assets/SoundAsset.cpp b/Engine/source/T3D/assets/SoundAsset.cpp index 45f78bc48..06a30b7e4 100644 --- a/Engine/source/T3D/assets/SoundAsset.cpp +++ b/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(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) @@ -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()) { diff --git a/Engine/source/T3D/fx/explosion.cpp b/Engine/source/T3D/fx/explosion.cpp index 88a5a9432..d73d1bc01 100644 --- a/Engine/source/T3D/fx/explosion.cpp +++ b/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()) { diff --git a/Engine/source/T3D/gameBase/gameConnection.cpp b/Engine/source/T3D/gameBase/gameConnection.cpp index f759a03da..0331e3764 100644 --- a/Engine/source/T3D/gameBase/gameConnection.cpp +++ b/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 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); + play2D(assetId); - else if ( !mControlObject ) - postNetEvent(new Sim3DAudioEvent(profile,transform)); - - 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 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; } diff --git a/Engine/source/T3D/gameBase/gameConnection.h b/Engine/source/T3D/gameBase/gameConnection.h index c735fb62c..64cdb3cbd 100644 --- a/Engine/source/T3D/gameBase/gameConnection.h +++ b/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. diff --git a/Engine/source/T3D/gameBase/gameConnectionEvents.cpp b/Engine/source/T3D/gameBase/gameConnectionEvents.cpp index 4aff2ce64..d2c3badce 100644 --- a/Engine/source/T3D/gameBase/gameConnectionEvents.cpp +++ b/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 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 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; diff --git a/Engine/source/T3D/gameBase/gameConnectionEvents.h b/Engine/source/T3D/gameBase/gameConnectionEvents.h index b9652a940..ede198645 100644 --- a/Engine/source/T3D/gameBase/gameConnectionEvents.h +++ b/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 mAsset; + MatrixF mTransform; + +public: + typedef NetEvent Parent; + + SimSoundAssetEvent(AssetPtr 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: diff --git a/Engine/source/T3D/shapeBase.cpp b/Engine/source/T3D/shapeBase.cpp index 4730ee6b7..dab20b693 100644 --- a/Engine/source/T3D/shapeBase.cpp +++ b/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 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 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; diff --git a/Engine/source/T3D/shapeBase.h b/Engine/source/T3D/shapeBase.h index 920289385..6ef0fd985 100644 --- a/Engine/source/T3D/shapeBase.h +++ b/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; /// 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 diff --git a/Engine/source/T3D/shapeImage.cpp b/Engine/source/T3D/shapeImage.cpp index ee5d62476..88d5f837c 100644 --- a/Engine/source/T3D/shapeImage.cpp +++ b/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; ishapeIsValid[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; igetSequence()->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; isound && lastState->sound->getSfxProfile()->getDescription()->mIsLooping) { - for(Vector::iterator i = image.mSoundSources.begin(); i != image.mSoundSources.end(); i++) + for (Vector::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 diff --git a/Engine/source/persistence/taml/fsTinyXml.cpp b/Engine/source/persistence/taml/fsTinyXml.cpp index e0dc53572..961d96b0e 100644 --- a/Engine/source/persistence/taml/fsTinyXml.cpp +++ b/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(ch)); +} + bool VfsXMLDocument::LoadFile(const char* pFilename) { // Expand the file-path. @@ -61,6 +96,114 @@ bool VfsXMLDocument::LoadFile(const char* pFilename) return true; } +bool VfsXMLDocument::LoadFile(FileStream& stream) +{ + // Delete the existing data: + Clear(); + // Clear shadowed error + ClearError(); + //TODO: Can't clear location, investigate if this gives issues. + //doc.location.Clear(); + + // Get the file size, so we can pre-allocate the string. HUGE speed impact. + long length = stream.getStreamSize(); + + // Strange case, but good to handle up front. + if (length <= 0) + { + SetError(tinyxml2::XML_ERROR_EMPTY_DOCUMENT, 0, 0); + return false; + } + + // Subtle bug here. TinyXml did use fgets. But from the XML spec: + // 2.11 End-of-Line Handling + // + // + // ...the XML processor MUST behave as if it normalized all line breaks in external + // parsed entities (including the document entity) on input, before parsing, by translating + // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to + // a single #xA character. + // + // + // It is not clear fgets does that, and certainly isn't clear it works cross platform. + // Generally, you expect fgets to translate from the convention of the OS to the c/unix + // convention, and not work generally. + + /* + while( fgets( buf, sizeof(buf), file ) ) + { + data += buf; + } + */ + + char* buf = new char[length + 1]; + buf[0] = 0; + + if (!stream.read(length, buf)) + { + delete[] buf; + SetError(tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, 0); + return false; + } + + // Process the buffer in place to normalize new lines. (See comment above.) + // Copies from the 'p' to 'q' pointer, where p can advance faster if + // a newline-carriage return is hit. + // + // Wikipedia: + // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or + // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... + // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others + // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS + // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 + + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; + + buf[length] = 0; + while (*p) + { + assert(p < (buf + length)); + assert(q <= (buf + length)); + assert(q <= p); + + if (*p == CR) + { + *q++ = LF; + p++; + if (*p == LF) + { + // check for CR+LF (and skip LF) + p++; + } + } + else + { + *q++ = *p++; + } + } + assert(q <= (buf + length)); + *q = 0; + + Parse(buf, length); + + delete[] buf; + return !Error(); +} + +bool VfsXMLDocument::SaveFile(FileStream& stream) +{ + // Clear any error from the last save, otherwise it will get reported + // for *this* call. + ClearError(); + VfsXMLPrinter printer(stream, false, 0); + PrettyXMLPrinter prettyPrinter(printer); + Print(&prettyPrinter); + return !Error(); +} + bool VfsXMLDocument::SaveFile(const char* pFilename) { // Expand the file-name into the file-path buffer. @@ -119,141 +262,110 @@ void VfsXMLDocument::SetError(tinyxml2::XMLError error, int lineNum, const char* delete[] buffer; } -VfsXMLPrinter::VfsXMLPrinter(FileStream& stream, bool compact, int depth) - : XMLPrinter(NULL, compact, depth), - m_Stream(stream) + +// Overwrite Visitation of elements to add newlines before attributes +PrettyXMLPrinter::PrettyXMLPrinter(VfsXMLPrinter& innerPrinter, int depth) + : mInnerPrinter(innerPrinter), + mDepth(depth) { -} - -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(ch)); -} - -bool VfsXMLDocument::LoadFile(FileStream& stream) -{ - // Delete the existing data: - Clear(); - // Clear shadowed error - ClearError(); - //TODO: Can't clear location, investigate if this gives issues. - //doc.location.Clear(); - - // Get the file size, so we can pre-allocate the string. HUGE speed impact. - long length = stream.getStreamSize(); - - // Strange case, but good to handle up front. - if (length <= 0) - { - SetError(tinyxml2::XML_ERROR_EMPTY_DOCUMENT, 0, 0); - return false; + for (int i = 0; i < ENTITY_RANGE; ++i) { + mEntityFlag[i] = false; + mRestrictedEntityFlag[i] = false; } - - // Subtle bug here. TinyXml did use fgets. But from the XML spec: - // 2.11 End-of-Line Handling - // - // - // ...the XML processor MUST behave as if it normalized all line breaks in external - // parsed entities (including the document entity) on input, before parsing, by translating - // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to - // a single #xA character. - // - // - // It is not clear fgets does that, and certainly isn't clear it works cross platform. - // Generally, you expect fgets to translate from the convention of the OS to the c/unix - // convention, and not work generally. - - /* - while( fgets( buf, sizeof(buf), file ) ) - { - data += buf; + for (int i = 0; i < NUM_ENTITIES; ++i) { + const char entityValue = entities[i].value; + const unsigned char flagIndex = static_cast(entityValue); + TIXMLASSERT(flagIndex < ENTITY_RANGE); + mEntityFlag[flagIndex] = true; } - */ + mRestrictedEntityFlag[static_cast('&')] = true; + mRestrictedEntityFlag[static_cast('<')] = true; + mRestrictedEntityFlag[static_cast('>')] = true; // not required, but consistency is nice +} - char* buf = new char[length + 1]; - buf[0] = 0; +void PrettyXMLPrinter::PrintString(const char* p, bool restricted) +{ + // Look for runs of bytes between entities to print. + const char* q = p; - if (!stream.read(length, buf)) - { - delete [] buf; - SetError(tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED, 0, 0); - return false; - } - - // Process the buffer in place to normalize new lines. (See comment above.) - // Copies from the 'p' to 'q' pointer, where p can advance faster if - // a newline-carriage return is hit. - // - // Wikipedia: - // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or - // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... - // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others - // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS - // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 - - const char* p = buf; // the read head - char* q = buf; // the write head - const char CR = 0x0d; - const char LF = 0x0a; - - buf[length] = 0; - while (*p) - { - assert(p < (buf+length)); - assert(q <= (buf+length)); - assert(q <= p); - - if (*p == CR) - { - *q++ = LF; - p++; - if (*p == LF) - { - // check for CR+LF (and skip LF) - 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(*q)]) { + while (p < q) { + const size_t delta = q - p; + const int toPrint = (INT_MAX < delta) ? INT_MAX : static_cast(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); } - else - { - *q++ = *p++; + // 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(delta); + mInnerPrinter.Write(p, toPrint); } } - assert(q <= (buf+length)); - *q = 0; - - Parse(buf, length); - - delete [] buf; - return !Error(); + else { + mInnerPrinter.Write(p); + } } -bool VfsXMLDocument::SaveFile(FileStream& stream) +bool PrettyXMLPrinter::VisitEnter(const tinyxml2::XMLElement& element, const tinyxml2::XMLAttribute* attribute) { - // Clear any error from the last save, otherwise it will get reported - // for *this* call. - ClearError(); - VfsXMLPrinter printer(stream, false, 0); - Print(&printer); - return !Error(); + 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('\"'); } diff --git a/Engine/source/persistence/taml/fsTinyXml.h b/Engine/source/persistence/taml/fsTinyXml.h index 2a83cb614..c4a0d55c2 100644 --- a/Engine/source/persistence/taml/fsTinyXml.h +++ b/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(0x0a); // all line endings are normalized to LF + static const char LF = LINE_FEED; + static const char CARRIAGE_RETURN = static_cast(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_ diff --git a/Engine/source/sfx/sfxPlayList.cpp b/Engine/source/sfx/sfxPlayList.cpp index 0e56d4607..ea186c125 100644 --- a/Engine/source/sfx/sfxPlayList.cpp +++ b/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; + } } } diff --git a/Engine/source/sfx/sfxProfile.cpp b/Engine/source/sfx/sfxProfile.cpp index 4cd6a8cdb..ab3bc44ab 100644 --- a/Engine/source/sfx/sfxProfile.cpp +++ b/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& 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; } diff --git a/Engine/source/sfx/sfxSystem.cpp b/Engine/source/sfx/sfxSystem.cpp index 3d793995e..d23880bfd 100644 --- a/Engine/source/sfx/sfxSystem.cpp +++ b/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 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 )