From bb7ee38bf45d6d13a2c8052bbae22ebc7921d09b Mon Sep 17 00:00:00 2001 From: JeffR Date: Sun, 25 May 2025 07:40:10 -0500 Subject: [PATCH] - 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 --- Engine/source/T3D/Scene.cpp | 68 ++++- Engine/source/T3D/Scene.h | 4 +- Engine/source/T3D/SceneGroup.cpp | 59 ++++ Engine/source/T3D/SceneGroup.h | 1 + Engine/source/T3D/SubScene.cpp | 69 ++++- Engine/source/T3D/SubScene.h | 5 +- Engine/source/T3D/assets/assetImporter.cpp | 4 +- Engine/source/T3D/gameBase/gameBase.cpp | 8 + Engine/source/T3D/gameBase/gameBase.h | 2 + Engine/source/assets/assetManager.cpp | 63 ++++ Engine/source/assets/assetManager.h | 3 + .../assets/assetManager_ScriptBinding.h | 30 ++ Engine/source/forest/forestItem.h | 7 +- .../source/gui/buttons/guiIconButtonCtrl.cpp | 2 +- Engine/source/gui/core/guiControl.cpp | 17 ++ Engine/source/gui/utility/guiInputCtrl.cpp | 19 ++ Engine/source/gui/utility/guiInputCtrl.h | 2 + Engine/source/gui/worldEditor/editTSCtrl.cpp | 10 + Engine/source/gui/worldEditor/editTSCtrl.h | 2 + Engine/source/ts/assimp/assimpAppMesh.cpp | 3 + Engine/source/ts/loader/appMesh.cpp | 3 + .../scripts/server/levelLoad.tscript | 2 +- .../rendering/scripts/graphicsOptions.tscript | 283 ++++++++++-------- .../tools/assetBrowser/assetImportConfigs.xml | 4 +- .../scripts/assetTypes/level.tscript | 46 +++ .../scripts/assetTypes/subScene.tscript | 46 +++ .../scripts/assetTypes/terrain.tscript | 43 +++ .../assetBrowser/scripts/editAsset.tscript | 83 +++++ .../assetBrowser/scripts/popupMenus.tscript | 62 +++- .../scripts/menuBar/menuBuilder.ed.tscript | 4 +- Templates/BaseGame/game/tools/settings.xml | 10 +- .../scripts/editors/worldEditor.ed.tscript | 35 ++- .../scripts/menuHandlers.ed.tscript | 216 +++++++++---- 33 files changed, 978 insertions(+), 237 deletions(-) diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index f58179196..c76d0251c 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/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 Scene::getObjectsByClass(String className) +void Scene::getObjectsByClass(SimObject* object, StringTableEntry className, Vector* objectsList, bool checkSubscenes) { - return Vector(); + 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(object); + if (subScene && !checkSubscenes) + return; + + //If possible, now we iterate over the children + SimGroup* group = dynamic_cast(object); + if (group) + { + for (U32 c = 0; c < group->size(); c++) + { + SimObject* childObj = dynamic_cast(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* objectsList = new Vector(); + + 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), diff --git a/Engine/source/T3D/Scene.h b/Engine/source/T3D/Scene.h index 5be94e189..1d22d7251 100644 --- a/Engine/source/T3D/Scene.h +++ b/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 getObjectsByClass(String className); + void getObjectsByClass(SimObject* object, StringTableEntry className, Vector* objectsList, bool checkSubscenes = false); void getUtilizedAssetsFromSceneObject(SimObject* object, Vector* usedAssetsList); diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp index 28f6fefb8..c753b446a 100644 --- a/Engine/source/T3D/SceneGroup.cpp +++ b/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(*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(); +} diff --git a/Engine/source/T3D/SceneGroup.h b/Engine/source/T3D/SceneGroup.h index b89af1c9a..f9e4afeaa 100644 --- a/Engine/source/T3D/SceneGroup.h +++ b/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; diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 02a0d8a54..effda500f 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/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); } diff --git a/Engine/source/T3D/SubScene.h b/Engine/source/T3D/SubScene.h index cfc3606ad..5b7fcf1f5 100644 --- a/Engine/source/T3D/SubScene.h +++ b/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, ()); diff --git a/Engine/source/T3D/assets/assetImporter.cpp b/Engine/source/T3D/assets/assetImporter.cpp index 2e5d9cf32..eca044509 100644 --- a/Engine/source/T3D/assets/assetImporter.cpp +++ b/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"); diff --git a/Engine/source/T3D/gameBase/gameBase.cpp b/Engine/source/T3D/gameBase/gameBase.cpp index f16dafc06..9e5a770b7 100644 --- a/Engine/source/T3D/gameBase/gameBase.cpp +++ b/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) diff --git a/Engine/source/T3D/gameBase/gameBase.h b/Engine/source/T3D/gameBase/gameBase.h index c18b30196..cfdc10bf4 100644 --- a/Engine/source/T3D/gameBase/gameBase.h +++ b/Engine/source/T3D/gameBase/gameBase.h @@ -262,6 +262,8 @@ public: static void initPersistFields(); static void consoleInit(); + virtual void onInspect(GuiInspector*) override; + /// @} ///@name Datablock diff --git a/Engine/source/assets/assetManager.cpp b/Engine/source/assets/assetManager.cpp index 2cc3e5d76..d084babce 100644 --- a/Engine/source/assets/assetManager.cpp +++ b/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 ) diff --git a/Engine/source/assets/assetManager.h b/Engine/source/assets/assetManager.h index 9f243fc17..f87ac10ad 100644 --- a/Engine/source/assets/assetManager.h +++ b/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 ); diff --git a/Engine/source/assets/assetManager_ScriptBinding.h b/Engine/source/assets/assetManager_ScriptBinding.h index 9e675ed52..806ec2aeb 100644 --- a/Engine/source/assets/assetManager_ScriptBinding.h +++ b/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") diff --git a/Engine/source/forest/forestItem.h b/Engine/source/forest/forestItem.h index 64e6eb611..28d9f4248 100644 --- a/Engine/source/forest/forestItem.h +++ b/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(); diff --git a/Engine/source/gui/buttons/guiIconButtonCtrl.cpp b/Engine/source/gui/buttons/guiIconButtonCtrl.cpp index 9d0093802..2eff0c0ab 100644 --- a/Engine/source/gui/buttons/guiIconButtonCtrl.cpp +++ b/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); diff --git a/Engine/source/gui/core/guiControl.cpp b/Engine/source/gui/core/guiControl.cpp index 5cd499c29..bc175e9fb 100644 --- a/Engine/source/gui/core/guiControl.cpp +++ b/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." ) diff --git a/Engine/source/gui/utility/guiInputCtrl.cpp b/Engine/source/gui/utility/guiInputCtrl.cpp index 31d2a2e86..830e6746b 100644 --- a/Engine/source/gui/utility/guiInputCtrl.cpp +++ b/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 ) diff --git a/Engine/source/gui/utility/guiInputCtrl.h b/Engine/source/gui/utility/guiInputCtrl.h index 62de1ea25..7ddedd780 100644 --- a/Engine/source/gui/utility/guiInputCtrl.h +++ b/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(); diff --git a/Engine/source/gui/worldEditor/editTSCtrl.cpp b/Engine/source/gui/worldEditor/editTSCtrl.cpp index d3c0fc5ef..547546914 100644 --- a/Engine/source/gui/worldEditor/editTSCtrl.cpp +++ b/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(); +} diff --git a/Engine/source/gui/worldEditor/editTSCtrl.h b/Engine/source/gui/worldEditor/editTSCtrl.h index ca526f78d..c235713b8 100644 --- a/Engine/source/gui/worldEditor/editTSCtrl.h +++ b/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; diff --git a/Engine/source/ts/assimp/assimpAppMesh.cpp b/Engine/source/ts/assimp/assimpAppMesh.cpp index 0eb015473..9f7ac8860 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.cpp +++ b/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()) { diff --git a/Engine/source/ts/loader/appMesh.cpp b/Engine/source/ts/loader/appMesh.cpp index a51b91002..704c3bf10 100644 --- a/Engine/source/ts/loader/appMesh.cpp +++ b/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()) { diff --git a/Templates/BaseGame/game/core/clientServer/scripts/server/levelLoad.tscript b/Templates/BaseGame/game/core/clientServer/scripts/server/levelLoad.tscript index e62dc2905..11769e32a 100644 --- a/Templates/BaseGame/game/core/clientServer/scripts/server/levelLoad.tscript +++ b/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); } diff --git a/Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript b/Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript index 450be2512..0fee38d62 100644 --- a/Templates/BaseGame/game/core/rendering/scripts/graphicsOptions.tscript +++ b/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"); + } } } diff --git a/Templates/BaseGame/game/tools/assetBrowser/assetImportConfigs.xml b/Templates/BaseGame/game/tools/assetBrowser/assetImportConfigs.xml index d5f93482a..4bb2ddf87 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/assetImportConfigs.xml +++ b/Templates/BaseGame/game/tools/assetBrowser/assetImportConfigs.xml @@ -35,9 +35,7 @@ 0 AutoPrune - FolderPrefix + name="DuplicateAutoResolution">AutoPrune 1 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!"); + } } \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript index b89365f22..ad34a83f5 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript +++ b/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(); + } + } +} diff --git a/Templates/BaseGame/game/tools/editorCore/scripts/menuBar/menuBuilder.ed.tscript b/Templates/BaseGame/game/tools/editorCore/scripts/menuBar/menuBuilder.ed.tscript index ce563a76e..ee48fd0d7 100644 --- a/Templates/BaseGame/game/tools/editorCore/scripts/menuBar/menuBuilder.ed.tscript +++ b/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) diff --git a/Templates/BaseGame/game/tools/settings.xml b/Templates/BaseGame/game/tools/settings.xml index 4759fc701..03b481b02 100644 --- a/Templates/BaseGame/game/tools/settings.xml +++ b/Templates/BaseGame/game/tools/settings.xml @@ -41,7 +41,7 @@ Edit Asset 0 634 1560 360 + name="LastPosExt">0 1047 2200 360 1 1 + name="forceSnapRotations">0 255 255 255 20 1 AIPlayer + name="SpawnClass">Player DefaultPlayerData @@ -276,7 +276,7 @@ lowerHeight + name="currentAction">raiseHeight screenCenter Modern + 0 1 @ 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 @ @ 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\"));"; }; }; diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript index f5cfe67ca..f059e40b1 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript +++ b/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) - { - // Find all of the terrain files - initContainerTypeSearch($TypeMasks::TerrainObjectType); - - while ((%terrainObject = containerSearchNext()) != 0) - { - if (!isWriteableFileName(%terrainObject.terrainFile)) - { - if (toolsMessageBox("Error", "Terrain file \""@ %terrainObject.terrainFile @ "\" is read-only. Continue?", "Ok", "Stop") == $MROk) - continue; - else - return false; - } - } - }*/ - - // now write the terrain and mission files out: - - if(EWorldEditor.isDirty || ETerrainEditor.isMissionDirty) - getScene(0).save(%autosaveFileName); + //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; - //TODO: Make Autosave work with terrains - /*if(ETerrainEditor.isDirty) + %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(%terrainObject.terrainAsset !$= "") + %saveArray = new ArrayObject(){}; + //loop over the entries and find the oldest one + for(%f=0; %f < %savesCount; %f++) { - //we utilize a terrain asset, so we'll update our dependencies while we're at it - %terrainObject.saveAsset(); + %saveArray.add(getField(%dirList, %f), %f); } - else + + %saveArray.sortk(true); + + %oldestFolder = %saveArray.getKey(0); + %newestFolder = %saveArray.getKey(%savesCount-1); + + %saveArray.delete(); + } + + if(%savesCount >= $Editor::MaxAutosaves) + { + AssetBrowser.dirHandler.deleteFolder(%path @ %oldestFolder @ "/"); + } + + %newSaveFolder = %newestFolder + 1; + %newSaveFolderPath = %path @ %newSaveFolder @ "/"; + + if(!isDirectory(%newSaveFolderPath)) + { + 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 );