diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index 2a5a20d6c..f10b7baec 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 @@ -175,12 +166,58 @@ void Scene::removeDynamicObject(SceneObject* 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) @@ -257,6 +294,21 @@ bool Scene::saveScene(StringTableEntry fileName) fileName = getOriginatingFile(); } + //Inform our objects we're saving, so if they do any special stuff + //they can do it before the actual write-out + for (U32 i = 0; i < mPermanentObjects.size(); i++) + { + SceneObject* obj = mPermanentObjects[i]; + obj->onSaving_callback(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 +338,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; @@ -413,8 +468,6 @@ 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)); diff --git a/Engine/source/T3D/Scene.h b/Engine/source/T3D/Scene.h index c3781afb2..838b58b54 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,14 +27,11 @@ class Scene : public NetObject, public virtual ITickable { typedef NetObject Parent; - bool mIsSubScene; - Scene* mParentScene; - Vector mSubScenes; + Vector mSubScenes; 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; @@ -96,6 +102,8 @@ public: } static Vector smSceneList; + + DECLARE_CALLBACK(void, onSaving, (const char* fileName)); }; @@ -136,3 +144,4 @@ Vector Scene::getObjectsByClass(bool checkSubscenes) return foundObjects; } +#endif diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp new file mode 100644 index 000000000..3efa80d7a --- /dev/null +++ b/Engine/source/T3D/SceneGroup.cpp @@ -0,0 +1,350 @@ +#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) +{ + //Put the SubScene group before everything that'd be SubScene-effecting, for orginazational purposes + GuiInspectorGroup* sceneGroupGrp = inspector->findExistentGroup(StringTable->insert("SceneGroup")); + 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) +{ + //transform difference + MatrixF oldTransform = getTransform(); + + Parent::setTransform(mat); + + // Calculate the delta transformation + MatrixF deltaTransform; + oldTransform.inverse(); + deltaTransform.mul(oldTransform, getTransform()); + + if (isServerObject()) + { + setMaskBits(TransformMask); + + // Update all child transforms + for (SimSetIterator itr(this); *itr; ++itr) + { + SceneObject* child = dynamic_cast(*itr); + if (child) + { + MatrixF childTransform = child->getTransform(); + MatrixF relativeTransform; + relativeTransform.mul(deltaTransform, childTransform); + child->setTransform(relativeTransform); + child->setScale(childTransform.getScale()); //we don't modify scale + } + } + } +} + +void SceneGroup::setRenderTransform(const MatrixF& mat) +{ + //transform difference + MatrixF oldTransform = getRenderTransform(); + + Parent::setRenderTransform(mat); + + // Calculate the delta transformation + MatrixF deltaTransform; + oldTransform.inverse(); + deltaTransform.mul(oldTransform, getRenderTransform()); + + // Update all child transforms + for (SimSetIterator itr(this); *itr; ++itr) + { + SceneObject* child = dynamic_cast(*itr); + if (child) + { + MatrixF childTransform = child->getRenderTransform(); + MatrixF relativeTransform; + relativeTransform.mul(deltaTransform, childTransform); + child->setRenderTransform(relativeTransform); + child->setScale(childTransform.getScale()); //we don't modify scale + } + } +} + +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..a1cf61dc5 --- /dev/null +++ b/Engine/source/T3D/SubScene.cpp @@ -0,0 +1,427 @@ +#include "SubScene.h" + +#include "gameMode.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" + +IMPLEMENT_CO_NETOBJECT_V1(SubScene); + +S32 SubScene::mUnloadTimeoutMs = 5000; + +SubScene::SubScene() : + mLevelAssetId(StringTable->EmptyString()), + mGameModesNames(StringTable->EmptyString()), + mScopeDistance(-1), + mLoaded(false), + mGlobalLayer(false) +{ + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask |= StaticObjectType; +} + +SubScene::~SubScene() +{ +} + +bool SubScene::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +void SubScene::onRemove() +{ + if (isClientObject()) + removeFromScene(); + + 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"); + + 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"); +} + +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); +} + +bool SubScene::testBox(const Box3F& testBox) +{ + if (mGlobalLayer) + return true; + + bool isOverlapped = getWorldBox().isOverlapped(testBox); + + return getWorldBox().isOverlapped(testBox); +} + +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) +{ +} + +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::_closeFile(bool removeFileNotify) +{ + AssertFatal(isServerObject(), "Trying to close out a subscene file on the client is bad!"); + + U32 count = size(); + + for (SimSetIterator itr(this); *itr; ++itr) + { + SimObject* child = dynamic_cast(*itr); + + if (child) + child->safeDeleteObject(); + } + + 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; + + _loadFile(true); + mLoaded = true; + + GameMode::findGameModes(mGameModesNames, &mGameModesList); + + for (U32 i = 0; i < mGameModesList.size(); i++) + { + mGameModesList[i]->onSubsceneLoaded_callback(); + } +} + +void SubScene::unload() +{ + if (!mLoaded) + 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 (SimSetIterator itr(this); *itr; ++itr) + { + SimGroup* childGrp = dynamic_cast(*itr); + if (childGrp && childGrp->isSelected()) + { + mStartUnloadTimerMS = Sim::getCurrentTime(); + return; //if a child is selected, then we don't want to unload + } + + for (SimSetIterator 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 + } + } + } + + _closeFile(true); + mLoaded = false; + + for (U32 i = 0; i < mGameModesList.size(); i++) + { + mGameModesList[i]->onSubsceneUnloaded_callback(); + } +} + +bool SubScene::save() +{ + //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(); + + StringTableEntry levelPath = mLevelAsset->getLevelPath(); + + for (SimGroup::iterator itr = begin(); itr != end(); itr++) + { + //Just in case there's a valid callback the scene object would like to invoke for saving + SceneObject* gc = dynamic_cast(*itr); + if (gc) + { + gc->onSaving_callback(mLevelAssetId); + } + + SimObject* sO = static_cast(*itr); + + if (!sO->save(levelPath)) + { + Con::errorf("SubScene::save() - error, failed to write object %s to file: %s", sO->getIdString(), levelPath); + return false; + } + } + + //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(); + +} + +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..5347a08b7 --- /dev/null +++ b/Engine/source/T3D/SubScene.h @@ -0,0 +1,108 @@ +#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 +#ifndef GAME_MODE_H +#include "gameMode.h" +#endif + +class SubScene : public SceneGroup +{ + typedef SceneGroup Parent; + +public: + enum MaskBits + { + NextFreeMask = Parent::NextFreeMask << 0 + }; + + void onLevelChanged() {} + +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 mGlobalLayer; +public: + SubScene(); + virtual ~SubScene(); + + DECLARE_CONOBJECT(SubScene); + DECLARE_CATEGORY("Object \t Collection"); + + static void initPersistFields(); + static void consoleInit(); + + // 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; + + bool testBox(const Box3F& testBox); + + 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 _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_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/gameMode.cpp b/Engine/source/T3D/gameMode.cpp new file mode 100644 index 000000000..404f13624 --- /dev/null +++ b/Engine/source/T3D/gameMode.cpp @@ -0,0 +1,436 @@ +#include "gameMode.h" + +#ifdef TORQUE_TOOLS +#include "gui/containers/guiDynamicCtrlArrayCtrl.h" +#endif + +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, (), (), + "@brief Called when a subScene has been loaded and has game mode implications.\n\n"); +IMPLEMENT_CALLBACK(GameMode, onSubsceneUnloaded, void, (), (), + "@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()) +{ + 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"); +} + +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(); +} + +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); +} + +DefineEngineFunction(getGameModesList, const char*, (), , "") +{ + char* returnBuffer = Con::getReturnBuffer(1024); + + String formattedList; + + for (SimGroup::iterator itr = Sim::getRootGroup()->begin(); itr != Sim::getRootGroup()->end(); itr++) + { + GameMode* gm = dynamic_cast(*itr); + if (gm) + { + formattedList += String(gm->getName()) + ";"; + } + } + + dSprintf(returnBuffer, 1024, "%s", formattedList.c_str()); + + return returnBuffer; +} + +//----------------------------------------------------------------------------- +// 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..85958620e --- /dev/null +++ b/Engine/source/T3D/gameMode.h @@ -0,0 +1,110 @@ +#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 + + +#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; + +public: + + GameMode(); + ~GameMode() = default; + static void initPersistFields(); + bool onAdd() override; + void onRemove() override; + + bool isActive() { return mIsActive; } + void setActive(const bool& active); + + 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, ()); + DECLARE_CALLBACK(void, onSubsceneUnloaded, ()); +}; + +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/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/scene/sceneObject.cpp b/Engine/source/scene/sceneObject.cpp index 435da6a54..cc9f67005 100644 --- a/Engine/source/scene/sceneObject.cpp +++ b/Engine/source/scene/sceneObject.cpp @@ -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/data/ExampleModule/ExampleModule.tscript b/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript index 63c051431..aec18097a 100644 --- a/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript @@ -15,19 +15,6 @@ function ExampleModule::initServer(%this) //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 @@ -46,6 +33,8 @@ function ExampleModule::initClient(%this) %prefPath = getPrefpath(); if(isScriptFile(%prefPath @ "/keybinds")) exec(%prefPath @ "/keybinds"); + + %this.queueExec("./scripts/server/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/scripts/server/ExampleGameMode.tscript b/Templates/BaseGame/game/data/ExampleModule/scripts/server/ExampleGameMode.tscript index e62e6654c..9c6d7942e 100644 --- a/Templates/BaseGame/game/data/ExampleModule/scripts/server/ExampleGameMode.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/scripts/server/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/UI.tscript b/Templates/BaseGame/game/data/UI/UI.tscript index cccea7eff..c46fe1575 100644 --- a/Templates/BaseGame/game/data/UI/UI.tscript +++ b/Templates/BaseGame/game/data/UI/UI.tscript @@ -99,4 +99,9 @@ function UI::onDestroyClientConnection(%this) function UI::registerGameMenus(%this, %menusArrayObj) { %menusArrayObj.add("System", SystemMenu); +} + +function listLevelsAndGameModes() +{ + } \ No newline at end of file diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui index 15ad905d0..7bc485001 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui @@ -9,6 +9,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; isContainer = "1"; canSaveDynamicFields = "1"; + currentMenuIdx = "0"; launchInEditor = "0"; previewButtonSize = "445 120"; @@ -39,31 +40,42 @@ $guiContent = new GuiControl(ChooseLevelMenu) { new GuiStackControl(ChooseLevelMenuTabList) { stackingType = "Horizontal"; padding = "10"; - position = "485 61"; - extent = "310 41"; + position = "405 61"; + extent = "470 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"; + command = "ChooseLevelMenu.openMenu(2);"; + tooltipProfile = "GuiToolTipProfile"; + internalName = "ConfigBtn"; class = "ChooseLevelMenuButton"; }; }; @@ -72,14 +84,12 @@ $guiContent = new GuiControl(ChooseLevelMenu) { extent = "1281 60"; horizSizing = "width"; profile = "GuiNonModalDefaultProfile"; - visible = "0"; tooltipProfile = "GuiToolTipProfile"; isContainer = "1"; - hidden = "1"; new GuiBitmapCtrl(ChooseLevelMenuPrevNavIcon) { BitmapAsset = "UI:Keyboard_Black_Q_image"; - position = "485 24"; + position = "405 24"; extent = "40 40"; vertSizing = "top"; profile = "GuiNonModalDefaultProfile"; @@ -87,19 +97,72 @@ $guiContent = new GuiControl(ChooseLevelMenu) { }; new GuiBitmapCtrl(ChooseLevelMenuNextNavIcon) { BitmapAsset = "UI:Keyboard_Black_E_image"; - position = "595 24"; + position = "515 24"; extent = "40 40"; vertSizing = "top"; profile = "GuiNonModalDefaultProfile"; tooltipProfile = "GuiToolTipProfile"; }; }; + 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 = "0 1"; + extent = "445 120"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiMenuDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + }; + new GuiBitmapCtrl(GameModePreviewBitmap) { + BitmapAsset = "UI:no_preview_image"; + position = "448 0"; + extent = "440 440"; + horizSizing = "left"; + profile = "GuiNonModalDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiTextCtrl(GameModeNameText) { + text = "Example Level"; + 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"; @@ -120,7 +183,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { }; }; new GuiBitmapCtrl(LevelPreviewBitmap) { - BitmapAsset = ""; + BitmapAsset = "UI:no_preview_image"; position = "448 0"; extent = "440 440"; horizSizing = "left"; @@ -128,7 +191,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; new GuiTextCtrl(LevelNameText) { - text = ""; + text = "Example Level"; position = "448 445"; extent = "440 20"; horizSizing = "left"; @@ -214,7 +277,6 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; new GuiTextEditCtrl(serverNameCTRL) { - text = ""; position = "606 4"; extent = "295 22"; horizSizing = "left"; @@ -240,7 +302,6 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; new GuiTextEditCtrl(serverPassCTRL) { - text = ""; position = "606 4"; extent = "295 22"; horizSizing = "left"; @@ -314,7 +375,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; new GuiIconButtonCtrl(ChooseLevelStartBtn) { - BitmapAsset = "UI:Keyboard_Black_Return_image"; + BitmapAsset = "UI:Keyboard_Black_Space_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..3d949d870 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript @@ -42,6 +42,70 @@ function ChooseLevelMenu::onWake(%this) %this.fillPrefEntries(); LevelPreviewArray.clear(); + refreshGameModesList(); + + 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; + + %this.schedule(32, openMenu, 0); +} + +function refreshGameModesList() +{ + GameModePreviewArray.clear(); + + $pref::Server::GameMode = ""; + ChooseLevelMenuTabList-->LevelBtn.active = false; + + //popilate the gamemodes list first + %gamemodeList = getGameModesList(); + for(%i=0; %i < getTokenCount(%gamemodeList, ";"); %i++) + { + %gameModeObj = getToken(%gamemodeList, ";", %i); + + if(isObject(%gameModeObj)) + { + %gameModeName = %gameModeObj.gameModeName; + if(%gameModeName $= "") + %gameModeName = %gameModeObj.getName(); + + %preview = new GuiButtonCtrl() { + position = "0 0"; + extent = ChooseLevelMenu.previewButtonSize; + buttonType = "PushButton"; + profile = GuiMenuButtonLeftJustProfile; + horizSizing = "right"; + vertSizing = "bottom"; + internalName = "button"; + class = "GameModePreviewButton"; + //command = "ChooseGameModeBegin(" @ %i @ ");"; + altCommand = "ChooseGameModeBegin(" @ %i @ ");"; //allow doubleclick to quick action it + text = %gameModeName; + gameModeObj = %gameModeObj; + gameModeDesc = %gameModeObj.description; + previewImage = %gameModeObj.previewImage; + textMargin = 120; + groupNum = 2; + cansave = false; + }; + + GameModePreviewArray.add(%preview); + } + } +} + +function refreshLevelsList() +{ + LevelPreviewArray.clear(); + + //fetch the levelAssets ChooseLevelAssetQuery.clear(); AssetDatabase.findAssetType(ChooseLevelAssetQuery, "LevelAsset"); @@ -57,6 +121,7 @@ function ChooseLevelMenu::onWake(%this) return; } + //filter the levelAssets by the gamemode selected for(%i=0; %i < %count; %i++) { %assetId = ChooseLevelAssetQuery.getAsset(%i); @@ -71,6 +136,32 @@ function ChooseLevelMenu::onWake(%this) if ( !isFile(%file) ) continue; + if( %levelAsset.isSubScene ) + continue; + + //filter for selected gamemode + %levelGameModes = %levelAsset.gameModesNames; + + echo("LevelAsset gamemodes: " @ %levelGameModes); + + %foundGameModeMatch = false; + for(%gm = 0; %gm < getTokenCount(%levelGameModes, ";"); %gm++) + { + %gameModeName = getToken(%levelGameModes, ";", %gm); + + echo("testing gamemode: " @ %gameModeName); + echo("selected gamemode: " @ $pref::Server::GameMode.getName()); + + if(%gameModeName $= $pref::Server::GameMode.getName()) + { + %foundGameModeMatch = true; + break; + } + } + + if(!%foundGameModeMatch) + continue; + %levelPreviewImg = %levelAsset.getPreviewImagePath(); if (!isFile(%levelPreviewImg)) @@ -78,7 +169,7 @@ function ChooseLevelMenu::onWake(%this) %preview = new GuiIconButtonCtrl() { position = "0 0"; - extent = %this.previewButtonSize; + extent = ChooseLevelMenu.previewButtonSize; buttonType = "PushButton"; profile = GuiMenuButtonLeftJustProfile; horizSizing = "right"; @@ -86,7 +177,7 @@ function ChooseLevelMenu::onWake(%this) internalName = "button"; class = "LevelPreviewButton"; //command = "$selectedLevelAsset = " @ %assetId @ ";"; - altCommand = "ChooseLevelBegin(1);"; //allow doubleclick to quick action it + altCommand = "ChooseLevelBegin(" @ %i @ ");"; //allow doubleclick to quick action it text = %levelAsset.levelName; bitmapAsset = %levelPreviewImg; levelAsset = %levelAsset; @@ -107,20 +198,10 @@ function ChooseLevelMenu::onWake(%this) // 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 +214,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) @@ -169,14 +250,26 @@ function LevelPreviewArray::syncGUI(%this) $selectedLevelAsset = %btn.levelAssetId; } +function GameModePreviewArray::syncGUI(%this) +{ + %btn = %this.getObject(%this.listPosition); + %btn.setHighlighted(true); + + $pref::Server::GameMode = %btn.gameModeObject; +} + 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); if(%currentIdx == ChooseLevelMenu.currentMenuIdx) return; @@ -187,12 +280,16 @@ 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); if(%currentIdx == ChooseLevelMenu.currentMenuIdx) return; @@ -201,15 +298,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 +319,63 @@ function ChooseLevelMenu::openMenu(%this, %menuIdx) %this.syncGui(); } -function ChooseLevelBegin(%val) +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) + ChooseGameModeBegin(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 ChooseGameModeBegin(%index) +{ + %entry = GameModePreviewArray.getObject(%index); + if(!isObject(%entry) || !isObject(%entry.gameModeObj)) + { + MessageBoxOK("Error", "Selected game mode does not have a valid mode"); + return; + } + + $pref::Server::GameMode = %entry.gameModeObj; + + ChooseLevelMenuTabList-->LevelBtn.active = true; + + refreshLevelsList(); + + ChooseLevelMenu.openMenu(1); +} + +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 +395,23 @@ function ChooseLevelMenu::onSleep( %this ) } } +function GameModePreviewButton::onHighlighted(%this, %highlighted) +{ + if(%highlighted) + { + $MenuList.listPosition = $MenuList.getObjectIndex(%this); + + 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/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..e58be2c7f --- /dev/null +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript @@ -0,0 +1,96 @@ +function AssetBrowser::createGameMode(%this) +{ + %moduleName = AssetBrowser.newAssetSettings.moduleName; + %moduleDef = ModuleDatabase.findModule(%moduleName, 1); + + %assetName = AssetBrowser.newAssetSettings.assetName; + %assetPath = NewAssetTargetAddress.getText() @ "/"; + + %scriptPath = %assetPath @ %assetName @ "." @ $TorqueScriptFileExtension; + %fullScriptPath = makeFullPath(%scriptPath); + + %file = new FileObject(); + %templateFile = new FileObject(); + + %postFXTemplateCodeFilePath = %this.templateFilesPath @ "gameMode." @ $TorqueScriptFileExtension @ ".template"; + + if(%file.openForWrite(%fullScriptPath) && %templateFile.openForRead(%postFXTemplateCodeFilePath)) + { + while( !%templateFile.isEOF() ) + { + %line = %templateFile.readline(); + %line = strreplace( %line, "@@", %assetName ); + + %file.writeline(%line); + } + + %file.close(); + %templateFile.close(); + } + else + { + %file.close(); + %templateFile.close(); + + warnf("createGameMode - Something went wrong and we couldn't write the gameMode script file!"); + } + + %localScriptPath = strReplace(%scriptPath, "data/" @ %moduleName @ "/", "./"); + %execLine = " %this.queueExec(\"" @ %localScriptPath @ "\");"; + + %moduleScriptPath = makeFullPath(%moduleDef.ModuleScriptFilePath @ "." @ $TorqueScriptFileExtension); + + echo("Attempting exec insert for file: " @ %moduleScriptPath); + + %lineIdx = Tools::findInFile(%moduleScriptPath, "*function*" @ %moduleName @ "::initClient*"); + if(%lineIdx != -1) + { + echo("INIT CLIENT FUNCTION LINE FOUND ON: " @ %lineIdx); + + %insertLineIdx = Tools::findInFunction(%moduleScriptPath, %moduleName, "initClient", "*//--FILE EXEC END--*"); + echo("FILE EXEC END LINE FOUND ON: " @ %insertLineIdx); + + if(%insertLineIdx == -1) + { + //If there are not 'blocking' comments, then just slap the exec on the end of the function def + //as it doesn't really matter now + Tools::appendLineToFunction(%moduleScriptPath, %moduleName, "initClient", %execLine); + } + else + { + Tools::insertInFile(%moduleScriptPath, %insertLineIdx, %execLine, true); + } + } + + %lineIdx = Tools::findInFile(%moduleScriptPath, "*function*" @ %moduleName @ "::initServer*"); + if(%lineIdx != -1) + { + echo("INIT SERVER FUNCTION LINE FOUND ON: " @ %lineIdx); + + %insertLineIdx = Tools::findInFunction(%moduleScriptPath, %moduleName, "initServer", "*//--FILE EXEC END--*"); + echo("FILE EXEC END LINE FOUND ON: " @ %insertLineIdx); + + if(%insertLineIdx == -1) + { + //If there are not 'blocking' comments, then just slap the exec on the end of the function def + //as it doesn't really matter now + Tools::appendLineToFunction(%moduleScriptPath, %moduleName, "initServer", %execLine); + } + else + { + Tools::insertInFile(%moduleScriptPath, %insertLineIdx, %execLine, true); + } + } + + //and we'll go ahead and force execute the script file so the gamemode is 'available' for use immediately + exec(%scriptPath); + + if(isObject(%assetName)) + { + //it's possible it got moved to an instant group upon execution, so we'll just + //shove it back to the RootGroup by force to be 100% sure + RootGroup.add(%assetName); + } + + return %scriptPath; +} \ 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..b9f566d84 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template @@ -9,6 +9,8 @@ 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 @@ -17,16 +19,14 @@ function @@::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/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 BEGIN-- + //--DATABLOCK EXEC END-- } //This is called when the server is shut down due to the game/map being exited @@ -37,6 +37,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 a39dbcf1f..2440bd8ef 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/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 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 ) @@ -2847,6 +2926,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/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/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)