You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1312 lines
39 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "HeadSpin.h"
#include "plPythonFileComponent.h"
#include "resource.h"
#include "plComponent.h"
#include "plComponentReg.h"
#include "../MaxMain/plMaxNode.h"
#include "hsUtils.h"
#include "plAutoUIBlock.h"
#include "plAutoUIParams.h"
#include "../pnSceneObject/plSceneObject.h"
#include "../MaxMain/plPluginResManager.h"
#include "../pnMessage/plObjRefMsg.h"
#include "plVolumeGadgetComponent.h"
#include "plGUIComponents.h"
#include "pfGUISkinComp.h"
#include "plExcludeRegionComponent.h"
#include "plAnimComponent.h"
#include "plNotetrackAnim.h"
#include "plOneShotComponent.h"
#include "plMultistageBehComponent.h"
#include "../plInterp/plAnimEaseTypes.h"
#include "../plAgeDescription/plAgeDescription.h"
#include "plWaterComponent.h"
#include "../plDrawable/plWaveSetBase.h"
#include "plClusterComponent.h"
#include "../plDrawable/plClusterGroup.h"
#include "plPhysicalComponents.h"
//#include "../plHavok1/plHKPhysical.h"
#include "../plAvatar/plSwimRegion.h"
#include "../../PubUtilLib/plSurface/plGrassShaderMod.h"
#include "plGrassComponent.h"
#include "notify.h"
#include <vector>
#include <set>
#include <map>
#include <string>
#include "../pfPython/plPythonFileMod.h"
#include "../pfPython/plPythonParameter.h"
// for DynamicText hack to get the plKeys (could probably be removed later)
#include "../plGImage/plDynamicTextMap.h"
//// plCommonPythonLib ///////////////////////////////////////////////////////
// Derived class for our global python fileMods, since they go in to the
// BuiltIn page
// 2.4.03 mcn - Added sceneObjects to the list, so that we can have an
// associated sceneObject for our mod
#include "../MaxMain/plCommonObjLib.h"
#include "../plSDL/plSDL.h"
class plCommonPythonLib : public plCommonObjLib
{
public:
virtual hsBool IsInteresting( const plKey &objectKey )
{
if( objectKey->GetUoid().GetClassType() == plPythonFileMod::Index() )
return true;
if( objectKey->GetUoid().GetClassType() == plSceneObject::Index() &&
strcmp( objectKey->GetUoid().GetObjectName(), plSDL::kAgeSDLObjectName ) == 0 )
return true;
return false;
}
};
static plCommonPythonLib sCommonPythonLib;
static void NotifyProc(void *param, NotifyInfo *info);
// Notify us on file open so we can check for bad Python components
void DummyCodeIncludePythonFileFunc()
{
RegisterNotification(NotifyProc, nil, NOTIFY_FILE_POST_OPEN);
RegisterNotification(NotifyProc, nil, NOTIFY_FILE_PRE_SAVE);
RegisterNotification(NotifyProc, nil, NOTIFY_SYSTEM_POST_RESET);
RegisterNotification(NotifyProc, nil, NOTIFY_SYSTEM_POST_NEW);
RegisterNotification(NotifyProc, nil, NOTIFY_SYSTEM_SHUTDOWN2);
}
// Param block ID's
enum
{
kPythonFileType_DEAD,
kPythonFilePB,
kPythonVersion,
kPythonFileIsGlobal
};
class plPythonFileComponent : public plComponent
{
public:
typedef std::map<plMaxNode*, plKey> PythonKeys;
// When we're auto-exporting and can't pop up an error dialog, we cache our
// errors here and blab about it at export time
static std::string fPythonError;
protected:
PythonKeys fModKeys;
hsTArray<plKey> fOthersKeys;
public:
plPythonFileComponent();
virtual hsBool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg);
virtual hsBool PreConvert(plMaxNode *node, plErrorMsg *pErrMsg);
virtual hsBool Convert(plMaxNode *node, plErrorMsg *pErrMsg);
virtual void AddReceiverKey(plKey key, plMaxNode* node=nil) { fOthersKeys.Append(key); }
// Returns false if the Python file for this component wasn't loaded, and clears
// the data in the PB to prevent Max from crashing.
enum Validate
{
kPythonOk,
kPythonNoFile,
kPythonBadVer,
kPythonNoVer,
};
Validate ValidateFile();
const char* GetPythonName();
virtual PythonKeys& GetKeys() { return fModKeys; }
virtual hsBool DeInit(plMaxNode *node, plErrorMsg *pErrMsg)
{
fModKeys.clear();
fOthersKeys.Reset();
return plComponent::DeInit(node, pErrMsg);
}
};
CLASS_DESC(plPythonFileComponent, gPythonFileComponentDesc, "Python File", "PythonFile", COMP_TYPE_LOGIC, PYTHON_FILE_CID);
std::string plPythonFileComponent::fPythonError;
////////////////////////////////////////////////////////////////////////////////
// Called by the Python File manager
//
// Storage for all the registered Python file types
static std::vector<plAutoUIBlock*> gAutoUIBlocks;
void PythonFile::AddAutoUIBlock(plAutoUIBlock *block)
{
for (int i = 0; i < gAutoUIBlocks.size(); i++)
{
if (gAutoUIBlocks[i]->GetBlockID() == block->GetBlockID())
{
char buf[256];
sprintf(buf,
"Duplicate Python File ID\n\n"
"%s and %s use ID %d\n\n"
"%s will be ignored.",
gAutoUIBlocks[i]->GetName(),
block->GetName(),
block->GetBlockID(),
block->GetName());
hsMessageBox(buf, "Warning", hsMBoxOk);
return;
}
}
gAutoUIBlocks.push_back(block);
}
plComponentClassDesc *PythonFile::GetClassDesc()
{
return &gPythonFileComponentDesc;
}
static plAutoUIBlock *FindAutoUI(IParamBlock2 *pb)
{
if (!pb)
return nil;
for (int i = 0; i < gAutoUIBlocks.size(); i++)
{
if (gAutoUIBlocks[i]->GetBlockID() == pb->ID())
return gAutoUIBlocks[i];
}
return nil;
}
////////////////////////////////////////////////////////////////////////////////
plPythonFileComponent::plPythonFileComponent()
{
fClassDesc = &gPythonFileComponentDesc;
fClassDesc->MakeAutoParamBlocks(this);
}
hsBool plPythonFileComponent::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg)
{
if (fPythonError.length() > 0)
{
pErrMsg->Set(true, "Python Error", fPythonError.c_str()).Show();
pErrMsg->Set();
fPythonError = "";
}
fModKeys.clear();
return true;
}
hsBool plPythonFileComponent::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
IParamBlock2 *pb = (IParamBlock2*)fCompPB->GetReferenceTarget(kPythonFilePB);
plAutoUIBlock *block = FindAutoUI(pb);
if (!block)
return false;
// if this is a multi-modifier python file component then is this the main (or first) maxnode
bool mainMultiModierNode = false;
plPythonFileMod *mod = TRACKED_NEW plPythonFileMod;
// create the modifier key ourselves so that we can get the name of the modifier in the name
plSceneObject *obj = node->GetSceneObject();
plKey modKey;
if( fCompPB->GetInt( kPythonFileIsGlobal ) )
{
// Do we already have this guy?
plPythonFileMod *existingOne = plPythonFileMod::ConvertNoRef( sCommonPythonLib.FindObject( plPythonFileMod::kGlobalNameKonstant ) );
if( existingOne != nil )
{
// This component already exists, which can happen since it's in a common page. So we need
// to nuke the key and its object so we can reuse it.
modKey = existingOne->GetKey();
// But first detach it from its target sceneObject
if( existingOne->GetTarget( 0 ) != nil )
existingOne->GetTarget( 0 )->GetKey()->Release( modKey );
if( !sCommonPythonLib.RemoveObjectAndKey( modKey ) )
{
pErrMsg->Set( true, "Python File Component Error",
"The global Python File Component %s is attempting to export over an already "
"existing component of the same name, and the exporter is unable to delete the "
"old object to replace it. This would be a good time to call mcn for help.", GetINode()->GetName() ).Show();
pErrMsg->Set( false );
}
// Pointer is invalid now anyways...
existingOne = nil;
}
// Also make sure we have an age SDL object to attach to (currently only used for python, hence why it's safe here)
obj = plSceneObject::ConvertNoRef( sCommonPythonLib.FindObject( plSDL::kAgeSDLObjectName ) );
if( obj != nil )
{
plKey foo = obj->GetKey();
if( !sCommonPythonLib.RemoveObjectAndKey( foo ) )
{
pErrMsg->Set( true, "Python File Component Error",
"The global Python File Component %s is attempting to export over an already "
"existing component of the same name, and the exporter is unable to delete the "
"old sceneObject to replace it. This would be a good time to call mcn for help.", GetINode()->GetName() ).Show();
pErrMsg->Set( false );
}
// Pointer is invalid now anyways...
obj = nil;
}
// Create us a new sceneObject to attach to
obj = TRACKED_NEW plSceneObject;
const plLocation &globalLoc = plPluginResManager::ResMgr()->GetCommonPage( node->GetLocation(), plAgeDescription::kGlobal );
hsAssert( globalLoc.IsValid(), "Invalid common page location!!!" );
modKey = hsgResMgr::ResMgr()->NewKey(plPythonFileMod::kGlobalNameKonstant, mod, globalLoc );
// Make a key for our special sceneObject too
plKey sdlObjectKey = hsgResMgr::ResMgr()->NewKey( plSDL::kAgeSDLObjectName, obj, globalLoc );
plPluginResManager::ResMgr()->AddLooseEnd(sdlObjectKey);
}
else
{
if (block->IsMultiModifier())
{
// yup, then only create one modifier that will all the objects will attach to
// in other words, one python module with many attached sceneobjects
if ( fModKeys.empty())
{
// its empty so create the first and only one
modKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), mod, node->GetLocation());
mainMultiModierNode = true;
}
else
{
// else we just take the first one (should be the only one)
PythonKeys::iterator pos;
pos = fModKeys.begin();
modKey = pos->second;
// Guess we won't be using that modifier we new'd up there. Does that mean we should delete it? Sure.
delete mod;
mod = nil;
}
}
else // else, nope... create a modifier for each object its attached to
{
modKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), mod, node->GetLocation());
}
}
hsgResMgr::ResMgr()->AddViaNotify(modKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
fModKeys[node] = modKey;
// only let non-multimodifier or multimodifier then only the main node register for notifies
if ( !block->IsMultiModifier() || mainMultiModierNode )
{
int nParams = block->NumParams();
for (int i = 0; i < nParams; i++)
{
plAutoUIParam *param = block->GetParam(i);
if (param->GetParamType() == plAutoUIParam::kTypeActivator)
{
// Register the modifier to recieve notifies from this activator.
// We'll let the modifier know the activator key when it's available,
// in the convert pass.
int numcomps = param->GetCount(pb);
for (int j=0; j< numcomps; j++ )
{
plComponentBase *comp = param->GetComponent(pb,j);
if (comp)
comp->AddReceiverKey(modKey);
}
}
if (param->GetParamType() == plAutoUIParam::kTypeGUIDialog)
{
// Register the modifier to recieve notifies from this activator.
// We'll let the modifier know the activator key when it's available,
// in the convert pass.
int numcomps = param->GetCount(pb);
for (int j=0; j< numcomps; j++ )
{
plComponentBase *comp = param->GetComponent(pb,j);
if (comp && comp->ClassID() == GUI_DIALOG_COMP_CLASS_ID )
{
// convert the comp to a GUIDialog component, so we can talk to it
plGUIDialogComponent *dialog_comp = (plGUIDialogComponent*)comp;
dialog_comp->SetNotifyReceiver(modKey);
}
}
}
}
}
return true;
}
#include "plActivatorBaseComponent.h"
#include "../pnKeyedObject/plKey.h"
#include "plResponderComponent.h"
hsBool plPythonFileComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
IParamBlock2 *pb = (IParamBlock2*)fCompPB->GetReferenceTarget(kPythonFilePB);
plAutoUIBlock *block = FindAutoUI(pb);
if (!block)
return false;
plKey modKey = fModKeys[node];
plPythonFileMod *mod = plPythonFileMod::ConvertNoRef(modKey->GetObjectPtr());
// add in all the receivers that want to be notified from the Python coded script
int j;
for (j = 0; j < fOthersKeys.Count(); j++)
{
mod->AddToNotifyList(fOthersKeys[j]);
}
// remove all the Keys we know about to be re-built on the next convert
fOthersKeys.Reset();
// set the name of the source file
mod->SetSourceFile(block->GetName());
int nParams = block->NumParams();
for (int i = 0; i < nParams; i++)
{
plAutoUIParam *param = block->GetParam(i);
plPythonParameter pyParam;
pyParam.fID = param->GetID();
// Get the data for the param
switch (param->GetParamType())
{
case plAutoUIParam::kTypeBool:
pyParam.SetToBoolean(param->GetBool(pb));
mod->AddParameter(pyParam);
break;
case plAutoUIParam::kTypeInt:
pyParam.SetToInt(param->GetInt(pb));
mod->AddParameter(pyParam);
break;
case plAutoUIParam::kTypeFloat:
pyParam.SetToFloat(param->GetFloat(pb));
mod->AddParameter(pyParam);
break;
case plAutoUIParam::kTypeString:
pyParam.SetToString(param->GetString(pb));
mod->AddParameter(pyParam);
break;
case plAutoUIParam::kTypeSceneObj:
{
int numKeys = param->GetCount(pb);
hsBool found_atleast_one_good_one = false;
for (int i = 0; i < numKeys; i++)
{
plKey skey = param->GetKey(pb, i);
if ( skey != nil )
{
pyParam.SetToSceneObject(skey, true);
// make sure that there really was a sceneobject
mod->AddParameter(pyParam);
found_atleast_one_good_one = true;
}
}
if ( !found_atleast_one_good_one )
{
char buf[512];
sprintf(buf,"The sceneobject attribute (ID=%d) that was selected in %s PythonFile, somehow does not exist!?",
pyParam.fID,this->GetINode()->GetName());
pErrMsg->Set(true, "PythonFile Warning", buf).Show();
pErrMsg->Set(false);
}
}
break;
case plAutoUIParam::kTypeComponent:
{
int count = param->GetCount(pb);
hsBool found_atleast_one_good_one = false;
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
if (comp)
{
for (int j = 0; j < comp->NumTargets(); j++)
{
plKey responderKey = Responder::GetKey(comp, comp->GetTarget(j));
if ( responderKey != nil )
{
pyParam.SetToResponder(responderKey);
mod->AddParameter(pyParam);
found_atleast_one_good_one = true;
}
}
if ( !found_atleast_one_good_one )
{
char buf[512];
sprintf(buf,"The responder attribute %s that was selected in %s PythonFile, somehow does not exist!?",
comp->GetINode()->GetName(),this->GetINode()->GetName());
pErrMsg->Set(true, "PythonFile Warning", buf).Show();
pErrMsg->Set(false);
}
}
}
}
break;
case plAutoUIParam::kTypeActivator:
{
int count = param->GetCount(pb);
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
// make sure we found a comp and then see if it is an activator type
if (comp && comp->CanConvertToType(ACTIVATOR_BASE_CID))
{
plActivatorBaseComponent *activator = (plActivatorBaseComponent*)comp;
const plActivatorBaseComponent::LogicKeys& logicKeys = activator->GetLogicKeys();
plActivatorBaseComponent::LogicKeys::const_iterator it;
for (it = logicKeys.begin(); it != logicKeys.end(); it++)
{
pyParam.SetToActivator(it->second);
mod->AddParameter(pyParam);
}
// do special stuff for Volume sensors because they are stupid turds
if (comp->ClassID() == VOLUMEGADGET_CID)
{
plVolumeGadgetComponent* pClick = (plVolumeGadgetComponent*)comp;
const plVolumeGadgetComponent::LogicKeys &logicKeys2 = pClick->GetLogicOutKeys();
plVolumeGadgetComponent::LogicKeys::const_iterator VGit;
for (VGit = logicKeys2.begin(); VGit != logicKeys2.end(); VGit++)
{
pyParam.SetToActivator(VGit->second);
mod->AddParameter(pyParam);
}
}
}
// now see if it is a PythonFile kinda activator thingy
else if (comp && comp->ClassID() == PYTHON_FILE_CID)
{
plPythonFileComponent *pyfact = (plPythonFileComponent*)comp;
const plPythonFileComponent::PythonKeys& pythonKeys = pyfact->GetKeys();
plPythonFileComponent::PythonKeys::const_iterator it;
for (it = pythonKeys.begin(); it != pythonKeys.end(); it++)
{
pyParam.SetToActivator(it->second);
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeDynamicText:
{
int numKeys = param->GetCount(pb);
for (int i = 0; i < numKeys; i++)
{
plKey key = param->GetKey(pb, i);
// make sure we got a key and that it is a DynamicTextMap
if (key && plDynamicTextMap::ConvertNoRef(key->GetObjectPtr()) )
{
pyParam.SetToDynamicText(key);
mod->AddParameter(pyParam);
}
}
}
break;
case plAutoUIParam::kTypeGUIDialog:
{
int count = param->GetCount(pb);
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
if (comp)
{
if (comp && comp->ClassID() == GUI_DIALOG_COMP_CLASS_ID )
{
// convert the comp to a GUIDialog component, so we can talk to it
plGUIDialogComponent *dialog_comp = (plGUIDialogComponent*)comp;
plKey dialogKey = dialog_comp->GetModifierKey();
pyParam.SetToGUIDialog(dialogKey);
if ( pyParam.fObjectKey == nil )
{
char buf[512];
sprintf(buf,"The GUIDialog attribute %s that was selected in %s PythonFile, somehow does not exist!?",
comp->GetINode()->GetName(),this->GetINode()->GetName());
pErrMsg->Set(true, "PythonFile Warning", buf).Show();
pErrMsg->Set(false);
}
else
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeGUIPopUpMenu:
{
int count = param->GetCount(pb);
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
if (comp)
{
if (comp && comp->ClassID() == GUI_MENUANCHOR_CLASSID )
{
// convert the comp to a GUIPopUpMenu component, so we can talk to it
plGUIMenuComponent *guiComp = (plGUIMenuComponent*)comp;
plKey key = guiComp->GetConvertedMenuKey();
pyParam.SetToGUIPopUpMenu( key );
if ( pyParam.fObjectKey == nil )
{
char buf[512];
sprintf(buf,"The GUIPopUpMenu attribute %s that was selected in %s PythonFile, somehow does not exist!?",
comp->GetINode()->GetName(),this->GetINode()->GetName());
pErrMsg->Set(true, "PythonFile Warning", buf).Show();
pErrMsg->Set(false);
}
else
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeGUISkin:
{
int count = param->GetCount(pb);
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
if (comp)
{
if (comp && comp->ClassID() == GUI_SKIN_CLASSID )
{
// convert the comp to a GUISkin component, so we can talk to it
plGUISkinComp *guiComp = (plGUISkinComp *)comp;
plKey key = guiComp->GetConvertedSkinKey();
pyParam.SetToGUISkin( key );
if ( pyParam.fObjectKey == nil )
{
char buf[512];
sprintf(buf,"The GUISkin attribute %s that was selected in %s PythonFile, somehow does not exist!?",
comp->GetINode()->GetName(),this->GetINode()->GetName());
pErrMsg->Set(true, "PythonFile Warning", buf).Show();
pErrMsg->Set(false);
}
else
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeExcludeRegion:
{
int count = param->GetCount(pb);
int number_of_real_targets_found = 0;
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
if (comp && comp->ClassID() == XREGION_CID )
{
for (int j = 0; j < comp->NumTargets(); j++)
{
plExcludeRegionComponent *excomp = (plExcludeRegionComponent*)comp;
plKey exKey = excomp->GetKey((plMaxNode*)(comp->GetTarget(j)));
if ( exKey != nil )
{
// only get one real target, just count the rest
if ( number_of_real_targets_found == 0 )
{
pyParam.SetToExcludeRegion(exKey);
mod->AddParameter(pyParam);
}
number_of_real_targets_found += 1;
}
}
if ( number_of_real_targets_found != 1 )
{
// there is zero or more than one node attached to this exclude region
char buf[512];
if ( number_of_real_targets_found == 0 )
sprintf(buf,"The ExcludeRegion %s that was selected as an attribute in %s PythonFile, has no scene nodes attached.",
comp->GetINode()->GetName(),this->GetINode()->GetName());
else
sprintf(buf,"The ExcludeRegion %s that was selected as an attribute in %s PythonFile, has more than one scene node attached (using first one found).",
comp->GetINode()->GetName(),this->GetINode()->GetName());
pErrMsg->Set(true, "PythonFile Warning", buf).Show();
pErrMsg->Set(false);
}
}
}
}
break;
case plAutoUIParam::kTypeWaterComponent:
{
plComponentBase* comp = param->GetComponent(pb, 0);
plWaveSetBase* wsb = nil;
if (comp)
{
wsb = plWaterComponent::GetWaveSet(comp->GetINode());
if (wsb != nil)
{
plKey waterKey = wsb->GetKey();
if ( waterKey != nil )
{
pyParam.SetToWaterComponent(waterKey);
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeSwimCurrentInterface:
{
plComponentBase* comp = param->GetComponent(pb, 0);
plSwimRegionInterface* sri = nil;
if (comp && comp->ClassID() == PHYS_SWIMSURFACE_CID)
{
plSwim2DComponent* swimcomp = (plSwim2DComponent*)comp;
std::map<plMaxNode*, plSwimRegionInterface*>::const_iterator containsNode;
plMaxNode* mnode = nil;
for (int i = 0; i < swimcomp->NumTargets(); i++)
{
mnode = (plMaxNode*)swimcomp->GetTarget(i);
containsNode = swimcomp->fSwimRegions.find(mnode);
if ( containsNode != swimcomp->fSwimRegions.end() )
{
sri = swimcomp->fSwimRegions[mnode];
break;
}
}
if (sri != nil)
{
plKey swimKey = sri->GetKey();
if ( swimKey != nil )
{
pyParam.SetToSwimCurrentInterface(swimKey);
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeClusterComponent:
{
plComponentBase* comp = param->GetComponent(pb, 0);
plClusterGroup* clusterGroup = nil;
if (comp && comp->ClassID() == CLUSTER_COMP_CID)
{
plClusterComponent* clusterComp = (plClusterComponent*)comp;
int numGroups = clusterComp->GetNumGroups();
int i;
for (i=0; i<numGroups; i++)
{
plClusterGroup* group = clusterComp->GetGroup(i);
plKey groupKey = group->GetKey();
if (groupKey != nil)
{
pyParam.SetToClusterComponent(groupKey);
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeAnimation:
{
int count = param->GetCount(pb);
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
if (comp && ( comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID ) )
{
plAnimComponentBase *animcomp = (plAnimComponentBase*)comp;
// save out the animation name first
const char *tempAnimName = animcomp->GetAnimName();
if (tempAnimName == nil)
pyParam.SetToAnimationName(ENTIRE_ANIMATION_NAME);
else
pyParam.SetToAnimationName(tempAnimName);
mod->AddParameter(pyParam);
// gather up all the modkeys for all the targets attached to this animation component
int j;
for ( j=0; j<comp->NumTargets(); j++ )
{
pyParam.SetToAnimation(animcomp->GetModKey((plMaxNode*)(comp->GetTarget(j))));
mod->AddParameter(pyParam);
}
}
}
}
break;
case plAutoUIParam::kTypeBehavior:
{
// The Behavior attribute is One-Shots and Multi-stage behaviors
// For Python usage: we will only allow using behaviors that are
// attached to one position node. In other words, Python will only
// be used for special cases.
int count = param->GetCount(pb);
int number_of_real_targets_found = 0;
for (int i = 0; i < count; i++)
{
plComponentBase *comp = param->GetComponent(pb, i);
if (comp && comp->ClassID() == ONESHOTCLASS_ID )
{
// gather up all the modkeys for all the targets attached to this animation component
int j;
for ( j=0; j<comp->NumTargets(); j++ )
{
plKey behKey = OneShotComp::GetOneShotKey(comp,(plMaxNode*)(comp->GetTarget(j)));
if ( behKey != nil )
{
// only get one real target, just count the rest
if ( number_of_real_targets_found == 0 )
{
pyParam.SetToBehavior(behKey);
mod->AddParameter(pyParam);
}
number_of_real_targets_found += 1;
}
}
}
else if (comp && comp->ClassID() == MULTISTAGE_BEH_CID )
{
// gather up all the modkeys for all the targets attached to this animation component
int j;
for ( j=0; j<comp->NumTargets(); j++ )
{
plKey behKey = MultiStageBeh::GetMultiStageBehKey(comp,(plMaxNode*)(comp->GetTarget(j)));
if ( behKey != nil )
{
// only get one real target, just count the rest
if ( number_of_real_targets_found == 0 )
{
pyParam.SetToBehavior(behKey);
mod->AddParameter(pyParam);
}
number_of_real_targets_found += 1;
}
}
}
if ( number_of_real_targets_found != 1 )
{
// there is zero or more than one node attached to this exclude region
char buf[512];
if ( number_of_real_targets_found == 0 )
sprintf(buf,"The Behavior component %s that was selected as an attribute in %s PythonFile, has no scene nodes attached.",
comp->GetINode()->GetName(),this->GetINode()->GetName());
else
sprintf(buf,"The Behavior component %s that was selected as an attribute in %s PythonFile, has more than one scene node attached (using first one found).",
comp->GetINode()->GetName(),this->GetINode()->GetName());
pErrMsg->Set(true, "PythonFile Warning", buf).Show();
pErrMsg->Set(false);
}
}
}
break;
case plAutoUIParam::kTypeMaterial:
{
int numKeys = param->GetCount(pb);
for (int i = 0; i < numKeys; i++)
{
plKey key = param->GetKey(pb, i);
// make sure we got a key and that it is a plMipmap
if (key && plMipmap::ConvertNoRef(key->GetObjectPtr()) )
{
pyParam.SetToMaterial(key);
mod->AddParameter(pyParam);
}
}
}
break;
case plAutoUIParam::kTypeMaterialAnimation:
{
plPickMaterialAnimationButtonParam* matAnim = (plPickMaterialAnimationButtonParam*)param;
matAnim->CreateKeyArray(pb);
int numKeys = param->GetCount(pb);
for (int i = 0; i < numKeys; i++)
{
plKey key = param->GetKey(pb, i);
if ( key )
{
pyParam.SetToMaterialAnimation(key);
mod->AddParameter(pyParam);
}
}
matAnim->DestroyKeyArray();
}
break;
case plAutoUIParam::kTypeDropDownList:
pyParam.SetToString(param->GetString(pb));
mod->AddParameter(pyParam);
break;
case plAutoUIParam::kTypeGrassComponent:
{
plComponentBase* comp = param->GetComponent(pb, 0);
plGrassShaderMod* shader = nil;
if (comp)
{
shader = plGrassComponent::GetShader(comp->GetINode());
if (shader != nil)
{
plKey shaderKey = shader->GetKey();
if ( shaderKey != nil )
{
pyParam.SetToGrassShaderComponent(shaderKey);
mod->AddParameter(pyParam);
}
}
}
}
break;
}
}
return true;
}
plPythonFileComponent::Validate plPythonFileComponent::ValidateFile()
{
// Make sure the type is in the valid range
IParamBlock2 *pb = (IParamBlock2*)fCompPB->GetReferenceTarget(kPythonFilePB);
plAutoUIBlock *block = FindAutoUI(pb);
if (!block || fCompPB->GetInt(kPythonVersion) > block->GetVersion())
{
// Bad type, clear out the PB so this won't blow up during a save
fCompPB->SetValue(kPythonFilePB, 0, (ReferenceTarget*)nil);
if (!block)
return kPythonNoFile;
else
return kPythonBadVer;
}
// Got a newer version of the Python file, update our version
if (fCompPB->GetInt(kPythonVersion) < block->GetVersion())
fCompPB->SetValue(kPythonVersion, 0, block->GetVersion());
if (block->GetVersion() == 0)
return kPythonNoVer;
return kPythonOk;
}
const char* plPythonFileComponent::GetPythonName()
{
// Make sure the type is in the valid range
IParamBlock2 *pb = (IParamBlock2*)fCompPB->GetReferenceTarget(kPythonFilePB);
plAutoUIBlock *block = FindAutoUI(pb);
if (block)
return block->GetName();
return "(unknown)";
}
////////////////////////////////////////////////////////////////////////////////
// Verify that all Python File components in the scene are OK, and warn the user
// if any aren't
//
class plPythonError
{
public:
plMaxNode* node;
const char* pythonName;
plPythonFileComponent::Validate error;
bool operator< (const plPythonError& rhs) const { return rhs.node < node; }
};
typedef std::set<plPythonError> ErrorSet;
static void CheckPythonFileCompsRecur(plMaxNode *node, ErrorSet& badNodes)
{
plComponentBase *comp = node->ConvertToComponent();
if (comp && comp->ClassID() == PYTHON_FILE_CID)
{
plPythonFileComponent* pyComp = (plPythonFileComponent*)comp;
const char* pythonName = pyComp->GetPythonName();
plPythonFileComponent::Validate valid = pyComp->ValidateFile();
if (valid != plPythonFileComponent::kPythonOk)
{
plPythonError err;
err.node = node;
err.pythonName = pythonName;
err.error = valid;
badNodes.insert(err);
}
}
for (int i = 0; i < node->NumberOfChildren(); i++)
CheckPythonFileCompsRecur((plMaxNode*)node->GetChildNode(i), badNodes);
}
static BOOL CALLBACK WarnDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg == WM_INITDIALOG)
{
HWND hList = GetDlgItem(hDlg, IDC_COMP_LIST);
LVCOLUMN lvc;
lvc.mask = LVCF_TEXT;
lvc.pszText = "Component";
ListView_InsertColumn(hList, 0, &lvc);
lvc.pszText = "Python File";
ListView_InsertColumn(hList, 1, &lvc);
lvc.pszText = "Error";
ListView_InsertColumn(hList, 2, &lvc);
ErrorSet *badNodes = (ErrorSet*)lParam;
ErrorSet::iterator it = badNodes->begin();
for (; it != badNodes->end(); it++)
{
plPythonError& err = *it;
plMaxNode* node = err.node;
plPythonFileComponent* comp = (plPythonFileComponent*)node->ConvertToComponent();
LVITEM lvi;
lvi.mask = LVIF_TEXT;
lvi.pszText = (char*)node->GetName();
lvi.iItem = 0;
lvi.iSubItem = 0;
int idx = ListView_InsertItem(hList, &lvi);
ListView_SetItemText(hList, idx, 1, (char*)err.pythonName);
switch (err.error)
{
case plPythonFileComponent::Validate::kPythonBadVer:
ListView_SetItemText(hList, idx, 2, "Old Version");
break;
case plPythonFileComponent::Validate::kPythonNoVer:
ListView_SetItemText(hList, idx, 2, "No Version");
break;
case plPythonFileComponent::Validate::kPythonNoFile:
ListView_SetItemText(hList, idx, 2, "No File/Python Error");
break;
}
}
ListView_SetColumnWidth(hList, 0, LVSCW_AUTOSIZE);
ListView_SetColumnWidth(hList, 1, LVSCW_AUTOSIZE);
ListView_SetColumnWidth(hList, 2, LVSCW_AUTOSIZE);
return TRUE;
}
else if (msg == WM_COMMAND)
{
if (HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL))
{
DestroyWindow(hDlg);
return TRUE;
}
}
return FALSE;
}
static void WriteBadPythonText(ErrorSet& badNodes)
{
ErrorSet::iterator it = badNodes.begin();
for (; it != badNodes.end(); it++)
{
plPythonError& err = *it;
const char* compName = err.node->GetName();
const char* pythonFile = err.pythonName;
const char* errorText = "";
switch (err.error)
{
case plPythonFileComponent::kPythonBadVer:
errorText = "Old Version";
break;
case plPythonFileComponent::Validate::kPythonNoVer:
errorText = "No Version";
break;
case plPythonFileComponent::Validate::kPythonNoFile:
errorText = "No File/Python Error";
break;
}
std::string& pythonError = plPythonFileComponent::fPythonError;
pythonError += "Python component ";
pythonError += compName;
pythonError += " (file ";
pythonError += pythonFile;
pythonError += ") is bad. Reason: ";
pythonError += errorText;
pythonError += "\n";
}
}
static void NotifyProc(void *param, NotifyInfo *info)
{
static bool gotBadPython = false;
if (info->intcode == NOTIFY_FILE_POST_OPEN)
{
ErrorSet badNodes;
CheckPythonFileCompsRecur((plMaxNode*)GetCOREInterface()->GetRootNode(), badNodes);
if (badNodes.size() > 0)
{
gotBadPython = true;
if (hsMessageBox_SuppressPrompts)
WriteBadPythonText(badNodes);
else
CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_PYTHON_FILE_WARN), GetCOREInterface()->GetMAXHWnd(), WarnDialogProc, (LPARAM)&badNodes);
}
else
gotBadPython = false;
}
else if (info->intcode == NOTIFY_FILE_PRE_SAVE)
{
if (gotBadPython)
{
hsMessageBox("This file has bad Python components in it, you REALLY shouldn't save it.",
"Python Component Warning",
hsMBoxOk);
}
}
else if (info->intcode == NOTIFY_SYSTEM_POST_RESET ||
info->intcode == NOTIFY_SYSTEM_POST_NEW)
{
gotBadPython = false;
}
// Have to do this at system shutdown 2 (really final shutdown) because it deletes the
// descriptor, which Max may still try to use in between shutdown 1 and 2.
else if (info->intcode == NOTIFY_SYSTEM_SHUTDOWN2)
{
int count = gAutoUIBlocks.size();
for (int i = 0; i < count; i++)
{
delete gAutoUIBlocks[i];
}
gAutoUIBlocks.clear();
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
class plPythonFileComponentProc : public ParamMap2UserDlgProc
{
public:
plPythonFileComponentProc() : fAutoUI(nil) {}
BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
void DeleteThis() { DestroyAutoUI(); }
protected:
plAutoUIBlock *fAutoUI;
void CreateAutoUI(plAutoUIBlock *autoUI, IParamBlock2 *pb);
void DestroyAutoUI();
};
static plPythonFileComponentProc gPythonFileProc;
ParamBlockDesc2 gPythonFileBlk
(
plComponent::kBlkComp, _T("PythonFile"), 0, &gPythonFileComponentDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,
//rollout
IDD_COMP_PYTHON_FILE, IDS_COMP_PYTHON, 0, 0, &gPythonFileProc,
kPythonFilePB, _T("pb"), TYPE_REFTARG, 0, 0,
end,
kPythonVersion, _T("version"), TYPE_INT, 0, 0,
end,
kPythonFileIsGlobal, _T("isGlobal"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_PYTHON_GLOBAL,
p_default, FALSE,
end,
end
);
#define WM_LOAD_AUTO_UI WM_APP+1
BOOL plPythonFileComponentProc::DlgProc(TimeValue t, IParamMap2 *pmap, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_INITDIALOG:
{
IParamBlock2 *pb = pmap->GetParamBlock();
HWND hCombo = GetDlgItem(hWnd, IDC_PYTHON_FILE);
ComboBox_ResetContent(hCombo);
SetDlgItemText(hWnd, IDC_VER_TEXT, "");
IParamBlock2 *pythonPB = (IParamBlock2*)pb->GetReferenceTarget(kPythonFilePB);
plAutoUIBlock *pythonBlock = FindAutoUI(pythonPB);
int numPythonFiles = gAutoUIBlocks.size();
for (int i = 0; i < numPythonFiles; i++)
{
plAutoUIBlock *block = gAutoUIBlocks[i];
const char *name = block->GetName();
int idx = ComboBox_AddString(hCombo, name);
ComboBox_SetItemData(hCombo, idx, i);
if (block == pythonBlock)
{
ComboBox_SetCurSel(hCombo, idx);
SetDlgItemInt(hWnd, IDC_VER_TEXT, block->GetVersion(), TRUE);
}
}
// Crappy hack, see WM_LOAD_AUTO_UI
PostMessage(hWnd, WM_LOAD_AUTO_UI, 0, 0);
}
return TRUE;
// Crappy hack. If we put up the python file UI before returning from WM_INITDIALOG
// it will show up ABOVE the main UI. To get around this we post a message that won't
// get processed until after the main UI is put up.
case WM_LOAD_AUTO_UI:
{
IParamBlock2 *pb = pmap->GetParamBlock();
IParamBlock2 *pythonPB = (IParamBlock2*)pb->GetReferenceTarget(kPythonFilePB);
plAutoUIBlock *pythonBlock = FindAutoUI(pythonPB);
if (pythonBlock && pythonPB)
CreateAutoUI(pythonBlock, pythonPB);
}
return TRUE;
case WM_COMMAND:
if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_PYTHON_FILE)
{
HWND hCombo = (HWND)lParam;
int sel = ComboBox_GetCurSel(hCombo);
int type = ComboBox_GetItemData(hCombo, sel);
plAutoUIBlock *block = gAutoUIBlocks[type];
IParamBlock2 *autoPB = block->CreatePB();
CreateAutoUI(block, autoPB);
IParamBlock2 *pb = pmap->GetParamBlock();
pb->SetValue(kPythonFilePB, 0, (ReferenceTarget*)autoPB);
SetDlgItemInt(hWnd, IDC_VER_TEXT, block->GetVersion(), TRUE);
return TRUE;
}
break;
}
return FALSE;
}
void plPythonFileComponentProc::CreateAutoUI(plAutoUIBlock *autoUI, IParamBlock2 *pb)
{
DestroyAutoUI();
if (autoUI && pb)
{
fAutoUI = autoUI;
autoUI->CreateAutoRollup(pb);
}
}
void plPythonFileComponentProc::DestroyAutoUI()
{
if (fAutoUI)
{
fAutoUI->DestroyAutoRollup();
fAutoUI = nil;
}
}