This commit is contained in:
marauder2k7 2026-05-03 17:13:24 +00:00 committed by GitHub
commit 903b2bd216
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 497 additions and 124 deletions

View file

@ -200,119 +200,141 @@ void SoundAsset::initPersistFields()
docsURL; docsURL;
// Call parent. // Call parent.
Parent::initPersistFields(); Parent::initPersistFields();
addGroup("SoundSlots"); GROUP("SoundSlots")
addArray("slots", SFXPlayList::SFXPlaylistSettings::NUM_SLOTS); {
addProtectedField("soundFile", TypeAssetLooseFilePath, Offset(mSoundFile, SoundAsset), ARRAY_BEGIN("slots", SFXPlayList::SFXPlaylistSettings::NUM_SLOTS)
&_setSoundFile, &defaultProtectedGetFn, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, "Path to the sound file."); .showWhen(&slotVisible);
addField("replay", TYPEID< SFXPlayList::EReplayMode >(), Offset(mPlaylist.mSlots.mReplayMode, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("soundFile", TypeAssetLooseFilePath, Offset(mSoundFile, SoundAsset))
"Behavior when an already playing sound is encountered on this slot from a previous cycle.\n" .doc("Path to the sound file.")
.onSet(&_setSoundFile);
ADD_FIELD("replay", TYPEID< SFXPlayList::EReplayMode >(), Offset(mPlaylist.mSlots.mReplayMode, SoundAsset))
.doc("Behavior when an already playing sound is encountered on this slot from a previous cycle.\n"
"Each slot can have an arbitrary number of sounds playing on it from previous cycles. This field determines " "Each slot can have an arbitrary number of sounds playing on it from previous cycles. This field determines "
"how SFXController will handle these sources."); "how SFXController will handle these sources.");
addField("transitionIn", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionIn, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS,
"Behavior when moving into this slot.\n" ADD_FIELD("transitionIn", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionIn, SoundAsset))
.doc("Behavior when moving into this slot.\n"
"After the delayIn time has expired (if any), this slot determines what the controller " "After the delayIn time has expired (if any), this slot determines what the controller "
"will do before actually playing the slot."); "will do before actually playing the slot.");
addField("transitionOut", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionOut, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("transitionOut", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionOut, SoundAsset))
"Behavior when moving out of this slot.\n" .doc("Behavior when moving out of this slot.\n"
"After the #detailTimeOut has expired (if any), this slot determines what the controller " "After the #detailTimeOut has expired (if any), this slot determines what the controller "
"will do before moving on to the next slot."); "will do before moving on to the next slot.");
addFieldV("delayTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeIn.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("delayTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"Seconds to wait after moving into slot before #transitionIn."); .doc("Seconds to wait after moving into slot before #transitionIn.");
addField("delayTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeIn.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("delayTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeIn.mVariance, SoundAsset))
"Bounds on randomization of #delayTimeIn.\n\n" .doc("Bounds on randomization of #delayTimeIn.\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("delayTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeOut.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("delayTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"Seconds to wait before moving out of slot after #transitionOut."); .doc("Seconds to wait before moving out of slot after #transitionOut.");
addField("delayTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeOut.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("delayTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeOut.mVariance, SoundAsset))
"Bounds on randomization of #delayTimeOut.\n\n" .doc("Bounds on randomization of #delayTimeOut.\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("fadeTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeIn.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("fadeTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"Seconds to fade sound in (-1 to use the track's own fadeInTime.)\n" .doc("Seconds to fade sound in (-1 to use the track's own fadeInTime.)\n"
"@see SFXDescription::fadeTimeIn"); "@see SFXDescription::fadeTimeIn");
addField("fadeTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeIn.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("fadeTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeIn.mVariance, SoundAsset))
"Bounds on randomization of #fadeInTime.\n\n" .doc("Bounds on randomization of #fadeInTime.\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("fadeTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeOut.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("fadeTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"Seconds to fade sound out (-1 to use the track's own fadeOutTime.)\n" .doc("Seconds to fade sound out (-1 to use the track's own fadeOutTime.)\n"
"@see SFXDescription::fadeTimeOut"); "@see SFXDescription::fadeTimeOut");
addField("fadeTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeOut.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("fadeTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeOut.mVariance, SoundAsset))
"Bounds on randomization of #fadeOutTime\n\n" .doc("Bounds on randomization of #fadeOutTime\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("referenceDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMinDistance.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("referenceDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMinDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"@c referenceDistance to set for 3D sounds in this slot (<1 to use @c referenceDistance of track's own description).\n" .doc("@c referenceDistance to set for 3D sounds in this slot (<1 to use @c referenceDistance of track's own description).\n"
"@see SFXDescription::referenceDistance"); "@see SFXDescription::referenceDistance");
addField("referenceDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMinDistance.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("referenceDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMinDistance.mVariance, SoundAsset))
"Bounds on randomization of #referenceDistance.\n\n" .doc("Bounds on randomization of #referenceDistance.\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("maxSlotDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMaxDistance.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("maxSlotDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMaxDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"@c maxDistance to apply to 3D sounds in this slot (<1 to use @c maxDistance of track's own description).\n" .doc("@c maxDistance to apply to 3D sounds in this slot (<1 to use @c maxDistance of track's own description).\n"
"@see SFXDescription::maxDistance"); "@see SFXDescription::maxDistance");
addField("maxSlotDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMaxDistance.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("maxSlotDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMaxDistance.mVariance, SoundAsset))
"Bounds on randomization of #maxDistance.\n\n" .doc("Bounds on randomization of #maxDistance.\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("volumeScale", TypeRangedF32, Offset(mPlaylist.mSlots.mVolumeScale.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("volumeScale", TypeRangedF32, Offset(mPlaylist.mSlots.mVolumeScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"Scale factor to apply to volume of sounds played on this list slot.\n" .doc("Scale factor to apply to volume of sounds played on this list slot.\n"
"This value will scale the actual volume level set on the track assigned to the slot, i.e. a value of 0.5 will " "This value will scale the actual volume level set on the track assigned to the slot, i.e. a value of 0.5 will "
"cause the track to play at half-volume."); "cause the track to play at half-volume.");
addField("volumeScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mVolumeScale.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("volumeScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mVolumeScale.mVariance, SoundAsset))
"Bounds on randomization of #volumeScale.\n\n" .doc("Bounds on randomization of #volumeScale.\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("pitchScale", TypeRangedF32, Offset(mPlaylist.mSlots.mPitchScale.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("pitchScale", TypeRangedF32, Offset(mPlaylist.mSlots.mPitchScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat)
"Scale factor to apply to pitch of sounds played on this list slot.\n" .doc("Scale factor to apply to pitch of sounds played on this list slot.\n"
"This value will scale the actual pitch set on the track assigned to the slot, i.e. a value of 0.5 will " "This value will scale the actual pitch set on the track assigned to the slot, i.e. a value of 0.5 will "
"cause the track to play at half its assigned speed."); "cause the track to play at half its assigned speed.");
addField("pitchScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mPitchScale.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("pitchScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mPitchScale.mVariance, SoundAsset))
"Bounds on randomization of #pitchScale.\n\n" .doc("Bounds on randomization of #pitchScale.\n\n"
"@ref SFXPlayList_randomization\n"); "@ref SFXPlayList_randomization\n");
addFieldV("repeatCount", TypeRangedS32, Offset(mPlaylist.mSlots.mRepeatCount, SoundAsset), &CommonValidators::PositiveInt, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("repeatCount", TypeRangedS32, Offset(mPlaylist.mSlots.mRepeatCount, SoundAsset)).validate(&CommonValidators::PositiveInt)
"Number of times to loop this slot."); .doc("Number of times to loop this slot.");
addField("state", TypeSFXStateName, Offset(mPlaylist.mSlots.mState, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("state", TypeSFXStateName, Offset(mPlaylist.mSlots.mState, SoundAsset))
"State that must be active for this slot to play.\n\n" .doc("State that must be active for this slot to play.\n\n"
"@ref SFXPlayList_states"); "@ref SFXPlayList_states");
addField("stateMode", TYPEID< SFXPlayList::EStateMode >(), Offset(mPlaylist.mSlots.mStateMode, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, ADD_FIELD("stateMode", TYPEID< SFXPlayList::EStateMode >(), Offset(mPlaylist.mSlots.mStateMode, SoundAsset))
"Behavior when assigned state is deactivated while slot is playing.\n\n" .doc("Behavior when assigned state is deactivated while slot is playing.\n\n"
"@ref SFXPlayList_states"); "@ref SFXPlayList_states");
endArray("slots");
endGroup("SoundSlots");
addGroup("General Profile"); ARRAY_END("slots");
addFieldV("pitchAdjust", TypeRangedF32, Offset(mProfileDesc.mPitch, SoundAsset), &CommonValidators::PositiveFloat, "Adjustment of the pitch value 1 is default."); }
addFieldV("volumeAdjust", TypeRangedF32, Offset(mProfileDesc.mVolume, SoundAsset), &CommonValidators::PositiveFloat, "Adjustment to the volume.");
addField("is3D", TypeBool, Offset(mProfileDesc.mIs3D, SoundAsset), "Set this sound to 3D."); GROUP("General Profile")
addField("isLooping", TypeBool, Offset(mProfileDesc.mIsLooping, SoundAsset), "Does this sound loop."); {
ADD_FIELD("pitchAdjust", TypeRangedF32, Offset(mProfileDesc.mPitch, SoundAsset))
.doc("Adjustment of the pitch value 1 is default.")
.validate(&CommonValidators::PositiveFloat);
ADD_FIELD("volumeAdjust", TypeRangedF32, Offset(mProfileDesc.mVolume, SoundAsset))
.doc("Adjustment to the volume.")
.validate(&CommonValidators::PositiveFloat);
ADD_FIELD("is3D", TypeBool, Offset(mProfileDesc.mIs3D, SoundAsset)).doc("Set this sound to 3D.");
ADD_FIELD("isLooping", TypeBool, Offset(mProfileDesc.mIsLooping, SoundAsset)).doc("Does this sound loop.");
// if streaming, a default packet size should be chosen for all sounds. // if streaming, a default packet size should be chosen for all sounds.
addField("isStreaming", TypeBool, Offset(mProfileDesc.mIsStreaming, SoundAsset), "Use streaming."); ADD_FIELD("isStreaming", TypeBool, Offset(mProfileDesc.mIsStreaming, SoundAsset)).doc("Use streaming.");
//....why? //....why?
addField("useHardware", TypeBool, Offset(mProfileDesc.mUseHardware, SoundAsset), "Use hardware mixing for this sound."); ADD_FIELD("useHardware", TypeBool, Offset(mProfileDesc.mUseHardware, SoundAsset)).doc("Use hardware mixing for this sound.");
addField("sourceGroup", TypeSFXSourceName, Offset(mProfileDesc.mSourceGroup, SoundAsset), "Group that sources playing with this description should be put into."); ADD_FIELD("sourceGroup", TypeSFXSourceName, Offset(mProfileDesc.mSourceGroup, SoundAsset)).doc("Group that sources playing with this description should be put into.");
addField("preload", TypeBool, Offset(mPreload, SoundAsset), "Whether to preload sound data when the profile is added to system."); ADD_FIELD("preload", TypeBool, Offset(mPreload, SoundAsset)).doc("Whether to preload sound data when the profile is added to system.");
endGroup("General Profile"); }
addGroup("Fading"); GROUP("Fading")
addFieldV("fadeInTime", TypeRangedF32, Offset(mProfileDesc.mFadeInTime, SoundAsset), &CommonValidators::PositiveFloat, "Number of seconds to gradually fade in volume from zero when playback starts."); {
addFieldV("fadeOutTime", TypeRangedF32, Offset(mProfileDesc.mFadeOutTime, SoundAsset), &CommonValidators::PositiveFloat, "Number of seconds to gradually fade out volume down to zero when playback is stopped or paused."); ADD_FIELD("fadeInTime", TypeRangedF32, Offset(mProfileDesc.mFadeInTime, SoundAsset))
addField("fadeInEase", TypeEaseF, Offset(mProfileDesc.mFadeInEase, SoundAsset), "Easing curve for fade-in transition."); .validate(&CommonValidators::PositiveFloat)
addField("fadeOutEase", TypeEaseF, Offset(mProfileDesc.mFadeOutEase, SoundAsset), "Easing curve for fade-out transition."); .doc("Number of seconds to gradually fade in volume from zero when playback starts.");
addField("fadeLoops", TypeBool, Offset(mProfileDesc.mFadeLoops, SoundAsset), "Fade each cycle of a loop in and/or out; otherwise only fade-in first cycle.");
endGroup("Fading");
addGroup("3D"); ADD_FIELD("fadeOutTime", TypeRangedF32, Offset(mProfileDesc.mFadeOutTime, SoundAsset))
addFieldV("minDistance", TypeRangedF32, Offset(mProfileDesc.mMinDistance, SoundAsset), &CommonValidators::PositiveFloat, "Minimum distance for sound."); .validate(&CommonValidators::PositiveFloat)
addFieldV("maxDistance", TypeRangedF32, Offset(mProfileDesc.mMaxDistance, SoundAsset), &CommonValidators::PositiveFloat, "Max distance for sound."); .doc("Number of seconds to gradually fade out volume down to zero when playback is stopped or paused.");
addFieldV("coneInsideAngle", TypeRangedS32, Offset(mProfileDesc.mConeInsideAngle, SoundAsset), &CommonValidators::S32_PosDegreeRange, "Cone inside angle.");
addFieldV("coneOutsideAngle", TypeRangedS32, Offset(mProfileDesc.mConeOutsideAngle, SoundAsset), &CommonValidators::S32_PosDegreeRange, "Cone outside angle.");
addFieldV("coneOutsideVolume", TypeRangedF32, Offset(mProfileDesc.mConeOutsideVolume, SoundAsset), &CommonValidators::NormalizedFloat, "Cone outside volume.");
addFieldV("rolloffFactor", TypeRangedF32, Offset(mProfileDesc.mRolloffFactor, SoundAsset), &CommonValidators::NegDefaultF32, "Rolloff factor.");
addField("scatterDistance", TypePoint3F, Offset(mProfileDesc.mScatterDistance, SoundAsset), "Randomization to the spacial position of the sound.");
endGroup("3D");
addGroup("Playlist settings"); ADD_FIELD("fadeInEase", TypeEaseF, Offset(mProfileDesc.mFadeInEase, SoundAsset)).doc("Easing curve for fade-in transition.");
addField("random", TYPEID< SFXPlayList::ERandomMode >(), Offset(mPlaylist.mRandomMode, SoundAsset), "Slot playback order randomization pattern."); ADD_FIELD("fadeOutEase", TypeEaseF, Offset(mProfileDesc.mFadeOutEase, SoundAsset)).doc("Easing curve for fade-out transition.");
addField("loopMode", TYPEID< SFXPlayList::ELoopMode >(), Offset(mPlaylist.mLoopMode, SoundAsset), "Behavior when description has looping enabled."); ADD_FIELD("fadeLoops", TypeBool, Offset(mProfileDesc.mFadeLoops, SoundAsset)).doc("Fade each cycle of a loop in and/or out; otherwise only fade-in first cycle.");
addFieldV("numSlotsToPlay", TypeRangedS32, Offset(mPlaylist.mNumSlotsToPlay, SoundAsset), &playlistSlotRange, "Number of slots to play."); }
addField("trace", TypeBool, Offset(mPlaylist.mTrace, SoundAsset), "Enable/disable execution tracing for this playlist (local only).");
endGroup("Playlist settings"); GROUP("3D")
{
ADD_FIELD("minDistance", TypeRangedF32, Offset(mProfileDesc.mMinDistance, SoundAsset)).validate(&CommonValidators::PositiveFloat).doc("Minimum distance for sound.");
ADD_FIELD("maxDistance", TypeRangedF32, Offset(mProfileDesc.mMaxDistance, SoundAsset)).validate(&CommonValidators::PositiveFloat).doc("Max distance for sound.");
ADD_FIELD("coneInsideAngle", TypeRangedS32, Offset(mProfileDesc.mConeInsideAngle, SoundAsset)).validate(&CommonValidators::S32_PosDegreeRange).doc("Cone inside angle.");
ADD_FIELD("coneOutsideAngle", TypeRangedS32, Offset(mProfileDesc.mConeOutsideAngle, SoundAsset)).validate(&CommonValidators::S32_PosDegreeRange).doc("Cone outside angle.");
ADD_FIELD("coneOutsideVolume", TypeRangedF32, Offset(mProfileDesc.mConeOutsideVolume, SoundAsset)).validate(&CommonValidators::NormalizedFloat).doc("Cone outside volume.");
ADD_FIELD("rolloffFactor", TypeRangedF32, Offset(mProfileDesc.mRolloffFactor, SoundAsset)).validate(&CommonValidators::NegDefaultF32).doc("Rolloff factor.");
ADD_FIELD("scatterDistance", TypePoint3F, Offset(mProfileDesc.mScatterDistance, SoundAsset)).doc("Randomization to the spacial position of the sound.");
}
GROUP("Playlist settings")
{
ADD_FIELD("random", TYPEID< SFXPlayList::ERandomMode >(), Offset(mPlaylist.mRandomMode, SoundAsset)).doc("Slot playback order randomization pattern.");
ADD_FIELD("loopMode", TYPEID< SFXPlayList::ELoopMode >(), Offset(mPlaylist.mLoopMode, SoundAsset)).doc("Behavior when description has looping enabled.");
ADD_FIELD("numSlotsToPlay", TypeRangedS32, Offset(mPlaylist.mNumSlotsToPlay, SoundAsset)).validate(&playlistSlotRange).doc("Number of slots to play.");
ADD_FIELD("trace", TypeBool, Offset(mPlaylist.mTrace, SoundAsset)).doc("Enable/disable execution tracing for this playlist (local only).");
}
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -462,6 +484,18 @@ bool SoundAsset::_setSoundFile(void* object, const char* index, const char* data
return true; return true;
} }
bool SoundAsset::slotVisible(void* object, const char* index)
{
S32 idx = index ? dAtoi(index) : 0;
if (idx == 0) return true;
SoundAsset* sa = static_cast<SoundAsset*>(object);
if (!sa)
return false;
return sa->mSoundFile[idx - 1] != NULL && sa->mSoundFile[idx - 1][0] != '\0';
}
StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName) StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName)
{ {
if (fileName == StringTable->EmptyString()) if (fileName == StringTable->EmptyString())

View file

@ -156,6 +156,7 @@ public:
DECLARE_CONOBJECT(SoundAsset); DECLARE_CONOBJECT(SoundAsset);
static bool _setSoundFile(void* object, const char* index, const char* data); static bool _setSoundFile(void* object, const char* index, const char* data);
static bool slotVisible(void* object, const char* index);
U32 load() override; U32 load() override;
inline StringTableEntry getSoundPath(const U32 slotId = 0) const { return mSoundPath[slotId]; }; inline StringTableEntry getSoundPath(const U32 slotId = 0) const { return mSoundPath[slotId]; };
SFXProfile* getSfxProfile(const U32 slotId = 0) { return mSFXProfile[slotId]; } SFXProfile* getSfxProfile(const U32 slotId = 0) { return mSFXProfile[slotId]; }

View file

@ -356,6 +356,83 @@ static char* suppressSpaces(const char* in_pname)
return replacebuf; return replacebuf;
} }
static U32 sg_currentArrayElementCount = 1;
void ConsoleObject::registerField(const char* name, U32 type, dsize_t offset, const FieldDescriptor& desc)
{
AbstractClassRep::Field f;
const bool isCollection = desc.isArrayBegin || desc.isArrayEnd || desc.isGroupBegin || desc.isGroupEnd;
if (isCollection)
{
char* pFieldNameBuf = suppressSpaces(name);
if (desc.isGroupBegin)
{
// Append group begin type to fieldname.
dStrcat(pFieldNameBuf, "_begingroup", 1024);
f.type = AbstractClassRep::StartGroupFieldType;
}
if (desc.isGroupEnd)
{
// Append group end type to fieldname.
dStrcat(pFieldNameBuf, "_endgroup", 1024);
f.type = AbstractClassRep::EndGroupFieldType;
}
if (desc.isArrayBegin)
{
// Append array type to fieldname.
dStrcat(pFieldNameBuf, "_beginarray", 1024);
f.type = AbstractClassRep::StartArrayFieldType;
}
if (desc.isArrayEnd)
{
// Append array end type to fieldname.
dStrcat(pFieldNameBuf, "_endarray", 1024);
f.type = AbstractClassRep::EndArrayFieldType;
}
f.pFieldname = StringTable->insert(pFieldNameBuf);
f.pGroupname = StringTable->insert(name);
f.elementCount = desc.elementCount;
}
else
{
ConsoleBaseType* conType = ConsoleBaseType::getType(desc._type);
AssertFatal(conType, avar("ConsoleObject::addProtectedField[%s] - invalid console type", name));
f.pFieldname = StringTable->insert(name);
f.type = desc._type;
f.table = conType->getEnumTable();
}
if (desc.isArrayBegin)
sg_currentArrayElementCount = desc.elementCount;
if (desc.isArrayEnd)
sg_currentArrayElementCount = 1;
if (desc.docs)
f.pFieldDocs = desc.docs;
if (!isCollection)
f.elementCount = desc.elementCount > 1 ? desc.elementCount : sg_currentArrayElementCount;
f.offset = desc._offset;
f.validator = desc.validator;
f.setDataFn = desc.setFn;
f.getDataFn = desc.getFn;
f.writeDataFn = desc.writeFn;
f.visibilityFn = desc.visibilityFn;
f.groupExpand = desc.isExpanded;
f.networkMask = desc.networkMask;
f.flag = desc.flags;
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addGroup(const char* in_pGroupname, const char* in_pGroupDocs) void ConsoleObject::addGroup(const char* in_pGroupname, const char* in_pGroupDocs)
{ {
// Remove spaces. // Remove spaces.
@ -1130,3 +1207,23 @@ DefineEngineFunction(linkNamespaces, bool, ( String childNSName, String parentNS
return true; return true;
} }
FieldDescriptor::~FieldDescriptor()
{
if (!_active)
return;
if (_name == NULL || _name[0] == '\0')
return;
_active = false;
// if we are a collection, sanitize some properties.
if (isGroupBegin || isGroupEnd || isArrayBegin || isArrayEnd)
{
elementCount = isArrayBegin ? elementCount : 0;
validator = NULL;
networkMask = 0;
}
ConsoleObject::registerField(_name, _type, _offset, *this);
}

View file

@ -456,6 +456,11 @@ public:
/// This is a function pointer typedef to support optional writing for fields. /// This is a function pointer typedef to support optional writing for fields.
typedef bool(*WriteDataNotify)(void* obj, StringTableEntry pFieldName); typedef bool(*WriteDataNotify)(void* obj, StringTableEntry pFieldName);
/// <summary>
/// This is a function pointer typedef to support optional visibility of fields.
/// </summary>
typedef bool(*VisibilityNotify)(void* obj, const char* array);
/// These are special field type values used to mark /// These are special field type values used to mark
/// groups and arrays in the field list. /// groups and arrays in the field list.
/// @see Field::type /// @see Field::type
@ -517,6 +522,7 @@ public:
setDataFn( NULL ), setDataFn( NULL ),
getDataFn( NULL ), getDataFn( NULL ),
writeDataFn(NULL), writeDataFn(NULL),
visibilityFn(NULL),
networkMask(0) networkMask(0)
{ {
doNotSubstitute = keepClearSubsOnly = false; doNotSubstitute = keepClearSubsOnly = false;
@ -538,6 +544,7 @@ public:
SetDataNotify setDataFn; ///< Set data notify Fn SetDataNotify setDataFn; ///< Set data notify Fn
GetDataNotify getDataFn; ///< Get data notify Fn GetDataNotify getDataFn; ///< Get data notify Fn
WriteDataNotify writeDataFn; ///< Function to determine whether data should be written or not. WriteDataNotify writeDataFn; ///< Function to determine whether data should be written or not.
VisibilityNotify visibilityFn; ///< Function to determine this field is visibile or not.
bool doNotSubstitute; bool doNotSubstitute;
bool keepClearSubsOnly; bool keepClearSubsOnly;
@ -764,9 +771,173 @@ template< typename T > EnginePropertyTable& ConcreteAbstractClassRep< T >::smPro
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Forward declaration of this function so it can be used in the class // Forward declaration of this function so it can be used in the class
bool defaultProtectedSetFn(void* object, const char* index, const char* data);
const char *defaultProtectedGetFn( void *obj, const char *data ); const char *defaultProtectedGetFn( void *obj, const char *data );
bool defaultProtectedWriteFn(void* obj, StringTableEntry pFieldName); bool defaultProtectedWriteFn(void* obj, StringTableEntry pFieldName);
struct FieldDescriptor {
// Context for auto-registration
const char* _name = NULL;
U32 _type = 0;
dsize_t _offset = 0;
bool isExpanded = false;
bool isGroupBegin = false;
bool isGroupEnd = false;
bool isArrayBegin = false;
bool isArrayEnd = false;
bool _active = true;
// Field properties
const char* docs = NULL;
AbstractClassRep::SetDataNotify setFn = &defaultProtectedSetFn;
AbstractClassRep::GetDataNotify getFn = &defaultProtectedGetFn;
AbstractClassRep::WriteDataNotify writeFn = &defaultProtectedWriteFn;
TypeValidator* validator = NULL;
U32 elementCount = 1;
U32 flags = 0;
U32 networkMask = 0;
AbstractClassRep::VisibilityNotify visibilityFn = NULL;
// Add documentation to this field.
FieldDescriptor& doc(const char* d) { docs = d; return *this; }
// The number of elements for this field (U32 number)
FieldDescriptor& elements(U32 n) { elementCount = n; return *this; }
// The validator for this field.
FieldDescriptor& validate(TypeValidator* v) { validator = v; return *this; }
// The set data function.
FieldDescriptor& onSet(AbstractClassRep::SetDataNotify fn) { setFn = fn; return *this; }
// The get data function.
FieldDescriptor& onGet(AbstractClassRep::GetDataNotify fn) { getFn = fn; return *this; }
// The on write function
FieldDescriptor& onWrite(AbstractClassRep::WriteDataNotify fn) { writeFn = fn; return *this; }
// The visibility function
FieldDescriptor& showWhen(AbstractClassRep::VisibilityNotify fn) { visibilityFn = fn; return *this; }
// The network mask changes will trigger.
FieldDescriptor& network(U32 mask) { networkMask = mask; return *this; }
// The flags for this field.
FieldDescriptor& withFlags(U32 f) { flags = f; return *this; }
// This field marks the start of a group.
FieldDescriptor& startGroup() { isGroupBegin = true; return *this; }
// This field marks the end of a group.
FieldDescriptor& endGroup() { isGroupEnd = true; return *this; }
// This field marks the start of an array.
FieldDescriptor& startArray() { isArrayBegin = true; return *this; }
// This field marks the end of an array.
FieldDescriptor& endArray() { isArrayEnd = true; return *this; }
// This field should start expanded in the inspector.
FieldDescriptor& expanded() { isExpanded = true; return *this; }
FieldDescriptor(const char* name, U32 type = 0, dsize_t offset = 0)
: _name(name), _type(type), _offset(offset)
{}
FieldDescriptor(const FieldDescriptor&) = delete;
FieldDescriptor& operator=(const FieldDescriptor&) = delete;
FieldDescriptor(FieldDescriptor&& other) noexcept
{
moveFrom(std::move(other));
}
FieldDescriptor& operator=(FieldDescriptor&& other) noexcept
{
if (this != &other)
moveFrom(std::move(other));
return *this;
}
void moveFrom(FieldDescriptor&& other)
{
// transfer all data
_name = other._name;
_type = other._type;
_offset = other._offset;
docs = other.docs;
setFn = other.setFn;
getFn = other.getFn;
writeFn = other.writeFn;
validator = other.validator;
elementCount = other.elementCount;
flags = other.flags;
networkMask = other.networkMask;
visibilityFn = other.visibilityFn;
isExpanded = other.isExpanded;
isGroupBegin = other.isGroupBegin;
isGroupEnd = other.isGroupEnd;
isArrayBegin = other.isArrayBegin;
isArrayEnd = other.isArrayEnd;
// transfer ownership of "registration responsibility"
_active = other._active;
// CRITICAL: disable the source so its destructor does NOTHING
other._active = false;
}
~FieldDescriptor();
};
/// Registers a standard field.
/// Usage:
/// FIELD("health", TypeS32, Offset(mHealth, Player)).doc("Player health");
#define ADD_FIELD(name, type, offset) \
FieldDescriptor(name, type, offset)
/// Field with fixed element count.
/// Usage:
/// FIELD_ARRAY("color", TypeColorF, Offset(mColor, GuiControl), 4);
#define FIELD_ARRAY(name, type, offset, count) \
FieldDescriptor(name, type, offset).elements(count)
/// Begins a field group in the inspector.
/// All fields declared after this will belong to the group until GROUP_END().
/// Usage:
/// GROUP_BEGIN("Rendering").expanded();
#define GROUP_BEGIN(name) \
FieldDescriptor(name).startGroup()
/// Ends the current field group.
/// Must match a preceding GROUP_BEGIN().
/// Usage:
/// GROUP_END("Rendering");
#define GROUP_END(name) \
FieldDescriptor(name).endGroup()
/// Begins an array block in the inspector.
/// Usage:
/// ARRAY_BEGIN("Waypoints");
#define ARRAY_BEGIN(name, count) \
FieldDescriptor(name).startArray().elements(count)
/// Ends an array block.
/// Usage:
/// ARRAY_END("Waypoints");
#define ARRAY_END(name) \
FieldDescriptor(name).endArray()
/// Declares a scoped inspector group.
///
/// Provides a structured grouping of fields in the inspector UI using a scoped
/// block. Internally, this macro guarantees that a matching group end marker
/// is emitted, preventing mismatched begin/end pairs.
#define GROUP(name) \
for (bool _once = true; _once; _once = false) \
for (FieldDescriptor(name).startGroup(); _once; FieldDescriptor(name).endGroup(), _once = false)
/// Declares a scoped inspector array block.
///
/// Defines a logical array grouping in the inspector UI. Fields declared within
/// the block are treated as elements of the array. Like GROUP, this macro
/// ensures that the array begin/end markers are correctly paired using scoped
/// execution.
#define ARRAY(name, count) \
for (bool _once = true; _once; _once = false) \
for (FieldDescriptor(name).startArray().elements(count); _once; FieldDescriptor(name).endArray(), _once = false)
//============================================================================= //=============================================================================
// ConsoleObject. // ConsoleObject.
//============================================================================= //=============================================================================
@ -871,6 +1042,7 @@ public:
/// @name Fields /// @name Fields
/// @{ /// @{
static void registerField(const char* name, U32 type, dsize_t offset, const FieldDescriptor& desc);
/// Mark the beginning of a group of fields. /// Mark the beginning of a group of fields.
/// ///

View file

@ -785,6 +785,59 @@ void GuiInspector::refresh()
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void GuiInspector::updateVisibility()
{
const U32 numTargets = getNumInspectObjects();
for (U32 i = 0; i < numTargets; i++)
{
SimObject* target = getInspectObject(i);
if (!target) return;
for (GuiInspectorGroup* group : mGroups)
{
const AbstractClassRep::Field* g = target->findField(group->getGroupName().c_str());
// if group has its own visibility function let it control it.
bool group_visible = (!g || !g->visibilityFn) ? true : g->visibilityFn(target, "0");
if (!group_visible)
{
group->setVisible(group_visible);
return;
}
bool anyVisible = false;
for (GuiInspectorField* field : group->mChildren)
{
const AbstractClassRep::Field* f = field->getField();
StringTableEntry idx = field->getArrayIndex();
bool visible = (!f || !f->visibilityFn) ? true : f->visibilityFn(target, idx);
field->setVisible(visible);
if (visible)
anyVisible = true;
}
// Per-array-element rollout visibility
for (GuiInspectorGroup::ArrayElementEntry& elem : group->mArrayElements)
{
bool visible = false;
if (!elem.arrayField || !elem.arrayField->visibilityFn || elem.elementIndex == 0)
visible = true;
if (!visible)
visible = elem.arrayField->visibilityFn(target, String::ToString(elem.elementIndex));
elem.rollout->setVisible(visible);
if (visible) anyVisible = true;
}
group->setVisible(anyVisible);
}
}
}
void GuiInspector::sendInspectPreApply() void GuiInspector::sendInspectPreApply()
{ {
const U32 numObjects = getNumInspectObjects(); const U32 numObjects = getNumInspectObjects();

View file

@ -90,6 +90,8 @@ public:
return NULL; return NULL;
} }
void updateVisibility();
S32 getComponentGroupTargetId() { return mComponentGroupTargetId; } S32 getComponentGroupTargetId() { return mComponentGroupTargetId; }
void setComponentGroupTargetId(S32 compId) { mComponentGroupTargetId = compId; } void setComponentGroupTargetId(S32 compId) { mComponentGroupTargetId = compId; }

View file

@ -835,6 +835,8 @@ void GuiInspectorField::updateValue()
} }
else else
setValue( getData() ); setValue( getData() );
mInspector->updateVisibility();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View file

@ -164,6 +164,7 @@ class GuiInspectorField : public GuiControl
/// ///
StringTableEntry getArrayIndex() const { return mFieldArrayIndex; } StringTableEntry getArrayIndex() const { return mFieldArrayIndex; }
AbstractClassRep::Field* getField() const { return mField; }
/// Called from within setData to allow child classes /// Called from within setData to allow child classes
/// to perform their own verification. /// to perform their own verification.

View file

@ -79,6 +79,7 @@ GuiInspectorGroup::GuiInspectorGroup( const String& groupName,
mChildren.clear(); mChildren.clear();
mMargin.set(0,0,4,0); mMargin.set(0,0,4,0);
VECTOR_SET_ASSOCIATION(mArrayElements);
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -229,6 +230,7 @@ void GuiInspectorGroup::clearFields()
// that we keep for our own convenience. // that we keep for our own convenience.
mArrayCtrls.clear(); mArrayCtrls.clear();
mChildren.clear(); mChildren.clear();
mArrayElements.clear();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -336,7 +338,7 @@ bool GuiInspectorGroup::inspectGroup()
GuiControlProfile* elementRolloutProfile = dynamic_cast<GuiControlProfile*>(Sim::findObject("GuiInspectorRolloutProfile0")); GuiControlProfile* elementRolloutProfile = dynamic_cast<GuiControlProfile*>(Sim::findObject("GuiInspectorRolloutProfile0"));
char buf[256]; char buf[256];
dSprintf(buf, 256, " [%i]", i); dSprintf(buf, 256, " [%i/%i]", i, field->elementCount);
elementRollout->setControlProfile(elementRolloutProfile); elementRollout->setControlProfile(elementRolloutProfile);
elementRollout->setCaption(buf); elementRollout->setCaption(buf);
@ -349,6 +351,8 @@ bool GuiInspectorGroup::inspectGroup()
elementRollout->instantCollapse(); elementRollout->instantCollapse();
arrayStack->addObject(elementRollout); arrayStack->addObject(elementRollout);
mArrayElements.push_back({ elementRollout, (S32)i, field });
} }
pArrayRollout = arrayRollout; pArrayRollout = arrayRollout;

View file

@ -44,6 +44,13 @@ private:
typedef GuiRolloutCtrl Parent; typedef GuiRolloutCtrl Parent;
public: public:
// Members // Members
struct ArrayElementEntry
{
GuiRolloutCtrl* rollout;
S32 elementIndex;
const AbstractClassRep::Field* arrayField; // the StartArrayFieldType field
};
Vector<ArrayElementEntry> mArrayElements;
SimObjectPtr<GuiInspector> mParent; SimObjectPtr<GuiInspector> mParent;
Vector<GuiInspectorField*> mChildren; Vector<GuiInspectorField*> mChildren;
GuiStackControl* mStack; GuiStackControl* mStack;