diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index 28ad16ecc..7fb53273a 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/Engine/source/T3D/Scene.cpp @@ -296,6 +296,9 @@ StringTableEntry Scene::getLevelAsset() bool Scene::saveScene(StringTableEntry fileName) { + if (!isServerObject()) + return false; + //So, we ultimately want to not only save out the level, but also collate all the assets utilized //by the static objects in the scene so we can have those before we parse the level file itself //Useful for preloading or stat tracking @@ -310,9 +313,7 @@ bool Scene::saveScene(StringTableEntry fileName) { if((*itr)->isMethod("onSaving")) { - ConsoleValue vars[3]; - vars[2].setString(fileName); - Con::execute((*itr), 3, vars); + Con::executef((*itr), "onSaving", fileName); } } diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 08e2941f0..673e1e33e 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -411,6 +411,9 @@ void SubScene::unload() bool SubScene::save() { + if (!isServerObject()) + return false; + //if there's nothing TO save, don't bother if (size() == 0 || !isLoaded()) return false; @@ -432,14 +435,20 @@ bool SubScene::save() for (SimGroupIterator itr(this); *itr; ++itr) { - if ((*itr)->isMethod("onSaving")) - { - ConsoleValue vars[3]; - vars[2].setString(mLevelAssetId); - Con::execute((*itr), 3, vars); - } + SimObject* childObj = (*itr); - prMger.setDirty((*itr), levelPath); + if (!prMger.isDirty(childObj)) + { + if ((*itr)->isMethod("onSaving")) + { + Con::executef((*itr), "onSaving", mLevelAssetId); + } + + if (childObj->getGroup() == this) + { + prMger.setDirty((*itr), levelPath); + } + } } prMger.saveDirty(); diff --git a/Engine/source/T3D/convexShape.cpp b/Engine/source/T3D/convexShape.cpp index c8341e8a8..0021ff37a 100644 --- a/Engine/source/T3D/convexShape.cpp +++ b/Engine/source/T3D/convexShape.cpp @@ -317,10 +317,10 @@ void ConvexShape::initPersistFields() addGroup( "Internal" ); addProtectedField( "surface", TypeRealString, 0, &protectedSetSurface, &defaultProtectedGetFn, - "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); addProtectedField( "surfaceTexture", TypeRealString, 0, &protectedSetSurfaceTexture, &defaultProtectedGetFn, - "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -498,6 +498,55 @@ bool ConvexShape::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 ConvexShape::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("surface") || fieldName == StringTable->insert("surfaceTexture")) + { + return mSurfaces.size(); + } + + return 0; +} + +const char* ConvexShape::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (index >= smMaxSurfaces) + return NULL; + + if (fieldName == StringTable->insert("surface")) + { + if(index >= mSurfaces.size()) + return NULL; + + const MatrixF& mat = mSurfaces[index]; + + QuatF quat(mat); + Point3F pos(mat.getPosition()); + + char buffer[1024]; + dMemset(buffer, 0, 1024); + + dSprintf(buffer, 1024, "%g %g %g %g %g %g %g %i %g %g %g %g %g %i %i", + quat.x, quat.y, quat.z, quat.w, pos.x, pos.y, pos.z, mSurfaceUVs[index].matID, + mSurfaceUVs[index].offset.x, mSurfaceUVs[index].offset.y, mSurfaceUVs[index].scale.x, + mSurfaceUVs[index].scale.y, mSurfaceUVs[index].zRot, mSurfaceUVs[index].horzFlip, mSurfaceUVs[index].vertFlip); + + return StringTable->insert(buffer); + } + else if (fieldName == StringTable->insert("surfaceTexture")) + { + if (index >= mSurfaceTextures.size()) + return NULL; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + + dSprintf(buffer, 1024, "%s", mSurfaceTextures[index].getMaterial()); + + return StringTable->insert(buffer); + } +} + void ConvexShape::onScaleChanged() { if ( isProperlyAdded() ) diff --git a/Engine/source/T3D/convexShape.h b/Engine/source/T3D/convexShape.h index dfe0eaea4..67918b038 100644 --- a/Engine/source/T3D/convexShape.h +++ b/Engine/source/T3D/convexShape.h @@ -83,7 +83,7 @@ class ConvexShape : public SceneObject typedef SceneObject Parent; friend class GuiConvexEditorCtrl; friend class GuiConvexEditorUndoAction; - friend class ConvexShapeCollisionConvex; + friend class ConvexShapeCollisionConvex; public: @@ -113,10 +113,10 @@ public: U32 p1; U32 p2; - U32 operator []( U32 index ) const + U32 operator [](U32 index) const { - AssertFatal( index >= 0 && index <= 2, "index out of range" ); - return *( (&p0) + index ); + AssertFatal(index >= 0 && index <= 2, "index out of range"); + return *((&p0) + index); } }; @@ -126,23 +126,23 @@ public: Vector< U32 > points; Vector< U32 > winding; Vector< Point2F > texcoords; - Vector< Triangle > triangles; + Vector< Triangle > triangles; Point3F tangent; Point3F normal; Point3F centroid; F32 area; S32 id; - }; + }; struct surfaceMaterial { // The name of the Material we will use for rendering DECLARE_MATERIALASSET(surfaceMaterial, Material); - + DECLARE_ASSET_SETGET(surfaceMaterial, Material); // The actual Material instance - BaseMatInstance* materialInst; + BaseMatInstance* materialInst; surfaceMaterial() { @@ -174,26 +174,26 @@ public: U32 mPrimCount; }; - struct Geometry - { - void generate(const Vector< PlaneF > &planes, const Vector< Point3F > &tangents, const Vector< surfaceMaterial > surfaceTextures, const Vector< Point2F > texOffset, const Vector< Point2F > texScale, const Vector< bool > horzFlip, const Vector< bool > vertFlip); + struct Geometry + { + void generate(const Vector< PlaneF >& planes, const Vector< Point3F >& tangents, const Vector< surfaceMaterial > surfaceTextures, const Vector< Point2F > texOffset, const Vector< Point2F > texScale, const Vector< bool > horzFlip, const Vector< bool > vertFlip); - Vector< Point3F > points; - Vector< Face > faces; - }; + Vector< Point3F > points; + Vector< Face > faces; + }; - static bool smRenderEdges; + static bool smRenderEdges; // To prevent bitpack overflows. // This is only indirectly enforced by trucation when serializing. static const S32 smMaxSurfaces = 100; public: - + ConvexShape(); virtual ~ConvexShape(); - DECLARE_CONOBJECT( ConvexShape ); + DECLARE_CONOBJECT(ConvexShape); DECLARE_CATEGORY("Object \t Simple"); // ConsoleObject @@ -203,73 +203,76 @@ public: void inspectPostApply() override; bool onAdd() override; void onRemove() override; - void writeFields(Stream &stream, U32 tabStop) override; - bool writeField( StringTableEntry fieldname, const char *value ) override; + void writeFields(Stream& stream, U32 tabStop) override; + bool writeField(StringTableEntry fieldname, const char* value) override; + + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; // NetObject - U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) override; - void unpackUpdate( NetConnection *conn, BitStream *stream ) override; + U32 packUpdate(NetConnection* conn, U32 mask, BitStream* stream) override; + void unpackUpdate(NetConnection* conn, BitStream* stream) override; // SceneObject void onScaleChanged() override; - void setTransform( const MatrixF &mat ) override; - void prepRenderImage( SceneRenderState *state ) override; - void buildConvex( const Box3F &box, Convex *convex ) override; - bool buildPolyList( PolyListContext context, AbstractPolyList *polyList, const Box3F &box, const SphereF &sphere ) override; - bool buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F &box, const SphereF &) override; - bool castRay( const Point3F &start, const Point3F &end, RayInfo *info ) override; - bool collideBox( const Point3F &start, const Point3F &end, RayInfo *info ) override; + void setTransform(const MatrixF& mat) override; + void prepRenderImage(SceneRenderState* state) override; + void buildConvex(const Box3F& box, Convex* convex) override; + bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) override; + bool buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F& box, const SphereF&) override; + bool castRay(const Point3F& start, const Point3F& end, RayInfo* info) override; + bool collideBox(const Point3F& start, const Point3F& end, RayInfo* info) override; - void updateBounds( bool recenter ); + void updateBounds(bool recenter); void recenter(); /// Geometry access. /// @{ - - MatrixF getSurfaceWorldMat( S32 faceid, bool scaled = false ) const; - void cullEmptyPlanes( Vector< U32 > *removedPlanes ); - void exportToCollada(); - void resizePlanes( const Point3F &size ); - void getSurfaceLineList( S32 surfId, Vector< Point3F > &lineList ); - Geometry& getGeometry() { return mGeometry; } - Vector& getSurfaces() { return mSurfaces; } - void getSurfaceTriangles( S32 surfId, Vector< Point3F > *outPoints, Vector< Point2F > *outCoords, bool worldSpace ); + + MatrixF getSurfaceWorldMat(S32 faceid, bool scaled = false) const; + void cullEmptyPlanes(Vector< U32 >* removedPlanes); + void exportToCollada(); + void resizePlanes(const Point3F& size); + void getSurfaceLineList(S32 surfId, Vector< Point3F >& lineList); + Geometry& getGeometry() { return mGeometry; } + Vector& getSurfaces() { return mSurfaces; } + void getSurfaceTriangles(S32 surfId, Vector< Point3F >* outPoints, Vector< Point2F >* outCoords, bool worldSpace); /// @} /// Geometry Visualization. /// @{ - void renderFaceEdges( S32 faceid, const ColorI &color = ColorI::WHITE, F32 lineWidth = 1.0f ); + void renderFaceEdges(S32 faceid, const ColorI& color = ColorI::WHITE, F32 lineWidth = 1.0f); /// @} - String getMaterialName() { return mMaterialName; } + String getMaterialName() { return mMaterialName; } protected: void _updateMaterial(); - void _updateGeometry( bool updateCollision = false ); + void _updateGeometry(bool updateCollision = false); void _updateCollision(); - void _export( OptimizedPolyList *plist, const Box3F &box, const SphereF &sphere ); + void _export(OptimizedPolyList* plist, const Box3F& box, const SphereF& sphere); - void _renderDebug( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *mat ); + void _renderDebug(ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* mat); - static S32 QSORT_CALLBACK _comparePlaneDist( const void *a, const void *b ); + static S32 QSORT_CALLBACK _comparePlaneDist(const void* a, const void* b); - static bool protectedSetSurface( void *object, const char *index, const char *data ); + static bool protectedSetSurface(void* object, const char* index, const char* data); + + static bool protectedSetSurfaceTexture(void* object, const char* index, const char* data); + static bool protectedSetSurfaceUV(void* object, const char* index, const char* data); - static bool protectedSetSurfaceTexture( void *object, const char *index, const char *data ); - static bool protectedSetSurfaceUV(void *object, const char *index, const char *data); - protected: - + DECLARE_MATERIALASSET(ConvexShape, Material); DECLARE_ASSET_SETGET(ConvexShape, Material); // The actual Material instance - BaseMatInstance* mMaterialInst; + BaseMatInstance* mMaterialInst; // The GFX vertex and primitive buffers /*GFXVertexBufferHandle< VertexType > mVertexBuffer; @@ -278,7 +281,7 @@ protected: U32 mVertCount; U32 mPrimCount;*/ - Geometry mGeometry; + Geometry mGeometry; Vector< PlaneF > mPlanes; @@ -291,14 +294,14 @@ protected: Vector< surfaceUV > mSurfaceUVs; Vector< surfaceBuffers > mSurfaceBuffers; - Convex *mConvexList; + Convex* mConvexList; - PhysicsBody *mPhysicsRep; + PhysicsBody* mPhysicsRep; /// Geometry visualization /// @{ - F32 mNormalLength; + F32 mNormalLength; /// @} diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 634b60225..8c6bfdec2 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -498,6 +498,7 @@ public: FIELD_ComponentInspectors = BIT(1), ///< Custom fields used by components. They are likely to be non-standard size/configuration, so ///< They are handled specially FIELD_CustomInspectors = BIT(2), ///< Display as a button in inspectors. + FIELD_SpecialtyArrayField = BIT(3) }; struct Field diff --git a/Engine/source/console/persistenceManager.cpp b/Engine/source/console/persistenceManager.cpp index 630dad448..0cb6671c4 100644 --- a/Engine/source/console/persistenceManager.cpp +++ b/Engine/source/console/persistenceManager.cpp @@ -652,6 +652,31 @@ S32 PersistenceManager::getPropertyIndex(ParsedObject* parsedObject, const char* return propertyIndex; } +S32 PersistenceManager::getSpecialPropertyAtOffset(ParsedObject* parsedObject, const char* fieldName, U32 offsetPos) +{ + S32 propertyIndex = -1; + + if (!parsedObject) + return propertyIndex; + + U32 hitCount = -1; + for (U32 i = 0; i < parsedObject->properties.size(); i++) + { + if (dStricmp(fieldName, parsedObject->properties[i].name) == 0) + { + hitCount++; + + if (hitCount == offsetPos) + { + propertyIndex = i; + break; + } + } + } + + return propertyIndex; +} + char* PersistenceManager::getObjectIndent(ParsedObject* object) { char* indent = Con::getReturnBuffer(2048); @@ -1361,166 +1386,335 @@ void PersistenceManager::updateObject(SimObject* object, ParsedObject* parentObj if ( f->type >= AbstractClassRep::ARCFirstCustomField || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors)) continue; - for(U32 j = 0; S32(j) < f->elementCount; j++) + if (f->flag.test(AbstractClassRep::FIELD_SpecialtyArrayField)) { - const char* value = getFieldValue(object, f->pFieldname, j); + U32 fieldArraySize = object->getSpecialFieldSize(f->pFieldname); - // Make sure we got a value - if (!value) - continue; - - // Let's see if this field is already in the file - S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); - - if (propertyIndex > -1) + for(U32 j = 0; j < fieldArraySize; j++) { - ParsedProperty& prop = parsedObject->properties[propertyIndex]; + const char* value = object->getSpecialFieldOut(f->pFieldname, j); - // If this field is on the remove list then remove it and continue - if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) - { - removeField( parsedObject->properties[ propertyIndex ] ); - dFree( value ); + // Make sure we got a value + if (!value) continue; - } - // Run the parsed value through the console system conditioners so - // that it will better match the data we got back from the object. - const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + // Let's see if this field is already in the file + S32 propertyIndex = getSpecialPropertyAtOffset(parsedObject, f->pFieldname, j); - // If our data doesn't match then we get to update it. - // - // As for copy-sources, we just assume here that if a property setting - // is there in the file, the user does not want it inherited from the copy-source - // even in the case the actual values are identical. - - if( dStricmp(value, evalue) != 0 ) + if (propertyIndex > -1) { - if( value[ 0 ] == '\0' && - dStricmp( getFieldValue( defaultObject, f->pFieldname, j ), value ) == 0 && - ( !object->getCopySource() || dStricmp( getFieldValue( object->getCopySource(), f->pFieldname, j ), value ) == 0 ) ) + ParsedProperty& prop = parsedObject->properties[propertyIndex]; + + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, f->pFieldname, j)) { - removeField( prop ); + removeField(parsedObject->properties[propertyIndex]); + dFree(value); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + + // If our data doesn't match then we get to update it. + // + // As for copy-sources, we just assume here that if a property setting + // is there in the file, the user does not want it inherited from the copy-source + // even in the case the actual values are identical. + + if (dStricmp(value, evalue) != 0) + { + if (value[0] == '\0' && + dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value) == 0 && + (!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value) == 0)) + { + removeField(prop); + } + else + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + } + else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + } + else + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + } + } + } + else + { + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, f->pFieldname, j)) + { + dFree(value); + continue; + } + + bool mustUpdate = false; + + // If we didn't find the property in the ParsedObject + // then we need to compare against the default value + // for this property and save it out if it is different + + const char* defaultValue = defaultObject->getSpecialFieldOut(f->pFieldname, j); + if (!defaultValue || dStricmp(value, defaultValue) != 0) + { + // Value differs. Check whether it also differs from the + // value in the copy source if there is one. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (!copySourceValue || dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + else + mustUpdate = true; } else + { + // Value does not differ. If it differs from the copy source's value, + // though, we still want to write it out as otherwise we'll see the + // copy source's value override us. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (copySourceValue && dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + } + + // The default value for most string type fields is + // NULL so we can't just continue here or we'd never ever + // write them out... + // + //if (!defaultValue) + // continue; + + // If the object's value is different from the default + // value then add it to the ParsedObject's newLines + if (mustUpdate) { // TODO: This should be wrapped in a helper method... probably. // Detect and collapse relative path information - if (f->type == TypeFilename || - f->type == TypeStringFilename || - f->type == TypeImageFilename || - f->type == TypePrefabFilename || - f->type == TypeShapeFilename || - f->type == TypeSoundFilename ) + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) { char fnBuf[1024]; Con::collapseScriptFilename(fnBuf, 1024, value); - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); } - else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + else if (f->type == TypeCommand) { char cmdBuf[1024]; expandEscape(cmdBuf, value); - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); } else - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); } + + if (defaultValue) + dFree(defaultValue); } + + //dFree(value); } - else + } + else + { + for (U32 j = 0; S32(j) < f->elementCount; j++) { - // No need to process a removed field that doesn't exist in the file - if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) - { - dFree( value ); + const char* value = getFieldValue(object, f->pFieldname, j); + + // Make sure we got a value + if (!value) continue; - } - - bool mustUpdate = false; - // If we didn't find the property in the ParsedObject - // then we need to compare against the default value - // for this property and save it out if it is different + // Let's see if this field is already in the file + S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); - const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); - if( !defaultValue || dStricmp( value, defaultValue ) != 0 ) + if (propertyIndex > -1) { - // Value differs. Check whether it also differs from the - // value in the copy source if there is one. - - if( object->getCopySource() ) + ParsedProperty& prop = parsedObject->properties[propertyIndex]; + + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) { - const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); - if( !copySourceValue || dStricmp( copySourceValue, value ) != 0 ) - mustUpdate = true; - - if( copySourceValue ) - dFree( copySourceValue ); + removeField(parsedObject->properties[propertyIndex]); + dFree(value); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + + // If our data doesn't match then we get to update it. + // + // As for copy-sources, we just assume here that if a property setting + // is there in the file, the user does not want it inherited from the copy-source + // even in the case the actual values are identical. + + if (dStricmp(value, evalue) != 0) + { + if (value[0] == '\0' && + dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value) == 0 && + (!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value) == 0)) + { + removeField(prop); + } + else + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + } + else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + } + else + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + } } - else - mustUpdate = true; } else { - // Value does not differ. If it differs from the copy source's value, - // though, we still want to write it out as otherwise we'll see the - // copy source's value override us. - - if( object->getCopySource() ) + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) { - const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); - if( copySourceValue && dStricmp( copySourceValue, value ) != 0 ) + dFree(value); + continue; + } + + bool mustUpdate = false; + + // If we didn't find the property in the ParsedObject + // then we need to compare against the default value + // for this property and save it out if it is different + + const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); + if (!defaultValue || dStricmp(value, defaultValue) != 0) + { + // Value differs. Check whether it also differs from the + // value in the copy source if there is one. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (!copySourceValue || dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + else mustUpdate = true; - - if( copySourceValue ) - dFree( copySourceValue ); - } - } - - // The default value for most string type fields is - // NULL so we can't just continue here or we'd never ever - // write them out... - // - //if (!defaultValue) - // continue; - - // If the object's value is different from the default - // value then add it to the ParsedObject's newLines - if ( mustUpdate ) - { - // TODO: This should be wrapped in a helper method... probably. - // Detect and collapse relative path information - if (f->type == TypeFilename || - f->type == TypeStringFilename || - f->type == TypeImageFilename || - f->type == TypePrefabFilename || - f->type == TypeShapeFilename || - f->type == TypeSoundFilename ) - { - char fnBuf[1024]; - Con::collapseScriptFilename(fnBuf, 1024, value); - - newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); - } - else if (f->type == TypeCommand) - { - char cmdBuf[1024]; - expandEscape(cmdBuf, value); - - newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); } else - newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); + { + // Value does not differ. If it differs from the copy source's value, + // though, we still want to write it out as otherwise we'll see the + // copy source's value override us. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (copySourceValue && dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + } + + // The default value for most string type fields is + // NULL so we can't just continue here or we'd never ever + // write them out... + // + //if (!defaultValue) + // continue; + + // If the object's value is different from the default + // value then add it to the ParsedObject's newLines + if (mustUpdate) + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); + } + else if (f->type == TypeCommand) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); + } + else + newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); + } + + if (defaultValue) + dFree(defaultValue); } - if (defaultValue) - dFree( defaultValue ); + dFree(value); } - - dFree( value ); } } diff --git a/Engine/source/console/persistenceManager.h b/Engine/source/console/persistenceManager.h index bba565da2..de8608ebb 100644 --- a/Engine/source/console/persistenceManager.h +++ b/Engine/source/console/persistenceManager.h @@ -221,6 +221,12 @@ protected: // Attempts to look up the property in the ParsedObject S32 getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos = 0); + // Attempts to look up the special array property in the ParsedObject + // This is distinct from getPropertyIndex because while that assumes it's an array'd field + // This figures the property in question is one that is specially tagged for implicit, arbitrarily sized lists + // Like ConvexShape's 'surfaces' or a spline's 'node' properties + S32 getSpecialPropertyAtOffset(ParsedObject* parsedObject, const char* fieldName, U32 offsetPos); + // Gets the amount of indent on the ParsedObject. char* getObjectIndent(ParsedObject* object); @@ -320,4 +326,4 @@ public: DECLARE_CONOBJECT(PersistenceManager); }; -#endif \ No newline at end of file +#endif diff --git a/Engine/source/console/simObject.h b/Engine/source/console/simObject.h index 77f6cd148..4868a4585 100644 --- a/Engine/source/console/simObject.h +++ b/Engine/source/console/simObject.h @@ -530,6 +530,9 @@ class SimObject: public ConsoleObject, public TamlCallbacks void setDataFieldType(const U32 fieldTypeId, StringTableEntry slotName, const char *array); void setDataFieldType(const char *typeName, StringTableEntry slotName, const char *array); + virtual U32 getSpecialFieldSize(StringTableEntry fieldName) { return 0; } + virtual const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) { return NULL; } + /// Get reference to the dictionary containing dynamic fields. /// /// See @ref simobject_console "here" for a detailed discussion of what this diff --git a/Engine/source/scene/sceneObject.cpp b/Engine/source/scene/sceneObject.cpp index cc9f67005..c44746c88 100644 --- a/Engine/source/scene/sceneObject.cpp +++ b/Engine/source/scene/sceneObject.cpp @@ -1724,7 +1724,7 @@ void SceneObject::updateRenderChangesByParent(){ MatrixF offset; offset.mul(renderXform, xform); - MatrixF mat; + MatrixF mat; //add the "offset" caused by the parents change, and add it to it's own // This is needed by objects that update their own render transform thru interpolate tick