diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index 2a5a20d6c..7fb53273a 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/Engine/source/T3D/Scene.cpp @@ -1,20 +1,26 @@ #include "Scene.h" #include "T3D/assets/LevelAsset.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/gameMode.h" Scene * Scene::smRootScene = nullptr; Vector Scene::smSceneList; +IMPLEMENT_CALLBACK(Scene, onSaving, void, (const char* fileName), (fileName), + "@brief Called when a scene is saved to allow scenes to special-handle prepwork for saving if required.\n\n" + + "@param fileName The level file being saved\n"); + IMPLEMENT_CO_NETOBJECT_V1(Scene); Scene::Scene() : - mIsSubScene(false), mParentScene(nullptr), mSceneId(-1), mIsEditing(false), mIsDirty(false), mEditPostFX(0) { - mGameModeName = StringTable->EmptyString(); + mGameModesNames = StringTable->EmptyString(); } Scene::~Scene() @@ -28,13 +34,12 @@ void Scene::initPersistFields() Parent::initPersistFields(); addGroup("Internal"); - addField("isSubscene", TypeBool, Offset(mIsSubScene, Scene), "", AbstractClassRep::FIELD_HideInInspectors); addField("isEditing", TypeBool, Offset(mIsEditing, Scene), "", AbstractClassRep::FIELD_HideInInspectors); addField("isDirty", TypeBool, Offset(mIsDirty, Scene), "", AbstractClassRep::FIELD_HideInInspectors); endGroup("Internal"); addGroup("Gameplay"); - addField("gameModeName", TypeString, Offset(mGameModeName, Scene), "The name of the gamemode that this scene utilizes"); + addField("gameModes", TypeGameModeList, Offset(mGameModesNames, Scene), "The game modes that this Scene is associated with."); endGroup("Gameplay"); addGroup("PostFX"); @@ -51,48 +56,33 @@ bool Scene::onAdd() smSceneList.push_back(this); mSceneId = smSceneList.size() - 1; - /*if (smRootScene == nullptr) - { - //we're the first scene, so we're the root. woo! - smRootScene = this; - } - else - { - mIsSubScene = true; - smRootScene->mSubScenes.push_back(this); - }*/ + GameMode::findGameModes(mGameModesNames, &mGameModesList); return true; } void Scene::onRemove() { + for (U32 i = 0; i < mGameModesList.size(); i++) + { + mGameModesList[i]->onSceneUnloaded_callback(); + } + Parent::onRemove(); smSceneList.remove(this); mSceneId = -1; - - /*if (smRootScene == this) - { - for (U32 i = 0; i < mSubScenes.size(); i++) - { - mSubScenes[i]->deleteObject(); - } - } - else if (smRootScene != nullptr) - { - for (U32 i = 0; i < mSubScenes.size(); i++) - { - if(mSubScenes[i]->getId() == getId()) - smRootScene->mSubScenes.erase(i); - } - }*/ } void Scene::onPostAdd() { if (isMethod("onPostAdd")) Con::executef(this, "onPostAdd"); + + for (U32 i = 0; i < mGameModesList.size(); i++) + { + mGameModesList[i]->onSceneLoaded_callback(); + } } bool Scene::_editPostEffects(void* object, const char* index, const char* data) @@ -110,11 +100,12 @@ bool Scene::_editPostEffects(void* object, const char* index, const char* data) void Scene::addObject(SimObject* object) { //Child scene - Scene* scene = dynamic_cast(object); + SubScene* scene = dynamic_cast(object); if (scene) { //We'll keep these principly separate so they don't get saved into each other mSubScenes.push_back(scene); + Parent::addObject(object); return; } @@ -135,7 +126,7 @@ void Scene::addObject(SimObject* object) void Scene::removeObject(SimObject* object) { //Child scene - Scene* scene = dynamic_cast(object); + SubScene* scene = dynamic_cast(object); if (scene) { //We'll keep these principly separate so they don't get saved into each other @@ -157,30 +148,88 @@ void Scene::removeObject(SimObject* object) Parent::removeObject(object); } -void Scene::addDynamicObject(SceneObject* object) +void Scene::addDynamicObject(SimObject* object) { mDynamicObjects.push_back(object); + SimGroup* cleanupGroup; + if(Sim::findObject("MissionCleanup", cleanupGroup)) + { + cleanupGroup->addObject(object); + } + //Do it like regular, though we should probably bail if we're trying to add non-scene objects to the scene? - Parent::addObject(object); + //Parent::addObject(object); } -void Scene::removeDynamicObject(SceneObject* object) +void Scene::removeDynamicObject(SimObject* object) { mDynamicObjects.remove(object); + SimGroup* cleanupGroup; + if (Sim::findObject("MissionCleanup", cleanupGroup)) + { + cleanupGroup->removeObject(object); + } + //Do it like regular, though we should probably bail if we're trying to add non-scene objects to the scene? - Parent::removeObject(object); + //Parent::removeObject(object); } void Scene::interpolateTick(F32 delta) { - } void Scene::processTick() { + if (!isServerObject()) + return; + //iterate over our subscenes to update their status of loaded or unloaded based on if any control objects intersect their bounds + for (U32 i = 0; i < mSubScenes.size(); i++) + { + bool hasClients = false; + + SimGroup* pClientGroup = Sim::getClientGroup(); + for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) + { + GameConnection* gc = dynamic_cast(*itr); + if (gc) + { + GameBase* controlObj = gc->getControlObject(); + if (controlObj == nullptr) + { + controlObj = gc->getCameraObject(); + } + + if (controlObj != nullptr) + { + if (mSubScenes[i]->testBox(controlObj->getWorldBox())) + { + //we have a client controlling object in the bounds, so we ensure the contents are loaded + hasClients = true; + break; + } + } + } + } + + if (hasClients) + { + mSubScenes[i]->setUnloadTimeMS(-1); + mSubScenes[i]->load(); + } + else + { + if (mSubScenes[i]->isLoaded() && mSubScenes[i]->getUnloadTimsMS() == -1) + { + mSubScenes[i]->setUnloadTimeMS(Sim::getCurrentTime()); + } + + if (Sim::getCurrentTime() - mSubScenes[i]->getUnloadTimsMS() > 5000) + mSubScenes[i]->unload(); + } + } } void Scene::advanceTime(F32 timeDelta) @@ -205,7 +254,7 @@ void Scene::dumpUtilizedAssets() Con::printf("Dumping utilized assets in scene!"); Vector utilizedAssetsList; - for (U32 i = 0; i < mPermanentObjects.size(); i++) + /*for (U32 i = 0; i < mPermanentObjects.size(); i++) { mPermanentObjects[i]->getUtilizedAssets(&utilizedAssetsList); } @@ -213,7 +262,7 @@ void Scene::dumpUtilizedAssets() for (U32 i = 0; i < mDynamicObjects.size(); i++) { mDynamicObjects[i]->getUtilizedAssets(&utilizedAssetsList); - } + }*/ for (U32 i = 0; i < utilizedAssetsList.size(); i++) { @@ -247,6 +296,9 @@ StringTableEntry Scene::getLevelAsset() bool Scene::saveScene(StringTableEntry fileName) { + if (!isServerObject()) + return false; + //So, we ultimately want to not only save out the level, but also collate all the assets utilized //by the static objects in the scene so we can have those before we parse the level file itself //Useful for preloading or stat tracking @@ -257,6 +309,21 @@ bool Scene::saveScene(StringTableEntry fileName) fileName = getOriginatingFile(); } + for (SimGroupIterator itr(this); *itr; ++itr) + { + if((*itr)->isMethod("onSaving")) + { + Con::executef((*itr), "onSaving", 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++) + { + mSubScenes[i]->save(); + } + bool saveSuccess = save(fileName); if (!saveSuccess) @@ -286,9 +353,12 @@ bool Scene::saveScene(StringTableEntry fileName) dSprintf(depValue, sizeof(depValue), "%s=%s", ASSET_ID_SIGNATURE, utilizedAssetsList[i]); levelAssetDef->setDataField(StringTable->insert(depSlotName), NULL, StringTable->insert(depValue)); - } + //update the gamemode list as well + levelAssetDef->setDataField(StringTable->insert("gameModesNames"), NULL, StringTable->insert(mGameModesNames)); + + //Finally, save saveSuccess = levelAssetDef->saveAsset(); return saveSuccess; @@ -314,11 +384,26 @@ void Scene::getUtilizedAssetsFromSceneObject(SimObject* object, Vector Scene::getObjectsByClass(String className, bool checkSubscenes) +Vector Scene::getObjectsByClass(String className) { return Vector(); } +void Scene::loadAtPosition(const Point3F& position) +{ + for (U32 i = 0; i < mSubScenes.size(); i++) + { + Box3F testBox = Box3F(0.5); + testBox.setCenter(position); + + if (mSubScenes[i]->testBox(testBox)) + { + mSubScenes[i]->setUnloadTimeMS(-1); + mSubScenes[i]->load(); + } + } +} + DefineEngineFunction(getScene, Scene*, (U32 sceneId), (0), "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") @@ -413,9 +498,13 @@ DefineEngineMethod(Scene, getLevelAsset, const char*, (), , DefineEngineMethod(Scene, save, bool, (const char* fileName), (""), "Save out the object to the given file.\n" "@param fileName The name of the file to save to." - "@param selectedOnly If true, only objects marked as selected will be saved out.\n" - "@param preAppendString Text which will be preprended directly to the object serialization.\n" "@param True on success, false on failure.") { return object->saveScene(StringTable->insert(fileName)); } + +DefineEngineMethod(Scene, loadAtPosition, void, (Point3F position), (Point3F::Zero), + "Loads any subscenes at a given point by force.\n") +{ + object->loadAtPosition(position); +} diff --git a/Engine/source/T3D/Scene.h b/Engine/source/T3D/Scene.h index c3781afb2..5be94e189 100644 --- a/Engine/source/T3D/Scene.h +++ b/Engine/source/T3D/Scene.h @@ -1,5 +1,5 @@ #pragma once - +#ifndef SCENE_H #include "console/engineAPI.h" #ifndef _NETOBJECT_H_ @@ -9,8 +9,16 @@ #ifndef _ITICKABLE_H_ #include "core/iTickable.h" #endif - +#ifndef _SCENEOBJECT_H_ #include "scene/sceneObject.h" +#endif + +#ifndef GAME_MODE_H +#include "gameMode.h" +#endif +#ifndef SUB_SCENE_H +#include "SubScene.h" +#endif /// Scene /// This object is effectively a smart container to hold and manage any relevent scene objects and data @@ -19,15 +27,12 @@ class Scene : public NetObject, public virtual ITickable { typedef NetObject Parent; - bool mIsSubScene; - Scene* mParentScene; - Vector mSubScenes; + Vector mSubScenes; - Vector mPermanentObjects; - - Vector mDynamicObjects; + Vector mPermanentObjects; + Vector mDynamicObjects; S32 mSceneId; @@ -37,7 +42,8 @@ class Scene : public NetObject, public virtual ITickable bool mEditPostFX; - StringTableEntry mGameModeName; + StringTableEntry mGameModesNames; + Vector mGameModesList; protected: static Scene * smRootScene; @@ -63,8 +69,8 @@ public: void addObject(SimObject* object) override; void removeObject(SimObject* object) override; - void addDynamicObject(SceneObject* object); - void removeDynamicObject(SceneObject* object); + void addDynamicObject(SimObject* object); + void removeDynamicObject(SimObject* object); void clearDynamicObjects() { mDynamicObjects.clear(); } void dumpUtilizedAssets(); @@ -80,12 +86,14 @@ public: void unpackUpdate(NetConnection *conn, BitStream *stream) override; // - Vector getObjectsByClass(String className, bool checkSubscenes); + Vector getObjectsByClass(String className); void getUtilizedAssetsFromSceneObject(SimObject* object, Vector* usedAssetsList); template - Vector getObjectsByClass(bool checkSubscenes); + Vector getObjectsByClass(); + + void loadAtPosition(const Point3F& position); static Scene *getRootScene() { @@ -96,11 +104,13 @@ public: } static Vector smSceneList; + + DECLARE_CALLBACK(void, onSaving, (const char* fileName)); }; template -Vector Scene::getObjectsByClass(bool checkSubscenes) +Vector Scene::getObjectsByClass() { Vector foundObjects; @@ -121,18 +131,6 @@ Vector Scene::getObjectsByClass(bool checkSubscenes) foundObjects.push_back(curObject); } - if (checkSubscenes) - { - for (U32 i = 0; i < mSubScenes.size(); i++) - { - Vector appendList = mSubScenes[i]->getObjectsByClass(true); - - for (U32 a = 0; a < appendList.size(); a++) - { - foundObjects.push_back(appendList[a]); - } - } - } - return foundObjects; } +#endif diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp new file mode 100644 index 000000000..4e250e02a --- /dev/null +++ b/Engine/source/T3D/SceneGroup.cpp @@ -0,0 +1,362 @@ +#include "SceneGroup.h" + +#include "gameBase/gameConnection.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "gui/editor/inspector/group.h" +#include "gui/worldEditor/editor.h" +#include "math/mathIO.h" +#include "physics/physicsShape.h" +#include "renderInstance/renderPassManager.h" +#include "scene/sceneRenderState.h" + +IMPLEMENT_CO_NETOBJECT_V1(SceneGroup); + +ConsoleDocClass(SceneGroup, + "@brief A collection of arbitrary objects which can be allocated and manipulated as a group.\n\n" + + "%SceneGroup always points to a (.SceneGroup) file which defines its objects. In " + "fact more than one %SceneGroup can reference this file and both will update " + "if the file is modified.\n\n" + + "%SceneGroup is a very simple object and only exists on the server. When it is " + "created it allocates children objects by reading the (.SceneGroup) file like " + "a list of instructions. It then sets their transform relative to the %SceneGroup " + "and Torque networking handles the rest by ghosting the new objects to clients. " + "%SceneGroup itself is not ghosted.\n\n" + + "@ingroup enviroMisc" +); + +SceneGroup::SceneGroup() +{ + // Not ghosted unless we're editing + mNetFlags.clear(Ghostable | ScopeAlways); + + mTypeMask |= StaticObjectType; +} + +SceneGroup::~SceneGroup() +{ +} + +void SceneGroup::initPersistFields() +{ + docsURL; + + addGroup("SceneGroup"); + endGroup("SceneGroup"); + + Parent::initPersistFields(); +} + +bool SceneGroup::onAdd() +{ + if (!Parent::onAdd()) + return false; + + mObjBox.set(Point3F(-0.5f, -0.5f, -0.5f), + Point3F(0.5f, 0.5f, 0.5f)); + + resetWorldBox(); + + // Not added to the scene unless we are editing. + if (gEditingMission) + onEditorEnable(); + + addToScene(); + + return true; +} + +void SceneGroup::onRemove() +{ + removeFromScene(); + Parent::onRemove(); +} + +void SceneGroup::onEditorEnable() +{ + if (isClientObject()) + return; + + // Just in case we are already in the scene, lets not cause an assert. + if (mContainer != NULL) + return; + + // Enable ghosting so we can see this on the client. + mNetFlags.set(Ghostable); + setScopeAlways(); + addToScene(); + + Parent::onEditorEnable(); +} + +void SceneGroup::onEditorDisable() +{ + if (isClientObject()) + return; + + // Just in case we are not in the scene, lets not cause an assert. + if (mContainer == NULL) + return; + + // Do not need this on the client if we are not editing. + removeFromScene(); + mNetFlags.clear(Ghostable); + clearScopeAlways(); + + Parent::onEditorDisable(); +} + +void SceneGroup::inspectPostApply() +{ + Parent::inspectPostApply(); +} + +void SceneGroup::onInspect(GuiInspector* inspector) +{ + Parent::onInspect(inspector); + + //Put the SubScene group before everything that'd be SubScene-effecting, for orginazational purposes + GuiInspectorGroup* sceneGroupGrp = inspector->findExistentGroup(StringTable->insert("Editing")); + if (!sceneGroupGrp) + return; + + GuiControl* stack = dynamic_cast(sceneGroupGrp->findObjectByInternalName(StringTable->insert("Stack"))); + + //Regen bounds button + GuiInspectorField* regenFieldGui = sceneGroupGrp->createInspectorField(); + regenFieldGui->init(inspector, sceneGroupGrp); + + regenFieldGui->setSpecialEditField(true); + regenFieldGui->setTargetObject(this); + + StringTableEntry fldnm = StringTable->insert("RegenerateBounds"); + + regenFieldGui->setSpecialEditVariableName(fldnm); + + regenFieldGui->setInspectorField(NULL, fldnm); + regenFieldGui->setDocs(""); + + stack->addObject(regenFieldGui); + + GuiButtonCtrl* regenButton = new GuiButtonCtrl(); + regenButton->registerObject(); + regenButton->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile"); + regenButton->setText("Regenerate Bounds"); + regenButton->resize(Point2I::Zero, regenFieldGui->getExtent()); + regenButton->setHorizSizing(GuiControl::horizResizeWidth); + regenButton->setVertSizing(GuiControl::vertResizeHeight); + + char rgBuffer[512]; + dSprintf(rgBuffer, 512, "%d.recalculateBounds();", this->getId()); + regenButton->setConsoleCommand(rgBuffer); + + regenFieldGui->addObject(regenButton); +} + +void SceneGroup::setTransform(const MatrixF& mat) +{ + if (isServerObject()) + { + setMaskBits(TransformMask); + + MatrixF newXform = mat; + MatrixF oldXform = getTransform(); + oldXform.affineInverse(); + + MatrixF offset; + offset.mul(newXform, oldXform); + + // Update all child transforms + for (SimSetIterator itr(this); *itr; ++itr) + { + SceneObject* child = dynamic_cast(*itr); + if (child) + { + MatrixF childMat; + + //add the "offset" caused by the parents change, and add it to it's own + // This is needed by objects that update their own render transform thru interpolate tick + // Mostly for stationary objects. + childMat.mul(offset, child->getTransform()); + child->setTransform(childMat); + + PhysicsShape* childPS = dynamic_cast(child); + if (childPS) + childPS->storeRestorePos(); + } + } + } + + Parent::setTransform(mat); +} + +void SceneGroup::setRenderTransform(const MatrixF& mat) +{ + MatrixF newXform = mat; + MatrixF oldXform = getRenderTransform(); + oldXform.affineInverse(); + + MatrixF offset; + offset.mul(newXform, oldXform); + + // Update all child transforms + for (SimSetIterator itr(this); *itr; ++itr) + { + SceneObject* child = dynamic_cast(*itr); + if (child) + { + MatrixF childMat; + + //add the "offset" caused by the parents change, and add it to it's own + // This is needed by objects that update their own render transform thru interpolate tick + // Mostly for stationary objects. + childMat.mul(offset, child->getRenderTransform()); + child->setRenderTransform(childMat); + + PhysicsShape* childPS = dynamic_cast(child); + if (childPS) + childPS->storeRestorePos(); + } + } + + Parent::setRenderTransform(mat); +} + +void SceneGroup::addObject(SimObject* object) +{ + Parent::addObject(object); + + // Recalculate the bounding box from scratch (simpler but potentially costly) + recalculateBoundingBox(); +} + +void SceneGroup::removeObject(SimObject* object) +{ + Parent::removeObject(object); + + // Recalculate the bounding box from scratch (simpler but potentially costly) + recalculateBoundingBox(); +} + +void SceneGroup::recalculateBoundingBox() +{ + if (empty()) + return; + + // Reset the bounding box + Box3F bounds; + + bounds.minExtents.set(1e10, 1e10, 1e10); + bounds.maxExtents.set(-1e10, -1e10, -1e10); + + // 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(); + + bounds.minExtents.setMin(childBox.minExtents); + bounds.maxExtents.setMax(childBox.maxExtents); + } + } + + MatrixF newTrans = mObjToWorld; + newTrans.setPosition(bounds.getCenter()); + Parent::setTransform(newTrans); + + mObjScale = Point3F(bounds.len_x(), bounds.len_y(), bounds.len_z()); + mWorldBox = bounds; + resetObjectBox(); + + setMaskBits(TransformMask); +} + +U32 SceneGroup::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + mathWrite(*stream, mObjBox); + + if (stream->writeFlag(mask & TransformMask)) + { + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + } + + return retMask; +} + +void SceneGroup::unpackUpdate(NetConnection* conn, BitStream* stream) +{ + Parent::unpackUpdate(conn, stream); + + mathRead(*stream, &mObjBox); + resetWorldBox(); + + // TransformMask + if (stream->readFlag()) + { + mathRead(*stream, &mObjToWorld); + mathRead(*stream, &mObjScale); + + setTransform(mObjToWorld); + } +} + +bool SceneGroup::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) +{ + Vector foundObjects; + if (empty()) + { + Con::warnf("SceneGroup::buildPolyList() - SceneGroup %s is empty!", getName()); + return false; + } + findObjectByType(foundObjects); + + for (S32 i = 0; i < foundObjects.size(); i++) + { + foundObjects[i]->buildPolyList(context, polyList, box, sphere); + } + + return true; +} + +bool SceneGroup::buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F& box, const SphereF& sphere) +{ + Vector foundObjects; + findObjectByType(foundObjects); + + for (S32 i = 0; i < foundObjects.size(); i++) + { + foundObjects[i]->buildExportPolyList(exportData, box, sphere); + } + + return true; +} + +void SceneGroup::getUtilizedAssets(Vector* usedAssetsList) +{ + //if (empty()) + return; + + Vector foundObjects; + findObjectByType(foundObjects); + + for (S32 i = 0; i < foundObjects.size(); i++) + { + SceneObject* child = foundObjects[i]; + + child->getUtilizedAssets(usedAssetsList); + } +} + +DefineEngineMethod(SceneGroup, recalculateBounds, void, (), , + "Recalculates the SceneGroups' bounds and centerpoint.\n") +{ + object->recalculateBoundingBox(); +} diff --git a/Engine/source/T3D/SceneGroup.h b/Engine/source/T3D/SceneGroup.h new file mode 100644 index 000000000..b89af1c9a --- /dev/null +++ b/Engine/source/T3D/SceneGroup.h @@ -0,0 +1,55 @@ +#pragma once +#ifndef SCENE_GROUP_H +#define SCENE_GROUP_H + +#ifndef _SCENEOBJECT_H_ +#include "scene/sceneObject.h" +#endif + +class SceneGroup : public SceneObject +{ + typedef SceneObject Parent; + +public: + enum MaskBits + { + TransformMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1 + }; + +public: + SceneGroup(); + virtual ~SceneGroup(); + + DECLARE_CONOBJECT(SceneGroup); + DECLARE_CATEGORY("Object \t Collection"); + + static void initPersistFields(); + + // SimObject + bool onAdd() override; + void onRemove() override; + void onEditorEnable() override; + void onEditorDisable() override; + void inspectPostApply() override; + void onInspect(GuiInspector* inspector) override; + + // NetObject + U32 packUpdate(NetConnection* conn, U32 mask, BitStream* stream) override; + void unpackUpdate(NetConnection* conn, BitStream* stream) override; + + // SceneObject + void setTransform(const MatrixF& mat) override; + void setRenderTransform(const MatrixF& mat) override; + + // Object management + void addObject(SimObject* object) override; + void removeObject(SimObject* object) override; + void recalculateBoundingBox(); + + /// + bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) override; + bool buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F& box, const SphereF&) override; + void getUtilizedAssets(Vector* usedAssetsList) override; +}; +#endif diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp new file mode 100644 index 000000000..673e1e33e --- /dev/null +++ b/Engine/source/T3D/SubScene.cpp @@ -0,0 +1,556 @@ +#include "SubScene.h" + +#include "gameMode.h" +#include "console/persistenceManager.h" +#include "console/script.h" +#include "scene/sceneRenderState.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "gui/editor/inspector/group.h" +#include "T3D/gameBase/gameBase.h" + +bool SubScene::smTransformChildren = false; + +IMPLEMENT_CO_NETOBJECT_V1(SubScene); + +S32 SubScene::mUnloadTimeoutMs = 5000; + +IMPLEMENT_CALLBACK(SubScene, onLoaded, void, (), (), + "@brief Called when a subScene has been loaded and has game mode implications.\n\n"); +IMPLEMENT_CALLBACK(SubScene, onUnloaded, void, (), (), + "@brief Called when a subScene has been unloaded and has game mode implications.\n\n"); + +SubScene::SubScene() : + mLevelAssetId(StringTable->EmptyString()), + mGameModesNames(StringTable->EmptyString()), + mScopeDistance(-1), + mLoaded(false), + mFreezeLoading(false), + mTickPeriodMS(1000), + mCurrTick(0), + mGlobalLayer(false) +{ + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask |= StaticObjectType; +} + +SubScene::~SubScene() +{ +} + +bool SubScene::onAdd() +{ + if (!Parent::onAdd()) + return false; + + setProcessTick(true); + + return true; +} + +void SubScene::onRemove() +{ + if (isClientObject()) + removeFromScene(); + + unload(); + + Parent::onRemove(); +} + +void SubScene::initPersistFields() +{ + addGroup("SubScene"); + addField("isGlobalLayer", TypeBool, Offset(mGlobalLayer, SubScene), ""); + INITPERSISTFIELD_LEVELASSET(Level, SubScene, "The level asset to load."); + addField("gameModes", TypeGameModeList, Offset(mGameModesNames, SubScene), "The game modes that this subscene is associated with."); + endGroup("SubScene"); + + addGroup("LoadingManagement"); + addField("freezeLoading", TypeBool, Offset(mFreezeLoading, SubScene), "If true, will prevent the zone from being changed from it's current loading state."); + addField("loadIf", TypeCommand, Offset(mLoadIf, SubScene), "evaluation condition (true/false)"); + + addField("tickPeriodMS", TypeS32, Offset(mTickPeriodMS, SubScene), "evaluation rate (ms)"); + + addField("onLoadCommand", TypeCommand, Offset(mOnLoadCommand, SubScene), "The command to execute when the subscene is loaded. Maximum 1023 characters."); + addField("onUnloadCommand", TypeCommand, Offset(mOnUnloadCommand, SubScene), "The command to execute when subscene is unloaded. Maximum 1023 characters."); + endGroup("LoadingManagement"); + + + Parent::initPersistFields(); +} + +void SubScene::consoleInit() +{ + Parent::consoleInit(); + + Con::addVariable("$SubScene::UnloadTimeoutMS", TypeBool, &SubScene::mUnloadTimeoutMs, "The amount of time in milliseconds it takes for a SubScene to be unloaded if it's inactive.\n" + "@ingroup Editors\n"); + + Con::addVariable("$SubScene::transformChildren", TypeBool, &SubScene::smTransformChildren, + "@brief If true, then transform manipulations modify child objects. If false, only triggering bounds is manipulated\n\n" + "@ingroup Editors"); +} + +void SubScene::addObject(SimObject* object) +{ + SceneObject::addObject(object); +} + +void SubScene::removeObject(SimObject* object) +{ + SceneObject::removeObject(object); +} + +U32 SubScene::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + stream->writeFlag(mGlobalLayer); + + return retMask; +} + +void SubScene::unpackUpdate(NetConnection* conn, BitStream* stream) +{ + Parent::unpackUpdate(conn, stream); + + mGlobalLayer = stream->readFlag(); + +} + +void SubScene::onInspect(GuiInspector* inspector) +{ + Parent::onInspect(inspector); + + //Put the SubScene group before everything that'd be SubScene-effecting, for orginazational purposes + GuiInspectorGroup* subsceneGrp = inspector->findExistentGroup(StringTable->insert("SubScene")); + if (!subsceneGrp) + return; + + GuiControl* stack = dynamic_cast(subsceneGrp->findObjectByInternalName(StringTable->insert("Stack"))); + + //Save button + GuiInspectorField* saveFieldGui = subsceneGrp->createInspectorField(); + saveFieldGui->init(inspector, subsceneGrp); + + saveFieldGui->setSpecialEditField(true); + saveFieldGui->setTargetObject(this); + + StringTableEntry fldnm = StringTable->insert("SaveSubScene"); + + saveFieldGui->setSpecialEditVariableName(fldnm); + + saveFieldGui->setInspectorField(NULL, fldnm); + saveFieldGui->setDocs(""); + + stack->addObject(saveFieldGui); + + GuiButtonCtrl* saveButton = new GuiButtonCtrl(); + saveButton->registerObject(); + saveButton->setDataField(StringTable->insert("profile"), NULL, "ToolsGuiButtonProfile"); + saveButton->setText("Save SubScene"); + saveButton->resize(Point2I::Zero, saveFieldGui->getExtent()); + saveButton->setHorizSizing(GuiControl::horizResizeWidth); + saveButton->setVertSizing(GuiControl::vertResizeHeight); + + char szBuffer[512]; + dSprintf(szBuffer, 512, "%d.save();", this->getId()); + saveButton->setConsoleCommand(szBuffer); + + saveFieldGui->addObject(saveButton); +} + +void SubScene::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits(-1); +} + +void SubScene::setTransform(const MatrixF& mat) +{ + if(SubScene::smTransformChildren) + { + Parent::setTransform(mat); + } + else + { + SceneObject::setTransform(mat); + } +} + +void SubScene::setRenderTransform(const MatrixF& mat) +{ + if (SubScene::smTransformChildren) + { + Parent::setRenderTransform(mat); + } + else + { + SceneObject::setRenderTransform(mat); + } +} + +bool SubScene::evaluateCondition() +{ + if (!mLoadIf.isEmpty()) + { + //test the mapper plugged in condition line + String resVar = getIdString() + String(".result"); + Con::setBoolVariable(resVar.c_str(), false); + String command = resVar + "=" + mLoadIf + ";"; + + Con::evaluatef(command.c_str()); + return Con::getBoolVariable(resVar.c_str()); + } + return true; +} + +bool SubScene::testBox(const Box3F& testBox) +{ + if (mGlobalLayer) + return true; + + bool passes = getWorldBox().isOverlapped(testBox); + if (passes) + passes = evaluateCondition(); + return passes; +} + +void SubScene::write(Stream& stream, U32 tabStop, U32 flags) +{ + MutexHandle handle; + handle.lock(mMutex); + + // export selected only? + if ((flags & SelectedOnly) && !isSelected()) + { + for (U32 i = 0; i < size(); i++) + (*this)[i]->write(stream, tabStop, flags); + + return; + + } + + stream.writeTabs(tabStop); + char buffer[2048]; + const U32 bufferWriteLen = dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() && !(flags & NoName) ? getName() : ""); + stream.write(bufferWriteLen, buffer); + writeFields(stream, tabStop + 1); + + //The only meaningful difference between this and simSet for writing is we skip the children, since they're just the levelAsset contents + + stream.writeTabs(tabStop); + stream.write(4, "};\r\n"); +} + +void SubScene::processTick(const Move* move) +{ + mCurrTick += TickMs; + if (mCurrTick > mTickPeriodMS) + { + mCurrTick = 0; + //re-evaluate + if (!evaluateCondition()) + unload(); + } +} + +void SubScene::_onFileChanged(const Torque::Path& path) +{ + if(mLevelAsset.isNull() || Torque::Path(mLevelAsset->getLevelPath()) != path) + return; + + AssertFatal(path == mLevelAsset->getLevelPath(), "Prefab::_onFileChanged - path does not match filename."); + + _closeFile(false); + _loadFile(false); + setMaskBits(U32_MAX); +} + +void SubScene::_removeContents(SimGroupIterator set) +{ + for (SimGroupIterator itr(set); *itr; ++itr) + { + + SimGroup* child = dynamic_cast(*itr); + if (child) + { + _removeContents(SimGroupIterator(child)); + + GameBase* asGameBase = dynamic_cast(child); + if (asGameBase) + { + asGameBase->scriptOnRemove(); + } + + Sim::cancelPendingEvents(child); + + child->safeDeleteObject(); + } + } +} + +void SubScene::_closeFile(bool removeFileNotify) +{ + AssertFatal(isServerObject(), "Trying to close out a subscene file on the client is bad!"); + + _removeContents(SimGroupIterator(this)); + + if (removeFileNotify && mLevelAsset.notNull() && mLevelAsset->getLevelPath() != StringTable->EmptyString()) + { + Torque::FS::RemoveChangeNotification(mLevelAsset->getLevelPath(), this, &SubScene::_onFileChanged); + } + + mGameModesList.clear(); +} + +void SubScene::_loadFile(bool addFileNotify) +{ + AssertFatal(isServerObject(), "Trying to load a SubScene file on the client is bad!"); + + if(mLevelAsset.isNull() || mLevelAsset->getLevelPath() == StringTable->EmptyString()) + return; + + String evalCmd = String::ToString("exec(\"%s\");", mLevelAsset->getLevelPath()); + + String instantGroup = Con::getVariable("InstantGroup"); + Con::setIntVariable("InstantGroup", this->getId()); + Con::evaluate((const char*)evalCmd.c_str(), false, mLevelAsset->getLevelPath()); + Con::setVariable("InstantGroup", instantGroup.c_str()); + + if (addFileNotify) + Torque::FS::AddChangeNotification(mLevelAsset->getLevelPath(), this, &SubScene::_onFileChanged); +} + +void SubScene::load() +{ + mStartUnloadTimerMS = -1; //reset unload timers + + //no need to load multiple times + if (mLoaded) + return; + + if (mFreezeLoading) + return; + + _loadFile(true); + mLoaded = true; + + GameMode::findGameModes(mGameModesNames, &mGameModesList); + + onLoaded_callback(); + for (U32 i = 0; i < mGameModesList.size(); i++) + { + mGameModesList[i]->onSubsceneLoaded_callback(this); + } + + if (!mOnLoadCommand.isEmpty()) + { + String command = "%this = " + String(getIdString()) + "; " + mLoadIf + ";"; + Con::evaluatef(command.c_str()); + } +} + +void SubScene::unload() +{ + if (!mLoaded) + return; + + if (mFreezeLoading) + return; + + if (isSelected()) + { + mStartUnloadTimerMS = Sim::getCurrentTime(); + return; //if a child is selected, then we don't want to unload + } + + //scan down through our child objects, see if any are marked as selected, + //if so, skip unloading and reset the timer + for (SimGroupIterator itr(this); *itr; ++itr) + { + SimGroup* childGrp = dynamic_cast(*itr); + if (childGrp) + { + if (childGrp->isSelected()) + { + mStartUnloadTimerMS = Sim::getCurrentTime(); + return; //if a child is selected, then we don't want to unload + } + for (SimGroupIterator cldItr(childGrp); *cldItr; ++cldItr) + { + SimObject* chldChld = dynamic_cast(*cldItr); + if (chldChld && chldChld->isSelected()) + { + mStartUnloadTimerMS = Sim::getCurrentTime(); + return; //if a child is selected, then we don't want to unload + } + } + } + } + + onUnloaded_callback(); + for (U32 i = 0; i < mGameModesList.size(); i++) + { + mGameModesList[i]->onSubsceneUnloaded_callback(this); + } + + if (!mOnUnloadCommand.isEmpty()) + { + String command = "%this = " + String(getIdString()) + "; " + mOnUnloadCommand + ";"; + Con::evaluatef(command.c_str()); + } + + _closeFile(true); + mLoaded = false; + +} + +bool SubScene::save() +{ + if (!isServerObject()) + return false; + + //if there's nothing TO save, don't bother + if (size() == 0 || !isLoaded()) + return false; + + if (mLevelAsset.isNull()) + return false; + + //If we're flagged for unload, push back the unload timer so we can't accidentally trip be saving partway through an unload + if (mStartUnloadTimerMS != -1) + mStartUnloadTimerMS = Sim::getCurrentTime(); + + PersistenceManager prMger; + + StringTableEntry levelPath = mLevelAsset->getLevelPath(); + + FileStream fs; + fs.open(levelPath, Torque::FS::File::Write); + fs.close(); + + for (SimGroupIterator itr(this); *itr; ++itr) + { + SimObject* childObj = (*itr); + + if (!prMger.isDirty(childObj)) + { + if ((*itr)->isMethod("onSaving")) + { + Con::executef((*itr), "onSaving", mLevelAssetId); + } + + if (childObj->getGroup() == this) + { + prMger.setDirty((*itr), levelPath); + } + } + } + + prMger.saveDirty(); + + //process our gameModeList and write it out to the levelAsset for metadata stashing + bool saveSuccess = false; + + //Get the level asset + if (mLevelAsset.isNull()) + return saveSuccess; + + //update the gamemode list as well + mLevelAsset->setDataField(StringTable->insert("gameModesNames"), NULL, StringTable->insert(mGameModesNames)); + + //Finally, save + saveSuccess = mLevelAsset->saveAsset(); + + return saveSuccess; +} + +void SubScene::_onSelected() +{ + if (!isLoaded() && isServerObject()) + load(); +} + +void SubScene::_onUnselected() +{ +} + +void SubScene::prepRenderImage(SceneRenderState* state) +{ + // only render if selected or render flag is set + if (/*!smRenderTriggers && */!isSelected()) + return; + + ObjectRenderInst* ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &SubScene::renderObject); + ri->type = RenderPassManager::RIT_Editor; + ri->translucentSort = true; + ri->defaultKey = 1; + state->getRenderPass()->addInst(ri); +} + +void SubScene::renderObject(ObjectRenderInst* ri, + SceneRenderState* state, + BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + GFXStateBlockDesc desc; + desc.setZReadWrite(true, false); + desc.setBlend(true); + + // Trigger polyhedrons are set up with outward facing normals and CCW ordering + // so can't enable backface culling. + desc.setCullMode(GFXCullNone); + + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + GFX->multWorld(mat); + + GFXDrawUtil* drawer = GFX->getDrawUtil(); + + //Box3F scale = getScale() + //Box3F bounds = Box3F(-m) + + Point3F scale = getScale(); + Box3F bounds = Box3F(-scale/2, scale/2); + + ColorI boundsColor = ColorI(135, 206, 235, 50); + + if (mGlobalLayer) + boundsColor = ColorI(200, 100, 100, 25); + else if (mLoaded) + boundsColor = ColorI(50, 200, 50, 50); + + drawer->drawCube(desc, bounds, boundsColor); + + // Render wireframe. + + desc.setFillModeWireframe(); + drawer->drawCube(desc, bounds, ColorI::BLACK); +} + +DefineEngineMethod(SubScene, save, bool, (),, + "Save out the subScene.\n") +{ + return object->save(); +} + + +DefineEngineMethod(SubScene, load, void, (), , + "Loads the SubScene's level file.\n") +{ + object->load(); +} + +DefineEngineMethod(SubScene, unload, void, (), , + "Unloads the SubScene's level file.\n") +{ + object->unload(); +} diff --git a/Engine/source/T3D/SubScene.h b/Engine/source/T3D/SubScene.h new file mode 100644 index 000000000..4117e40c1 --- /dev/null +++ b/Engine/source/T3D/SubScene.h @@ -0,0 +1,126 @@ +#pragma once +#ifndef SUB_SCENE_H +#define SUB_SCENE_H + +#ifndef SCENE_GROUP_H +#include "SceneGroup.h" +#endif +#ifndef LEVEL_ASSET_H +#include "assets/LevelAsset.h" +#endif + +class GameMode; + +class SubScene : public SceneGroup +{ + typedef SceneGroup Parent; + +public: + enum MaskBits + { + NextFreeMask = Parent::NextFreeMask << 0 + }; + + void onLevelChanged() {} + +protected: + static bool smTransformChildren; + +private: + DECLARE_LEVELASSET(SubScene, Level, onLevelChanged); + + StringTableEntry mGameModesNames; + Vector mGameModesList; + + F32 mScopeDistance; + + /// + /// How long we wait once every control object has left the SubScene's load boundary for us to unload the levelAsset. + /// + S32 mStartUnloadTimerMS; + + bool mLoaded; + bool mFreezeLoading; + + String mLoadIf; + String mOnLoadCommand; + String mOnUnloadCommand; + + S32 mTickPeriodMS; + U32 mCurrTick; + + bool mGlobalLayer; + +public: + SubScene(); + virtual ~SubScene(); + + DECLARE_CONOBJECT(SubScene); + DECLARE_CATEGORY("Object \t Collection"); + + static void initPersistFields(); + static void consoleInit(); + StringTableEntry getTypeHint() const override { return (getLevelAsset()) ? getLevelAsset()->getAssetName() : StringTable->EmptyString(); } + + // SimObject + bool onAdd() override; + void onRemove() override; + + U32 packUpdate(NetConnection* conn, U32 mask, BitStream* stream) override; + void unpackUpdate(NetConnection* conn, BitStream* stream) override; + + void addObject(SimObject* object); + void removeObject(SimObject* object); + //void onEditorEnable() override; + //void onEditorDisable() override; + void inspectPostApply() override; + + void setTransform(const MatrixF& mat) override; + void setRenderTransform(const MatrixF& mat) override; + + bool testBox(const Box3F& testBox); + bool evaluateCondition(); + void _onSelected() override; + void _onUnselected() override; + + static S32 mUnloadTimeoutMs; + +protected: + void write(Stream& stream, U32 tabStop, U32 flags = 0) override; + + // + void _onFileChanged(const Torque::Path& path); + void _removeContents(SimGroupIterator); + void _closeFile(bool removeFileNotify); + void _loadFile(bool addFileNotify); + + // +public: + void processTick(const Move* move) override; + + // + void onInspect(GuiInspector* inspector) override; + + void load(); + void unload(); + + void prepRenderImage(SceneRenderState* state) override; + void renderObject(ObjectRenderInst* ri, + SceneRenderState* state, + BaseMatInstance* overrideMat); + + bool isLoaded() { return mLoaded; } + void setUnloadTimeMS(S32 unloadTimeMS) { + mStartUnloadTimerMS = unloadTimeMS; + } + inline S32 getUnloadTimsMS() { + return mStartUnloadTimerMS; + } + + bool save(); + + DECLARE_CALLBACK(void, onLoaded, ()); + DECLARE_CALLBACK(void, onUnloaded, ()); + DECLARE_ASSET_SETGET(SubScene, Level); +}; +#endif diff --git a/Engine/source/T3D/assets/ImageAsset.cpp b/Engine/source/T3D/assets/ImageAsset.cpp index b51327c4e..89088f7fe 100644 --- a/Engine/source/T3D/assets/ImageAsset.cpp +++ b/Engine/source/T3D/assets/ImageAsset.cpp @@ -104,7 +104,7 @@ ConsoleSetType(TypeImageAssetId) } // Warn. - Con::warnf("(TypeAssetId) - Cannot set multiple args to a single asset."); + Con::warnf("(TypeImageAssetId) - Cannot set multiple args to a single asset."); } //----------------------------------------------------------------------------- @@ -748,7 +748,7 @@ void GuiInspectorTypeImageAssetPtr::setPreviewImage(StringTableEntry assetId) IMPLEMENT_CONOBJECT(GuiInspectorTypeImageAssetId); ConsoleDocClass(GuiInspectorTypeImageAssetId, - "@brief Inspector field type for Shapes\n\n" + "@brief Inspector field type for Images\n\n" "Editor use only.\n\n" "@internal" ); diff --git a/Engine/source/T3D/assets/LevelAsset.cpp b/Engine/source/T3D/assets/LevelAsset.cpp index 0dcd04ec4..98bb2cc5c 100644 --- a/Engine/source/T3D/assets/LevelAsset.cpp +++ b/Engine/source/T3D/assets/LevelAsset.cpp @@ -42,12 +42,14 @@ // Debug Profiling. #include "platform/profiler.h" +#include "gfx/gfxDrawUtil.h" + //----------------------------------------------------------------------------- IMPLEMENT_CONOBJECT(LevelAsset); -ConsoleType(LevelAssetPtr, TypeLevelAssetPtr, const char*, ASSET_ID_FIELD_PREFIX) +ConsoleType(LevelAssetPtr, TypeLevelAssetPtr, const char*, "") //----------------------------------------------------------------------------- @@ -74,6 +76,28 @@ ConsoleSetType(TypeLevelAssetPtr) Con::warnf("(TypeLevelAssetPtr) - Cannot set multiple args to a single asset."); } +//----------------------------------------------------------------------------- +ConsoleType(assetIdString, TypeLevelAssetId, const char*, "") + +ConsoleGetType(TypeLevelAssetId) +{ + // Fetch asset Id. + return *((const char**)(dptr)); +} + +ConsoleSetType(TypeLevelAssetId) +{ + // Was a single argument specified? + if (argc == 1) + { + *((const char**)dptr) = StringTable->insert(argv[0]); + + return; + } + + // Warn. + Con::warnf("(TypeLevelAssetId) - Cannot set multiple args to a single asset."); +} //----------------------------------------------------------------------------- LevelAsset::LevelAsset() : AssetBase(), mIsSubLevel(false) @@ -91,7 +115,7 @@ LevelAsset::LevelAsset() : AssetBase(), mIsSubLevel(false) mForestPath = StringTable->EmptyString(); mNavmeshPath = StringTable->EmptyString(); - mGamemodeName = StringTable->EmptyString(); + mGameModesNames = StringTable->EmptyString(); mMainLevelAsset = StringTable->EmptyString(); mEditorFile = StringTable->EmptyString(); @@ -134,7 +158,7 @@ void LevelAsset::initPersistFields() &setBakedSceneFile, &getBakedSceneFile, "Path to the level file with the objects generated as part of the baking process"); addField("isSubScene", TypeBool, Offset(mIsSubLevel, LevelAsset), "Is this a sublevel to another Scene"); - addField("gameModeName", TypeString, Offset(mGamemodeName, LevelAsset), "Name of the Game Mode to be used with this level"); + addField("gameModesNames", TypeString, Offset(mGameModesNames, LevelAsset), "Name of the Game Mode to be used with this level"); } //------------------------------------------------------------------------------ @@ -357,7 +381,7 @@ void LevelAsset::unloadDependencies() } } -DefineEngineMethod(LevelAsset, getLevelPath, const char*, (),, +DefineEngineMethod(LevelAsset, getLevelPath, const char*, (), , "Gets the full path of the asset's defined level file.\n" "@return The string result of the level path") { @@ -417,3 +441,99 @@ DefineEngineMethod(LevelAsset, unloadDependencies, void, (), , { return object->unloadDependencies(); } + +//----------------------------------------------------------------------------- +// GuiInspectorTypeAssetId +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiInspectorTypeLevelAssetPtr); + +ConsoleDocClass(GuiInspectorTypeLevelAssetPtr, + "@brief Inspector field type for Shapes\n\n" + "Editor use only.\n\n" + "@internal" +); + +void GuiInspectorTypeLevelAssetPtr::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeLevelAssetPtr)->setInspectorFieldType("GuiInspectorTypeLevelAssetPtr"); +} + +GuiControl* GuiInspectorTypeLevelAssetPtr::constructEditControl() +{ + // Create base filename edit controls + GuiControl* retCtrl = Parent::constructEditControl(); + if (retCtrl == NULL) + return retCtrl; + + // Change filespec + char szBuffer[512]; + dSprintf(szBuffer, sizeof(szBuffer), "AssetBrowser.showDialog(\"LevelAsset\", \"AssetBrowser.changeAsset\", %s, \"\");", + getIdString()); + mBrowseButton->setField("Command", szBuffer); + + setDataField(StringTable->insert("targetObject"), NULL, mInspector->getInspectObject()->getIdString()); + + // Create "Open in Editor" button + mEditButton = new GuiBitmapButtonCtrl(); + + dSprintf(szBuffer, sizeof(szBuffer), "$createAndAssignField = %s; AssetBrowser.setupCreateNewAsset(\"LevelAsset\", AssetBrowser.selectedModule, \"createAndAssignLevelAsset\");", + getIdString()); + mEditButton->setField("Command", szBuffer); + + char bitmapName[512] = "ToolsModule:iconAdd_image"; + mEditButton->setBitmap(StringTable->insert(bitmapName)); + + mEditButton->setDataField(StringTable->insert("Profile"), NULL, "GuiButtonProfile"); + mEditButton->setDataField(StringTable->insert("tooltipprofile"), NULL, "GuiToolTipProfile"); + mEditButton->setDataField(StringTable->insert("hovertime"), NULL, "1000"); + mEditButton->setDataField(StringTable->insert("tooltip"), NULL, "Test play this sound"); + + mEditButton->registerObject(); + addObject(mEditButton); + + return retCtrl; +} + +bool GuiInspectorTypeLevelAssetPtr::updateRects() +{ + S32 dividerPos, dividerMargin; + mInspector->getDivider(dividerPos, dividerMargin); + Point2I fieldExtent = getExtent(); + Point2I fieldPos = getPosition(); + + mCaptionRect.set(0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y); + mEditCtrlRect.set(fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin - 34, fieldExtent.y); + + bool resized = mEdit->resize(mEditCtrlRect.point, mEditCtrlRect.extent); + if (mBrowseButton != NULL) + { + mBrowseRect.set(fieldExtent.x - 32, 2, 14, fieldExtent.y - 4); + resized |= mBrowseButton->resize(mBrowseRect.point, mBrowseRect.extent); + } + + if (mEditButton != NULL) + { + RectI shapeEdRect(fieldExtent.x - 16, 2, 14, fieldExtent.y - 4); + resized |= mEditButton->resize(shapeEdRect.point, shapeEdRect.extent); + } + + return resized; +} + +IMPLEMENT_CONOBJECT(GuiInspectorTypeLevelAssetId); + +ConsoleDocClass(GuiInspectorTypeLevelAssetId, + "@brief Inspector field type for Levels\n\n" + "Editor use only.\n\n" + "@internal" +); + +void GuiInspectorTypeLevelAssetId::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeLevelAssetId)->setInspectorFieldType("GuiInspectorTypeLevelAssetId"); +} diff --git a/Engine/source/T3D/assets/LevelAsset.h b/Engine/source/T3D/assets/LevelAsset.h index 11cedd7d6..8b02495ed 100644 --- a/Engine/source/T3D/assets/LevelAsset.h +++ b/Engine/source/T3D/assets/LevelAsset.h @@ -40,6 +40,11 @@ #endif #include "T3D/assets/ImageAsset.h" +#ifndef _GUI_INSPECTOR_TYPES_H_ +#include "gui/editor/guiInspectorTypes.h" +#endif +#include + //----------------------------------------------------------------------------- class LevelAsset : public AssetBase { @@ -64,7 +69,7 @@ class LevelAsset : public AssetBase bool mIsSubLevel; StringTableEntry mMainLevelAsset; - StringTableEntry mGamemodeName; + StringTableEntry mGameModesNames; Vector mAssetDependencies; @@ -114,7 +119,7 @@ public: U32 load() override { return Ok; }; protected: - static bool setLevelFile(void *obj, const char *index, const char *data) { static_cast(obj)->setLevelFile(data); return false; } + static bool setLevelFile(void* obj, const char* index, const char* data) { static_cast(obj)->setLevelFile(data); return false; } static const char* getLevelFile(void* obj, const char* data) { return static_cast(obj)->getLevelFile(); } static bool setEditorFile(void* obj, const char* index, const char* data) { static_cast(obj)->setEditorFile(data); return false; } @@ -135,10 +140,96 @@ protected: void initializeAsset(void) override; void onAssetRefresh(void) override; - void loadAsset(); + void loadAsset(); + + typedef Signal LevelAssetChanged; + LevelAssetChanged mChangeSignal; + +public: + LevelAssetChanged& getChangedSignal() { return mChangeSignal; } }; +#ifdef TORQUE_TOOLS +class GuiInspectorTypeLevelAssetPtr : public GuiInspectorTypeFileName +{ + typedef GuiInspectorTypeFileName Parent; +public: + + GuiBitmapButtonCtrl* mEditButton; + + DECLARE_CONOBJECT(GuiInspectorTypeLevelAssetPtr); + static void consoleInit(); + + GuiControl* constructEditControl() override; + bool updateRects() override; +}; + +class GuiInspectorTypeLevelAssetId : public GuiInspectorTypeLevelAssetPtr +{ + typedef GuiInspectorTypeLevelAssetPtr Parent; +public: + + DECLARE_CONOBJECT(GuiInspectorTypeLevelAssetId); + static void consoleInit(); +}; +#endif + + DefineConsoleType(TypeLevelAssetPtr, LevelAsset) +DefineConsoleType(TypeLevelAssetId, String) + +#pragma region Singular Asset Macros + +//Singular assets +/// +/// Declares an level asset +/// This establishes the assetId, asset and legacy filepath fields, along with supplemental getter and setter functions +/// +#define DECLARE_LEVELASSET(className, name, changeFunc) public: \ + StringTableEntry m##name##AssetId;\ + AssetPtr m##name##Asset;\ +public: \ + const AssetPtr & get##name##Asset() const { return m##name##Asset; }\ + void set##name##Asset(const AssetPtr &_in) { m##name##Asset = _in;}\ + \ + bool _set##name(StringTableEntry _in)\ + {\ + if(m##name##AssetId != _in)\ + {\ + if (m##name##Asset.notNull())\ + {\ + m##name##Asset->getChangedSignal().remove(this, &className::changeFunc);\ + }\ + if (_in == NULL || _in == StringTable->EmptyString())\ + {\ + m##name##AssetId = StringTable->EmptyString();\ + m##name##Asset = NULL;\ + return true;\ + }\ + if (AssetDatabase.isDeclaredAsset(_in))\ + {\ + m##name##AssetId = _in;\ + m##name##Asset = _in;\ + return true;\ + }\ + }\ + \ + if(get##name() == StringTable->EmptyString())\ + return true;\ + \ + return false;\ + }\ + \ + const StringTableEntry get##name() const\ + {\ + return m##name##AssetId;\ + }\ + bool name##Valid() {return (get##name() != StringTable->EmptyString() && m##name##Asset->getStatus() == AssetBase::Ok); } + +#define INITPERSISTFIELD_LEVELASSET(name, consoleClass, docs) \ + addProtectedField(assetText(name, Asset), TypeLevelAssetId, Offset(m##name##AssetId, consoleClass), _set##name##Data, &defaultProtectedGetFn, assetDoc(name, asset docs.)); + +#pragma endregion #endif // _ASSET_BASE_H_ diff --git a/Engine/source/T3D/convexShape.cpp b/Engine/source/T3D/convexShape.cpp index c8341e8a8..f5c773293 100644 --- a/Engine/source/T3D/convexShape.cpp +++ b/Engine/source/T3D/convexShape.cpp @@ -317,10 +317,10 @@ void ConvexShape::initPersistFields() addGroup( "Internal" ); addProtectedField( "surface", TypeRealString, 0, &protectedSetSurface, &defaultProtectedGetFn, - "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); addProtectedField( "surfaceTexture", TypeRealString, 0, &protectedSetSurfaceTexture, &defaultProtectedGetFn, - "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -498,6 +498,57 @@ bool ConvexShape::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 ConvexShape::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("surface") || fieldName == StringTable->insert("surfaceTexture")) + { + return mSurfaces.size(); + } + + return 0; +} + +const char* ConvexShape::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (index >= smMaxSurfaces) + return NULL; + + if (fieldName == StringTable->insert("surface")) + { + if(index >= mSurfaces.size()) + return NULL; + + const MatrixF& mat = mSurfaces[index]; + + QuatF quat(mat); + Point3F pos(mat.getPosition()); + + char buffer[1024]; + dMemset(buffer, 0, 1024); + + dSprintf(buffer, 1024, "%g %g %g %g %g %g %g %i %g %g %g %g %g %i %i", + quat.x, quat.y, quat.z, quat.w, pos.x, pos.y, pos.z, mSurfaceUVs[index].matID, + mSurfaceUVs[index].offset.x, mSurfaceUVs[index].offset.y, mSurfaceUVs[index].scale.x, + mSurfaceUVs[index].scale.y, mSurfaceUVs[index].zRot, mSurfaceUVs[index].horzFlip, mSurfaceUVs[index].vertFlip); + + return StringTable->insert(buffer); + } + else if (fieldName == StringTable->insert("surfaceTexture")) + { + if (index >= mSurfaceTextures.size()) + return NULL; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + + dSprintf(buffer, 1024, "%s", mSurfaceTextures[index].getMaterial()); + + return StringTable->insert(buffer); + } + + return NULL; +} + void ConvexShape::onScaleChanged() { if ( isProperlyAdded() ) diff --git a/Engine/source/T3D/convexShape.h b/Engine/source/T3D/convexShape.h index dfe0eaea4..67918b038 100644 --- a/Engine/source/T3D/convexShape.h +++ b/Engine/source/T3D/convexShape.h @@ -83,7 +83,7 @@ class ConvexShape : public SceneObject typedef SceneObject Parent; friend class GuiConvexEditorCtrl; friend class GuiConvexEditorUndoAction; - friend class ConvexShapeCollisionConvex; + friend class ConvexShapeCollisionConvex; public: @@ -113,10 +113,10 @@ public: U32 p1; U32 p2; - U32 operator []( U32 index ) const + U32 operator [](U32 index) const { - AssertFatal( index >= 0 && index <= 2, "index out of range" ); - return *( (&p0) + index ); + AssertFatal(index >= 0 && index <= 2, "index out of range"); + return *((&p0) + index); } }; @@ -126,23 +126,23 @@ public: Vector< U32 > points; Vector< U32 > winding; Vector< Point2F > texcoords; - Vector< Triangle > triangles; + Vector< Triangle > triangles; Point3F tangent; Point3F normal; Point3F centroid; F32 area; S32 id; - }; + }; struct surfaceMaterial { // The name of the Material we will use for rendering DECLARE_MATERIALASSET(surfaceMaterial, Material); - + DECLARE_ASSET_SETGET(surfaceMaterial, Material); // The actual Material instance - BaseMatInstance* materialInst; + BaseMatInstance* materialInst; surfaceMaterial() { @@ -174,26 +174,26 @@ public: U32 mPrimCount; }; - struct Geometry - { - void generate(const Vector< PlaneF > &planes, const Vector< Point3F > &tangents, const Vector< surfaceMaterial > surfaceTextures, const Vector< Point2F > texOffset, const Vector< Point2F > texScale, const Vector< bool > horzFlip, const Vector< bool > vertFlip); + struct Geometry + { + void generate(const Vector< PlaneF >& planes, const Vector< Point3F >& tangents, const Vector< surfaceMaterial > surfaceTextures, const Vector< Point2F > texOffset, const Vector< Point2F > texScale, const Vector< bool > horzFlip, const Vector< bool > vertFlip); - Vector< Point3F > points; - Vector< Face > faces; - }; + Vector< Point3F > points; + Vector< Face > faces; + }; - static bool smRenderEdges; + static bool smRenderEdges; // To prevent bitpack overflows. // This is only indirectly enforced by trucation when serializing. static const S32 smMaxSurfaces = 100; public: - + ConvexShape(); virtual ~ConvexShape(); - DECLARE_CONOBJECT( ConvexShape ); + DECLARE_CONOBJECT(ConvexShape); DECLARE_CATEGORY("Object \t Simple"); // ConsoleObject @@ -203,73 +203,76 @@ public: void inspectPostApply() override; bool onAdd() override; void onRemove() override; - void writeFields(Stream &stream, U32 tabStop) override; - bool writeField( StringTableEntry fieldname, const char *value ) override; + void writeFields(Stream& stream, U32 tabStop) override; + bool writeField(StringTableEntry fieldname, const char* value) override; + + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; // NetObject - U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) override; - void unpackUpdate( NetConnection *conn, BitStream *stream ) override; + U32 packUpdate(NetConnection* conn, U32 mask, BitStream* stream) override; + void unpackUpdate(NetConnection* conn, BitStream* stream) override; // SceneObject void onScaleChanged() override; - void setTransform( const MatrixF &mat ) override; - void prepRenderImage( SceneRenderState *state ) override; - void buildConvex( const Box3F &box, Convex *convex ) override; - bool buildPolyList( PolyListContext context, AbstractPolyList *polyList, const Box3F &box, const SphereF &sphere ) override; - bool buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F &box, const SphereF &) override; - bool castRay( const Point3F &start, const Point3F &end, RayInfo *info ) override; - bool collideBox( const Point3F &start, const Point3F &end, RayInfo *info ) override; + void setTransform(const MatrixF& mat) override; + void prepRenderImage(SceneRenderState* state) override; + void buildConvex(const Box3F& box, Convex* convex) override; + bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) override; + bool buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F& box, const SphereF&) override; + bool castRay(const Point3F& start, const Point3F& end, RayInfo* info) override; + bool collideBox(const Point3F& start, const Point3F& end, RayInfo* info) override; - void updateBounds( bool recenter ); + void updateBounds(bool recenter); void recenter(); /// Geometry access. /// @{ - - MatrixF getSurfaceWorldMat( S32 faceid, bool scaled = false ) const; - void cullEmptyPlanes( Vector< U32 > *removedPlanes ); - void exportToCollada(); - void resizePlanes( const Point3F &size ); - void getSurfaceLineList( S32 surfId, Vector< Point3F > &lineList ); - Geometry& getGeometry() { return mGeometry; } - Vector& getSurfaces() { return mSurfaces; } - void getSurfaceTriangles( S32 surfId, Vector< Point3F > *outPoints, Vector< Point2F > *outCoords, bool worldSpace ); + + MatrixF getSurfaceWorldMat(S32 faceid, bool scaled = false) const; + void cullEmptyPlanes(Vector< U32 >* removedPlanes); + void exportToCollada(); + void resizePlanes(const Point3F& size); + void getSurfaceLineList(S32 surfId, Vector< Point3F >& lineList); + Geometry& getGeometry() { return mGeometry; } + Vector& getSurfaces() { return mSurfaces; } + void getSurfaceTriangles(S32 surfId, Vector< Point3F >* outPoints, Vector< Point2F >* outCoords, bool worldSpace); /// @} /// Geometry Visualization. /// @{ - void renderFaceEdges( S32 faceid, const ColorI &color = ColorI::WHITE, F32 lineWidth = 1.0f ); + void renderFaceEdges(S32 faceid, const ColorI& color = ColorI::WHITE, F32 lineWidth = 1.0f); /// @} - String getMaterialName() { return mMaterialName; } + String getMaterialName() { return mMaterialName; } protected: void _updateMaterial(); - void _updateGeometry( bool updateCollision = false ); + void _updateGeometry(bool updateCollision = false); void _updateCollision(); - void _export( OptimizedPolyList *plist, const Box3F &box, const SphereF &sphere ); + void _export(OptimizedPolyList* plist, const Box3F& box, const SphereF& sphere); - void _renderDebug( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *mat ); + void _renderDebug(ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* mat); - static S32 QSORT_CALLBACK _comparePlaneDist( const void *a, const void *b ); + static S32 QSORT_CALLBACK _comparePlaneDist(const void* a, const void* b); - static bool protectedSetSurface( void *object, const char *index, const char *data ); + static bool protectedSetSurface(void* object, const char* index, const char* data); + + static bool protectedSetSurfaceTexture(void* object, const char* index, const char* data); + static bool protectedSetSurfaceUV(void* object, const char* index, const char* data); - static bool protectedSetSurfaceTexture( void *object, const char *index, const char *data ); - static bool protectedSetSurfaceUV(void *object, const char *index, const char *data); - protected: - + DECLARE_MATERIALASSET(ConvexShape, Material); DECLARE_ASSET_SETGET(ConvexShape, Material); // The actual Material instance - BaseMatInstance* mMaterialInst; + BaseMatInstance* mMaterialInst; // The GFX vertex and primitive buffers /*GFXVertexBufferHandle< VertexType > mVertexBuffer; @@ -278,7 +281,7 @@ protected: U32 mVertCount; U32 mPrimCount;*/ - Geometry mGeometry; + Geometry mGeometry; Vector< PlaneF > mPlanes; @@ -291,14 +294,14 @@ protected: Vector< surfaceUV > mSurfaceUVs; Vector< surfaceBuffers > mSurfaceBuffers; - Convex *mConvexList; + Convex* mConvexList; - PhysicsBody *mPhysicsRep; + PhysicsBody* mPhysicsRep; /// Geometry visualization /// @{ - F32 mNormalLength; + F32 mNormalLength; /// @} diff --git a/Engine/source/T3D/gameMode.cpp b/Engine/source/T3D/gameMode.cpp new file mode 100644 index 000000000..32253cca5 --- /dev/null +++ b/Engine/source/T3D/gameMode.cpp @@ -0,0 +1,460 @@ +#include "gameMode.h" + +#ifdef TORQUE_TOOLS +#include "gui/containers/guiDynamicCtrlArrayCtrl.h" +#endif + +#include "console/arrayObject.h" + +IMPLEMENT_CONOBJECT(GameMode); + +IMPLEMENT_CALLBACK(GameMode, onActivated, void, (), (), + "@brief Called when a gamemode is activated.\n\n"); +IMPLEMENT_CALLBACK(GameMode, onDeactivated, void, (), (), + "@brief Called when a gamemode is deactivated.\n\n"); +IMPLEMENT_CALLBACK(GameMode, onSceneLoaded, void, (), (), + "@brief Called when a scene has been loaded and has game mode implications.\n\n"); +IMPLEMENT_CALLBACK(GameMode, onSceneUnloaded, void, (), (), + "@brief Called when a scene has been unloaded and has game mode implications.\n\n"); +IMPLEMENT_CALLBACK(GameMode, onSubsceneLoaded, void, (SubScene*), ("SubScene"), + "@brief Called when a subScene has been loaded and has game mode implications.\n\n"); +IMPLEMENT_CALLBACK(GameMode, onSubsceneUnloaded, void, (SubScene*), ("SubScene"), + "@brief Called when a subScene has been unloaded and has game mode implications.\n\n"); + + +ConsoleType(GameModeList, TypeGameModeList, const char*, "") + +ConsoleGetType(TypeGameModeList) +{ + // Fetch asset Id. + return *((const char**)(dptr)); +} + +//----------------------------------------------------------------------------- + +ConsoleSetType(TypeGameModeList) +{ + // Was a single argument specified? + if (argc == 1) + { + // Yes, so fetch field value. + *((const char**)dptr) = StringTable->insert(argv[0]); + + return; + } + + // Warn. + //Con::warnf("(TypeGameModeList) - Cannot set multiple args to a single asset."); +} + +GameMode::GameMode() : + mGameModeName(StringTable->EmptyString()), + mGameModeDesc(StringTable->EmptyString()), + mIsActive(false), + mIsAlwaysActive(false) +{ + INIT_ASSET(PreviewImage); +} + +void GameMode::initPersistFields() +{ + Parent::initPersistFields(); + + addField("gameModeName", TypeString, Offset(mGameModeName, GameMode), "Human-readable name of the gamemode"); + addField("description", TypeString, Offset(mGameModeDesc, GameMode), "Description of the gamemode"); + + INITPERSISTFIELD_IMAGEASSET(PreviewImage, GameMode, "Preview Image"); + + addField("active", TypeBool, Offset(mIsActive, GameMode), "Is the gamemode active"); + addField("alwaysActive", TypeBool, Offset(mIsAlwaysActive, GameMode), "Is the gamemode always active"); +} + +bool GameMode::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +void GameMode::onRemove() +{ + Parent::onRemove(); +} + +void GameMode::findGameModes(const char* gameModeList, Vector *outGameModes) +{ + if (outGameModes == nullptr) + return; + + Vector gameModeNames; + U32 uCount = StringUnit::getUnitCount(gameModeList, ";"); + for (U32 i = 0; i < uCount; i++) + { + String name = StringUnit::getUnit(gameModeList, i, ";"); + if (!name.isEmpty()) + gameModeNames.push_back(name); + } + + for (U32 i = 0; i < gameModeNames.size(); i++) + { + GameMode* gm; + if (Sim::findObject(gameModeNames[i].c_str(), gm)) + { + outGameModes->push_back(gm); + } + } +} + +void GameMode::setActive(const bool& active) +{ + mIsActive = active; + if (mIsActive) + onActivated_callback(); + else + onDeactivated_callback(); +} + +void GameMode::setAlwaysActive(const bool& alwaysActive) +{ + mIsAlwaysActive = alwaysActive; +} + +DefineEngineMethod(GameMode, isActive, bool, (), , + "Returns if the GameMode is currently active.\n" + "@return The active status of the GameMode") +{ + return object->isActive(); +} + +DefineEngineMethod(GameMode, setActive, void, (bool active), (true), + "Sets the active state of the GameMode.\n" + "@param active A bool of the state the GameMode should be set to") +{ + object->setActive(active); +} + +DefineEngineMethod(GameMode, isALwaysActive, bool, (), , + "Returns if the GameMode is currently active.\n" + "@return The active status of the GameMode") +{ + return object->isActive(); +} + +DefineEngineMethod(GameMode, setAlwaysActive, void, (bool alwaysActive), (true), + "Sets the active state of the GameMode.\n" + "@param active A bool of the state the GameMode should be set to") +{ + object->setAlwaysActive(alwaysActive); +} + +DefineEngineFunction(getGameModesList, ArrayObject*, (), , "") +{ + ArrayObject* dictionary = new ArrayObject(); + dictionary->registerObject(); + + char activeValBuffer[16]; + + for (SimGroup::iterator itr = Sim::getRootGroup()->begin(); itr != Sim::getRootGroup()->end(); itr++) + { + GameMode* gm = dynamic_cast(*itr); + if (gm) + { + dSprintf(activeValBuffer, 16, "%d", (gm->mIsActive || gm->mIsAlwaysActive)); + dictionary->push_back(gm->getName(), activeValBuffer); + } + } + + return dictionary; +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeAssetId +//----------------------------------------------------------------------------- +#ifdef TORQUE_TOOLS + +GuiInspectorTypeGameModeList::GuiInspectorTypeGameModeList() + : mHelper(NULL), + mRollout(NULL), + mArrayCtrl(NULL) +{ + +} +IMPLEMENT_CONOBJECT(GuiInspectorTypeGameModeList); + +ConsoleDocClass(GuiInspectorTypeGameModeList, + "@brief Inspector field type for selecting GameModes\n\n" + "Editor use only.\n\n" + "@internal" +); + +bool GuiInspectorTypeGameModeList::onAdd() +{ + // Skip our parent because we aren't using mEditCtrl + // and according to our parent that would be cause to fail onAdd. + if (!Parent::Parent::onAdd()) + return false; + + if (!mInspector) + return false; + + //build out our list of gamemodes + Vector gameModesList; + + for (SimGroup::iterator itr = Sim::getRootGroup()->begin(); itr != Sim::getRootGroup()->end(); itr++) + { + GameMode* gm = dynamic_cast(*itr); + if (gm) + gameModesList.push_back(gm); + } + + static StringTableEntry sProfile = StringTable->insert("profile"); + setDataField(sProfile, NULL, "GuiInspectorFieldProfile"); + setBounds(0, 0, 100, 18); + + // Allocate our children controls... + + mRollout = new GuiRolloutCtrl(); + mRollout->setMargin(14, 0, 0, 0); + mRollout->setCanCollapse(false); + mRollout->registerObject(); + addObject(mRollout); + + mArrayCtrl = new GuiDynamicCtrlArrayControl(); + mArrayCtrl->setDataField(sProfile, NULL, "GuiInspectorBitMaskArrayProfile"); + mArrayCtrl->setField("autoCellSize", "true"); + mArrayCtrl->setField("fillRowFirst", "true"); + mArrayCtrl->setField("dynamicSize", "true"); + mArrayCtrl->setField("rowSpacing", "4"); + mArrayCtrl->setField("colSpacing", "1"); + mArrayCtrl->setField("frozen", "true"); + mArrayCtrl->registerObject(); + + mRollout->addObject(mArrayCtrl); + + GuiCheckBoxCtrl* pCheckBox = NULL; + + for (S32 i = 0; i < gameModesList.size(); i++) + { + pCheckBox = new GuiCheckBoxCtrl(); + pCheckBox->setText(gameModesList[i]->getName()); + pCheckBox->registerObject(); + mArrayCtrl->addObject(pCheckBox); + + pCheckBox->autoSize(); + + // Override the normal script callbacks for GuiInspectorTypeCheckBox + char szBuffer[512]; + dSprintf(szBuffer, 512, "%d.applyValue();", getId()); + pCheckBox->setField("Command", szBuffer); + } + + mArrayCtrl->setField("frozen", "false"); + mArrayCtrl->refresh(); + + mHelper = new GuiInspectorTypeGameModeListHelper(); + mHelper->init(mInspector, mParent); + mHelper->mParentRollout = mRollout; + mHelper->mParentField = this; + mHelper->setInspectorField(mField, mCaption, mFieldArrayIndex); + mHelper->registerObject(); + mHelper->setExtent(pCheckBox->getExtent()); + mHelper->setPosition(0, 0); + mRollout->addObject(mHelper); + + mRollout->sizeToContents(); + mRollout->instantCollapse(); + + updateValue(); + + return true; +} + +void GuiInspectorTypeGameModeList::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeGameModeList)->setInspectorFieldType("GuiInspectorTypeGameModeList"); +} + +void GuiInspectorTypeGameModeList::childResized(GuiControl* child) +{ + setExtent(mRollout->getExtent()); +} + +bool GuiInspectorTypeGameModeList::resize(const Point2I& newPosition, const Point2I& newExtent) +{ + if (!Parent::resize(newPosition, newExtent)) + return false; + + // Hack... height of 18 is hardcoded + return mHelper->resize(Point2I(0, 0), Point2I(newExtent.x, 18)); +} + +bool GuiInspectorTypeGameModeList::updateRects() +{ + if (!mRollout) + return false; + + bool result = mRollout->setExtent(getExtent()); + + for (U32 i = 0; i < mArrayCtrl->size(); i++) + { + GuiInspectorField* pField = dynamic_cast(mArrayCtrl->at(i)); + if (pField) + if (pField->updateRects()) + result = true; + } + + if (mHelper && mHelper->updateRects()) + result = true; + + return result; +} + +StringTableEntry GuiInspectorTypeGameModeList::getValue() +{ + if (!mRollout) + return StringTable->insert(""); + + String results = ""; + + for (U32 i = 0; i < mArrayCtrl->size(); i++) + { + GuiCheckBoxCtrl* pCheckBox = dynamic_cast(mArrayCtrl->at(i)); + + if (pCheckBox->getStateOn()) + results += pCheckBox->getText() + String(";"); + } + + if (!results.isEmpty()) + return StringTable->insert(results.c_str()); + else + return StringTable->EmptyString(); +} + +void GuiInspectorTypeGameModeList::setValue(StringTableEntry value) +{ + Vector gameModeNames; + U32 uCount = StringUnit::getUnitCount(value, ";"); + for (U32 i = 0; i < uCount; i++) + { + String name = StringUnit::getUnit(value, i, ";"); + if (!name.isEmpty()) + gameModeNames.push_back(name); + } + + for (U32 i = 0; i < mArrayCtrl->size(); i++) + { + GuiCheckBoxCtrl* pCheckBox = dynamic_cast(mArrayCtrl->at(i)); + + for (U32 m = 0; m < gameModeNames.size(); m++) + { + if (gameModeNames[m].equal(pCheckBox->getText())) + { + pCheckBox->setStateOn(true); + } + } + } + + mHelper->setValue(value); +} + +void GuiInspectorTypeGameModeList::updateData() +{ + StringTableEntry data = getValue(); + setData(data); +} + +DefineEngineMethod(GuiInspectorTypeGameModeList, applyValue, void, (), , "") +{ + object->updateData(); +} + +GuiInspectorTypeGameModeListHelper::GuiInspectorTypeGameModeListHelper() + : mButton(NULL), + mParentRollout(NULL), + mParentField(NULL) +{ +} + +IMPLEMENT_CONOBJECT(GuiInspectorTypeGameModeListHelper); + +ConsoleDocClass(GuiInspectorTypeGameModeListHelper, + "@brief Inspector field type support for GameModes lists.\n\n" + "Editor use only.\n\n" + "@internal" +); + +GuiControl* GuiInspectorTypeGameModeListHelper::constructEditControl() +{ + GuiControl* retCtrl = new GuiTextEditCtrl(); + retCtrl->setDataField(StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile"); + retCtrl->setField("hexDisplay", "true"); + + _registerEditControl(retCtrl); + + char szBuffer[512]; + dSprintf(szBuffer, 512, "%d.apply(%d.getText());", mParentField->getId(), retCtrl->getId()); + retCtrl->setField("AltCommand", szBuffer); + retCtrl->setField("Validate", szBuffer); + + mButton = new GuiBitmapButtonCtrl(); + + RectI browseRect(Point2I((getLeft() + getWidth()) - 26, getTop() + 2), Point2I(20, getHeight() - 4)); + dSprintf(szBuffer, 512, "%d.toggleExpanded(false);", mParentRollout->getId()); + mButton->setField("Command", szBuffer); + mButton->setField("buttonType", "ToggleButton"); + mButton->setDataField(StringTable->insert("Profile"), NULL, "GuiInspectorButtonProfile"); + mButton->setBitmap(StringTable->insert("ToolsModule:arrowBtn_N_image")); + mButton->setStateOn(true); + mButton->setExtent(16, 16); + mButton->registerObject(); + addObject(mButton); + + mButton->resize(browseRect.point, browseRect.extent); + + return retCtrl; +} + +bool GuiInspectorTypeGameModeListHelper::resize(const Point2I& newPosition, const Point2I& newExtent) +{ + if (!Parent::resize(newPosition, newExtent)) + return false; + + if (mEdit != NULL) + { + return updateRects(); + } + + return false; +} + +bool GuiInspectorTypeGameModeListHelper::updateRects() +{ + S32 dividerPos, dividerMargin; + mInspector->getDivider(dividerPos, dividerMargin); + Point2I fieldExtent = getExtent(); + Point2I fieldPos = getPosition(); + + mCaptionRect.set(0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y); + mEditCtrlRect.set(fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin - 32, fieldExtent.y); + + bool editResize = mEdit->resize(mEditCtrlRect.point, mEditCtrlRect.extent); + bool buttonResize = false; + + if (mButton != NULL) + { + mButtonRect.set(fieldExtent.x - 26, 2, 16, 16); + buttonResize = mButton->resize(mButtonRect.point, mButtonRect.extent); + } + + return (editResize || buttonResize); +} + +void GuiInspectorTypeGameModeListHelper::setValue(StringTableEntry newValue) +{ + GuiTextEditCtrl* edit = dynamic_cast(mEdit); + edit->setText(newValue); +} +#endif diff --git a/Engine/source/T3D/gameMode.h b/Engine/source/T3D/gameMode.h new file mode 100644 index 000000000..d28deecc0 --- /dev/null +++ b/Engine/source/T3D/gameMode.h @@ -0,0 +1,117 @@ +#pragma once +#ifndef GAME_MODE_H +#define GAME_MODE_H + +#ifdef TORQUE_TOOLS +#ifndef _GUI_INSPECTOR_TYPES_H_ +#include "gui/editor/guiInspectorTypes.h" +#endif +#endif + +#ifndef SUB_SCENE_H +#include "SubScene.h" +#endif + +#include "T3D/assets/ImageAsset.h" + +class GameMode : public SimObject +{ + typedef SimObject Parent; +private: + StringTableEntry mGameModeName; + StringTableEntry mGameModeDesc; + + DECLARE_IMAGEASSET(GameMode, PreviewImage, previewChange, GFXStaticTextureSRGBProfile); + DECLARE_ASSET_SETGET(GameMode, PreviewImage); + + bool mIsActive; + bool mIsAlwaysActive; + +public: + + GameMode(); + ~GameMode() = default; + static void initPersistFields(); + bool onAdd() override; + void onRemove() override; + + bool isActive() { return mIsActive; } + void setActive(const bool& active); + + bool isAlwaysActive() { return mIsAlwaysActive; } + void setAlwaysActive(const bool& alwaysActive); + + DECLARE_CONOBJECT(GameMode); + + static void findGameModes(const char* gameModeList, Vector* outGameModes); + + void previewChange() {} + + DECLARE_CALLBACK(void, onActivated, ()); + DECLARE_CALLBACK(void, onDeactivated, ()); + DECLARE_CALLBACK(void, onSceneLoaded, ()); + DECLARE_CALLBACK(void, onSceneUnloaded, ()); + DECLARE_CALLBACK(void, onSubsceneLoaded, (SubScene*)); + DECLARE_CALLBACK(void, onSubsceneUnloaded, (SubScene*)); +}; + +DefineConsoleType(TypeGameModeList, String) + +#ifdef TORQUE_TOOLS +class GuiInspectorTypeGameModeListHelper; + +class GuiInspectorTypeGameModeList : public GuiInspectorField +{ + typedef GuiInspectorField Parent; +public: + + GuiInspectorTypeGameModeListHelper* mHelper; + GuiRolloutCtrl* mRollout; + GuiDynamicCtrlArrayControl* mArrayCtrl; + Vector mChildren; + + GuiBitmapButtonCtrl* mButton; + RectI mButtonRect; + + DECLARE_CONOBJECT(GuiInspectorTypeGameModeList); + + GuiInspectorTypeGameModeList(); + + // ConsoleObject + bool onAdd() override; + static void consoleInit(); + + // GuiInspectorField + bool resize(const Point2I& newPosition, const Point2I& newExtent) override; + void childResized(GuiControl* child) override; + bool updateRects() override; + void updateData() override; + StringTableEntry getValue() override; + void setValue(StringTableEntry value) override; +}; + +class GuiInspectorTypeGameModeListHelper : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +public: + + GuiInspectorTypeGameModeListHelper(); + + DECLARE_CONOBJECT(GuiInspectorTypeGameModeListHelper); + + GuiBitmapButtonCtrl* mButton; + GuiRolloutCtrl* mParentRollout; + GuiInspectorTypeGameModeList* mParentField; + RectI mButtonRect; + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + GuiControl* constructEditControl() override; + bool resize(const Point2I& newPosition, const Point2I& newExtent) override; + bool updateRects() override; + void setValue(StringTableEntry value) override; +}; +#endif +#endif diff --git a/Engine/source/T3D/prefab.cpp b/Engine/source/T3D/prefab.cpp index 1351fd92b..d1e828765 100644 --- a/Engine/source/T3D/prefab.cpp +++ b/Engine/source/T3D/prefab.cpp @@ -371,6 +371,12 @@ void Prefab::_loadFile( bool addFileNotify ) return; } + SimObjectPtr rootScene = Scene::getRootScene(); + if(rootScene.isValid()) + { + rootScene->addDynamicObject(group); + } + if ( addFileNotify ) Torque::FS::AddChangeNotification( mFilename, this, &Prefab::_onFileChanged ); diff --git a/Engine/source/T3D/trigger.cpp b/Engine/source/T3D/trigger.cpp index 0320d6208..dcccd0778 100644 --- a/Engine/source/T3D/trigger.cpp +++ b/Engine/source/T3D/trigger.cpp @@ -173,7 +173,7 @@ Trigger::Trigger() mPhysicsRep = NULL; mTripOnce = false; mTrippedBy = 0xFFFFFFFF; - mTripCondition = ""; + mTripIf = ""; //Default up a basic square Point3F vecs[3] = { Point3F(1.0, 0.0, 0.0), @@ -379,7 +379,7 @@ void Trigger::initPersistFields() "representing the edges extending from the corner.\n"); addField("TripOnce", TypeBool, Offset(mTripOnce, Trigger),"Do we trigger callacks just the once?"); - addField("TripCondition", TypeRealString, Offset(mTripCondition, Trigger),"evaluation condition to trip callbacks (true/false)"); + addField("tripIf", TypeRealString, Offset(mTripIf, Trigger),"evaluation condition to trip callbacks (true/false)"); addField("TrippedBy", TypeGameTypeMasksType, Offset(mTrippedBy, Trigger), "typemask filter"); addProtectedField("enterCommand", TypeCommand, Offset(mEnterCommand, Trigger), &setEnterCmd, &defaultProtectedGetFn, "The command to execute when an object enters this trigger. Object id stored in %%obj. Maximum 1023 characters." ); @@ -697,13 +697,13 @@ bool Trigger::testTrippable() bool Trigger::testCondition() { - if (mTripCondition.isEmpty()) + if (mTripIf.isEmpty()) return true; //we've got no tests to run so just do it //test the mapper plugged in condition line String resVar = getIdString() + String(".result"); Con::setBoolVariable(resVar.c_str(), false); - String command = resVar + "=" + mTripCondition + ";"; + String command = resVar + "=" + mTripIf + ";"; Con::evaluatef(command.c_str()); if (Con::getBoolVariable(resVar.c_str()) == 1) { diff --git a/Engine/source/T3D/trigger.h b/Engine/source/T3D/trigger.h index 7cd00dd34..9b9190e67 100644 --- a/Engine/source/T3D/trigger.h +++ b/Engine/source/T3D/trigger.h @@ -87,7 +87,7 @@ class Trigger : public GameBase bool mTripped; S32 mTrippedBy; - String mTripCondition; + String mTripIf; String mEnterCommand; String mLeaveCommand; String mTickCommand; diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 634b60225..8c6bfdec2 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -498,6 +498,7 @@ public: FIELD_ComponentInspectors = BIT(1), ///< Custom fields used by components. They are likely to be non-standard size/configuration, so ///< They are handled specially FIELD_CustomInspectors = BIT(2), ///< Display as a button in inspectors. + FIELD_SpecialtyArrayField = BIT(3) }; struct Field diff --git a/Engine/source/console/persistenceManager.cpp b/Engine/source/console/persistenceManager.cpp index 630dad448..0cb6671c4 100644 --- a/Engine/source/console/persistenceManager.cpp +++ b/Engine/source/console/persistenceManager.cpp @@ -652,6 +652,31 @@ S32 PersistenceManager::getPropertyIndex(ParsedObject* parsedObject, const char* return propertyIndex; } +S32 PersistenceManager::getSpecialPropertyAtOffset(ParsedObject* parsedObject, const char* fieldName, U32 offsetPos) +{ + S32 propertyIndex = -1; + + if (!parsedObject) + return propertyIndex; + + U32 hitCount = -1; + for (U32 i = 0; i < parsedObject->properties.size(); i++) + { + if (dStricmp(fieldName, parsedObject->properties[i].name) == 0) + { + hitCount++; + + if (hitCount == offsetPos) + { + propertyIndex = i; + break; + } + } + } + + return propertyIndex; +} + char* PersistenceManager::getObjectIndent(ParsedObject* object) { char* indent = Con::getReturnBuffer(2048); @@ -1361,166 +1386,335 @@ void PersistenceManager::updateObject(SimObject* object, ParsedObject* parentObj if ( f->type >= AbstractClassRep::ARCFirstCustomField || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors)) continue; - for(U32 j = 0; S32(j) < f->elementCount; j++) + if (f->flag.test(AbstractClassRep::FIELD_SpecialtyArrayField)) { - const char* value = getFieldValue(object, f->pFieldname, j); + U32 fieldArraySize = object->getSpecialFieldSize(f->pFieldname); - // Make sure we got a value - if (!value) - continue; - - // Let's see if this field is already in the file - S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); - - if (propertyIndex > -1) + for(U32 j = 0; j < fieldArraySize; j++) { - ParsedProperty& prop = parsedObject->properties[propertyIndex]; + const char* value = object->getSpecialFieldOut(f->pFieldname, j); - // If this field is on the remove list then remove it and continue - if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) - { - removeField( parsedObject->properties[ propertyIndex ] ); - dFree( value ); + // Make sure we got a value + if (!value) continue; - } - // Run the parsed value through the console system conditioners so - // that it will better match the data we got back from the object. - const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + // Let's see if this field is already in the file + S32 propertyIndex = getSpecialPropertyAtOffset(parsedObject, f->pFieldname, j); - // If our data doesn't match then we get to update it. - // - // As for copy-sources, we just assume here that if a property setting - // is there in the file, the user does not want it inherited from the copy-source - // even in the case the actual values are identical. - - if( dStricmp(value, evalue) != 0 ) + if (propertyIndex > -1) { - if( value[ 0 ] == '\0' && - dStricmp( getFieldValue( defaultObject, f->pFieldname, j ), value ) == 0 && - ( !object->getCopySource() || dStricmp( getFieldValue( object->getCopySource(), f->pFieldname, j ), value ) == 0 ) ) + ParsedProperty& prop = parsedObject->properties[propertyIndex]; + + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, f->pFieldname, j)) { - removeField( prop ); + removeField(parsedObject->properties[propertyIndex]); + dFree(value); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + + // If our data doesn't match then we get to update it. + // + // As for copy-sources, we just assume here that if a property setting + // is there in the file, the user does not want it inherited from the copy-source + // even in the case the actual values are identical. + + if (dStricmp(value, evalue) != 0) + { + if (value[0] == '\0' && + dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value) == 0 && + (!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value) == 0)) + { + removeField(prop); + } + else + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + } + else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + } + else + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + } + } + } + else + { + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, f->pFieldname, j)) + { + dFree(value); + continue; + } + + bool mustUpdate = false; + + // If we didn't find the property in the ParsedObject + // then we need to compare against the default value + // for this property and save it out if it is different + + const char* defaultValue = defaultObject->getSpecialFieldOut(f->pFieldname, j); + if (!defaultValue || dStricmp(value, defaultValue) != 0) + { + // Value differs. Check whether it also differs from the + // value in the copy source if there is one. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (!copySourceValue || dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + else + mustUpdate = true; } else + { + // Value does not differ. If it differs from the copy source's value, + // though, we still want to write it out as otherwise we'll see the + // copy source's value override us. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (copySourceValue && dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + } + + // The default value for most string type fields is + // NULL so we can't just continue here or we'd never ever + // write them out... + // + //if (!defaultValue) + // continue; + + // If the object's value is different from the default + // value then add it to the ParsedObject's newLines + if (mustUpdate) { // TODO: This should be wrapped in a helper method... probably. // Detect and collapse relative path information - if (f->type == TypeFilename || - f->type == TypeStringFilename || - f->type == TypeImageFilename || - f->type == TypePrefabFilename || - f->type == TypeShapeFilename || - f->type == TypeSoundFilename ) + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) { char fnBuf[1024]; Con::collapseScriptFilename(fnBuf, 1024, value); - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); } - else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + else if (f->type == TypeCommand) { char cmdBuf[1024]; expandEscape(cmdBuf, value); - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); } else - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); } + + if (defaultValue) + dFree(defaultValue); } + + //dFree(value); } - else + } + else + { + for (U32 j = 0; S32(j) < f->elementCount; j++) { - // No need to process a removed field that doesn't exist in the file - if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) - { - dFree( value ); + const char* value = getFieldValue(object, f->pFieldname, j); + + // Make sure we got a value + if (!value) continue; - } - - bool mustUpdate = false; - // If we didn't find the property in the ParsedObject - // then we need to compare against the default value - // for this property and save it out if it is different + // Let's see if this field is already in the file + S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); - const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); - if( !defaultValue || dStricmp( value, defaultValue ) != 0 ) + if (propertyIndex > -1) { - // Value differs. Check whether it also differs from the - // value in the copy source if there is one. - - if( object->getCopySource() ) + ParsedProperty& prop = parsedObject->properties[propertyIndex]; + + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) { - const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); - if( !copySourceValue || dStricmp( copySourceValue, value ) != 0 ) - mustUpdate = true; - - if( copySourceValue ) - dFree( copySourceValue ); + removeField(parsedObject->properties[propertyIndex]); + dFree(value); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + + // If our data doesn't match then we get to update it. + // + // As for copy-sources, we just assume here that if a property setting + // is there in the file, the user does not want it inherited from the copy-source + // even in the case the actual values are identical. + + if (dStricmp(value, evalue) != 0) + { + if (value[0] == '\0' && + dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value) == 0 && + (!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value) == 0)) + { + removeField(prop); + } + else + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + } + else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + } + else + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + } } - else - mustUpdate = true; } else { - // Value does not differ. If it differs from the copy source's value, - // though, we still want to write it out as otherwise we'll see the - // copy source's value override us. - - if( object->getCopySource() ) + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) { - const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); - if( copySourceValue && dStricmp( copySourceValue, value ) != 0 ) + dFree(value); + continue; + } + + bool mustUpdate = false; + + // If we didn't find the property in the ParsedObject + // then we need to compare against the default value + // for this property and save it out if it is different + + const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); + if (!defaultValue || dStricmp(value, defaultValue) != 0) + { + // Value differs. Check whether it also differs from the + // value in the copy source if there is one. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (!copySourceValue || dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + else mustUpdate = true; - - if( copySourceValue ) - dFree( copySourceValue ); - } - } - - // The default value for most string type fields is - // NULL so we can't just continue here or we'd never ever - // write them out... - // - //if (!defaultValue) - // continue; - - // If the object's value is different from the default - // value then add it to the ParsedObject's newLines - if ( mustUpdate ) - { - // TODO: This should be wrapped in a helper method... probably. - // Detect and collapse relative path information - if (f->type == TypeFilename || - f->type == TypeStringFilename || - f->type == TypeImageFilename || - f->type == TypePrefabFilename || - f->type == TypeShapeFilename || - f->type == TypeSoundFilename ) - { - char fnBuf[1024]; - Con::collapseScriptFilename(fnBuf, 1024, value); - - newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); - } - else if (f->type == TypeCommand) - { - char cmdBuf[1024]; - expandEscape(cmdBuf, value); - - newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); } else - newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); + { + // Value does not differ. If it differs from the copy source's value, + // though, we still want to write it out as otherwise we'll see the + // copy source's value override us. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (copySourceValue && dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + } + + // The default value for most string type fields is + // NULL so we can't just continue here or we'd never ever + // write them out... + // + //if (!defaultValue) + // continue; + + // If the object's value is different from the default + // value then add it to the ParsedObject's newLines + if (mustUpdate) + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); + } + else if (f->type == TypeCommand) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); + } + else + newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); + } + + if (defaultValue) + dFree(defaultValue); } - if (defaultValue) - dFree( defaultValue ); + dFree(value); } - - dFree( value ); } } diff --git a/Engine/source/console/persistenceManager.h b/Engine/source/console/persistenceManager.h index bba565da2..de8608ebb 100644 --- a/Engine/source/console/persistenceManager.h +++ b/Engine/source/console/persistenceManager.h @@ -221,6 +221,12 @@ protected: // Attempts to look up the property in the ParsedObject S32 getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos = 0); + // Attempts to look up the special array property in the ParsedObject + // This is distinct from getPropertyIndex because while that assumes it's an array'd field + // This figures the property in question is one that is specially tagged for implicit, arbitrarily sized lists + // Like ConvexShape's 'surfaces' or a spline's 'node' properties + S32 getSpecialPropertyAtOffset(ParsedObject* parsedObject, const char* fieldName, U32 offsetPos); + // Gets the amount of indent on the ParsedObject. char* getObjectIndent(ParsedObject* object); @@ -320,4 +326,4 @@ public: DECLARE_CONOBJECT(PersistenceManager); }; -#endif \ No newline at end of file +#endif diff --git a/Engine/source/console/simObject.cpp b/Engine/source/console/simObject.cpp index 7a9bb4404..b1863d5bd 100644 --- a/Engine/source/console/simObject.cpp +++ b/Engine/source/console/simObject.cpp @@ -101,6 +101,8 @@ SimObjectId SimObject::smForcedId = 0; bool SimObject::preventNameChanging = false; IMPLEMENT_CALLBACK(SimObject, onInspectPostApply, void, (SimObject* obj), (obj), "Generic callback for when an object is edited"); +IMPLEMENT_CALLBACK(SimObject, onSelected, void, (SimObject* obj), (obj), "Generic callback for when an object is selected"); +IMPLEMENT_CALLBACK(SimObject, onUnselected, void, (SimObject* obj), (obj), "Generic callback for when an object is un-selected"); namespace Sim { @@ -527,6 +529,14 @@ bool SimObject::save(const char *pcFileName, bool bOnlySelected, const char *pre } +bool SimObject::saveAppend(const char* pcFileName, bool bOnlySelected, const char* preappend) +{ + + + return true; + +} + //----------------------------------------------------------------------------- SimPersistID* SimObject::getOrCreatePersistentId() @@ -2207,11 +2217,13 @@ void SimObject::setSelected( bool sel ) { mFlags.set( Selected ); _onSelected(); + onSelected_callback(this); } else { mFlags.clear( Selected ); _onUnselected(); + onUnselected_callback(this); } } diff --git a/Engine/source/console/simObject.h b/Engine/source/console/simObject.h index 563f86e12..4868a4585 100644 --- a/Engine/source/console/simObject.h +++ b/Engine/source/console/simObject.h @@ -530,6 +530,9 @@ class SimObject: public ConsoleObject, public TamlCallbacks void setDataFieldType(const U32 fieldTypeId, StringTableEntry slotName, const char *array); void setDataFieldType(const char *typeName, StringTableEntry slotName, const char *array); + virtual U32 getSpecialFieldSize(StringTableEntry fieldName) { return 0; } + virtual const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) { return NULL; } + /// Get reference to the dictionary containing dynamic fields. /// /// See @ref simobject_console "here" for a detailed discussion of what this @@ -579,6 +582,7 @@ class SimObject: public ConsoleObject, public TamlCallbacks /// Save object as a TorqueScript File. virtual bool save( const char* pcFilePath, bool bOnlySelected = false, const char *preappend = NULL ); + virtual bool saveAppend(const char* pcFilePath, bool bOnlySelected = false, const char* preappend = NULL); /// Check if a method exists in the objects current namespace. virtual bool isMethod( const char* methodName ); @@ -981,6 +985,8 @@ class SimObject: public ConsoleObject, public TamlCallbacks DECLARE_CONOBJECT( SimObject ); DECLARE_CALLBACK(void, onInspectPostApply, (SimObject* obj)); + DECLARE_CALLBACK(void, onSelected, (SimObject* obj)); + DECLARE_CALLBACK(void, onUnselected, (SimObject* obj)); static SimObject* __findObject( const char* id ) { return Sim::findObject( id ); } static const char* __getObjectId( ConsoleObject* object ) diff --git a/Engine/source/environment/decalRoad.cpp b/Engine/source/environment/decalRoad.cpp index b5ba0c8d4..97003e80e 100644 --- a/Engine/source/environment/decalRoad.cpp +++ b/Engine/source/environment/decalRoad.cpp @@ -322,7 +322,7 @@ void DecalRoad::initPersistFields() addGroup( "Internal" ); addProtectedField( "node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, - "Do not modify, for internal use." ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -473,6 +473,36 @@ bool DecalRoad::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 DecalRoad::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("node")) + { + return mNodes.size(); + } + + return 0; +} + +const char* DecalRoad::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (fieldName == StringTable->insert("node")) + { + if (index >= mNodes.size()) + return NULL; + + const RoadNode& node = mNodes[index]; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "%f %f %f %f", node.point.x, node.point.y, node.point.z, node.width); + + return StringTable->insert(buffer); + } + + return NULL; +} + + void DecalRoad::onEditorEnable() { } diff --git a/Engine/source/environment/decalRoad.h b/Engine/source/environment/decalRoad.h index f1aecf187..481e5b92a 100644 --- a/Engine/source/environment/decalRoad.h +++ b/Engine/source/environment/decalRoad.h @@ -169,6 +169,9 @@ public: void onStaticModified(const char* slotName, const char*newValue = NULL) override; void writeFields(Stream &stream, U32 tabStop) override; bool writeField( StringTableEntry fieldname, const char *value ) override; + + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; // NetObject U32 packUpdate(NetConnection *, U32, BitStream *) override; diff --git a/Engine/source/environment/meshRoad.cpp b/Engine/source/environment/meshRoad.cpp index 93593df7a..6785db065 100644 --- a/Engine/source/environment/meshRoad.cpp +++ b/Engine/source/environment/meshRoad.cpp @@ -956,10 +956,10 @@ void MeshRoad::initPersistFields() addGroup( "Internal" ); addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, - "Do not modify, for internal use." ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); addProtectedField( "ProfileNode", TypeString, 0, &addProfileNodeFromField, &emptyStringProtectedGetFn, - "Do not modify, for internal use." ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -1168,6 +1168,60 @@ bool MeshRoad::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 MeshRoad::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("Node")) + { + return mNodes.size(); + } + else if (fieldName == StringTable->insert("ProfileNode")) + { + return mSideProfile.mNodes.size(); + } + + return 0; +} + +const char* MeshRoad::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (fieldName == StringTable->insert("Node")) + { + if (index >= mNodes.size()) + return NULL; + + const MeshRoadNode& node = mNodes[index]; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "Node = \"%g %g %g %g %g %g %g %g\";", node.point.x, node.point.y, node.point.z, node.width, node.depth, node.normal.x, node.normal.y, node.normal.z); + + return StringTable->insert(buffer); + } + else if (fieldName == StringTable->insert("ProfileNode")) + { + Point3F nodePos = mSideProfile.mNodes[index].getPosition(); + U8 smooth, mtrl; + + if (index) + mtrl = mSideProfile.mSegMtrls[index - 1]; + else + mtrl = 0; + + if (mSideProfile.mNodes[index].isSmooth()) + smooth = 1; + else + smooth = 0; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "ProfileNode = \"%.6f %.6f %d %d\";", nodePos.x, nodePos.y, smooth, mtrl); + + return StringTable->insert(buffer); + } + + return NULL; +} + void MeshRoad::onEditorEnable() { } diff --git a/Engine/source/environment/meshRoad.h b/Engine/source/environment/meshRoad.h index 17747e83b..437d76a6d 100644 --- a/Engine/source/environment/meshRoad.h +++ b/Engine/source/environment/meshRoad.h @@ -525,6 +525,9 @@ public: void writeFields(Stream &stream, U32 tabStop) override; bool writeField( StringTableEntry fieldname, const char *value ) override; + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; + // NetObject U32 packUpdate(NetConnection *, U32, BitStream *) override; void unpackUpdate(NetConnection *, BitStream *) override; diff --git a/Engine/source/environment/river.cpp b/Engine/source/environment/river.cpp index 725f4a350..98088f737 100644 --- a/Engine/source/environment/river.cpp +++ b/Engine/source/environment/river.cpp @@ -648,7 +648,8 @@ void River::initPersistFields() addGroup( "Internal" ); - addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, "For internal use, do not modify." ); + addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, "For internal use, do not modify.", + AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -785,6 +786,38 @@ bool River::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 River::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("node")) + { + return mNodes.size(); + } + + return 0; +} + +const char* River::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (fieldName == StringTable->insert("node")) + { + if (index >= mNodes.size()) + return NULL; + + const RiverNode& node = mNodes[index]; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "Node = \"%f %f %f %f %f %f %f %f\";", node.point.x, node.point.y, node.point.z, + node.width, + node.depth, + node.normal.x, node.normal.y, node.normal.z); + + return StringTable->insert(buffer); + } + + return NULL; +} + void River::innerRender( SceneRenderState *state ) { GFXDEBUGEVENT_SCOPE( River_innerRender, ColorI( 255, 0, 0 ) ); diff --git a/Engine/source/environment/river.h b/Engine/source/environment/river.h index ded0e7e37..5676b7505 100644 --- a/Engine/source/environment/river.h +++ b/Engine/source/environment/river.h @@ -394,6 +394,9 @@ public: void writeFields(Stream &stream, U32 tabStop) override; bool writeField( StringTableEntry fieldname, const char *value ) override; + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; + // NetObject U32 packUpdate(NetConnection *, U32, BitStream *) override; void unpackUpdate(NetConnection *, BitStream *) override; diff --git a/Engine/source/gui/core/guiControl.h b/Engine/source/gui/core/guiControl.h index 1cdeb4bab..c4e2649ba 100644 --- a/Engine/source/gui/core/guiControl.h +++ b/Engine/source/gui/core/guiControl.h @@ -372,6 +372,13 @@ class GuiControl : public SimGroup inline const S32 getHorizSizing() const { return mHorizSizing; } inline const S32 getVertSizing() const { return mVertSizing; } + + void setHorizSizing(horizSizingOptions horizSizing) { + mHorizSizing = horizSizing; + } + void setVertSizing(vertSizingOptions vertSizing) { + mVertSizing = vertSizing; + } /// @} diff --git a/Engine/source/gui/editor/inspector/group.cpp b/Engine/source/gui/editor/inspector/group.cpp index 64ed2b352..2f517d52d 100644 --- a/Engine/source/gui/editor/inspector/group.cpp +++ b/Engine/source/gui/editor/inspector/group.cpp @@ -178,7 +178,7 @@ GuiInspectorField* GuiInspectorGroup::constructField( S32 fieldType ) // return our new datablock field with correct datablock type enumeration info return dbFieldClass; -} + } // Nope, not a datablock. So maybe it has a valid inspector field override we can use? if(!cbt->getInspectorFieldType()) @@ -641,40 +641,50 @@ void GuiInspectorGroup::addInspectorField(StringTableEntry name, StringTableEntr { S32 fieldType = -1; - if (typeName == StringTable->insert("int")) - fieldType = TypeS32; - else if (typeName == StringTable->insert("float")) - fieldType = TypeF32; - else if (typeName == StringTable->insert("vector")) - fieldType = TypePoint3F; - else if (typeName == StringTable->insert("vector2")) - fieldType = TypePoint2F; - else if (typeName == StringTable->insert("material")) - fieldType = TypeMaterialAssetId; - else if (typeName == StringTable->insert("image")) - fieldType = TypeImageAssetId; - else if (typeName == StringTable->insert("shape")) - fieldType = TypeShapeAssetId; - else if (typeName == StringTable->insert("sound")) - fieldType = TypeSoundAssetId; - else if (typeName == StringTable->insert("bool")) - fieldType = TypeBool; - else if (typeName == StringTable->insert("object")) - fieldType = TypeSimObjectPtr; - else if (typeName == StringTable->insert("string")) - fieldType = TypeString; - else if (typeName == StringTable->insert("colorI")) - fieldType = TypeColorI; - else if (typeName == StringTable->insert("colorF")) - fieldType = TypeColorF; - else if (typeName == StringTable->insert("ease")) - fieldType = TypeEaseF; - else if (typeName == StringTable->insert("command")) - fieldType = TypeCommand; - else if (typeName == StringTable->insert("filename")) - fieldType = TypeStringFilename; + String typeNameTyped = typeName; + if (!typeNameTyped.startsWith("Type")) + typeNameTyped = String("Type") + typeNameTyped; + + ConsoleBaseType* typeRef = AbstractClassRep::getTypeByName(typeNameTyped.c_str()); + if(typeRef) + { + fieldType = typeRef->getTypeID(); + } else - fieldType = -1; + { + if (typeName == StringTable->insert("int")) + fieldType = TypeS32; + else if (typeName == StringTable->insert("float")) + fieldType = TypeF32; + else if (typeName == StringTable->insert("vector")) + fieldType = TypePoint3F; + else if (typeName == StringTable->insert("vector2")) + fieldType = TypePoint2F; + else if (typeName == StringTable->insert("material")) + fieldType = TypeMaterialAssetId; + else if (typeName == StringTable->insert("image")) + fieldType = TypeImageAssetId; + else if (typeName == StringTable->insert("shape")) + fieldType = TypeShapeAssetId; + else if (typeName == StringTable->insert("sound")) + fieldType = TypeSoundAssetId; + else if (typeName == StringTable->insert("bool")) + fieldType = TypeBool; + else if (typeName == StringTable->insert("object")) + fieldType = TypeSimObjectPtr; + else if (typeName == StringTable->insert("string")) + fieldType = TypeString; + else if (typeName == StringTable->insert("colorI")) + fieldType = TypeColorI; + else if (typeName == StringTable->insert("colorF")) + fieldType = TypeColorF; + else if (typeName == StringTable->insert("ease")) + fieldType = TypeEaseF; + else if (typeName == StringTable->insert("command")) + fieldType = TypeCommand; + else if (typeName == StringTable->insert("filename")) + fieldType = TypeStringFilename; + } GuiInspectorField* fieldGui; diff --git a/Engine/source/gui/editor/inspector/variableInspector.cpp b/Engine/source/gui/editor/inspector/variableInspector.cpp index 6afcb46e5..c856c8b1a 100644 --- a/Engine/source/gui/editor/inspector/variableInspector.cpp +++ b/Engine/source/gui/editor/inspector/variableInspector.cpp @@ -176,38 +176,53 @@ void GuiVariableInspector::addField(const char* name, const char* label, const c //find the field type S32 fieldTypeMask = -1; - if (newField->mFieldTypeName == StringTable->insert("int")) - fieldTypeMask = TypeS32; - else if (newField->mFieldTypeName == StringTable->insert("float")) - fieldTypeMask = TypeF32; - else if (newField->mFieldTypeName == StringTable->insert("vector")) - fieldTypeMask = TypePoint3F; - else if (newField->mFieldTypeName == StringTable->insert("vector2")) - fieldTypeMask = TypePoint2F; - else if (newField->mFieldTypeName == StringTable->insert("material")) - fieldTypeMask = TypeMaterialAssetId; - else if (newField->mFieldTypeName == StringTable->insert("image")) - fieldTypeMask = TypeImageAssetId; - else if (newField->mFieldTypeName == StringTable->insert("shape")) - fieldTypeMask = TypeShapeAssetId; - else if (newField->mFieldTypeName == StringTable->insert("bool")) - fieldTypeMask = TypeBool; - else if (newField->mFieldTypeName == StringTable->insert("object")) - fieldTypeMask = TypeSimObjectPtr; - else if (newField->mFieldTypeName == StringTable->insert("string")) - fieldTypeMask = TypeString; - else if (newField->mFieldTypeName == StringTable->insert("colorI")) - fieldTypeMask = TypeColorI; - else if (newField->mFieldTypeName == StringTable->insert("colorF")) - fieldTypeMask = TypeColorF; - else if (newField->mFieldTypeName == StringTable->insert("ease")) - fieldTypeMask = TypeEaseF; - else if (newField->mFieldTypeName == StringTable->insert("command")) - fieldTypeMask = TypeCommand; - else if (newField->mFieldTypeName == StringTable->insert("filename")) - fieldTypeMask = TypeStringFilename; + String typeNameTyped = typeName; + if (!typeNameTyped.startsWith("Type")) + typeNameTyped = String("Type") + typeNameTyped; + + ConsoleBaseType* typeRef = AbstractClassRep::getTypeByName(typeNameTyped.c_str()); + if (typeRef) + { + fieldTypeMask = typeRef->getTypeID(); + + if (!typeRef->getInspectorFieldType()) + fieldTypeMask = TypeString; + + newField->mFieldTypeName = StringTable->insert(typeRef->getTypeName()); + } else - fieldTypeMask = -1; + { + if (newField->mFieldTypeName == StringTable->insert("int")) + fieldTypeMask = TypeS32; + else if (newField->mFieldTypeName == StringTable->insert("float")) + fieldTypeMask = TypeF32; + else if (newField->mFieldTypeName == StringTable->insert("vector")) + fieldTypeMask = TypePoint3F; + else if (newField->mFieldTypeName == StringTable->insert("vector2")) + fieldTypeMask = TypePoint2F; + else if (newField->mFieldTypeName == StringTable->insert("material")) + fieldTypeMask = TypeMaterialAssetId; + else if (newField->mFieldTypeName == StringTable->insert("image")) + fieldTypeMask = TypeImageAssetId; + else if (newField->mFieldTypeName == StringTable->insert("shape")) + fieldTypeMask = TypeShapeAssetId; + else if (newField->mFieldTypeName == StringTable->insert("bool")) + fieldTypeMask = TypeBool; + else if (newField->mFieldTypeName == StringTable->insert("object")) + fieldTypeMask = TypeSimObjectPtr; + else if (newField->mFieldTypeName == StringTable->insert("string")) + fieldTypeMask = TypeString; + else if (newField->mFieldTypeName == StringTable->insert("colorI")) + fieldTypeMask = TypeColorI; + else if (newField->mFieldTypeName == StringTable->insert("colorF")) + fieldTypeMask = TypeColorF; + else if (newField->mFieldTypeName == StringTable->insert("ease")) + fieldTypeMask = TypeEaseF; + else if (newField->mFieldTypeName == StringTable->insert("command")) + fieldTypeMask = TypeCommand; + else if (newField->mFieldTypeName == StringTable->insert("filename")) + fieldTypeMask = TypeStringFilename; + } newField->mFieldType = fieldTypeMask; // diff --git a/Engine/source/scene/sceneObject.cpp b/Engine/source/scene/sceneObject.cpp index 435da6a54..c44746c88 100644 --- a/Engine/source/scene/sceneObject.cpp +++ b/Engine/source/scene/sceneObject.cpp @@ -1724,7 +1724,7 @@ void SceneObject::updateRenderChangesByParent(){ MatrixF offset; offset.mul(renderXform, xform); - MatrixF mat; + MatrixF mat; //add the "offset" caused by the parents change, and add it to it's own // This is needed by objects that update their own render transform thru interpolate tick @@ -2013,3 +2013,8 @@ void SceneObject::onNewParent(SceneObject *newParent) { if (isServerObject()) on void SceneObject::onLostParent(SceneObject *oldParent) { if (isServerObject()) onLostParent_callback(oldParent); } void SceneObject::onNewChild(SceneObject *newKid) { if (isServerObject()) onNewChild_callback(newKid); } void SceneObject::onLostChild(SceneObject *lostKid) { if (isServerObject()) onLostChild_callback(lostKid); } + +IMPLEMENT_CALLBACK(SceneObject, onSaving, void, (const char* fileName), (fileName), + "@brief Called when a saving is occuring to allow objects to special-handle prepwork for saving if required.\n\n" + + "@param fileName The level file being saved\n"); diff --git a/Engine/source/scene/sceneObject.h b/Engine/source/scene/sceneObject.h index 1ebe3fd72..e12f1a0f8 100644 --- a/Engine/source/scene/sceneObject.h +++ b/Engine/source/scene/sceneObject.h @@ -913,6 +913,8 @@ class SceneObject : public NetObject, public ProcessObject DECLARE_CALLBACK(void, onLostChild, (SceneObject *subObject)); // PATHSHAPE END + DECLARE_CALLBACK(void, onSaving, (const char* fileName)); + virtual void getUtilizedAssets(Vector* usedAssetsList) {} }; diff --git a/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript b/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript index fc6e0d33c..05438e600 100644 --- a/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript +++ b/Templates/BaseGame/game/core/utility/scripts/helperFunctions.tscript @@ -325,6 +325,8 @@ function replaceInFile(%fileName, %fromWord, %toWord) //Go through our scriptfile and replace the old namespace with the new %editedFileContents = ""; + %lineArray = new ArrayObject(){}; + %file = new FileObject(); if ( %file.openForRead( %fileName ) ) { @@ -334,6 +336,8 @@ function replaceInFile(%fileName, %fromWord, %toWord) %line = trim( %line ); %editedFileContents = %editedFileContents @ strreplace(%line, %fromWord, %toWord) @ "\n"; + + %lineArray.add(strreplace(%line, %fromWord, %toWord)); } %file.close(); @@ -341,12 +345,18 @@ function replaceInFile(%fileName, %fromWord, %toWord) if(%editedFileContents !$= "") { - %file.openForWrite(%fileName); - - %file.writeline(%editedFileContents); - - %file.close(); + if( %file.openForWrite(%fileName) ) + { + for(%i=0; %i < %lineArray.getCount(); %i++) + { + %file.writeline(%lineArray.getKey(%i)); + } + + %file.close(); + } } + + %lineArray.delete(); } //------------------------------------------------------------------------------ diff --git a/Templates/BaseGame/game/core/utility/scripts/scene.tscript b/Templates/BaseGame/game/core/utility/scripts/scene.tscript index 4062c4749..2d8897dee 100644 --- a/Templates/BaseGame/game/core/utility/scripts/scene.tscript +++ b/Templates/BaseGame/game/core/utility/scripts/scene.tscript @@ -1,52 +1,22 @@ function callGamemodeFunction(%gameModeFuncName, %arg0, %arg1, %arg2, %arg3, %arg4, %arg5, %arg6) { - %activeSceneCount = getSceneCount(); - - %hasGameMode = 0; - for(%i=0; %i < %activeSceneCount; %i++) + %validGameModeCall = false; + %gamemodeList = getGameModesList(); + %gameModeCount = %gamemodeList.count(); + for(%i=0; %i < %gameModeCount; %i++) { - %gamemodeName = getScene(%i).gameModeName; - if(%gamemodeName !$= "") + %gameModeObj = %gamemodeList.getKey(%i); + %active = %gamemodeList.getValue(%i); + + if(!isObject(%gameModeObj) || !%active) + continue; + + if(%gameModeObj.isMethod(%gameModeFuncName)) { - //if the scene defines a game mode, go ahead and envoke it here - if(isObject(%gamemodeName) && %gamemodeName.isMethod(%gameModeFuncName)) - { - eval(%gamemodeName @ "."@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - else - { - //if we don't have an object, attempt the static call - if(isMethod(%gamemodeName, %gameModeFuncName)) - { - eval(%gamemodeName @ "::"@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - } + eval(%gameModeObj @ "."@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); + %validGameModeCall = true; } } - //if none of our scenes have gamemodes, we need to kick off a default - if(%hasGameMode == 0) - { - %defaultModeName = ProjectSettings.value("Gameplay/GameModes/defaultModeName"); - if(%defaultModeName !$= "") - { - if(isObject(%defaultModeName) && %defaultModeName.isMethod(%gameModeFuncName)) - { - eval(%defaultModeName @ "."@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - else - { - if(isMethod(%defaultModeName, %gameModeFuncName)) - { - eval(%defaultModeName @ "::"@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - } - } - } - - return %hasGameMode; + return %validGameModeCall; } \ No newline at end of file diff --git a/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript b/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript index 63c051431..033b2155a 100644 --- a/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript @@ -9,25 +9,12 @@ function ExampleModule::onDestroy(%this) //This is called when the server is initially set up by the game application function ExampleModule::initServer(%this) { - %this.queueExec("./scripts/server/ExampleGameMode"); + %this.queueExec("./scripts/shared/ExampleGameMode"); } //This is called when the server is created for an actual game/map to be played function ExampleModule::onCreateGameServer(%this) { - //These are common managed data files. For any datablock-based stuff that gets generated by the editors - //(that doesn't have a specific associated file, like data for a player class) will go into these. - //So we'll register them now if they exist. - if(isFile("./scripts/managedData/managedDatablocks." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedDatablocks"); - if(isFile("./scripts/managedData/managedForestItemData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedForestItemData"); - if(isFile("./scripts/managedData/managedForestBrushData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedForestBrushData"); - if(isFile("./scripts/managedData/managedParticleData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedParticleData"); - if(isFile("./scripts/managedData/managedParticleEmitterData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedParticleEmitterData"); } //This is called when the server is shut down due to the game/map being exited @@ -38,7 +25,7 @@ function ExampleModule::onDestroyGameServer(%this) //This is called when the client is initially set up by the game application function ExampleModule::initClient(%this) { - %this.queueExec("./scripts/client/inputCommands"); + %this.queueExec("./scripts/client/inputCommands"); //client scripts exec("./scripts/client/defaultkeybinds"); @@ -46,6 +33,8 @@ function ExampleModule::initClient(%this) %prefPath = getPrefpath(); if(isScriptFile(%prefPath @ "/keybinds")) exec(%prefPath @ "/keybinds"); + + %this.queueExec("./scripts/shared/ExampleGameMode"); } //This is called when a client connects to a server diff --git a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml index 61d1a8664..7f67926e4 100644 --- a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml +++ b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.asset.taml @@ -8,4 +8,5 @@ ForestFile="@assetFile=ExampleLevel.forest" NavmeshFile="@assetFile=ExampleLevel.nav" staticObjectAssetDependency0="@asset=Prototyping:FloorGray" + gameModesNames="ExampleGameMode;" VersionId="1"/> diff --git a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis index 1a27a6576..40bffd526 100644 --- a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis +++ b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis @@ -1,7 +1,7 @@ //--- OBJECT WRITE BEGIN --- new Scene(ExampleLevel) { Enabled = "1"; - gameModeName="ExampleGameMode"; + gameModes="ExampleGameMode"; new LevelInfo(theLevelInfo) { fogColor = "0.6 0.6 0.7 1"; diff --git a/Templates/BaseGame/game/data/ExampleModule/scripts/server/ExampleGameMode.tscript b/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript similarity index 91% rename from Templates/BaseGame/game/data/ExampleModule/scripts/server/ExampleGameMode.tscript rename to Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript index e62e6654c..9c6d7942e 100644 --- a/Templates/BaseGame/game/data/ExampleModule/scripts/server/ExampleGameMode.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript @@ -1,11 +1,5 @@ -function ExampleGameMode::onCreateGame() -{ - // Note: The Game object will be cleaned up by MissionCleanup. Therefore its lifetime is - // limited to that of the mission. - new ScriptObject(ExampleGameMode){}; - - return ExampleGameMode; -} +if(!isObject(ExampleGameMode)) + new GameMode(ExampleGameMode){}; //----------------------------------------------------------------------------- // The server has started up so do some game start up @@ -159,6 +153,30 @@ function ExampleGameMode::onInitialControlSet(%this) } +function ExampleGameMode::onSceneLoaded(%this) +{ + echo("==================================="); + echo("ExampleGameMode - Scene is loaded"); +} + +function ExampleGameMode::onSceneUnloaded(%this) +{ + echo("==================================="); + echo("ExampleGameMode - Scene is unloaded"); +} + +function ExampleGameMode::onSubsceneLoaded(%this) +{ + echo("==================================="); + echo("ExampleGameMode - Subscene is loaded"); +} + +function ExampleGameMode::onSubsceneUnloaded(%this) +{ + echo("==================================="); + echo("ExampleGameMode - Subscene is unloaded"); +} + function ExampleGameMode::spawnCamera(%this, %client, %spawnPoint) { // Set the control object to the default camera diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui index 15ad905d0..a1fcf3a46 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui @@ -9,8 +9,8 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; isContainer = "1"; canSaveDynamicFields = "1"; + currentMenuIdx = "0"; launchInEditor = "0"; - previewButtonSize = "445 120"; new GuiInputCtrl(ChooseLevelInputHandler) { ignoreMouseEvents = "1"; @@ -43,39 +43,50 @@ $guiContent = new GuiControl(ChooseLevelMenu) { extent = "310 41"; horizSizing = "center"; profile = "GuiDefaultProfile"; - visible = "0"; tooltipProfile = "GuiToolTipProfile"; - hidden = "1"; new GuiButtonCtrl() { - text = "Level"; + text = "Game Mode"; groupNum = "1"; extent = "150 41"; profile = "GuiMenuButtonProfile"; command = "ChooseLevelMenu.openMenu(0);"; tooltipProfile = "GuiToolTipProfile"; + internalName = "GameModeBtn"; class = "ChooseLevelMenuButton"; }; new GuiButtonCtrl() { - text = "Server Config"; + text = "Level"; groupNum = "1"; position = "160 0"; extent = "150 41"; profile = "GuiMenuButtonProfile"; command = "ChooseLevelMenu.openMenu(1);"; tooltipProfile = "GuiToolTipProfile"; + internalName = "LevelBtn"; class = "ChooseLevelMenuButton"; }; + new GuiButtonCtrl() { + text = "Server Config"; + groupNum = "1"; + position = "320 0"; + extent = "150 41"; + profile = "GuiMenuButtonProfile"; + visible = "0"; + command = "ChooseLevelMenu.openMenu(2);"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "ConfigBtn"; + class = "ChooseLevelMenuButton"; + hidden = "1"; + }; }; new GuiControl(ChooseLevelMenuNavButtonOverlay) { position = "0 61"; extent = "1281 60"; horizSizing = "width"; profile = "GuiNonModalDefaultProfile"; - visible = "0"; tooltipProfile = "GuiToolTipProfile"; isContainer = "1"; - hidden = "1"; new GuiBitmapCtrl(ChooseLevelMenuPrevNavIcon) { BitmapAsset = "UI:Keyboard_Black_Q_image"; @@ -94,13 +105,67 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; }; - new GuiContainer(LevelSelectContainer) { + new GuiContainer(GameModeSelectContainer) { position = "196 119"; extent = "888 566"; horizSizing = "center"; profile = "GuiNonModalDefaultProfile"; tooltipProfile = "GuiToolTipProfile"; + new GuiScrollCtrl(GameModePreviewScroll) { + hScrollBar = "alwaysOff"; + vScrollBar = "dynamic"; + extent = "445 562"; + vertSizing = "height"; + profile = "GuiMenuScrollProfile"; + tooltipProfile = "GuiToolTipProfile"; + + new GuiStackControl(GameModePreviewArray) { + padding = "5"; + position = "1 1"; + extent = "443 60"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiMenuDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + }; + new GuiBitmapCtrl(GameModePreviewBitmap) { + position = "448 0"; + extent = "440 440"; + horizSizing = "left"; + profile = "GuiNonModalDefaultProfile"; + visible = "0"; + tooltipProfile = "GuiToolTipProfile"; + hidden = "1"; + }; + new GuiTextCtrl(GameModeNameText) { + text = "DeathMatchGame"; + position = "448 445"; + extent = "440 20"; + horizSizing = "left"; + profile = "MenuSubHeaderText"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "GameModeNameTxt"; + }; + new GuiMLTextCtrl(GameModeDescriptionText) { + position = "448 473"; + extent = "440 19"; + horizSizing = "left"; + profile = "GuiMLTextProfile"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "GameModeDescTxt"; + }; + }; + new GuiContainer(LevelSelectContainer) { + position = "196 119"; + extent = "888 566"; + horizSizing = "center"; + profile = "GuiNonModalDefaultProfile"; + visible = "0"; + tooltipProfile = "GuiToolTipProfile"; + hidden = "1"; + new GuiScrollCtrl(LevelPreviewScroll) { hScrollBar = "alwaysOff"; vScrollBar = "dynamic"; @@ -112,7 +177,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { new GuiStackControl(LevelPreviewArray) { padding = "5"; position = "0 1"; - extent = "445 120"; + extent = "445 60"; horizSizing = "width"; vertSizing = "height"; profile = "GuiMenuDefaultProfile"; @@ -214,7 +279,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; new GuiTextEditCtrl(serverNameCTRL) { - text = ""; + text = "Torque 3D Server"; position = "606 4"; extent = "295 22"; horizSizing = "left"; @@ -240,7 +305,6 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; new GuiTextEditCtrl(serverPassCTRL) { - text = ""; position = "606 4"; extent = "295 22"; horizSizing = "left"; @@ -313,8 +377,8 @@ $guiContent = new GuiControl(ChooseLevelMenu) { profile = "GuiMenuPanelProfile"; tooltipProfile = "GuiToolTipProfile"; - new GuiIconButtonCtrl(ChooseLevelStartBtn) { - BitmapAsset = "UI:Keyboard_Black_Return_image"; + new GuiIconButtonCtrl(ChooseLevelNextBtn) { + BitmapAsset = "UI:Keyboard_Black_Blank_image"; sizeIconToButton = "1"; makeIconSquare = "1"; textLocation = "Center"; diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript index 970ff0f87..ff9599e64 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript @@ -25,7 +25,7 @@ function ChooseLevelMenu::onAdd( %this ) if(!isObject(ChooseLevelAssetQuery)) new AssetQuery(ChooseLevelAssetQuery); - %this.previewButtonSize = "445 120"; + $LevelPreviewButtonSize = "445 60"; } function ChooseLevelMenu::fillPrefEntries( %this ) @@ -40,8 +40,107 @@ function ChooseLevelMenu::fillPrefEntries( %this ) function ChooseLevelMenu::onWake(%this) { %this.fillPrefEntries(); + + refreshGameModesList(); + refreshLevelsList(); + + if(!$pref::HostMultiPlayer) + ChooseLevelTitleText.setText("SINGLE PLAYER"); + else + ChooseLevelTitleText.setText("CREATE SERVER"); + + ChooseLevelMenuTabList-->ConfigBtn.visible = $pref::HostMultiPlayer; + + ChooseLevelMenuTabList.visible = true;//$pref::HostMultiPlayer; + ChooseLevelMenuNavButtonOverlay.visible = true;//$pref::HostMultiPlayer; + + //If we've only got one gamemode, just force it to use that one, as we can + //assume that is THE game's gamemode + %gamemodeList = getGameModesList(); + if(%gamemodeList.count() == 1) + { + %gameModeObj = %gamemodeList.getKey(0); + if(isObject(%gameModeObj)) + { + %gameModeObj.active = true; + + ChooseLevelMenuTabList-->LevelBtn.active = true; + ChooseLevelMenuTabList-->GameModeBtn.active = false; + + ChooseLevelMenu.openMenu(1); + + return; + } + } + else + { + //Otherwise open the gamemode list as normal + %this.schedule(32, openMenu, 0); + } +} + +function refreshGameModesList() +{ + GameModePreviewArray.clear(); + + $pref::Server::GameMode = ""; + ChooseLevelMenuTabList-->LevelBtn.active = false; + + //popilate the gamemodes list first + %gamemodeList = getGameModesList(); + %gameModeCount = %gamemodeList.count(); + + for (%i=0; %i<%gameModeCount; %i++) + { + %gameModeObj = %gamemodeList.getKey(%i); + if(isObject(%gameModeObj)) + { + %gameModeName = %gameModeObj.gameModeName; + if(%gameModeName $= "") + %gameModeName = %gameModeObj.getName(); + + %preview = new GuiContainer() { + position = "0 0"; + extent = $LevelPreviewButtonSize; + horizSizing = "right"; + vertSizing = "bottom"; + gameModeObj = %gameModeObj; + gameModeDesc = %gameModeObj.description; + previewImage = %gameModeObj.previewImage; + cansave = false; + + new GuiToggleButtonCtrl() { + position = $LevelPreviewButtonSize.y SPC 0; + extent = $LevelPreviewButtonSize.x - $LevelPreviewButtonSize.y - 5 SPC $LevelPreviewButtonSize.y; + profile = GuiMenuButtonProfile; + horizSizing = "right"; + vertSizing = "bottom"; + internalName = "button"; + class = "GameModePreviewButton"; + text = %gameModeName; + command = "ToggleGameMode(" @ %i @ ");"; //allow doubleclick to quick action it + textMargin = 120; + }; + + new GuiBitmapCtrl() { + position = "0 0"; + extent = $LevelPreviewButtonSize.y SPC $LevelPreviewButtonSize.y; + internalName = "checkbox"; + bitmapAsset = "UI:toggleMarker_image"; + }; + }; + + + GameModePreviewArray.add(%preview); + } + } +} + +function refreshLevelsList() +{ LevelPreviewArray.clear(); + //fetch the levelAssets ChooseLevelAssetQuery.clear(); AssetDatabase.findAssetType(ChooseLevelAssetQuery, "LevelAsset"); @@ -57,6 +156,10 @@ function ChooseLevelMenu::onWake(%this) return; } + %gameModesList = getGameModesList(); + + //filter the levelAssets by the gamemode selected + %filteredIndex = 0; for(%i=0; %i < %count; %i++) { %assetId = ChooseLevelAssetQuery.getAsset(%i); @@ -71,6 +174,46 @@ function ChooseLevelMenu::onWake(%this) if ( !isFile(%file) ) continue; + if( %levelAsset.isSubScene ) + continue; + + //filter for selected gamemode + %levelGameModes = %levelAsset.gameModesNames; + + //If the level has no gamemodes defined, we just assume the default case of it being a wildcard to whatever gamemode + //is available + if(%levelGameModes $= "") + { + %foundGameModeMatch = true; + } + else + { + %foundGameModeMatch = false; + for(%gm = 0; %gm < getTokenCount(%levelGameModes, ";"); %gm++) + { + %gameModeName = getToken(%levelGameModes, ";", %gm); + for(%g=0; %g < %gameModesList.count(); %g++) + { + %gameModeObj = %gameModesList.getKey(%g); + + if(!isObject(%gameModeObj) || (!%gameModeObj.active && !%gameModeObj.alwaysActive && %gameModesList.count() > 1)) + continue; + + if(%gameModeName $= %gameModeObj.getName()) + { + %foundGameModeMatch = true; + break; + } + } + + if(%foundGameModeMatch) + break; + } + } + + if(!%foundGameModeMatch) + continue; + %levelPreviewImg = %levelAsset.getPreviewImagePath(); if (!isFile(%levelPreviewImg)) @@ -78,7 +221,7 @@ function ChooseLevelMenu::onWake(%this) %preview = new GuiIconButtonCtrl() { position = "0 0"; - extent = %this.previewButtonSize; + extent = $LevelPreviewButtonSize; buttonType = "PushButton"; profile = GuiMenuButtonLeftJustProfile; horizSizing = "right"; @@ -86,7 +229,7 @@ function ChooseLevelMenu::onWake(%this) internalName = "button"; class = "LevelPreviewButton"; //command = "$selectedLevelAsset = " @ %assetId @ ";"; - altCommand = "ChooseLevelBegin(1);"; //allow doubleclick to quick action it + altCommand = "ChooseLevelBegin(" @ %filteredIndex @ ");"; //allow doubleclick to quick action it text = %levelAsset.levelName; bitmapAsset = %levelPreviewImg; levelAsset = %levelAsset; @@ -101,26 +244,19 @@ function ChooseLevelMenu::onWake(%this) }; LevelPreviewArray.add(%preview); + + %filteredIndex++; } - + + GameModePreviewArray.listPosition = 0; LevelPreviewArray.listPosition = 0; // Also add the new level mission as defined in the world editor settings // if we are choosing a level to launch in the editor. - if ( %this.launchInEditor ) + if ( ChooseLevelMenu.launchInEditor ) { - %this.addMissionFile( "tools/levels/DefaultEditorLevel.mis" ); + ChooseLevelMenu.addMissionFile( "tools/levels/DefaultEditorLevel.mis" ); } - - if(!$pref::HostMultiPlayer) - ChooseLevelTitleText.setText("SINGLE PLAYER"); - else - ChooseLevelTitleText.setText("CREATE SERVER"); - - ChooseLevelMenuTabList.visible = $pref::HostMultiPlayer; - ChooseLevelMenuNavButtonOverlay.visible = $pref::HostMultiPlayer; - - %this.schedule(32, openMenu, 0); } if(!isObject( ChooseLevelActionMap ) ) @@ -133,8 +269,8 @@ if(!isObject( ChooseLevelActionMap ) ) ChooseLevelActionMap.bind( keyboard, e, ChooseLevelMenuNextMenu); ChooseLevelActionMap.bind( gamepad, btn_r, ChooseLevelMenuNextMenu); - ChooseLevelActionMap.bind( keyboard, Space, ChooseLevelBegin ); - ChooseLevelActionMap.bind( gamepad, btn_a, ChooseLevelBegin ); + ChooseLevelActionMap.bind( keyboard, Space, ChooseLevelMenuOption ); + ChooseLevelActionMap.bind( gamepad, btn_a, ChooseLevelMenuOption ); } function ChooseLevelMenu::syncGUI(%this) @@ -155,10 +291,31 @@ function ChooseLevelMenu::syncGUI(%this) ChooseLevelBackBtn.setBitmap(BaseUIActionMap.getCommandButtonBitmap(%device, "BaseUIBackOut")); - ChooseLevelStartBtn.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelBegin")); + if(ChooseLevelMenu.currentMenuIdx == 0) + ChooseLevelNextBtn.text = "Toggle Mode"; + else + ChooseLevelNextBtn.text = "Start Game"; + + ChooseLevelNextBtn.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelMenuOption")); ChooseLevelMenuPrevNavIcon.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelMenuPrevMenu")); ChooseLevelMenuNextNavIcon.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelMenuNextMenu")); + + %hasActiveGameMode = false; + for(%i=0; %i < GameModePreviewArray.getCount(); %i++) + { + %btn = GameModePreviewArray.getObject(%i); + if(!isObject(%btn.gameModeObj)) + continue; + + if((%btn.gameModeObj.isActive() || %btn.gameModeObj.isAlwaysActive()) == true) + { + %hasActiveGameMode = true; + break; + } + } + + ChooseLevelMenuTabList-->LevelBtn.active = %hasActiveGameMode; } function LevelPreviewArray::syncGUI(%this) @@ -169,14 +326,40 @@ function LevelPreviewArray::syncGUI(%this) $selectedLevelAsset = %btn.levelAssetId; } +function GameModePreviewArray::syncGUI(%this) +{ + for(%i=0; %i < %this.getCount(); %i++) + { + %btn = %this.getObject(%i); + %btn-->button.setHighlighted(false); + + %bitmapAst = (%btn.gameModeObj.active || %btn.gameModeObj.alwaysActive) ? "UI:toggleMarker_h_image" : "UI:toggleMarker_image"; + %btn-->checkbox.setBitmap(%bitmapAst); + } + + %btn = %this.getObject(%this.listPosition); + %btn-->button.setHighlighted(true); +} + function ChooseLevelMenuPrevMenu(%val) { - if(%val && $pref::HostMultiPlayer) + if(%val) { %currentIdx = ChooseLevelMenu.currentMenuIdx; ChooseLevelMenu.currentMenuIdx -= 1; - ChooseLevelMenu.currentMenuIdx = mClamp(ChooseLevelMenu.currentMenuIdx, 0, 1); + %endIndex = 1; + if($pref::HostMultiPlayer) + %endIndex = 2; + + ChooseLevelMenu.currentMenuIdx = mClamp(ChooseLevelMenu.currentMenuIdx, 0, %endIndex); + + //bail if we're trying to step into a hidden or disabled menu + if(!ChooseLevelMenu.isMenuAvailable(ChooseLevelMenu.currentMenuIdx)) + { + ChooseLevelMenu.currentMenuIdx = %currentIdx; + return; + } if(%currentIdx == ChooseLevelMenu.currentMenuIdx) return; @@ -187,12 +370,23 @@ function ChooseLevelMenuPrevMenu(%val) function ChooseLevelMenuNextMenu(%val) { - if(%val && $pref::HostMultiPlayer) + if(%val) { %currentIdx = ChooseLevelMenu.currentMenuIdx; ChooseLevelMenu.currentMenuIdx += 1; - ChooseLevelMenu.currentMenuIdx = mClamp(ChooseLevelMenu.currentMenuIdx, 0, 1); + %endIndex = 1; + if($pref::HostMultiPlayer) + %endIndex = 2; + + ChooseLevelMenu.currentMenuIdx = mClamp(ChooseLevelMenu.currentMenuIdx, 0, %endIndex); + + //bail if we're trying to step into a hidden or disabled menu + if(!ChooseLevelMenu.isMenuAvailable(ChooseLevelMenu.currentMenuIdx)) + { + ChooseLevelMenu.currentMenuIdx = %currentIdx; + return; + } if(%currentIdx == ChooseLevelMenu.currentMenuIdx) return; @@ -201,15 +395,17 @@ function ChooseLevelMenuNextMenu(%val) } } - function ChooseLevelMenu::openMenu(%this, %menuIdx) { - LevelSelectContainer.setVisible(%menuIdx == 0); - ServerConfigContainer.setVisible(%menuIdx == 1); - + GameModeSelectContainer.setVisible(%menuIdx == 0); + LevelSelectContainer.setVisible(%menuIdx == 1); + ServerConfigContainer.setVisible(%menuIdx == 2); + if(%menuIdx == 0) - $MenuList = LevelPreviewArray; + $MenuList = GameModePreviewArray; else if(%menuIdx == 1) + $MenuList = LevelPreviewArray; + else if(%menuIdx == 2) $MenuList = ServerConfigList; %this.currentMenuIdx = %menuIdx; @@ -220,37 +416,78 @@ function ChooseLevelMenu::openMenu(%this, %menuIdx) %this.syncGui(); } -function ChooseLevelBegin(%val) +function ChooseLevelMenu::isMenuAvailable(%this, %menuIdx) +{ + if(%menuIdx == 0) + %btn = %this-->GameModeBtn; + else if(%menuIdx == 1) + %btn = %this-->LevelBtn; + else if(%menuIdx == 2) + %btn = %this-->ConfigBtn; + + return %btn.isActive() && %btn.isVisible(); +} + +function ChooseLevelMenuOption(%val) { if(%val) { - // So we can't fire the button when loading is in progress. - if ( isObject( ServerGroup ) ) - return; - - Canvas.popDialog(); - - %entry = LevelPreviewArray.getObject(LevelPreviewArray.listPosition); - - if(!AssetDatabase.isDeclaredAsset(%entry.levelAssetId)) - { - MessageBoxOK("Error", "Selected level preview does not have a valid level asset!"); - return; - } - - $selectedLevelAsset = %entry.levelAssetId; + if(ChooseLevelMenu.currentMenuIdx == 0) + ToggleGameMode(ChooseLevelMenu.listPosition); + else if(ChooseLevelMenu.currentMenuIdx == 1) + ChooseLevelBegin(ChooseLevelMenu.listPosition); + } +} - // Launch the chosen level with the editor open? - if ( ChooseLevelMenu.launchInEditor ) - { - activatePackage( "BootEditor" ); - ChooseLevelMenu.launchInEditor = false; - StartGame(%entry.levelAssetId, "SinglePlayer"); - } - else - { - StartGame(%entry.levelAssetId); - } +function ToggleGameMode(%index) +{ + if(%index $= "") + %index = $MenuList.listPosition; + + %entry = GameModePreviewArray.getObject(%index); + if(!isObject(%entry) || !isObject(%entry.gameModeObj)) + { + MessageBoxOK("Error", "Selected game mode does not have a valid mode"); + return; + } + + %entry.gameModeObj.active = !%entry.gameModeObj.active; + + refreshLevelsList(); + + $MenuList.syncGui(); + ChooseLevelMenu.syncGui(); + +} + +function ChooseLevelBegin(%index) +{ + // So we can't fire the button when loading is in progress. + if ( isObject( ServerGroup ) ) + return; + + Canvas.popDialog(); + + %entry = LevelPreviewArray.getObject(%index); + + if(!AssetDatabase.isDeclaredAsset(%entry.levelAssetId)) + { + MessageBoxOK("Error", "Selected level preview does not have a valid level asset!"); + return; + } + + $selectedLevelAsset = %entry.levelAssetId; + + // Launch the chosen level with the editor open? + if ( ChooseLevelMenu.launchInEditor ) + { + activatePackage( "BootEditor" ); + ChooseLevelMenu.launchInEditor = false; + StartGame(%entry.levelAssetId, "SinglePlayer"); + } + else + { + StartGame(%entry.levelAssetId); } } @@ -270,6 +507,23 @@ function ChooseLevelMenu::onSleep( %this ) } } +function GameModePreviewButton::onHighlighted(%this, %highlighted) +{ + if(%highlighted) + { + $MenuList.listPosition = $MenuList.getObjectIndex(%this.getParent()); + + GameModePreviewBitmap.bitmapAsset = %this.previewImage; + + GameModePreviewBitmap.visible = (%this.previewImage !$= ""); + + GameModeNameText.text = %this.text; + GameModeDescriptionText.setText(%this.gameModeDesc); + + GameModePreviewScroll.scrollToObject(%this); + } +} + function LevelPreviewButton::onHighlighted(%this, %highlighted) { if(%highlighted) diff --git a/Templates/BaseGame/game/data/UI/images/toggleMarker.png b/Templates/BaseGame/game/data/UI/images/toggleMarker.png new file mode 100644 index 000000000..6d3a0dfd4 Binary files /dev/null and b/Templates/BaseGame/game/data/UI/images/toggleMarker.png differ diff --git a/Templates/BaseGame/game/data/UI/images/toggleMarker_h.png b/Templates/BaseGame/game/data/UI/images/toggleMarker_h.png new file mode 100644 index 000000000..d97740823 Binary files /dev/null and b/Templates/BaseGame/game/data/UI/images/toggleMarker_h.png differ diff --git a/Templates/BaseGame/game/data/UI/images/toggleMarker_h_image.asset.taml b/Templates/BaseGame/game/data/UI/images/toggleMarker_h_image.asset.taml new file mode 100644 index 000000000..57c53f354 --- /dev/null +++ b/Templates/BaseGame/game/data/UI/images/toggleMarker_h_image.asset.taml @@ -0,0 +1,3 @@ + diff --git a/Templates/BaseGame/game/data/UI/images/toggleMarker_image.asset.taml b/Templates/BaseGame/game/data/UI/images/toggleMarker_image.asset.taml new file mode 100644 index 000000000..d8b681616 --- /dev/null +++ b/Templates/BaseGame/game/data/UI/images/toggleMarker_image.asset.taml @@ -0,0 +1,3 @@ + diff --git a/Templates/BaseGame/game/tools/assetBrowser/main.tscript b/Templates/BaseGame/game/tools/assetBrowser/main.tscript index d2c935100..e4aad3292 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/main.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/main.tscript @@ -87,6 +87,7 @@ function initializeAssetBrowser() exec("./scripts/looseFileAudit." @ $TorqueScriptFileExtension); exec("./scripts/creator." @ $TorqueScriptFileExtension); exec("./scripts/setAssetTarget." @ $TorqueScriptFileExtension); + exec("./scripts/utils." @ $TorqueScriptFileExtension); //Processing for the different asset types exec("./scripts/assetTypes/component." @ $TorqueScriptFileExtension); @@ -110,6 +111,7 @@ function initializeAssetBrowser() exec("./scripts/assetTypes/looseFiles." @ $TorqueScriptFileExtension); exec("./scripts/assetTypes/prefab." @ $TorqueScriptFileExtension); exec("./scripts/assetTypes/creatorObj." @ $TorqueScriptFileExtension); + exec("./scripts/assetTypes/gameMode." @ $TorqueScriptFileExtension); new ScriptObject( AssetBrowserPlugin ) { diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/addModuleWindow.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/addModuleWindow.tscript index ec1345c42..3f96e2966 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/addModuleWindow.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/addModuleWindow.tscript @@ -79,7 +79,6 @@ function AssetBrowser_addModuleWindow::CreateNewModule(%this) %line = strreplace( %line, "@@", %newModuleName ); %file.writeline(%line); - echo(%line); } %file.close(); diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript new file mode 100644 index 000000000..ae5e59b3e --- /dev/null +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript @@ -0,0 +1,35 @@ +AssetBrowser::registerObjectType("GameModeType", "Gamemode Objects", "Gamemode"); + +function GameModeType::setupCreateNew() +{ + +} + +function GameModeType::onCreateNew() +{ + +} + +function GameModeType::buildBrowserElement(%this, %className, %previewData) +{ + //echo("DatablockObjectType::buildBrowserElement() - " @ %datablock @ ", " @ %name @ ", " @ %previewData); + %previewData.assetName = %this.getName(); + %previewData.assetPath = %this.getFileName(); + + %previewData.previewImage = "ToolsModule:genericAssetIcon_image"; + + //Lets see if we have a icon for specifically for this datablock type + %dataClass = %this.getClassName(); + %dataClass = strreplace(%dataClass, "Data", ""); + + %previewImage = "tools/classIcons/" @ %dataClass @ ".png"; + if(isFile(%previewImage)) + { + %previewData.previewImage = %previewImage; + } + + //%previewData.assetFriendlyName = %assetDef.assetName; + %previewData.assetDesc = %this; + %previewData.tooltip = "\nGameMode Name: " @ %previewData.assetName @ + "\nPath: " @ %previewData.assetPath; +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/level.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/level.tscript index 2d538ae4b..2594a5378 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/level.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/level.tscript @@ -3,6 +3,8 @@ function AssetBrowser::setupCreateNewLevelAsset(%this) NewAssetPropertiesInspector.startGroup("Level"); NewAssetPropertiesInspector.addField("LevelName", "Level Name", "String", "Human-readable name of new level", "", "", %this.newAssetSettings); NewAssetPropertiesInspector.addField("levelPreviewImage", "Level Preview Image", "Image", "Preview Image for the level", "", "", %this.newAssetSettings); + + NewAssetPropertiesInspector.addField("isSubScene", "Is SubScene", "bool", "Is this levelAsset intended as a subScene", "", "", %this.newAssetSettings); NewAssetPropertiesInspector.endGroup(); } @@ -20,14 +22,16 @@ function AssetBrowser::createLevelAsset(%this) %assetPath = NewAssetTargetAddress.getText() @ "/"; + %misExtension = AssetBrowser.newAssetSettings.isSubScene ? ".subMis" : ".mis"; + %tamlpath = %assetPath @ %assetName @ ".asset.taml"; - %levelPath = %assetPath @ %assetName @ ".mis"; + %levelPath = %assetPath @ %assetName @ %misExtension; %asset = new LevelAsset() { AssetName = %assetName; versionId = 1; - LevelFile = %assetName @ ".mis"; + LevelFile = %assetName @ %misExtension; DecalsFile = %assetName @ ".mis.decals"; PostFXPresetFile = %assetName @ ".postfxpreset." @ $TorqueScriptFileExtension; ForestFile = %assetName @ ".forest"; @@ -35,6 +39,7 @@ function AssetBrowser::createLevelAsset(%this) LevelName = AssetBrowser.newAssetSettings.levelName; AssetDescription = AssetBrowser.newAssetSettings.description; PreviewImage = AssetBrowser.newAssetSettings.levelPreviewImage; + isSubScene = AssetBrowser.newAssetSettings.isSubScene; }; TamlWrite(%asset, %tamlpath); @@ -58,15 +63,32 @@ function AssetBrowser::createLevelAsset(%this) %moduleDef = ModuleDatabase.findModule(%moduleName, 1); %addSuccess = AssetDatabase.addDeclaredAsset(%moduleDef, %tamlpath); + + if(!%addSuccess) + { + error("AssetBrowser::createLevelAsset() - failed to add declared asset: " @ %tamlpath @ " for module: " @ %moduleDef); + } AssetBrowser.refresh(); return %tamlpath; } +//============================================================================== +//SubScene derivative +//============================================================================== +function AssetBrowser::setupCreateNewSubScene(%this) +{ + %this.newAssetSettings.isSubScene = true; + %this.newAssetSettings.assetType = "LevelAsset"; +} + +//============================================================================== + function AssetBrowser::editLevelAsset(%this, %assetDef) { - schedule( 1, 0, "EditorOpenMission", %assetDef); + if(!%assetDef.isSubScene) + schedule( 1, 0, "EditorOpenMission", %assetDef); } //Renames the asset @@ -150,4 +172,100 @@ function AssetBrowser::buildLevelAssetPreview(%this, %assetDef, %previewData) "Asset Type: Level Asset\n" @ "Asset Definition ID: " @ %assetDef @ "\n" @ "Level File path: " @ %assetDef.getLevelPath(); +} + +function createAndAssignLevelAsset(%moduleName, %assetName) +{ + $createAndAssignField.apply(%moduleName @ ":" @ %assetName); +} + +function EWorldEditor::createSelectedAsSubScene( %this, %object ) +{ + //create new level asset here + AssetBrowser.setupCreateNewAsset("SubScene", AssetBrowser.selectedModule, "finishCreateSelectedAsSubScene"); + %this.contextActionObject = %object; +} + +function finishCreateSelectedAsSubScene(%assetId) +{ + echo("finishCreateSelectedAsSubScene"); + + %subScene = new SubScene() { + levelAsset = %assetId; + }; + + %levelAssetDef = AssetDatabase.acquireAsset(%assetId); + %levelFilePath = %levelAssetDef.getLevelPath(); + + //write out to file + EWorldEditor.contextActionObject.save(%levelFilePath); + + AssetDatabase.releaseAsset(%assetId); + + getRootScene().add(%subScene); + + EWorldEditor.contextActionObject.delete(); + + %subScene.load(); + + %subScene.recalculateBounds(); + + %scalar = $SubScene::createScalar; + if(%scalar $= "") + %scalar = 1.5; + + //pad for loading boundary + %subScene.scale = VectorScale(%subScene.scale, %scalar); +} + +function GuiInspectorTypeLevelAssetPtr::onControlDropped( %this, %payload, %position ) +{ + Canvas.popDialog(EditorDragAndDropLayer); + + // Make sure this is a color swatch drag operation. + if( !%payload.parentGroup.isInNamespaceHierarchy( "AssetPreviewControlType_AssetDrop" ) ) + return; + + %assetType = %payload.assetType; + %module = %payload.moduleName; + %assetName = %payload.assetName; + + if(%assetType $= "LevelAsset") + { + %cmd = %this @ ".apply(\""@ %module @ ":" @ %assetName @ "\");"; + eval(%cmd); + } + + EWorldEditor.isDirty = true; +} + +function AssetBrowser::onLevelAssetEditorDropped(%this, %assetDef, %position) +{ + %assetId = %assetDef.getAssetId(); + + if(!%assetDef.isSubScene) + { + errorf("Cannot drag-and-drop LevelAsset into WorldEditor"); + return; + } + + %newSubScene = new SubScene() + { + position = %position; + levelAsset = %assetId; + }; + + getScene(0).add(%newSubScene); + + %newSubScene.load(); + %newSubScene.recalculateBounds(); + + EWorldEditor.clearSelection(); + EWorldEditor.selectObject(%newSubScene); + + EWorldEditor.dropSelection(); + + EWorldEditor.isDirty = true; + + MECreateUndoAction::submit(%newSubScene ); } \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript index 226635dba..a88ed702b 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/editAsset.tscript @@ -176,6 +176,31 @@ function AssetBrowser::performRenameAsset(%this, %originalAssetName, %newName) %buildCommand = %this @ ".rename" @ EditAssetPopup.assetType @ "(" @ %assetDef @ "," @ %newName @ ");"; eval(%buildCommand); } + else + { + //do generic action here + %oldAssetId = %moduleName @ ":" @ %originalAssetName; + %assetDef = AssetDatabase.acquireAsset(%oldAssetId); + + %assetFileName = fileName(%assetDef.getFileName()); + %assetFilePath = filePath(%assetDef.getFileName()); + + %pattern = %assetFilePath @ "/" @ %assetFileName @ ".*"; + + echo("Searching for matches of asset files following pattern: " @ %pattern); + + //get list of files that match the name in the same dir + //do a rename on them + for (%file = findFirstFileMultiExpr(%pattern); %file !$= ""; %file = findNextFileMultiExpr(%pattern)) + { + if(%file !$= %assetFileName) + renameAssetLooseFile(%file, %newName); + } + + //update the assetDef + replaceInFile(%assetDef.getFileName(), %originalAssetName, %newName); + renameAssetFile(%assetDef, %newName); + } } else { diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript index 469e742ac..2df5bd3c7 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/popupMenus.tscript @@ -117,7 +117,8 @@ function AssetBrowser::buildPopupMenus(%this) //item[ 0 ] = "Create Component" TAB AddNewComponentAssetPopup; item[ 0 ] = "Create Script" TAB "" TAB "AssetBrowser.setupCreateNewAsset(\"ScriptAsset\", AssetBrowser.selectedModule);"; item[ 1 ] = "Create State Machine" TAB "" TAB "AssetBrowser.setupCreateNewAsset(\"StateMachineAsset\", AssetBrowser.selectedModule);"; - //item[ 3 ] = "-"; + item[ 2 ] = "-"; + item[ 3 ] = "Create Game Mode" TAB "" TAB "AssetBrowser.setupCreateNewAsset(\"GameMode\", AssetBrowser.selectedModule);"; //item[ 3 ] = "Create Game Object" TAB "" TAB "AssetBrowser.createNewGameObjectAsset(\"NewGameObject\", AssetBrowser.selectedModule);"; }; //%this.AddNewScriptAssetPopup.insertSubMenu(0, "Create Component", AddNewComponentAssetPopup); diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/gameMode.tscript.template b/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/gameMode.tscript.template new file mode 100644 index 000000000..05a8121f7 --- /dev/null +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/gameMode.tscript.template @@ -0,0 +1,176 @@ +//This file implements game mode logic for an Example gamemode. The primary functions: +//@@::onMissionStart +//@@::onMissionReset +//@@::onMissionEnd +//Are the primary hooks for the server to start, restart and end any active gamemodes +//onMissionStart, for example is called from core/clientServer/scripts/server/levelLoad.cs +//It's called once the server has successfully loaded the level, and has parsed +//through any active scenes to get GameModeNames defined by them. It then iterates +//over them and calls these callbacks to envoke gamemode behaviors. This allows multiple +//gamemodes to be in effect at one time. Modules can implement as many gamemodes as you want. +// +//For levels that can be reused for multiple gammodes, the general setup would be a primary level file +//with the Scene in it having the main geometry, weapons, terrain, etc. You would then have subScenes that +//each contain what's necessary for the given gamemode, such as a subScene that just adds the flags and capture +//triggers for a CTF mode. The subscene would then have it's GameModeName defined to run the CTF gamemode logic +//and the levelLoad code will execute it. + +if(!isObject(@@)) +{ + new GameMode(@@){}; +} + +//This function is called when the level finishes loading. It sets up the initial configuration, variables and +//spawning and dynamic objects, timers or rules needed for the gamemode to run +function @@::onMissionStart(%this) +{ + //set up the game and game variables + %this.initGameVars(); + + if (%this.isActive()) + { + error("@@::onMissionStart: End the game first!"); + return; + } + + %this.setActive(true); +} + +//This function is called when the level ends. It can be envoked due to the gamemode ending +//but is also kicked off when the game server is shut down as a form of cleanup for anything the gamemode +//created or is managing like the above mentioned dynamic objects or timers +function @@::onMissionEnded(%this) +{ + if (!%this.isActive()) + { + error("@@::onMissionEnded: No game running!"); + return; + } + + %this.setActive(false); +} + +//This function is called in the event the server resets and is used to re-initialize the gamemode +function @@::onMissionReset(%this) +{ + // Called by resetMission(), after all the temporary mission objects + // have been deleted. + %this.initGameVars(); +} + +//This sets up our gamemode's duration time +function @@::initGameVars(%this) +{ + // Set the gameplay parameters + %this.duration = 30 * 60; +} + +//This is called when the timer runs out, allowing the gamemode to end +function @@::onGameDurationEnd(%this) +{ + //we don't end if we're currently editing the level + if (%this.duration && !(EditorIsActive() && GuiEditorIsActive())) + %this.onMissionEnded(); +} + +//This is called to actually spawn a control object for the player to utilize +//A player character, spectator camera, etc. +function @@::spawnControlObject(%this, %client) +{ + //In this example, we just spawn a camera + /*if (!isObject(%client.camera)) + { + if(!isObject(Observer)) + { + datablock CameraData(Observer) + { + mode = "Observer"; + }; + } + + %client.camera = spawnObject("Camera", Observer); + } + + // If we have a camera then set up some properties + if (isObject(%client.camera)) + { + MissionCleanup.add( %this.camera ); + %client.camera.scopeToClient(%client); + + %client.setControlObject(%client.camera); + + %client.camera.setTransform("0 0 1 0 0 0 0"); + }*/ + +} + +//This is called when the client has finished loading the mission, but aren't "in" it yet +//allowing for some last-second prepwork +function @@::onClientMissionLoaded(%this) +{ + $clientLoaded++; + if ($clientLoaded == $clientConneted) + $gameReady = true; +} + +//This is called when the client has initially established a connection to the game server +//It's used for setting up anything ahead of time for the client, such as loading in client-passed +//config stuffs, saved data or the like that should be handled BEFORE the client has actually entered +//the game itself +function @@::onClientConnect(%this, %client) +{ + $clientConneted++; + getPlayer(%client); +} + + +//This is called when a client enters the game server. It's used to spawn a player object +//set up any client-specific properties such as saved configs, values, their name, etc +//These callbacks are activated in core/clientServer/scripts/server/levelDownload.cs +function @@::onClientEnterGame(%this, %client) +{ + $Game::DefaultPlayerClass = "Player"; + $Game::DefaultPlayerDataBlock = "DefaultPlayerData"; + $Game::DefaultPlayerSpawnGroups = "spawn_player CameraSpawnPoints PlayerSpawnPoints PlayerDropPoints"; + + $Game::DefaultCameraClass = "Camera"; + $Game::DefaultCameraDataBlock = "Observer"; + $Game::DefaultCameraSpawnGroups = "spawn_player CameraSpawnPoints PlayerSpawnPoints PlayerDropPoints"; + + + //Spawn NPCs for the map and stuff here? + + // This function currently relies on some helper functions defined in + // core/scripts/spawn.cs. For custom spawn behaviors one can either + // override the properties on the SpawnSphere's or directly override the + // functions themselves. +} + +function @@::onSceneLoaded(%this) +{ +} + +function @@::onSceneUnloaded(%this) +{ +} + +function @@::onSubsceneLoaded(%this) +{ +} + +function @@::onSubsceneUnloaded(%this) +{ +} + +//This is called when the player leaves the game server. It's used to clean up anything that +//was spawned or setup for the client when it connected, in onClientEnterGame +//These callbacks are activated in core/clientServer/scripts/server/levelDownload.cs +function @@::onClientLeaveGame(%this, %client) +{ + // Cleanup the camera + if (isObject(%this.camera)) + %this.camera.delete(); + // Cleanup the player + if (isObject(%this.player)) + %this.player.delete(); +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template b/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template index 37a2feab9..fc83a1b86 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template @@ -9,24 +9,23 @@ function @@::onDestroy(%this) //This is called when the server is initially set up by the game application function @@::initServer(%this) { + //--FILE EXEC BEGIN-- + //--FILE EXEC END-- } //This is called when the server is created for an actual game/map to be played function @@::onCreateGameServer(%this) { + //--DATABLOCK EXEC BEGIN-- //These are common managed data files. For any datablock-based stuff that gets generated by the editors //(that doesn't have a specific associated file, like data for a player class) will go into these. //So we'll register them now if they exist. - if(isFile("./scripts/managedData/managedDatablocks." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedDatablocks"); - if(isFile("./scripts/managedData/managedForestItemData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedForestItemData"); - if(isFile("./scripts/managedData/managedForestBrushData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedForestBrushData"); - if(isFile("./scripts/managedData/managedParticleEmitterData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedParticleEmitterData"); - if(isFile("./scripts/managedData/managedParticleData." @ $TorqueScriptFileExtension)) - %this.registerDatablock("./scripts/managedData/managedParticleData"); + %this.registerDatablock("./scripts/managedData/managedDatablocks"); + %this.registerDatablock("./scripts/managedData/managedForestItemData"); + %this.registerDatablock("./scripts/managedData/managedForestBrushData"); + %this.registerDatablock("./scripts/managedData/managedParticleEmitterData"); + %this.registerDatablock("./scripts/managedData/managedParticleData"); + //--DATABLOCK EXEC END-- } //This is called when the server is shut down due to the game/map being exited @@ -37,6 +36,8 @@ function @@::onDestroyGameServer(%this) //This is called when the client is initially set up by the game application function @@::initClient(%this) { + //--FILE EXEC BEGIN-- + //--FILE EXEC END-- } //This is called when a client connects to a server diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/utils.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/utils.tscript new file mode 100644 index 000000000..a72471fa0 --- /dev/null +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/utils.tscript @@ -0,0 +1,253 @@ +function testRpl() +{ + ToolUtilityScripts::appendLineToFunction("data/prototyping/prototyping.tscript", "Prototyping", "onDestroyGameServer", "//AAAAAAAAAAAAAAAAAAAAA"); +} + +function Tools::appendLineToFunction(%file, %nameSpace, %functionName, %appendLine) +{ + %fileOutput = new ArrayObject(){}; + %fileObj = new FileObject(){}; + + %insideFunction = false; + %scopeDepth = 0; + + if ( %fileObj.openForRead( %file ) ) + { + while ( !%fileObj.isEOF() ) + { + %line = %fileObj.readLine(); + + if(strIsMatchExpr("*function *(*)*", %line)) + { + %fileOutput.add(%line); + + %start = strpos(%line, "function "); + %end = strpos(%line, "(", %start); + + %scannedFunctionName = ""; + + if(%start != -1 && %end != -1) + { + %scannedFunctionName = getSubStr(%line, %start + 9, %end-%start-9); + } + + %matchesFunctionSig = false; + if(%nameSpace !$= "") + { + if(%scannedFunctionName $= (%nameSpace @ "::" @ %functionName)) + %matchesFunctionSig = true; + } + else + { + if(%scannedFunctionName $= %functionName) + %matchesFunctionSig = true; + } + + if(!%matchesFunctionSig) + continue; + + %insideFunction = true; + + if(strIsMatchExpr("*{*", %line)) + { + %scopeDepth++; + } + if(strIsMatchExpr("*}*", %line)) + { + %scopeDepth--; + } + } + else + { + if(%insideFunction && strIsMatchExpr("*{*", %line)) + { + %scopeDepth++; + } + if(%insideFunction && strIsMatchExpr("*}*", %line)) + { + %scopeDepth--; + + if(%scopeDepth == 0) //we've fully backed out of the function scope, so resolve back to the parent + { + %insideFunction = false; + + %fileOutput.add(%appendLine); + } + } + + %fileOutput.add(%line); + } + } + %fileObj.close(); + } + + if ( %fileObj.openForWrite( %file ) ) + { + for(%i=0; %i < %fileOutput.count(); %i++) + { + %line = %fileOutput.getKey(%i); + + %fileObj.writeLine(%line); + } + %fileObj.close(); + } +} + +function Tools::findInFile(%file, %findText, %startingLineId) +{ + if(%startingLineId $= "") + %startingLineId = 0; + + %fileObj = new FileObject(){}; + + if ( %fileObj.openForRead( %file ) ) + { + %lineId = 0; + while ( !%fileObj.isEOF() ) + { + if(%lineId >= %startingLineId) + { + %line = %fileObj.readLine(); + + if(strIsMatchExpr(%findText, %line)) + { + return %lineId; + } + } + + %lineId++; + } + %fileObj.close(); + } + + return -1; +} + +function Tools::findInFunction(%file, %nameSpace, %functionName, %findText) +{ + %fileObj = new FileObject(){}; + + %insideFunction = false; + %scopeDepth = 0; + + if ( %fileObj.openForRead( %file ) ) + { + %lineId = -1; + while ( !%fileObj.isEOF() ) + { + %line = %fileObj.readLine(); + %lineId++; + + if(strIsMatchExpr("*function *(*)*", %line)) + { + %start = strpos(%line, "function "); + %end = strpos(%line, "(", %start); + + %scannedFunctionName = ""; + + if(%start != -1 && %end != -1) + { + %scannedFunctionName = getSubStr(%line, %start + 9, %end-%start-9); + } + + %matchesFunctionSig = false; + if(%nameSpace !$= "") + { + if(%scannedFunctionName $= (%nameSpace @ "::" @ %functionName)) + %matchesFunctionSig = true; + } + else + { + if(%scannedFunctionName $= %functionName) + %matchesFunctionSig = true; + } + + if(!%matchesFunctionSig) + continue; + + %insideFunction = true; + + if(strIsMatchExpr("*{*", %line)) + { + %scopeDepth++; + } + if(strIsMatchExpr("*}*", %line)) + { + %scopeDepth--; + } + } + else + { + if(%insideFunction && strIsMatchExpr("*{*", %line)) + { + %scopeDepth++; + } + if(%insideFunction && strIsMatchExpr("*}*", %line)) + { + %scopeDepth--; + + if(%scopeDepth == 0) //we've fully backed out of the function scope, so resolve back to the parent + { + %insideFunction = false; + break; + } + } + + if(%insideFunction && strIsMatchExpr(%findText, %line)) + { + break; + } + } + } + %fileObj.close(); + } + + return %lineId; +} + +function Tools::insertInFile(%file, %insertLineId, %insertText, %insertBefore) +{ + %fileOutput = new ArrayObject(){}; + %fileObj = new FileObject(){}; + + if ( %fileObj.openForRead( %file ) ) + { + %lineId = 0; + while ( !%fileObj.isEOF() ) + { + %line = %fileObj.readLine(); + + if(%insertLineId == %lineId) + { + if(!%insertBefore || %insertBefore $= "") + { + %fileOutput.add(%line); + %fileOutput.add(%insertText); + } + else + { + %fileOutput.add(%insertText); + %fileOutput.add(%line); + } + } + else + { + %fileOutput.add(%line); + } + + %lineId++; + } + %fileObj.close(); + } + + if ( %fileObj.openForWrite( %file ) ) + { + for(%i=0; %i < %fileOutput.count(); %i++) + { + %line = %fileOutput.getKey(%i); + + %fileObj.writeLine(%line); + } + %fileObj.close(); + } +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/gui/editorSettingsWindow.ed.tscript b/Templates/BaseGame/game/tools/gui/editorSettingsWindow.ed.tscript index cc0638cca..a0b9347c2 100644 --- a/Templates/BaseGame/game/tools/gui/editorSettingsWindow.ed.tscript +++ b/Templates/BaseGame/game/tools/gui/editorSettingsWindow.ed.tscript @@ -301,6 +301,10 @@ function ESettingsWindow::getGeneralSettings(%this) SettingsInspector.addSettingsField("WorldEditor/AutosaveInterval", "Autosave Interval(in minutes)", "int", ""); SettingsInspector.endGroup(); + SettingsInspector.startGroup("SubScenes"); + SettingsInspector.addSettingsField("WorldEditor/subSceneCreateScalar", "SubScene Creation Scalar", "float", "Amount to scale SubScene's bounds by when creating from scene objects.", "1.5"); + SettingsInspector.endGroup(); + SettingsInspector.startGroup("Paths"); //SettingsInspector.addSettingsField("WorldEditor/torsionPath", "Torsion Path", "filename", ""); SettingsInspector.endGroup(); diff --git a/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript b/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript index 1decb8b8b..98836938b 100644 --- a/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript +++ b/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript @@ -1,20 +1,24 @@ function GuiVariableInspector::onInspectorFieldModified(%this, %targetObj, %fieldName, %index, %oldValue, %newValue) { - echo("FIELD CHANGED: " @ %fieldName @ " from " @ %oldValue @ " to " @ %newValue); + //echo("FIELD CHANGED: " @ %fieldName @ " from " @ %oldValue @ " to " @ %newValue); } function GuiInspectorVariableGroup::onConstructField(%this, %fieldName, %fieldLabel, %fieldTypeName, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) { - %inspector = %this.getParent(); - %makeCommand = %this @ ".build" @ %fieldTypeName @ "Field(\""@ %fieldName @ "\",\"" @ %fieldLabel @ "\",\"" @ %fieldDesc @ "\",\"" @ - %fieldDefaultVal @ "\",\"" @ %fieldDataVals @ "\",\"" @ %inspector @ "." @ %callbackName @ "\",\"" @ %ownerObj @"\");"; - eval(%makeCommand); + return GuiInspectorGroup::onConstructField(%this, %fieldName, %fieldLabel, %fieldTypeName, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj); } function GuiInspectorGroup::onConstructField(%this, %fieldName, %fieldLabel, %fieldTypeName, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) { - %inspector = %this.getParent(); - %makeCommand = %this @ ".build" @ %fieldTypeName @ "Field(\""@ %fieldName @ "\",\"" @ %fieldLabel @ "\",\"" @ %fieldDesc @ "\",\"" @ - %fieldDefaultVal @ "\",\"" @ %fieldDataVals @ "\",\"" @ %inspector @ "." @ %callbackName @ "\",\"" @ %ownerObj @"\");"; - eval(%makeCommand); + if(%this.isMethod("build" @ %fieldTypeName @ "Field")) + { + %inspector = %this.getParent(); + %makeCommand = %this @ ".build" @ %fieldTypeName @ "Field(\""@ %fieldName @ "\",\"" @ %fieldLabel @ "\",\"" @ %fieldDesc @ "\",\"" @ + %fieldDefaultVal @ "\",\"" @ %fieldDataVals @ "\",\"" @ %inspector @ "." @ %callbackName @ "\",\"" @ %ownerObj @"\");"; + %ret = eval(%makeCommand); + + if(%ret == true || %ret $= "") + return true; + } + return false; } diff --git a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml index 9b3fe43b3..6551949e3 100644 --- a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml +++ b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml @@ -2,6 +2,7 @@ AssetName="DefaultEditorLevel" LevelFile="@assetFile=DefaultEditorLevel.mis" LevelName="DefaultEditorLevel" + PostFXPresetFile="@assetFile=tools/levels/DefaultEditorLevel.postfxpreset.tscript" description="An empty room" previewImageAsset0="@asset=ToolsModule:DefaultEditorLevel_preview_image" previewImageAsset1="@asset=ToolsModule:DefaultEditorLevel_preview_image" diff --git a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis index 466e48cc7..363a23a5a 100644 --- a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis +++ b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis @@ -1,33 +1,20 @@ //--- OBJECT WRITE BEGIN --- new Scene(EditorTemplateLevel) { - canSave = "1"; - canSaveDynamicFields = "1"; - Enabled = "1"; + isEditing = "1"; + enabled = "1"; new LevelInfo(theLevelInfo) { - nearClip = "0.1"; - visibleDistance = "1000"; - visibleGhostDistance = "0"; - decalBias = "0.0015"; - fogColor = "0.6 0.6 0.7 1"; - fogDensity = "0"; + FogColor = "0.6 0.6 0.7 1"; fogDensityOffset = "700"; - fogAtmosphereHeight = "0"; canvasClearColor = "0 0 0 255"; - ambientLightBlendPhase = "1"; - ambientLightBlendCurve = "0 0 -1 -1"; soundAmbience = "AudioAmbienceDefault"; - soundDistanceModel = "Linear"; - canSave = "1"; - canSaveDynamicFields = "1"; - Enabled = "1"; + enabled = "1"; }; new ScatterSky(DynamicSky) { sunScale = "0.991102 0.921582 0.83077 1"; zOffset = "-3000"; azimuth = "25"; brightness = "5"; - flareType = "LightFlareExample1"; MoonMatAsset = "Core_Rendering:moon_wglow"; useNightCubemap = "1"; nightCubemap = "nightCubemap"; @@ -44,12 +31,11 @@ new Scene(EditorTemplateLevel) { persistentId = "289ad401-3140-11ed-aae8-c0cb519281fc"; reflectionPath = "tools/levels/DefaultEditorLevel/probes/"; }; - new GroundPlane() { scaleU = "32"; scaleV = "32"; MaterialAsset = "Prototyping:FloorGray"; - Enabled = "1"; + enabled = "1"; position = "0 0 0"; rotation = "1 0 0 0"; scale = "1 1 1"; diff --git a/Templates/BaseGame/game/tools/settings.xml b/Templates/BaseGame/game/tools/settings.xml index 1485670da..4759fc701 100644 --- a/Templates/BaseGame/game/tools/settings.xml +++ b/Templates/BaseGame/game/tools/settings.xml @@ -375,6 +375,8 @@ name="orthoShowGrid">1 Blank Level + 2 AssetWork_Debug.exe renderHandleBtn.performClick();", "" );// Render Node @@ -1093,21 +1095,38 @@ function WorldEditorInspectorPlugin::onWorldEditorStartup( %this ) function WorldEditorInspectorPlugin::onActivated( %this ) { Parent::onActivated( %this ); + + //Clears the button pallete stack + EWToolsPaletteWindow.setStackCtrl(ToolsPaletteArray); //legacy ctrl adhereance + EWToolsPaletteWindow.clearButtons(); + + EWToolsPaletteWindow.setActionMap(WorldEditorInspectorPlugin.map); + + //Adds a button to the pallete stack + //Name Icon Click Command Tooltip text Keybind + EWToolsPaletteWindow.addButton("Select", "ToolsModule:arrow_n_image", "EWorldEditorNoneModeBtn::onClick();", "", "Select Arrow", "1"); + EWToolsPaletteWindow.addButton("Move", "ToolsModule:translate_n_image", "EWorldEditorMoveModeBtn::onClick();", "", "Move Selection", "2"); + EWToolsPaletteWindow.addButton("Rotate", "ToolsModule:rotate_n_image", "EWorldEditorRotateModeBtn::onClick();", "", "Rotate Selection", "3"); + EWToolsPaletteWindow.addButton("Scale", "ToolsModule:Scale_n_image", "EWorldEditorScaleModeBtn::onClick();", "", "Scale Selection", "4"); + + EWToolsPaletteWindow.refresh(); EditorGui-->InspectorWindow.setVisible( true ); EditorGui-->TreeWindow.setVisible( true ); EditorGui-->WorldEditorToolbar.setVisible( true ); - %this.map.push(); + //%this.map.push(); } function WorldEditorInspectorPlugin::onDeactivated( %this ) { Parent::onDeactivated( %this ); + + EWToolsPaletteWindow.popActionMap(); EditorGui-->InspectorWindow.setVisible( false ); EditorGui-->TreeWindow.setVisible( false ); EditorGui-->WorldEditorToolbar.setVisible( false ); - %this.map.pop(); + //%this.map.pop(); } function WorldEditorInspectorPlugin::onEditMenuSelect( %this, %editMenu ) @@ -2044,7 +2063,7 @@ function EditorTree::onRightMouseUp( %this, %itemId, %mouse, %obj ) %popup.item[ 0 ] = "Delete" TAB "" TAB "EditorMenuEditDelete();"; %popup.item[ 1 ] = "Group" TAB "" TAB "EWorldEditor.addSimGroup( true );"; %popup.item[ 2 ] = "-"; - %popup.item[ 3 ] = "Make select a Sub-Level" TAB "" TAB "MakeSelectionASublevel();"; + %popup.item[ 3 ] = "Make selected a Sub-Level" TAB "" TAB "MakeSelectionASublevel();"; } else { @@ -2140,6 +2159,14 @@ function EditorTree::onRightMouseUp( %this, %itemId, %mouse, %obj ) %popup.enableItem(15, true); } } + else if(%obj.getClassName() $= "SimGroup" || + %obj.getClassName() $= "SceneGroup") + { + //if it's ACTUALLY a SimGroup or SceneGroup, have the ability to + //upconvert to SubScene + %popup.item[ 12 ] = "-"; + %popup.item[ 13 ] = "Convert to SubScene" TAB "" TAB "EWorldEditor.createSelectedAsSubScene( " @ %popup.object @ " );"; + } } } } @@ -2179,7 +2206,7 @@ function EditorTree::isValidDragTarget( %this, %id, %obj ) if( %obj.name $= "CameraBookmarks" ) return EWorldEditor.areAllSelectedObjectsOfType( "CameraBookmark" ); else - return ( %obj.getClassName() $= "SimGroup" || %obj.isMemberOfClass("Scene")); + return ( %obj.getClassName() $= "SimGroup" || %obj.isMemberOfClass("Scene") || %obj.isMemberOfClass("SceneGroup")); } function EditorTree::onBeginReparenting( %this ) @@ -2620,6 +2647,77 @@ function EWorldEditor::addSimGroup( %this, %groupCurrentSelection ) %this.syncGui(); } +function EWorldEditor::addSceneGroup( %this, %groupCurrentSelection ) +{ + %activeSelection = %this.getActiveSelection(); + if ( %groupCurrentSelection && %activeSelection.getObjectIndex( getScene(0) ) != -1 ) + { + toolsMessageBoxOK( "Error", "Cannot add Scene to a new SceneGroup" ); + return; + } + + // Find our parent. + + %parent = getScene(0); + if( !%groupCurrentSelection && isObject( %activeSelection ) && %activeSelection.getCount() > 0 ) + { + %firstSelectedObject = %activeSelection.getObject( 0 ); + if( %firstSelectedObject.isMemberOfClass( "SimGroup" ) ) + %parent = %firstSelectedObject; + else if( %firstSelectedObject.getId() != getScene(0).getId() ) + %parent = %firstSelectedObject.parentGroup; + } + + // If we are about to do a group-selected as well, + // starting recording an undo compound. + + if( %groupCurrentSelection ) + Editor.getUndoManager().pushCompound( "Group Selected" ); + + // Create the SimGroup. + + %object = new SceneGroup() + { + parentGroup = %parent; + }; + MECreateUndoAction::submit( %object ); + + // Put selected objects into the group, if requested. + + if( %groupCurrentSelection && isObject( %activeSelection ) ) + { + %undo = UndoActionReparentObjects::create( EditorTree ); + + %numObjects = %activeSelection.getCount(); + for( %i = 0; %i < %numObjects; %i ++ ) + { + %sel = %activeSelection.getObject( %i ); + %undo.add( %sel, %sel.parentGroup, %object ); + %object.add( %sel ); + } + + %undo.addToManager( Editor.getUndoManager() ); + } + + // Stop recording for group-selected. + + if( %groupCurrentSelection ) + Editor.getUndoManager().popCompound(); + + // When not grouping selection, make the newly created SimGroup the + // current selection. + + if( !%groupCurrentSelection ) + { + EWorldEditor.clearSelection(); + EWorldEditor.selectObject( %object ); + } + + // Refresh the Gui. + + %this.syncGui(); +} + function EWorldEditor::toggleLockChildren( %this, %simGroup ) { foreach( %child in %simGroup ) @@ -2887,6 +2985,16 @@ function EWAddSimGroupButton::onCtrlClick( %this ) EWorldEditor.addSimGroup( true ); } +function EWAddSceneGroupButton::onDefaultClick( %this ) +{ + EWorldEditor.addSceneGroup(); +} + +function EWAddSceneGroupButton::onCtrlClick( %this ) +{ + EWorldEditor.addSceneGroup( true ); +} + //------------------------------------------------------------------------------ function EWToolsToolbar::reset( %this ) diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/buttonPalette.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/buttonPalette.tscript new file mode 100644 index 000000000..f401f939d --- /dev/null +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/buttonPalette.tscript @@ -0,0 +1,160 @@ +function ButtonPalette::setStackCtrl(%this, %ctrl) +{ + %this.stackCtrl = %ctrl; +} + +function ButtonPalette::getStackCtrl(%this) +{ + if(isObject(%this.stackCtrl)) + return %this.stackCtrl; + else + return %this; +} + +function ButtonPalette::setActionMap(%this, %actionMap) +{ + %this.actionMap = %actionMap; + %this.actionMap.push(); +} + +function ButtonPalette::getActionMap(%this) +{ + return %this.actionMap; +} + +function ButtonPalette::popActionMap(%this, %actionMap) +{ + %this.actionMap.pop(); +} + +function ButtonPalette::clearButtons(%this) +{ + %stackCtrl = %this.getStackCtrl(); + %stackCtrl.clear(); + + for(%i=0; %i < %this.numButtons; %i++) + { + if(isObject(%this.actionMap)) + { + %this.actionMap.unbind("keyboard", getField(%this.buttons[%i], 5)); + } + + %this.buttons[%i] = ""; + } + + %this.numButtons = 0; +} + + //Name, Icon, Click Command, Variable, Tooltip text, Keybind +function ButtonPalette::addButton(%this, %name, %iconAsset, %command, %syncVariable, %toolTip, %keybind) +{ + if(%this.numButtons $= "") + %this.numButtons = 0; + + for(%i=0; %i < %this.numButtons; %i++) + { + %buttonInfo = %this.buttons[%i]; + + //echo("Testing button info: " @ getField(%buttonInfo, 0) @ " against new button name: " @ %name); + if(getField(%buttonInfo, 0) $= %name) //no duplicates + return; + } + + %this.buttons[%this.numButtons] = %name TAB %iconAsset TAB %command TAB %syncVariable TAB %toolTip TAB %keybind; + + %this.numButtons++; +} + +function ButtonPalette::removeButton(%this, %name) +{ + %found = false; + for(%i=0; %i < %this.numButtons; %i++) + { + if(getField(%this.buttons[%i], 0) $= %name) + { + %found = true; + + if(isObject(%this.actionMap)) + { + %this.actionMap.unbind("keyboard", getField(%this.buttons[%i], 5)); + } + } + + if(%found) + { + %this.buttons[%i] = %this.buttons[%i+1]; + } + } + + if(%found) + %this.numButtons--; + + return %found; +} + +function ButtonPalette::findButton(%this, %name) +{ + %stackCtrl = %this.getStackCtrl(); + for(%i=0; %i < %stackCtrl.getCount(); %i++) + { + %btnObj = %stackCtrl.getObject(%i); + + if(%btnObj.buttonName $= %name) + return %btnObj; + } + + return 0; +} + +function ButtonPalette::refresh(%this) +{ + %stackCtrl = %this.getStackCtrl(); + %stackCtrl.clear(); + + %this.visible = true; + %extents = "25 25"; + + if(%this.numButtons == 0) + { + %this.visible = false; + return; + } + + for(%i=0; %i < %this.numButtons; %i++) + { + %buttonInfo = %this.buttons[%i]; + + %paletteButton = new GuiBitmapButtonCtrl() { + canSaveDynamicFields = "0"; + internalName = getField(%buttonInfo, 0) @ "_paletteButton"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = "0 0"; + Extent = "25 19"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = getField(%buttonInfo, 4) SPC "(" @ getField(%buttonInfo, 5) @ ")"; + hovertime = "1000"; + bitmapAsset = getField(%buttonInfo, 1); + buttonType = "RadioButton"; + useMouseEvents = "0"; + buttonName = getField(%buttonInfo, 0); + command = getField(%buttonInfo, 2); + variable = getField(%buttonInfo, 3); + }; + + %extents.y += 23; + + if(isObject(%this.actionMap)) + %this.actionMap.bindCmd( keyboard, getField(%buttonInfo, 5), %paletteButton @ ".performClick();", "" ); + + %stackCtrl.add(%paletteButton); + } + + %this.extent.y = %extents.y; +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/editorPrefs.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/editorPrefs.ed.tscript index bd1061536..ccba5dea2 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/editorPrefs.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/editorPrefs.ed.tscript @@ -34,6 +34,7 @@ EditorSettings.setDefaultValue( "orthoFOV", "50" ); EditorSettings.setDefaultValue( "orthoShowGrid", "1" ); EditorSettings.setDefaultValue( "AutosaveInterval", "5" ); EditorSettings.setDefaultValue( "forceSidebarToSide", "1" ); +EditorSettings.setDefaultValue( "subSceneCreateScalar", $SubScene::createScalar ); if( isFile( "C:/Program Files/Torsion/Torsion.exe" ) ) EditorSettings.setDefaultValue( "torsionPath", "C:/Program Files/Torsion/Torsion.exe" ); diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript index 817a29e14..36388ef21 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript @@ -554,3 +554,142 @@ function simGroup::onInspectPostApply(%this) %this.callOnChildren("setHidden",%this.hidden); %this.callOnChildren("setLocked",%this.locked); } + +function simGroup::SelectFiteredObjects(%this, %min, %max) +{ + EWorldEditor.clearSelection(); + %this.callOnChildren("filteredSelect", %min, %max ); +} + +function SceneObject::filteredSelect(%this, %min, %max) +{ + echo("SceneObject::filteredSelect() - min: " @ %min @ " max: " @ %max); + %box = %this.getWorldBox(); + %xlength = mAbs(getWord(%box,0) - getWord(%box,3)); + %ylength = mAbs(getWord(%box,1) - getWord(%box,4)); + %Zlength = mAbs(getWord(%box,2) - getWord(%box,5)); + %diagSq = mPow(%xlength,2)+mPow(%ylength,2)+mPow(%Zlength,2); + if (%diagSq > mPow(%min,2) && %diagSq < mPow(%max,2)) + { + EWorldEditor.selectObject(%this); + } +} + +function simGroup::onInspect(%obj, %inspector) +{ + //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 GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName, %fieldLabel, %fieldDesc, + %fieldDefaultVal, %fieldDataVals, %callback, %ownerObj) +{ + + //Set defaults if needbe + if(%ownerObj.minSize $= "") + %ownerObj.minSize = 0.1; + if(%ownerObj.maxSize $= "") + %ownerObj.maxSize = 1; + + %container = new GuiControl() { + canSaveDynamicFields = "0"; + Profile = "EditorContainerProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = "0 0"; + Extent = "300 80"; + MinExtent = "8 2"; + canSave = "0"; + Visible = "1"; + hovertime = "100"; + tooltip = "";// %tooltip; + tooltipProfile = "EditorToolTipProfile"; + + new GuiTextCtrl() { + profile = GuiInspectorFieldProfile; + text = %fieldLabel; + Position = "16 2"; + Extent = "300 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + }; + + new GuiControl() { + Position = "40 20"; + Extent = "270 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + + new GuiTextCtrl() { + profile = GuiInspectorFieldProfile; + text = "Minimum Size:"; + Position = "0 2"; + Extent = "150 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + }; + + new GuiTextEditCtrl() { + profile = GuiInspectorTextEditProfile; + variable = %ownerObj @ ".minSize"; + Position = "150 2"; + Extent = "150 18"; + HorizSizing = "left"; + VertSizing = "bottom"; + }; + }; + + new GuiControl() { + Position = "40 40"; + Extent = "270 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + + new GuiTextCtrl() { + profile = GuiInspectorFieldProfile; + text = "Maximum Size:"; + Position = "0 2"; + Extent = "150 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + }; + + new GuiTextEditCtrl() { + profile = GuiInspectorTextEditProfile; + variable = %ownerObj @ ".maxSize"; + Position = "150 2"; + Extent = "150 18"; + HorizSizing = "left"; + VertSizing = "bottom"; + }; + }; + + new GuiButtonCtrl() { + canSaveDynamicFields = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = "40 60"; + Extent = "265 18"; + MinExtent = "8 2"; + canSave = "0"; + Visible = "1"; + hovertime = "100"; + tooltip = ""; //%tooltip; + tooltipProfile = "EditorToolTipProfile"; + text = "Select"; + maxLength = "1024"; + command = %ownerObj @ ".SelectFiteredObjects("@ %ownerObj.minSize @","@ %ownerObj.maxSize @");"; + }; + }; + + %this-->stack.add(%container); +} diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript new file mode 100644 index 000000000..70cf643ba --- /dev/null +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript @@ -0,0 +1,54 @@ +function SubScene::onSelected(%this) +{ + EWToolsPaletteWindow.clearButtons(); + + //Adds a button to the pallete stack + EWToolsPaletteWindow.addButton("Select", "ToolsModule:arrow_n_image", "EWorldEditorNoneModeBtn::onClick();", "", "Select Arrow", "1"); + EWToolsPaletteWindow.addButton("Move", "ToolsModule:translate_n_image", "SubSceneMoveModeBtn::onClick();", "", "Move Selection", "2"); + EWToolsPaletteWindow.addButton("Rotate", "ToolsModule:rotate_n_image", "SubSceneRotateModeBtn::onClick();", "", "Rotate Selection", "3"); + EWToolsPaletteWindow.addButton("Scale", "ToolsModule:Scale_n_image", "EWorldEditorScaleModeBtn::onClick();", "", "Scale Selection", "4"); + + EWToolsPaletteWindow.addButton("SubSceneMove", "ToolsModule:translate_n_image", "SubSceneChildMoveModeBtn::onClick();", "", "Move SubScene + Children", "5"); + EWToolsPaletteWindow.addButton("SubSceneRotate", "ToolsModule:rotate_n_image", "SubSceneChildRotateModeBtn::onClick();", "", "Rotate SubScene + Children", "6"); + + EWToolsPaletteWindow.refresh(); +} + +function SubScene::onUnselected(%this) +{ + EWToolsPaletteWindow.clearButtons(); + + //Adds a button to the pallete stack + EWToolsPaletteWindow.addButton("Select", "ToolsModule:arrow_n_image", "EWorldEditorNoneModeBtn::onClick();", "", "Select Arrow", "1"); + EWToolsPaletteWindow.addButton("Move", "ToolsModule:translate_n_image", "EWorldEditorMoveModeBtn::onClick();", "", "Move Selection", "2"); + EWToolsPaletteWindow.addButton("Rotate", "ToolsModule:rotate_n_image", "EWorldEditorRotateModeBtn::onClick();", "", "Rotate Selection", "3"); + EWToolsPaletteWindow.addButton("Scale", "ToolsModule:Scale_n_image", "EWorldEditorScaleModeBtn::onClick();", "", "Scale Selection", "4"); + + $SubScene::transformChildren = false; + + EWToolsPaletteWindow.refresh(); +} + +function SubSceneMoveModeBtn::onClick(%this) +{ + EWorldEditorMoveModeBtn::onClick(); + $SubScene::transformChildren = false; +} + +function SubSceneRotateModeBtn::onClick(%this) +{ + EWorldEditorRotateModeBtn::onClick(); + $SubScene::transformChildren = false; +} + +function SubSceneChildMoveModeBtn::onClick() +{ + EWorldEditorMoveModeBtn::onClick(); + $SubScene::transformChildren = true; +} + +function SubSceneChildRotateModeBtn::onClick() +{ + EWorldEditorRotateModeBtn::onClick(); + $SubScene::transformChildren = true; +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript index 7859a95d1..cc7eeaa62 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/menuHandlers.ed.tscript @@ -329,9 +329,6 @@ function EditorSaveMission() if(EWorldEditor.isDirty || ETerrainEditor.isMissionDirty) { - //Inform objects a save is happening, in case there is any special pre-save behavior a class needs to do - getScene(0).callOnChildren("onSaving", $Server::MissionFile); - getScene(0).save($Server::MissionFile); } @@ -604,18 +601,20 @@ function EditorOpenSceneAppend(%levelAsset) function MakeSelectionASublevel() { - /*%size = EWorldEditor.getSelectionSize(); + %size = EWorldEditor.getSelectionSize(); if ( %size == 0 ) return; - //Make a new Scene object - + //Make a group for the objects to go into to be processed as into a + //subscene + %group = new SimGroup(); for(%i=0; %i < %size; %i++) { - + %group.add(EWorldEditor.getSelectedObject(%i)); } - %a = EWorldEditor.getSelectedObject(0); - %b = EWorldEditor.getSelectedObject(1);*/ + + //Now that we have a group, process it into a subscene + EWorldEditor.createSelectedAsSubScene(%group); } function updateEditorRecentLevelsList(%levelAssetId)