/*==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 . 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 #include #include #include #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 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 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 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::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; iGetGroup(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; jNumTargets(); 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; jNumTargets(); 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; jNumTargets(); 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 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; } }