Selaa lähdekoodia

Adds FIELD_SpecialtyArrayField field type and handling for it in PersistenceManager, as well as a use-case of it for the surface field in ConvexShape

JeffR 9 kuukautta sitten
vanhempi
commit
61d9e82ce5

+ 4 - 3
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);
       }
    }
 

+ 15 - 6
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"))
+      SimObject* childObj = (*itr);
+
+      if (!prMger.isDirty(childObj))
       {
-         ConsoleValue vars[3];
-         vars[2].setString(mLevelAssetId);
-         Con::execute((*itr), 3, vars);
-      }
+         if ((*itr)->isMethod("onSaving"))
+         {
+            Con::executef((*itr), "onSaving", mLevelAssetId);
+         }
 
-      prMger.setDirty((*itr), levelPath);
+         if (childObj->getGroup() == this)
+         {
+            prMger.setDirty((*itr), levelPath);
+         }
+      }
    }
 
    prMger.saveDirty();

+ 51 - 2
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() )

+ 57 - 54
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<MatrixF>& 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<MatrixF>& 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;
 
    /// @}
 

+ 1 - 0
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

+ 303 - 109
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;
+         for(U32 j = 0; j < fieldArraySize; j++)
+         {
+            const char* value = object->getSpecialFieldOut(f->pFieldname, j);
 
-         // Let's see if this field is already in the file
-         S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j);
+            // Make sure we got a value
+            if (!value)
+               continue;
 
-         if (propertyIndex > -1)
-         {
-            ParsedProperty& prop = parsedObject->properties[propertyIndex];
+            // Let's see if this field is already in the file
+            S32 propertyIndex = getSpecialPropertyAtOffset(parsedObject, 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))
+            if (propertyIndex > -1)
             {
-               removeField( parsedObject->properties[ propertyIndex ] );
-               dFree( value );
-               continue;
-            }
+               ParsedProperty& prop = parsedObject->properties[propertyIndex];
 
-            // 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 this field is on the remove list then remove it and continue
+               if (findRemoveField(object, f->pFieldname, j))
+               {
+                  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
             {
-               if( value[ 0 ] == '\0' &&
-                   dStricmp( getFieldValue( defaultObject, f->pFieldname, j ), value ) == 0 &&
-                   ( !object->getCopySource() || dStricmp( getFieldValue( object->getCopySource(), f->pFieldname, j ), value ) == 0 ) )
+               // 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)
                {
-                  removeField( prop );
+                  // 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 )
-                     mustUpdate = true;
-                     
-                  if( copySourceValue )
-                     dFree( copySourceValue );
+                  dFree(value);
+                  continue;
                }
-            }
 
-            // 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;
+               bool mustUpdate = false;
 
-            // 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 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)
                {
-                  char fnBuf[1024];
-                  Con::collapseScriptFilename(fnBuf, 1024, value);
+                  // 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;
 
-                  newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j));
+                     if (copySourceValue)
+                        dFree(copySourceValue);
+                  }
+                  else
+                     mustUpdate = true;
                }
-               else if (f->type == TypeCommand)
+               else
                {
-                  char cmdBuf[1024];
-                  expandEscape(cmdBuf, value);
+                  // 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.
 
-                  newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j));
+                  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
-                  newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j));              
+
+               // 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 );
       }
    }
 

+ 7 - 1
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
+#endif

+ 3 - 0
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

+ 1 - 1
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