Merge pull request #299 from Areloch/LevelDependencyLoadingAndFixes

Level Asset Dependency handling and various level fixes
This commit is contained in:
Brian Roberts 2020-08-21 07:28:24 -05:00 committed by GitHub
commit 8e61600a82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 263 additions and 255 deletions

View file

@ -1,4 +1,5 @@
#include "Scene.h"
#include "T3D/assets/LevelAsset.h"
Scene * Scene::smRootScene = nullptr;
Vector<Scene*> Scene::smSceneList;
@ -180,6 +181,93 @@ void Scene::unpackUpdate(NetConnection *conn, BitStream *stream)
}
void Scene::dumpUtilizedAssets()
{
Con::printf("Dumping utilized assets in scene!");
Vector<StringTableEntry> utilizedAssetsList;
for (U32 i = 0; i < mPermanentObjects.size(); i++)
{
mPermanentObjects[i]->getUtilizedAssets(&utilizedAssetsList);
}
for (U32 i = 0; i < mDynamicObjects.size(); i++)
{
mDynamicObjects[i]->getUtilizedAssets(&utilizedAssetsList);
}
for (U32 i = 0; i < utilizedAssetsList.size(); i++)
{
Con::printf("Utilized Asset: %s", utilizedAssetsList[i]);
}
Con::printf("Utilized Asset dump complete!");
}
StringTableEntry Scene::getOriginatingFile()
{
return getFilename();
}
StringTableEntry Scene::getLevelAsset()
{
StringTableEntry levelFile = getFilename();
if (levelFile == StringTable->EmptyString())
return StringTable->EmptyString();
AssetQuery* query = new AssetQuery();
query->registerObject();
S32 foundAssetcount = AssetDatabase.findAssetLooseFile(query, levelFile);
if (foundAssetcount == 0)
return StringTable->EmptyString();
else
return query->mAssetList[0];
}
bool Scene::saveScene(StringTableEntry fileName)
{
//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
//First, save the level file
if (fileName == StringTable->EmptyString())
{
fileName = getOriginatingFile();
}
bool saveSuccess = save(fileName);
if (!saveSuccess)
return false;
//Get the level asset
StringTableEntry levelAsset = getLevelAsset();
if (levelAsset == StringTable->EmptyString())
return saveSuccess;
LevelAsset* levelAssetDef = AssetDatabase.acquireAsset<LevelAsset>(levelAsset);
levelAssetDef->clearAssetDependencyFields("staticObjectAssetDependency");
//Next, lets build out our
Vector<StringTableEntry> utilizedAssetsList;
for (U32 i = 0; i < mPermanentObjects.size(); i++)
{
mPermanentObjects[i]->getUtilizedAssets(&utilizedAssetsList);
}
for (U32 i = 0; i < utilizedAssetsList.size(); i++)
{
levelAssetDef->addAssetDependencyField("staticObjectAssetDependency", utilizedAssetsList[i]);
}
saveSuccess = levelAssetDef->saveAsset();
return saveSuccess;
}
//
Vector<SceneObject*> Scene::getObjectsByClass(String className, bool checkSubscenes)
{
@ -251,3 +339,34 @@ DefineEngineMethod(Scene, getObjectsByClass, String, (String className), (""),
//return object->getObjectsByClass(className);
return "";
}
DefineEngineMethod(Scene, dumpUtilizedAssets, void, (), ,
"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")
{
object->dumpUtilizedAssets();
}
DefineEngineMethod(Scene, getOriginatingFile, const char*, (), ,
"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")
{
return object->getOriginatingFile();
}
DefineEngineMethod(Scene, getLevelAsset, const char*, (), ,
"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")
{
return object->getLevelAsset();
}
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));
}

View file

@ -62,6 +62,13 @@ public:
void addDynamicObject(SceneObject* object);
void removeDynamicObject(SceneObject* object);
void dumpUtilizedAssets();
StringTableEntry getOriginatingFile();
StringTableEntry getLevelAsset();
bool saveScene(StringTableEntry fileName);
//
//Networking
U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream);

View file

@ -330,9 +330,52 @@ void LevelAsset::setNavmeshFile(const char* pNavmeshFile)
refreshAsset();
}
void LevelAsset::loadDependencies()
{
//First, load any material, animation, etc assets we may be referencing in our asset
// Find any asset dependencies.
AssetManager::typeAssetDependsOnHash::Iterator assetDependenciesItr = mpOwningAssetManager->getDependedOnAssets()->find(mpAssetDefinition->mAssetId);
// Does the asset have any dependencies?
if (assetDependenciesItr != mpOwningAssetManager->getDependedOnAssets()->end())
{
// Iterate all dependencies.
while (assetDependenciesItr != mpOwningAssetManager->getDependedOnAssets()->end() && assetDependenciesItr->key == mpAssetDefinition->mAssetId)
{
//Force it to be loaded by acquiring it
StringTableEntry assetId = assetDependenciesItr->value;
mAssetDependencies.push_back(AssetDatabase.acquireAsset<AssetBase>(assetId));
// Next dependency.
assetDependenciesItr++;
}
}
}
void LevelAsset::unloadDependencies()
{
for (U32 i = 0; i < mAssetDependencies.size(); i++)
{
AssetBase* assetDef = mAssetDependencies[i];
AssetDatabase.releaseAsset(assetDef->getAssetId());
}
}
DefineEngineMethod(LevelAsset, getLevelFile, const char*, (),,
"Creates a new script asset using the targetFilePath.\n"
"@return The bool result of calling exec")
{
return object->getLevelPath();
}
DefineEngineMethod(LevelAsset, loadDependencies, void, (), ,
"Initiates the loading of asset dependencies for this level.")
{
return object->loadDependencies();
}
DefineEngineMethod(LevelAsset, unloadDependencies, void, (), ,
"Initiates the unloading of previously loaded asset dependencies for this level.")
{
return object->unloadDependencies();
}

View file

@ -67,6 +67,8 @@ class LevelAsset : public AssetBase
StringTableEntry mGamemodeName;
Vector<AssetBase*> mAssetDependencies;
public:
LevelAsset();
virtual ~LevelAsset();
@ -78,6 +80,9 @@ public:
/// Declare Console Object.
DECLARE_CONOBJECT(LevelAsset);
void loadDependencies();
void unloadDependencies();
void setLevelFile(const char* pImageFile);
inline StringTableEntry getLevelFile(void) const { return mLevelFile; };
void setPostFXPresetFile(const char* pPostFXPresetFile);

View file

@ -221,7 +221,6 @@ bool TerrainAsset::loadTerrain()
//Force the asset to become initialized if it hasn't been already
AssetPtr<TerrainMaterialAsset> matAsset = assetId;
mTerrMaterialAssets.push_front(matAsset);
}

View file

@ -1691,6 +1691,12 @@ void TSStatic::updateMaterials()
mShapeInstance->initMaterialList();
}
void TSStatic::getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList)
{
if(!mShapeAsset.isNull())
usedAssetsList->push_back_unique(mShapeAssetId);
}
//------------------------------------------------------------------------
//These functions are duplicated in tsStatic and shapeBase.
//They each function a little differently; but achieve the same purpose of gathering

View file

@ -286,6 +286,8 @@ public:
bool isAnimated() { return mPlayAmbient; }
virtual void getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList);
private:
virtual void onStaticModified(const char* slotName, const char* newValue = NULL);
protected:

View file

@ -253,30 +253,22 @@ bool AssetManager::loadModuleAutoLoadAssets(ModuleDefinition* pModuleDefinition)
if (assetDef->mAssetType == pAutoloadAssets->getAssetType())
{
//TODO: this is stupid and ugly, need to properly automagically parse the class for registration
AssetBase* assetBase = nullptr;
String assetPath = assetDef->mAssetBaseFilePath;
assetPath.replace("//", "/");
if (assetDef->mAssetType == StringTable->insert("GUIAsset"))
{
assetBase = mTaml.read<GUIAsset>(assetDef->mAssetBaseFilePath);
}
else if (assetDef->mAssetType == StringTable->insert("ScriptAsset"))
{
assetBase = mTaml.read<ScriptAsset>(assetDef->mAssetBaseFilePath);
}
else if (assetDef->mAssetType == StringTable->insert("MaterialAsset"))
{
assetBase = mTaml.read<MaterialAsset>(assetDef->mAssetBaseFilePath);
}
else if (assetDef->mAssetType == StringTable->insert("GameObjectAsset"))
{
assetBase = mTaml.read<GameObjectAsset>(assetDef->mAssetBaseFilePath);
}
String autoLoadPath = pModuleDefinition->getModulePath();
autoLoadPath += "/";
autoLoadPath += pAutoloadAssets->getPath();
//load the asset now if valid
if (assetBase)
if (pAutoloadAssets->getPath() == StringTable->EmptyString() || assetPath.startsWith(autoLoadPath.c_str()))
{
assetBase->setOwned(this, assetDef);
AssetBase* assetBase = dynamic_cast<AssetBase*>(mTaml.read(assetDef->mAssetBaseFilePath));
//load the asset now if valid
if (assetBase)
{
assetBase->setOwned(this, assetDef);
}
}
}
}

View file

@ -942,6 +942,8 @@ class SceneObject : public NetObject, private SceneContainer::Link, public Proce
/// notification that a direct child object has been detached
virtual void onLostChild(SceneObject *subObject);
// PATHSHAPE END
virtual void getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList) {}
};
#endif // _SCENEOBJECT_H_

View file

@ -1420,6 +1420,11 @@ void TerrainBlock::getMinMaxHeight( F32 *minHeight, F32 *maxHeight ) const
*maxHeight = fixedToFloat( sq->maxHeight );
}
void TerrainBlock::getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList)
{
if (!mTerrainAsset.isNull())
usedAssetsList->push_back_unique(mTerrainAssetId);
}
//-----------------------------------------------------------------------------
// Console Methods
//-----------------------------------------------------------------------------

View file

@ -474,6 +474,8 @@ public:
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
void inspectPostApply();
virtual void getUtilizedAssets(Vector<StringTableEntry>* usedAssetsList);
protected:
bool mIgnoreZodiacs;

View file

@ -60,11 +60,11 @@ function Core_ClientServer::onDestroy( %this )
}
//-----------------------------------------------------------------------------
function StartGame( %mission, %hostingType )
function StartGame( %levelAsset, %hostingType )
{
if( %mission $= "" )
if( %levelAsset $= "" )
{
%mission = $selectedLevelFile;
%levelAsset = $selectedLevelAsset;
}
if (%hostingType !$= "")
@ -88,7 +88,7 @@ function StartGame( %mission, %hostingType )
Canvas.repaint();
}
createAndConnectToLocalServer( %serverType, %mission );
createAndConnectToLocalServer( %serverType, %levelAsset );
}
function JoinGame( %serverIndex )

View file

@ -33,22 +33,26 @@ $MissionLoadPause = 5000;
//-----------------------------------------------------------------------------
//This is the first call made by the server to kick the loading process off
function loadMission( %missionName, %isFirstMission )
function loadMission( %levelAsset, %isFirstMission )
{
endMission();
echo("*** LOADING MISSION: " @ %missionName);
$Server::LevelAsset = AssetDatabase.acquireAsset(%levelAsset);
echo("*** LOADING MISSION: " @ $Server::LevelAsset.LevelName);
echo("*** Stage 1 load");
// increment the mission sequence (used for ghost sequencing)
$missionSequence++;
$missionRunning = false;
$Server::MissionFile = %missionName;
$Server::MissionFile = $Server::LevelAsset.getLevelFile();
$Server::LoadFailMsg = "";
$Server::LevelAsset.loadDependencies();
// Extract mission info from the mission file,
// including the display name and stuff to send
// to the client.
buildLoadInfo( %missionName );
buildLoadInfo( $Server::MissionFile );
// Download mission info to the clients
%count = ClientGroup.getCount();
@ -163,6 +167,9 @@ function endMission()
getScene(0).delete();
MissionCleanup.delete();
if(isObject($Server::LevelAsset))
AssetDatabase.releaseAsset($Server::LevelAsset.getAssetId()); //cleanup
if ($Pref::Server::EnableDatablockCache)
resetDatablockCache();
DatablockFilesList.empty();

View file

@ -93,9 +93,9 @@ function portInit(%port)
/// create a local client connection to the server.
//
/// @return true if successful.
function createAndConnectToLocalServer( %serverType, %level )
function createAndConnectToLocalServer( %serverType, %levelAsset )
{
if( !createServer( %serverType, %level ) )
if( !createServer( %serverType, %levelAsset ) )
return false;
%conn = new GameConnection( ServerConnection );
@ -124,7 +124,7 @@ function createAndConnectToLocalServer( %serverType, %level )
/// Create a server with either a "SinglePlayer" or "MultiPlayer" type
/// Specify the level to load on the server
function createServer(%serverType, %level)
function createServer(%serverType, %levelAsset)
{
if($Game::firstTimeServerRun == true)
{
@ -135,7 +135,7 @@ function createServer(%serverType, %level)
// working with the server session we think we are.
$Server::Session++;
if (%level $= "")
if (%levelAsset $= "")
{
error("createServer(): level name unspecified");
return false;
@ -143,7 +143,7 @@ function createServer(%serverType, %level)
// Make sure our level name is relative so that it can send
// across the network correctly
%level = makeRelativePath(%level, getWorkingDirectory());
//%level = makeRelativePath(%level, getWorkingDirectory());
destroyServer();
@ -176,7 +176,7 @@ function createServer(%serverType, %level)
// the server has been created
onServerCreated();
loadMission(%level, true);
loadMission(%levelAsset, true);
$Game::running = true;

View file

@ -51,6 +51,9 @@ function ChooseLevelDlg::onWake( %this )
for(%i=0; %i < %count; %i++)
{
%assetId = %assetQuery.getAsset(%i);
if(AssetDatabase.getAssetModule(%assetId).ModuleId $= "ToolsModule")
continue;
%levelAsset = AssetDatabase.acquireAsset(%assetId);
@ -93,53 +96,6 @@ function ChooseLevelDlg::onWake( %this )
else
LevelSelectTitle.setText("CREATE SERVER");
/*for (%i = 0; %i < LevelList.rowCount(); %i++)
{
%preview = new GuiButtonCtrl() {
profile = "GuiMenuButtonProfile";
internalName = "SmallPreview" @ %i;
Extent = "368 35";
text = getField(CL_levelList.getRowText(%i), 0);
command = "ChooseLevelWindow.previewSelected(ChooseLevelWindow->SmallPreviews->SmallPreview" @ %i @ ");";
buttonType = "RadioButton";
};
ChooseLevelWindow->SmallPreviews.add(%preview);
%rowText = CL_levelList.getRowText(%i);
// Set the level index
%preview.levelIndex = %i;
// Get the name
%name = getField(CL_levelList.getRowText(%i), 0);
%preview.levelName = %name;
%file = getField(CL_levelList.getRowText(%i), 1);
// Find the preview image
%levelPreview = getField(CL_levelList.getRowText(%i), 3);
// Test against all of the different image formats
// This should probably be moved into an engine function
if (isFile(%levelPreview @ ".png") ||
isFile(%levelPreview @ ".jpg") ||
isFile(%levelPreview @ ".bmp") ||
isFile(%levelPreview @ ".gif") ||
isFile(%levelPreview @ ".jng") ||
isFile(%levelPreview @ ".mng") ||
isFile(%levelPreview @ ".tga"))
{
%preview.bitmap = %levelPreview;
}
// Get the description
%desc = getField(CL_levelList.getRowText(%i), 2);
%preview.levelDesc = %desc;
}*/
ChooseLevelButtonHolder.setActive();
}
@ -184,28 +140,7 @@ function ChooseLevelDlg::addMissionFile( %this, %file )
function ChooseLevelDlg::addLevelAsset( %this, %levelAsset )
{
%file = %levelAsset.getLevelFile();
/*%levelName = fileBase(%file);
%levelDesc = "A Torque level";
%LevelInfoObject = getLevelInfo(%file);
if (%LevelInfoObject != 0)
{
if(%LevelInfoObject.levelName !$= "")
%levelName = %LevelInfoObject.levelName;
else if(%LevelInfoObject.name !$= "")
%levelName = %LevelInfoObject.name;
if (%LevelInfoObject.desc0 !$= "")
%levelDesc = %LevelInfoObject.desc0;
if (%LevelInfoObject.preview !$= "")
%levelPreview = %LevelInfoObject.preview;
%LevelInfoObject.delete();
}*/
%file = %levelAsset.getAssetId();
%levelName = %levelAsset.LevelName;
%levelDesc = %levelAsset.description;
@ -224,7 +159,7 @@ function LevelList::onChange(%this)
ChooseLevelWindow->LevelName.text = getField(%levelEntry, 0);
// Get the level file
$selectedLevelFile = getField(%levelEntry, 1);
$selectedLevelAsset = getField(%levelEntry, 1);
// Find the preview image
%levelPreview = getField(%levelEntry, 3);

View file

@ -16,7 +16,7 @@ function ToolsModule::onCreate(%this)
// to find exactly which subsystems should be readied before kicking things off.
// ----------------------------------------------------------------------------
ModuleDatabase.LoadExplicit( "MainEditor" );
//ModuleDatabase.LoadExplicit( "MainEditor" );
//ModuleDatabase.LoadExplicit( "Tools_ObjectViewer" );
}

View file

@ -5,4 +5,10 @@
ScriptFile="Tools.cs"
CreateFunction="onCreate"
DestroyFunction="onDestroy"
Group="Tools"/>
Group="Tools">
<DeclaredAssets
canSave="true"
canSaveDynamicFields="true"
Extension="asset.taml"
Recurse="true" />
</ModuleDefinition>

View file

@ -1,141 +0,0 @@
//--- OBJECT WRITE BEGIN ---
new Scene(EditorTemplateLevel) {
canSave = "1";
canSaveDynamicFields = "1";
isSubScene = "0";
isEditing = "0";
isDirty = "0";
cdTrack = "2";
CTF_scoreLimit = "5";
Enabled = "1";
musicTrack = "lush";
new LevelInfo(theLevelInfo) {
nearClip = "0.1";
visibleDistance = "1000";
visibleGhostDistance = "0";
decalBias = "0.0015";
fogColor = "0.6 0.6 0.7 1";
fogDensity = "0";
fogDensityOffset = "700";
fogAtmosphereHeight = "0";
canvasClearColor = "0 0 0 255";
ambientLightBlendPhase = "1";
ambientLightBlendCurve = "0 0 -1 -1";
soundAmbience = "AudioAmbienceDefault";
soundDistanceModel = "Linear";
canSave = "1";
canSaveDynamicFields = "1";
advancedLightmapSupport = "0";
desc0 = "A blank room template that acts as a starting point.";
Enabled = "1";
LevelName = "Blank Room Template";
};
new SkyBox(theSky) {
Material = "BlankSkyMat";
drawBottom = "0";
fogBandHeight = "0";
dirtyGameObject = "0";
position = "0 0 0";
rotation = "1 0 0 0";
scale = "1 1 1";
canSave = "1";
canSaveDynamicFields = "1";
};
new Sun(theSun) {
azimuth = "230.396";
elevation = "45";
color = "0.968628 0.901961 0.901961 1";
ambient = "0.337255 0.533333 0.619608 1";
brightness = "1";
castShadows = "1";
staticRefreshFreq = "250";
dynamicRefreshFreq = "8";
coronaEnabled = "1";
coronaScale = "0.5";
coronaTint = "1 1 1 1";
coronaUseLightColor = "1";
flareScale = "1";
attenuationRatio = "0 1 1";
shadowType = "PSSM";
texSize = "2048";
overDarkFactor = "3000 1500 750 250";
shadowDistance = "200";
shadowSoftness = "0.25";
numSplits = "4";
logWeight = "0.9";
fadeStartDistance = "0";
lastSplitTerrainOnly = "0";
representedInLightmap = "0";
shadowDarkenColor = "0 0 0 -1";
includeLightmappedGeometryInShadow = "0";
dirtyGameObject = "0";
position = "0 0 0";
rotation = "1 0 0 0";
scale = "1 1 1";
canSave = "1";
canSaveDynamicFields = "1";
bias = "0.1";
Blur = "1";
Enabled = "1";
height = "1024";
lightBleedFactor = "0.8";
minVariance = "0";
pointShadowType = "PointShadowType_Paraboloid";
shadowBox = "-100 -100 -100 100 100 100";
splitFadeDistances = "1 1 1 1";
width = "3072";
};
new GroundPlane() {
squareSize = "128";
scaleU = "25";
scaleV = "25";
Material = "Grid_512_Grey";
dirtyGameObject = "0";
canSave = "1";
canSaveDynamicFields = "1";
Enabled = "1";
position = "0 0 0";
rotation = "1 0 0 0";
scale = "1 1 1";
};
new Skylight() {
Enabled = "1";
ReflectionMode = "Baked Cubemap";
dirtyGameObject = "0";
position = "1.37009 -5.23561 46.5817";
rotation = "1 0 0 0";
canSave = "1";
canSaveDynamicFields = "1";
persistentId = "d5eb3afb-dced-11e9-a423-bb0e346e3870";
reflectionPath = "tools/levels/BlankRoom/probes/";
};
new TSStatic() {
ShapeAsset = "pbr:material_ball";
playAmbient = "1";
meshCulling = "0";
originSort = "0";
overrideColor = "0 0 0 0";
collisionType = "Collision Mesh";
decalType = "Collision Mesh";
allowPlayerStep = "0";
alphaFadeEnable = "0";
alphaFadeStart = "100";
alphaFadeEnd = "150";
alphaFadeInverse = "0";
renderNormals = "0";
forceDetail = "-1";
ignoreZodiacs = "0";
useGradientRange = "0";
gradientRange = "0 180";
invertGradientRange = "0";
dirtyGameObject = "0";
position = "-0.000554562 -0.0734091 1.05277";
rotation = "1 0 0 0";
scale = "1 1 1";
canSave = "1";
canSaveDynamicFields = "1";
materialSlot0 = "base_material_ball";
};
};
//--- OBJECT WRITE END ---

View file

@ -0,0 +1,10 @@
<LevelAsset
canSave="true"
canSaveDynamicFields="true"
AssetName="DefaultEditorLevel"
LevelFile="@assetFile=DefaultEditorLevel.mis"
LevelName="DefaultEditorLevel"
isSubScene="false"
description="An empty room"
staticObjectAssetDependency0="@Asset=FPSGameplay:station01"
VersionId="1" />

View file

@ -272,7 +272,7 @@ function fastLoadWorldEdit(%val)
if( !$missionRunning )
{
EditorNewLevel("tools/levels/DefaultEditorLevel.mis");
EditorNewLevel("ToolsModule:DefaultEditorLevel");
}
else
{

View file

@ -199,7 +199,7 @@ function EditorOpenDeclarationInTorsion( %object )
EditorOpenFileInTorsion( makeFullPath( %fileName ), %object.getDeclarationLine() );
}
function EditorNewLevel( %file )
function EditorNewLevel( %level )
{
%saveFirst = false;
if ( EditorIsDirty() )
@ -219,18 +219,18 @@ function EditorNewLevel( %file )
Editor.getUndoManager().clearAll();
}
if( %file $= "" )
if( %level $= "" )
{
%file = "tools/levels/DefaultEditorLevel.mis";
%level = "ToolsModule:DefaultEditorLevel";
}
if( !$missionRunning )
{
activatePackage( "BootEditor" );
StartGame( %file );
StartGame( %level );
}
else
EditorOpenMission(%file);
EditorOpenMission(%level);
//EWorldEditor.isDirty = true;
//ETerrainEditor.isDirty = true;
@ -360,6 +360,15 @@ function EditorSaveMissionAs( %levelAsset )
if( fileExt( %missionName ) !$= ".mis" )
%missionName = %missionName @ ".mis";
//Update to be our active
$Server::MissionFile = %missionName;
%Server::LevelAsset = %levelAssetDef;
//Do the save
EditorSaveMission();
//TODO: doublecheck that we rename the scene properly
//Make sure we have a selected module so we can create our module
//if(AssetBrowser.selectedModule $= "")
// Canvas.pushDialog(