From 1721bd007e08fee17528fe47ccc43515e5f2b816 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 3 May 2026 15:50:44 +0100 Subject: [PATCH 1/3] Adds a new FieldDescriptor struct Use the field descriptor struct to add fields instead of the overloads Adds a visibility function to control whether a field is visible in the inspector EG: ADD_FIELD("soundFile", TypeAssetLooseFilePath, Offset(mSoundFile, SoundAsset)) .doc("Path to the sound file.") .elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) .onSet(&_setSoundFile); the grammar can change to make these easier to work with, review changes carefully --- Engine/source/console/consoleObject.cpp | 86 ++++++++ Engine/source/console/consoleObject.h | 202 +++++++++++++++++-- Engine/source/gui/editor/guiInspector.cpp | 53 +++++ Engine/source/gui/editor/guiInspector.h | 2 + Engine/source/gui/editor/inspector/field.cpp | 2 + Engine/source/gui/editor/inspector/field.h | 1 + Engine/source/gui/editor/inspector/group.cpp | 6 +- Engine/source/gui/editor/inspector/group.h | 7 + 8 files changed, 343 insertions(+), 16 deletions(-) diff --git a/Engine/source/console/consoleObject.cpp b/Engine/source/console/consoleObject.cpp index dfa17bca2..df71c2b21 100644 --- a/Engine/source/console/consoleObject.cpp +++ b/Engine/source/console/consoleObject.cpp @@ -356,6 +356,72 @@ static char* suppressSpaces(const char* in_pname) return replacebuf; } +void ConsoleObject::registerField(const char* name, U32 type, dsize_t offset, const FieldDescriptor& desc) +{ + AbstractClassRep::Field f; + // Remove spaces. + + 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); + } + 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.docs) + f.pFieldDocs = desc.docs; + + 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.elementCount = desc.elementCount; + 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) { // Remove spaces. @@ -1130,3 +1196,23 @@ DefineEngineFunction(linkNamespaces, bool, ( String childNSName, String parentNS 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); +} diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 2d5586613..bdfde53c3 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -456,6 +456,11 @@ public: /// This is a function pointer typedef to support optional writing for fields. typedef bool(*WriteDataNotify)(void* obj, StringTableEntry pFieldName); + /// + /// This is a function pointer typedef to support optional visibility of fields. + /// + typedef bool(*VisibilityNotify)(void* obj, const char* array); + /// These are special field type values used to mark /// groups and arrays in the field list. /// @see Field::type @@ -517,27 +522,29 @@ public: setDataFn( NULL ), getDataFn( NULL ), writeDataFn(NULL), + visibilityFn(NULL), networkMask(0) { doNotSubstitute = keepClearSubsOnly = false; } - StringTableEntry pFieldname; ///< Name of the field. - const char* pGroupname; ///< Optionally filled field containing the group name. - /// - /// This is filled when type is StartField or EndField + StringTableEntry pFieldname; ///< Name of the field. + const char* pGroupname; ///< Optionally filled field containing the group name. + /// + /// This is filled when type is StartField or EndField - const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc. - bool groupExpand; ///< Flag to track expanded/not state of this group in the editor. - U32 type; ///< A data type ID or one of the special custom fields. @see ACRFieldTypes - U32 offset; ///< Memory offset from beginning of class for this field. - S32 elementCount; ///< Number of elements, if this is an array. - const EnumTable * table; ///< If this is an enum, this points to the table defining it. - BitSet32 flag; ///< Stores various flags - TypeValidator *validator; ///< Validator, if any. - SetDataNotify setDataFn; ///< Set data notify Fn - GetDataNotify getDataFn; ///< Get data notify Fn - WriteDataNotify writeDataFn; ///< Function to determine whether data should be written or not. + const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc. + bool groupExpand; ///< Flag to track expanded/not state of this group in the editor. + U32 type; ///< A data type ID or one of the special custom fields. @see ACRFieldTypes + U32 offset; ///< Memory offset from beginning of class for this field. + S32 elementCount; ///< Number of elements, if this is an array. + const EnumTable * table; ///< If this is an enum, this points to the table defining it. + BitSet32 flag; ///< Stores various flags + TypeValidator *validator; ///< Validator, if any. + SetDataNotify setDataFn; ///< Set data notify Fn + GetDataNotify getDataFn; ///< Get data notify Fn + 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 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 +bool defaultProtectedSetFn(void* object, const char* index, const char* data); const char *defaultProtectedGetFn( void *obj, const char *data ); 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. //============================================================================= @@ -871,6 +1042,7 @@ public: /// @name Fields /// @{ + static void registerField(const char* name, U32 type, dsize_t offset, const FieldDescriptor& desc); /// Mark the beginning of a group of fields. /// diff --git a/Engine/source/gui/editor/guiInspector.cpp b/Engine/source/gui/editor/guiInspector.cpp index 3089e38c7..5596523d4 100644 --- a/Engine/source/gui/editor/guiInspector.cpp +++ b/Engine/source/gui/editor/guiInspector.cpp @@ -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() { const U32 numObjects = getNumInspectObjects(); diff --git a/Engine/source/gui/editor/guiInspector.h b/Engine/source/gui/editor/guiInspector.h index 6e8a9363b..25d328bf0 100644 --- a/Engine/source/gui/editor/guiInspector.h +++ b/Engine/source/gui/editor/guiInspector.h @@ -90,6 +90,8 @@ public: return NULL; } + void updateVisibility(); + S32 getComponentGroupTargetId() { return mComponentGroupTargetId; } void setComponentGroupTargetId(S32 compId) { mComponentGroupTargetId = compId; } diff --git a/Engine/source/gui/editor/inspector/field.cpp b/Engine/source/gui/editor/inspector/field.cpp index 4871a0049..b237c7278 100644 --- a/Engine/source/gui/editor/inspector/field.cpp +++ b/Engine/source/gui/editor/inspector/field.cpp @@ -835,6 +835,8 @@ void GuiInspectorField::updateValue() } else setValue( getData() ); + + mInspector->updateVisibility(); } //----------------------------------------------------------------------------- diff --git a/Engine/source/gui/editor/inspector/field.h b/Engine/source/gui/editor/inspector/field.h index 30dd7442b..2f2a11968 100644 --- a/Engine/source/gui/editor/inspector/field.h +++ b/Engine/source/gui/editor/inspector/field.h @@ -164,6 +164,7 @@ class GuiInspectorField : public GuiControl /// StringTableEntry getArrayIndex() const { return mFieldArrayIndex; } + AbstractClassRep::Field* getField() const { return mField; } /// Called from within setData to allow child classes /// to perform their own verification. diff --git a/Engine/source/gui/editor/inspector/group.cpp b/Engine/source/gui/editor/inspector/group.cpp index 5d4a292e9..18a3d3e57 100644 --- a/Engine/source/gui/editor/inspector/group.cpp +++ b/Engine/source/gui/editor/inspector/group.cpp @@ -79,6 +79,7 @@ GuiInspectorGroup::GuiInspectorGroup( const String& groupName, mChildren.clear(); mMargin.set(0,0,4,0); + VECTOR_SET_ASSOCIATION(mArrayElements); } //----------------------------------------------------------------------------- @@ -229,6 +230,7 @@ void GuiInspectorGroup::clearFields() // that we keep for our own convenience. mArrayCtrls.clear(); mChildren.clear(); + mArrayElements.clear(); } //----------------------------------------------------------------------------- @@ -336,7 +338,7 @@ bool GuiInspectorGroup::inspectGroup() GuiControlProfile* elementRolloutProfile = dynamic_cast(Sim::findObject("GuiInspectorRolloutProfile0")); char buf[256]; - dSprintf(buf, 256, " [%i]", i); + dSprintf(buf, 256, " [%i/%i]", i, field->elementCount); elementRollout->setControlProfile(elementRolloutProfile); elementRollout->setCaption(buf); @@ -349,6 +351,8 @@ bool GuiInspectorGroup::inspectGroup() elementRollout->instantCollapse(); arrayStack->addObject(elementRollout); + + mArrayElements.push_back({ elementRollout, (S32)i, field }); } pArrayRollout = arrayRollout; diff --git a/Engine/source/gui/editor/inspector/group.h b/Engine/source/gui/editor/inspector/group.h index 11ca3eee5..498e4243f 100644 --- a/Engine/source/gui/editor/inspector/group.h +++ b/Engine/source/gui/editor/inspector/group.h @@ -44,6 +44,13 @@ private: typedef GuiRolloutCtrl Parent; public: // Members + struct ArrayElementEntry + { + GuiRolloutCtrl* rollout; + S32 elementIndex; + const AbstractClassRep::Field* arrayField; // the StartArrayFieldType field + }; + Vector mArrayElements; SimObjectPtr mParent; Vector mChildren; GuiStackControl* mStack; From 33220cc9880dc39f71f4755fd3d145b6f9799b81 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 3 May 2026 17:00:19 +0100 Subject: [PATCH 2/3] Convert sound asset Convert sound asset to use the new field constructors Also add a simple example of the visibility control. --- Engine/source/T3D/assets/SoundAsset.cpp | 252 ++++++++++++++---------- Engine/source/T3D/assets/SoundAsset.h | 1 + 2 files changed, 145 insertions(+), 108 deletions(-) diff --git a/Engine/source/T3D/assets/SoundAsset.cpp b/Engine/source/T3D/assets/SoundAsset.cpp index e8b6d3cdb..c6cc23b71 100644 --- a/Engine/source/T3D/assets/SoundAsset.cpp +++ b/Engine/source/T3D/assets/SoundAsset.cpp @@ -200,119 +200,143 @@ void SoundAsset::initPersistFields() docsURL; // Call parent. Parent::initPersistFields(); - addGroup("SoundSlots"); - addArray("slots", SFXPlayList::SFXPlaylistSettings::NUM_SLOTS); - addProtectedField("soundFile", TypeAssetLooseFilePath, Offset(mSoundFile, SoundAsset), - &_setSoundFile, &defaultProtectedGetFn, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, "Path to the sound file."); + GROUP("SoundSlots") + { + ARRAY_BEGIN("slots", SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .showWhen(&slotVisible); - addField("replay", TYPEID< SFXPlayList::EReplayMode >(), Offset(mPlaylist.mSlots.mReplayMode, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "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 " - "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" - "After the delayIn time has expired (if any), this slot determines what the controller " - "will do before actually playing the slot."); - addField("transitionOut", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionOut, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Behavior when moving out of this slot.\n" - "After the #detailTimeOut has expired (if any), this slot determines what the controller " - "will do before moving on to the next slot."); - addFieldV("delayTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeIn.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Seconds to wait after moving into slot before #transitionIn."); - addField("delayTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeIn.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #delayTimeIn.\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("delayTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeOut.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Seconds to wait before moving out of slot after #transitionOut."); - addField("delayTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeOut.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #delayTimeOut.\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("fadeTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeIn.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Seconds to fade sound in (-1 to use the track's own fadeInTime.)\n" - "@see SFXDescription::fadeTimeIn"); - addField("fadeTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeIn.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #fadeInTime.\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("fadeTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeOut.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Seconds to fade sound out (-1 to use the track's own fadeOutTime.)\n" - "@see SFXDescription::fadeTimeOut"); - addField("fadeTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeOut.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #fadeOutTime\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("referenceDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMinDistance.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "@c referenceDistance to set for 3D sounds in this slot (<1 to use @c referenceDistance of track's own description).\n" - "@see SFXDescription::referenceDistance"); - addField("referenceDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMinDistance.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #referenceDistance.\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("maxSlotDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMaxDistance.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "@c maxDistance to apply to 3D sounds in this slot (<1 to use @c maxDistance of track's own description).\n" - "@see SFXDescription::maxDistance"); - addField("maxSlotDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMaxDistance.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #maxDistance.\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("volumeScale", TypeRangedF32, Offset(mPlaylist.mSlots.mVolumeScale.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "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 " - "cause the track to play at half-volume."); - addField("volumeScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mVolumeScale.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #volumeScale.\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("pitchScale", TypeRangedF32, Offset(mPlaylist.mSlots.mPitchScale.mValue, SoundAsset), &CommonValidators::PositiveFloat, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "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 " - "cause the track to play at half its assigned speed."); - addField("pitchScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mPitchScale.mVariance, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Bounds on randomization of #pitchScale.\n\n" - "@ref SFXPlayList_randomization\n"); - addFieldV("repeatCount", TypeRangedS32, Offset(mPlaylist.mSlots.mRepeatCount, SoundAsset), &CommonValidators::PositiveInt, SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Number of times to loop this slot."); - addField("state", TypeSFXStateName, Offset(mPlaylist.mSlots.mState, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "State that must be active for this slot to play.\n\n" - "@ref SFXPlayList_states"); - addField("stateMode", TYPEID< SFXPlayList::EStateMode >(), Offset(mPlaylist.mSlots.mStateMode, SoundAsset), SFXPlayList::SFXPlaylistSettings::NUM_SLOTS, - "Behavior when assigned state is deactivated while slot is playing.\n\n" - "@ref SFXPlayList_states"); - endArray("slots"); - endGroup("SoundSlots"); + ADD_FIELD("soundFile", TypeAssetLooseFilePath, Offset(mSoundFile, SoundAsset)) + .doc("Path to the sound file.") + .elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .onSet(&_setSoundFile); - addGroup("General Profile"); - 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."); - addField("isLooping", TypeBool, Offset(mProfileDesc.mIsLooping, SoundAsset), "Does this sound loop."); - // if streaming, a default packet size should be chosen for all sounds. - addField("isStreaming", TypeBool, Offset(mProfileDesc.mIsStreaming, SoundAsset), "Use streaming."); - //....why? - addField("useHardware", TypeBool, Offset(mProfileDesc.mUseHardware, SoundAsset), "Use hardware mixing for this sound."); - addField("sourceGroup", TypeSFXSourceName, Offset(mProfileDesc.mSourceGroup, SoundAsset), "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."); - endGroup("General Profile"); + 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 " + "how SFXController will handle these sources.") + .elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS); - addGroup("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."); - addField("fadeInEase", TypeEaseF, Offset(mProfileDesc.mFadeInEase, SoundAsset), "Easing curve for fade-in transition."); - addField("fadeOutEase", TypeEaseF, Offset(mProfileDesc.mFadeOutEase, SoundAsset), "Easing curve for fade-out transition."); - 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"); + ADD_FIELD("transitionIn", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionIn, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Behavior when moving into this slot.\n" + "After the delayIn time has expired (if any), this slot determines what the controller " + "will do before actually playing the slot."); + ADD_FIELD("transitionOut", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionOut, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Behavior when moving out of this slot.\n" + "After the #detailTimeOut has expired (if any), this slot determines what the controller " + "will do before moving on to the next slot."); + ADD_FIELD("delayTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Seconds to wait after moving into slot before #transitionIn."); + ADD_FIELD("delayTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeIn.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #delayTimeIn.\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("delayTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Seconds to wait before moving out of slot after #transitionOut."); + ADD_FIELD("delayTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeOut.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #delayTimeOut.\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("fadeTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Seconds to fade sound in (-1 to use the track's own fadeInTime.)\n" + "@see SFXDescription::fadeTimeIn"); + ADD_FIELD("fadeTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeIn.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #fadeInTime.\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("fadeTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Seconds to fade sound out (-1 to use the track's own fadeOutTime.)\n" + "@see SFXDescription::fadeTimeOut"); + ADD_FIELD("fadeTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeOut.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #fadeOutTime\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("referenceDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMinDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .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"); + ADD_FIELD("referenceDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMinDistance.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #referenceDistance.\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("maxSlotDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMaxDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .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"); + ADD_FIELD("maxSlotDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMaxDistance.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #maxDistance.\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("volumeScale", TypeRangedF32, Offset(mPlaylist.mSlots.mVolumeScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .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 " + "cause the track to play at half-volume."); + ADD_FIELD("volumeScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mVolumeScale.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #volumeScale.\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("pitchScale", TypeRangedF32, Offset(mPlaylist.mSlots.mPitchScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .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 " + "cause the track to play at half its assigned speed."); + ADD_FIELD("pitchScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mPitchScale.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Bounds on randomization of #pitchScale.\n\n" + "@ref SFXPlayList_randomization\n"); + ADD_FIELD("repeatCount", TypeRangedS32, Offset(mPlaylist.mSlots.mRepeatCount, SoundAsset)).validate(&CommonValidators::PositiveInt).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Number of times to loop this slot."); + ADD_FIELD("state", TypeSFXStateName, Offset(mPlaylist.mSlots.mState, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("State that must be active for this slot to play.\n\n" + "@ref SFXPlayList_states"); + ADD_FIELD("stateMode", TYPEID< SFXPlayList::EStateMode >(), Offset(mPlaylist.mSlots.mStateMode, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + .doc("Behavior when assigned state is deactivated while slot is playing.\n\n" + "@ref SFXPlayList_states"); - addGroup("3D"); - addFieldV("minDistance", TypeRangedF32, Offset(mProfileDesc.mMinDistance, SoundAsset), &CommonValidators::PositiveFloat, "Minimum distance for sound."); - addFieldV("maxDistance", TypeRangedF32, Offset(mProfileDesc.mMaxDistance, SoundAsset), &CommonValidators::PositiveFloat, "Max distance for sound."); - 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"); + ARRAY_END("slots"); + } - addGroup("Playlist settings"); - addField("random", TYPEID< SFXPlayList::ERandomMode >(), Offset(mPlaylist.mRandomMode, SoundAsset), "Slot playback order randomization pattern."); - addField("loopMode", TYPEID< SFXPlayList::ELoopMode >(), Offset(mPlaylist.mLoopMode, SoundAsset), "Behavior when description has looping enabled."); - 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("General Profile") + { + 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. + ADD_FIELD("isStreaming", TypeBool, Offset(mProfileDesc.mIsStreaming, SoundAsset)).doc("Use streaming."); + //....why? + ADD_FIELD("useHardware", TypeBool, Offset(mProfileDesc.mUseHardware, SoundAsset)).doc("Use hardware mixing for this sound."); + ADD_FIELD("sourceGroup", TypeSFXSourceName, Offset(mProfileDesc.mSourceGroup, SoundAsset)).doc("Group that sources playing with this description should be put into."); + ADD_FIELD("preload", TypeBool, Offset(mPreload, SoundAsset)).doc("Whether to preload sound data when the profile is added to system."); + } + + GROUP("Fading") + { + ADD_FIELD("fadeInTime", TypeRangedF32, Offset(mProfileDesc.mFadeInTime, SoundAsset)) + .validate(&CommonValidators::PositiveFloat) + .doc("Number of seconds to gradually fade in volume from zero when playback starts."); + + ADD_FIELD("fadeOutTime", TypeRangedF32, Offset(mProfileDesc.mFadeOutTime, SoundAsset)) + .validate(&CommonValidators::PositiveFloat) + .doc("Number of seconds to gradually fade out volume down to zero when playback is stopped or paused."); + + ADD_FIELD("fadeInEase", TypeEaseF, Offset(mProfileDesc.mFadeInEase, SoundAsset)).doc("Easing curve for fade-in transition."); + ADD_FIELD("fadeOutEase", TypeEaseF, Offset(mProfileDesc.mFadeOutEase, SoundAsset)).doc("Easing curve for fade-out transition."); + 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."); + } + + 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 +486,18 @@ bool SoundAsset::_setSoundFile(void* object, const char* index, const char* data 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(object); + if (!sa) + return false; + + return sa->mSoundFile[idx - 1] != NULL && sa->mSoundFile[idx - 1][0] != '\0'; +} + StringTableEntry SoundAsset::getAssetIdByFileName(StringTableEntry fileName) { if (fileName == StringTable->EmptyString()) diff --git a/Engine/source/T3D/assets/SoundAsset.h b/Engine/source/T3D/assets/SoundAsset.h index d005ec0c1..7bb15ae1e 100644 --- a/Engine/source/T3D/assets/SoundAsset.h +++ b/Engine/source/T3D/assets/SoundAsset.h @@ -156,6 +156,7 @@ public: DECLARE_CONOBJECT(SoundAsset); static bool _setSoundFile(void* object, const char* index, const char* data); + static bool slotVisible(void* object, const char* index); U32 load() override; inline StringTableEntry getSoundPath(const U32 slotId = 0) const { return mSoundPath[slotId]; }; SFXProfile* getSfxProfile(const U32 slotId = 0) { return mSFXProfile[slotId]; } From 6829ac2dd08d11576afc7a749d508e95b12ae4ba Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 3 May 2026 18:13:17 +0100 Subject: [PATCH 3/3] remove requirement of element count in array If we define an array field have every other field inside that array declared inherit its element count. Can be override with a .elements(N) if N is > 1. --- Engine/source/T3D/assets/SoundAsset.cpp | 46 ++++++++++++------------- Engine/source/console/consoleObject.cpp | 17 +++++++-- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/Engine/source/T3D/assets/SoundAsset.cpp b/Engine/source/T3D/assets/SoundAsset.cpp index c6cc23b71..51ce5375b 100644 --- a/Engine/source/T3D/assets/SoundAsset.cpp +++ b/Engine/source/T3D/assets/SoundAsset.cpp @@ -207,77 +207,75 @@ void SoundAsset::initPersistFields() ADD_FIELD("soundFile", TypeAssetLooseFilePath, Offset(mSoundFile, SoundAsset)) .doc("Path to the sound file.") - .elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) .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 " - "how SFXController will handle these sources.") - .elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS); + "how SFXController will handle these sources."); - ADD_FIELD("transitionIn", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionIn, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + 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 " "will do before actually playing the slot."); - ADD_FIELD("transitionOut", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionOut, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("transitionOut", TYPEID< SFXPlayList::ETransitionMode >(), Offset(mPlaylist.mSlots.mTransitionOut, SoundAsset)) .doc("Behavior when moving out of this slot.\n" "After the #detailTimeOut has expired (if any), this slot determines what the controller " "will do before moving on to the next slot."); - ADD_FIELD("delayTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("delayTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .doc("Seconds to wait after moving into slot before #transitionIn."); - ADD_FIELD("delayTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeIn.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("delayTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeIn.mVariance, SoundAsset)) .doc("Bounds on randomization of #delayTimeIn.\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("delayTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("delayTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mDelayTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .doc("Seconds to wait before moving out of slot after #transitionOut."); - ADD_FIELD("delayTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeOut.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("delayTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mDelayTimeOut.mVariance, SoundAsset)) .doc("Bounds on randomization of #delayTimeOut.\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("fadeTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("fadeTimeIn", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeIn.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .doc("Seconds to fade sound in (-1 to use the track's own fadeInTime.)\n" "@see SFXDescription::fadeTimeIn"); - ADD_FIELD("fadeTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeIn.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("fadeTimeInVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeIn.mVariance, SoundAsset)) .doc("Bounds on randomization of #fadeInTime.\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("fadeTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("fadeTimeOut", TypeRangedF32, Offset(mPlaylist.mSlots.mFadeTimeOut.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .doc("Seconds to fade sound out (-1 to use the track's own fadeOutTime.)\n" "@see SFXDescription::fadeTimeOut"); - ADD_FIELD("fadeTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeOut.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("fadeTimeOutVariance", TypePoint2F, Offset(mPlaylist.mSlots.mFadeTimeOut.mVariance, SoundAsset)) .doc("Bounds on randomization of #fadeOutTime\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("referenceDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMinDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("referenceDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMinDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .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"); - ADD_FIELD("referenceDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMinDistance.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("referenceDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMinDistance.mVariance, SoundAsset)) .doc("Bounds on randomization of #referenceDistance.\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("maxSlotDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMaxDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("maxSlotDistance", TypeRangedF32, Offset(mPlaylist.mSlots.mMaxDistance.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .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"); - ADD_FIELD("maxSlotDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMaxDistance.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("maxSlotDistanceVariance", TypePoint2F, Offset(mPlaylist.mSlots.mMaxDistance.mVariance, SoundAsset)) .doc("Bounds on randomization of #maxDistance.\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("volumeScale", TypeRangedF32, Offset(mPlaylist.mSlots.mVolumeScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("volumeScale", TypeRangedF32, Offset(mPlaylist.mSlots.mVolumeScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .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 " "cause the track to play at half-volume."); - ADD_FIELD("volumeScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mVolumeScale.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("volumeScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mVolumeScale.mVariance, SoundAsset)) .doc("Bounds on randomization of #volumeScale.\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("pitchScale", TypeRangedF32, Offset(mPlaylist.mSlots.mPitchScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("pitchScale", TypeRangedF32, Offset(mPlaylist.mSlots.mPitchScale.mValue, SoundAsset)).validate(&CommonValidators::PositiveFloat) .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 " "cause the track to play at half its assigned speed."); - ADD_FIELD("pitchScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mPitchScale.mVariance, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("pitchScaleVariance", TypePoint2F, Offset(mPlaylist.mSlots.mPitchScale.mVariance, SoundAsset)) .doc("Bounds on randomization of #pitchScale.\n\n" "@ref SFXPlayList_randomization\n"); - ADD_FIELD("repeatCount", TypeRangedS32, Offset(mPlaylist.mSlots.mRepeatCount, SoundAsset)).validate(&CommonValidators::PositiveInt).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("repeatCount", TypeRangedS32, Offset(mPlaylist.mSlots.mRepeatCount, SoundAsset)).validate(&CommonValidators::PositiveInt) .doc("Number of times to loop this slot."); - ADD_FIELD("state", TypeSFXStateName, Offset(mPlaylist.mSlots.mState, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("state", TypeSFXStateName, Offset(mPlaylist.mSlots.mState, SoundAsset)) .doc("State that must be active for this slot to play.\n\n" "@ref SFXPlayList_states"); - ADD_FIELD("stateMode", TYPEID< SFXPlayList::EStateMode >(), Offset(mPlaylist.mSlots.mStateMode, SoundAsset)).elements(SFXPlayList::SFXPlaylistSettings::NUM_SLOTS) + ADD_FIELD("stateMode", TYPEID< SFXPlayList::EStateMode >(), Offset(mPlaylist.mSlots.mStateMode, SoundAsset)) .doc("Behavior when assigned state is deactivated while slot is playing.\n\n" "@ref SFXPlayList_states"); diff --git a/Engine/source/console/consoleObject.cpp b/Engine/source/console/consoleObject.cpp index df71c2b21..ad941d453 100644 --- a/Engine/source/console/consoleObject.cpp +++ b/Engine/source/console/consoleObject.cpp @@ -356,12 +356,14 @@ static char* suppressSpaces(const char* in_pname) return replacebuf; } +static U32 sg_currentArrayElementCount = 1; + void ConsoleObject::registerField(const char* name, U32 type, dsize_t offset, const FieldDescriptor& desc) { AbstractClassRep::Field f; - // Remove spaces. - + const bool isCollection = desc.isArrayBegin || desc.isArrayEnd || desc.isGroupBegin || desc.isGroupEnd; + if (isCollection) { char* pFieldNameBuf = suppressSpaces(name); @@ -395,6 +397,7 @@ void ConsoleObject::registerField(const char* name, U32 type, dsize_t offset, co f.pFieldname = StringTable->insert(pFieldNameBuf); f.pGroupname = StringTable->insert(name); + f.elementCount = desc.elementCount; } else { @@ -405,16 +408,24 @@ void ConsoleObject::registerField(const char* name, U32 type, dsize_t offset, co 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.elementCount = desc.elementCount; f.groupExpand = desc.isExpanded; f.networkMask = desc.networkMask; f.flag = desc.flags;