/*==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 "plVolumeGadgetComponent.h"
#include "resource.h"
#include "plComponentReg.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnKeyedObject/hsKeyedObject.h"
#include "pnKeyedObject/plKey.h"
#include "plPhysical/plCollisionDetector.h" // MM
#include "plModifier/plLogicModifier.h"
#include "pnModifier/plConditionalObject.h"
#include "plPhysical/plPickingDetector.h"
#include "pfConditional/plActivatorConditionalObject.h"
#include "pfConditional/plFacingConditionalObject.h"
#include "pfConditional/plObjectInBoxConditionalObject.h"
#include "pnMessage/plObjRefMsg.h"
#include "pnMessage/plNotifyMsg.h"
#include "pnMessage/plCursorChangeMsg.h"
#include "hsResMgr.h"
#include "MaxMain/plMaxNode.h"
#include "MaxConvert/plConvert.h"
#include "plResponderComponent.h"
// Physics Dependencies below
#include "MaxConvert/hsConverterUtils.h" //Conversion Dependencies
#include "plPhysicalComponents.h"
#include "pnMessage/plIntRefMsg.h"
#include "plComponentProcBase.h"
#include "MaxMain/plPhysicalProps.h"
#include "plPhysical/plSimDefs.h"
void DummyCodeIncludeFuncVolumeGadget() {}
CLASS_DESC(plVolumeGadgetComponent, gVolumeGadgetDesc, "Region Sensor", "RegionSensor", COMP_TYPE_DETECTOR, VOLUMEGADGET_CID)
enum
{
kVolumeGadgetInside_DEAD, // removed
kVolumeGadgetEnter,
kVolumeGadgetExit,
kVolumeUnEnter,
kVolumeUnExit,
kVolumeOneShot,
kVolumeEnterType,
kVolumeExitType,
kVolumeExitNum,
kVolumeEnterNum,
kUseVolumeNode,
kVolumeNode,
kVolumeBoundsType,
kVolumeReportChoice_DEAD,
kVolumeReportBoolTab_DEAD,
kVolumeEnabled,
kVolumeReportGroups_DEAD,
kVolumeDirectional,
kVolumeDegrees,
kVolumeTriggerOnFacing,
kVolumeWalkingForward,
kVolumeReportOn,
kSkipServerArbitration,
};
enum
{
kVolumeMain,
kVolumeReport,
};
enum
{
kEnterTypeEach,
kEnterTypeCount,
};
enum
{
kExitTypeEach,
kExitTypeFirst,
kExitTypeCount,
};
class VolumeDlgProc : public ParamMap2UserDlgProc
{
protected:
// Because there is no p_disable_ctrls
void IEnable(HWND hWnd, bool value)
{
#define Enable_Item(id, val) EnableWindow(GetDlgItem(hWnd, id), val)
Enable_Item(IDC_COMP_PHYSGADGET_ENTERBOX, !value);
}
public:
BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg == WM_COMMAND && HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_TRIGGER_ON_FACING_CHECK)
IEnable(hWnd, Button_GetCheck((HWND)lParam) == BST_CHECKED);
else if (msg == WM_INITDIALOG)
IEnable(hWnd, (map->GetParamBlock()->GetInt(kVolumeTriggerOnFacing) != 0));
return FALSE;
}
void DeleteThis() {}
};
static VolumeDlgProc gVolumeDlgProc;
ParamBlockDesc2 gVolumeGadgetBlock
(
plComponent::kBlkComp, _T("RegionGadgetComp"), 0, &gVolumeGadgetDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP, plComponent::kRefComp,
1,
kVolumeMain, IDD_COMP_DETECTOR_REGION, IDS_COMP_DETECTOR_REGION, 0, 0, &gVolumeDlgProc,
kVolumeGadgetEnter, _T("Enter"), TYPE_BOOL, 0, 0,
p_ui, kVolumeMain, TYPE_SINGLECHEKBOX, IDC_COMP_PHYSGADGET_ENTERBOX,
p_enable_ctrls, 2, kVolumeEnterType, kVolumeEnterNum,
end,
kVolumeGadgetExit, _T("Exit"), TYPE_BOOL, 0, 0,
p_ui, kVolumeMain, TYPE_SINGLECHEKBOX, IDC_COMP_PHYSGADGET_EXITBOX,
p_enable_ctrls, 2, kVolumeExitType, kVolumeExitNum,
end,
kVolumeTriggerOnFacing, _T("triggerOnFacing"), TYPE_BOOL, 0, 0,
p_ui, kVolumeMain, TYPE_SINGLECHEKBOX, IDC_TRIGGER_ON_FACING_CHECK,
p_enable_ctrls, 2, kVolumeDegrees, kVolumeWalkingForward,
end,
kVolumeOneShot, _T("oneshot"), TYPE_BOOL, 0, 0,
p_ui, kVolumeMain, TYPE_SINGLECHEKBOX, IDC_ONESHOT,
end,
kVolumeEnterType, _T("enterType"), TYPE_INT, 0, 0,
p_ui, kVolumeMain, TYPE_RADIO, 2, IDC_RADIO_EACHENTRY, IDC_RADIO_ENTRYCOUNT,
p_vals, kEnterTypeEach, kEnterTypeCount,
end,
kVolumeExitType, _T("exitType"), TYPE_INT, 0, 0,
p_ui, kVolumeMain, TYPE_RADIO, 3, IDC_RADIO_EACHEXIT, IDC_RADIO_FIRSTEXIT, IDC_RADIO_EXITCOUNT,
p_vals, kExitTypeEach, kExitTypeFirst, kExitTypeCount,
end,
kVolumeExitNum, _T("exitNum"), TYPE_INT, P_ANIMATABLE, 0,
p_range, 0, 100,
p_default, 1,
p_ui, kVolumeMain, TYPE_SPINNER, EDITTYPE_INT,
IDC_CAMERACMD_OFFSETX3, IDC_CAMERACMD_SPIN_OFFSETX3, SPIN_AUTOSCALE,
end,
kVolumeEnterNum, _T("nterNum"), TYPE_INT, P_ANIMATABLE, 0,
p_range, 0, 100,
p_default, 1,
p_ui, kVolumeMain, TYPE_SPINNER, EDITTYPE_INT,
IDC_CAMERACMD_OFFSETX2, IDC_CAMERACMD_SPIN_OFFSETX2, SPIN_AUTOSCALE,
end,
kUseVolumeNode, _T("UseVolumeNode"), TYPE_BOOL, 0, 0,
p_ui, kVolumeMain, TYPE_SINGLECHEKBOX, IDC_COMP_PHYS_CUSTOMCHK,
end,
kVolumeNode, _T("UserBoundChoice"), TYPE_INODE, 0, 0,
p_ui, kVolumeMain, TYPE_PICKNODEBUTTON, IDC_COMP_PHYS_PICKSTATE_DETECTOR,
p_sclassID, GEOMOBJECT_CLASS_ID,
p_prompt, IDS_COMP_PHYS_CHOSEN_SIMP,
//p_accessor, &gPhysCoreAccessor,
end,
kVolumeBoundsType, _T("BoundingConditions"), TYPE_INT, 0, 0,
p_ui, kVolumeMain, TYPE_RADIO, 4, IDC_RADIO_BSPHERE2, IDC_RADIO_BBOX, IDC_RADIO_BHULL, IDC_RADIO_PICKSTATE,
p_vals, plSimDefs::kSphereBounds, plSimDefs::kBoxBounds, plSimDefs::kHullBounds, plSimDefs::kProxyBounds,
p_default, plSimDefs::kHullBounds,
end,
kVolumeReportGroups_DEAD, _T("reportGroups"), TYPE_INT, 0,0,
end,
kVolumeEnabled, _T("enabled"), TYPE_BOOL, 0, 0,
p_ui, kVolumeMain, TYPE_SINGLECHEKBOX, IDC_ENABLED,
p_default, TRUE,
end,
kVolumeDegrees, _T("degrees"), TYPE_INT, 0, 0,
p_range, 1, 180,
p_default, 45,
p_ui, kVolumeMain, TYPE_SPINNER, EDITTYPE_POS_INT,
IDC_COMP_CLICK_DEG, IDC_COMP_CLICK_DEGSPIN, SPIN_AUTOSCALE,
end,
kVolumeWalkingForward, _T("walkingForward"), TYPE_BOOL, 0, 0,
p_ui, kVolumeMain, TYPE_SINGLECHEKBOX, IDC_WALKING_FORWARD_CHECK,
end,
kVolumeReportOn, _T("reportOn"), TYPE_INT, 0, 0,
p_ui, kVolumeMain, TYPE_RADIO, 3, IDC_RADIO_REPORT_AVATAR, IDC_RADIO_REPORT_DYN, IDC_RADIO_REPORT_BOTH,
p_vals, 1<MakeAutoParamBlocks(this);
}
plKey plVolumeGadgetComponent::GetLogicOutKey(plMaxNode* node)
{
LogicKeys::const_iterator it = fLogicModOutKeys.find(node);
if (it != fLogicModOutKeys.end())
return it->second;
return nil;
}
void plVolumeGadgetComponent::CollectNonDrawables(INodeTab& nonDrawables)
{
if(fCompPB->GetInt(kUseVolumeNode))
{
INode* boundsNode = fCompPB->GetINode(kVolumeNode);
if( boundsNode )
nonDrawables.Append(1, &boundsNode);
}
AddTargetsToList(nonDrawables);
}
// Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plVolumeGadgetComponent::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg)
{
fLogicModKeys.clear();
fLogicModOutKeys.clear();
node->SetForceLocal(true);
node->SetDrawable(false);
plPhysicalProps *physProps = node->GetPhysicalProps();
physProps->SetBoundsType(fCompPB->GetInt(kVolumeBoundsType), node, pErrMsg);
if(fCompPB->GetInt(kUseVolumeNode))
{
plMaxNode *boundNode = (plMaxNode*)fCompPB->GetINode(kVolumeNode);
if (boundNode)
if(boundNode->CanConvert())
physProps->SetProxyNode(boundNode, node, pErrMsg);
else
{
pErrMsg->Set(true, "Volume Sensor Warning", "The Volume Sensor %s has a Proxy Surface %s that was Ignored.\nThe Sensors geometry will be used instead.", node->GetName(), boundNode->GetName()).Show();
pErrMsg->Set(false);
physProps->SetProxyNode(nil, node, pErrMsg);
}
}
// only if movable will it have mass (then it will keep track of movements in PhysX)
if ( node->IsMovable() || node->IsTMAnimatedRecur() )
physProps->SetMass(1.0, node, pErrMsg);
// physProps->SetAllowLOS(true, node, pErrMsg);
physProps->SetGroup(plSimDefs::kGroupDetector, node, pErrMsg);
UInt32 reportOn = fCompPB->GetInt(kVolumeReportOn);
physProps->SetReportGroup(reportOn, node, pErrMsg);
return true;
}
hsBool plVolumeGadgetComponent::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
plLocation loc = node->GetLocation();
plSceneObject *obj = node->GetSceneObject();
// Create and register the VolumeGadget's logic component
if(fCompPB->GetInt(kVolumeGadgetEnter) || fCompPB->GetInt(kVolumeTriggerOnFacing))
{
plLogicModifier *logic = TRACKED_NEW plLogicModifier;
char tmpName[256];
sprintf(tmpName, "%s_Enter", IGetUniqueName(node));
plKey logicKey = hsgResMgr::ResMgr()->NewKey(tmpName, logic, node->GetLocation());
hsgResMgr::ResMgr()->AddViaNotify(logicKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
fLogicModKeys[node] = logicKey;
if (fCompPB->GetInt(kVolumeOneShot))
logic->SetFlag(plLogicModBase::kOneShot);
logic->SetFlag(plLogicModBase::kMultiTrigger);
}
if(fCompPB->GetInt(kVolumeGadgetExit))
{
plLogicModifier *logic = TRACKED_NEW plLogicModifier;
char tmpName[256];
sprintf(tmpName, "%s_Exit", IGetUniqueName(node));
plKey logicKey = hsgResMgr::ResMgr()->NewKey(tmpName, logic, node->GetLocation());
hsgResMgr::ResMgr()->AddViaNotify(logicKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
fLogicModOutKeys[node] = logicKey;
if (fCompPB->GetInt(kVolumeOneShot))
logic->SetFlag(plLogicModBase::kOneShot);
logic->SetFlag(plLogicModBase::kMultiTrigger);
}
return true;
}
void plVolumeGadgetComponent::ICreateConditions(plMaxNode* node, plErrorMsg* errMsg, bool enter)
{
bool disabled = (fCompPB->GetInt(kVolumeEnabled) == 0);
plLocation loc = node->GetLocation();
plSceneObject *obj = node->GetSceneObject();
char tmpName[256];
plKey logicKey;
if (enter)
logicKey = fLogicModKeys[node];
else
logicKey = fLogicModOutKeys[node];
plLogicModifier *logic = plLogicModifier::ConvertNoRef(logicKey->GetObjectPtr());
hsTArray receivers;
IGetReceivers(node, receivers);
for (int i = 0; i < receivers.Count(); i++)
logic->AddNotifyReceiver(receivers[i]);
// Create the detector
plDetectorModifier* detector = nil;
if (enter && fCompPB->GetInt(kVolumeTriggerOnFacing))
{
plObjectInVolumeAndFacingDetector* newDetector = TRACKED_NEW plObjectInVolumeAndFacingDetector;
int deg = fCompPB->GetInt(kVolumeDegrees);
if (deg > 180)
deg = 180;
newDetector->SetFacingTolerance(deg);
bool walkingForward = (fCompPB->GetInt(kVolumeWalkingForward) != 0);
newDetector->SetNeedWalkingForward(walkingForward);
detector = newDetector;
}
else
detector = TRACKED_NEW plObjectInVolumeDetector;
const char* prefix = "Exit";
if (enter)
prefix = "Enter";
// Register the detector
sprintf(tmpName, "%s_%s", IGetUniqueName(node), prefix);
plKey detectorKey = hsgResMgr::ResMgr()->NewKey(tmpName, detector, loc);
hsgResMgr::ResMgr()->AddViaNotify(detectorKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
plVolumeSensorConditionalObject* boxCond=nil;
if((fCompPB->GetInt(kSkipServerArbitration)==0))
{//we want server arbitration
boxCond = TRACKED_NEW plVolumeSensorConditionalObject;
}
else
{
boxCond = TRACKED_NEW plVolumeSensorConditionalObjectNoArbitration;
}
sprintf(tmpName, "%s_%s", IGetUniqueName(node), prefix);
plKey boxKey = hsgResMgr::ResMgr()->NewKey(tmpName, boxCond, loc);
if (enter)
boxCond->SetType(plVolumeSensorConditionalObject::kTypeEnter);
else
boxCond->SetType(plVolumeSensorConditionalObject::kTypeExit);
if (enter && !fCompPB->GetInt(kVolumeTriggerOnFacing))
{
int trigType = fCompPB->GetInt(kVolumeEnterType);
switch (trigType)
{
case kEnterTypeEach:
break;
case kEnterTypeCount:
{
int count = fCompPB->GetInt(kVolumeEnterNum);
boxCond->SetTrigNum(count);
break;
}
}
}
else if (!enter)
{
int trigType = fCompPB->GetInt(kVolumeExitType);
switch (trigType)
{
case kExitTypeEach:
break;
case kExitTypeFirst:
boxCond->SetFirst(true);
break;
case kExitTypeCount:
{
int count = fCompPB->GetInt(kVolumeExitNum);
boxCond->SetTrigNum(count);
break;
}
}
}
// link everything up:
detector->AddLogicObj(boxKey); // This MUST be first!!
detector->AddLogicObj(logicKey); // send messages to this logic component
logic->AddCondition(boxCond);
logic->SetDisabled(disabled);
// If this is for the SceneViewer, set the local only flag since the read function will never be called
if (plConvert::Instance().IsForSceneViewer())
logic->SetLocalOnly(true);
}
hsBool plVolumeGadgetComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
if (fCompPB->GetInt(kVolumeTriggerOnFacing))
ICreateConditions(node, pErrMsg, true);
else if (fCompPB->GetInt(kVolumeGadgetEnter))
ICreateConditions(node, pErrMsg, true);
if (fCompPB->GetInt(kVolumeGadgetExit))
ICreateConditions(node, pErrMsg, false);
return true;
}
hsBool plVolumeGadgetComponent::DeInit( plMaxNode *node, plErrorMsg *pErrMsg )
{
fLogicModOutKeys.clear();
return plActivatorBaseComponent::DeInit( node, pErrMsg );
}