Browse Source

- 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 months ago
parent
commit
bb7ee38bf4
33 changed files with 975 additions and 234 deletions
  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];
       return query->mAssetList[0];
 }
 }
 
 
-bool Scene::saveScene(StringTableEntry fileName)
+bool Scene::saveScene(StringTableEntry fileName, const bool& saveSubScenes)
 {
 {
    if (!isServerObject())
    if (!isServerObject())
       return false;
       return false;
@@ -316,9 +316,12 @@ bool Scene::saveScene(StringTableEntry fileName)
 
 
    //Inform our subscenes we're saving so they can do any
    //Inform our subscenes we're saving so they can do any
    //special work required as well
    //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);
    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)
 void Scene::loadAtPosition(const Point3F& position)
@@ -460,15 +484,37 @@ DefineEngineMethod(Scene, removeDynamicObject, void, (SceneObject* sceneObj), (n
    object->removeDynamicObject(sceneObj);
    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"
    "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)
    if (className == String::EmptyString)
       return "";
       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, (), ,
 DefineEngineMethod(Scene, dumpUtilizedAssets, void, (), ,
@@ -492,12 +538,12 @@ DefineEngineMethod(Scene, getLevelAsset, const char*, (), ,
    return object->getLevelAsset();
    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"
    "Save out the object to the given file.\n"
    "@param fileName The name of the file to save to."
    "@param fileName The name of the file to save to."
    "@param True on success, false on failure.")
    "@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),
 DefineEngineMethod(Scene, loadAtPosition, void, (Point3F position), (Point3F::Zero),

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

@@ -78,7 +78,7 @@ public:
    StringTableEntry getOriginatingFile();
    StringTableEntry getOriginatingFile();
    StringTableEntry getLevelAsset();
    StringTableEntry getLevelAsset();
 
 
-   bool saveScene(StringTableEntry fileName);
+   bool saveScene(StringTableEntry fileName, const bool& saveSubScenes = true);
 
 
    //
    //
    //Networking
    //Networking
@@ -86,7 +86,7 @@ public:
    void unpackUpdate(NetConnection *conn, BitStream *stream) override;
    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);
    void getUtilizedAssetsFromSceneObject(SimObject* object, Vector<StringTableEntry>* usedAssetsList);
 
 

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

@@ -9,6 +9,7 @@
 #include "physics/physicsShape.h"
 #include "physics/physicsShape.h"
 #include "renderInstance/renderPassManager.h"
 #include "renderInstance/renderPassManager.h"
 #include "scene/sceneRenderState.h"
 #include "scene/sceneRenderState.h"
+#include "Scene.h"
 
 
 IMPLEMENT_CO_NETOBJECT_V1(SceneGroup);
 IMPLEMENT_CO_NETOBJECT_V1(SceneGroup);
 
 
@@ -156,6 +157,37 @@ void SceneGroup::onInspect(GuiInspector* inspector)
    regenButton->setConsoleCommand(rgBuffer);
    regenButton->setConsoleCommand(rgBuffer);
 
 
    regenFieldGui->addObject(regenButton);
    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
 #endif
 }
 }
 
 
@@ -279,6 +311,27 @@ void SceneGroup::recalculateBoundingBox()
    setMaskBits(TransformMask);
    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 SceneGroup::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
 {
 {
    U32 retMask = Parent::packUpdate(conn, mask, stream);
    U32 retMask = Parent::packUpdate(conn, mask, stream);
@@ -363,3 +416,9 @@ DefineEngineMethod(SceneGroup, recalculateBounds, void, (), ,
 {
 {
    object->recalculateBoundingBox();
    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 addObject(SimObject* object) override;
    void removeObject(SimObject* object) override;
    void removeObject(SimObject* object) override;
    void recalculateBoundingBox();
    void recalculateBoundingBox();
+   void reparentOOBObjects();
 
 
    ///
    ///
    bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) override;
    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/gfxDrawUtil.h"
 #include "gfx/gfxTransformSaver.h"
 #include "gfx/gfxTransformSaver.h"
 #include "gui/editor/inspector/group.h"
 #include "gui/editor/inspector/group.h"
+#include "gui/worldEditor/editor.h"
+#include "math/mathIO.h"
 #include "T3D/gameBase/gameBase.h"
 #include "T3D/gameBase/gameBase.h"
 
 
 bool SubScene::smTransformChildren = false;
 bool SubScene::smTransformChildren = false;
@@ -32,7 +34,9 @@ SubScene::SubScene() :
    mTickPeriodMS(1000),
    mTickPeriodMS(1000),
    mCurrTick(0),
    mCurrTick(0),
    mGlobalLayer(false),
    mGlobalLayer(false),
-   mSaving(false)
+   mSaving(false),
+   mUseSeparateLoadBounds(false),
+   mLoadBounds(Point3F::One)
 {
 {
    mNetFlags.set(Ghostable | ScopeAlways);
    mNetFlags.set(Ghostable | ScopeAlways);
 
 
@@ -70,6 +74,8 @@ void SubScene::initPersistFields()
    INITPERSISTFIELD_SUBSCENEASSET(SubScene, SubScene, "The subscene asset to load.");
    INITPERSISTFIELD_SUBSCENEASSET(SubScene, SubScene, "The subscene asset to load.");
    addField("tickPeriodMS", TypeS32, Offset(mTickPeriodMS, SubScene), "evaluation rate (ms)");
    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("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");
    endGroup("SubScene");
 
 
    addGroup("LoadingManagement");
    addGroup("LoadingManagement");
@@ -113,6 +119,11 @@ U32 SubScene::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
    U32 retMask = Parent::packUpdate(conn, mask, stream);
    U32 retMask = Parent::packUpdate(conn, mask, stream);
 
 
    stream->writeFlag(mGlobalLayer);
    stream->writeFlag(mGlobalLayer);
+   if(stream->writeFlag(mUseSeparateLoadBounds))
+   {
+      mathWrite(*stream, mLoadBounds);
+   }
+
 
 
    return retMask;
    return retMask;
 }
 }
@@ -123,6 +134,11 @@ void SubScene::unpackUpdate(NetConnection* conn, BitStream* stream)
 
 
    mGlobalLayer = stream->readFlag();
    mGlobalLayer = stream->readFlag();
 
 
+   mUseSeparateLoadBounds = stream->readFlag();
+   if(mUseSeparateLoadBounds)
+   {
+      mathRead(*stream, &mLoadBounds);
+   }
 }
 }
 
 
 void SubScene::onInspect(GuiInspector* inspector)
 void SubScene::onInspect(GuiInspector* inspector)
@@ -220,7 +236,21 @@ bool SubScene::testBox(const Box3F& testBox)
    bool passes = mGlobalLayer;
    bool passes = mGlobalLayer;
 
 
    if (!passes)
    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)
    if (passes)
       passes = evaluateCondition();
       passes = evaluateCondition();
@@ -268,6 +298,9 @@ void SubScene::processTick(const Move* move)
 
 
 void SubScene::_onFileChanged(const Torque::Path& path)
 void SubScene::_onFileChanged(const Torque::Path& path)
 {
 {
+   if (gEditingMission)
+      return;
+
    if(mSubSceneAsset.isNull() || Torque::Path(mSubSceneAsset->getLevelPath()) != path)
    if(mSubSceneAsset.isNull() || Torque::Path(mSubSceneAsset->getLevelPath()) != path)
       return;
       return;
 
 
@@ -426,7 +459,7 @@ void SubScene::unload()
 
 
 }
 }
 
 
-bool SubScene::save()
+bool SubScene::save(const String& filename)
 {
 {
    if (!isServerObject())
    if (!isServerObject())
       return false;
       return false;
@@ -451,6 +484,9 @@ bool SubScene::save()
 
 
    StringTableEntry levelPath = mSubSceneAsset->getLevelPath();
    StringTableEntry levelPath = mSubSceneAsset->getLevelPath();
 
 
+   if (filename.isNotEmpty())
+      levelPath = StringTable->insert(filename.c_str());
+
    FileStream fs;
    FileStream fs;
    fs.open(levelPath, Torque::FS::File::Write);
    fs.open(levelPath, Torque::FS::File::Write);
    fs.close();
    fs.close();
@@ -547,8 +583,26 @@ void SubScene::renderObject(ObjectRenderInst* ri,
    //Box3F scale = getScale()
    //Box3F scale = getScale()
    //Box3F bounds = Box3F(-m)
    //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();
    Point3F scale = getScale();
-   Box3F bounds = Box3F(-scale/2, scale/2);
+   Box3F bounds = Box3F(-scale / 2, scale / 2);
 
 
    ColorI boundsColor = ColorI(135, 206, 235, 50);
    ColorI boundsColor = ColorI(135, 206, 235, 50);
 
 
@@ -565,10 +619,11 @@ void SubScene::renderObject(ObjectRenderInst* ri,
    drawer->drawCube(desc, bounds, ColorI::BLACK);
    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 mGlobalLayer;
 
 
+   bool mUseSeparateLoadBounds;
+   Point3F mLoadBounds;
+
 public:
 public:
    SubScene();
    SubScene();
    virtual ~SubScene();
    virtual ~SubScene();
@@ -118,7 +121,7 @@ public:
       return mStartUnloadTimerMS;
       return mStartUnloadTimerMS;
    }
    }
 
 
-   bool save();
+   bool save(const String& filename = String());
 
 
    DECLARE_CALLBACK(void, onLoaded, ());
    DECLARE_CALLBACK(void, onLoaded, ());
    DECLARE_CALLBACK(void, onUnloaded, ());
    DECLARE_CALLBACK(void, onUnloaded, ());

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

@@ -78,7 +78,7 @@ AssetImportConfig::AssetImportConfig() :
    SeparateAnimations(false),
    SeparateAnimations(false),
    SeparateAnimationPrefix(""),
    SeparateAnimationPrefix(""),
    animTiming("FrameCount"),
    animTiming("FrameCount"),
-   animFPS(false),
+   animFPS(30),
    AlwaysAddShapeAnimationSuffix(true),
    AlwaysAddShapeAnimationSuffix(true),
    AddedShapeAnimationSuffix("_anim"),
    AddedShapeAnimationSuffix("_anim"),
    GenerateCollisions(false),
    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("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("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("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("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");
       addField("AddedShapeAnimationSuffix", TypeString, Offset(AddedShapeAnimationSuffix, AssetImportConfig), " If AlwaysAddShapeAnimationSuffix is on, this is the suffix to be added");
    endGroup("Animation");
    endGroup("Animation");

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

@@ -349,6 +349,14 @@ void GameBase::inspectPostApply()
    setMaskBits(ExtendedInfoMask);
    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)
 void GameBase::processTick(const Move * move)

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

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

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

@@ -2435,6 +2435,69 @@ AssetManager::typeAssetDependsOnHash* AssetManager::getDependedOnAssets()
    // Find any asset dependencies.
    // Find any asset dependencies.
    return &mAssetDependsOn;
    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 )
 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();
     typeAssetDependsOnHash* getDependedOnAssets();
 
 
+    S32 getAssetLooseFileCount(const char* pAssetId);
+    const char* getAssetLooseFile(const char* pAssetId, const S32& index);
+
     /// Declare Console Object.
     /// Declare Console Object.
     DECLARE_CONOBJECT( AssetManager );
     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, (),,
 DefineEngineMethod(AssetManager, getDeclaredAssetCount, bool, (),,
    "Gets the number of declared assets.\n"
    "Gets the number of declared assets.\n"
    "@return Returns 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
    /// Called from Forest the first time a datablock is used
    /// in order to lazy load content.
    /// 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;
          return;
 
 
       _preload();
       _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 );
          Point2I start( mTextMargin, ( getHeight() - mProfile->mFont->getHeight() ) / 2 );
          if (mBitmapAsset.notNull() && mIconLocation != IconLocNone)
          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);
          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, (),,
 DefineEngineMethod( GuiControl, getGlobalPosition, Point2I, (),,
    "Get the position of the control relative to the root of the GuiControl hierarchy it is contained in.\n"
    "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." )
    "@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();
    clearFirstResponder();
 }
 }
 
 
+void GuiInputCtrl::setActive(bool value)
+{
+   Parent::setActive(value);
+
+   if (value)
+   {
+      if (!smDesignTime && !mIgnoreMouseEvents)
+         mouseLock();
+
+      setFirstResponder();
+   }
+   else
+   {
+      mouseUnlock();
+      clearFirstResponder();
+   }
+
+}
+
 
 
 //------------------------------------------------------------------------------
 //------------------------------------------------------------------------------
 static bool isModifierKey( U16 keyCode )
 static bool isModifierKey( U16 keyCode )

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

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

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

@@ -1409,3 +1409,13 @@ DefineEngineMethod( EditTSCtrl, isMiddleMouseDown, bool, (),, "" )
 {
 {
    return object->isMiddleMouseDown();
    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 on3DMouseWheelDown(const Gui3DMouseEvent &){};
       virtual void get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &);
       virtual void get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &);
 
 
+      virtual bool isLeftMouseDown() { return mLeftMouseDown; }
       virtual bool isMiddleMouseDown() {return mMiddleMouseDown;}
       virtual bool isMiddleMouseDown() {return mMiddleMouseDown;}
+      virtual bool isRightMouseDown() { return mLeftMouseDown; }
 
 
       bool resize(const Point2I& newPosition, const Point2I& newExtent) override;
       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()
 TSMesh* AssimpAppMesh::constructTSMesh()
 {
 {
+   if (points.empty() || normals.empty() || primitives.empty() || indices.empty())
+      return NULL;
+
    TSMesh* tsmesh;
    TSMesh* tsmesh;
    if (isSkin())
    if (isSkin())
    {
    {

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

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

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

@@ -156,7 +156,7 @@ function loadMissionStage3()
 function GameBase::onCreateGame(%this)
 function GameBase::onCreateGame(%this)
 {
 {
     %db = %this.getDatablock();
     %db = %this.getDatablock();
-    if (%db.isMethod("onCreateGame"))
+    if (isObject(%db) && %db.isMethod("onCreateGame"))
     {
     {
         %db.onCreateGame(%this);
         %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;
    %intel = ( strstr( strupr( getDisplayDeviceInformation() ), "INTEL" ) != -1 ) ? true : false;
    %videoMem = GFXCardProfilerAPI::getVideoMemoryMB();
    %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
    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
       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
             <Setting
                 name="AutomaticallyPromptMissingFiles">0</Setting>
                 name="AutomaticallyPromptMissingFiles">0</Setting>
             <Setting
             <Setting
-                name="DuplicatAutoResolution">AutoPrune</Setting>
-            <Setting
-                name="DuplicateAutoResolution">FolderPrefix</Setting>
+                name="DuplicateAutoResolution">AutoPrune</Setting>
             <Setting
             <Setting
                 name="PreventImportWithErrors">1</Setting>
                 name="PreventImportWithErrors">1</Setting>
             <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);
    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)
 function LevelAsset::buildBrowserElement(%this, %previewData)
 {
 {
    %previewData.assetName = %this.assetName;
    %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;  
 	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)
 function SubSceneAsset::buildBrowserElement(%this, %previewData)
 {
 {
    %previewData.assetName = %this.assetName;
    %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)
 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);
          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 ) )
    if( !isObject( EditLevelAssetPopup ) )
    {
    {
       new PopupMenu( EditLevelAssetPopup )
       new PopupMenu( EditLevelAssetPopup )
@@ -59,9 +71,11 @@ function AssetBrowser::buildPopupMenus(%this)
          item[ 5 ] = "-";
          item[ 5 ] = "-";
          Item[ 6 ] = "Duplicate Asset" TAB "" TAB "AssetBrowser.duplicateAsset();";
          Item[ 6 ] = "Duplicate Asset" TAB "" TAB "AssetBrowser.duplicateAsset();";
          item[ 7 ] = "-";
          item[ 7 ] = "-";
-         item[ 8 ] = "Open File Location" TAB "" TAB "AssetBrowser.openFileLocation();";
+         Item[ 8 ] = "Restore Backup" TAB RestoreBackupListPopup;
          item[ 9 ] = "-";
          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 = "";
          jumpFileName = "";
          jumpLineNumber = "";
          jumpLineNumber = "";
@@ -82,9 +96,11 @@ function AssetBrowser::buildPopupMenus(%this)
          item[ 3 ] = "-";
          item[ 3 ] = "-";
          Item[ 4 ] = "Duplicate Asset" TAB "" TAB "AssetBrowser.duplicateAsset();";
          Item[ 4 ] = "Duplicate Asset" TAB "" TAB "AssetBrowser.duplicateAsset();";
          item[ 5 ] = "-";
          item[ 5 ] = "-";
-         item[ 6 ] = "Open File Location" TAB "" TAB "AssetBrowser.openFileLocation();";
+         Item[ 6 ] = "Restore Backup" TAB RestoreBackupListPopup;
          item[ 7 ] = "-";
          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 = "";
          jumpFileName = "";
          jumpLineNumber = "";
          jumpLineNumber = "";
@@ -398,3 +414,41 @@ function AddNewScriptAssetPopupMenu::setupDefaultState(%this)
 function AddNewScriptAssetPopupMenu::setupGuiControls(%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);
       %this.insertItem(%pos, %name !$= "-" ? %name : "", %accel, %cmd, %bitmapIdx $= "" ? -1 : %bitmapIdx);
    }
    }
+   
+   return %pos;
 }
 }
 
 
 function MenuBuilder::appendItem(%this, %item)
 function MenuBuilder::appendItem(%this, %item)
 {
 {
-   %this.addItem(%this.getItemCount(), %item);
+   return %this.addItem(%this.getItemCount(), %item);
 }
 }
 
 
 function MenuBuilder::onAdd(%this)
 function MenuBuilder::onAdd(%this)

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

@@ -41,7 +41,7 @@
             <Setting
             <Setting
                 name="doubleClickAction">Edit Asset</Setting>
                 name="doubleClickAction">Edit Asset</Setting>
             <Setting
             <Setting
-                name="LastPosExt">0 634 1560 360</Setting>
+                name="LastPosExt">0 1047 2200 360</Setting>
             <Setting
             <Setting
                 name="previewTileSize">1</Setting>
                 name="previewTileSize">1</Setting>
             <Setting
             <Setting
@@ -76,7 +76,7 @@
         <Group
         <Group
             name="Grid">
             name="Grid">
             <Setting
             <Setting
-                name="forceSnapRotations">1</Setting>
+                name="forceSnapRotations">0</Setting>
             <Setting
             <Setting
                 name="gridColor">255 255 255 20</Setting>
                 name="gridColor">255 255 255 20</Setting>
             <Setting
             <Setting
@@ -210,7 +210,7 @@
         <Setting
         <Setting
             name="backgroundBuild">1</Setting>
             name="backgroundBuild">1</Setting>
         <Setting
         <Setting
-            name="SpawnClass">AIPlayer</Setting>
+            name="SpawnClass">Player</Setting>
         <Setting
         <Setting
             name="spawnDatablock">DefaultPlayerData</Setting>
             name="spawnDatablock">DefaultPlayerData</Setting>
     </Group>
     </Group>
@@ -276,7 +276,7 @@
     <Group
     <Group
         name="TerrainEditor">
         name="TerrainEditor">
         <Setting
         <Setting
-            name="currentAction">lowerHeight</Setting>
+            name="currentAction">raiseHeight</Setting>
         <Group
         <Group
             name="ActionValues">
             name="ActionValues">
             <Setting
             <Setting
@@ -367,6 +367,8 @@
             name="dropType">screenCenter</Setting>
             name="dropType">screenCenter</Setting>
         <Setting
         <Setting
             name="EditorLayoutMode">Modern</Setting>
             name="EditorLayoutMode">Modern</Setting>
+        <Setting
+            name="forceLoadDAE">0</Setting>
         <Setting
         <Setting
             name="forceSidebarToSide">1</Setting>
             name="forceSidebarToSide">1</Setting>
         <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);    
     %this.callOnChildren("setLocked",%this.locked);    
 }
 }
 
 
-function simGroup::SelectFiteredObjects(%this, %min, %max)
+function simGroup::SelectFilteredObjects(%this, %min, %max)
 {
 {
     EWorldEditor.clearSelection();
     EWorldEditor.clearSelection();
     %this.callOnChildren("filteredSelect", %min, %max );
     %this.callOnChildren("filteredSelect", %min, %max );
@@ -577,16 +577,29 @@ function SceneObject::filteredSelect(%this, %min, %max)
 
 
 function simGroup::onInspect(%obj, %inspector)
 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,
 function GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName, %fieldLabel, %fieldDesc,
@@ -687,7 +700,7 @@ function GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName,
          tooltipProfile = "EditorToolTipProfile";
          tooltipProfile = "EditorToolTipProfile";
          text = "Select";
          text = "Select";
          maxLength = "1024";
          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()
 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;
       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)
 function EditorOpenMission(%levelAsset)
@@ -722,6 +791,14 @@ function EditorExplodePrefab()
 
 
 function makeSelectedAMesh(%assetId)
 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);
    %assetDef = AssetDatabase.acquireAsset(%assetId);
    
    
    %assetPath = AssetDatabase.getAssetPath(%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
       //Next, for safety purposes(and convenience!) we'll make them a prefab aping off the filepath/name provided
       //TODO: Make this an editor option
       //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";
       %prefabPath = %assetPath @ "/" @ %assetDef.AssetName @ ".prefab";
       EWorldEditor.makeSelectionPrefab(%prefabPath, false);
       EWorldEditor.makeSelectionPrefab(%prefabPath, false);
       %selectionPos = EWorldEditor.getSelectedObject(0).getPosition();
       %selectionPos = EWorldEditor.getSelectedObject(0).getPosition();
@@ -752,8 +844,14 @@ function makeSelectedAMesh(%assetId)
          shapeAsset = %assetId;
          shapeAsset = %assetId;
          position = %selectionPos;
          position = %selectionPos;
       };
       };
-      
-      getRootScene().add(%newStatic);
+
+      if(%sameParent)
+         %firstParent.add(%newStatic);
+      else
+         getRootScene().add(%newStatic);
+         
+      EWorldEditor.clearSelection();
+      EWorldEditor.selectObject(%newStatic);
    }
    }
    
    
    EditorTree.buildVisibleTree( true );  
    EditorTree.buildVisibleTree( true );