/*==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 "plResponderComponentPriv.h" #include "resource.h" #include "plComponent.h" #include "plComponentReg.h" #include "../pnSceneObject/plSceneObject.h" #include "../pnKeyedObject/hsKeyedObject.h" #include "../pnKeyedObject/plKey.h" #include "../pnNetCommon/plSDLTypes.h" #include "../plModifier/plResponderModifier.h" #include "../plModifier/plLogicModifier.h" #include "../plModifier/plAxisAnimModifier.h" #include "../pfConditional/plActivatorConditionalObject.h" #include "../pfConditional/plORConditionalObject.h" #include "../pnMessage/plObjRefMsg.h" #include "../pnMessage/plNotifyMsg.h" #include "hsResMgr.h" #include "../MaxMain/plMaxNode.h" #include "plPickNode.h" #include "../MaxMain/plPlasmaRefMsgs.h" #include "plResponderLink.h" #include "plResponderAnim.h" #include "plResponderMtl.h" #include "plResponderWait.h" #include "../MaxMain/plMaxAccelerators.h" IParamBlock2 *CreateWaitBlk(); int ResponderGetActivatorCount(plComponentBase *comp) { if (comp->ClassID() == RESPONDER_CID) return comp->GetParamBlockByID(plComponentBase::kBlkComp)->Count(kResponderActivators); return -1; } plComponentBase *ResponderGetActivator(plComponentBase *comp, int idx) { if (comp->ClassID() == RESPONDER_CID) { IParamBlock2 *pb = comp->GetParamBlockByID(plComponentBase::kBlkComp); plMaxNode *activatorNode = (plMaxNode*)pb->GetINode(kResponderActivators, 0, idx); return activatorNode->ConvertToComponent(); } return nil; } plKey Responder::GetKey(plComponentBase *comp, plMaxNodeBase *node) { if (comp->ClassID() != RESPONDER_CID) return nil; plResponderComponent *responder = (plResponderComponent*)comp; if (responder->fModKeys.find((plMaxNode*)node) != responder->fModKeys.end()) return responder->fModKeys[(plMaxNode*)node]; return nil; } CLASS_DESC(plResponderComponent, gResponderDesc, "Responder", "Responder", COMP_TYPE_LOGIC, RESPONDER_CID) class plResponderProc; extern plResponderProc gResponderComponentProc; // When one of our parameters that is a ref changes, send out the component ref // changed message. Normally, messages from component refs are ignored since // they pass along all the messages of the ref, which generates a lot of false // converts. class plResponderAccessor : public PBAccessor { public: void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t) { if (id == kResponderActivators || id == kResponderState) { plResponderComponent *comp = (plResponderComponent*)owner; comp->NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED); } } }; static plResponderAccessor gResponderAccessor; ParamBlockDesc2 gResponderBlock ( plComponent::kBlkComp, _T("responderComp"), 0, &gResponderDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp, IDD_COMP_RESPOND, IDS_COMP_RESPOND, 0, 0, &gResponderComponentProc, kResponderState, _T("state"), TYPE_REFTARG_TAB, 0, 0, 0, p_accessor, &gResponderAccessor, end, kResponderStateName, _T("stateName"), TYPE_STRING_TAB, 0, 0, 0, end, kResponderActivators, _T("activators"), TYPE_INODE_TAB, 0, 0, 0, p_ui, TYPE_NODELISTBOX, IDC_LIST_TARGS, 0, 0, IDC_DEL_TARGS, p_accessor, &gResponderAccessor, end, kResponderStateDef, _T("defState"), TYPE_INT, 0, 0, end, kResponderEnabled, _T("enabled"), TYPE_BOOL, 0, 0, p_default, TRUE, p_ui, TYPE_SINGLECHEKBOX, IDC_ENABLED, end, kResponderTrigger, _T("trigger"), TYPE_BOOL, 0, 0, p_default, TRUE, p_ui, TYPE_SINGLECHEKBOX, IDC_CHECK_TRIGGER, end, kResponderUnTrigger, _T("unTrigger"), TYPE_BOOL, 0, 0, p_default, FALSE, p_ui, TYPE_SINGLECHEKBOX, IDC_CHECK_UNTRIGGER, end, kResponderLocalDetect, _T("localDetect"), TYPE_BOOL, 0, 0, p_default, FALSE, p_ui, TYPE_SINGLECHEKBOX, IDC_DETECT_LOCAL_CHECK, end, kResponderSkipFFSound, _T("skipFFSound"), TYPE_BOOL, 0, 0, p_default, FALSE, p_ui, TYPE_SINGLECHEKBOX, IDC_CHECK_SKIPFFSOUND, end, end ); ParamBlockDesc2 gStateBlock ( kResponderStateBlk, _T("responderState"), 0, &gResponderDesc, 0, kStateCmdParams, _T("cmdParam"), TYPE_REFTARG_TAB, 0, 0, 0, end, kStateCmdWait, _T("cmdWait"), TYPE_REFTARG_TAB, 0, 0, 0, end, kStateCmdSwitch, _T("cmdSwitch"), TYPE_INT, 0, 0, end, kStateCmdEnabled, _T("enabled"), TYPE_BOOL_TAB, 0, 0, 0, p_default, TRUE, end, end ); std::vector gResponderCmds; plResponderCmd *plResponderCmd::Find(IParamBlock2 *pb) { if (!pb) return nil; ParamBlockDesc2 *pbDesc = pb->GetDesc(); for (int i = 0; i < gResponderCmds.size(); i++) { if (gResponderCmds[i]->GetDesc() == pbDesc) return gResponderCmds[i]; } return nil; } IParamBlock2* plResponderCmd::CreatePB(int idx) { hsAssert(NumTypes() == 1, "Can't auto-create the pb for a cmd with multiple types"); IParamBlock2 *pb = CreateParameterBlock2(GetDesc(), nil); return pb; } void DummyCodeIncludeFuncResponder() { gResponderCmds.push_back(&(plResponderCmdVisibility::Instance())); gResponderCmds.push_back(&(plResponderCmdLink::Instance())); gResponderCmds.push_back(&(plResponderCmdEnable::Instance())); gResponderCmds.push_back(&(plResponderCmdXRegion::Instance())); gResponderCmds.push_back(&(plResponderCmdOneShot::Instance())); gResponderCmds.push_back(&(plResponderCmdNotify::Instance())); gResponderCmds.push_back(&(plResponderCmdDetectorEnable::Instance())); gResponderCmds.push_back(&(plResponderCmdCamTransition::Instance())); gResponderCmds.push_back(&(plResponderCmdCamForce::Instance())); gResponderCmds.push_back(&(plResponderCmdAnim::Instance())); gResponderCmds.push_back(&(plResponderCmdMtl::Instance())); gResponderCmds.push_back(&(plResponderCmdDelay::Instance())); gResponderCmds.push_back(&(plResponderCmdFootSurface::Instance())); gResponderCmds.push_back(&(plResponderCmdMultistage::Instance())); gResponderCmds.push_back(&(plResponderCmdPhysEnable::Instance())); gResponderCmds.push_back(&(plResponderCmdSubWorld::Instance())); for (int i = 0; i < gResponderCmds.size(); i++) gResponderCmds[i]->GetDesc()->SetClassDesc(&gResponderDesc); ResponderWait::SetDesc(&gResponderDesc); } plResponderComponent::plResponderComponent() { fClassDesc = &gResponderDesc; fClassDesc->MakeAutoParamBlocks(this); } hsBool plResponderComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg) { int numStates = fCompPB->Count(kResponderState); for (int i = 0; i < numStates; i++) { IParamBlock2 *statePB = (IParamBlock2*)fCompPB->GetReferenceTarget(kResponderState, 0, i); for (int j = 0; j < statePB->Count(kStateCmdParams); j++) { IParamBlock2 *cmdPB = (IParamBlock2*)statePB->GetReferenceTarget(kStateCmdParams, 0, j); plResponderCmd *cmd = plResponderCmd::Find(cmdPB); cmd->SetupProperties(node, pErrMsg, cmdPB); } } return true; } hsBool plResponderComponent::PreConvert(plMaxNode *node,plErrorMsg *pErrMsg) { plSceneObject* rObj = node->GetSceneObject(); plLocation loc = node->GetLocation(); // Create and register the RESPONDER's logic component plResponderModifier *responder = TRACKED_NEW plResponderModifier; plKey responderKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), responder, loc); hsgResMgr::ResMgr()->AddViaNotify(responderKey, TRACKED_NEW plObjRefMsg(rObj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef); // Tell all the activators to notify us for (int i = 0; i < fCompPB->Count(kResponderActivators); i++) { plMaxNode *activatorNode = (plMaxNode*)fCompPB->GetINode(kResponderActivators, 0, i); plComponentBase *comp = activatorNode ? activatorNode->ConvertToComponent() : nil; if (comp) { if (fCompPB->GetInt(kResponderLocalDetect)) comp->AddReceiverKey(responderKey, node); else comp->AddReceiverKey(responderKey); } } fModKeys[node] = responderKey; return true; } plResponderModifier* plResponderComponent::IGetResponderMod(plMaxNode* node) { plKey responderKey = fModKeys[node]; return plResponderModifier::ConvertNoRef(responderKey->GetObjectPtr()); } hsBool plResponderComponent::Convert(plMaxNode* node, plErrorMsg* pErrMsg) { IFixOldPB(); // Create the commands for each state int numStates = fCompPB->Count(kResponderState); plResponderModifier *responder = IGetResponderMod(node); responder->fStates.SetCount(numStates); for (int i = 0; i < numStates; i++) { CmdIdxs cmdIdxs; IConvertCmds(node, pErrMsg, i, cmdIdxs); int numCallbacks = 0; ISetupDefaultWait(node, pErrMsg, i, cmdIdxs, numCallbacks); IConvertCmdWaits(node, pErrMsg, i, cmdIdxs, numCallbacks); IParamBlock2 *statePB = (IParamBlock2*)fCompPB->GetReferenceTarget(kResponderState, 0, i); responder->fStates[i].fNumCallbacks = numCallbacks; responder->fStates[i].fSwitchToState = statePB->GetInt(kStateCmdSwitch); } // Set the initial state responder->fCurState = fCompPB->GetInt(kResponderStateDef); responder->fEnabled = fCompPB->GetInt(kResponderEnabled) != 0; if (fCompPB->GetInt(kResponderTrigger)) responder->fFlags |= plResponderModifier::kDetectTrigger; if (fCompPB->GetInt(kResponderUnTrigger)) responder->fFlags |= plResponderModifier::kDetectUnTrigger; if (fCompPB->GetInt(kResponderSkipFFSound)) responder->fFlags |= plResponderModifier::kSkipFFSound; // Unless it's been overridden somewhere else, don't save our state on the server if (!node->GetOverrideHighLevelSDL()) responder->AddToSDLExcludeList(kSDLResponder); return true; } hsBool plResponderComponent::DeInit(plMaxNode *node, plErrorMsg *pErrMsg) { fModKeys.clear(); return true; } void plResponderComponent::IConvertCmds(plMaxNode* node, plErrorMsg* pErrMsg, int state, CmdIdxs& cmdIdxs) { IParamBlock2 *statePB = (IParamBlock2*)fCompPB->GetReferenceTarget(kResponderState, 0, state); plResponderModifier *responder = IGetResponderMod(node); // Add the messages to the logic modifier for (int i = 0; i < statePB->Count(kStateCmdParams); i++) { plMessage *msg = nil; BOOL enabled = statePB->GetInt(kStateCmdEnabled, 0, i); if (!enabled) continue; IParamBlock2 *cmdPB = (IParamBlock2*)statePB->GetReferenceTarget(kStateCmdParams, 0, i); try { plResponderCmd *cmd = plResponderCmd::Find(cmdPB); if (cmd) msg = cmd->CreateMsg(node, pErrMsg, cmdPB); } catch (char *reason) { char buf[512]; char stateName[128]; const char *curStateName = fCompPB->GetStr(kResponderStateName, 0, state); if (curStateName && *curStateName != '\0') strcpy(stateName, fCompPB->GetStr(kResponderStateName, 0, state)); else sprintf(stateName, "State %d", state+1); sprintf(buf, "A responder command failed to export.\n\nResponder:\t%s\nState:\t\t%s\nCommand:\t%d\n\nReason: %s", GetINode()->GetName(), stateName, i+1, reason); pErrMsg->Set(true, "Responder Warning", buf).Show(); pErrMsg->Set(false); } if (msg) { msg->SetSender(responder->GetKey()); responder->AddCommand(msg, state); int idx = responder->fStates[state].fCmds.Count()-1; cmdIdxs[i] = idx; } } } static IParamBlock2 *GetWaitBlk(IParamBlock2 *state, int idx) { return (IParamBlock2*)state->GetReferenceTarget(kStateCmdWait, 0, idx); } void plResponderComponent::ISetupDefaultWait(plMaxNode* node, plErrorMsg* pErrMsg, int state, CmdIdxs& cmdIdxs, int &numCallbacks) { IParamBlock2 *statePB = (IParamBlock2*)fCompPB->GetReferenceTarget(kResponderState, 0, state); plResponderModifier *responder = IGetResponderMod(node); hsTArray& cmds = responder->fStates[state].fCmds; int numCmds = cmds.Count(); for (int i = 0; i < numCmds; i++) { IParamBlock2 *waitPB = GetWaitBlk(statePB, i); ResponderWait::FixupWaitBlock(waitPB); // If we're supposed to wait for this command, and it converted, create a callback if (ResponderWait::GetWaitOnMe(waitPB) && cmdIdxs.find(i) != cmdIdxs.end()) { int convertedIdx = cmdIdxs[i]; ResponderWaitInfo waitInfo; waitInfo.responderName = GetINode()->GetName(); waitInfo.receiver = responder->GetKey(); waitInfo.callbackUser = numCallbacks++; waitInfo.msg = cmds[convertedIdx].fMsg; waitInfo.point = nil; IParamBlock2 *pb = (IParamBlock2*)statePB->GetReferenceTarget(kStateCmdParams, 0, i); plResponderCmd *cmd = plResponderCmd::Find(pb); cmd->CreateWait(node, pErrMsg, pb, waitInfo); } } } void plResponderComponent::IConvertCmdWaits(plMaxNode* node, plErrorMsg* pErrMsg, int state, CmdIdxs& cmdIdxs, int &numCallbacks) { IParamBlock2 *statePB = (IParamBlock2*)fCompPB->GetReferenceTarget(kResponderState, 0, state); plResponderModifier *responder = IGetResponderMod(node); hsTArray& cmds = responder->fStates[state].fCmds; int numWaits = statePB->Count(kStateCmdWait); for (int i = 0; i < numWaits; i++) { IParamBlock2 *waitPB = GetWaitBlk(statePB, i); int wait = ResponderWait::GetWaitingOn(waitPB); // If the waiter and waitee both converted, create the callback if (cmdIdxs.find(wait) != cmdIdxs.end() && cmdIdxs.find(i) != cmdIdxs.end()) { int convertedIdx = cmdIdxs[wait]; ResponderWaitInfo waitInfo; waitInfo.responderName = GetINode()->GetName(); waitInfo.receiver = responder->GetKey(); waitInfo.callbackUser = numCallbacks++; waitInfo.msg = cmds[convertedIdx].fMsg; waitInfo.point = ResponderWait::GetWaitPoint(waitPB); responder->AddCallback(state, convertedIdx, waitInfo.callbackUser); cmds[cmdIdxs[i]].fWaitOn = waitInfo.callbackUser; IParamBlock2 *pb = (IParamBlock2*)statePB->GetReferenceTarget(kStateCmdParams, 0, wait); plResponderCmd *cmd = plResponderCmd::Find(pb); cmd->CreateWait(node, pErrMsg, pb, waitInfo); } } } void plResponderComponent::IFixOldPB() { if (fCompPB) { if (fCompPB->Count(kResponderState) == 0) { IParamBlock2 *pb = CreateParameterBlock2(&gStateBlock, nil); int idx = fCompPB->Append(kResponderState, 1, (ReferenceTarget**)&pb); pb->SetValue(kStateCmdSwitch, 0, idx); } if (fCompPB->Count(kResponderStateName) == 0) { char *name = ""; fCompPB->Append(kResponderStateName, 1, &name); } // Make sure there is an enabled value for each command in the state for (int i = 0; i < fCompPB->Count(kResponderState); i++) { IParamBlock2* pb = (IParamBlock2*)fCompPB->GetReferenceTarget(kResponderState, 0, i); if (pb->Count(kStateCmdEnabled) != pb->Count(kStateCmdParams)) pb->SetCount(kStateCmdEnabled, pb->Count(kStateCmdParams)); } } } //////////////////////////////////////////////////////////////////////////////// #define CUSTOM_DRAW enum { kStateName, kStateAdd, kStateRemove, kStateDefault, kStateCopy, }; class plResponderProc : public ParamMap2UserDlgProc { protected: HWND fhDlg; IParamBlock2 *fPB; IParamBlock2 *fStatePB; int fCurState; plResponderComponent *fComp; IParamMap2 *fCmdMap; IParamMap2 *fWaitMap; int fCmdIdx; typedef std::map NameID; NameID fNames; HMENU fhMenu; typedef std::pair CmdID; typedef std::map MenuCmd; MenuCmd fMenuCmds; HWND fhList; bool fIgnoreNextDrop; public: plResponderProc(); BOOL DlgProc(TimeValue t, IParamMap2 *pm, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); void DeleteThis() { IRemoveCmdRollups(); } protected: void ICreateMenu(); void IAddMenuItem(HMENU hMenu, int id); void ICmdRightClick(HWND hCmdList); // Add and remove command rollups void ICreateCmdRollups(); void IRemoveCmdRollups(); IParamMap2 *ICreateMap(IParamBlock2 *pb); // Helper const char* GetCommandName(int cmdIdx); void LoadList(); BOOL DragListProc(HWND hWnd, DRAGLISTINFO *info); #ifdef CUSTOM_DRAW void IDrawComboItem(DRAWITEMSTRUCT *dis); #endif void LoadState(); void AddCommand(); void RemoveCurCommand(); void MoveCommand(int oldIdx, int newIdx); // Takes a freshly created state PB and adds it as a new state, then returns its index int AddState(IParamBlock2 *pb); }; static plResponderProc gResponderComponentProc; void plResponderProc::IAddMenuItem(HMENU hMenu, int id) { AppendMenu(hMenu, MF_STRING, id+1, fNames[id]); } #include "hsSTLSortUtils.h" void plResponderProc::ICreateMenu() { fhMenu = CreatePopupMenu(); std::map menus; int cmdID = 0; for (int i = 0; i < gResponderCmds.size(); i++) { plResponderCmd *cmd = gResponderCmds[i]; for (int j = 0; j < cmd->NumTypes(); j++) { HMENU hParent = fhMenu; const char *category = cmd->GetCategory(j); if (category) { // Menu for this category hasn't been created yet, make one if (menus.find(category) == menus.end()) { hParent = CreatePopupMenu(); menus[category] = hParent; InsertMenu(fhMenu, 0, MF_BYPOSITION | MF_POPUP, (UINT)hParent, category); } else hParent = menus[category]; } const char *name = cmd->GetName(j); cmdID++; fMenuCmds[cmdID] = CmdID(cmd, j); AppendMenu(hParent, MF_STRING, cmdID, name); } } } plResponderProc::plResponderProc() : fCmdMap(nil), fCmdIdx(-1), fCurState(0), fhMenu(nil), fIgnoreNextDrop(false) { } const char* plResponderProc::GetCommandName(int cmdIdx) { static char buf[256]; if (fStatePB->Count(kStateCmdParams) > cmdIdx) { buf[0] = '\0'; BOOL enabled = fStatePB->GetInt(kStateCmdEnabled, 0, cmdIdx); if (!enabled) strcat(buf, "[D]"); IParamBlock2 *cmdPB = (IParamBlock2*)fStatePB->GetReferenceTarget(kStateCmdParams, 0, cmdIdx); plResponderCmd *cmd = plResponderCmd::Find(cmdPB); IParamBlock2 *waitPB = (IParamBlock2*)fStatePB->GetReferenceTarget(kStateCmdWait, 0, cmdIdx); int waitingOn = ResponderWait::GetWaitingOn(waitPB); if (waitingOn != -1) { char num[10]; sprintf(num, "(%d)", waitingOn+1); strcat(buf, num); } strcat(buf, cmd->GetInstanceName(cmdPB)); return buf; } hsAssert(0, "Bad index to GetCommandName"); return nil; } void plResponderProc::LoadList() { ListBox_ResetContent(fhList); for (int i = 0; i < fStatePB->Count(kStateCmdParams); i++) { const char* name = GetCommandName(i); ListBox_AddString(fhList, name); } ListBox_SetCurSel(fhList, -1); } void plResponderProc::AddCommand() { RECT rect; GetWindowRect(GetDlgItem(fhDlg, IDC_ADD_CMD), &rect); // Create the popup menu and get the option the user selects SetForegroundWindow(fhDlg); int type = TrackPopupMenu(fhMenu, TPM_RIGHTALIGN | TPM_NONOTIFY | TPM_RETURNCMD, rect.left, rect.top, 0, fhDlg, NULL); PostMessage(fhDlg, WM_USER, 0, 0); if (type == 0) return; CmdID& cmdID = fMenuCmds[type]; plResponderCmd *cmd = cmdID.first; int cmdIdx = cmdID.second; IParamBlock2 *cmdPB = cmd->CreatePB(cmdIdx); fStatePB->Append(kStateCmdParams, 1, (ReferenceTarget**)&cmdPB); IParamBlock2 *waitPB = ResponderWait::CreatePB(); fStatePB->Append(kStateCmdWait, 1, (ReferenceTarget**)&waitPB); BOOL enabled = TRUE; fStatePB->Append(kStateCmdEnabled, 1, &enabled); const char* name = GetCommandName(fStatePB->Count(kStateCmdParams)-1); int idx = ListBox_AddString(fhList, name); ListBox_SetCurSel(fhList, idx); ICreateCmdRollups(); } void plResponderProc::RemoveCurCommand() { int idx = ListBox_GetCurSel(fhList); if (idx == LB_ERR) return; // Destroy the current rollup, since it's this guy IRemoveCmdRollups(); // Delete all traces of this command fStatePB->Delete(kStateCmdParams, idx, 1); fStatePB->Delete(kStateCmdWait, idx, 1); fStatePB->Delete(kStateCmdEnabled, idx, 1); ListBox_DeleteString(fhList, idx); // Patch the wait commands ResponderWait::CmdRemoved(fStatePB, idx); fCmdIdx = -1; } void plResponderProc::IRemoveCmdRollups() { if (fCmdMap) { DestroyCPParamMap2(fCmdMap); fCmdMap = nil; } if (fWaitMap) { DestroyCPParamMap2(fWaitMap); fWaitMap = nil; } } IParamMap2 *plResponderProc::ICreateMap(IParamBlock2 *pb) { ParamBlockDesc2 *pd = pb->GetDesc(); // Don't show anything if there isn't a UI if (pd->Count() < 1) { pb->ReleaseDesc(); return nil; } // Create the rollout IParamMap2 *map = CreateCPParamMap2(0, pb, GetCOREInterface(), hInstance, MAKEINTRESOURCE(pd->dlg_template), GetString(pd->title), pd->flags, pd->dlgProc, NULL, ROLLUP_CAT_STANDARD); // Save the rollout in the paramblock pb->SetMap(map); pb->ReleaseDesc(); return map; } void plResponderProc::ICreateCmdRollups() { // Get the index of the current command HWND hCmds = GetDlgItem(fhDlg, IDC_CMD_LIST); int cmdIdx = ListBox_GetCurSel(hCmds); if (cmdIdx != LB_ERR && cmdIdx != fCmdIdx) { fCmdIdx = cmdIdx; fIgnoreNextDrop = true; // Save the current scroll position and reset it at the end, so the panels // won't always jerk back up to the top IRollupWindow *rollup = GetCOREInterface()->GetCommandPanelRollup(); int scrollPos = rollup->GetScrollPos(); // Destroy the last command's rollups IRemoveCmdRollups(); // Create the rollup for the current command IParamBlock2 *pb = (IParamBlock2*)fStatePB->GetReferenceTarget(kStateCmdParams, 0, fCmdIdx); fCmdMap = ICreateMap(pb); ResponderWait::InitDlg(fStatePB, fCmdIdx, GetDlgItem(fhDlg, IDC_CMD_LIST)); pb = (IParamBlock2*)fStatePB->GetReferenceTarget(kStateCmdWait, 0, fCmdIdx); fWaitMap = ICreateMap(pb); rollup->SetScrollPos(scrollPos); } } BOOL plResponderProc::DragListProc(HWND hWnd, DRAGLISTINFO *info) { static int oldIdx = -1; int curIdx = LBItemFromPt(info->hWnd, info->ptCursor, TRUE); switch (info->uNotification) { // Allow the drag case DL_BEGINDRAG: // When you click on an item in the listbox, the rollups are changed and Max can // shift the position of dialog you were just clicking in. Since this happens // before you let go of the mouse button, the listbox thinks you are dragging. // To get around it, we don't allow a selection change and a drag in the same click. if (fIgnoreNextDrop) { SetWindowLong(hWnd, DWL_MSGRESULT, FALSE); } else { oldIdx = curIdx; SetWindowLong(hWnd, DWL_MSGRESULT, TRUE); } return TRUE; case DL_DRAGGING: { if (curIdx < oldIdx) DrawInsert(hWnd, info->hWnd, curIdx); else if (curIdx > oldIdx && ListBox_GetCount(info->hWnd) > curIdx+1) DrawInsert(hWnd, info->hWnd, curIdx+1); else DrawInsert(hWnd, info->hWnd, -1); } return TRUE; case DL_CANCELDRAG: // Clear drag arrow DrawInsert(hWnd, info->hWnd, -1); return TRUE; case DL_DROPPED: { if (fIgnoreNextDrop) { fIgnoreNextDrop = false; return TRUE; } // Clear drag arrow DrawInsert(hWnd, info->hWnd, -1); if (curIdx != -1 && oldIdx != -1 && curIdx != oldIdx) { // Make sure this won't mess up any wait commands, or at least // that the user approves if it does. if (!ResponderWait::ValidateCmdMove(fStatePB, oldIdx, curIdx)) return TRUE; MoveCommand(oldIdx, curIdx); } return TRUE; } } return FALSE; } #ifdef CUSTOM_DRAW void plResponderProc::IDrawComboItem(DRAWITEMSTRUCT *dis) { if (dis->itemID == -1) // empty item return; // The colors depend on whether the item is selected. COLORREF clrForeground = SetTextColor(dis->hDC, GetSysColor(dis->itemState & ODS_SELECTED ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); COLORREF clrBackground = SetBkColor(dis->hDC, GetSysColor(dis->itemState & ODS_SELECTED ? COLOR_HIGHLIGHT : COLOR_WINDOW)); // Calculate the vertical and horizontal position. TEXTMETRIC tm; GetTextMetrics(dis->hDC, &tm); int y = (dis->rcItem.bottom + dis->rcItem.top - tm.tmHeight) / 2; int x = LOWORD(GetDialogBaseUnits()) / 4; // If this is a command, not a state, make it bold HFONT oldFont = nil; if (dis->itemData != kStateName) { LOGFONT lf; memset(&lf, 0, sizeof(lf)); lf.lfHeight = tm.tmHeight; lf.lfWeight = FW_BOLD; GetTextFace(dis->hDC, LF_FACESIZE, lf.lfFaceName); HFONT boldFont = CreateFontIndirect(&lf); oldFont = SelectFont(dis->hDC, boldFont); } // Get and display the text for the list item. char buf[256]; ComboBox_GetLBText(dis->hwndItem, dis->itemID, buf); if (fPB->GetInt(kResponderStateDef) == dis->itemID) { char buf2[256]; sprintf(buf2, "* %s", buf); strcpy(buf, buf2); } ExtTextOut(dis->hDC, x, y, ETO_CLIPPED | ETO_OPAQUE, &dis->rcItem, buf, strlen(buf), NULL); // Restore the previous colors. SetTextColor(dis->hDC, clrForeground); SetBkColor(dis->hDC, clrBackground); if (oldFont) DeleteFont(SelectFont(dis->hDC, oldFont)); // If the item has the focus, draw focus rectangle. if (dis->itemState & ODS_FOCUS) DrawFocusRect(dis->hDC, &dis->rcItem); } #endif void plResponderProc::LoadState() { fStatePB = (IParamBlock2*)fPB->GetReferenceTarget(kResponderState, 0, fCurState); IRemoveCmdRollups(); LoadList(); HWND hSwitchCombo = GetDlgItem(fhDlg, IDC_SWITCH_COMBO); ComboBox_SetCurSel(hSwitchCombo, fStatePB->GetInt(kStateCmdSwitch)); } // THE MAGICAL TURDFEST. Max's default RemapDir tries to always clone your referenced // objects. So when we reference an INode it wants to make a clone of that INode // (and fails for some reason). To get around this I just check if the object to be cloned // is a paramblock, and actually clone it if it is. Otherwise, I just return the object. // // UPDATE: Looks like it's only with ResponderComponents. Probably the parentless PB's (which // exist due to another bug). Who cares, this works. class MyRemapDir : public RemapDir { public: RefTargetHandle CloneRef(RefTargetHandle oldTarg) { if (oldTarg == NULL) return NULL; else if (oldTarg->SuperClassID() == PARAMETER_BLOCK2_CLASS_ID) return oldTarg->Clone(*this); else return oldTarg; } void PatchPointer(RefTargetHandle* patchThis, RefTargetHandle oldTarg) { hsAssert(0, "shit"); } RefTargetHandle FindMapping(RefTargetHandle from) { hsAssert(0, "shit"); return NULL; } void AddEntry(RefTargetHandle hfrom, RefTargetHandle hto) { hsAssert(0, "shit"); } void Backpatch() { hsAssert(0, "shit"); } void Clear() { hsAssert(0, "shit"); } void DeleteThis() { hsAssert(0, "shit"); } void AddPostPatchProc(PostPatchProc* proc, bool toDelete) { hsAssert(0, "shit"); } }; // Even turdier - I had to define this to compile RefTargetHandle RemapDir::CloneRef(RefTargetHandle oldTarg) { return NULL; } static MyRemapDir gMyRemapDir; RefTargetHandle plResponderComponent::Clone(RemapDir &remap) { plComponentBase *obj = (plComponentBase*)fClassDesc->Create(false); // Do the base clone BaseClone(this, obj, remap); // Copy our references obj->ReplaceReference(kRefComp, fCompPB->Clone(gMyRemapDir)); obj->ReplaceReference(kRefTargs, fTargsPB->Clone(remap)); return obj; } BOOL plResponderProc::DlgProc(TimeValue t, IParamMap2 *pm, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { static UINT dragListMsg = 0; if (dragListMsg != 0 && msg == dragListMsg) if (DragListProc(hWnd, (DRAGLISTINFO*)lParam)) return TRUE; switch (msg) { case WM_INITDIALOG: { if (!fhMenu) ICreateMenu(); fhDlg = hWnd; fhList = GetDlgItem(fhDlg, IDC_CMD_LIST); fCurState = 0; fCmdIdx = -1; fPB = pm->GetParamBlock(); fComp = (plResponderComponent*)fPB->GetOwner(); fComp->IFixOldPB(); LoadState(); // Make it so the user can drag commands to different positions dragListMsg = RegisterWindowMessage(DRAGLISTMSGSTRING); MakeDragList(GetDlgItem(hWnd, IDC_CMD_LIST)); // Setup the State Name combo HWND hStateName = GetDlgItem(hWnd, IDC_STATE_COMBO); ComboBox_LimitText(hStateName, 256); // I give up, Windows doesn't want to tell me the real font size #if 0//def CUSTOM_DRAW // TEMP HDC hDC = GetDC(hStateName); HFONT sysFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); HFONT oldFont = SelectFont(hDC, sysFont); TEXTMETRIC tm; GetTextMetrics(hDC, &tm); ComboBox_SetItemHeight(hStateName, 0, tm.tmHeight+2); DeleteFont(SelectFont(hDC, oldFont)); ReleaseDC(hStateName, hDC); #endif // Add the commands int idx = ComboBox_AddString(hStateName, "Add State"); ComboBox_SetItemData(hStateName, idx, kStateAdd); idx = ComboBox_AddString(hStateName, "Remove Current State"); ComboBox_SetItemData(hStateName, idx, kStateRemove); idx = ComboBox_AddString(hStateName, "Set Current as Default"); ComboBox_SetItemData(hStateName, idx, kStateDefault); idx = ComboBox_AddString(hStateName, "Copy Current State"); ComboBox_SetItemData(hStateName, idx, kStateCopy); HWND hSwitchCombo = GetDlgItem(hWnd, IDC_SWITCH_COMBO); int numStates = fPB->Count(kResponderStateName); for (int i = 0; i < numStates; i++) { const char *stateName = fPB->GetStr(kResponderStateName, 0, i); char buf[128]; if (!stateName || *stateName == '\0') { sprintf(buf, "State %d", i+1); stateName = buf; } ComboBox_InsertString(hStateName, i, stateName); ComboBox_AddString(hSwitchCombo, stateName); } ComboBox_SetCurSel(hStateName, fCurState); ComboBox_SetCurSel(hSwitchCombo, fStatePB->GetInt(kStateCmdSwitch)); } return TRUE; #ifdef CUSTOM_DRAW case WM_DRAWITEM: if (wParam == IDC_STATE_COMBO) { IDrawComboItem((DRAWITEMSTRUCT*)lParam); return TRUE; } break; #endif case WM_SETCURSOR: { if (HIWORD(lParam) == WM_RBUTTONDOWN && HWND(wParam) == GetDlgItem(hWnd, IDC_CMD_LIST)) { ICmdRightClick(HWND(wParam)); return TRUE; } } break; case WM_COMMAND: if (HIWORD(wParam) == BN_CLICKED) { if (LOWORD(wParam) == IDC_ADD_ACTIVATOR) { // Adding an activator. Set it and refresh the UI to show it in our list. plPick::Activator(fPB, kResponderActivators, false); pm->Invalidate(kResponderActivators); return TRUE; } else if (LOWORD(wParam) == IDC_ADD_CMD) { AddCommand(); return TRUE; } // Remove the currently selected condition else if (LOWORD(wParam) == IDC_REMOVE_CMD) { RemoveCurCommand(); return TRUE; } } else if (HIWORD(wParam) == LBN_SELCHANGE && LOWORD(wParam) == IDC_CMD_LIST) { ICreateCmdRollups(); return TRUE; } else if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_SWITCH_COMBO) { int sel = ComboBox_GetCurSel((HWND)lParam); if (sel != CB_ERR) fStatePB->SetValue(kStateCmdSwitch, 0, sel); } else if (LOWORD(wParam) == IDC_STATE_COMBO) { HWND hCombo = (HWND)lParam; int code = HIWORD(wParam); // Disable accelerators when the combo has focus, so that new names can be typed in if (code == CBN_SETFOCUS) { plMaxAccelerators::Disable(); return TRUE; } else if (code == CBN_KILLFOCUS) { plMaxAccelerators::Enable(); return TRUE; } // State name changed, save it in the PB else if (code == CBN_EDITCHANGE) { char buf[256]; ComboBox_GetText(hCombo, buf, sizeof(buf)); const char *curName = fPB->GetStr(kResponderStateName, 0, fCurState); if (!curName || strcmp(buf, curName)) { HWND hSwitch = GetDlgItem(hWnd, IDC_SWITCH_COMBO); int sel = ComboBox_GetCurSel(hSwitch); ComboBox_DeleteString(hSwitch, fCurState); ComboBox_InsertString(hSwitch, fCurState, buf); ComboBox_SetCurSel(hSwitch, sel); fPB->SetValue(kResponderStateName, 0, buf, fCurState); ComboBox_DeleteString(hCombo, fCurState); ComboBox_InsertString(hCombo, fCurState, buf); // ComboBox_SetCurSel(hCombo, fCurState); } return TRUE; } else if (code == CBN_SELCHANGE) { int sel = ComboBox_GetCurSel(hCombo); int type = ComboBox_GetItemData(hCombo, sel); if (type == kStateAdd) { IParamBlock2 *pb = CreateParameterBlock2(&gStateBlock, nil); fCurState = AddState(pb); fCmdIdx = -1; } else if (type == kStateRemove) { int count = fPB->Count(kResponderState); // Don't let the user remove the last state if (count == 1) { hsMessageBox("You must have at least one state.", "Error", hsMessageBoxNormal); ComboBox_SetCurSel(hCombo, fCurState); return TRUE; } // Verify that the user really wants to delete the state else { int ret = hsMessageBox("Are you sure you want to remove this state?", "Verify Remove", hsMessageBoxYesNo); if (ret == hsMBoxNo) { ComboBox_SetCurSel(hCombo, fCurState); return TRUE; } } fPB->Delete(kResponderState, fCurState, 1); fPB->Delete(kResponderStateName, fCurState, 1); ComboBox_DeleteString(hCombo, fCurState); ComboBox_SetCurSel(hCombo, 0); HWND hSwitch = GetDlgItem(hWnd, IDC_SWITCH_COMBO); ComboBox_DeleteString(hSwitch, fCurState); // If the deleted state was the default, set the default to the first int defState = fPB->GetInt(kResponderStateDef); if (fCurState == defState) fPB->SetValue(kResponderStateDef, 0, 0); else if (fCurState < defState) fPB->SetValue(kResponderStateDef, 0, defState-1); // Patch up the switch commands for (int i = fCurState; i < fPB->Count(kResponderState); i++) { IParamBlock2 *pb = (IParamBlock2*)fPB->GetReferenceTarget(kResponderState, 0, i); int switchState = pb->GetInt(kStateCmdSwitch); // TODO: might want to warn about this if (switchState == fCurState) pb->SetValue(kStateCmdSwitch, 0, 0); else if (switchState > fCurState) pb->SetValue(kStateCmdSwitch, 0, switchState-1); } fCurState = 0; fCmdIdx = -1; } else if (type == kStateDefault) { // Set the current state as the default fPB->SetValue(kResponderStateDef, 0, fCurState); ComboBox_SetCurSel(hCombo, fCurState); } else if (type == kStateCopy) { // Clone the state PB IParamBlock2 *origPB = (IParamBlock2*)fPB->GetReferenceTarget(kResponderState, 0, fCurState); IParamBlock2 *copyPB = (IParamBlock2*)origPB->Clone(gMyRemapDir); fCurState = AddState(copyPB); fCmdIdx = -1; } else { fCurState = sel; fCmdIdx = -1; } LoadState(); return TRUE; } } } return FALSE; } void plResponderProc::ICmdRightClick(HWND hCmdList) { // Get the position of the cursor in screen and tree client coords POINT point, localPoint; GetCursorPos(&point); localPoint = point; ScreenToClient(hCmdList, &localPoint); LRESULT res = SendMessage(hCmdList, LB_ITEMFROMPOINT, 0, MAKELPARAM(localPoint.x, localPoint.y)); WORD index = LOWORD(res); if (index == WORD(LB_ERR)) return; RECT rect; SendMessage(hCmdList, LB_GETITEMRECT, index, (LPARAM)&rect); // Make sure we're actually ON an item, LB_ITEMFROMPOINT get the closest instead of exact if (localPoint.y >= rect.top && localPoint.y <= rect.bottom) { BOOL enabled = fStatePB->GetInt(kStateCmdEnabled, 0, index); HMENU hMenu = CreatePopupMenu(); AppendMenu(hMenu, MF_STRING, 1, enabled ? "Disable" : "Enable"); SetForegroundWindow(fhDlg); int sel = TrackPopupMenu(hMenu, TPM_NONOTIFY | TPM_RETURNCMD, point.x, point.y, 0, fhDlg, NULL); if (sel == 1) { fStatePB->SetValue(kStateCmdEnabled, 0, !enabled, index); ListBox_DeleteString(hCmdList, index); ListBox_InsertString(hCmdList, index, GetCommandName(index)); } DestroyMenu(hMenu); } } int plResponderProc::AddState(IParamBlock2 *pb) { int idx = fPB->Append(kResponderState, 1, (ReferenceTarget**)&pb); pb->SetValue(kStateCmdSwitch, 0, idx); char *name = ""; fPB->Append(kResponderStateName, 1, &name); HWND hCombo = GetDlgItem(fhDlg, IDC_STATE_COMBO); char buf[128]; sprintf(buf, "State %d", idx+1); ComboBox_InsertString(hCombo, idx, buf); ComboBox_SetCurSel(hCombo, idx); HWND hSwitch = GetDlgItem(fhDlg, IDC_SWITCH_COMBO); ComboBox_AddString(hSwitch, buf); return idx; } void plResponderProc::MoveCommand(int oldIdx, int newIdx) { // Move data int insertIdx = (newIdx > oldIdx) ? newIdx+1 : newIdx; int deleteIdx = (newIdx < oldIdx) ? oldIdx+1 : oldIdx; ReferenceTarget *targ = fStatePB->GetReferenceTarget(kStateCmdParams, 0, oldIdx); fStatePB->Insert(kStateCmdParams, insertIdx, 1, &targ); fStatePB->Delete(kStateCmdParams, deleteIdx, 1); ReferenceTarget *wait = fStatePB->GetReferenceTarget(kStateCmdWait, 0, oldIdx); fStatePB->Insert(kStateCmdWait, insertIdx, 1, &wait); fStatePB->Delete(kStateCmdWait, deleteIdx, 1); BOOL oldEnabled = fStatePB->GetInt(kStateCmdEnabled, 0, oldIdx); BOOL newEnabled = fStatePB->GetInt(kStateCmdEnabled, 0, newIdx); fStatePB->SetValue(kStateCmdEnabled, 0, oldEnabled, newIdx); fStatePB->SetValue(kStateCmdEnabled, 0, newEnabled, oldIdx); ResponderWait::CmdMoved(fStatePB, oldIdx, newIdx); LoadList(); // Reselect item // (This doesn't send the LBN_SELCHANGE message so we do that manually) ListBox_SetCurSel(fhList, newIdx); ICreateCmdRollups(); }