From ae8eca48e1f24203b2db677e4cd1666337fd800b Mon Sep 17 00:00:00 2001 From: Areloch Date: Sun, 1 Sep 2024 16:39:00 -0500 Subject: [PATCH 01/17] Implementation of Subscenes, SceneGroups and Gamemodes Standardizes Gamemodes to be an actual class with data and utility functions that can be parsed Adds inspector field handling for selecting gamemodes Updated Scene class to work with Gamemodes for the gamemode field Updates editor suite elements to be able to create SubScenes, SceneGroups and Gamemodes Adds ability to convert SimGroup to SubScene Updates BaseUI's chooselevel menu to have gamemode selection and filters shown levels based on selected gamemode --- Engine/source/T3D/Scene.cpp | 125 +++-- Engine/source/T3D/Scene.h | 23 +- Engine/source/T3D/SceneGroup.cpp | 350 ++++++++++++++ Engine/source/T3D/SceneGroup.h | 55 +++ Engine/source/T3D/SubScene.cpp | 427 +++++++++++++++++ Engine/source/T3D/SubScene.h | 108 +++++ Engine/source/T3D/assets/ImageAsset.cpp | 4 +- Engine/source/T3D/assets/LevelAsset.cpp | 128 ++++- Engine/source/T3D/assets/LevelAsset.h | 97 +++- Engine/source/T3D/gameMode.cpp | 436 ++++++++++++++++++ Engine/source/T3D/gameMode.h | 110 +++++ Engine/source/gui/core/guiControl.h | 7 + Engine/source/scene/sceneObject.cpp | 5 + Engine/source/scene/sceneObject.h | 2 + .../utility/scripts/helperFunctions.tscript | 20 +- .../data/ExampleModule/ExampleModule.tscript | 15 +- .../levels/ExampleLevel.asset.taml | 1 + .../scripts/server/ExampleGameMode.tscript | 34 +- Templates/BaseGame/game/data/UI/UI.tscript | 5 + .../game/data/UI/guis/ChooseLevelMenu.gui | 91 +++- .../game/data/UI/guis/ChooseLevelMenu.tscript | 244 ++++++++-- .../game/tools/assetBrowser/main.tscript | 2 + .../scripts/addModuleWindow.tscript | 1 - .../scripts/assetTypes/gameMode.tscript | 96 ++++ .../scripts/assetTypes/level.tscript | 124 ++++- .../assetBrowser/scripts/editAsset.tscript | 25 + .../assetBrowser/scripts/popupMenus.tscript | 3 +- .../templateFiles/gameMode.tscript.template | 176 +++++++ .../templateFiles/module.tscript.template | 22 +- .../tools/assetBrowser/scripts/utils.tscript | 253 ++++++++++ .../tools/gui/editorSettingsWindow.ed.tscript | 4 + Templates/BaseGame/game/tools/settings.xml | 2 + .../gui/WorldEditorTreeWindow.ed.gui | 28 +- .../worldEditor/scripts/EditorGui.ed.tscript | 93 +++- .../scripts/editorPrefs.ed.tscript | 1 + .../scripts/menuHandlers.ed.tscript | 17 +- 36 files changed, 2962 insertions(+), 172 deletions(-) create mode 100644 Engine/source/T3D/SceneGroup.cpp create mode 100644 Engine/source/T3D/SceneGroup.h create mode 100644 Engine/source/T3D/SubScene.cpp create mode 100644 Engine/source/T3D/SubScene.h create mode 100644 Engine/source/T3D/gameMode.cpp create mode 100644 Engine/source/T3D/gameMode.h create mode 100644 Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript create mode 100644 Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/gameMode.tscript.template create mode 100644 Templates/BaseGame/game/tools/assetBrowser/scripts/utils.tscript diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index 2a5a20d6c5..f10b7baecd 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 c3781afb26..838b58b54f 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 @@ class Scene : public NetObject, public virtual ITickable } 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 0000000000..3efa80d7a7 --- /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 0000000000..b89af1c9aa --- /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 0000000000..a1cf61dc59 --- /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 0000000000..5347a08b78 --- /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 b51327c4e6..89088f7fe8 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 0dcd04ec4b..98bb2cc5ce 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 11cedd7d63..8b02495ed3 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 @@ class LevelAsset : public AssetBase 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 @@ class LevelAsset : public AssetBase 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 0000000000..404f136242 --- /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 0000000000..85958620ef --- /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 1cdeb4bab7..c4e2649ba0 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 435da6a546..cc9f670059 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 1ebe3fd726..e12f1a0f89 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 fc6e0d33c7..05438e6000 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 63c051431b..aec18097ab 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 61d1a86643..7f67926e48 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 e62e6654c2..9c6d7942ee 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 cccea7effc..c46fe15758 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 15ad905d0f..7bc4850019 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 970ff0f87f..3d949d8709 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; - } + if(ChooseLevelMenu.currentMenuIdx == 0) + ChooseGameModeBegin(ChooseLevelMenu.listPosition); + else if(ChooseLevelMenu.currentMenuIdx == 1) + ChooseLevelBegin(ChooseLevelMenu.listPosition); + } +} + +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; - $selectedLevelAsset = %entry.levelAssetId; + 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); - } + // 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 d2c9351002..e4aad32927 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 ec1345c429..3f96e29666 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 0000000000..e58be2c7fd --- /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 2d538ae4b9..2594a53787 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 226635dba9..a88ed702b6 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 469e742acc..2df5bd3c75 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 0000000000..05a8121f75 --- /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 37a2feab94..b9f566d84f 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 0000000000..a72471fa02 --- /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 a39dbcf1f7..2440bd8ef3 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 1485670da6..4759fc7014 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 bd10615366..ccba5dea2b 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 7859a95d11..cc7eeaa620 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) From d896a2b99a6b3d4826bd6a1bddff938147df3e3a Mon Sep 17 00:00:00 2001 From: Areloch Date: Sun, 1 Sep 2024 17:14:08 -0500 Subject: [PATCH 02/17] Removed scan-down of subscenes from Scene's getObjectsByClass call Properly returned value for SubScene::save() --- Engine/source/T3D/Scene.cpp | 2 +- Engine/source/T3D/Scene.h | 19 +++---------------- Engine/source/T3D/SubScene.cpp | 1 + 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index f10b7baecd..95ff6da17f 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/Engine/source/T3D/Scene.cpp @@ -369,7 +369,7 @@ void Scene::getUtilizedAssetsFromSceneObject(SimObject* object, Vector Scene::getObjectsByClass(String className, bool checkSubscenes) +Vector Scene::getObjectsByClass(String className) { return Vector(); } diff --git a/Engine/source/T3D/Scene.h b/Engine/source/T3D/Scene.h index 838b58b54f..d99b1f4b96 100644 --- a/Engine/source/T3D/Scene.h +++ b/Engine/source/T3D/Scene.h @@ -86,12 +86,12 @@ class Scene : public NetObject, public virtual ITickable void unpackUpdate(NetConnection *conn, BitStream *stream) override; // - Vector getObjectsByClass(String className, bool checkSubscenes); + Vector getObjectsByClass(String className); void getUtilizedAssetsFromSceneObject(SimObject* object, Vector* usedAssetsList); template - Vector getObjectsByClass(bool checkSubscenes); + Vector getObjectsByClass(); static Scene *getRootScene() { @@ -108,7 +108,7 @@ class Scene : public NetObject, public virtual ITickable template -Vector Scene::getObjectsByClass(bool checkSubscenes) +Vector Scene::getObjectsByClass() { Vector foundObjects; @@ -129,19 +129,6 @@ Vector Scene::getObjectsByClass(bool checkSubscenes) foundObjects.push_back(curObject); } - if (checkSubscenes) - { - for (U32 i = 0; i < mSubScenes.size(); i++) - { - Vector appendList = mSubScenes[i]->getObjectsByClass(true); - - for (U32 a = 0; a < appendList.size(); a++) - { - foundObjects.push_back(appendList[a]); - } - } - } - return foundObjects; } #endif diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index a1cf61dc59..2cd64e9a6f 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -338,6 +338,7 @@ bool SubScene::save() //Finally, save saveSuccess = mLevelAsset->saveAsset(); + return saveSuccess; } void SubScene::_onSelected() From d7335a73e419c667f15abd1a46d9595b64691c2c Mon Sep 17 00:00:00 2001 From: Areloch Date: Sun, 1 Sep 2024 17:59:24 -0500 Subject: [PATCH 03/17] Removed dummy placeholder function stub --- Templates/BaseGame/game/data/UI/UI.tscript | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Templates/BaseGame/game/data/UI/UI.tscript b/Templates/BaseGame/game/data/UI/UI.tscript index c46fe15758..cccea7effc 100644 --- a/Templates/BaseGame/game/data/UI/UI.tscript +++ b/Templates/BaseGame/game/data/UI/UI.tscript @@ -99,9 +99,4 @@ function UI::onDestroyClientConnection(%this) function UI::registerGameMenus(%this, %menusArrayObj) { %menusArrayObj.add("System", SystemMenu); -} - -function listLevelsAndGameModes() -{ - } \ No newline at end of file From 4bb26bf96c7579ac110ca4b6d37ca871df935ab4 Mon Sep 17 00:00:00 2001 From: Areloch Date: Sun, 1 Sep 2024 23:26:10 -0500 Subject: [PATCH 04/17] Reorganized the exec's for datablocks in module template file to be within the start/stop blocks Tweaked example module script file to comply Moved ExampleGameMode script file to scripts/shared since client and server need access to the gamemode for logic to work --- .../BaseGame/game/data/ExampleModule/ExampleModule.tscript | 6 +++--- .../scripts/{server => shared}/ExampleGameMode.tscript | 0 .../scripts/templateFiles/module.tscript.template | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) rename Templates/BaseGame/game/data/ExampleModule/scripts/{server => shared}/ExampleGameMode.tscript (100%) diff --git a/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript b/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript index aec18097ab..033b2155a9 100644 --- a/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/ExampleModule.tscript @@ -9,7 +9,7 @@ function ExampleModule::onDestroy(%this) //This is called when the server is initially set up by the game application function ExampleModule::initServer(%this) { - %this.queueExec("./scripts/server/ExampleGameMode"); + %this.queueExec("./scripts/shared/ExampleGameMode"); } //This is called when the server is created for an actual game/map to be played @@ -25,7 +25,7 @@ function ExampleModule::onDestroyGameServer(%this) //This is called when the client is initially set up by the game application function ExampleModule::initClient(%this) { - %this.queueExec("./scripts/client/inputCommands"); + %this.queueExec("./scripts/client/inputCommands"); //client scripts exec("./scripts/client/defaultkeybinds"); @@ -34,7 +34,7 @@ function ExampleModule::initClient(%this) if(isScriptFile(%prefPath @ "/keybinds")) exec(%prefPath @ "/keybinds"); - %this.queueExec("./scripts/server/ExampleGameMode"); + %this.queueExec("./scripts/shared/ExampleGameMode"); } //This is called when a client connects to a server diff --git a/Templates/BaseGame/game/data/ExampleModule/scripts/server/ExampleGameMode.tscript b/Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript similarity index 100% rename from Templates/BaseGame/game/data/ExampleModule/scripts/server/ExampleGameMode.tscript rename to Templates/BaseGame/game/data/ExampleModule/scripts/shared/ExampleGameMode.tscript 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 b9f566d84f..fc83a1b867 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/templateFiles/module.tscript.template @@ -16,6 +16,7 @@ function @@::initServer(%this) //This is called when the server is created for an actual game/map to be played function @@::onCreateGameServer(%this) { + //--DATABLOCK EXEC BEGIN-- //These are common managed data files. For any datablock-based stuff that gets generated by the editors //(that doesn't have a specific associated file, like data for a player class) will go into these. //So we'll register them now if they exist. @@ -24,8 +25,6 @@ function @@::onCreateGameServer(%this) %this.registerDatablock("./scripts/managedData/managedForestBrushData"); %this.registerDatablock("./scripts/managedData/managedParticleEmitterData"); %this.registerDatablock("./scripts/managedData/managedParticleData"); - - //--DATABLOCK EXEC BEGIN-- //--DATABLOCK EXEC END-- } From 6ff92f61bb1e611e3a8202544d7b1faff01b92ab Mon Sep 17 00:00:00 2001 From: Areloch Date: Tue, 3 Sep 2024 18:25:28 -0500 Subject: [PATCH 05/17] Fixed SceneGroup onInspect call to call up through parent Adds utility methods and SimGroup onInspect injection to add a select button in inspector to select objects under simgroup --- Engine/source/T3D/SceneGroup.cpp | 2 + .../scripts/editors/worldEditor.ed.tscript | 74 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp index 3efa80d7a7..5dc0e6fecd 100644 --- a/Engine/source/T3D/SceneGroup.cpp +++ b/Engine/source/T3D/SceneGroup.cpp @@ -116,6 +116,8 @@ void SceneGroup::inspectPostApply() void SceneGroup::onInspect(GuiInspector* inspector) { + Parent::onInspect(inspector); + //Put the SubScene group before everything that'd be SubScene-effecting, for orginazational purposes GuiInspectorGroup* sceneGroupGrp = inspector->findExistentGroup(StringTable->insert("SceneGroup")); if (!sceneGroupGrp) diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript index 817a29e14c..69678c833e 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript @@ -554,3 +554,77 @@ function simGroup::onInspectPostApply(%this) %this.callOnChildren("setHidden",%this.hidden); %this.callOnChildren("setLocked",%this.locked); } + +function simGroup::SelectFiteredObjects(%this, %min, %max) +{ + EWorldEditor.clearSelection(); + %this.callOnChildren("filteredSelect", %min, %max ); +} + +function SceneObject::filteredSelect(%this, %min, %max) +{ + %box = %this.getWorldBox(); + %xlength = mAbs(getWord(%box,0) - getWord(%box,3)); + %ylength = mAbs(getWord(%box,1) - getWord(%box,4)); + %Zlength = mAbs(getWord(%box,2) - getWord(%box,5)); + %diagSq = mPow(%xlength,2)+mPow(%ylength,2)+mPow(%Zlength,2); + if (%diagSq > mPow(%min,2) && %diagSq < mPow(%max,2)) + { + EWorldEditor.selectObject(%this); + } +} + +function simGroup::onInspect(%obj, %inspector) +{ + //Find the 'Editing' group in the inspector + %group = %inspector.findExistentGroup("Editing"); + if(isObject(%group)) + { + //We add a field of the type 'SimGroupSelectionButton'. This isn't a 'real' type, so when the inspector group tries to add it + //it will route down through GuiInspectorGroup(the namespace of %group) and call onConstructField in an attemp to see if there's any + //script defined functions that can build a field of that type. + //We happen to define the required 'build @ @ Field()' function below, allowing us to build out the custom field type + %group.addField("minSize", "F32", "min diagonal size of objects"); + %group.addField("maxSize", "F32", "max diagonal size of objects"); + %group.addField("select", "SimGroupSelectionButton", "Select filtered objects"); + } +} + +function GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName, %fieldLabel, %fieldDesc, + %fieldDefaultVal, %fieldDataVals, %callback, %ownerObj) +{ + %container = new GuiControl() { + canSaveDynamicFields = "0"; + Profile = "EditorContainerProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = "0 0"; + Extent = "300 18"; + MinExtent = "8 2"; + canSave = "0"; + Visible = "1"; + hovertime = "100"; + tooltip = "";// %tooltip; + tooltipProfile = "EditorToolTipProfile"; + + new GuiButtonCtrl() { + canSaveDynamicFields = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = "16 3"; + Extent = "300 18"; + MinExtent = "8 2"; + canSave = "0"; + Visible = "1"; + hovertime = "100"; + tooltip = ""; //%tooltip; + tooltipProfile = "EditorToolTipProfile"; + text = %fieldName; + maxLength = "1024"; + command = %ownerObj @ ".SelectFiteredObjects("@ %ownerObj.minSize @","@ %ownerObj.maxSize @");"; + }; + }; + + %this-->stack.add(%container); +} From a931e9a3081390f34cb1bdb4d7ded0327b204331 Mon Sep 17 00:00:00 2001 From: Areloch Date: Thu, 5 Sep 2024 13:49:13 -0500 Subject: [PATCH 06/17] Tweaks the Scene and SubScene save calls to handle simGroups to call onto their children --- Engine/source/T3D/Scene.cpp | 21 +++++++++++++++++++-- Engine/source/T3D/SubScene.cpp | 18 +++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index 95ff6da17f..d8061f32be 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/Engine/source/T3D/Scene.cpp @@ -296,11 +296,28 @@ bool Scene::saveScene(StringTableEntry fileName) //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++) + for (SimGroup::iterator itr = begin(); itr != end(); itr++) + { + SimGroup* sg = dynamic_cast(*itr); + if (sg) + { + ConsoleValue vars[3]; + vars[2].setString(fileName); + sg->callOnChildren("onSaving", 3, vars); + } + + SceneObject* sO = dynamic_cast(*itr); + if (sO) + { + sO->onSaving_callback(fileName); + } + } + + /*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 diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 2cd64e9a6f..5b93c473d3 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -309,15 +309,23 @@ bool SubScene::save() 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) + //Inform our objects we're saving, so if they do any special stuff + //they can do it before the actual write-out + SimGroup* sg = dynamic_cast(*itr); + if (sg) { - gc->onSaving_callback(mLevelAssetId); + ConsoleValue vars[3]; + vars[2].setString(mLevelAssetId); + sg->callOnChildren("onSaving", 3, vars); } - SimObject* sO = static_cast(*itr); + SceneObject* scO = dynamic_cast(*itr); + if (scO) + { + scO->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); From e4d07c7e8df3b571cc7da93fb616764582cb79af Mon Sep 17 00:00:00 2001 From: JeffR Date: Fri, 4 Oct 2024 00:10:26 -0500 Subject: [PATCH 07/17] Adds utility function and method to be able to enact a load of subscenes at a specific world position Adds loadIf conditional logic to evaluate if a subscene is 'allowed' to load when tested Adds isAlwaysActive to GameMode to be able to flag a gamemode as being defaulted to on and used automatically Updated GetGameModesList function to return an arrayObject of the gamemodes found Overhauled CallGameModeFunction to utilize the gamemodes list with active/alwaysActive modes being called against, rather than level-scanning Updated ChooseLevelMenu to be able to toggle on/off multiple gamemodes with an image indicator if it's active or not --- Engine/source/T3D/Scene.cpp | 21 +++ Engine/source/T3D/Scene.h | 2 + Engine/source/T3D/SubScene.cpp | 16 +- Engine/source/T3D/SubScene.h | 1 + Engine/source/T3D/gameMode.cpp | 40 +++- Engine/source/T3D/gameMode.h | 4 + .../game/core/utility/scripts/scene.tscript | 58 ++---- .../game/data/UI/guis/ChooseLevelMenu.gui | 27 +-- .../game/data/UI/guis/ChooseLevelMenu.tscript | 173 +++++++++++++----- .../game/data/UI/images/toggleMarker.png | Bin 0 -> 6747 bytes .../game/data/UI/images/toggleMarker_h.png | Bin 0 -> 6759 bytes .../UI/images/toggleMarker_h_image.asset.taml | 3 + .../UI/images/toggleMarker_image.asset.taml | 3 + .../scripts/assetTypes/gameMode.tscript | 107 +++-------- 14 files changed, 258 insertions(+), 197 deletions(-) create mode 100644 Templates/BaseGame/game/data/UI/images/toggleMarker.png create mode 100644 Templates/BaseGame/game/data/UI/images/toggleMarker_h.png create mode 100644 Templates/BaseGame/game/data/UI/images/toggleMarker_h_image.asset.taml create mode 100644 Templates/BaseGame/game/data/UI/images/toggleMarker_image.asset.taml diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index d8061f32be..0d7908fe34 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/Engine/source/T3D/Scene.cpp @@ -391,6 +391,21 @@ Vector Scene::getObjectsByClass(String className) return Vector(); } +void Scene::loadAtPosition(const Point3F& position) +{ + for (U32 i = 0; i < mSubScenes.size(); i++) + { + Box3F testBox = Box3F(0.5); + testBox.setCenter(position); + + if (mSubScenes[i]->testBox(testBox)) + { + mSubScenes[i]->setUnloadTimeMS(-1); + mSubScenes[i]->load(); + } + } +} + DefineEngineFunction(getScene, Scene*, (U32 sceneId), (0), "Get the root Scene object that is loaded.\n" "@return The id of the Root Scene. Will be 0 if no root scene is loaded") @@ -489,3 +504,9 @@ DefineEngineMethod(Scene, save, bool, (const char* fileName), (""), { return object->saveScene(StringTable->insert(fileName)); } + +DefineEngineMethod(Scene, loadAtPosition, void, (Point3F position), (Point3F::Zero), + "Loads any subscenes at a given point by force.\n") +{ + object->loadAtPosition(position); +} diff --git a/Engine/source/T3D/Scene.h b/Engine/source/T3D/Scene.h index d99b1f4b96..8f48b80b12 100644 --- a/Engine/source/T3D/Scene.h +++ b/Engine/source/T3D/Scene.h @@ -93,6 +93,8 @@ class Scene : public NetObject, public virtual ITickable template Vector getObjectsByClass(); + void loadAtPosition(const Point3F& position); + static Scene *getRootScene() { if (Scene::smSceneList.empty()) diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 5b93c473d3..59f68d3dd2 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -49,6 +49,7 @@ void SubScene::initPersistFields() addGroup("SubScene"); addField("isGlobalLayer", TypeBool, Offset(mGlobalLayer, SubScene), ""); INITPERSISTFIELD_LEVELASSET(Level, SubScene, "The level asset to load."); + addField("loadIf", TypeCommand, Offset(mLoadIf, SubScene), "evaluation condition (true/false)"); addField("gameModes", TypeGameModeList, Offset(mGameModesNames, SubScene), "The game modes that this subscene is associated with."); endGroup("SubScene"); @@ -143,9 +144,18 @@ bool SubScene::testBox(const Box3F& testBox) if (mGlobalLayer) return true; - bool isOverlapped = getWorldBox().isOverlapped(testBox); - - return getWorldBox().isOverlapped(testBox); + bool passes = getWorldBox().isOverlapped(testBox); + if (passes && !mLoadIf.isEmpty()) + { + //test the mapper plugged in condition line + String resVar = getIdString() + String(".result"); + Con::setBoolVariable(resVar.c_str(), false); + String command = resVar + "=" + mLoadIf + ";"; + Con::evaluatef(command.c_str()); + passes = Con::getBoolVariable(resVar.c_str()); + } + + return passes; } void SubScene::write(Stream& stream, U32 tabStop, U32 flags) diff --git a/Engine/source/T3D/SubScene.h b/Engine/source/T3D/SubScene.h index 5347a08b78..53c8b12967 100644 --- a/Engine/source/T3D/SubScene.h +++ b/Engine/source/T3D/SubScene.h @@ -38,6 +38,7 @@ class SubScene : public SceneGroup S32 mStartUnloadTimerMS; bool mLoaded; + String mLoadIf; bool mGlobalLayer; public: diff --git a/Engine/source/T3D/gameMode.cpp b/Engine/source/T3D/gameMode.cpp index 404f136242..0032b12ad1 100644 --- a/Engine/source/T3D/gameMode.cpp +++ b/Engine/source/T3D/gameMode.cpp @@ -4,6 +4,8 @@ #include "gui/containers/guiDynamicCtrlArrayCtrl.h" #endif +#include "console/arrayObject.h" + IMPLEMENT_CONOBJECT(GameMode); IMPLEMENT_CALLBACK(GameMode, onActivated, void, (), (), @@ -47,7 +49,9 @@ ConsoleSetType(TypeGameModeList) GameMode::GameMode() : mGameModeName(StringTable->EmptyString()), - mGameModeDesc(StringTable->EmptyString()) + mGameModeDesc(StringTable->EmptyString()), + mIsActive(false), + mIsAlwaysActive(false) { INIT_ASSET(PreviewImage); } @@ -62,6 +66,7 @@ void GameMode::initPersistFields() INITPERSISTFIELD_IMAGEASSET(PreviewImage, GameMode, "Preview Image"); addField("active", TypeBool, Offset(mIsActive, GameMode), "Is the gamemode active"); + addField("alwaysActive", TypeBool, Offset(mIsAlwaysActive, GameMode), "Is the gamemode always active"); } bool GameMode::onAdd() @@ -110,6 +115,11 @@ void GameMode::setActive(const bool& active) onDeactivated_callback(); } +void GameMode::setAlwaysActive(const bool& alwaysActive) +{ + mIsAlwaysActive = alwaysActive; +} + DefineEngineMethod(GameMode, isActive, bool, (), , "Returns if the GameMode is currently active.\n" "@return The active status of the GameMode") @@ -124,24 +134,38 @@ DefineEngineMethod(GameMode, setActive, void, (bool active), (true), object->setActive(active); } -DefineEngineFunction(getGameModesList, const char*, (), , "") +DefineEngineMethod(GameMode, isALwaysActive, bool, (), , + "Returns if the GameMode is currently active.\n" + "@return The active status of the GameMode") +{ + return object->isActive(); +} + +DefineEngineMethod(GameMode, setAlwaysActive, void, (bool alwaysActive), (true), + "Sets the active state of the GameMode.\n" + "@param active A bool of the state the GameMode should be set to") +{ + object->setAlwaysActive(alwaysActive); +} + +DefineEngineFunction(getGameModesList, ArrayObject*, (), , "") { - char* returnBuffer = Con::getReturnBuffer(1024); + ArrayObject* dictionary = new ArrayObject(); + dictionary->registerObject(); - String formattedList; + char activeValBuffer[16]; for (SimGroup::iterator itr = Sim::getRootGroup()->begin(); itr != Sim::getRootGroup()->end(); itr++) { GameMode* gm = dynamic_cast(*itr); if (gm) { - formattedList += String(gm->getName()) + ";"; + dSprintf(activeValBuffer, 16, "%d", (gm->mIsActive || gm->mIsAlwaysActive)); + dictionary->push_back(gm->getName(), activeValBuffer); } } - dSprintf(returnBuffer, 1024, "%s", formattedList.c_str()); - - return returnBuffer; + return dictionary; } //----------------------------------------------------------------------------- diff --git a/Engine/source/T3D/gameMode.h b/Engine/source/T3D/gameMode.h index 85958620ef..9ebde69edb 100644 --- a/Engine/source/T3D/gameMode.h +++ b/Engine/source/T3D/gameMode.h @@ -22,6 +22,7 @@ class GameMode : public SimObject DECLARE_ASSET_SETGET(GameMode, PreviewImage); bool mIsActive; + bool mIsAlwaysActive; public: @@ -34,6 +35,9 @@ class GameMode : public SimObject bool isActive() { return mIsActive; } void setActive(const bool& active); + bool isAlwaysActive() { return mIsAlwaysActive; } + void setAlwaysActive(const bool& alwaysActive); + DECLARE_CONOBJECT(GameMode); static void findGameModes(const char* gameModeList, Vector* outGameModes); diff --git a/Templates/BaseGame/game/core/utility/scripts/scene.tscript b/Templates/BaseGame/game/core/utility/scripts/scene.tscript index 4062c47491..2d8897dee8 100644 --- a/Templates/BaseGame/game/core/utility/scripts/scene.tscript +++ b/Templates/BaseGame/game/core/utility/scripts/scene.tscript @@ -1,52 +1,22 @@ function callGamemodeFunction(%gameModeFuncName, %arg0, %arg1, %arg2, %arg3, %arg4, %arg5, %arg6) { - %activeSceneCount = getSceneCount(); - - %hasGameMode = 0; - for(%i=0; %i < %activeSceneCount; %i++) + %validGameModeCall = false; + %gamemodeList = getGameModesList(); + %gameModeCount = %gamemodeList.count(); + for(%i=0; %i < %gameModeCount; %i++) { - %gamemodeName = getScene(%i).gameModeName; - if(%gamemodeName !$= "") + %gameModeObj = %gamemodeList.getKey(%i); + %active = %gamemodeList.getValue(%i); + + if(!isObject(%gameModeObj) || !%active) + continue; + + if(%gameModeObj.isMethod(%gameModeFuncName)) { - //if the scene defines a game mode, go ahead and envoke it here - if(isObject(%gamemodeName) && %gamemodeName.isMethod(%gameModeFuncName)) - { - eval(%gamemodeName @ "."@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - else - { - //if we don't have an object, attempt the static call - if(isMethod(%gamemodeName, %gameModeFuncName)) - { - eval(%gamemodeName @ "::"@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - } + eval(%gameModeObj @ "."@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); + %validGameModeCall = true; } } - //if none of our scenes have gamemodes, we need to kick off a default - if(%hasGameMode == 0) - { - %defaultModeName = ProjectSettings.value("Gameplay/GameModes/defaultModeName"); - if(%defaultModeName !$= "") - { - if(isObject(%defaultModeName) && %defaultModeName.isMethod(%gameModeFuncName)) - { - eval(%defaultModeName @ "."@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - else - { - if(isMethod(%defaultModeName, %gameModeFuncName)) - { - eval(%defaultModeName @ "::"@%gameModeFuncName@"(\""@%arg0@"\", \""@%arg1@"\", \""@%arg2@"\", \""@%arg3@"\", \""@%arg4@"\", \""@%arg5@"\", \""@%arg6@"\");" ); - %hasGameMode = 1; - } - } - } - } - - return %hasGameMode; + return %validGameModeCall; } \ No newline at end of file diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui index 7bc4850019..8fb0928e2c 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui @@ -11,7 +11,6 @@ $guiContent = new GuiControl(ChooseLevelMenu) { canSaveDynamicFields = "1"; currentMenuIdx = "0"; launchInEditor = "0"; - previewButtonSize = "445 120"; new GuiInputCtrl(ChooseLevelInputHandler) { ignoreMouseEvents = "1"; @@ -40,8 +39,8 @@ $guiContent = new GuiControl(ChooseLevelMenu) { new GuiStackControl(ChooseLevelMenuTabList) { stackingType = "Horizontal"; padding = "10"; - position = "405 61"; - extent = "470 41"; + position = "485 61"; + extent = "310 41"; horizSizing = "center"; profile = "GuiDefaultProfile"; tooltipProfile = "GuiToolTipProfile"; @@ -73,10 +72,12 @@ $guiContent = new GuiControl(ChooseLevelMenu) { position = "320 0"; extent = "150 41"; profile = "GuiMenuButtonProfile"; + visible = "0"; command = "ChooseLevelMenu.openMenu(2);"; tooltipProfile = "GuiToolTipProfile"; internalName = "ConfigBtn"; class = "ChooseLevelMenuButton"; + hidden = "1"; }; }; new GuiControl(ChooseLevelMenuNavButtonOverlay) { @@ -89,7 +90,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { new GuiBitmapCtrl(ChooseLevelMenuPrevNavIcon) { BitmapAsset = "UI:Keyboard_Black_Q_image"; - position = "405 24"; + position = "485 24"; extent = "40 40"; vertSizing = "top"; profile = "GuiNonModalDefaultProfile"; @@ -97,7 +98,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { }; new GuiBitmapCtrl(ChooseLevelMenuNextNavIcon) { BitmapAsset = "UI:Keyboard_Black_E_image"; - position = "515 24"; + position = "595 24"; extent = "40 40"; vertSizing = "top"; profile = "GuiNonModalDefaultProfile"; @@ -121,8 +122,8 @@ $guiContent = new GuiControl(ChooseLevelMenu) { new GuiStackControl(GameModePreviewArray) { padding = "5"; - position = "0 1"; - extent = "445 120"; + position = "1 1"; + extent = "443 60"; horizSizing = "width"; vertSizing = "height"; profile = "GuiMenuDefaultProfile"; @@ -130,15 +131,16 @@ $guiContent = new GuiControl(ChooseLevelMenu) { }; }; new GuiBitmapCtrl(GameModePreviewBitmap) { - BitmapAsset = "UI:no_preview_image"; position = "448 0"; extent = "440 440"; horizSizing = "left"; profile = "GuiNonModalDefaultProfile"; + visible = "0"; tooltipProfile = "GuiToolTipProfile"; + hidden = "1"; }; new GuiTextCtrl(GameModeNameText) { - text = "Example Level"; + text = "DeathMatchGame"; position = "448 445"; extent = "440 20"; horizSizing = "left"; @@ -175,7 +177,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { new GuiStackControl(LevelPreviewArray) { padding = "5"; position = "0 1"; - extent = "445 120"; + extent = "445 60"; horizSizing = "width"; vertSizing = "height"; profile = "GuiMenuDefaultProfile"; @@ -277,6 +279,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; new GuiTextEditCtrl(serverNameCTRL) { + text = "Torque 3D Server"; position = "606 4"; extent = "295 22"; horizSizing = "left"; @@ -374,8 +377,8 @@ $guiContent = new GuiControl(ChooseLevelMenu) { profile = "GuiMenuPanelProfile"; tooltipProfile = "GuiToolTipProfile"; - new GuiIconButtonCtrl(ChooseLevelStartBtn) { - BitmapAsset = "UI:Keyboard_Black_Space_image"; + new GuiIconButtonCtrl(ChooseLevelNextBtn) { + BitmapAsset = "UI:Keyboard_Black_Blank_image"; sizeIconToButton = "1"; makeIconSquare = "1"; textLocation = "Center"; diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript index 3d949d8709..ac1b9d7828 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript @@ -25,7 +25,7 @@ function ChooseLevelMenu::onAdd( %this ) if(!isObject(ChooseLevelAssetQuery)) new AssetQuery(ChooseLevelAssetQuery); - %this.previewButtonSize = "445 120"; + $LevelPreviewButtonSize = "445 60"; } function ChooseLevelMenu::fillPrefEntries( %this ) @@ -40,9 +40,9 @@ function ChooseLevelMenu::fillPrefEntries( %this ) function ChooseLevelMenu::onWake(%this) { %this.fillPrefEntries(); - LevelPreviewArray.clear(); refreshGameModesList(); + refreshLevelsList(); if(!$pref::HostMultiPlayer) ChooseLevelTitleText.setText("SINGLE PLAYER"); @@ -66,35 +66,48 @@ function refreshGameModesList() //popilate the gamemodes list first %gamemodeList = getGameModesList(); - for(%i=0; %i < getTokenCount(%gamemodeList, ";"); %i++) + %gameModeCount = %gamemodeList.count(); + + for (%i=0; %i<%gameModeCount; %i++) { - %gameModeObj = getToken(%gamemodeList, ";", %i); - + %gameModeObj = %gamemodeList.getKey(%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; - }; + %preview = new GuiContainer() { + position = "0 0"; + extent = $LevelPreviewButtonSize; + horizSizing = "right"; + vertSizing = "bottom"; + gameModeObj = %gameModeObj; + gameModeDesc = %gameModeObj.description; + previewImage = %gameModeObj.previewImage; + cansave = false; + + new GuiToggleButtonCtrl() { + position = $LevelPreviewButtonSize.y SPC 0; + extent = $LevelPreviewButtonSize.x - $LevelPreviewButtonSize.y - 5 SPC $LevelPreviewButtonSize.y; + profile = GuiMenuButtonProfile; + horizSizing = "right"; + vertSizing = "bottom"; + internalName = "button"; + class = "GameModePreviewButton"; + text = %gameModeName; + command = "ToggleGameMode(" @ %i @ ");"; //allow doubleclick to quick action it + textMargin = 120; + }; + + new GuiBitmapCtrl() { + position = "0 0"; + extent = $LevelPreviewButtonSize.y SPC $LevelPreviewButtonSize.y; + internalName = "checkbox"; + bitmapAsset = "UI:toggleMarker_image"; + }; + }; + GameModePreviewArray.add(%preview); } @@ -122,6 +135,7 @@ function refreshLevelsList() } //filter the levelAssets by the gamemode selected + %filteredIndex = 0; for(%i=0; %i < %count; %i++) { %assetId = ChooseLevelAssetQuery.getAsset(%i); @@ -142,21 +156,26 @@ function refreshLevelsList() //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()) + for(%g=0; %g < GameModePreviewArray.getCount(); %g++) { - %foundGameModeMatch = true; - break; + %gmb = GameModePreviewArray.getObject(%g); + if(!isObject(%gmb.gameModeObj) || (!%gmb.gameModeObj.active && !%gmb.gameModeObj.alwaysActive)) + continue; + + if(%gameModeName $= %gmb.gameModeObj.getName()) + { + %foundGameModeMatch = true; + break; + } } + + if(%foundGameModeMatch) + break; } if(!%foundGameModeMatch) @@ -169,7 +188,7 @@ function refreshLevelsList() %preview = new GuiIconButtonCtrl() { position = "0 0"; - extent = ChooseLevelMenu.previewButtonSize; + extent = $LevelPreviewButtonSize; buttonType = "PushButton"; profile = GuiMenuButtonLeftJustProfile; horizSizing = "right"; @@ -177,7 +196,7 @@ function refreshLevelsList() internalName = "button"; class = "LevelPreviewButton"; //command = "$selectedLevelAsset = " @ %assetId @ ";"; - altCommand = "ChooseLevelBegin(" @ %i @ ");"; //allow doubleclick to quick action it + altCommand = "ChooseLevelBegin(" @ %filteredIndex @ ");"; //allow doubleclick to quick action it text = %levelAsset.levelName; bitmapAsset = %levelPreviewImg; levelAsset = %levelAsset; @@ -192,8 +211,11 @@ function refreshLevelsList() }; LevelPreviewArray.add(%preview); + + %filteredIndex++; } - + + GameModePreviewArray.listPosition = 0; LevelPreviewArray.listPosition = 0; // Also add the new level mission as defined in the world editor settings @@ -236,10 +258,31 @@ function ChooseLevelMenu::syncGUI(%this) ChooseLevelBackBtn.setBitmap(BaseUIActionMap.getCommandButtonBitmap(%device, "BaseUIBackOut")); - ChooseLevelStartBtn.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelBegin")); + if(ChooseLevelMenu.currentMenuIdx == 0) + ChooseLevelNextBtn.text = "Toggle Mode"; + else + ChooseLevelNextBtn.text = "Start Game"; + + ChooseLevelNextBtn.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelMenuOption")); ChooseLevelMenuPrevNavIcon.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelMenuPrevMenu")); ChooseLevelMenuNextNavIcon.setBitmap(ChooseLevelActionMap.getCommandButtonBitmap(%device, "ChooseLevelMenuNextMenu")); + + %hasActiveGameMode = false; + for(%i=0; %i < GameModePreviewArray.getCount(); %i++) + { + %btn = GameModePreviewArray.getObject(%i); + if(!isObject(%btn.gameModeObj)) + continue; + + if((%btn.gameModeObj.isActive() || %btn.gameModeObj.isAlwaysActive()) == true) + { + %hasActiveGameMode = true; + break; + } + } + + ChooseLevelMenuTabList-->LevelBtn.active = %hasActiveGameMode; } function LevelPreviewArray::syncGUI(%this) @@ -252,10 +295,19 @@ function LevelPreviewArray::syncGUI(%this) function GameModePreviewArray::syncGUI(%this) { + for(%i=0; %i < %this.getCount(); %i++) + { + %btn = %this.getObject(%i); + %btn-->button.setHighlighted(false); + + %bitmapAst = (%btn.gameModeObj.active || %btn.gameModeObj.alwaysActive) ? "UI:toggleMarker_h_image" : "UI:toggleMarker_image"; + %btn-->checkbox.setBitmap(%bitmapAst); + } + %btn = %this.getObject(%this.listPosition); - %btn.setHighlighted(true); + %btn-->button.setHighlighted(true); - $pref::Server::GameMode = %btn.gameModeObject; + //$pref::Server::GameMode = %btn.gameModeObject; } function ChooseLevelMenuPrevMenu(%val) @@ -270,6 +322,13 @@ function ChooseLevelMenuPrevMenu(%val) %endIndex = 2; ChooseLevelMenu.currentMenuIdx = mClamp(ChooseLevelMenu.currentMenuIdx, 0, %endIndex); + + //bail if we're trying to step into a hidden or disabled menu + if(!ChooseLevelMenu.isMenuAvailable(ChooseLevelMenu.currentMenuIdx)) + { + ChooseLevelMenu.currentMenuIdx = %currentIdx; + return; + } if(%currentIdx == ChooseLevelMenu.currentMenuIdx) return; @@ -290,6 +349,13 @@ function ChooseLevelMenuNextMenu(%val) %endIndex = 2; ChooseLevelMenu.currentMenuIdx = mClamp(ChooseLevelMenu.currentMenuIdx, 0, %endIndex); + + //bail if we're trying to step into a hidden or disabled menu + if(!ChooseLevelMenu.isMenuAvailable(ChooseLevelMenu.currentMenuIdx)) + { + ChooseLevelMenu.currentMenuIdx = %currentIdx; + return; + } if(%currentIdx == ChooseLevelMenu.currentMenuIdx) return; @@ -319,33 +385,48 @@ function ChooseLevelMenu::openMenu(%this, %menuIdx) %this.syncGui(); } +function ChooseLevelMenu::isMenuAvailable(%this, %menuIdx) +{ + if(%menuIdx == 0) + %btn = %this-->GameModeBtn; + else if(%menuIdx == 1) + %btn = %this-->LevelBtn; + else if(%menuIdx == 2) + %btn = %this-->ConfigBtn; + + return %btn.isActive() && %btn.isVisible(); +} + function ChooseLevelMenuOption(%val) { if(%val) { if(ChooseLevelMenu.currentMenuIdx == 0) - ChooseGameModeBegin(ChooseLevelMenu.listPosition); + ToggleGameMode(ChooseLevelMenu.listPosition); else if(ChooseLevelMenu.currentMenuIdx == 1) ChooseLevelBegin(ChooseLevelMenu.listPosition); } } -function ChooseGameModeBegin(%index) +function ToggleGameMode(%index) { + if(%index $= "") + %index = $MenuList.listPosition; + %entry = GameModePreviewArray.getObject(%index); if(!isObject(%entry) || !isObject(%entry.gameModeObj)) { MessageBoxOK("Error", "Selected game mode does not have a valid mode"); return; } - - $pref::Server::GameMode = %entry.gameModeObj; - - ChooseLevelMenuTabList-->LevelBtn.active = true; + + %entry.gameModeObj.active = !%entry.gameModeObj.active; refreshLevelsList(); - ChooseLevelMenu.openMenu(1); + $MenuList.syncGui(); + ChooseLevelMenu.syncGui(); + } function ChooseLevelBegin(%index) @@ -399,7 +480,7 @@ function GameModePreviewButton::onHighlighted(%this, %highlighted) { if(%highlighted) { - $MenuList.listPosition = $MenuList.getObjectIndex(%this); + $MenuList.listPosition = $MenuList.getObjectIndex(%this.getParent()); GameModePreviewBitmap.bitmapAsset = %this.previewImage; diff --git a/Templates/BaseGame/game/data/UI/images/toggleMarker.png b/Templates/BaseGame/game/data/UI/images/toggleMarker.png new file mode 100644 index 0000000000000000000000000000000000000000..6d3a0dfd4a94b584cc7e14ea2d639778720b1cd7 GIT binary patch literal 6747 zcmeHMc~}$I77sh+frw(D6iW<>ip`#o1;Q2}ECwiG30q~7OkgA%Nm$iZMG%WpQN$`L zRqI+stxJ(jP^zL>iaf1S<1Pk4Kt)j$eK%oITif@1-|PF!eBaF6Ip^HpJS3Z-0(NMMXQO95lxOhk-A zWwssSOAmLtSU%r7OZ~wDRK4#+1!ZH=#F5xG~Ww&nwHF2L{*H?A_jJw8S-_@wcnC{g2mV&!z2~V3KkD zMEIO%IoU5UJw_FMiOEj+=be|7)_!!lYEt@dMpYk8gUfM_I==G#L#HlpffAq68 z<*8|FPgioPEFxKIYz2DxHM>OL3u{wdc2q7rBwM$WQF`{m!qrI4y~7ugldh#wMN5@W zsN!b4Q9|rmetf!>W9;MXwvNS=j9zU8u8m#wJt5tyl(&Tzd+d~E^UecAam(%AAg+7U z&XD@pRptD8!W~;l#ipi?31{o_H@NSB4CXPNtZ%QbPYf?kTPi}u(Z|y+dKE3OYgv|O zaQkrl?mi6)bL*`+but#!TIiRCvvWIng1Z zp?n*j5jFmRjvSIo5Bd5iLbuU+GqlL7-LEg8oguoo;FN1KL*?oamF zoIklSV%*d+->-rqf|;wk$j-@mjt6cPtMDd5G#{@`_~qN;yROl*jn4D8Zd;dvtO$FE zJ9zQCttD|CSwRHK$NhHGRk4d}JGhttlADD{D7 zpPrLj7naB!cC|p|p?MEAU%OQtp0jQ3mZ&0rf^FBJ#+di9BX?RbaZhp{{t^4L`e9D$7VC|zc6SOw8TTD8tSED9>@w9_`_%27g6*!#*Opb^M(=TWP~Nca5&lTg zrYpr~cGt!7rqKLX+=-r9RQbp%^5EHC)zD3C&>0!oXV%|5vjvx1_RZ$GwDolVuaB`( zzWQQL-}>p;B&PR;}wGKZtw<;Gw83;mII&JCbfoz zrn+2*;{Hhp+R}GU+$=;t36Bd&J9yf%YQvFrHj9ETpK=ItblIL*vtPA|R{GJz6TPOl zFMaau{4vthq)vwjqRWrWg4)7`SAB)st3pC9m&T`Jx zHp9A4iv!xOXw1!{1}%Obw~Z40qiyN;ah*Aowk{c)VUlA}o|>16 zL_dY0=B44ohJ!cKXB#)8PzH+;4ktW_!x`BRz%HNqOX+{!?o z#miW(JC6A-`)s-Gc%<#?xlfije)&~A`G{-cExxyv>*SNxFU*cs(F!70$s!nIj6at= z6WW@08}7-Ie(CgEdZ%%(mr-==$=NA}2g<&qvts+qwO38?(A zxiX1BVltToBAGxYx$b6v`l5UD~x2q^9wz=~wKTCPf#D@M)VKp$2AVF)!M{u7UMnm?F>ao;||NRjmJ z11lbnCzD7$HoE)UWABPRDT~P#f?mMBXa^sB!4QymqGF2(5Mt&jvI+db9=3O zkW8h9WI}kPI|91Xs5ANdS|{FoW_MM3@o*QwEa! z*Pe)ktN^)E3IU!WQYZ-~C}c@`d|kO%9JxfUj0Ce2W@AaO#NkLLEaCehAa)gCJkSw| zzthINMXM_|%O^aDjV0rU?Ddjx38WSSAHaM^WFmQnN)M%1MS&qA)sU_vBs!TvBr=#J zI*CG|xY1u3eFiIsyddLAL_CF|*NpOECrG<*(gaY1#ZkhL8ukMhzy^iJkw`cqIf_hT zQOGPJbq0~d8U`G-mMf#=atRx&%LqmnquwQ*ng^CS5u}Dj;K)=KiO3>SbZ}$_i$cQ@ zX|Kb9EFj{ne`l>*)G!_ZA22)+QK{w1EWK!GrHh19hu(%>C5V2B!(jA_6$=s$WmE;F z!y>((0BcAkOon7hFj&=xr}c;&dD|=y3y5woQGlZm$wV9kQE51VNF>CG1p+aXE}&BB zMDd&GD!Evl0V!dhBv4j1)xkm6yT^}yTL(J1{p^Mb6rS<=|UQPsOF=&{9UwMuxRR;@$Z5plW-&= zNNN;`$|93lG&1)85FV8VyV0q15)KkjNjR#Q>;}SP(s2+?%%IVTOp#FVJF5KG;eqKy z#J%D_eIz^&f^JhDp{@tvkK`WayC1AX0CNMBa;cuzDmB;;S)xB*=ZSy0&I9V}`oE3h zL;D0N;7|MOD>LNyYo`mq#vvIvwSYqqVe}LgcNAC#j@cRDE6G;-Pjvs9mZQu3u8maDGBY@&jQhzAU~g|%(ia<>_@yf*2d$huSlQ(fKg$`57p7P;4$yA4)G?sG?{~%i zFiB&VB8x1Xn3C*{e*VM3sTgl#KERKoE;W}7_!CPFbDKRx5-eYgFt>Ibe3!8Nm7TsD5$F%L8|n8`PoS#a88<-$SZ zb(5{m88X2$@zN)Esa8MDDzihEl>FXXk)s}ndtCEXskwE8+1?*t?khi0+wtS!^pZK{ z#xAPRA7^wtGfbJW>eA|6Y54uqLGhq?PQS`lVt!Y=GdInNaqgaF&z0}1Um56rw#Ulh z{No?a-dIWY{KhaGzSUh!Er;`*4GUkoo!o6MrL_oro_|!nFQO=EA?_@-urxpw5Vqve z!f9RK$*Rj*d+%%}wthN!04J;3zOeV4>UQy5wZ#9ip!UYP!K&`yTjSDRT)28EMt*wk z`c13gzO5At(5_ue);ik37BSiW_l<#>z0LH=;azpnE*HWJFYwRDqS2B{V$!wmzUNPi zdFYm@@6v3l5TLS#FQ(Ctw)6@dvd@>U4oDKU_1P^=`!3;%(59qql7HV+*42g`etC@! zcK3k&B>e3RSem&%x7e)EU)`kixnC!NC`7tdby?Mg!W zVqaItWX_43oW1=9I181T^fV0MI*W~+qwSkH#8nIM2kH{%;!9)eRCRX2N0Lby1x+wi zk-3D$01l0RWWUpG_-q-Ln*OY7H7#m$a!v#F7OvGfme%f#D2 zK)voJaBr>auNyx``|Pg=KDU#kZfAt{m{A6{o`@?>>{4Ch2p6xNaA&54*5nUhgnTd?z-Zl#gm_Q|LB?whi z7))XF&kJ2GT?VD{!G;?3nym$mHV-b%{-O)_Vy4lPW4=vkIhildExx=v!sn}?Vs`(e zO(u`_#qjkzmpL7D%y6satnVn1?AR?*6%`CUjH8P3cYBnt)Q+3s3(g+KM$HsN@vr9X~4nDfjS<7aPnE7cA}SIZ$u|34Y9UJCg6j?1lH6 zP)qy0vTG(yFPfHr{%}X{EkAY7+Pk0Z`FNd6o^(I`?wpGTx)rY+Olr?C@A(<4^mS6N zA$6T4o^B2IH~#V}=-`~WCKET2fCE-Xs>;*5S6Qn+F};1t(=b-aZg&N$>Gy8@(zxWRQeoXV zEjO7z?w&k8+ja5f&ojU!1!d}9>nB7gsiN{IFAeY?>;YJ(vh(Z)J3Au_n!O%Q6WR`T zPQ~TyhT0$Z`Frl{StNBpj-0I6dnKkTdPZRzKe@Nw5vO}1zOibUW?@%T>^##bDaZ4w>V^;R!tfBW_8 zp_MX)o#46+SWwi-dEVg|UgR2dwrmh^e_qHgerWr3fAz06OAZS+1f5=(lbHFmcHm`W zx<&nX#Vcd;uA?{n!`ctxaO2hpe0)NfK0YJ+0k#Virfqf)Z1l7}wKObfZ@BGUJYmZ& zm_2dI@?58>_1=Pyj`qaL?pQ&Bz|a_5{mU7vXJkhhTE#7~i_!PGuiJFBp4sL%|M}9; zc#9kL%RR1lefm?i?i&8KgZx(8oy)dw4sJRX^I3#R4MERC$GUz^(*pM|*v$>{)UL)e zmn*GXAIuxLtqz+oTQ`;PyX_^@Y~^Yt|57`ne{w(+J2bO3>x^jy{&)M!Xx`8CJlm;C ze$&}~x3lb;`^VI{W6^QwLe!3)g! zZNH;scHOfN(!!=&m+N~ch$Z*Z3g_Y2J9 zpHr>#a5#(Q0&IVdVl9BV5)ldENjNA;DUxFQHxB3Kp_C%r1XO|Npm74RJE6a_mVg)V z+zE?lEPy5TLE{C1D`aTcir{eWiUcl$NAQ?8!A%Kc03uX@;FThwSPm=Q30hnj8*9j9 z0$wYkNN^`au|n`Z5*dnzNDv7i`Y8oT6vDg-csCi34~P2t4?|#I?u2-SLJE_~$;rv2 zWGYD_iz9;!27?Sx$P@|@lOW1d#0o@76wB>35JMQgsGKVkNEHH!7_Y%ZIFdw#JAr`l z@gw$$q%77byjVUQ1uPz9B_bt*B!Dauk>AviEBul$kYR(~sv!@@jydE|R4z%BaZ$e{ zRIIQc3lWOG_US)>z{JM@@VKJ}NE2m3Z9I5fGAcwx*j#eVY;cUtniJQk#+dmu9Icmc z+84lNg^Y@8LLMg&Nwt$;$sGeMjh84S@_31KRD2BjDEm)SCxClQ=f-+h-U_ zlGc1oip7F`CEP?!7?{591dKxB33xE%=f$8pQz*_9AAb<^gXrFLs+SK9qI&x{gAAJU z8|qB4T!DzW=!m)iQ%AW}KF4JSELWwL4Tb-yo0enpk5hzh%l&=8uU5-^e z#sh$V;~b1OlYt@Ql{OkPj>g4|9R4Mb@Mp#sY^W4$&;w5!bYI%~BF3 zYRC~{0TvX>Yn7W%1;8#c=J>GdT5a~f6v0Xa_K~580`yF;A_x)zN;n0C zsT3H1J_0~^7;sctA`6#Dgzf}QMDQ9PwI*rUEUeA(5Ct*6%tvBmNeAq!qDYIUqhclfwslr@!DnuBix~g%8?|L zr?nHq8e(zd5pf)fb@kz8Jwg|}Wfjo)9Kaa`I7BKy0f-0!(TE%#k4xlpIDCc+2ZCGx z{#bOmgs(_OWT;mhmR5Iymu82-hm74AtDf+LWEE{l?$ORT$;;J&PQYUPu>P#O;cML|ISGYNCW{a zsNo<4Qz$TvLij(p2Qg@z8GM8SamME4zjhB>P5^PV z{wctSdv0XSraV%*Zsb1~_i(-YqA~$ix&AVVNL$x(1-2o=yua@Az`yMCSm|r@-&W#7 z`vmC!m-^bw3>p5$=>n_9AsTjS!45&>(NoZHnPMgKcV5Gb{qLLtkAFYOJH_{XxZa2B zog(l~!tbl=eYoB!0`DaJzPkQr;hOOJKME?wj@ilBUy_0WnkV*$#efqO;EQ{u`Po8s zNc{tQjh64 z`nAsrccNxE{L)hX7mPJtKCQP>V%xpv v{+4C diff --git a/Templates/BaseGame/game/data/UI/images/toggleMarker_image.asset.taml b/Templates/BaseGame/game/data/UI/images/toggleMarker_image.asset.taml new file mode 100644 index 0000000000..d8b681616b --- /dev/null +++ b/Templates/BaseGame/game/data/UI/images/toggleMarker_image.asset.taml @@ -0,0 +1,3 @@ + diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript index e58be2c7fd..ae5e59b3e2 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetTypes/gameMode.tscript @@ -1,96 +1,35 @@ -function AssetBrowser::createGameMode(%this) +AssetBrowser::registerObjectType("GameModeType", "Gamemode Objects", "Gamemode"); + +function GameModeType::setupCreateNew() { - %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"; +function GameModeType::onCreateNew() +{ - 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); - } - } +function GameModeType::buildBrowserElement(%this, %className, %previewData) +{ + //echo("DatablockObjectType::buildBrowserElement() - " @ %datablock @ ", " @ %name @ ", " @ %previewData); + %previewData.assetName = %this.getName(); + %previewData.assetPath = %this.getFileName(); - %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); - } - } + %previewData.previewImage = "ToolsModule:genericAssetIcon_image"; - //and we'll go ahead and force execute the script file so the gamemode is 'available' for use immediately - exec(%scriptPath); + //Lets see if we have a icon for specifically for this datablock type + %dataClass = %this.getClassName(); + %dataClass = strreplace(%dataClass, "Data", ""); - if(isObject(%assetName)) + %previewImage = "tools/classIcons/" @ %dataClass @ ".png"; + if(isFile(%previewImage)) { - //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); + %previewData.previewImage = %previewImage; } - return %scriptPath; + //%previewData.assetFriendlyName = %assetDef.assetName; + %previewData.assetDesc = %this; + %previewData.tooltip = "\nGameMode Name: " @ %previewData.assetName @ + "\nPath: " @ %previewData.assetPath; } \ No newline at end of file From 020bd03cf98a14e4af16a10a041e76f8252510bb Mon Sep 17 00:00:00 2001 From: JeffR Date: Fri, 4 Oct 2024 00:22:12 -0500 Subject: [PATCH 08/17] Adds logic so levels without a gamemode defined at all are treated as wildcarded to any given gamemode selected --- .../game/data/UI/guis/ChooseLevelMenu.tscript | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript index ac1b9d7828..f25ddff520 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript @@ -156,26 +156,35 @@ function refreshLevelsList() //filter for selected gamemode %levelGameModes = %levelAsset.gameModesNames; - %foundGameModeMatch = false; - for(%gm = 0; %gm < getTokenCount(%levelGameModes, ";"); %gm++) + //If the level has no gamemodes defined, we just assume the default case of it being a wildcard to whatever gamemode + //is available + if(%levelGameModes $= "") { - %gameModeName = getToken(%levelGameModes, ";", %gm); - - for(%g=0; %g < GameModePreviewArray.getCount(); %g++) + %foundGameModeMatch = true; + } + else + { + %foundGameModeMatch = false; + for(%gm = 0; %gm < getTokenCount(%levelGameModes, ";"); %gm++) { - %gmb = GameModePreviewArray.getObject(%g); - if(!isObject(%gmb.gameModeObj) || (!%gmb.gameModeObj.active && !%gmb.gameModeObj.alwaysActive)) - continue; - - if(%gameModeName $= %gmb.gameModeObj.getName()) + %gameModeName = getToken(%levelGameModes, ";", %gm); + + for(%g=0; %g < GameModePreviewArray.getCount(); %g++) { - %foundGameModeMatch = true; - break; + %gmb = GameModePreviewArray.getObject(%g); + if(!isObject(%gmb.gameModeObj) || (!%gmb.gameModeObj.active && !%gmb.gameModeObj.alwaysActive)) + continue; + + if(%gameModeName $= %gmb.gameModeObj.getName()) + { + %foundGameModeMatch = true; + break; + } } + + if(%foundGameModeMatch) + break; } - - if(%foundGameModeMatch) - break; } if(!%foundGameModeMatch) From 484ece3d28e586723c7ad85893b042d5d30ad791 Mon Sep 17 00:00:00 2001 From: JeffR Date: Mon, 21 Oct 2024 00:08:07 -0500 Subject: [PATCH 09/17] adds loadIf conditional eval, onLoad/UnloadCommands, ability to freeze loading state and per-subscene ticking for conditional checks Fixes for child iteration of subscenes Renamed tripCondition field in triggers to tripIf for consistency/naming clarity Added ability for callbacks for gamemode to have reference on which subscene was loaded/unloaded for respective callback --- Engine/source/T3D/SubScene.cpp | 136 +++++++++++++++++++++++++-------- Engine/source/T3D/SubScene.h | 18 ++++- Engine/source/T3D/gameMode.cpp | 4 +- Engine/source/T3D/gameMode.h | 7 +- Engine/source/T3D/trigger.cpp | 8 +- Engine/source/T3D/trigger.h | 2 +- 6 files changed, 132 insertions(+), 43 deletions(-) diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 59f68d3dd2..03e3fbe69e 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -7,16 +7,25 @@ #include "gfx/gfxDrawUtil.h" #include "gfx/gfxTransformSaver.h" #include "gui/editor/inspector/group.h" +#include "T3D/gameBase/gameBase.h" IMPLEMENT_CO_NETOBJECT_V1(SubScene); S32 SubScene::mUnloadTimeoutMs = 5000; +IMPLEMENT_CALLBACK(SubScene, onLoaded, void, (), (), + "@brief Called when a subScene has been loaded and has game mode implications.\n\n"); +IMPLEMENT_CALLBACK(SubScene, onUnloaded, void, (), (), + "@brief Called when a subScene has been unloaded and has game mode implications.\n\n"); + SubScene::SubScene() : mLevelAssetId(StringTable->EmptyString()), mGameModesNames(StringTable->EmptyString()), mScopeDistance(-1), mLoaded(false), + mFreezeLoading(false), + mTickPeriodMS(1000), + mCurrTick(0), mGlobalLayer(false) { mNetFlags.set(Ghostable | ScopeAlways); @@ -33,6 +42,8 @@ bool SubScene::onAdd() if (!Parent::onAdd()) return false; + setProcessTick(true); + return true; } @@ -41,6 +52,8 @@ void SubScene::onRemove() if (isClientObject()) removeFromScene(); + unload(); + Parent::onRemove(); } @@ -49,10 +62,20 @@ void SubScene::initPersistFields() addGroup("SubScene"); addField("isGlobalLayer", TypeBool, Offset(mGlobalLayer, SubScene), ""); INITPERSISTFIELD_LEVELASSET(Level, SubScene, "The level asset to load."); - addField("loadIf", TypeCommand, Offset(mLoadIf, SubScene), "evaluation condition (true/false)"); addField("gameModes", TypeGameModeList, Offset(mGameModesNames, SubScene), "The game modes that this subscene is associated with."); endGroup("SubScene"); + addGroup("LoadingManagement"); + addField("freezeLoading", TypeBool, Offset(mFreezeLoading, SubScene), "If true, will prevent the zone from being changed from it's current loading state."); + addField("loadIf", TypeCommand, Offset(mLoadIf, SubScene), "evaluation condition (true/false)"); + + addField("tickPeriodMS", TypeS32, Offset(mTickPeriodMS, SubScene), "evaluation rate (ms)"); + + addField("onLoadCommand", TypeCommand, Offset(mOnLoadCommand, SubScene), "The command to execute when the subscene is loaded. Maximum 1023 characters."); + addField("onUnloadCommand", TypeCommand, Offset(mOnUnloadCommand, SubScene), "The command to execute when subscene is unloaded. Maximum 1023 characters."); + endGroup("LoadingManagement"); + + Parent::initPersistFields(); } @@ -139,22 +162,29 @@ void SubScene::inspectPostApply() setMaskBits(-1); } -bool SubScene::testBox(const Box3F& testBox) +bool SubScene::evaluateCondition() { - if (mGlobalLayer) - return true; - - bool passes = getWorldBox().isOverlapped(testBox); - if (passes && !mLoadIf.isEmpty()) + if (!mLoadIf.isEmpty()) { //test the mapper plugged in condition line String resVar = getIdString() + String(".result"); Con::setBoolVariable(resVar.c_str(), false); String command = resVar + "=" + mLoadIf + ";"; + Con::evaluatef(command.c_str()); - passes = Con::getBoolVariable(resVar.c_str()); + return Con::getBoolVariable(resVar.c_str()); } + return true; +} +bool SubScene::testBox(const Box3F& testBox) +{ + if (mGlobalLayer) + return true; + + bool passes = getWorldBox().isOverlapped(testBox); + if (passes) + passes = evaluateCondition(); return passes; } @@ -187,6 +217,14 @@ void SubScene::write(Stream& stream, U32 tabStop, U32 flags) void SubScene::processTick(const Move* move) { + mCurrTick += TickMs; + if (mCurrTick > mTickPeriodMS) + { + mCurrTick = 0; + //re-evaluate + if (!evaluateCondition()) + unload(); + } } void SubScene::_onFileChanged(const Torque::Path& path) @@ -201,19 +239,34 @@ void SubScene::_onFileChanged(const Torque::Path& path) setMaskBits(U32_MAX); } -void SubScene::_closeFile(bool removeFileNotify) +void SubScene::_removeContents(SimGroupIterator set) { - AssertFatal(isServerObject(), "Trying to close out a subscene file on the client is bad!"); - - U32 count = size(); - - for (SimSetIterator itr(this); *itr; ++itr) + for (SimGroupIterator itr(set); *itr; ++itr) { - SimObject* child = dynamic_cast(*itr); + SimGroup* child = dynamic_cast(*itr); if (child) + { + _removeContents(SimGroupIterator(child)); + + GameBase* asGameBase = dynamic_cast(child); + if (asGameBase) + { + asGameBase->scriptOnRemove(); + } + + Sim::cancelPendingEvents(child); + child->safeDeleteObject(); + } } +} + +void SubScene::_closeFile(bool removeFileNotify) +{ + AssertFatal(isServerObject(), "Trying to close out a subscene file on the client is bad!"); + + _removeContents(SimGroupIterator(this)); if (removeFileNotify && mLevelAsset.notNull() && mLevelAsset->getLevelPath() != StringTable->EmptyString()) { @@ -249,14 +302,24 @@ void SubScene::load() if (mLoaded) return; + if (mFreezeLoading) + return; + _loadFile(true); mLoaded = true; GameMode::findGameModes(mGameModesNames, &mGameModesList); + onLoaded_callback(); for (U32 i = 0; i < mGameModesList.size(); i++) { - mGameModesList[i]->onSubsceneLoaded_callback(); + mGameModesList[i]->onSubsceneLoaded_callback(this); + } + + if (!mOnLoadCommand.isEmpty()) + { + String command = "%this = " + String(getIdString()) + "; " + mLoadIf + ";"; + Con::evaluatef(command.c_str()); } } @@ -265,6 +328,9 @@ void SubScene::unload() if (!mLoaded) return; + if (mFreezeLoading) + return; + if (isSelected()) { mStartUnloadTimerMS = Sim::getCurrentTime(); @@ -273,33 +339,43 @@ void SubScene::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) + for (SimGroupIterator itr(this); *itr; ++itr) { SimGroup* childGrp = dynamic_cast(*itr); - if (childGrp && childGrp->isSelected()) + if (childGrp) { - 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()) + if (childGrp->isSelected()) { mStartUnloadTimerMS = Sim::getCurrentTime(); return; //if a child is selected, then we don't want to unload } + for (SimGroupIterator cldItr(childGrp); *cldItr; ++cldItr) + { + SimObject* chldChld = dynamic_cast(*cldItr); + if (chldChld && chldChld->isSelected()) + { + mStartUnloadTimerMS = Sim::getCurrentTime(); + return; //if a child is selected, then we don't want to unload + } + } } } - _closeFile(true); - mLoaded = false; - + onUnloaded_callback(); for (U32 i = 0; i < mGameModesList.size(); i++) { - mGameModesList[i]->onSubsceneUnloaded_callback(); + mGameModesList[i]->onSubsceneUnloaded_callback(this); } + + if (!mOnUnloadCommand.isEmpty()) + { + String command = "%this = " + String(getIdString()) + "; " + mOnUnloadCommand + ";"; + Con::evaluatef(command.c_str()); + } + + _closeFile(true); + mLoaded = false; + } bool SubScene::save() diff --git a/Engine/source/T3D/SubScene.h b/Engine/source/T3D/SubScene.h index 53c8b12967..e70ae812b5 100644 --- a/Engine/source/T3D/SubScene.h +++ b/Engine/source/T3D/SubScene.h @@ -8,9 +8,8 @@ #ifndef LEVEL_ASSET_H #include "assets/LevelAsset.h" #endif -#ifndef GAME_MODE_H -#include "gameMode.h" -#endif + +class GameMode; class SubScene : public SceneGroup { @@ -38,7 +37,14 @@ class SubScene : public SceneGroup S32 mStartUnloadTimerMS; bool mLoaded; + bool mFreezeLoading; + String mLoadIf; + String mOnLoadCommand; + String mOnUnloadCommand; + + S32 mTickPeriodMS; + U32 mCurrTick; bool mGlobalLayer; public: @@ -50,6 +56,7 @@ class SubScene : public SceneGroup static void initPersistFields(); static void consoleInit(); + StringTableEntry getTypeHint() const override { return (getLevelAsset()) ? getLevelAsset()->getAssetName() : StringTable->EmptyString(); } // SimObject bool onAdd() override; @@ -65,7 +72,7 @@ class SubScene : public SceneGroup void inspectPostApply() override; bool testBox(const Box3F& testBox); - + bool evaluateCondition(); void _onSelected() override; void _onUnselected() override; @@ -76,6 +83,7 @@ class SubScene : public SceneGroup // void _onFileChanged(const Torque::Path& path); + void _removeContents(SimGroupIterator); void _closeFile(bool removeFileNotify); void _loadFile(bool addFileNotify); @@ -104,6 +112,8 @@ class SubScene : public SceneGroup bool save(); + DECLARE_CALLBACK(void, onLoaded, ()); + DECLARE_CALLBACK(void, onUnloaded, ()); DECLARE_ASSET_SETGET(SubScene, Level); }; #endif diff --git a/Engine/source/T3D/gameMode.cpp b/Engine/source/T3D/gameMode.cpp index 0032b12ad1..32253cca58 100644 --- a/Engine/source/T3D/gameMode.cpp +++ b/Engine/source/T3D/gameMode.cpp @@ -16,9 +16,9 @@ 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, (), (), +IMPLEMENT_CALLBACK(GameMode, onSubsceneLoaded, void, (SubScene*), ("SubScene"), "@brief Called when a subScene has been loaded and has game mode implications.\n\n"); -IMPLEMENT_CALLBACK(GameMode, onSubsceneUnloaded, void, (), (), +IMPLEMENT_CALLBACK(GameMode, onSubsceneUnloaded, void, (SubScene*), ("SubScene"), "@brief Called when a subScene has been unloaded and has game mode implications.\n\n"); diff --git a/Engine/source/T3D/gameMode.h b/Engine/source/T3D/gameMode.h index 9ebde69edb..d28deecc06 100644 --- a/Engine/source/T3D/gameMode.h +++ b/Engine/source/T3D/gameMode.h @@ -8,6 +8,9 @@ #endif #endif +#ifndef SUB_SCENE_H +#include "SubScene.h" +#endif #include "T3D/assets/ImageAsset.h" @@ -48,8 +51,8 @@ class GameMode : public SimObject DECLARE_CALLBACK(void, onDeactivated, ()); DECLARE_CALLBACK(void, onSceneLoaded, ()); DECLARE_CALLBACK(void, onSceneUnloaded, ()); - DECLARE_CALLBACK(void, onSubsceneLoaded, ()); - DECLARE_CALLBACK(void, onSubsceneUnloaded, ()); + DECLARE_CALLBACK(void, onSubsceneLoaded, (SubScene*)); + DECLARE_CALLBACK(void, onSubsceneUnloaded, (SubScene*)); }; DefineConsoleType(TypeGameModeList, String) diff --git a/Engine/source/T3D/trigger.cpp b/Engine/source/T3D/trigger.cpp index 0320d62086..dcccd07786 100644 --- a/Engine/source/T3D/trigger.cpp +++ b/Engine/source/T3D/trigger.cpp @@ -173,7 +173,7 @@ Trigger::Trigger() mPhysicsRep = NULL; mTripOnce = false; mTrippedBy = 0xFFFFFFFF; - mTripCondition = ""; + mTripIf = ""; //Default up a basic square Point3F vecs[3] = { Point3F(1.0, 0.0, 0.0), @@ -379,7 +379,7 @@ void Trigger::initPersistFields() "representing the edges extending from the corner.\n"); addField("TripOnce", TypeBool, Offset(mTripOnce, Trigger),"Do we trigger callacks just the once?"); - addField("TripCondition", TypeRealString, Offset(mTripCondition, Trigger),"evaluation condition to trip callbacks (true/false)"); + addField("tripIf", TypeRealString, Offset(mTripIf, Trigger),"evaluation condition to trip callbacks (true/false)"); addField("TrippedBy", TypeGameTypeMasksType, Offset(mTrippedBy, Trigger), "typemask filter"); addProtectedField("enterCommand", TypeCommand, Offset(mEnterCommand, Trigger), &setEnterCmd, &defaultProtectedGetFn, "The command to execute when an object enters this trigger. Object id stored in %%obj. Maximum 1023 characters." ); @@ -697,13 +697,13 @@ bool Trigger::testTrippable() bool Trigger::testCondition() { - if (mTripCondition.isEmpty()) + if (mTripIf.isEmpty()) return true; //we've got no tests to run so just do it //test the mapper plugged in condition line String resVar = getIdString() + String(".result"); Con::setBoolVariable(resVar.c_str(), false); - String command = resVar + "=" + mTripCondition + ";"; + String command = resVar + "=" + mTripIf + ";"; Con::evaluatef(command.c_str()); if (Con::getBoolVariable(resVar.c_str()) == 1) { diff --git a/Engine/source/T3D/trigger.h b/Engine/source/T3D/trigger.h index 7cd00dd347..9b9190e674 100644 --- a/Engine/source/T3D/trigger.h +++ b/Engine/source/T3D/trigger.h @@ -87,7 +87,7 @@ class Trigger : public GameBase bool mTripped; S32 mTrippedBy; - String mTripCondition; + String mTripIf; String mEnterCommand; String mLeaveCommand; String mTickCommand; From 10d1aeca1fa65fbb5fdfe19f4cec2cf464aa1bf5 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sun, 27 Oct 2024 20:04:13 -0500 Subject: [PATCH 10/17] Shift tracking of dynamicObjects from strict child objects for Scenes, to tracking the objects but keeping them in the Cleanup Group to fix prefab loading behavior Shifted to utilizing SimGroupInterator and persistenceManager to fix saving issues with SubScenes Shifted to utilizing SimGroupIterator for Scene saving to standardize and minimize object misses Changed prefab load file logic to add loaded child simgroup to rootscene for consistent tracking and avoiding conflicts with subScene's hijacking the ImmediateGroup var Reduced duplication of buildfield callback for inspectors Added more standard field type lookups to inspector group when creating a field to avoid misses for types like 'F32' or similar. Folded the added-on MinSize/MaxSize/SimgroupSelect fields for SimGroup editing into a single compound field and fixed bugs with it's behavior so it works as expected now --- Engine/source/T3D/Scene.cpp | 45 +++++------ Engine/source/T3D/Scene.h | 8 +- Engine/source/T3D/SubScene.cpp | 31 +++---- Engine/source/T3D/prefab.cpp | 6 ++ Engine/source/gui/editor/inspector/group.cpp | 78 ++++++++++-------- .../editor/inspector/variableInspector.cpp | 77 +++++++++++------- .../tools/gui/fieldTypes/fieldTypes.tscript | 22 ++--- .../levels/DefaultEditorLevel.asset.taml | 1 + .../scripts/editors/worldEditor.ed.tscript | 81 +++++++++++++++++-- 9 files changed, 221 insertions(+), 128 deletions(-) diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index 0d7908fe34..28ad16ecc7 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/Engine/source/T3D/Scene.cpp @@ -148,20 +148,32 @@ void Scene::removeObject(SimObject* object) Parent::removeObject(object); } -void Scene::addDynamicObject(SceneObject* object) +void Scene::addDynamicObject(SimObject* object) { mDynamicObjects.push_back(object); + SimGroup* cleanupGroup; + if(Sim::findObject("MissionCleanup", cleanupGroup)) + { + cleanupGroup->addObject(object); + } + //Do it like regular, though we should probably bail if we're trying to add non-scene objects to the scene? - Parent::addObject(object); + //Parent::addObject(object); } -void Scene::removeDynamicObject(SceneObject* object) +void Scene::removeDynamicObject(SimObject* object) { mDynamicObjects.remove(object); + SimGroup* cleanupGroup; + if (Sim::findObject("MissionCleanup", cleanupGroup)) + { + cleanupGroup->removeObject(object); + } + //Do it like regular, though we should probably bail if we're trying to add non-scene objects to the scene? - Parent::removeObject(object); + //Parent::removeObject(object); } void Scene::interpolateTick(F32 delta) @@ -242,7 +254,7 @@ void Scene::dumpUtilizedAssets() Con::printf("Dumping utilized assets in scene!"); Vector utilizedAssetsList; - for (U32 i = 0; i < mPermanentObjects.size(); i++) + /*for (U32 i = 0; i < mPermanentObjects.size(); i++) { mPermanentObjects[i]->getUtilizedAssets(&utilizedAssetsList); } @@ -250,7 +262,7 @@ void Scene::dumpUtilizedAssets() for (U32 i = 0; i < mDynamicObjects.size(); i++) { mDynamicObjects[i]->getUtilizedAssets(&utilizedAssetsList); - } + }*/ for (U32 i = 0; i < utilizedAssetsList.size(); i++) { @@ -294,31 +306,16 @@ 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 (SimGroup::iterator itr = begin(); itr != end(); itr++) + for (SimGroupIterator itr(this); *itr; ++itr) { - SimGroup* sg = dynamic_cast(*itr); - if (sg) + if((*itr)->isMethod("onSaving")) { ConsoleValue vars[3]; vars[2].setString(fileName); - sg->callOnChildren("onSaving", 3, vars); - } - - SceneObject* sO = dynamic_cast(*itr); - if (sO) - { - sO->onSaving_callback(fileName); + Con::execute((*itr), 3, vars); } } - /*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++) diff --git a/Engine/source/T3D/Scene.h b/Engine/source/T3D/Scene.h index 8f48b80b12..5be94e189f 100644 --- a/Engine/source/T3D/Scene.h +++ b/Engine/source/T3D/Scene.h @@ -31,8 +31,8 @@ class Scene : public NetObject, public virtual ITickable Vector mSubScenes; - Vector mPermanentObjects; - Vector mDynamicObjects; + Vector mPermanentObjects; + Vector mDynamicObjects; S32 mSceneId; @@ -69,8 +69,8 @@ class Scene : public NetObject, public virtual ITickable void addObject(SimObject* object) override; void removeObject(SimObject* object) override; - void addDynamicObject(SceneObject* object); - void removeDynamicObject(SceneObject* object); + void addDynamicObject(SimObject* object); + void removeDynamicObject(SimObject* object); void clearDynamicObjects() { mDynamicObjects.clear(); } void dumpUtilizedAssets(); diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 03e3fbe69e..371131a377 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -1,6 +1,7 @@ #include "SubScene.h" #include "gameMode.h" +#include "console/persistenceManager.h" #include "console/script.h" #include "scene/sceneRenderState.h" #include "renderInstance/renderPassManager.h" @@ -391,34 +392,28 @@ bool SubScene::save() if (mStartUnloadTimerMS != -1) mStartUnloadTimerMS = Sim::getCurrentTime(); + PersistenceManager prMger; + StringTableEntry levelPath = mLevelAsset->getLevelPath(); - for (SimGroup::iterator itr = begin(); itr != end(); itr++) + FileStream fs; + fs.open(levelPath, Torque::FS::File::Write); + fs.close(); + + for (SimGroupIterator itr(this); *itr; ++itr) { - //Inform our objects we're saving, so if they do any special stuff - //they can do it before the actual write-out - SimGroup* sg = dynamic_cast(*itr); - if (sg) + if ((*itr)->isMethod("onSaving")) { ConsoleValue vars[3]; vars[2].setString(mLevelAssetId); - sg->callOnChildren("onSaving", 3, vars); + Con::execute((*itr), 3, vars); } - SceneObject* scO = dynamic_cast(*itr); - if (scO) - { - scO->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; - } + prMger.setDirty((*itr), levelPath); } + prMger.saveDirty(); + //process our gameModeList and write it out to the levelAsset for metadata stashing bool saveSuccess = false; diff --git a/Engine/source/T3D/prefab.cpp b/Engine/source/T3D/prefab.cpp index 1351fd92be..d1e828765d 100644 --- a/Engine/source/T3D/prefab.cpp +++ b/Engine/source/T3D/prefab.cpp @@ -371,6 +371,12 @@ void Prefab::_loadFile( bool addFileNotify ) return; } + SimObjectPtr rootScene = Scene::getRootScene(); + if(rootScene.isValid()) + { + rootScene->addDynamicObject(group); + } + if ( addFileNotify ) Torque::FS::AddChangeNotification( mFilename, this, &Prefab::_onFileChanged ); diff --git a/Engine/source/gui/editor/inspector/group.cpp b/Engine/source/gui/editor/inspector/group.cpp index 64ed2b352d..2f517d52d3 100644 --- a/Engine/source/gui/editor/inspector/group.cpp +++ b/Engine/source/gui/editor/inspector/group.cpp @@ -178,7 +178,7 @@ GuiInspectorField* GuiInspectorGroup::constructField( S32 fieldType ) // return our new datablock field with correct datablock type enumeration info return dbFieldClass; -} + } // Nope, not a datablock. So maybe it has a valid inspector field override we can use? if(!cbt->getInspectorFieldType()) @@ -641,40 +641,50 @@ void GuiInspectorGroup::addInspectorField(StringTableEntry name, StringTableEntr { S32 fieldType = -1; - if (typeName == StringTable->insert("int")) - fieldType = TypeS32; - else if (typeName == StringTable->insert("float")) - fieldType = TypeF32; - else if (typeName == StringTable->insert("vector")) - fieldType = TypePoint3F; - else if (typeName == StringTable->insert("vector2")) - fieldType = TypePoint2F; - else if (typeName == StringTable->insert("material")) - fieldType = TypeMaterialAssetId; - else if (typeName == StringTable->insert("image")) - fieldType = TypeImageAssetId; - else if (typeName == StringTable->insert("shape")) - fieldType = TypeShapeAssetId; - else if (typeName == StringTable->insert("sound")) - fieldType = TypeSoundAssetId; - else if (typeName == StringTable->insert("bool")) - fieldType = TypeBool; - else if (typeName == StringTable->insert("object")) - fieldType = TypeSimObjectPtr; - else if (typeName == StringTable->insert("string")) - fieldType = TypeString; - else if (typeName == StringTable->insert("colorI")) - fieldType = TypeColorI; - else if (typeName == StringTable->insert("colorF")) - fieldType = TypeColorF; - else if (typeName == StringTable->insert("ease")) - fieldType = TypeEaseF; - else if (typeName == StringTable->insert("command")) - fieldType = TypeCommand; - else if (typeName == StringTable->insert("filename")) - fieldType = TypeStringFilename; + String typeNameTyped = typeName; + if (!typeNameTyped.startsWith("Type")) + typeNameTyped = String("Type") + typeNameTyped; + + ConsoleBaseType* typeRef = AbstractClassRep::getTypeByName(typeNameTyped.c_str()); + if(typeRef) + { + fieldType = typeRef->getTypeID(); + } else - fieldType = -1; + { + if (typeName == StringTable->insert("int")) + fieldType = TypeS32; + else if (typeName == StringTable->insert("float")) + fieldType = TypeF32; + else if (typeName == StringTable->insert("vector")) + fieldType = TypePoint3F; + else if (typeName == StringTable->insert("vector2")) + fieldType = TypePoint2F; + else if (typeName == StringTable->insert("material")) + fieldType = TypeMaterialAssetId; + else if (typeName == StringTable->insert("image")) + fieldType = TypeImageAssetId; + else if (typeName == StringTable->insert("shape")) + fieldType = TypeShapeAssetId; + else if (typeName == StringTable->insert("sound")) + fieldType = TypeSoundAssetId; + else if (typeName == StringTable->insert("bool")) + fieldType = TypeBool; + else if (typeName == StringTable->insert("object")) + fieldType = TypeSimObjectPtr; + else if (typeName == StringTable->insert("string")) + fieldType = TypeString; + else if (typeName == StringTable->insert("colorI")) + fieldType = TypeColorI; + else if (typeName == StringTable->insert("colorF")) + fieldType = TypeColorF; + else if (typeName == StringTable->insert("ease")) + fieldType = TypeEaseF; + else if (typeName == StringTable->insert("command")) + fieldType = TypeCommand; + else if (typeName == StringTable->insert("filename")) + fieldType = TypeStringFilename; + } GuiInspectorField* fieldGui; diff --git a/Engine/source/gui/editor/inspector/variableInspector.cpp b/Engine/source/gui/editor/inspector/variableInspector.cpp index 6afcb46e53..c856c8b1a1 100644 --- a/Engine/source/gui/editor/inspector/variableInspector.cpp +++ b/Engine/source/gui/editor/inspector/variableInspector.cpp @@ -176,38 +176,53 @@ void GuiVariableInspector::addField(const char* name, const char* label, const c //find the field type S32 fieldTypeMask = -1; - if (newField->mFieldTypeName == StringTable->insert("int")) - fieldTypeMask = TypeS32; - else if (newField->mFieldTypeName == StringTable->insert("float")) - fieldTypeMask = TypeF32; - else if (newField->mFieldTypeName == StringTable->insert("vector")) - fieldTypeMask = TypePoint3F; - else if (newField->mFieldTypeName == StringTable->insert("vector2")) - fieldTypeMask = TypePoint2F; - else if (newField->mFieldTypeName == StringTable->insert("material")) - fieldTypeMask = TypeMaterialAssetId; - else if (newField->mFieldTypeName == StringTable->insert("image")) - fieldTypeMask = TypeImageAssetId; - else if (newField->mFieldTypeName == StringTable->insert("shape")) - fieldTypeMask = TypeShapeAssetId; - else if (newField->mFieldTypeName == StringTable->insert("bool")) - fieldTypeMask = TypeBool; - else if (newField->mFieldTypeName == StringTable->insert("object")) - fieldTypeMask = TypeSimObjectPtr; - else if (newField->mFieldTypeName == StringTable->insert("string")) - fieldTypeMask = TypeString; - else if (newField->mFieldTypeName == StringTable->insert("colorI")) - fieldTypeMask = TypeColorI; - else if (newField->mFieldTypeName == StringTable->insert("colorF")) - fieldTypeMask = TypeColorF; - else if (newField->mFieldTypeName == StringTable->insert("ease")) - fieldTypeMask = TypeEaseF; - else if (newField->mFieldTypeName == StringTable->insert("command")) - fieldTypeMask = TypeCommand; - else if (newField->mFieldTypeName == StringTable->insert("filename")) - fieldTypeMask = TypeStringFilename; + String typeNameTyped = typeName; + if (!typeNameTyped.startsWith("Type")) + typeNameTyped = String("Type") + typeNameTyped; + + ConsoleBaseType* typeRef = AbstractClassRep::getTypeByName(typeNameTyped.c_str()); + if (typeRef) + { + fieldTypeMask = typeRef->getTypeID(); + + if (!typeRef->getInspectorFieldType()) + fieldTypeMask = TypeString; + + newField->mFieldTypeName = StringTable->insert(typeRef->getTypeName()); + } else - fieldTypeMask = -1; + { + if (newField->mFieldTypeName == StringTable->insert("int")) + fieldTypeMask = TypeS32; + else if (newField->mFieldTypeName == StringTable->insert("float")) + fieldTypeMask = TypeF32; + else if (newField->mFieldTypeName == StringTable->insert("vector")) + fieldTypeMask = TypePoint3F; + else if (newField->mFieldTypeName == StringTable->insert("vector2")) + fieldTypeMask = TypePoint2F; + else if (newField->mFieldTypeName == StringTable->insert("material")) + fieldTypeMask = TypeMaterialAssetId; + else if (newField->mFieldTypeName == StringTable->insert("image")) + fieldTypeMask = TypeImageAssetId; + else if (newField->mFieldTypeName == StringTable->insert("shape")) + fieldTypeMask = TypeShapeAssetId; + else if (newField->mFieldTypeName == StringTable->insert("bool")) + fieldTypeMask = TypeBool; + else if (newField->mFieldTypeName == StringTable->insert("object")) + fieldTypeMask = TypeSimObjectPtr; + else if (newField->mFieldTypeName == StringTable->insert("string")) + fieldTypeMask = TypeString; + else if (newField->mFieldTypeName == StringTable->insert("colorI")) + fieldTypeMask = TypeColorI; + else if (newField->mFieldTypeName == StringTable->insert("colorF")) + fieldTypeMask = TypeColorF; + else if (newField->mFieldTypeName == StringTable->insert("ease")) + fieldTypeMask = TypeEaseF; + else if (newField->mFieldTypeName == StringTable->insert("command")) + fieldTypeMask = TypeCommand; + else if (newField->mFieldTypeName == StringTable->insert("filename")) + fieldTypeMask = TypeStringFilename; + } newField->mFieldType = fieldTypeMask; // diff --git a/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript b/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript index 1decb8b8b1..98836938bc 100644 --- a/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript +++ b/Templates/BaseGame/game/tools/gui/fieldTypes/fieldTypes.tscript @@ -1,20 +1,24 @@ function GuiVariableInspector::onInspectorFieldModified(%this, %targetObj, %fieldName, %index, %oldValue, %newValue) { - echo("FIELD CHANGED: " @ %fieldName @ " from " @ %oldValue @ " to " @ %newValue); + //echo("FIELD CHANGED: " @ %fieldName @ " from " @ %oldValue @ " to " @ %newValue); } function GuiInspectorVariableGroup::onConstructField(%this, %fieldName, %fieldLabel, %fieldTypeName, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) { - %inspector = %this.getParent(); - %makeCommand = %this @ ".build" @ %fieldTypeName @ "Field(\""@ %fieldName @ "\",\"" @ %fieldLabel @ "\",\"" @ %fieldDesc @ "\",\"" @ - %fieldDefaultVal @ "\",\"" @ %fieldDataVals @ "\",\"" @ %inspector @ "." @ %callbackName @ "\",\"" @ %ownerObj @"\");"; - eval(%makeCommand); + return GuiInspectorGroup::onConstructField(%this, %fieldName, %fieldLabel, %fieldTypeName, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj); } function GuiInspectorGroup::onConstructField(%this, %fieldName, %fieldLabel, %fieldTypeName, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callbackName, %ownerObj) { - %inspector = %this.getParent(); - %makeCommand = %this @ ".build" @ %fieldTypeName @ "Field(\""@ %fieldName @ "\",\"" @ %fieldLabel @ "\",\"" @ %fieldDesc @ "\",\"" @ - %fieldDefaultVal @ "\",\"" @ %fieldDataVals @ "\",\"" @ %inspector @ "." @ %callbackName @ "\",\"" @ %ownerObj @"\");"; - eval(%makeCommand); + if(%this.isMethod("build" @ %fieldTypeName @ "Field")) + { + %inspector = %this.getParent(); + %makeCommand = %this @ ".build" @ %fieldTypeName @ "Field(\""@ %fieldName @ "\",\"" @ %fieldLabel @ "\",\"" @ %fieldDesc @ "\",\"" @ + %fieldDefaultVal @ "\",\"" @ %fieldDataVals @ "\",\"" @ %inspector @ "." @ %callbackName @ "\",\"" @ %ownerObj @"\");"; + %ret = eval(%makeCommand); + + if(%ret == true || %ret $= "") + return true; + } + return false; } diff --git a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml index 9b3fe43b3b..6551949e3b 100644 --- a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml +++ b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.asset.taml @@ -2,6 +2,7 @@ AssetName="DefaultEditorLevel" LevelFile="@assetFile=DefaultEditorLevel.mis" LevelName="DefaultEditorLevel" + PostFXPresetFile="@assetFile=tools/levels/DefaultEditorLevel.postfxpreset.tscript" description="An empty room" previewImageAsset0="@asset=ToolsModule:DefaultEditorLevel_preview_image" previewImageAsset1="@asset=ToolsModule:DefaultEditorLevel_preview_image" diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript index 69678c833e..36388ef219 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/worldEditor.ed.tscript @@ -563,6 +563,7 @@ function simGroup::SelectFiteredObjects(%this, %min, %max) function SceneObject::filteredSelect(%this, %min, %max) { + echo("SceneObject::filteredSelect() - min: " @ %min @ " max: " @ %max); %box = %this.getWorldBox(); %xlength = mAbs(getWord(%box,0) - getWord(%box,3)); %ylength = mAbs(getWord(%box,1) - getWord(%box,4)); @@ -584,22 +585,27 @@ function simGroup::onInspect(%obj, %inspector) //it will route down through GuiInspectorGroup(the namespace of %group) and call onConstructField in an attemp to see if there's any //script defined functions that can build a field of that type. //We happen to define the required 'build @ @ Field()' function below, allowing us to build out the custom field type - %group.addField("minSize", "F32", "min diagonal size of objects"); - %group.addField("maxSize", "F32", "max diagonal size of objects"); - %group.addField("select", "SimGroupSelectionButton", "Select filtered objects"); + %group.addField("Select Objects", "SimGroupSelectionButton", "Select filtered objects"); } } function GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName, %fieldLabel, %fieldDesc, %fieldDefaultVal, %fieldDataVals, %callback, %ownerObj) { + + //Set defaults if needbe + if(%ownerObj.minSize $= "") + %ownerObj.minSize = 0.1; + if(%ownerObj.maxSize $= "") + %ownerObj.maxSize = 1; + %container = new GuiControl() { canSaveDynamicFields = "0"; Profile = "EditorContainerProfile"; HorizSizing = "right"; VertSizing = "bottom"; Position = "0 0"; - Extent = "300 18"; + Extent = "300 80"; MinExtent = "8 2"; canSave = "0"; Visible = "1"; @@ -607,24 +613,83 @@ function GuiInspectorGroup::buildSimGroupSelectionButtonField(%this, %fieldName, tooltip = "";// %tooltip; tooltipProfile = "EditorToolTipProfile"; + new GuiTextCtrl() { + profile = GuiInspectorFieldProfile; + text = %fieldLabel; + Position = "16 2"; + Extent = "300 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + }; + + new GuiControl() { + Position = "40 20"; + Extent = "270 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + + new GuiTextCtrl() { + profile = GuiInspectorFieldProfile; + text = "Minimum Size:"; + Position = "0 2"; + Extent = "150 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + }; + + new GuiTextEditCtrl() { + profile = GuiInspectorTextEditProfile; + variable = %ownerObj @ ".minSize"; + Position = "150 2"; + Extent = "150 18"; + HorizSizing = "left"; + VertSizing = "bottom"; + }; + }; + + new GuiControl() { + Position = "40 40"; + Extent = "270 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + + new GuiTextCtrl() { + profile = GuiInspectorFieldProfile; + text = "Maximum Size:"; + Position = "0 2"; + Extent = "150 18"; + HorizSizing = "right"; + VertSizing = "bottom"; + }; + + new GuiTextEditCtrl() { + profile = GuiInspectorTextEditProfile; + variable = %ownerObj @ ".maxSize"; + Position = "150 2"; + Extent = "150 18"; + HorizSizing = "left"; + VertSizing = "bottom"; + }; + }; + new GuiButtonCtrl() { canSaveDynamicFields = "0"; Profile = "ToolsGuiButtonProfile"; HorizSizing = "right"; VertSizing = "bottom"; - Position = "16 3"; - Extent = "300 18"; + Position = "40 60"; + Extent = "265 18"; MinExtent = "8 2"; canSave = "0"; Visible = "1"; hovertime = "100"; tooltip = ""; //%tooltip; tooltipProfile = "EditorToolTipProfile"; - text = %fieldName; + text = "Select"; maxLength = "1024"; command = %ownerObj @ ".SelectFiteredObjects("@ %ownerObj.minSize @","@ %ownerObj.maxSize @");"; }; }; - + %this-->stack.add(%container); } From e2d0cc19819635ea321b63c90bb1dbae90c9e8cf Mon Sep 17 00:00:00 2001 From: JeffR Date: Tue, 5 Nov 2024 20:14:36 -0600 Subject: [PATCH 11/17] Fixes issue where Regenerate Bounds button for SceneGroup/SubScenes wasn't displaying by moving it to Editing inspector group Added mode toggle for if changing the transform influences the child objects of a SubScene or not Added onSelected/onUnselected callbacks for SimObjects to allow contextual behavior in the editor Added functionality of programmatic/dynamic Tool Button Palettes Added logic so when selecting SubScenes the world editor palette has new buttons for letting the move/rotate actions influence the child objects --- Engine/source/T3D/SceneGroup.cpp | 2 +- Engine/source/T3D/SubScene.cpp | 30 ++++ Engine/source/T3D/SubScene.h | 7 + Engine/source/console/simObject.cpp | 12 ++ Engine/source/console/simObject.h | 3 + .../game/tools/levels/DefaultEditorLevel.mis | 38 ++--- .../worldEditor/gui/ToolsPaletteWindow.ed.gui | 2 +- .../game/tools/worldEditor/main.tscript | 3 + .../worldEditor/scripts/EditorGui.ed.tscript | 27 ++- .../worldEditor/scripts/buttonPalette.tscript | 160 ++++++++++++++++++ .../interfaces/subSceneEditing.tscript | 59 +++++++ 11 files changed, 318 insertions(+), 25 deletions(-) create mode 100644 Templates/BaseGame/game/tools/worldEditor/scripts/buttonPalette.tscript create mode 100644 Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp index 5dc0e6fecd..08195b976e 100644 --- a/Engine/source/T3D/SceneGroup.cpp +++ b/Engine/source/T3D/SceneGroup.cpp @@ -119,7 +119,7 @@ void SceneGroup::onInspect(GuiInspector* inspector) Parent::onInspect(inspector); //Put the SubScene group before everything that'd be SubScene-effecting, for orginazational purposes - GuiInspectorGroup* sceneGroupGrp = inspector->findExistentGroup(StringTable->insert("SceneGroup")); + GuiInspectorGroup* sceneGroupGrp = inspector->findExistentGroup(StringTable->insert("Editing")); if (!sceneGroupGrp) return; diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 371131a377..08e2941f00 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -10,6 +10,8 @@ #include "gui/editor/inspector/group.h" #include "T3D/gameBase/gameBase.h" +bool SubScene::smTransformChildren = false; + IMPLEMENT_CO_NETOBJECT_V1(SubScene); S32 SubScene::mUnloadTimeoutMs = 5000; @@ -86,6 +88,10 @@ void SubScene::consoleInit() Con::addVariable("$SubScene::UnloadTimeoutMS", TypeBool, &SubScene::mUnloadTimeoutMs, "The amount of time in milliseconds it takes for a SubScene to be unloaded if it's inactive.\n" "@ingroup Editors\n"); + + Con::addVariable("$SubScene::transformChildren", TypeBool, &SubScene::smTransformChildren, + "@brief If true, then transform manipulations modify child objects. If false, only triggering bounds is manipulated\n\n" + "@ingroup Editors"); } void SubScene::addObject(SimObject* object) @@ -163,6 +169,30 @@ void SubScene::inspectPostApply() setMaskBits(-1); } +void SubScene::setTransform(const MatrixF& mat) +{ + if(SubScene::smTransformChildren) + { + Parent::setTransform(mat); + } + else + { + SceneObject::setTransform(mat); + } +} + +void SubScene::setRenderTransform(const MatrixF& mat) +{ + if (SubScene::smTransformChildren) + { + Parent::setRenderTransform(mat); + } + else + { + SceneObject::setRenderTransform(mat); + } +} + bool SubScene::evaluateCondition() { if (!mLoadIf.isEmpty()) diff --git a/Engine/source/T3D/SubScene.h b/Engine/source/T3D/SubScene.h index e70ae812b5..4117e40c11 100644 --- a/Engine/source/T3D/SubScene.h +++ b/Engine/source/T3D/SubScene.h @@ -23,6 +23,9 @@ class SubScene : public SceneGroup void onLevelChanged() {} +protected: + static bool smTransformChildren; + private: DECLARE_LEVELASSET(SubScene, Level, onLevelChanged); @@ -47,6 +50,7 @@ class SubScene : public SceneGroup U32 mCurrTick; bool mGlobalLayer; + public: SubScene(); virtual ~SubScene(); @@ -71,6 +75,9 @@ class SubScene : public SceneGroup //void onEditorDisable() override; void inspectPostApply() override; + void setTransform(const MatrixF& mat) override; + void setRenderTransform(const MatrixF& mat) override; + bool testBox(const Box3F& testBox); bool evaluateCondition(); void _onSelected() override; diff --git a/Engine/source/console/simObject.cpp b/Engine/source/console/simObject.cpp index 7a9bb4404b..b1863d5bd1 100644 --- a/Engine/source/console/simObject.cpp +++ b/Engine/source/console/simObject.cpp @@ -101,6 +101,8 @@ SimObjectId SimObject::smForcedId = 0; bool SimObject::preventNameChanging = false; IMPLEMENT_CALLBACK(SimObject, onInspectPostApply, void, (SimObject* obj), (obj), "Generic callback for when an object is edited"); +IMPLEMENT_CALLBACK(SimObject, onSelected, void, (SimObject* obj), (obj), "Generic callback for when an object is selected"); +IMPLEMENT_CALLBACK(SimObject, onUnselected, void, (SimObject* obj), (obj), "Generic callback for when an object is un-selected"); namespace Sim { @@ -527,6 +529,14 @@ bool SimObject::save(const char *pcFileName, bool bOnlySelected, const char *pre } +bool SimObject::saveAppend(const char* pcFileName, bool bOnlySelected, const char* preappend) +{ + + + return true; + +} + //----------------------------------------------------------------------------- SimPersistID* SimObject::getOrCreatePersistentId() @@ -2207,11 +2217,13 @@ void SimObject::setSelected( bool sel ) { mFlags.set( Selected ); _onSelected(); + onSelected_callback(this); } else { mFlags.clear( Selected ); _onUnselected(); + onUnselected_callback(this); } } diff --git a/Engine/source/console/simObject.h b/Engine/source/console/simObject.h index 563f86e12d..77f6cd1482 100644 --- a/Engine/source/console/simObject.h +++ b/Engine/source/console/simObject.h @@ -579,6 +579,7 @@ class SimObject: public ConsoleObject, public TamlCallbacks /// Save object as a TorqueScript File. virtual bool save( const char* pcFilePath, bool bOnlySelected = false, const char *preappend = NULL ); + virtual bool saveAppend(const char* pcFilePath, bool bOnlySelected = false, const char* preappend = NULL); /// Check if a method exists in the objects current namespace. virtual bool isMethod( const char* methodName ); @@ -981,6 +982,8 @@ class SimObject: public ConsoleObject, public TamlCallbacks DECLARE_CONOBJECT( SimObject ); DECLARE_CALLBACK(void, onInspectPostApply, (SimObject* obj)); + DECLARE_CALLBACK(void, onSelected, (SimObject* obj)); + DECLARE_CALLBACK(void, onUnselected, (SimObject* obj)); static SimObject* __findObject( const char* id ) { return Sim::findObject( id ); } static const char* __getObjectId( ConsoleObject* object ) diff --git a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis index 466e48cc79..f54cb5693c 100644 --- a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis +++ b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis @@ -1,33 +1,20 @@ //--- OBJECT WRITE BEGIN --- new Scene(EditorTemplateLevel) { - canSave = "1"; - canSaveDynamicFields = "1"; - Enabled = "1"; + isEditing = "1"; + enabled = "1"; new LevelInfo(theLevelInfo) { - nearClip = "0.1"; - visibleDistance = "1000"; - visibleGhostDistance = "0"; - decalBias = "0.0015"; - fogColor = "0.6 0.6 0.7 1"; - fogDensity = "0"; + FogColor = "0.6 0.6 0.7 1"; fogDensityOffset = "700"; - fogAtmosphereHeight = "0"; canvasClearColor = "0 0 0 255"; - ambientLightBlendPhase = "1"; - ambientLightBlendCurve = "0 0 -1 -1"; soundAmbience = "AudioAmbienceDefault"; - soundDistanceModel = "Linear"; - canSave = "1"; - canSaveDynamicFields = "1"; - Enabled = "1"; + enabled = "1"; }; new ScatterSky(DynamicSky) { sunScale = "0.991102 0.921582 0.83077 1"; zOffset = "-3000"; azimuth = "25"; brightness = "5"; - flareType = "LightFlareExample1"; MoonMatAsset = "Core_Rendering:moon_wglow"; useNightCubemap = "1"; nightCubemap = "nightCubemap"; @@ -44,16 +31,29 @@ new Scene(EditorTemplateLevel) { persistentId = "289ad401-3140-11ed-aae8-c0cb519281fc"; reflectionPath = "tools/levels/DefaultEditorLevel/probes/"; }; - new GroundPlane() { scaleU = "32"; scaleV = "32"; MaterialAsset = "Prototyping:FloorGray"; - Enabled = "1"; + enabled = "1"; position = "0 0 0"; rotation = "1 0 0 0"; scale = "1 1 1"; }; + new SubScene() { + LevelAsset = "Prototyping:PrefabTestSubScene"; + position = "4.38205 -5.66842 1.53303"; + scale = "33.0705 24.1137 4.59909"; + }; + new Trigger() { + dataBlock = "DefaultTrigger"; + position = "0 0 -7.19786"; + scale = "15.3957 15.3957 15.3957"; + firstDataCheck = "1"; + }; + new Prefab() { + fileName = "data/Prototyping/prefabs/testPrefab.prefab"; + }; }; //--- OBJECT WRITE END --- diff --git a/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteWindow.ed.gui b/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteWindow.ed.gui index 79f5f2a295..b7732d6294 100644 --- a/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteWindow.ed.gui +++ b/Templates/BaseGame/game/tools/worldEditor/gui/ToolsPaletteWindow.ed.gui @@ -42,7 +42,7 @@ $guiContent = new GuiControl() { minSize = "50 50"; EdgeSnap = false; text = ""; - class = "EWToolsPaletteWindowClass"; + class = "ButtonPalette"; new GuiDynamicCtrlArrayControl(ToolsPaletteArray) { canSaveDynamicFields = "0"; diff --git a/Templates/BaseGame/game/tools/worldEditor/main.tscript b/Templates/BaseGame/game/tools/worldEditor/main.tscript index ef0dfdc050..c3a3e88add 100644 --- a/Templates/BaseGame/game/tools/worldEditor/main.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/main.tscript @@ -69,6 +69,7 @@ function initializeWorldEditor() exec("./scripts/probeBake.ed." @ $TorqueScriptFileExtension); exec("./scripts/visibility/visibilityLayer.ed." @ $TorqueScriptFileExtension); exec("./scripts/visibility/probeViz." @ $TorqueScriptFileExtension); + exec("./scripts/buttonPalette." @ $TorqueScriptFileExtension); exec("tools/gui/postFxEditor." @ $TorqueScriptFileExtension ); exec("tools/gui/renderTargetVisualizer.ed." @ $TorqueScriptFileExtension); @@ -77,6 +78,8 @@ function initializeWorldEditor() loadDirectory(expandFilename("./scripts/editors")); loadDirectory(expandFilename("./scripts/interfaces")); + exec("./scripts/interfaces/subSceneEditing.tscript"); + // Create the default editor plugins before calling buildMenus. new ScriptObject( WorldEditorPlugin ) diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript index f54b8b43d5..fecc479c6c 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/EditorGui.ed.tscript @@ -1069,11 +1069,13 @@ function WorldEditorInspectorPlugin::onWorldEditorStartup( %this ) //connect editor windows GuiWindowCtrl::attach( EWInspectorWindow, EWTreeWindow); - %map = new ActionMap(); + %map = new ActionMap(); + + /* %map.bindCmd( keyboard, "1", "EWorldEditorNoneModeBtn.performClick();", "" ); // Select %map.bindCmd( keyboard, "2", "EWorldEditorMoveModeBtn.performClick();", "" ); // Move %map.bindCmd( keyboard, "3", "EWorldEditorRotateModeBtn.performClick();", "" ); // Rotate - %map.bindCmd( keyboard, "4", "EWorldEditorScaleModeBtn.performClick();", "" ); // Scale + %map.bindCmd( keyboard, "4", "EWorldEditorScaleModeBtn.performClick();", "" ); // Scale*/ %map.bindCmd( keyboard, "f", "FitToSelectionBtn.performClick();", "" );// Fit Camera to Selection %map.bindCmd( keyboard, "z", "EditorGuiStatusBar.setCamera(\"Standard Camera\");", "" );// Free camera %map.bindCmd( keyboard, "n", "ToggleNodeBar->renderHandleBtn.performClick();", "" );// Render Node @@ -1093,21 +1095,38 @@ function WorldEditorInspectorPlugin::onWorldEditorStartup( %this ) function WorldEditorInspectorPlugin::onActivated( %this ) { Parent::onActivated( %this ); + + //Clears the button pallete stack + EWToolsPaletteWindow.setStackCtrl(ToolsPaletteArray); //legacy ctrl adhereance + EWToolsPaletteWindow.clearButtons(); + + EWToolsPaletteWindow.setActionMap(WorldEditorInspectorPlugin.map); + + //Adds a button to the pallete stack + //Name Icon Click Command Tooltip text Keybind + EWToolsPaletteWindow.addButton("Select", "ToolsModule:arrow_n_image", "EWorldEditorNoneModeBtn::onClick();", "", "Select Arrow", "1"); + EWToolsPaletteWindow.addButton("Move", "ToolsModule:translate_n_image", "EWorldEditorMoveModeBtn::onClick();", "", "Move Selection", "2"); + EWToolsPaletteWindow.addButton("Rotate", "ToolsModule:rotate_n_image", "EWorldEditorRotateModeBtn::onClick();", "", "Rotate Selection", "3"); + EWToolsPaletteWindow.addButton("Scale", "ToolsModule:Scale_n_image", "EWorldEditorScaleModeBtn::onClick();", "", "Scale Selection", "4"); + + EWToolsPaletteWindow.refresh(); EditorGui-->InspectorWindow.setVisible( true ); EditorGui-->TreeWindow.setVisible( true ); EditorGui-->WorldEditorToolbar.setVisible( true ); - %this.map.push(); + //%this.map.push(); } function WorldEditorInspectorPlugin::onDeactivated( %this ) { Parent::onDeactivated( %this ); + + EWToolsPaletteWindow.popActionMap(); EditorGui-->InspectorWindow.setVisible( false ); EditorGui-->TreeWindow.setVisible( false ); EditorGui-->WorldEditorToolbar.setVisible( false ); - %this.map.pop(); + //%this.map.pop(); } function WorldEditorInspectorPlugin::onEditMenuSelect( %this, %editMenu ) diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/buttonPalette.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/buttonPalette.tscript new file mode 100644 index 0000000000..f401f939d6 --- /dev/null +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/buttonPalette.tscript @@ -0,0 +1,160 @@ +function ButtonPalette::setStackCtrl(%this, %ctrl) +{ + %this.stackCtrl = %ctrl; +} + +function ButtonPalette::getStackCtrl(%this) +{ + if(isObject(%this.stackCtrl)) + return %this.stackCtrl; + else + return %this; +} + +function ButtonPalette::setActionMap(%this, %actionMap) +{ + %this.actionMap = %actionMap; + %this.actionMap.push(); +} + +function ButtonPalette::getActionMap(%this) +{ + return %this.actionMap; +} + +function ButtonPalette::popActionMap(%this, %actionMap) +{ + %this.actionMap.pop(); +} + +function ButtonPalette::clearButtons(%this) +{ + %stackCtrl = %this.getStackCtrl(); + %stackCtrl.clear(); + + for(%i=0; %i < %this.numButtons; %i++) + { + if(isObject(%this.actionMap)) + { + %this.actionMap.unbind("keyboard", getField(%this.buttons[%i], 5)); + } + + %this.buttons[%i] = ""; + } + + %this.numButtons = 0; +} + + //Name, Icon, Click Command, Variable, Tooltip text, Keybind +function ButtonPalette::addButton(%this, %name, %iconAsset, %command, %syncVariable, %toolTip, %keybind) +{ + if(%this.numButtons $= "") + %this.numButtons = 0; + + for(%i=0; %i < %this.numButtons; %i++) + { + %buttonInfo = %this.buttons[%i]; + + //echo("Testing button info: " @ getField(%buttonInfo, 0) @ " against new button name: " @ %name); + if(getField(%buttonInfo, 0) $= %name) //no duplicates + return; + } + + %this.buttons[%this.numButtons] = %name TAB %iconAsset TAB %command TAB %syncVariable TAB %toolTip TAB %keybind; + + %this.numButtons++; +} + +function ButtonPalette::removeButton(%this, %name) +{ + %found = false; + for(%i=0; %i < %this.numButtons; %i++) + { + if(getField(%this.buttons[%i], 0) $= %name) + { + %found = true; + + if(isObject(%this.actionMap)) + { + %this.actionMap.unbind("keyboard", getField(%this.buttons[%i], 5)); + } + } + + if(%found) + { + %this.buttons[%i] = %this.buttons[%i+1]; + } + } + + if(%found) + %this.numButtons--; + + return %found; +} + +function ButtonPalette::findButton(%this, %name) +{ + %stackCtrl = %this.getStackCtrl(); + for(%i=0; %i < %stackCtrl.getCount(); %i++) + { + %btnObj = %stackCtrl.getObject(%i); + + if(%btnObj.buttonName $= %name) + return %btnObj; + } + + return 0; +} + +function ButtonPalette::refresh(%this) +{ + %stackCtrl = %this.getStackCtrl(); + %stackCtrl.clear(); + + %this.visible = true; + %extents = "25 25"; + + if(%this.numButtons == 0) + { + %this.visible = false; + return; + } + + for(%i=0; %i < %this.numButtons; %i++) + { + %buttonInfo = %this.buttons[%i]; + + %paletteButton = new GuiBitmapButtonCtrl() { + canSaveDynamicFields = "0"; + internalName = getField(%buttonInfo, 0) @ "_paletteButton"; + Enabled = "1"; + isContainer = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + Position = "0 0"; + Extent = "25 19"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = getField(%buttonInfo, 4) SPC "(" @ getField(%buttonInfo, 5) @ ")"; + hovertime = "1000"; + bitmapAsset = getField(%buttonInfo, 1); + buttonType = "RadioButton"; + useMouseEvents = "0"; + buttonName = getField(%buttonInfo, 0); + command = getField(%buttonInfo, 2); + variable = getField(%buttonInfo, 3); + }; + + %extents.y += 23; + + if(isObject(%this.actionMap)) + %this.actionMap.bindCmd( keyboard, getField(%buttonInfo, 5), %paletteButton @ ".performClick();", "" ); + + %stackCtrl.add(%paletteButton); + } + + %this.extent.y = %extents.y; +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript new file mode 100644 index 0000000000..435f88b4d0 --- /dev/null +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript @@ -0,0 +1,59 @@ +function SubScene::onSelected(%this) +{ + echo("SELECTED SUBSCENE"); + + %moveButton = EWToolsPaletteWindow.findButton("Move"); + %rotateButton = EWToolsPaletteWindow.findButton("Rotate"); + + %moveButton.originalCommand = %moveButton.command; + %moveButton.command = "SubSceneMoveModeBtn::onClick();"; + + %rotateButton.originalCommand = %rotateButton.command; + %rotateButton.command = "SubSceneRotateModeBtn::onClick();"; + + EWToolsPaletteWindow.addButton("SubSceneMove", "ToolsModule:translate_n_image", "SubSceneChildMoveModeBtn::onClick();", "", "Move SubScene + Children", "1"); + EWToolsPaletteWindow.addButton("SubSceneRotate", "ToolsModule:rotate_n_image", "SubSceneChildRotateModeBtn::onClick();", "", "Rotate SubScene + Children", "2"); + + EWToolsPaletteWindow.refresh(); +} + +function SubScene::onUnselected(%this) +{ + echo("UN-SELECTED SUBSCENE"); + EWToolsPaletteWindow.removeButton("SubSceneMove"); + EWToolsPaletteWindow.removeButton("SubSceneRotate"); + + %moveButton = EWToolsPaletteWindow.findButton("Move"); + %rotateButton = EWToolsPaletteWindow.findButton("Rotate"); + + %moveButton.command = %moveButton.originalCommand; + %rotateButton.command = %rotateButton.originalCommand; + + $SubScene::transformChildren = false; + + EWToolsPaletteWindow.refresh(); +} + +function SubSceneMoveModeBtn::onClick(%this) +{ + EWorldEditorMoveModeBtn::onClick(); + $SubScene::transformChildren = false; +} + +function SubSceneRotateModeBtn::onClick(%this) +{ + EWorldEditorRotateModeBtn::onClick(); + $SubScene::transformChildren = false; +} + +function SubSceneChildMoveModeBtn::onClick() +{ + EWorldEditorMoveModeBtn::onClick(); + $SubScene::transformChildren = true; +} + +function SubSceneChildRotateModeBtn::onClick() +{ + EWorldEditorRotateModeBtn::onClick(); + $SubScene::transformChildren = true; +} \ No newline at end of file From 9ff2a56466cf4dda57c9cd18b88d5e2a7ffcc568 Mon Sep 17 00:00:00 2001 From: JeffR Date: Wed, 13 Nov 2024 17:10:27 -0600 Subject: [PATCH 12/17] Fixed issue of mis-transforming child objects so rotation would be weird when rotating subscenes Fixed issue of action buttons breaking with subscenes when going between child-object manip modes and not --- Engine/source/T3D/SceneGroup.cpp | 44 ++++++++++++------- .../interfaces/subSceneEditing.tscript | 37 +++++++--------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp index 08195b976e..127b5e069a 100644 --- a/Engine/source/T3D/SceneGroup.cpp +++ b/Engine/source/T3D/SceneGroup.cpp @@ -164,9 +164,8 @@ void SceneGroup::setTransform(const MatrixF& mat) Parent::setTransform(mat); // Calculate the delta transformation - MatrixF deltaTransform; - oldTransform.inverse(); - deltaTransform.mul(oldTransform, getTransform()); + MatrixF deltaTransform = mat; + deltaTransform.mul(oldTransform.inverse()); if (isServerObject()) { @@ -178,11 +177,19 @@ void SceneGroup::setTransform(const MatrixF& mat) SceneObject* child = dynamic_cast(*itr); if (child) { + // Get the child's current transform MatrixF childTransform = child->getTransform(); - MatrixF relativeTransform; - relativeTransform.mul(deltaTransform, childTransform); - child->setTransform(relativeTransform); - child->setScale(childTransform.getScale()); //we don't modify scale + + // Apply the delta transformation (ignoring scale) + MatrixF updatedTransform = childTransform; + updatedTransform.mul(deltaTransform); + + // Update the child's transform + child->setTransform(updatedTransform); + + PhysicsShape* childPS = dynamic_cast(child); + if (childPS) + childPS->storeRestorePos(); } } } @@ -196,9 +203,8 @@ void SceneGroup::setRenderTransform(const MatrixF& mat) Parent::setRenderTransform(mat); // Calculate the delta transformation - MatrixF deltaTransform; - oldTransform.inverse(); - deltaTransform.mul(oldTransform, getRenderTransform()); + MatrixF deltaTransform = mat; + deltaTransform.mul(oldTransform.inverse()); // Update all child transforms for (SimSetIterator itr(this); *itr; ++itr) @@ -206,11 +212,19 @@ void SceneGroup::setRenderTransform(const MatrixF& mat) 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 + // Get the child's current transform + MatrixF childTransform = child->getTransform(); + + // Apply the delta transformation (ignoring scale) + MatrixF updatedTransform = childTransform; + updatedTransform.mul(deltaTransform); + + // Update the child's transform + child->setTransform(updatedTransform); + + PhysicsShape* childPS = dynamic_cast(child); + if (childPS) + childPS->storeRestorePos(); } } } diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript index 435f88b4d0..70cf643bae 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/subSceneEditing.tscript @@ -1,33 +1,28 @@ function SubScene::onSelected(%this) { - echo("SELECTED SUBSCENE"); - - %moveButton = EWToolsPaletteWindow.findButton("Move"); - %rotateButton = EWToolsPaletteWindow.findButton("Rotate"); - - %moveButton.originalCommand = %moveButton.command; - %moveButton.command = "SubSceneMoveModeBtn::onClick();"; - - %rotateButton.originalCommand = %rotateButton.command; - %rotateButton.command = "SubSceneRotateModeBtn::onClick();"; + EWToolsPaletteWindow.clearButtons(); + + //Adds a button to the pallete stack + EWToolsPaletteWindow.addButton("Select", "ToolsModule:arrow_n_image", "EWorldEditorNoneModeBtn::onClick();", "", "Select Arrow", "1"); + EWToolsPaletteWindow.addButton("Move", "ToolsModule:translate_n_image", "SubSceneMoveModeBtn::onClick();", "", "Move Selection", "2"); + EWToolsPaletteWindow.addButton("Rotate", "ToolsModule:rotate_n_image", "SubSceneRotateModeBtn::onClick();", "", "Rotate Selection", "3"); + EWToolsPaletteWindow.addButton("Scale", "ToolsModule:Scale_n_image", "EWorldEditorScaleModeBtn::onClick();", "", "Scale Selection", "4"); - EWToolsPaletteWindow.addButton("SubSceneMove", "ToolsModule:translate_n_image", "SubSceneChildMoveModeBtn::onClick();", "", "Move SubScene + Children", "1"); - EWToolsPaletteWindow.addButton("SubSceneRotate", "ToolsModule:rotate_n_image", "SubSceneChildRotateModeBtn::onClick();", "", "Rotate SubScene + Children", "2"); + EWToolsPaletteWindow.addButton("SubSceneMove", "ToolsModule:translate_n_image", "SubSceneChildMoveModeBtn::onClick();", "", "Move SubScene + Children", "5"); + EWToolsPaletteWindow.addButton("SubSceneRotate", "ToolsModule:rotate_n_image", "SubSceneChildRotateModeBtn::onClick();", "", "Rotate SubScene + Children", "6"); EWToolsPaletteWindow.refresh(); } function SubScene::onUnselected(%this) { - echo("UN-SELECTED SUBSCENE"); - EWToolsPaletteWindow.removeButton("SubSceneMove"); - EWToolsPaletteWindow.removeButton("SubSceneRotate"); - - %moveButton = EWToolsPaletteWindow.findButton("Move"); - %rotateButton = EWToolsPaletteWindow.findButton("Rotate"); - - %moveButton.command = %moveButton.originalCommand; - %rotateButton.command = %rotateButton.originalCommand; + EWToolsPaletteWindow.clearButtons(); + + //Adds a button to the pallete stack + EWToolsPaletteWindow.addButton("Select", "ToolsModule:arrow_n_image", "EWorldEditorNoneModeBtn::onClick();", "", "Select Arrow", "1"); + EWToolsPaletteWindow.addButton("Move", "ToolsModule:translate_n_image", "EWorldEditorMoveModeBtn::onClick();", "", "Move Selection", "2"); + EWToolsPaletteWindow.addButton("Rotate", "ToolsModule:rotate_n_image", "EWorldEditorRotateModeBtn::onClick();", "", "Rotate Selection", "3"); + EWToolsPaletteWindow.addButton("Scale", "ToolsModule:Scale_n_image", "EWorldEditorScaleModeBtn::onClick();", "", "Scale Selection", "4"); $SubScene::transformChildren = false; From 2c8adfdf9300def720cb39251466d28ca0958ddb Mon Sep 17 00:00:00 2001 From: JeffR Date: Thu, 14 Nov 2024 19:29:41 -0600 Subject: [PATCH 13/17] Fixed child relative transforms for when SceneGroups are updated --- Engine/source/T3D/SceneGroup.cpp | 54 +++++++++++++------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp index 127b5e069a..c84456f9a9 100644 --- a/Engine/source/T3D/SceneGroup.cpp +++ b/Engine/source/T3D/SceneGroup.cpp @@ -158,15 +158,6 @@ void SceneGroup::onInspect(GuiInspector* inspector) void SceneGroup::setTransform(const MatrixF& mat) { - //transform difference - MatrixF oldTransform = getTransform(); - - Parent::setTransform(mat); - - // Calculate the delta transformation - MatrixF deltaTransform = mat; - deltaTransform.mul(oldTransform.inverse()); - if (isServerObject()) { setMaskBits(TransformMask); @@ -177,15 +168,17 @@ void SceneGroup::setTransform(const MatrixF& mat) SceneObject* child = dynamic_cast(*itr); if (child) { - // Get the child's current transform - MatrixF childTransform = child->getTransform(); + // Get the child's current world transform + MatrixF childWorldTrans = child->getTransform(); - // Apply the delta transformation (ignoring scale) - MatrixF updatedTransform = childTransform; - updatedTransform.mul(deltaTransform); + MatrixF childLocalTrans; + childLocalTrans = mWorldToObj.mul(childWorldTrans); - // Update the child's transform - child->setTransform(updatedTransform); + MatrixF updatedTrans; + updatedTrans.mul(mat, childLocalTrans); + + // Set the child's new world transform + child->setTransform(updatedTrans); PhysicsShape* childPS = dynamic_cast(child); if (childPS) @@ -193,40 +186,37 @@ void SceneGroup::setTransform(const MatrixF& mat) } } } + + Parent::setTransform(mat); } void SceneGroup::setRenderTransform(const MatrixF& mat) { - //transform difference - MatrixF oldTransform = getRenderTransform(); - - Parent::setRenderTransform(mat); - - // Calculate the delta transformation - MatrixF deltaTransform = mat; - deltaTransform.mul(oldTransform.inverse()); - // Update all child transforms for (SimSetIterator itr(this); *itr; ++itr) { SceneObject* child = dynamic_cast(*itr); if (child) { - // Get the child's current transform - MatrixF childTransform = child->getTransform(); + // Get the child's current world transform + MatrixF childWorldTrans = child->getRenderTransform(); - // Apply the delta transformation (ignoring scale) - MatrixF updatedTransform = childTransform; - updatedTransform.mul(deltaTransform); + MatrixF childLocalTrans; + childLocalTrans = mWorldToObj.mul(childWorldTrans); - // Update the child's transform - child->setTransform(updatedTransform); + MatrixF updatedTrans; + updatedTrans.mul(mat, childLocalTrans); + + // Set the child's new world transform + child->setRenderTransform(updatedTrans); PhysicsShape* childPS = dynamic_cast(child); if (childPS) childPS->storeRestorePos(); } } + + Parent::setRenderTransform(mat); } void SceneGroup::addObject(SimObject* object) From 75fb6683f539a814bfcd709749a40017eaadbb93 Mon Sep 17 00:00:00 2001 From: JeffR Date: Thu, 14 Nov 2024 20:23:23 -0600 Subject: [PATCH 14/17] Fixed up render transform handling for SceneGroups --- Engine/source/T3D/SceneGroup.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp index c84456f9a9..f4829cd005 100644 --- a/Engine/source/T3D/SceneGroup.cpp +++ b/Engine/source/T3D/SceneGroup.cpp @@ -192,31 +192,30 @@ void SceneGroup::setTransform(const MatrixF& mat) void SceneGroup::setRenderTransform(const MatrixF& mat) { + MatrixF newTransform = mat; + Parent::setRenderTransform(mat); + // Update all child transforms for (SimSetIterator itr(this); *itr; ++itr) { SceneObject* child = dynamic_cast(*itr); if (child) { - // Get the child's current world transform - MatrixF childWorldTrans = child->getRenderTransform(); - - MatrixF childLocalTrans; - childLocalTrans = mWorldToObj.mul(childWorldTrans); + MatrixF childOffset = child->getRenderTransform(); + childOffset.mulL(newTransform.inverse()); - MatrixF updatedTrans; - updatedTrans.mul(mat, childLocalTrans); + // Calculate the child's new transform + MatrixF newChildTransform = childOffset; + newChildTransform.mulL(newTransform); - // Set the child's new world transform - child->setRenderTransform(updatedTrans); + // Apply the new transform to the child + child->setRenderTransform(newChildTransform); PhysicsShape* childPS = dynamic_cast(child); if (childPS) childPS->storeRestorePos(); } } - - Parent::setRenderTransform(mat); } void SceneGroup::addObject(SimObject* object) From e56df9200233002331bfaaef1e0febd74fc4fbc0 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sun, 17 Nov 2024 14:53:04 -0600 Subject: [PATCH 15/17] Used the math from PathShape updating to standardize updating the transform of the child objects --- Engine/source/T3D/SceneGroup.cpp | 47 +++++++++++-------- .../game/tools/levels/DefaultEditorLevel.mis | 14 ------ 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/Engine/source/T3D/SceneGroup.cpp b/Engine/source/T3D/SceneGroup.cpp index f4829cd005..4e250e02a1 100644 --- a/Engine/source/T3D/SceneGroup.cpp +++ b/Engine/source/T3D/SceneGroup.cpp @@ -162,23 +162,26 @@ void SceneGroup::setTransform(const MatrixF& mat) { setMaskBits(TransformMask); + MatrixF newXform = mat; + MatrixF oldXform = getTransform(); + oldXform.affineInverse(); + + MatrixF offset; + offset.mul(newXform, oldXform); + // Update all child transforms for (SimSetIterator itr(this); *itr; ++itr) { SceneObject* child = dynamic_cast(*itr); if (child) { - // Get the child's current world transform - MatrixF childWorldTrans = child->getTransform(); - - MatrixF childLocalTrans; - childLocalTrans = mWorldToObj.mul(childWorldTrans); + MatrixF childMat; - MatrixF updatedTrans; - updatedTrans.mul(mat, childLocalTrans); - - // Set the child's new world transform - child->setTransform(updatedTrans); + //add the "offset" caused by the parents change, and add it to it's own + // This is needed by objects that update their own render transform thru interpolate tick + // Mostly for stationary objects. + childMat.mul(offset, child->getTransform()); + child->setTransform(childMat); PhysicsShape* childPS = dynamic_cast(child); if (childPS) @@ -192,8 +195,12 @@ void SceneGroup::setTransform(const MatrixF& mat) void SceneGroup::setRenderTransform(const MatrixF& mat) { - MatrixF newTransform = mat; - Parent::setRenderTransform(mat); + MatrixF newXform = mat; + MatrixF oldXform = getRenderTransform(); + oldXform.affineInverse(); + + MatrixF offset; + offset.mul(newXform, oldXform); // Update all child transforms for (SimSetIterator itr(this); *itr; ++itr) @@ -201,21 +208,21 @@ void SceneGroup::setRenderTransform(const MatrixF& mat) SceneObject* child = dynamic_cast(*itr); if (child) { - MatrixF childOffset = child->getRenderTransform(); - childOffset.mulL(newTransform.inverse()); - - // Calculate the child's new transform - MatrixF newChildTransform = childOffset; - newChildTransform.mulL(newTransform); + MatrixF childMat; - // Apply the new transform to the child - child->setRenderTransform(newChildTransform); + //add the "offset" caused by the parents change, and add it to it's own + // This is needed by objects that update their own render transform thru interpolate tick + // Mostly for stationary objects. + childMat.mul(offset, child->getRenderTransform()); + child->setRenderTransform(childMat); PhysicsShape* childPS = dynamic_cast(child); if (childPS) childPS->storeRestorePos(); } } + + Parent::setRenderTransform(mat); } void SceneGroup::addObject(SimObject* object) diff --git a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis index f54cb5693c..363a23a5a4 100644 --- a/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis +++ b/Templates/BaseGame/game/tools/levels/DefaultEditorLevel.mis @@ -40,20 +40,6 @@ new Scene(EditorTemplateLevel) { rotation = "1 0 0 0"; scale = "1 1 1"; }; - new SubScene() { - LevelAsset = "Prototyping:PrefabTestSubScene"; - position = "4.38205 -5.66842 1.53303"; - scale = "33.0705 24.1137 4.59909"; - }; - new Trigger() { - dataBlock = "DefaultTrigger"; - position = "0 0 -7.19786"; - scale = "15.3957 15.3957 15.3957"; - firstDataCheck = "1"; - }; - new Prefab() { - fileName = "data/Prototyping/prefabs/testPrefab.prefab"; - }; }; //--- OBJECT WRITE END --- From 61d9e82ce52ca7d01b2f7072f71230c7b4f08655 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sat, 7 Dec 2024 13:20:30 -0600 Subject: [PATCH 16/17] Adds FIELD_SpecialtyArrayField field type and handling for it in PersistenceManager, as well as a use-case of it for the surface field in ConvexShape --- Engine/source/T3D/Scene.cpp | 7 +- Engine/source/T3D/SubScene.cpp | 21 +- Engine/source/T3D/convexShape.cpp | 53 ++- Engine/source/T3D/convexShape.h | 111 ++--- Engine/source/console/consoleObject.h | 1 + Engine/source/console/persistenceManager.cpp | 412 ++++++++++++++----- Engine/source/console/persistenceManager.h | 8 +- Engine/source/console/simObject.h | 3 + Engine/source/scene/sceneObject.cpp | 2 +- 9 files changed, 442 insertions(+), 176 deletions(-) diff --git a/Engine/source/T3D/Scene.cpp b/Engine/source/T3D/Scene.cpp index 28ad16ecc7..7fb53273a8 100644 --- a/Engine/source/T3D/Scene.cpp +++ b/Engine/source/T3D/Scene.cpp @@ -296,6 +296,9 @@ StringTableEntry Scene::getLevelAsset() bool Scene::saveScene(StringTableEntry fileName) { + if (!isServerObject()) + return false; + //So, we ultimately want to not only save out the level, but also collate all the assets utilized //by the static objects in the scene so we can have those before we parse the level file itself //Useful for preloading or stat tracking @@ -310,9 +313,7 @@ bool Scene::saveScene(StringTableEntry fileName) { if((*itr)->isMethod("onSaving")) { - ConsoleValue vars[3]; - vars[2].setString(fileName); - Con::execute((*itr), 3, vars); + Con::executef((*itr), "onSaving", fileName); } } diff --git a/Engine/source/T3D/SubScene.cpp b/Engine/source/T3D/SubScene.cpp index 08e2941f00..673e1e33ee 100644 --- a/Engine/source/T3D/SubScene.cpp +++ b/Engine/source/T3D/SubScene.cpp @@ -411,6 +411,9 @@ void SubScene::unload() bool SubScene::save() { + if (!isServerObject()) + return false; + //if there's nothing TO save, don't bother if (size() == 0 || !isLoaded()) return false; @@ -432,14 +435,20 @@ bool SubScene::save() for (SimGroupIterator itr(this); *itr; ++itr) { - if ((*itr)->isMethod("onSaving")) + SimObject* childObj = (*itr); + + if (!prMger.isDirty(childObj)) { - ConsoleValue vars[3]; - vars[2].setString(mLevelAssetId); - Con::execute((*itr), 3, vars); - } + if ((*itr)->isMethod("onSaving")) + { + Con::executef((*itr), "onSaving", mLevelAssetId); + } - prMger.setDirty((*itr), levelPath); + if (childObj->getGroup() == this) + { + prMger.setDirty((*itr), levelPath); + } + } } prMger.saveDirty(); diff --git a/Engine/source/T3D/convexShape.cpp b/Engine/source/T3D/convexShape.cpp index c8341e8a81..0021ff37a8 100644 --- a/Engine/source/T3D/convexShape.cpp +++ b/Engine/source/T3D/convexShape.cpp @@ -317,10 +317,10 @@ void ConvexShape::initPersistFields() addGroup( "Internal" ); addProtectedField( "surface", TypeRealString, 0, &protectedSetSurface, &defaultProtectedGetFn, - "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); addProtectedField( "surfaceTexture", TypeRealString, 0, &protectedSetSurfaceTexture, &defaultProtectedGetFn, - "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -498,6 +498,55 @@ bool ConvexShape::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 ConvexShape::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("surface") || fieldName == StringTable->insert("surfaceTexture")) + { + return mSurfaces.size(); + } + + return 0; +} + +const char* ConvexShape::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (index >= smMaxSurfaces) + return NULL; + + if (fieldName == StringTable->insert("surface")) + { + if(index >= mSurfaces.size()) + return NULL; + + const MatrixF& mat = mSurfaces[index]; + + QuatF quat(mat); + Point3F pos(mat.getPosition()); + + char buffer[1024]; + dMemset(buffer, 0, 1024); + + dSprintf(buffer, 1024, "%g %g %g %g %g %g %g %i %g %g %g %g %g %i %i", + quat.x, quat.y, quat.z, quat.w, pos.x, pos.y, pos.z, mSurfaceUVs[index].matID, + mSurfaceUVs[index].offset.x, mSurfaceUVs[index].offset.y, mSurfaceUVs[index].scale.x, + mSurfaceUVs[index].scale.y, mSurfaceUVs[index].zRot, mSurfaceUVs[index].horzFlip, mSurfaceUVs[index].vertFlip); + + return StringTable->insert(buffer); + } + else if (fieldName == StringTable->insert("surfaceTexture")) + { + if (index >= mSurfaceTextures.size()) + return NULL; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + + dSprintf(buffer, 1024, "%s", mSurfaceTextures[index].getMaterial()); + + return StringTable->insert(buffer); + } +} + void ConvexShape::onScaleChanged() { if ( isProperlyAdded() ) diff --git a/Engine/source/T3D/convexShape.h b/Engine/source/T3D/convexShape.h index dfe0eaea44..67918b038d 100644 --- a/Engine/source/T3D/convexShape.h +++ b/Engine/source/T3D/convexShape.h @@ -83,7 +83,7 @@ class ConvexShape : public SceneObject typedef SceneObject Parent; friend class GuiConvexEditorCtrl; friend class GuiConvexEditorUndoAction; - friend class ConvexShapeCollisionConvex; + friend class ConvexShapeCollisionConvex; public: @@ -113,10 +113,10 @@ class ConvexShape : public SceneObject U32 p1; U32 p2; - U32 operator []( U32 index ) const + U32 operator [](U32 index) const { - AssertFatal( index >= 0 && index <= 2, "index out of range" ); - return *( (&p0) + index ); + AssertFatal(index >= 0 && index <= 2, "index out of range"); + return *((&p0) + index); } }; @@ -126,23 +126,23 @@ class ConvexShape : public SceneObject Vector< U32 > points; Vector< U32 > winding; Vector< Point2F > texcoords; - Vector< Triangle > triangles; + Vector< Triangle > triangles; Point3F tangent; Point3F normal; Point3F centroid; F32 area; S32 id; - }; + }; struct surfaceMaterial { // The name of the Material we will use for rendering DECLARE_MATERIALASSET(surfaceMaterial, Material); - + DECLARE_ASSET_SETGET(surfaceMaterial, Material); // The actual Material instance - BaseMatInstance* materialInst; + BaseMatInstance* materialInst; surfaceMaterial() { @@ -174,26 +174,26 @@ class ConvexShape : public SceneObject U32 mPrimCount; }; - struct Geometry - { - void generate(const Vector< PlaneF > &planes, const Vector< Point3F > &tangents, const Vector< surfaceMaterial > surfaceTextures, const Vector< Point2F > texOffset, const Vector< Point2F > texScale, const Vector< bool > horzFlip, const Vector< bool > vertFlip); + struct Geometry + { + void generate(const Vector< PlaneF >& planes, const Vector< Point3F >& tangents, const Vector< surfaceMaterial > surfaceTextures, const Vector< Point2F > texOffset, const Vector< Point2F > texScale, const Vector< bool > horzFlip, const Vector< bool > vertFlip); - Vector< Point3F > points; - Vector< Face > faces; - }; + Vector< Point3F > points; + Vector< Face > faces; + }; - static bool smRenderEdges; + static bool smRenderEdges; // To prevent bitpack overflows. // This is only indirectly enforced by trucation when serializing. static const S32 smMaxSurfaces = 100; public: - + ConvexShape(); virtual ~ConvexShape(); - DECLARE_CONOBJECT( ConvexShape ); + DECLARE_CONOBJECT(ConvexShape); DECLARE_CATEGORY("Object \t Simple"); // ConsoleObject @@ -203,73 +203,76 @@ class ConvexShape : public SceneObject void inspectPostApply() override; bool onAdd() override; void onRemove() override; - void writeFields(Stream &stream, U32 tabStop) override; - bool writeField( StringTableEntry fieldname, const char *value ) override; + void writeFields(Stream& stream, U32 tabStop) override; + bool writeField(StringTableEntry fieldname, const char* value) override; + + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; // NetObject - U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) override; - void unpackUpdate( NetConnection *conn, BitStream *stream ) override; + U32 packUpdate(NetConnection* conn, U32 mask, BitStream* stream) override; + void unpackUpdate(NetConnection* conn, BitStream* stream) override; // SceneObject void onScaleChanged() override; - void setTransform( const MatrixF &mat ) override; - void prepRenderImage( SceneRenderState *state ) override; - void buildConvex( const Box3F &box, Convex *convex ) override; - bool buildPolyList( PolyListContext context, AbstractPolyList *polyList, const Box3F &box, const SphereF &sphere ) override; - bool buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F &box, const SphereF &) override; - bool castRay( const Point3F &start, const Point3F &end, RayInfo *info ) override; - bool collideBox( const Point3F &start, const Point3F &end, RayInfo *info ) override; + void setTransform(const MatrixF& mat) override; + void prepRenderImage(SceneRenderState* state) override; + void buildConvex(const Box3F& box, Convex* convex) override; + bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) override; + bool buildExportPolyList(ColladaUtils::ExportData* exportData, const Box3F& box, const SphereF&) override; + bool castRay(const Point3F& start, const Point3F& end, RayInfo* info) override; + bool collideBox(const Point3F& start, const Point3F& end, RayInfo* info) override; - void updateBounds( bool recenter ); + void updateBounds(bool recenter); void recenter(); /// Geometry access. /// @{ - - MatrixF getSurfaceWorldMat( S32 faceid, bool scaled = false ) const; - void cullEmptyPlanes( Vector< U32 > *removedPlanes ); - void exportToCollada(); - void resizePlanes( const Point3F &size ); - void getSurfaceLineList( S32 surfId, Vector< Point3F > &lineList ); - Geometry& getGeometry() { return mGeometry; } - Vector& getSurfaces() { return mSurfaces; } - void getSurfaceTriangles( S32 surfId, Vector< Point3F > *outPoints, Vector< Point2F > *outCoords, bool worldSpace ); + + MatrixF getSurfaceWorldMat(S32 faceid, bool scaled = false) const; + void cullEmptyPlanes(Vector< U32 >* removedPlanes); + void exportToCollada(); + void resizePlanes(const Point3F& size); + void getSurfaceLineList(S32 surfId, Vector< Point3F >& lineList); + Geometry& getGeometry() { return mGeometry; } + Vector& getSurfaces() { return mSurfaces; } + void getSurfaceTriangles(S32 surfId, Vector< Point3F >* outPoints, Vector< Point2F >* outCoords, bool worldSpace); /// @} /// Geometry Visualization. /// @{ - void renderFaceEdges( S32 faceid, const ColorI &color = ColorI::WHITE, F32 lineWidth = 1.0f ); + void renderFaceEdges(S32 faceid, const ColorI& color = ColorI::WHITE, F32 lineWidth = 1.0f); /// @} - String getMaterialName() { return mMaterialName; } + String getMaterialName() { return mMaterialName; } protected: void _updateMaterial(); - void _updateGeometry( bool updateCollision = false ); + void _updateGeometry(bool updateCollision = false); void _updateCollision(); - void _export( OptimizedPolyList *plist, const Box3F &box, const SphereF &sphere ); + void _export(OptimizedPolyList* plist, const Box3F& box, const SphereF& sphere); - void _renderDebug( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *mat ); + void _renderDebug(ObjectRenderInst* ri, SceneRenderState* state, BaseMatInstance* mat); - static S32 QSORT_CALLBACK _comparePlaneDist( const void *a, const void *b ); + static S32 QSORT_CALLBACK _comparePlaneDist(const void* a, const void* b); - static bool protectedSetSurface( void *object, const char *index, const char *data ); + static bool protectedSetSurface(void* object, const char* index, const char* data); + + static bool protectedSetSurfaceTexture(void* object, const char* index, const char* data); + static bool protectedSetSurfaceUV(void* object, const char* index, const char* data); - static bool protectedSetSurfaceTexture( void *object, const char *index, const char *data ); - static bool protectedSetSurfaceUV(void *object, const char *index, const char *data); - protected: - + DECLARE_MATERIALASSET(ConvexShape, Material); DECLARE_ASSET_SETGET(ConvexShape, Material); // The actual Material instance - BaseMatInstance* mMaterialInst; + BaseMatInstance* mMaterialInst; // The GFX vertex and primitive buffers /*GFXVertexBufferHandle< VertexType > mVertexBuffer; @@ -278,7 +281,7 @@ class ConvexShape : public SceneObject U32 mVertCount; U32 mPrimCount;*/ - Geometry mGeometry; + Geometry mGeometry; Vector< PlaneF > mPlanes; @@ -291,14 +294,14 @@ class ConvexShape : public SceneObject Vector< surfaceUV > mSurfaceUVs; Vector< surfaceBuffers > mSurfaceBuffers; - Convex *mConvexList; + Convex* mConvexList; - PhysicsBody *mPhysicsRep; + PhysicsBody* mPhysicsRep; /// Geometry visualization /// @{ - F32 mNormalLength; + F32 mNormalLength; /// @} diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index 634b602254..8c6bfdec28 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -498,6 +498,7 @@ class AbstractClassRep : public ConsoleBaseType FIELD_ComponentInspectors = BIT(1), ///< Custom fields used by components. They are likely to be non-standard size/configuration, so ///< They are handled specially FIELD_CustomInspectors = BIT(2), ///< Display as a button in inspectors. + FIELD_SpecialtyArrayField = BIT(3) }; struct Field diff --git a/Engine/source/console/persistenceManager.cpp b/Engine/source/console/persistenceManager.cpp index 630dad448e..0cb6671c4a 100644 --- a/Engine/source/console/persistenceManager.cpp +++ b/Engine/source/console/persistenceManager.cpp @@ -652,6 +652,31 @@ S32 PersistenceManager::getPropertyIndex(ParsedObject* parsedObject, const char* return propertyIndex; } +S32 PersistenceManager::getSpecialPropertyAtOffset(ParsedObject* parsedObject, const char* fieldName, U32 offsetPos) +{ + S32 propertyIndex = -1; + + if (!parsedObject) + return propertyIndex; + + U32 hitCount = -1; + for (U32 i = 0; i < parsedObject->properties.size(); i++) + { + if (dStricmp(fieldName, parsedObject->properties[i].name) == 0) + { + hitCount++; + + if (hitCount == offsetPos) + { + propertyIndex = i; + break; + } + } + } + + return propertyIndex; +} + char* PersistenceManager::getObjectIndent(ParsedObject* object) { char* indent = Con::getReturnBuffer(2048); @@ -1361,166 +1386,335 @@ void PersistenceManager::updateObject(SimObject* object, ParsedObject* parentObj if ( f->type >= AbstractClassRep::ARCFirstCustomField || f->flag.test(AbstractClassRep::FieldFlags::FIELD_ComponentInspectors)) continue; - for(U32 j = 0; S32(j) < f->elementCount; j++) + if (f->flag.test(AbstractClassRep::FIELD_SpecialtyArrayField)) { - const char* value = getFieldValue(object, f->pFieldname, j); + U32 fieldArraySize = object->getSpecialFieldSize(f->pFieldname); - // Make sure we got a value - if (!value) - continue; + for(U32 j = 0; j < fieldArraySize; j++) + { + const char* value = object->getSpecialFieldOut(f->pFieldname, j); - // Let's see if this field is already in the file - S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); + // Make sure we got a value + if (!value) + continue; - if (propertyIndex > -1) - { - ParsedProperty& prop = parsedObject->properties[propertyIndex]; + // Let's see if this field is already in the file + S32 propertyIndex = getSpecialPropertyAtOffset(parsedObject, f->pFieldname, j); - // If this field is on the remove list then remove it and continue - if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) + if (propertyIndex > -1) { - removeField( parsedObject->properties[ propertyIndex ] ); - dFree( value ); - continue; - } + ParsedProperty& prop = parsedObject->properties[propertyIndex]; - // Run the parsed value through the console system conditioners so - // that it will better match the data we got back from the object. - const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); - - // If our data doesn't match then we get to update it. - // - // As for copy-sources, we just assume here that if a property setting - // is there in the file, the user does not want it inherited from the copy-source - // even in the case the actual values are identical. - - if( dStricmp(value, evalue) != 0 ) + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, f->pFieldname, j)) + { + removeField(parsedObject->properties[propertyIndex]); + dFree(value); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + + // If our data doesn't match then we get to update it. + // + // As for copy-sources, we just assume here that if a property setting + // is there in the file, the user does not want it inherited from the copy-source + // even in the case the actual values are identical. + + if (dStricmp(value, evalue) != 0) + { + if (value[0] == '\0' && + dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value) == 0 && + (!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value) == 0)) + { + removeField(prop); + } + else + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + } + else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + } + else + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + } + } + } + else { - if( value[ 0 ] == '\0' && - dStricmp( getFieldValue( defaultObject, f->pFieldname, j ), value ) == 0 && - ( !object->getCopySource() || dStricmp( getFieldValue( object->getCopySource(), f->pFieldname, j ), value ) == 0 ) ) + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, f->pFieldname, j)) + { + dFree(value); + continue; + } + + bool mustUpdate = false; + + // If we didn't find the property in the ParsedObject + // then we need to compare against the default value + // for this property and save it out if it is different + + const char* defaultValue = defaultObject->getSpecialFieldOut(f->pFieldname, j); + if (!defaultValue || dStricmp(value, defaultValue) != 0) { - removeField( prop ); + // Value differs. Check whether it also differs from the + // value in the copy source if there is one. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (!copySourceValue || dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + else + mustUpdate = true; } else + { + // Value does not differ. If it differs from the copy source's value, + // though, we still want to write it out as otherwise we'll see the + // copy source's value override us. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (copySourceValue && dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } + } + + // The default value for most string type fields is + // NULL so we can't just continue here or we'd never ever + // write them out... + // + //if (!defaultValue) + // continue; + + // If the object's value is different from the default + // value then add it to the ParsedObject's newLines + if (mustUpdate) { // TODO: This should be wrapped in a helper method... probably. // Detect and collapse relative path information - if (f->type == TypeFilename || - f->type == TypeStringFilename || - f->type == TypeImageFilename || - f->type == TypePrefabFilename || - f->type == TypeShapeFilename || - f->type == TypeSoundFilename ) + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) { char fnBuf[1024]; Con::collapseScriptFilename(fnBuf, 1024, value); - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); } - else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + else if (f->type == TypeCommand) { char cmdBuf[1024]; expandEscape(cmdBuf, value); - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); } else - updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); } + + if (defaultValue) + dFree(defaultValue); } + + //dFree(value); } - else + } + else + { + for (U32 j = 0; S32(j) < f->elementCount; j++) { - // No need to process a removed field that doesn't exist in the file - if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) - { - dFree( value ); + const char* value = getFieldValue(object, f->pFieldname, j); + + // Make sure we got a value + if (!value) continue; - } - - bool mustUpdate = false; - // If we didn't find the property in the ParsedObject - // then we need to compare against the default value - // for this property and save it out if it is different + // Let's see if this field is already in the file + S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); - const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); - if( !defaultValue || dStricmp( value, defaultValue ) != 0 ) + if (propertyIndex > -1) { - // Value differs. Check whether it also differs from the - // value in the copy source if there is one. - - if( object->getCopySource() ) + ParsedProperty& prop = parsedObject->properties[propertyIndex]; + + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) { - const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); - if( !copySourceValue || dStricmp( copySourceValue, value ) != 0 ) - mustUpdate = true; - - if( copySourceValue ) - dFree( copySourceValue ); + removeField(parsedObject->properties[propertyIndex]); + dFree(value); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + + // If our data doesn't match then we get to update it. + // + // As for copy-sources, we just assume here that if a property setting + // is there in the file, the user does not want it inherited from the copy-source + // even in the case the actual values are identical. + + if (dStricmp(value, evalue) != 0) + { + if (value[0] == '\0' && + dStricmp(getFieldValue(defaultObject, f->pFieldname, j), value) == 0 && + (!object->getCopySource() || dStricmp(getFieldValue(object->getCopySource(), f->pFieldname, j), value) == 0)) + { + removeField(prop); + } + else + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, fnBuf, true); + } + else if (f->type == TypeCommand || f->type == TypeString || f->type == TypeRealString) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, cmdBuf, true); + } + else + updateToken(prop.valueLine, prop.valuePosition, prop.endPosition - prop.valuePosition, value, true); + } } - else - mustUpdate = true; } else { - // Value does not differ. If it differs from the copy source's value, - // though, we still want to write it out as otherwise we'll see the - // copy source's value override us. - - if( object->getCopySource() ) + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, f->pFieldname, j) || !object->writeField(f->pFieldname, value)) { - const char* copySourceValue = getFieldValue( object->getCopySource(), f->pFieldname, j ); - if( copySourceValue && dStricmp( copySourceValue, value ) != 0 ) - mustUpdate = true; - - if( copySourceValue ) - dFree( copySourceValue ); + dFree(value); + continue; } - } - // The default value for most string type fields is - // NULL so we can't just continue here or we'd never ever - // write them out... - // - //if (!defaultValue) - // continue; + bool mustUpdate = false; - // If the object's value is different from the default - // value then add it to the ParsedObject's newLines - if ( mustUpdate ) - { - // TODO: This should be wrapped in a helper method... probably. - // Detect and collapse relative path information - if (f->type == TypeFilename || - f->type == TypeStringFilename || - f->type == TypeImageFilename || - f->type == TypePrefabFilename || - f->type == TypeShapeFilename || - f->type == TypeSoundFilename ) + // If we didn't find the property in the ParsedObject + // then we need to compare against the default value + // for this property and save it out if it is different + + const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); + if (!defaultValue || dStricmp(value, defaultValue) != 0) { - char fnBuf[1024]; - Con::collapseScriptFilename(fnBuf, 1024, value); + // Value differs. Check whether it also differs from the + // value in the copy source if there is one. + + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (!copySourceValue || dStricmp(copySourceValue, value) != 0) + mustUpdate = true; - newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); + if (copySourceValue) + dFree(copySourceValue); + } + else + mustUpdate = true; } - else if (f->type == TypeCommand) + else { - char cmdBuf[1024]; - expandEscape(cmdBuf, value); + // Value does not differ. If it differs from the copy source's value, + // though, we still want to write it out as otherwise we'll see the + // copy source's value override us. - newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); + if (object->getCopySource()) + { + const char* copySourceValue = getFieldValue(object->getCopySource(), f->pFieldname, j); + if (copySourceValue && dStricmp(copySourceValue, value) != 0) + mustUpdate = true; + + if (copySourceValue) + dFree(copySourceValue); + } } - else - newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); + + // The default value for most string type fields is + // NULL so we can't just continue here or we'd never ever + // write them out... + // + //if (!defaultValue) + // continue; + + // If the object's value is different from the default + // value then add it to the ParsedObject's newLines + if (mustUpdate) + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename || + f->type == TypePrefabFilename || + f->type == TypeShapeFilename || + f->type == TypeSoundFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); + } + else if (f->type == TypeCommand) + { + char cmdBuf[1024]; + expandEscape(cmdBuf, value); + + newLines.push_back(createNewProperty(f->pFieldname, cmdBuf, f->elementCount > 1, j)); + } + else + newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); + } + + if (defaultValue) + dFree(defaultValue); } - if (defaultValue) - dFree( defaultValue ); + dFree(value); } - - dFree( value ); } } diff --git a/Engine/source/console/persistenceManager.h b/Engine/source/console/persistenceManager.h index bba565da20..de8608ebb9 100644 --- a/Engine/source/console/persistenceManager.h +++ b/Engine/source/console/persistenceManager.h @@ -221,6 +221,12 @@ class PersistenceManager : public SimObject // Attempts to look up the property in the ParsedObject S32 getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos = 0); + // Attempts to look up the special array property in the ParsedObject + // This is distinct from getPropertyIndex because while that assumes it's an array'd field + // This figures the property in question is one that is specially tagged for implicit, arbitrarily sized lists + // Like ConvexShape's 'surfaces' or a spline's 'node' properties + S32 getSpecialPropertyAtOffset(ParsedObject* parsedObject, const char* fieldName, U32 offsetPos); + // Gets the amount of indent on the ParsedObject. char* getObjectIndent(ParsedObject* object); @@ -320,4 +326,4 @@ class PersistenceManager : public SimObject DECLARE_CONOBJECT(PersistenceManager); }; -#endif \ No newline at end of file +#endif diff --git a/Engine/source/console/simObject.h b/Engine/source/console/simObject.h index 77f6cd1482..4868a45853 100644 --- a/Engine/source/console/simObject.h +++ b/Engine/source/console/simObject.h @@ -530,6 +530,9 @@ class SimObject: public ConsoleObject, public TamlCallbacks void setDataFieldType(const U32 fieldTypeId, StringTableEntry slotName, const char *array); void setDataFieldType(const char *typeName, StringTableEntry slotName, const char *array); + virtual U32 getSpecialFieldSize(StringTableEntry fieldName) { return 0; } + virtual const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) { return NULL; } + /// Get reference to the dictionary containing dynamic fields. /// /// See @ref simobject_console "here" for a detailed discussion of what this diff --git a/Engine/source/scene/sceneObject.cpp b/Engine/source/scene/sceneObject.cpp index cc9f670059..c44746c885 100644 --- a/Engine/source/scene/sceneObject.cpp +++ b/Engine/source/scene/sceneObject.cpp @@ -1724,7 +1724,7 @@ void SceneObject::updateRenderChangesByParent(){ MatrixF offset; offset.mul(renderXform, xform); - MatrixF mat; + MatrixF mat; //add the "offset" caused by the parents change, and add it to it's own // This is needed by objects that update their own render transform thru interpolate tick From bf9692a451bcaebb00a3e993be9a32bb36627e84 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sun, 15 Dec 2024 23:48:16 -0600 Subject: [PATCH 17/17] Updates DecalRoad, MeshRoad and River to be able to write out via persistManager using specialityField functions, similar to ConvexShape Fixes behavior with gamemode selection in ChooseLevelMenu so if there is only one gamemode, it is auto-selected and advances to the level selection Update ExampleLevel in ExampleModule to have updated gamemodes field name --- Engine/source/T3D/convexShape.cpp | 2 + Engine/source/environment/decalRoad.cpp | 32 ++++++- Engine/source/environment/decalRoad.h | 3 + Engine/source/environment/meshRoad.cpp | 58 +++++++++++- Engine/source/environment/meshRoad.h | 3 + Engine/source/environment/river.cpp | 35 ++++++- Engine/source/environment/river.h | 3 + .../ExampleModule/levels/ExampleLevel.mis | 2 +- .../game/data/UI/guis/ChooseLevelMenu.gui | 4 +- .../game/data/UI/guis/ChooseLevelMenu.tscript | 92 ++++++++++++------- 10 files changed, 192 insertions(+), 42 deletions(-) diff --git a/Engine/source/T3D/convexShape.cpp b/Engine/source/T3D/convexShape.cpp index 0021ff37a8..f5c7732938 100644 --- a/Engine/source/T3D/convexShape.cpp +++ b/Engine/source/T3D/convexShape.cpp @@ -545,6 +545,8 @@ const char* ConvexShape::getSpecialFieldOut(StringTableEntry fieldName, const U3 return StringTable->insert(buffer); } + + return NULL; } void ConvexShape::onScaleChanged() diff --git a/Engine/source/environment/decalRoad.cpp b/Engine/source/environment/decalRoad.cpp index b5ba0c8d4d..97003e80e9 100644 --- a/Engine/source/environment/decalRoad.cpp +++ b/Engine/source/environment/decalRoad.cpp @@ -322,7 +322,7 @@ void DecalRoad::initPersistFields() addGroup( "Internal" ); addProtectedField( "node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, - "Do not modify, for internal use." ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -473,6 +473,36 @@ bool DecalRoad::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 DecalRoad::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("node")) + { + return mNodes.size(); + } + + return 0; +} + +const char* DecalRoad::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (fieldName == StringTable->insert("node")) + { + if (index >= mNodes.size()) + return NULL; + + const RoadNode& node = mNodes[index]; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "%f %f %f %f", node.point.x, node.point.y, node.point.z, node.width); + + return StringTable->insert(buffer); + } + + return NULL; +} + + void DecalRoad::onEditorEnable() { } diff --git a/Engine/source/environment/decalRoad.h b/Engine/source/environment/decalRoad.h index f1aecf187e..481e5b92ae 100644 --- a/Engine/source/environment/decalRoad.h +++ b/Engine/source/environment/decalRoad.h @@ -169,6 +169,9 @@ class DecalRoad : public SceneObject void onStaticModified(const char* slotName, const char*newValue = NULL) override; void writeFields(Stream &stream, U32 tabStop) override; bool writeField( StringTableEntry fieldname, const char *value ) override; + + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; // NetObject U32 packUpdate(NetConnection *, U32, BitStream *) override; diff --git a/Engine/source/environment/meshRoad.cpp b/Engine/source/environment/meshRoad.cpp index 93593df7a3..6785db0657 100644 --- a/Engine/source/environment/meshRoad.cpp +++ b/Engine/source/environment/meshRoad.cpp @@ -956,10 +956,10 @@ void MeshRoad::initPersistFields() addGroup( "Internal" ); addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, - "Do not modify, for internal use." ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); addProtectedField( "ProfileNode", TypeString, 0, &addProfileNodeFromField, &emptyStringProtectedGetFn, - "Do not modify, for internal use." ); + "Do not modify, for internal use.", AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -1168,6 +1168,60 @@ bool MeshRoad::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 MeshRoad::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("Node")) + { + return mNodes.size(); + } + else if (fieldName == StringTable->insert("ProfileNode")) + { + return mSideProfile.mNodes.size(); + } + + return 0; +} + +const char* MeshRoad::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (fieldName == StringTable->insert("Node")) + { + if (index >= mNodes.size()) + return NULL; + + const MeshRoadNode& node = mNodes[index]; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "Node = \"%g %g %g %g %g %g %g %g\";", node.point.x, node.point.y, node.point.z, node.width, node.depth, node.normal.x, node.normal.y, node.normal.z); + + return StringTable->insert(buffer); + } + else if (fieldName == StringTable->insert("ProfileNode")) + { + Point3F nodePos = mSideProfile.mNodes[index].getPosition(); + U8 smooth, mtrl; + + if (index) + mtrl = mSideProfile.mSegMtrls[index - 1]; + else + mtrl = 0; + + if (mSideProfile.mNodes[index].isSmooth()) + smooth = 1; + else + smooth = 0; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "ProfileNode = \"%.6f %.6f %d %d\";", nodePos.x, nodePos.y, smooth, mtrl); + + return StringTable->insert(buffer); + } + + return NULL; +} + void MeshRoad::onEditorEnable() { } diff --git a/Engine/source/environment/meshRoad.h b/Engine/source/environment/meshRoad.h index 17747e83b9..437d76a6d3 100644 --- a/Engine/source/environment/meshRoad.h +++ b/Engine/source/environment/meshRoad.h @@ -525,6 +525,9 @@ class MeshRoad : public SceneObject void writeFields(Stream &stream, U32 tabStop) override; bool writeField( StringTableEntry fieldname, const char *value ) override; + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; + // NetObject U32 packUpdate(NetConnection *, U32, BitStream *) override; void unpackUpdate(NetConnection *, BitStream *) override; diff --git a/Engine/source/environment/river.cpp b/Engine/source/environment/river.cpp index 725f4a3505..98088f7373 100644 --- a/Engine/source/environment/river.cpp +++ b/Engine/source/environment/river.cpp @@ -648,7 +648,8 @@ void River::initPersistFields() addGroup( "Internal" ); - addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, "For internal use, do not modify." ); + addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, "For internal use, do not modify.", + AbstractClassRep::FIELD_HideInInspectors | AbstractClassRep::FIELD_SpecialtyArrayField); endGroup( "Internal" ); @@ -785,6 +786,38 @@ bool River::writeField( StringTableEntry fieldname, const char *value ) return Parent::writeField( fieldname, value ); } +U32 River::getSpecialFieldSize(StringTableEntry fieldName) +{ + if (fieldName == StringTable->insert("node")) + { + return mNodes.size(); + } + + return 0; +} + +const char* River::getSpecialFieldOut(StringTableEntry fieldName, const U32& index) +{ + if (fieldName == StringTable->insert("node")) + { + if (index >= mNodes.size()) + return NULL; + + const RiverNode& node = mNodes[index]; + + char buffer[1024]; + dMemset(buffer, 0, 1024); + dSprintf(buffer, 1024, "Node = \"%f %f %f %f %f %f %f %f\";", node.point.x, node.point.y, node.point.z, + node.width, + node.depth, + node.normal.x, node.normal.y, node.normal.z); + + return StringTable->insert(buffer); + } + + return NULL; +} + void River::innerRender( SceneRenderState *state ) { GFXDEBUGEVENT_SCOPE( River_innerRender, ColorI( 255, 0, 0 ) ); diff --git a/Engine/source/environment/river.h b/Engine/source/environment/river.h index ded0e7e372..5676b75052 100644 --- a/Engine/source/environment/river.h +++ b/Engine/source/environment/river.h @@ -394,6 +394,9 @@ class River : public WaterObject void writeFields(Stream &stream, U32 tabStop) override; bool writeField( StringTableEntry fieldname, const char *value ) override; + U32 getSpecialFieldSize(StringTableEntry fieldName) override; + const char* getSpecialFieldOut(StringTableEntry fieldName, const U32& index) override; + // NetObject U32 packUpdate(NetConnection *, U32, BitStream *) override; void unpackUpdate(NetConnection *, BitStream *) override; diff --git a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis index 1a27a65765..40bffd5266 100644 --- a/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis +++ b/Templates/BaseGame/game/data/ExampleModule/levels/ExampleLevel.mis @@ -1,7 +1,7 @@ //--- OBJECT WRITE BEGIN --- new Scene(ExampleLevel) { Enabled = "1"; - gameModeName="ExampleGameMode"; + gameModes="ExampleGameMode"; new LevelInfo(theLevelInfo) { fogColor = "0.6 0.6 0.7 1"; diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui index 8fb0928e2c..a1fcf3a462 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.gui @@ -185,7 +185,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { }; }; new GuiBitmapCtrl(LevelPreviewBitmap) { - BitmapAsset = "UI:no_preview_image"; + BitmapAsset = ""; position = "448 0"; extent = "440 440"; horizSizing = "left"; @@ -193,7 +193,7 @@ $guiContent = new GuiControl(ChooseLevelMenu) { tooltipProfile = "GuiToolTipProfile"; }; new GuiTextCtrl(LevelNameText) { - text = "Example Level"; + text = ""; position = "448 445"; extent = "440 20"; horizSizing = "left"; diff --git a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript index f25ddff520..ff9599e647 100644 --- a/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/ChooseLevelMenu.tscript @@ -54,7 +54,29 @@ function ChooseLevelMenu::onWake(%this) ChooseLevelMenuTabList.visible = true;//$pref::HostMultiPlayer; ChooseLevelMenuNavButtonOverlay.visible = true;//$pref::HostMultiPlayer; - %this.schedule(32, openMenu, 0); + //If we've only got one gamemode, just force it to use that one, as we can + //assume that is THE game's gamemode + %gamemodeList = getGameModesList(); + if(%gamemodeList.count() == 1) + { + %gameModeObj = %gamemodeList.getKey(0); + if(isObject(%gameModeObj)) + { + %gameModeObj.active = true; + + ChooseLevelMenuTabList-->LevelBtn.active = true; + ChooseLevelMenuTabList-->GameModeBtn.active = false; + + ChooseLevelMenu.openMenu(1); + + return; + } + } + else + { + //Otherwise open the gamemode list as normal + %this.schedule(32, openMenu, 0); + } } function refreshGameModesList() @@ -77,36 +99,36 @@ function refreshGameModesList() if(%gameModeName $= "") %gameModeName = %gameModeObj.getName(); - %preview = new GuiContainer() { - position = "0 0"; - extent = $LevelPreviewButtonSize; + %preview = new GuiContainer() { + position = "0 0"; + extent = $LevelPreviewButtonSize; + horizSizing = "right"; + vertSizing = "bottom"; + gameModeObj = %gameModeObj; + gameModeDesc = %gameModeObj.description; + previewImage = %gameModeObj.previewImage; + cansave = false; + + new GuiToggleButtonCtrl() { + position = $LevelPreviewButtonSize.y SPC 0; + extent = $LevelPreviewButtonSize.x - $LevelPreviewButtonSize.y - 5 SPC $LevelPreviewButtonSize.y; + profile = GuiMenuButtonProfile; horizSizing = "right"; vertSizing = "bottom"; - gameModeObj = %gameModeObj; - gameModeDesc = %gameModeObj.description; - previewImage = %gameModeObj.previewImage; - cansave = false; - - new GuiToggleButtonCtrl() { - position = $LevelPreviewButtonSize.y SPC 0; - extent = $LevelPreviewButtonSize.x - $LevelPreviewButtonSize.y - 5 SPC $LevelPreviewButtonSize.y; - profile = GuiMenuButtonProfile; - horizSizing = "right"; - vertSizing = "bottom"; - internalName = "button"; - class = "GameModePreviewButton"; - text = %gameModeName; - command = "ToggleGameMode(" @ %i @ ");"; //allow doubleclick to quick action it - textMargin = 120; - }; - - new GuiBitmapCtrl() { - position = "0 0"; - extent = $LevelPreviewButtonSize.y SPC $LevelPreviewButtonSize.y; - internalName = "checkbox"; - bitmapAsset = "UI:toggleMarker_image"; - }; + internalName = "button"; + class = "GameModePreviewButton"; + text = %gameModeName; + command = "ToggleGameMode(" @ %i @ ");"; //allow doubleclick to quick action it + textMargin = 120; + }; + + new GuiBitmapCtrl() { + position = "0 0"; + extent = $LevelPreviewButtonSize.y SPC $LevelPreviewButtonSize.y; + internalName = "checkbox"; + bitmapAsset = "UI:toggleMarker_image"; }; + }; GameModePreviewArray.add(%preview); @@ -134,6 +156,8 @@ function refreshLevelsList() return; } + %gameModesList = getGameModesList(); + //filter the levelAssets by the gamemode selected %filteredIndex = 0; for(%i=0; %i < %count; %i++) @@ -168,14 +192,14 @@ function refreshLevelsList() for(%gm = 0; %gm < getTokenCount(%levelGameModes, ";"); %gm++) { %gameModeName = getToken(%levelGameModes, ";", %gm); - - for(%g=0; %g < GameModePreviewArray.getCount(); %g++) + for(%g=0; %g < %gameModesList.count(); %g++) { - %gmb = GameModePreviewArray.getObject(%g); - if(!isObject(%gmb.gameModeObj) || (!%gmb.gameModeObj.active && !%gmb.gameModeObj.alwaysActive)) + %gameModeObj = %gameModesList.getKey(%g); + + if(!isObject(%gameModeObj) || (!%gameModeObj.active && !%gameModeObj.alwaysActive && %gameModesList.count() > 1)) continue; - if(%gameModeName $= %gmb.gameModeObj.getName()) + if(%gameModeName $= %gameModeObj.getName()) { %foundGameModeMatch = true; break; @@ -315,8 +339,6 @@ function GameModePreviewArray::syncGUI(%this) %btn = %this.getObject(%this.listPosition); %btn-->button.setHighlighted(true); - - //$pref::Server::GameMode = %btn.gameModeObject; } function ChooseLevelMenuPrevMenu(%val)