Просмотр исходного кода

- Reimplements autosave logic to handle levels, subscenes and terrains in a more consistent, reliable way.
- Adds entry to RMB menu in Asset Browser to restore an asset to a backup copy taken from autosaves
- Adds reparent out-of-bounds objects button to SceneGroup inspector
- Adds ability to have SubScene have a different loading bounds from the actual subscene bounds, allowing load triggering to happen ahead of the bounds of the subscene itself
- Fixes asset importer handling of animFPS field to be the correct type
- Adds onInspect handling to GameBase allowing better handling for any game class type with editor integration
- Add getAssetLooseFileCount and getAssetLooseFile to AssetManager to be able to iterate over all loose files associated to an asset
- Add standard/default preload function def to forestItem
- Fixes handling of text placement on GuiIconButtonCtrl when text is set to the right
- Adds setGlobalCenter utility function
- Adds ability to set guiInputCtrl active state
- Matched util functions for tracking if left and right mouse buttons are down to EditTSCtrl alongside the existing middle mouse
- Add empty element sanity check to appMesh loader
- Add callback for GameBase when game is created
- Add default graphics options config for steamdeck
- Fix typo in assetImportConfig default
- Filters SceneGroup utility buttons in inspector to only show for relevent class types

JeffR 4 месяцев назад
Родитель
Сommit
bb7ee38bf4
33 измененных файлов с 975 добавлено и 234 удалено
  1. 57 11
      Engine/source/T3D/Scene.cpp
  2. 2 2
      Engine/source/T3D/Scene.h
  3. 59 0
      Engine/source/T3D/SceneGroup.cpp
  4. 1 0
      Engine/source/T3D/SceneGroup.h
  5. 62 7
      Engine/source/T3D/SubScene.cpp
  6. 4 1
      Engine/source/T3D/SubScene.h
  7. 2 2
      Engine/source/T3D/assets/assetImporter.cpp
  8. 8 0
      Engine/source/T3D/gameBase/gameBase.cpp
  9. 2 0
      Engine/source/T3D/gameBase/gameBase.h
  10. 63 0
      Engine/source/assets/assetManager.cpp
  11. 3 0
      Engine/source/assets/assetManager.h
  12. 30 0
      Engine/source/assets/assetManager_ScriptBinding.h
  13. 4 3
      Engine/source/forest/forestItem.h
  14. 1 1
      Engine/source/gui/buttons/guiIconButtonCtrl.cpp
  15. 17 0
      Engine/source/gui/core/guiControl.cpp
  16. 19 0
      Engine/source/gui/utility/guiInputCtrl.cpp
  17. 2 0
      Engine/source/gui/utility/guiInputCtrl.h
  18. 10 0
      Engine/source/gui/worldEditor/editTSCtrl.cpp
  19. 2 0
      Engine/source/gui/worldEditor/editTSCtrl.h
  20. 3 0
      Engine/source/ts/assimp/assimpAppMesh.cpp
  21. 3 0
      Engine/source/ts/loader/appMesh.cpp
  22. 1 1
      Templates/BaseGame/game/core/clientServer/scripts/server/levelLoad.tscript
  23. 156 127
      Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript
  24. 1 3
      Templates/BaseGame/game/tools/assetBrowser/assetImportConfigs.xml
  25. 46 0
      Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/level.tscript
  26. 46 0
      Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/subScene.tscript
  27. 43 0
      Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/terrain.tscript
  28. 83 0
      Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript
  29. 58 4
      Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript
  30. 3 1
      Templates/BaseGame/game/tools/editorCore/scripts/menuBar/menuBuilder.ed.tscript
  31. 6 4
      Templates/BaseGame/game/tools/settings.xml
  32. 24 11
      Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript
  33. 154 56
      Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript

+ 57 - 11
Engine/source/T3D/Scene.cpp

@@ -291,7 +291,7 @@ StringTableEntry Scene::getLevelAsset()
       return query->mAssetList[0];
 }
 
-bool Scene::saveScene(StringTableEntry fileName)
+bool Scene::saveScene(StringTableEntry fileName, const bool& saveSubScenes)
 {
    if (!isServerObject())
       return false;
@@ -316,9 +316,12 @@ bool Scene::saveScene(StringTableEntry fileName)
 
    //Inform our subscenes we're saving so they can do any
    //special work required as well
-   for (U32 i = 0; i < mSubScenes.size(); i++)
+   if (saveSubScenes)
    {
-      mSubScenes[i]->save();
+      for (U32 i = 0; i < mSubScenes.size(); i++)
+      {
+         mSubScenes[i]->save();
+      }
    }
 
    bool saveSuccess = save(fileName);
@@ -381,9 +384,30 @@ void Scene::getUtilizedAssetsFromSceneObject(SimObject* object, Vector<StringTab
 }
 
 //
-Vector<SceneObject*> Scene::getObjectsByClass(String className)
+void Scene::getObjectsByClass(SimObject* object, StringTableEntry className, Vector<SimObject*>* objectsList, bool checkSubscenes)
 {
-   return Vector<SceneObject*>();
+   if(object->getClassName() == className)
+   {
+      objectsList->push_back(object);
+   }
+
+   //If it's a subscene and we DON'T want to scan through them, bail out now
+   SubScene* subScene = dynamic_cast<SubScene*>(object);
+   if (subScene && !checkSubscenes)
+      return;
+
+   //If possible, now we iterate over the children
+   SimGroup* group = dynamic_cast<SimGroup*>(object);
+   if (group)
+   {
+      for (U32 c = 0; c < group->size(); c++)
+      {
+         SimObject* childObj = dynamic_cast<SimObject*>(group->getObject(c));
+
+         //Recurse down
+         getObjectsByClass(childObj, className, objectsList);
+      }
+   }
 }
 
 void Scene::loadAtPosition(const Point3F& position)
@@ -460,15 +484,37 @@ DefineEngineMethod(Scene, removeDynamicObject, void, (SceneObject* sceneObj), (n
    object->removeDynamicObject(sceneObj);
 }
 
-DefineEngineMethod(Scene, getObjectsByClass, String, (String className), (""),
+DefineEngineMethod(Scene, getObjectsByClass, String, (String className, bool checkSubScenes), ("", false),
    "Get the root Scene object that is loaded.\n"
-   "@return The id of the Root Scene. Will be 0 if no root scene is loaded")
+   "@param className The name of the class of objects to get a list of.\n"
+   "@param checkSubScenes If true, will also scan through currently loaded subscenes to get matching objects.\n"
+   "@return A space-separated list of object ids that match the searched-for className")
 {
    if (className == String::EmptyString)
       return "";
 
-   //return object->getObjectsByClass(className);
-   return "";
+   Vector<SimObject*>* objectsList = new Vector<SimObject*>();
+
+   object->getObjectsByClass(object, StringTable->insert(className.c_str()), objectsList, checkSubScenes);
+
+   char* retBuffer = Con::getReturnBuffer(1024);
+
+   U32 len = 0;
+   S32 i;
+   //Get the length of our return string
+   for(U32 i=0; i < objectsList->size(); i++)
+      len += dStrlen((*objectsList)[i]->getIdString());
+
+   char* ret = Con::getReturnBuffer(len + 1);
+   ret[0] = 0;
+   for (U32 i = 0; i < objectsList->size(); i++)
+   {
+      dStrcat(ret, (*objectsList)[i]->getIdString(), len + 1);
+      dStrcat(ret, " ", len + 1);
+   }
+      
+
+   return ret;
 }
 
 DefineEngineMethod(Scene, dumpUtilizedAssets, void, (), ,
@@ -492,12 +538,12 @@ DefineEngineMethod(Scene, getLevelAsset, const char*, (), ,
    return object->getLevelAsset();
 }
 
-DefineEngineMethod(Scene, save, bool, (const char* fileName), (""),
+DefineEngineMethod(Scene, save, bool, (const char* fileName, bool saveSubScenes), ("", true),
    "Save out the object to the given file.\n"
    "@param fileName The name of the file to save to."
    "@param True on success, false on failure.")
 {
-   return object->saveScene(StringTable->insert(fileName));
+   return object->saveScene(StringTable->insert(fileName), saveSubScenes);
 }
 
 DefineEngineMethod(Scene, loadAtPosition, void, (Point3F position), (Point3F::Zero),

+ 2 - 2
Engine/source/T3D/Scene.h

@@ -78,7 +78,7 @@ public:
    StringTableEntry getOriginatingFile();
    StringTableEntry getLevelAsset();
 
-   bool saveScene(StringTableEntry fileName);
+   bool saveScene(StringTableEntry fileName, const bool& saveSubScenes = true);
 
    //
    //Networking
@@ -86,7 +86,7 @@ public:
    void unpackUpdate(NetConnection *conn, BitStream *stream) override;
 
    //
-   Vector<SceneObject*> getObjectsByClass(String className);
+   void getObjectsByClass(SimObject* object, StringTableEntry className, Vector<SimObject*>* objectsList, bool checkSubscenes = false);
 
    void getUtilizedAssetsFromSceneObject(SimObject* object, Vector<StringTableEntry>* usedAssetsList);
 

+ 59 - 0
Engine/source/T3D/SceneGroup.cpp

@@ -9,6 +9,7 @@
 #include "physics/physicsShape.h"
 #include "renderInstance/renderPassManager.h"
 #include "scene/sceneRenderState.h"
+#include "Scene.h"
 
 IMPLEMENT_CO_NETOBJECT_V1(SceneGroup);
 
@@ -156,6 +157,37 @@ void SceneGroup::onInspect(GuiInspector* inspector)
    regenButton->setConsoleCommand(rgBuffer);
 
    regenFieldGui->addObject(regenButton);
+
+   //
+    //Regen bounds button
+   GuiInspectorField* reparentFieldGui = sceneGroupGrp->createInspectorField();
+   reparentFieldGui->init(inspector, sceneGroupGrp);
+
+   reparentFieldGui->setSpecialEditField(true);
+   reparentFieldGui->setTargetObject(this);
+
+   fldnm = StringTable->insert("ReparentOOBObjs");
+
+   reparentFieldGui->setSpecialEditVariableName(fldnm);
+
+   reparentFieldGui->setInspectorField(NULL, fldnm);
+   reparentFieldGui->setDocs("");
+
+   stack->addObject(reparentFieldGui);
+
+   GuiButtonCtrl* reparentButton = new GuiButtonCtrl();
+   reparentButton->registerObject();
+   reparentButton->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile");
+   reparentButton->setText("Reparent Out-of-bounds Objs");
+   reparentButton->resize(Point2I::Zero, regenFieldGui->getExtent());
+   reparentButton->setHorizSizing(GuiControl::horizResizeWidth);
+   reparentButton->setVertSizing(GuiControl::vertResizeHeight);
+
+   char rprntBuffer[512];
+   dSprintf(rprntBuffer, 512, "%d.reparentOOBObjects();", this->getId());
+   reparentButton->setConsoleCommand(rprntBuffer);
+
+   reparentFieldGui->addObject(reparentButton);
 #endif
 }
 
@@ -279,6 +311,27 @@ void SceneGroup::recalculateBoundingBox()
    setMaskBits(TransformMask);
 }
 
+void SceneGroup::reparentOOBObjects()
+{
+   if (empty())
+      return;
+
+   // Extend the bounding box to include each child's bounding box
+   for (SimSetIterator itr(this); *itr; ++itr)
+   {
+      SceneObject* child = dynamic_cast<SceneObject*>(*itr);
+      if (child)
+      {
+         const Box3F& childBox = child->getWorldBox();
+
+         if(!mWorldBox.isOverlapped(childBox))
+         {
+            Scene::getRootScene()->addObject(child);
+         }
+      }
+   }
+}
+
 U32 SceneGroup::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
 {
    U32 retMask = Parent::packUpdate(conn, mask, stream);
@@ -363,3 +416,9 @@ DefineEngineMethod(SceneGroup, recalculateBounds, void, (), ,
 {
    object->recalculateBoundingBox();
 }
+
+DefineEngineMethod(SceneGroup, reparentOOBObjects, void, (), ,
+   "Finds objects that are children of the SceneGroup and, if not overlapping or in the bounds, reparents them to the root scene.\n")
+{
+   object->reparentOOBObjects();
+}

+ 1 - 0
Engine/source/T3D/SceneGroup.h

@@ -46,6 +46,7 @@ public:
    void addObject(SimObject* object) override;
    void removeObject(SimObject* object) override;
    void recalculateBoundingBox();
+   void reparentOOBObjects();
 
    ///
    bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) override;

+ 62 - 7
Engine/source/T3D/SubScene.cpp

@@ -10,6 +10,8 @@
 #include "gfx/gfxDrawUtil.h"
 #include "gfx/gfxTransformSaver.h"
 #include "gui/editor/inspector/group.h"
+#include "gui/worldEditor/editor.h"
+#include "math/mathIO.h"
 #include "T3D/gameBase/gameBase.h"
 
 bool SubScene::smTransformChildren = false;
@@ -32,7 +34,9 @@ SubScene::SubScene() :
    mTickPeriodMS(1000),
    mCurrTick(0),
    mGlobalLayer(false),
-   mSaving(false)
+   mSaving(false),
+   mUseSeparateLoadBounds(false),
+   mLoadBounds(Point3F::One)
 {
    mNetFlags.set(Ghostable | ScopeAlways);
 
@@ -70,6 +74,8 @@ void SubScene::initPersistFields()
    INITPERSISTFIELD_SUBSCENEASSET(SubScene, SubScene, "The subscene asset to load.");
    addField("tickPeriodMS", TypeS32, Offset(mTickPeriodMS, SubScene), "evaluation rate (ms)");
    addField("gameModes", TypeGameModeList, Offset(mGameModesNames, SubScene), "The game modes that this subscene is associated with.");
+   addField("UseSeparateLoadBounds", TypeBool, Offset(mUseSeparateLoadBounds, SubScene), "If true, this subscene will utilize a separate bounds for triggering loading/unloading than it's object bounds");
+   addField("LoadBounds", TypePoint3F, Offset(mLoadBounds, SubScene), "If UseSeparateLoadBounds is true, this subscene will use this value to set up the load/unload bounds");
    endGroup("SubScene");
 
    addGroup("LoadingManagement");
@@ -113,6 +119,11 @@ U32 SubScene::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
    U32 retMask = Parent::packUpdate(conn, mask, stream);
 
    stream->writeFlag(mGlobalLayer);
+   if(stream->writeFlag(mUseSeparateLoadBounds))
+   {
+      mathWrite(*stream, mLoadBounds);
+   }
+
 
    return retMask;
 }
@@ -123,6 +134,11 @@ void SubScene::unpackUpdate(NetConnection* conn, BitStream* stream)
 
    mGlobalLayer = stream->readFlag();
 
+   mUseSeparateLoadBounds = stream->readFlag();
+   if(mUseSeparateLoadBounds)
+   {
+      mathRead(*stream, &mLoadBounds);
+   }
 }
 
 void SubScene::onInspect(GuiInspector* inspector)
@@ -220,7 +236,21 @@ bool SubScene::testBox(const Box3F& testBox)
    bool passes = mGlobalLayer;
 
    if (!passes)
-      passes = getWorldBox().isOverlapped(testBox);
+   {
+      if(mUseSeparateLoadBounds)
+      {
+         Box3F loadBox = Box3F(-mLoadBounds.x, -mLoadBounds.y, -mLoadBounds.z,
+                                    mLoadBounds.x, mLoadBounds.y, mLoadBounds.z);
+
+         loadBox.setCenter(getPosition());
+
+         passes = loadBox.isOverlapped(testBox);
+      }
+      else
+      {
+         passes = getWorldBox().isOverlapped(testBox);
+      }
+   }
 
    if (passes)
       passes = evaluateCondition();
@@ -268,6 +298,9 @@ void SubScene::processTick(const Move* move)
 
 void SubScene::_onFileChanged(const Torque::Path& path)
 {
+   if (gEditingMission)
+      return;
+
    if(mSubSceneAsset.isNull() || Torque::Path(mSubSceneAsset->getLevelPath()) != path)
       return;
 
@@ -426,7 +459,7 @@ void SubScene::unload()
 
 }
 
-bool SubScene::save()
+bool SubScene::save(const String& filename)
 {
    if (!isServerObject())
       return false;
@@ -451,6 +484,9 @@ bool SubScene::save()
 
    StringTableEntry levelPath = mSubSceneAsset->getLevelPath();
 
+   if (filename.isNotEmpty())
+      levelPath = StringTable->insert(filename.c_str());
+
    FileStream fs;
    fs.open(levelPath, Torque::FS::File::Write);
    fs.close();
@@ -547,8 +583,26 @@ void SubScene::renderObject(ObjectRenderInst* ri,
    //Box3F scale = getScale()
    //Box3F bounds = Box3F(-m)
 
+   if(mUseSeparateLoadBounds && !mGlobalLayer)
+   {
+      Box3F loadBounds = Box3F(-mLoadBounds.x, -mLoadBounds.y, -mLoadBounds.z,
+         mLoadBounds.x, mLoadBounds.y, mLoadBounds.z);
+
+      //bounds.setCenter(getPosition());
+
+      ColorI loadBoundsColor = ColorI(200, 200, 100, 50);
+
+      drawer->drawCube(desc, loadBounds, loadBoundsColor);
+
+      // Render wireframe.
+
+      desc.setFillModeWireframe();
+      drawer->drawCube(desc, loadBounds, ColorI::BLACK);
+      desc.setFillModeSolid();
+   }
+
    Point3F scale = getScale();
-   Box3F bounds = Box3F(-scale/2, scale/2);
+   Box3F bounds = Box3F(-scale / 2, scale / 2);
 
    ColorI boundsColor = ColorI(135, 206, 235, 50);
 
@@ -565,10 +619,11 @@ void SubScene::renderObject(ObjectRenderInst* ri,
    drawer->drawCube(desc, bounds, ColorI::BLACK);
 }
 
-DefineEngineMethod(SubScene, save, bool, (),,
-   "Save out the subScene.\n")
+DefineEngineMethod(SubScene, save, bool, (const char* filename), (""),
+   "Save out the subScene.\n"
+   "@param filename (optional) If empty, the subScene will save to it's regular asset path. If defined, it will save out to the filename provided")
 {
-   return object->save();
+   return object->save(filename);
 }
 
 

+ 4 - 1
Engine/source/T3D/SubScene.h

@@ -52,6 +52,9 @@ private:
 
    bool mGlobalLayer;
 
+   bool mUseSeparateLoadBounds;
+   Point3F mLoadBounds;
+
 public:
    SubScene();
    virtual ~SubScene();
@@ -118,7 +121,7 @@ public:
       return mStartUnloadTimerMS;
    }
 
-   bool save();
+   bool save(const String& filename = String());
 
    DECLARE_CALLBACK(void, onLoaded, ());
    DECLARE_CALLBACK(void, onUnloaded, ());

+ 2 - 2
Engine/source/T3D/assets/assetImporter.cpp

@@ -78,7 +78,7 @@ AssetImportConfig::AssetImportConfig() :
    SeparateAnimations(false),
    SeparateAnimationPrefix(""),
    animTiming("FrameCount"),
-   animFPS(false),
+   animFPS(30),
    AlwaysAddShapeAnimationSuffix(true),
    AddedShapeAnimationSuffix("_anim"),
    GenerateCollisions(false),
@@ -193,7 +193,7 @@ void AssetImportConfig::initPersistFields()
       addField("SeparateAnimations", TypeBool, Offset(SeparateAnimations, AssetImportConfig), "When importing a shape file, should the animations within be separated out into unique files");
       addField("SeparateAnimationPrefix", TypeRealString, Offset(SeparateAnimationPrefix, AssetImportConfig), "If separating animations out from a source file, what prefix should be added to the names for grouping association");
       addField("animTiming", TypeRealString, Offset(animTiming, AssetImportConfig), "Defines the animation timing for the given animation sequence. Options are FrameTime, Seconds, Milliseconds");
-      addField("animFPS", TypeBool, Offset(animFPS, AssetImportConfig), "The FPS of the animation sequence");
+      addField("animFPS", TypeF32, Offset(animFPS, AssetImportConfig), "The FPS of the animation sequence");
       addField("AlwaysAddShapeAnimationSuffix", TypeBool, Offset(AlwaysAddShapeAnimationSuffix, AssetImportConfig), "When importing a shape animation, this indicates if it should automatically add a standard suffix onto the name");
       addField("AddedShapeAnimationSuffix", TypeString, Offset(AddedShapeAnimationSuffix, AssetImportConfig), " If AlwaysAddShapeAnimationSuffix is on, this is the suffix to be added");
    endGroup("Animation");

+ 8 - 0
Engine/source/T3D/gameBase/gameBase.cpp

@@ -349,6 +349,14 @@ void GameBase::inspectPostApply()
    setMaskBits(ExtendedInfoMask);
 }
 
+void GameBase::onInspect(GuiInspector* inspector)
+{
+   if (mDataBlock && mDataBlock->isMethod("onInspect"))
+      Con::executef(mDataBlock, "onInspect", this, inspector);
+   else
+      Parent::onInspect(inspector);
+}
+
 //----------------------------------------------------------------------------
 
 void GameBase::processTick(const Move * move)

+ 2 - 0
Engine/source/T3D/gameBase/gameBase.h

@@ -262,6 +262,8 @@ public:
    static void initPersistFields();
    static void consoleInit();
 
+   virtual void onInspect(GuiInspector*) override;
+
    /// @}
 
    ///@name Datablock

+ 63 - 0
Engine/source/assets/assetManager.cpp

@@ -2435,6 +2435,69 @@ AssetManager::typeAssetDependsOnHash* AssetManager::getDependedOnAssets()
    // Find any asset dependencies.
    return &mAssetDependsOn;
 }
+
+//-----------------------------------------------------------------------------
+S32 AssetManager::getAssetLooseFileCount(const char* pAssetId)
+{
+   // Debug Profiling.
+   PROFILE_SCOPE(AssetManager_getAssetLooseFileCount);
+
+   // Sanity!
+   AssertFatal(pAssetId != NULL, "Cannot get loose files for NULL asset Id.");
+
+   // Find asset.
+   AssetDefinition* pAssetDefinition = findAsset(pAssetId);
+
+   // Did we find the asset?
+   if (pAssetDefinition == NULL)
+   {
+      // No, so warn.
+      Con::warnf("Asset Manager: Failed to get loose files for asset Id '%s' as it does not exist.", pAssetId);
+      return false;
+   }
+
+   S32 looseFileCount = pAssetDefinition->mAssetLooseFiles.size();
+
+   // Cleanup our reference
+   pAssetDefinition = NULL;
+
+   return looseFileCount;
+}
+
+//-----------------------------------------------------------------------------
+
+const char* AssetManager::getAssetLooseFile(const char* pAssetId, const S32& index)
+{
+   // Debug Profiling.
+   PROFILE_SCOPE(AssetManager_getAssetLooseFile);
+
+   // Sanity!
+   AssertFatal(pAssetId != NULL, "Cannot get loose file for NULL asset Id.");
+
+   // Find asset.
+   AssetDefinition* pAssetDefinition = findAsset(pAssetId);
+
+   // Did we find the asset?
+   if (pAssetDefinition == NULL)
+   {
+      // No, so warn.
+      Con::warnf("Asset Manager: Failed to get loose file for asset Id '%s' as it does not exist.", pAssetId);
+      return false;
+   }
+
+   if(index < 0 || index >= pAssetDefinition->mAssetLooseFiles.size())
+   {
+      Con::warnf("Asset Manager : Failed to get loose file for asset Id '%s' as the index was out of range.", pAssetId);
+   }
+
+   StringTableEntry looseFile = pAssetDefinition->mAssetLooseFiles[index];
+
+   // Cleanup our reference
+   pAssetDefinition = NULL;
+
+   return looseFile;
+}
+
 //-----------------------------------------------------------------------------
 
 bool AssetManager::scanDeclaredAssets( const char* pPath, const char* pExtension, const bool recurse, ModuleDefinition* pModuleDefinition )

+ 3 - 0
Engine/source/assets/assetManager.h

@@ -376,6 +376,9 @@ public:
 
     typeAssetDependsOnHash* getDependedOnAssets();
 
+    S32 getAssetLooseFileCount(const char* pAssetId);
+    const char* getAssetLooseFile(const char* pAssetId, const S32& index);
+
     /// Declare Console Object.
     DECLARE_CONOBJECT( AssetManager );
 

+ 30 - 0
Engine/source/assets/assetManager_ScriptBinding.h

@@ -763,6 +763,36 @@ DefineEngineMethod(AssetManager, findAssetLooseFile, S32, (const char* assetQuer
 
 //-----------------------------------------------------------------------------
 
+DefineEngineMethod(AssetManager, getAssetLooseFileCount, S32, (const char* assetId), (""),
+   "Gets the number of loose files associated with the given assetId.\n"
+   "@param assetId The assetId to check.\n"
+   "@return The number of loose files associated with the assetId.\n")
+{
+   // Fetch asset loose file.
+   const char* pAssetId = assetId;
+
+   // Perform query.
+   return object->getAssetLooseFileCount(pAssetId);
+}
+
+//-----------------------------------------------------------------------------
+
+DefineEngineMethod(AssetManager, getAssetLooseFile, const char*, (const char* assetId, S32 index), ("", 0),
+   "Gets the loose file associated to the given assetId at the provided index.\n"
+   "@param assetId The assetId to check.\n"
+   "@param index The index of the loose file to get.\n"
+   "@return The file name of the associated loose file.\n")
+{
+
+   // Fetch asset loose file.
+   const char* pAssetId = assetId;
+
+   // Perform query.
+   return object->getAssetLooseFile(pAssetId, index);
+}
+
+//-----------------------------------------------------------------------------
+
 DefineEngineMethod(AssetManager, getDeclaredAssetCount, bool, (),,
    "Gets the number of declared assets.\n"
    "@return Returns the number of declared assets.\n")

+ 4 - 3
Engine/source/forest/forestItem.h

@@ -118,9 +118,10 @@ public:
 
    /// Called from Forest the first time a datablock is used
    /// in order to lazy load content.
-   void preload() 
-   { 
-      if ( !mNeedPreload ) 
+   bool preload(bool server, String& errorStr) override { return false; }; // we don't ghost ForestItemData specifically. we do do so for TSForestItemData
+   void preload()
+   {
+      if (!mNeedPreload)
          return;
 
       _preload();

+ 1 - 1
Engine/source/gui/buttons/guiIconButtonCtrl.cpp

@@ -382,7 +382,7 @@ void GuiIconButtonCtrl::renderButton( Point2I &offset, const RectI& updateRect )
          Point2I start( mTextMargin, ( getHeight() - mProfile->mFont->getHeight() ) / 2 );
          if (mBitmapAsset.notNull() && mIconLocation != IconLocNone)
          {
-            start.x = iconRect.extent.x + mButtonMargin.x + mTextMargin;
+            start.x = getWidth() - (iconRect.extent.x + mButtonMargin.x + textWidth);
          }
 
          drawer->setBitmapModulation(fontColor);

+ 17 - 0
Engine/source/gui/core/guiControl.cpp

@@ -2855,6 +2855,23 @@ DefineEngineMethod( GuiControl, getGlobalCenter, Point2I, (),,
 
 //-----------------------------------------------------------------------------
 
+DefineEngineMethod(GuiControl, setGlobalCenter, void, (S32 x, S32 y), ,
+   "Set the coordinate of the control's center point in coordinates relative to the root control in its control hierarchy.\n"
+   "@param x The X coordinate of the new center point of the control relative to the root control's.\n"
+   "@param y The Y coordinate of the new center point of the control relative to the root control's.")
+{
+   //see if we can turn the x/y into ints directly, 
+   Point2I lPosOffset = object->globalToLocalCoord(Point2I(x, y));
+
+   lPosOffset += object->getPosition();
+
+   const Point2I ext = object->getExtent();
+   Point2I newpos(lPosOffset.x - ext.x / 2, lPosOffset.y - ext.y / 2);
+   object->setPosition(newpos);
+}
+
+//-----------------------------------------------------------------------------
+
 DefineEngineMethod( GuiControl, getGlobalPosition, Point2I, (),,
    "Get the position of the control relative to the root of the GuiControl hierarchy it is contained in.\n"
    "@return The control's current position in root-relative coordinates." )

+ 19 - 0
Engine/source/gui/utility/guiInputCtrl.cpp

@@ -133,6 +133,25 @@ void GuiInputCtrl::onSleep()
    clearFirstResponder();
 }
 
+void GuiInputCtrl::setActive(bool value)
+{
+   Parent::setActive(value);
+
+   if (value)
+   {
+      if (!smDesignTime && !mIgnoreMouseEvents)
+         mouseLock();
+
+      setFirstResponder();
+   }
+   else
+   {
+      mouseUnlock();
+      clearFirstResponder();
+   }
+
+}
+
 
 //------------------------------------------------------------------------------
 static bool isModifierKey( U16 keyCode )

+ 2 - 0
Engine/source/gui/utility/guiInputCtrl.h

@@ -51,6 +51,8 @@ public:
    bool onWake() override;
    void onSleep() override;
 
+   virtual void setActive(bool state);
+
    bool onInputEvent( const InputEventInfo &event ) override;
 
    static void initPersistFields();

+ 10 - 0
Engine/source/gui/worldEditor/editTSCtrl.cpp

@@ -1409,3 +1409,13 @@ DefineEngineMethod( EditTSCtrl, isMiddleMouseDown, bool, (),, "" )
 {
    return object->isMiddleMouseDown();
 }
+
+DefineEngineMethod(EditTSCtrl, isLeftMouseDown, bool, (), , "")
+{
+   return object->isLeftMouseDown();
+}
+
+DefineEngineMethod(EditTSCtrl, isRightMouseDown, bool, (), , "")
+{
+   return object->isRightMouseDown();
+}

+ 2 - 0
Engine/source/gui/worldEditor/editTSCtrl.h

@@ -189,7 +189,9 @@ class EditTSCtrl : public GuiTSCtrl
       virtual void on3DMouseWheelDown(const Gui3DMouseEvent &){};
       virtual void get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &);
 
+      virtual bool isLeftMouseDown() { return mLeftMouseDown; }
       virtual bool isMiddleMouseDown() {return mMiddleMouseDown;}
+      virtual bool isRightMouseDown() { return mLeftMouseDown; }
 
       bool resize(const Point2I& newPosition, const Point2I& newExtent) override;
 

+ 3 - 0
Engine/source/ts/assimp/assimpAppMesh.cpp

@@ -101,6 +101,9 @@ void AssimpAppMesh::computeBounds(Box3F& bounds)
 
 TSMesh* AssimpAppMesh::constructTSMesh()
 {
+   if (points.empty() || normals.empty() || primitives.empty() || indices.empty())
+      return NULL;
+
    TSMesh* tsmesh;
    if (isSkin())
    {

+ 3 - 0
Engine/source/ts/loader/appMesh.cpp

@@ -126,6 +126,9 @@ void AppMesh::computeNormals()
 
 TSMesh* AppMesh::constructTSMesh()
 {
+   if (points.empty() || normals.empty() || primitives.empty() || indices.empty())
+      return NULL;
+
    TSMesh* tsmesh;
    if (isSkin())
    {

+ 1 - 1
Templates/BaseGame/game/core/clientServer/scripts/server/levelLoad.tscript

@@ -156,7 +156,7 @@ function loadMissionStage3()
 function GameBase::onCreateGame(%this)
 {
     %db = %this.getDatablock();
-    if (%db.isMethod("onCreateGame"))
+    if (isObject(%db) && %db.isMethod("onCreateGame"))
     {
         %db.onCreateGame(%this);
     }

+ 156 - 127
Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript

@@ -1199,140 +1199,169 @@ function AutodetectGraphics()
    %intel = ( strstr( strupr( getDisplayDeviceInformation() ), "INTEL" ) != -1 ) ? true : false;
    %videoMem = GFXCardProfilerAPI::getVideoMemoryMB();
    
-   if ( %shaderVer < 2.0 )
-   {      
-      echo("Your video card does not meet the minimum requirment of shader model 2.0.");
-   }
-   
-   if ( %shaderVer < 3.0 || %intel )
+   //Is this a steamdeck?
+   if(startsWith(getDisplayDeviceInformation(), "AMD Custom GPU 0405"))
    {
-      // Allow specular and normals for 2.0a and 2.0b
-      if ( %shaderVer > 2.0 )
-      {
-         MeshQualityGroup.applySetting("Lowest");
-         TextureQualityGroup.applySetting("Lowest");
-         GroundCoverDensityGroup.applySetting("Lowest");
-         DecalLifetimeGroup.applySetting("None");
-         TerrainQualityGroup.applySetting("Lowest");
-         ShaderQualityGroup.applySetting("High");
-         
-         ShadowQualityList.applySetting("None");
-         
-         SoftShadowList.applySetting("Off");
-         
-         $pref::Shadows::useShadowCaching = true;
-         
-         AnisotropicFilterOptionsGroup.applySetting("None");
-         AntiAliasingOptionsGroup.applySetting("Off");
-         ParallaxOptionsGroup.applySetting("Off");
-         TrueWaterReflectionsOptionsGroup.applySetting("Off");
-         PostFXSSAOOptionsGroup.applySetting("Off");
-         PostFXDOFOptionsGroup.applySetting("Off");
-         PostFXVignetteOptionsGroup.applySetting("Off");
-         PostFXLightRayOptionsGroup.applySetting("Off");
-      }
-      else
-      {
-         MeshQualityGroup.applySetting("Lowest");
-         TextureQualityGroup.applySetting("Lowest");
-         GroundCoverDensityGroup.applySetting("Lowest");
-         DecalLifetimeGroup.applySetting("None");
-         TerrainQualityGroup.applySetting("Lowest");
-         ShaderQualityGroup.applySetting("Low");
-         
-         ShadowQualityList.applySetting("None");
-         
-         SoftShadowList.applySetting("Off");
-         
-         $pref::Shadows::useShadowCaching = true;
-         
-         AnisotropicFilterOptionsGroup.applySetting("None");
-         AntiAliasingOptionsGroup.applySetting("Off");
-         ParallaxOptionsGroup.applySetting("Off");
-         TrueWaterReflectionsOptionsGroup.applySetting("Off");
-         PostFXSSAOOptionsGroup.applySetting("Off");
-         PostFXDOFOptionsGroup.applySetting("Off");
-         PostFXVignetteOptionsGroup.applySetting("Off");
-         PostFXLightRayOptionsGroup.applySetting("Off");
-      }
-   }   
+      //If we're on a steamdeck, we can specifically calibrate settings for the platform here
+      MeshQualityGroup.applySetting("Medium");
+      TextureQualityGroup.applySetting("Medium");
+      GroundCoverDensityGroup.applySetting("Medium");
+      DecalLifetimeGroup.applySetting("Medium");
+      TerrainQualityGroup.applySetting("Medium");
+      ShaderQualityGroup.applySetting("High");
+      
+      ShadowQualityList.applySetting("None");
+      
+      SoftShadowList.applySetting("Low");
+      
+      $pref::Shadows::useShadowCaching = true;
+      
+      AnisotropicFilterOptionsGroup.applySetting("4x");
+      AntiAliasingOptionsGroup.applySetting("SMAA");
+      ParallaxOptionsGroup.applySetting("On");
+      TrueWaterReflectionsOptionsGroup.applySetting("On");
+      PostFXSSAOOptionsGroup.applySetting("Off");
+      PostFXDOFOptionsGroup.applySetting("On");
+      PostFXVignetteOptionsGroup.applySetting("On");
+      PostFXLightRayOptionsGroup.applySetting("On");
+   }
    else
    {
-      if ( %videoMem > 1000 )
-      {
-         MeshQualityGroup.applySetting("High");
-         TextureQualityGroup.applySetting("High");
-         GroundCoverDensityGroup.applySetting("High");
-         DecalLifetimeGroup.applySetting("High");
-         TerrainQualityGroup.applySetting("High");
-         ShaderQualityGroup.applySetting("High");
-         
-         ShadowQualityList.applySetting("High");
-         
-         SoftShadowList.applySetting("High");
-         
-         //Should this default to on in ultra settings?
-         $pref::Shadows::useShadowCaching = true;
-         
-         AnisotropicFilterOptionsGroup.applySetting("16x");
-         AntiAliasingOptionsGroup.applySetting("SMAA High");
-         ParallaxOptionsGroup.applySetting("On");
-         TrueWaterReflectionsOptionsGroup.applySetting("On");
-         PostFXSSAOOptionsGroup.applySetting("On");
-         PostFXDOFOptionsGroup.applySetting("On");
-         PostFXVignetteOptionsGroup.applySetting("On");
-         PostFXLightRayOptionsGroup.applySetting("On");
+      if ( %shaderVer < 2.0 )
+      {      
+         echo("Your video card does not meet the minimum requirment of shader model 2.0.");
       }
-      else if ( %videoMem > 400 || %videoMem == 0 )
+      
+      if ( %shaderVer < 3.0 || %intel )
       {
-         MeshQualityGroup.applySetting("Medium");
-         TextureQualityGroup.applySetting("Medium");
-         GroundCoverDensityGroup.applySetting("Medium");
-         DecalLifetimeGroup.applySetting("Medium");
-         TerrainQualityGroup.applySetting("Medium");
-         ShaderQualityGroup.applySetting("High");
-         
-         ShadowQualityList.applySetting("Medium");
-         
-         SoftShadowList.applySetting("Low");
-         
-         $pref::Shadows::useShadowCaching = true;
-         
-         AnisotropicFilterOptionsGroup.applySetting("4x");
-         AntiAliasingOptionsGroup.applySetting("SMAA");
-         ParallaxOptionsGroup.applySetting("On");
-         TrueWaterReflectionsOptionsGroup.applySetting("On");
-         PostFXSSAOOptionsGroup.applySetting("Off");
-         PostFXDOFOptionsGroup.applySetting("On");
-         PostFXVignetteOptionsGroup.applySetting("On");
-         PostFXLightRayOptionsGroup.applySetting("On");
-         
-         if ( %videoMem == 0 )
-            echo("Torque was unable to detect available video memory. Applying 'Medium' quality.");
-      }
+         // Allow specular and normals for 2.0a and 2.0b
+         if ( %shaderVer > 2.0 )
+         {
+            MeshQualityGroup.applySetting("Lowest");
+            TextureQualityGroup.applySetting("Lowest");
+            GroundCoverDensityGroup.applySetting("Lowest");
+            DecalLifetimeGroup.applySetting("None");
+            TerrainQualityGroup.applySetting("Lowest");
+            ShaderQualityGroup.applySetting("High");
+            
+            ShadowQualityList.applySetting("None");
+            
+            SoftShadowList.applySetting("Off");
+            
+            $pref::Shadows::useShadowCaching = true;
+            
+            AnisotropicFilterOptionsGroup.applySetting("None");
+            AntiAliasingOptionsGroup.applySetting("Off");
+            ParallaxOptionsGroup.applySetting("Off");
+            TrueWaterReflectionsOptionsGroup.applySetting("Off");
+            PostFXSSAOOptionsGroup.applySetting("Off");
+            PostFXDOFOptionsGroup.applySetting("Off");
+            PostFXVignetteOptionsGroup.applySetting("Off");
+            PostFXLightRayOptionsGroup.applySetting("Off");
+         }
+         else
+         {
+            MeshQualityGroup.applySetting("Lowest");
+            TextureQualityGroup.applySetting("Lowest");
+            GroundCoverDensityGroup.applySetting("Lowest");
+            DecalLifetimeGroup.applySetting("None");
+            TerrainQualityGroup.applySetting("Lowest");
+            ShaderQualityGroup.applySetting("Low");
+            
+            ShadowQualityList.applySetting("None");
+            
+            SoftShadowList.applySetting("Off");
+            
+            $pref::Shadows::useShadowCaching = true;
+            
+            AnisotropicFilterOptionsGroup.applySetting("None");
+            AntiAliasingOptionsGroup.applySetting("Off");
+            ParallaxOptionsGroup.applySetting("Off");
+            TrueWaterReflectionsOptionsGroup.applySetting("Off");
+            PostFXSSAOOptionsGroup.applySetting("Off");
+            PostFXDOFOptionsGroup.applySetting("Off");
+            PostFXVignetteOptionsGroup.applySetting("Off");
+            PostFXLightRayOptionsGroup.applySetting("Off");
+         }
+      }   
       else
       {
-         MeshQualityGroup.applySetting("Low");
-         TextureQualityGroup.applySetting("Low");
-         GroundCoverDensityGroup.applySetting("Low");
-         DecalLifetimeGroup.applySetting("Low");
-         TerrainQualityGroup.applySetting("Low");
-         ShaderQualityGroup.applySetting("Low");
-         
-         ShadowQualityList.applySetting("None");
-         
-         SoftShadowList.applySetting("Off");
-         
-         $pref::Shadows::useShadowCaching = true;
-         
-         AnisotropicFilterOptionsGroup.applySetting("None");
-         AntiAliasingOptionsGroup.applySetting("FXAA");
-         ParallaxOptionsGroup.applySetting("On");
-         TrueWaterReflectionsOptionsGroup.applySetting("On");
-         PostFXSSAOOptionsGroup.applySetting("Off");
-         PostFXDOFOptionsGroup.applySetting("Off");
-         PostFXVignetteOptionsGroup.applySetting("Off");
-         PostFXLightRayOptionsGroup.applySetting("Off");
+         if ( %videoMem > 1000 )
+         {
+            MeshQualityGroup.applySetting("High");
+            TextureQualityGroup.applySetting("High");
+            GroundCoverDensityGroup.applySetting("High");
+            DecalLifetimeGroup.applySetting("High");
+            TerrainQualityGroup.applySetting("High");
+            ShaderQualityGroup.applySetting("High");
+            
+            ShadowQualityList.applySetting("High");
+            
+            SoftShadowList.applySetting("High");
+            
+            //Should this default to on in ultra settings?
+            $pref::Shadows::useShadowCaching = true;
+            
+            AnisotropicFilterOptionsGroup.applySetting("16x");
+            AntiAliasingOptionsGroup.applySetting("SMAA High");
+            ParallaxOptionsGroup.applySetting("On");
+            TrueWaterReflectionsOptionsGroup.applySetting("On");
+            PostFXSSAOOptionsGroup.applySetting("On");
+            PostFXDOFOptionsGroup.applySetting("On");
+            PostFXVignetteOptionsGroup.applySetting("On");
+            PostFXLightRayOptionsGroup.applySetting("On");
+         }
+         else if ( %videoMem > 400 || %videoMem == 0 )
+         {
+            MeshQualityGroup.applySetting("Medium");
+            TextureQualityGroup.applySetting("Medium");
+            GroundCoverDensityGroup.applySetting("Medium");
+            DecalLifetimeGroup.applySetting("Medium");
+            TerrainQualityGroup.applySetting("Medium");
+            ShaderQualityGroup.applySetting("High");
+            
+            ShadowQualityList.applySetting("Medium");
+            
+            SoftShadowList.applySetting("Low");
+            
+            $pref::Shadows::useShadowCaching = true;
+            
+            AnisotropicFilterOptionsGroup.applySetting("4x");
+            AntiAliasingOptionsGroup.applySetting("SMAA");
+            ParallaxOptionsGroup.applySetting("On");
+            TrueWaterReflectionsOptionsGroup.applySetting("On");
+            PostFXSSAOOptionsGroup.applySetting("Off");
+            PostFXDOFOptionsGroup.applySetting("On");
+            PostFXVignetteOptionsGroup.applySetting("On");
+            PostFXLightRayOptionsGroup.applySetting("On");
+            
+            if ( %videoMem == 0 )
+               echo("Torque was unable to detect available video memory. Applying 'Medium' quality.");
+         }
+         else
+         {
+            MeshQualityGroup.applySetting("Low");
+            TextureQualityGroup.applySetting("Low");
+            GroundCoverDensityGroup.applySetting("Low");
+            DecalLifetimeGroup.applySetting("Low");
+            TerrainQualityGroup.applySetting("Low");
+            ShaderQualityGroup.applySetting("Low");
+            
+            ShadowQualityList.applySetting("None");
+            
+            SoftShadowList.applySetting("Off");
+            
+            $pref::Shadows::useShadowCaching = true;
+            
+            AnisotropicFilterOptionsGroup.applySetting("None");
+            AntiAliasingOptionsGroup.applySetting("FXAA");
+            ParallaxOptionsGroup.applySetting("On");
+            TrueWaterReflectionsOptionsGroup.applySetting("On");
+            PostFXSSAOOptionsGroup.applySetting("Off");
+            PostFXDOFOptionsGroup.applySetting("Off");
+            PostFXVignetteOptionsGroup.applySetting("Off");
+            PostFXLightRayOptionsGroup.applySetting("Off");
+         }
       }
    }
    

+ 1 - 3
Templates/BaseGame/game/tools/assetBrowser/assetImportConfigs.xml

@@ -35,9 +35,7 @@
             <Setting
                 name="AutomaticallyPromptMissingFiles">0</Setting>
             <Setting
-                name="DuplicatAutoResolution">AutoPrune</Setting>
-            <Setting
-                name="DuplicateAutoResolution">FolderPrefix</Setting>
+                name="DuplicateAutoResolution">AutoPrune</Setting>
             <Setting
                 name="PreventImportWithErrors">1</Setting>
             <Setting

+ 46 - 0
Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/level.tscript

@@ -99,6 +99,52 @@ function LevelAsset::onEdit(%this)
    schedule( 1, 0, "EditorOpenMission", %this);
 }
 
+function LevelAsset::onShowActionMenu(%this)
+{
+   if( !isObject( EditLevelAssetPopup ) )
+   {
+      new PopupMenu( EditLevelAssetPopup )
+      {
+         superClass = "MenuBuilder";
+         class = "EditorWorldMenu";
+
+         jumpFileName = "";
+         jumpLineNumber = "";
+      };
+   }
+   
+   //Regen the menu so we're fully up and current with options and references
+   EditLevelAssetPopup.clearItems();
+   
+   EditLevelAssetPopup.appendItem("Edit Asset" TAB "" TAB $CurrentAssetBrowser @ ".editAsset();");
+   EditLevelAssetPopup.appendItem("Rename Asset" TAB "" TAB $CurrentAssetBrowser @ ".renameAsset();");
+   EditLevelAssetPopup.appendItem("Reload Asset" TAB "" TAB $CurrentAssetBrowser @ ".refreshAsset();");
+   EditLevelAssetPopup.appendItem("Asset Properties" TAB "" TAB $CurrentAssetBrowser @ ".editAssetInfo();");
+   EditLevelAssetPopup.appendItem("-");
+   EditLevelAssetPopup.appendItem("Duplicate Asset" TAB "" TAB $CurrentAssetBrowser @ ".duplicateAsset();");
+   EditLevelAssetPopup.appendItem("-");
+   EditLevelAssetPopup.appendItem("Regenerate Preview Image" TAB "" TAB $CurrentAssetBrowser @ ".regeneratePreviewImage();");
+   EditLevelAssetPopup.appendItem("-");
+   EditLevelAssetPopup.appendItem("Restore Backup" TAB RestoreBackupListPopup);
+   EditLevelAssetPopup.appendItem("-");
+   EditLevelAssetPopup.appendItem("Open File Location" TAB "" TAB $CurrentAssetBrowser @ ".openFileLocation();");
+   EditLevelAssetPopup.appendItem("-");
+   EditLevelAssetPopup.appendItem("Delete Asset" TAB "" TAB $CurrentAssetBrowser @ ".deleteAsset();");
+   
+   %assetId = %this.getAssetId();
+   %assetType = AssetDatabase.getAssetType(%assetId);
+   
+   EditLevelAssetPopup.objectData = %assetId;
+   EditLevelAssetPopup.objectType = %assetType;
+   
+   RestoreBackupListPopup.populateList(%assetId);
+   EditLevelAssetPopup.reloadItems();
+   
+   EditLevelAssetPopup.showPopup(Canvas); 
+   
+   $CurrentAssetBrowser.popupMenu = EditLevelAssetPopup;
+}
+
 function LevelAsset::buildBrowserElement(%this, %previewData)
 {
    %previewData.assetName = %this.assetName;

+ 46 - 0
Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/subScene.tscript

@@ -64,6 +64,52 @@ function SubSceneAsset::onCreateNew(%this)
 	return %tamlpath;  
 }
 
+function SubSceneAsset::onShowActionMenu(%this)
+{
+   if( !isObject( EditSubSceneAssetPopup ) )
+   {
+      new PopupMenu( EditSubSceneAssetPopup )
+      {
+         superClass = "MenuBuilder";
+         class = "EditorWorldMenu";
+
+         jumpFileName = "";
+         jumpLineNumber = "";
+      };
+   }
+   
+   //Regen the menu so we're fully up and current with options and references
+   EditSubSceneAssetPopup.clearItems();
+   
+   EditSubSceneAssetPopup.appendItem("Edit Asset" TAB "" TAB $CurrentAssetBrowser @ ".editAsset();");
+   EditSubSceneAssetPopup.appendItem("Rename Asset" TAB "" TAB $CurrentAssetBrowser @ ".renameAsset();");
+   EditSubSceneAssetPopup.appendItem("Reload Asset" TAB "" TAB $CurrentAssetBrowser @ ".refreshAsset();");
+   EditSubSceneAssetPopup.appendItem("Asset Properties" TAB "" TAB $CurrentAssetBrowser @ ".editAssetInfo();");
+   EditSubSceneAssetPopup.appendItem("-");
+   EditSubSceneAssetPopup.appendItem("Duplicate Asset" TAB "" TAB $CurrentAssetBrowser @ ".duplicateAsset();");
+   EditSubSceneAssetPopup.appendItem("-");
+   EditSubSceneAssetPopup.appendItem("Regenerate Preview Image" TAB "" TAB $CurrentAssetBrowser @ ".regeneratePreviewImage();");
+   EditSubSceneAssetPopup.appendItem("-");
+   EditSubSceneAssetPopup.appendItem("Restore Backup" TAB RestoreBackupListPopup);
+   EditSubSceneAssetPopup.appendItem("-");
+   EditSubSceneAssetPopup.appendItem("Open File Location" TAB "" TAB $CurrentAssetBrowser @ ".openFileLocation();");
+   EditSubSceneAssetPopup.appendItem("-");
+   EditSubSceneAssetPopup.appendItem("Delete Asset" TAB "" TAB $CurrentAssetBrowser @ ".deleteAsset();");
+   
+   %assetId = %this.getAssetId();
+   %assetType = AssetDatabase.getAssetType(%assetId);
+   
+   EditSubSceneAssetPopup.objectData = %assetId;
+   EditSubSceneAssetPopup.objectType = %assetType;
+   
+   RestoreBackupListPopup.populateList(%assetId);
+   EditSubSceneAssetPopup.reloadItems();
+   
+   EditSubSceneAssetPopup.showPopup(Canvas); 
+   
+   $CurrentAssetBrowser.popupMenu = EditSubSceneAssetPopup;
+}
+
 function SubSceneAsset::buildBrowserElement(%this, %previewData)
 {
    %previewData.assetName = %this.assetName;

+ 43 - 0
Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/terrain.tscript

@@ -132,6 +132,49 @@ function createTerrainBlock(%assetId)
    //
 }
 
+function TerrainAsset::onShowActionMenu(%this)
+{
+   if( !isObject( EditTerrainAssetPopup ) )
+   {
+      new PopupMenu( EditTerrainAssetPopup )
+      {
+         superClass = "MenuBuilder";
+         class = "EditorWorldMenu";
+
+         jumpFileName = "";
+         jumpLineNumber = "";
+      };
+   }
+   
+   //Regen the menu so we're fully up and current with options and references
+   EditTerrainAssetPopup.clearItems();
+   
+   EditTerrainAssetPopup.appendItem("Edit Asset" TAB "" TAB $CurrentAssetBrowser @ ".editAsset();");
+   EditTerrainAssetPopup.appendItem("Rename Asset" TAB "" TAB $CurrentAssetBrowser @ ".renameAsset();");
+   EditTerrainAssetPopup.appendItem("Reload Asset" TAB "" TAB $CurrentAssetBrowser @ ".refreshAsset();");
+   EditTerrainAssetPopup.appendItem("Asset Properties" TAB "" TAB $CurrentAssetBrowser @ ".editAssetInfo();");
+   EditTerrainAssetPopup.appendItem("-");
+   EditTerrainAssetPopup.appendItem("Duplicate Asset" TAB "" TAB $CurrentAssetBrowser @ ".duplicateAsset();");
+   EditTerrainAssetPopup.appendItem("-");
+   EditTerrainAssetPopup.appendItem("Restore Backup" TAB RestoreBackupListPopup);
+   EditTerrainAssetPopup.appendItem("-");
+   EditTerrainAssetPopup.appendItem("Open File Location" TAB "" TAB $CurrentAssetBrowser @ ".openFileLocation();");
+   EditTerrainAssetPopup.appendItem("-");
+   EditTerrainAssetPopup.appendItem("Delete Asset" TAB "" TAB $CurrentAssetBrowser @ ".deleteAsset();");
+   
+   %assetId = %this.getAssetId();
+   %assetType = AssetDatabase.getAssetType(%assetId);
+   
+   EditTerrainAssetPopup.objectData = %assetId;
+   EditTerrainAssetPopup.objectType = %assetType;
+   
+   RestoreBackupListPopup.populateList(%assetId);
+   EditTerrainAssetPopup.reloadItems();
+   
+   EditTerrainAssetPopup.showPopup(Canvas); 
+   
+   $CurrentAssetBrowser.popupMenu = EditTerrainAssetPopup;
+}
 
 function TerrainAsset::onWorldEditorDropped(%this, %position)
 {

+ 83 - 0
Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript

@@ -520,4 +520,87 @@ function AssetBrowser::openFolderLocation(%this, %folderPath)
          systemCommand(%cmd);
       }
    }
+}
+
+//-------------------------------------------------------------
+function AssetBrowser::getAssetBackupCount(%this, %assetId)
+{
+   //process it and then check if we have any autosave backups
+   %processedId = strReplace(EditAssetPopup.assetId, ":", "_");
+   %autosavePath = "tools/autosave/" @ %processedId @ "/";
+   
+   RestoreBackupListPopup.clearItems();
+   
+   if(isDirectory(%autosavePath))
+   {
+      %dirs = getDirectoryList(%autosavePath); 
+      %count = getFieldCount(%dirs);
+      
+      return %count;
+   }
+   
+   return 0;
+}
+
+function AssetBrowser::restoreAssetBackup(%this, %assetId, %index)
+{
+   if(!AssetDatabase.isDeclaredAsset(%assetId))
+   {
+      error("AssetBrowser::restoreAssetBackup() - Attempted to restore backed up version of asset: " @ %assetId @ " but asset is not validly declared!");
+      return;
+   }
+   
+   //process it and then check if we have any autosave backups
+   %processedId = strReplace(%assetId, ":", "_");
+   %autosavePath = "tools/autosave/" @ %processedId @ "/";
+   
+   if(isDirectory(%autosavePath))
+   {
+      %dirs = getDirectoryList(%autosavePath); 
+      %count = getFieldCount(%dirs);
+      if(%count > 0)
+      {
+         %saveArray = new ArrayObject(){};
+         //loop over the entries and find the oldest one
+         for(%f=0; %f < %count; %f++)
+         {
+            %saveArray.add(getField(%dirs, %f));            
+         }
+         
+         %saveArray.sortk();
+         
+         %folderName = %saveArray.getKey(%index);
+         
+         //now we just copy the contents of the folder into our assetId path and refresh
+         %assetPath = AssetDatabase.getAssetPath(%assetId);
+         
+         %autosaveFullPath = %autosavePath @ "/" @ %folderName @ "/";
+         %autosaveFullPath = strReplace(%autosaveFullPath, "//", "/");
+         
+         %file = findFirstFile( %autosaveFullPath @ "*.*" );
+         while( %file !$= "" )
+         {      
+            %fileName = fileName(%file);
+            %assetFileName = %assetPath @ "/" @ %fileName;
+            
+            warn("| Copying file from: " @ %file @ " to: " @ %assetFileName);
+            if(!pathCopy(%file, %assetFileName, false))
+            {
+               error("AssetBrowser::restoreAssetBackup() - Something went wrong when copying the file: " @ %file @ " to " @ %assetFileName);  
+            }
+            
+            %file = findNextFile( %autosaveFullPath @ "*.*" );
+         }
+         
+         AssetBrowser.reloadAsset(%assetId);
+      }
+      else
+      {
+         error("AssetBrowser::restoreAssetBackup() - Attempted to restore backed up version of asset: " @ %assetId @ " but no autosaves were found!");
+      }
+   }
+   else
+   {
+      error("AssetBrowser::restoreAssetBackup() - Attempted to restore backed up version of asset: " @ %assetId @ " but autosave directory doesn't exist!");
+   }
 }

+ 58 - 4
Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript

@@ -43,6 +43,18 @@ function AssetBrowser::buildPopupMenus(%this)
       };
    }
    
+   if( !isObject( RestoreBackupListPopup ) )
+   {
+      new PopupMenu( RestoreBackupListPopup )
+      {
+         superClass = "MenuBuilder";
+         class = "EditorWorldMenu";
+         //isPopup = true;
+         
+         radioSelection = false;
+      };
+   }
+   
    if( !isObject( EditLevelAssetPopup ) )
    {
       new PopupMenu( EditLevelAssetPopup )
@@ -59,9 +71,11 @@ function AssetBrowser::buildPopupMenus(%this)
          item[ 5 ] = "-";
          Item[ 6 ] = "Duplicate Asset" TAB "" TAB "AssetBrowser.duplicateAsset();";
          item[ 7 ] = "-";
-         item[ 8 ] = "Open File Location" TAB "" TAB "AssetBrowser.openFileLocation();";
+         Item[ 8 ] = "Restore Backup" TAB RestoreBackupListPopup;
          item[ 9 ] = "-";
-         item[ 10 ] = "Delete Asset" TAB "" TAB "AssetBrowser.deleteAsset();";
+         item[ 10 ] = "Open File Location" TAB "" TAB "AssetBrowser.openFileLocation();";
+         item[ 11 ] = "-";
+         item[ 12 ] = "Delete Asset" TAB "" TAB "AssetBrowser.deleteAsset();";
 
          jumpFileName = "";
          jumpLineNumber = "";
@@ -82,9 +96,11 @@ function AssetBrowser::buildPopupMenus(%this)
          item[ 3 ] = "-";
          Item[ 4 ] = "Duplicate Asset" TAB "" TAB "AssetBrowser.duplicateAsset();";
          item[ 5 ] = "-";
-         item[ 6 ] = "Open File Location" TAB "" TAB "AssetBrowser.openFileLocation();";
+         Item[ 6 ] = "Restore Backup" TAB RestoreBackupListPopup;
          item[ 7 ] = "-";
-         item[ 8 ] = "Delete Asset" TAB "" TAB "AssetBrowser.deleteAsset();";
+         item[ 8 ] = "Open File Location" TAB "" TAB "AssetBrowser.openFileLocation();";
+         item[ 9 ] = "-";
+         item[ 10 ] = "Delete Asset" TAB "" TAB "AssetBrowser.deleteAsset();";
 
          jumpFileName = "";
          jumpLineNumber = "";
@@ -398,3 +414,41 @@ function AddNewScriptAssetPopupMenu::setupDefaultState(%this)
 function AddNewScriptAssetPopupMenu::setupGuiControls(%this)
 {
 }
+
+function RestoreBackupListPopup::populateList(%this, %assetId)
+{
+   //process it and then check if we have any autosave backups
+   %processedId = strReplace(%assetId, ":", "_");
+   %autosavePath = "tools/autosave/" @ %processedId @ "/";
+   
+   RestoreBackupListPopup.clearItems();
+   
+   if(isDirectory(%autosavePath))
+   {
+      %dirs = getDirectoryList(%autosavePath); 
+      %count = getFieldCount(%dirs);
+
+      if(%count > 0)
+      {
+         %saveArray = new ArrayObject(){};
+         //loop over the entries and find the oldest one
+         for(%f=0; %f < %count; %f++)
+         {
+            %saveArray.add(getField(%dirs, %f));            
+         }
+         
+         %saveArray.sortk();
+         
+         for(%i=0; %i < %count; %i++)
+         {
+            %folderName = %saveArray.getKey(%i);
+            %labelText = %folderName @ " (" @ fileModifiedTime(%autosavePath @ %folderName) @ ")";
+            RestoreBackupListPopup.addItem(%i, %labelText TAB "" TAB "AssetBrowser.restoreAssetBackup(\"" @ %assetId @ "\"," @ %i @ ");");
+            
+            echo("Added restore item: " @ %labelText TAB "" TAB "AssetBrowser.restoreAssetBackup(\"" @ %assetId @ "\"," @ %i @ ");");
+         }
+         
+         %saveArray.delete();
+      }    
+   }
+}

+ 3 - 1
Templates/BaseGame/game/tools/editorCore/scripts/menuBar/menuBuilder.ed.tscript

@@ -333,11 +333,13 @@ function MenuBuilder::addItem(%this, %pos, %item)
    {
       %this.insertItem(%pos, %name !$= "-" ? %name : "", %accel, %cmd, %bitmapIdx $= "" ? -1 : %bitmapIdx);
    }
+   
+   return %pos;
 }
 
 function MenuBuilder::appendItem(%this, %item)
 {
-   %this.addItem(%this.getItemCount(), %item);
+   return %this.addItem(%this.getItemCount(), %item);
 }
 
 function MenuBuilder::onAdd(%this)

+ 6 - 4
Templates/BaseGame/game/tools/settings.xml

@@ -41,7 +41,7 @@
             <Setting
                 name="doubleClickAction">Edit Asset</Setting>
             <Setting
-                name="LastPosExt">0 634 1560 360</Setting>
+                name="LastPosExt">0 1047 2200 360</Setting>
             <Setting
                 name="previewTileSize">1</Setting>
             <Setting
@@ -76,7 +76,7 @@
         <Group
             name="Grid">
             <Setting
-                name="forceSnapRotations">1</Setting>
+                name="forceSnapRotations">0</Setting>
             <Setting
                 name="gridColor">255 255 255 20</Setting>
             <Setting
@@ -210,7 +210,7 @@
         <Setting
             name="backgroundBuild">1</Setting>
         <Setting
-            name="SpawnClass">AIPlayer</Setting>
+            name="SpawnClass">Player</Setting>
         <Setting
             name="spawnDatablock">DefaultPlayerData</Setting>
     </Group>
@@ -276,7 +276,7 @@
     <Group
         name="TerrainEditor">
         <Setting
-            name="currentAction">lowerHeight</Setting>
+            name="currentAction">raiseHeight</Setting>
         <Group
             name="ActionValues">
             <Setting
@@ -367,6 +367,8 @@
             name="dropType">screenCenter</Setting>
         <Setting
             name="EditorLayoutMode">Modern</Setting>
+        <Setting
+            name="forceLoadDAE">0</Setting>
         <Setting
             name="forceSidebarToSide">1</Setting>
         <Setting

+ 24 - 11
Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript

@@ -555,7 +555,7 @@ function simGroup::onInspectPostApply(%this)
     %this.callOnChildren("setLocked",%this.locked);    
 }
 
-function simGroup::SelectFiteredObjects(%this, %min, %max)
+function simGroup::SelectFilteredObjects(%this, %min, %max)
 {
     EWorldEditor.clearSelection();
     %this.callOnChildren("filteredSelect", %min, %max );
@@ -577,16 +577,29 @@ function SceneObject::filteredSelect(%this, %min, %max)
 
 function simGroup::onInspect(%obj, %inspector)
 {
-   //Find the 'Editing' group in the inspector
-   %group = %inspector.findExistentGroup("Editing");
-   if(isObject(%group))
+   if(%obj.isInNamespaceHierarchy("SceneGroup") || %obj.isInNamespaceHierarchy("SubScene"))
    {
-      //We add a field of the type 'SimGroupSelectionButton'. This isn't a 'real' type, so when the inspector group tries to add it
-      //it will route down through GuiInspectorGroup(the namespace of %group) and call onConstructField in an attemp to see if there's any
-      //script defined functions that can build a field of that type.
-      //We happen to define the required 'build @ <fieldTypeName> @ Field()' function below, allowing us to build out the custom field type
-      %group.addField("Select Objects", "SimGroupSelectionButton", "Select filtered objects");
-    }
+      //Find the 'Editing' group in the inspector
+      %group = %inspector.findExistentGroup("Editing");
+      if(isObject(%group))
+      {
+         //We add a field of the type 'SimGroupSelectionButton'. This isn't a 'real' type, so when the inspector group tries to add it
+         //it will route down through GuiInspectorGroup(the namespace of %group) and call onConstructField in an attemp to see if there's any
+         //script defined functions that can build a field of that type.
+         //We happen to define the required 'build @ <fieldTypeName> @ Field()' function below, allowing us to build out the custom field type
+         %group.addField("Select Objects", "SimGroupSelectionButton", "Select filtered objects");
+       }
+   }
+}
+
+function scene::onInspect(%obj, %inspector)
+{
+    simGroup::onInspect(%obj, %inspector);
+}
+
+function subScene::onInspect(%obj, %inspector)
+{
+    simGroup::onInspect(%obj, %inspector);
 }
 
 function GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName, %fieldLabel, %fieldDesc,
@@ -687,7 +700,7 @@ function GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName,
          tooltipProfile = "EditorToolTipProfile";
          text = "Select";
          maxLength = "1024";
-         command = %ownerObj @ ".SelectFiteredObjects("@ %ownerObj.minSize @","@ %ownerObj.maxSize @");";
+         command = %ownerObj @ ".SelectFilteredObjects("@ %ownerObj @".getFieldValue(\"minSize\"),"@ %ownerObj @".getFieldValue(\"maxSize\"));";
       };
    };
    

+ 154 - 56
Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript

@@ -427,79 +427,148 @@ function EditorSaveMissionAs( %levelAsset )
 
 function EditorAutoSaveMission()
 {
-   // just save the mission without renaming it
+   //re-init the schedule
+   %autosaveInterval = EditorSettings.value("WorldEditor/AutosaveInterval", "5");
+   %autosaveInterval = %autosaveInterval * 60000; //convert to milliseconds from minutes
    
-   if($Editor::AutoSaveIndex $= "" || $Editor::AutoSaveIndex $= "5")
-      $Editor::AutoSaveIndex = 1;
-   else
-      $Editor::AutoSaveIndex++;
+   if(EditorGui.autosaveSchedule !$= "")
+      cancel(EditorGui.autosaveSchedule);
       
-   %autosaveFileName = "tools/autosave/" @ fileBase($Server::MissionFile) @ "_autosave" @ $Editor::AutoSaveIndex @ fileExt($Server::MissionFile);
+   EditorGui.autosaveSchedule = schedule( %autosaveInterval, 0, "EditorAutoSaveMission" );
    
-   // first check for dirty and read-only files:
-   if((EWorldEditor.isDirty || ETerrainEditor.isMissionDirty) && !isWriteableFileName(%autosaveFileName))
+   // first check for dirty
+   if(!EWorldEditor.isDirty && !ETerrainEditor.isMissionDirty)
    {
       return false;
    }
    
-   //TODO: Make Autosave work with terrains
-   /*if(ETerrainEditor.isDirty)
+   //Also skip out if we're actively performing an action
+   if(EditorGui.currentEditor.editorGui.isLeftMouseDown() || EditorGui.currentEditor.editorGui.isMiddleMouseDown() ||
+         EditorGui.currentEditor.editorGui.isRightMouseDown())
+      return false;
+      
+   %backupFilePathBase = "tools/autosave/";
+   if(!isObject(AssetBackupListArray))
    {
-      // Find all of the terrain files
-      initContainerTypeSearch($TypeMasks::TerrainObjectType);
-
-      while ((%terrainObject = containerSearchNext()) != 0)
+      new ArrayObject(AssetBackupListArray){};
+   }
+   
+   AssetBackupListArray.empty();
+   
+   //Next, we figure out what all we're planning to save
+   %terrainObjects = getRootScene().getObjectsByClass("TerrainBlock", false);
+   for(%i=0; %i < getWordCount(%terrainObjects); %i++)
+   {
+      %terrObj = getWord(%terrainObjects, %i);
+      %terrAssetId = %terrObj.terrainAsset;
+      
+      %sanitizedName = strReplace(%terrAssetId, ":", "_");
+      
+      %terrAssetBackupPath = %backupFilePathBase @ %sanitizedName @ "/";
+      
+      AssetBackupListArray.add(%terrAssetBackupPath, %terrAssetId SPC %terrObj);
+   }
+   
+   %subScenes = getRootScene().getObjectsByClass("SubScene", false);
+   for(%i=0; %i < getWordCount(%subScenes); %i++)
+   {
+      %subSceneObj = getWord(%subScenes, %i);
+      %subSceneAssetId = %subSceneObj.levelAsset;
+      
+      %sanitizedName = strReplace(%subSceneAssetId, ":", "_");
+      
+      %subSceneAssetBackupPath = %backupFilePathBase @ %sanitizedName @ "/";
+      
+      AssetBackupListArray.add(%subSceneAssetBackupPath, %subSceneAssetId SPC %subSceneObj);
+   }
+   
+   %levelAssetId = $Server::LevelAsset.getAssetId();
+   %levelSanitizedName = strReplace(%levelAssetId, ":", "_");
+   %levelAssetBackupPath = %backupFilePathBase @ %levelSanitizedName @ "/";
+   AssetBackupListArray.add(%levelAssetBackupPath, %levelAssetId SPC getRootScene());
+   
+   //Now we process through our assets to find index counts and save off a copy duplicate to the backup path
+   if($Editor::MaxAutosaves $= "")
+      $Editor::MaxAutosaves = 10;
+      
+   for(%i=0; %i < AssetBackupListArray.count(); %i++)
+   {
+      %path = AssetBackupListArray.getKey(%i);
+      %assetId = getWord(AssetBackupListArray.getValue(%i), 0);
+      %obj = getWord(AssetBackupListArray.getValue(%i), 1);
+      
+      %dirList = getDirectoryList(%path);
+      %savesCount = getFieldCount(%dirList);
+      %newestFolder = 0;
+      %oldestFolder = -1;
+      
+      if(%savesCount != 0)
       {
-         if (!isWriteableFileName(%terrainObject.terrainFile))
+         %saveArray = new ArrayObject(){};
+         //loop over the entries and find the oldest one
+         for(%f=0; %f < %savesCount; %f++)
          {
-            if (toolsMessageBox("Error", "Terrain file \""@ %terrainObject.terrainFile @ "\" is read-only.  Continue?", "Ok", "Stop") == $MROk)
-               continue;
-            else
-               return false;
+            %saveArray.add(getField(%dirList, %f), %f);            
          }
+         
+         %saveArray.sortk(true);
+         
+         %oldestFolder = %saveArray.getKey(0);
+         %newestFolder = %saveArray.getKey(%savesCount-1);
+         
+         %saveArray.delete();
       }
-   }*/
-  
-   // now write the terrain and mission files out:
 
-   if(EWorldEditor.isDirty || ETerrainEditor.isMissionDirty)
-      getScene(0).save(%autosaveFileName);
+      if(%savesCount >= $Editor::MaxAutosaves)
+      {
+         AssetBrowser.dirHandler.deleteFolder(%path @ %oldestFolder @ "/");
+      }
       
-   //TODO: Make Autosave work with terrains
-   /*if(ETerrainEditor.isDirty)
-   {
-      // Find all of the terrain files
-      initContainerTypeSearch($TypeMasks::TerrainObjectType);
-
-      while ((%terrainObject = containerSearchNext()) != 0)
+      %newSaveFolder = %newestFolder + 1;
+      %newSaveFolderPath = %path @ %newSaveFolder @ "/";
+      
+      if(!isDirectory(%newSaveFolderPath))
       {
-         if(%terrainObject.terrainAsset !$= "")
-         {
-            //we utilize a terrain asset, so we'll update our dependencies while we're at it
-            %terrainObject.saveAsset();
-         }
-         else
+         AssetBrowser.dirHandler.createFolder(%newSaveFolderPath);
+      }
+      else
+      {
+         error("EditorAutoSaveMission() - Somehow we indicated a brand new save folder, but it already exists? Stopping to avoid problems");
+         continue;
+      }
+      
+      %assetFilePath = AssetDatabase.getAssetFilePath(%assetId);
+      %assetFileName = fileName(%assetFilePath);
+      %assetPath = filePath(%assetFilePath) @ "/";
+      
+      if(!pathCopy(%assetFilePath, %newSaveFolderPath @ %assetFileName))
+      {
+         error("EditorAutoSaveMission() - failed to copy the asset file: " @ %assetFilePath @ " to backup directory!");
+         continue;
+      }
+      
+      //Do the actual copy of the files for backup purposes now
+      %looseFileCount = AssetDatabase.getAssetLooseFileCount(%assetId);
+      for(%lf = 0; %lf < %looseFileCount; %lf++)
+      {
+         %looseFile = AssetDatabase.getAssetLooseFile(%assetId, %lf);
+         %looseFileName = fileName(%looseFile);
+         
+         if(!isFile(%looseFile))
+            continue; //only bother with real files
+         
+         if(!pathCopy(%looseFile, %newSaveFolderPath @ %looseFileName))
          {
-            %terrainObject.save(%terrainObject.terrainFile);
+            error("EditorAutoSaveMission() - failed to copy the asset loose file: " @ %assetPath @ %looseFileName @ " to backup directory!");
          }
+         
+         %fileExt = fileExt(%looseFile);
+         if(%fileExt $= ".mis")
+            %obj.save(%newSaveFolderPath @ %looseFileName, false);
+         else if(%fileExt $= ".subMis" || %fileExt $= ".ter")
+            %obj.save(%newSaveFolderPath @ %looseFileName); //Save out the current status of it to the file so we have our actual snapshot
       }
    }
-
-   ETerrainPersistMan.saveDirty();*/
-      
-   // Give EditorPlugins a chance to save.
-   for ( %i = 0; %i < EditorPluginSet.getCount(); %i++ )
-   {
-      %obj = EditorPluginSet.getObject(%i);
-      if ( %obj.isDirty() )
-         %obj.onSaveMission( %autosaveFileName );      
-   } 
-   
-   %autosaveInterval = EditorSettings.value("WorldEditor/AutosaveInterval", "5");
-   %autosaveInterval = %autosaveInterval * 60000; //convert to milliseconds from minutes
-   EditorGui.autosaveSchedule = schedule( %autosaveInterval, 0, "EditorAutoSaveMission" );
-   
-   return true;
 }
 
 function EditorOpenMission(%levelAsset)
@@ -722,6 +791,14 @@ function EditorExplodePrefab()
 
 function makeSelectedAMesh(%assetId)
 {      
+   %selectedCount = EWorldEditor.getSelectionSize();
+   
+   if(!%selectedCount)
+   {
+      error("You need to select at least one object to turn it into a mesh!");
+      return;  
+   }
+   
    %assetDef = AssetDatabase.acquireAsset(%assetId);
    
    %assetPath = AssetDatabase.getAssetPath(%assetId);
@@ -740,6 +817,21 @@ function makeSelectedAMesh(%assetId)
    {      
       //Next, for safety purposes(and convenience!) we'll make them a prefab aping off the filepath/name provided
       //TODO: Make this an editor option
+      
+      //We want to figure out where this stuff goes, so scan through our selected objects
+      //And see if we have a common parent
+      %sameParent = true;
+      %firstParent = EWorldEditor.getSelectedObject(0).parentGroup;
+      for(%i=1; %i < %selectedCount; %i++)
+      {
+         %selectedObj = EWorldEditor.getSelectedObject(%i);
+         if(%firstParent != %selectedObj.parentGroup)
+         {
+            %sameParent = false;
+            break;
+         }
+      }
+      
       %prefabPath = %assetPath @ "/" @ %assetDef.AssetName @ ".prefab";
       EWorldEditor.makeSelectionPrefab(%prefabPath, false);
       %selectionPos = EWorldEditor.getSelectedObject(0).getPosition();
@@ -752,8 +844,14 @@ function makeSelectedAMesh(%assetId)
          shapeAsset = %assetId;
          position = %selectionPos;
       };
-      
-      getRootScene().add(%newStatic);
+
+      if(%sameParent)
+         %firstParent.add(%newStatic);
+      else
+         getRootScene().add(%newStatic);
+         
+      EWorldEditor.clearSelection();
+      EWorldEditor.selectObject(%newStatic);
    }
    
    EditorTree.buildVisibleTree( true );