/*==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 "plClickableComponent.h"
#include "resource.h"
#include "plComponentReg.h"
#include "../pnSceneObject/plSceneObject.h"
#include "../pnSceneObject/plSimulationInterface.h"
#include "../pnKeyedObject/hsKeyedObject.h"
#include "../pnKeyedObject/plKey.h"
#include "../plPhysical/plCollisionDetector.h" // MM
#include "../plModifier/plLogicModifier.h"
#include "../../NucleusLib/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 "../MaxMain/plPhysicalProps.h"
#include "../plPhysical/plSimDefs.h"
#include "plResponderComponent.h"
#include "../MaxMain/plPhysicalProps.h"
void DummyCodeIncludeFuncClickable() {}
CLASS_DESC(plClickableComponent, gClickableDesc, "Clickable", "Clickable", COMP_TYPE_DETECTOR, CLICKABLE_CID)
enum
{
kClickableDirectional,
kClickableDegrees,
kClickableUseProxy,
kClickableProxy,
kClickableUseRegion,
kClickableProxyRegion,
kClickableToggle_DEAD,
kClickableOneShot,
kClickableBoundsType,
kClickableEnabled,
kClickablePhysical,
kClickableIgnoreProxyRegion,
kClickableFriction,
};
ParamBlockDesc2 gClickableBlock
(
plComponent::kBlkComp, _T("clickable"), 0, &gClickableDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,
IDD_COMP_DETECTOR_CLICKABLE, IDS_COMP_DETECTOR_CLICKABLE, 0, 0, NULL,
kClickableDirectional, _T("directional"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_COMP_CLICK_OMNI,
end,
kClickableDegrees, _T("degrees"), TYPE_INT, P_ANIMATABLE, 0,
p_range, 1, 180,
p_default, 180,
p_ui, TYPE_SPINNER, EDITTYPE_POS_INT,
IDC_COMP_CLICK_DEG, IDC_COMP_CLICK_DEGSPIN, SPIN_AUTOSCALE,
end,
kClickableUseProxy, _T("useProxy"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_COMP_CLICK_USEPROXY,
p_enable_ctrls, 1, kClickableProxy,
end,
kClickableProxy, _T("proxyPrimitave"), TYPE_INODE, 0, 0,
p_ui, TYPE_PICKNODEBUTTON, IDC_COMP_CLICK_PROXY,
// p_sclassID, GEOMOBJECT_CLASS_ID,
p_prompt, IDS_COMP_PHYS_CHOSEN_BASE,
end,
kClickableProxyRegion, _T("proxyRegion"), TYPE_INODE, 0, 0,
p_ui, TYPE_PICKNODEBUTTON, IDC_COMP_CLICK_PROXYREGION,
// p_sclassID, GEOMOBJECT_CLASS_ID,
p_prompt, IDS_COMP_PHYS_CHOSEN_BASE,
end,
kClickableOneShot, _T("oneshot"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_ONESHOT,
end,
kClickableBoundsType, _T("BoundingConditions"), TYPE_INT, 0, 0,
p_ui, TYPE_RADIO, 4, IDC_RADIO_BSPHERE, IDC_RADIO_BBOX, IDC_RADIO_BHULL, IDC_RADIO_PICKSTATE,
p_vals, plSimDefs::kSphereBounds, plSimDefs::kBoxBounds, plSimDefs::kHullBounds, plSimDefs::kProxyBounds,
p_default, plSimDefs::kHullBounds,
end,
kClickableEnabled, _T("enabled"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_ENABLED,
p_default, TRUE,
end,
kClickablePhysical, _T("physical"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_COLLIDABLE_CHECK,
p_default, TRUE,
end,
kClickableIgnoreProxyRegion, _T("ignoreProxyRegion"), TYPE_BOOL, 0, 0,
p_ui, TYPE_SINGLECHEKBOX, IDC_IGNORE_REGION_CHECK,
p_default, FALSE,
end,
kClickableFriction, _T("friction"), TYPE_FLOAT, 0, 0,
p_range, 0.0f, FLT_MAX,
p_default, 0.0f,
p_ui, TYPE_SPINNER, EDITTYPE_POS_FLOAT,
IDC_COMP_CLICKABLE_FRIC_EDIT1, IDC_COMP_CLICKABLE_FRIC_SPIN1, SPIN_AUTOSCALE,
end,
end
);
plClickableComponent::plClickableComponent()
{
fClassDesc = &gClickableDesc;
fClassDesc->MakeAutoParamBlocks(this);
}
void plClickableComponent::CollectNonDrawables(INodeTab& nonDrawables)
{
if( fCompPB->GetInt(kClickableUseProxy) )
{
INode* clickNode = fCompPB->GetINode(kClickableProxy);
if( clickNode )
nonDrawables.Append(1, &clickNode);
}
INode* detectNode = fCompPB->GetINode(kClickableProxyRegion);
if( detectNode )
nonDrawables.Append(1, &detectNode);
}
hsBool plClickableComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg)
{
plActivatorBaseComponent::SetupProperties(node, pErrMsg);
bool physical = (fCompPB->GetInt(kClickablePhysical) != 0);
//
// Phys Props for the Clickable itself.
//
plMaxNode *clickNode = node;
if (fCompPB->GetInt(kClickableUseProxy))
{
clickNode = (plMaxNode*)fCompPB->GetINode(kClickableProxy);
if (clickNode)
clickNode->SetDrawable(false);
else
clickNode = node;
}
if (clickNode)
{
plPhysicalProps *physProps = clickNode->GetPhysicalProps();
physProps->SetLOSUIItem(true, clickNode, pErrMsg);
if (physical)
{
physProps->SetGroup(plSimDefs::kGroupStatic, clickNode, pErrMsg);
// only if movable will it have mass (then it will keep track of movements in PhysX)
if ( clickNode->IsMovable() || clickNode->IsTMAnimatedRecur() )
physProps->SetMass(1.0, clickNode, pErrMsg);
physProps->SetFriction(fCompPB->GetFloat(kClickableFriction),clickNode,pErrMsg);
}
else
{
physProps->SetGroup(plSimDefs::kGroupLOSOnly, clickNode, pErrMsg);
if(clickNode->IsMovable() || clickNode->IsTMAnimatedRecur())
{
physProps->SetMass(1.0, clickNode, pErrMsg);
}
}
physProps->SetBoundsType(fCompPB->GetInt(kClickableBoundsType), clickNode, pErrMsg);
}
//
// Phys Properties for the auto-generated Detector Region...
//
plMaxNode* detectNode = (plMaxNode*)fCompPB->GetINode(kClickableProxyRegion);
if (detectNode)
{
plPhysicalProps *physPropsDetector = detectNode->GetPhysicalProps();
// physPropsDetector->SetAllowLOS(true, detectNode, pErrMsg);
physPropsDetector->SetProxyNode(detectNode, node, pErrMsg);
physPropsDetector->SetBoundsType(plSimDefs::kHullBounds, detectNode, pErrMsg);
// only if movable will it have mass (then it will keep track of movements in PhysX)
if ( detectNode->IsMovable() || detectNode->IsTMAnimatedRecur() )
physPropsDetector->SetMass(1.0, detectNode, pErrMsg);
physPropsDetector->SetGroup(plSimDefs::kGroupDetector, detectNode, pErrMsg );
physPropsDetector->SetReportGroup(1<GetInt(kClickableUseProxy))
{
clickNode = (plMaxNode*)fCompPB->GetINode(kClickableProxy);
if (clickNode)
clickNode->SetDrawable(false);
else
clickNode = node;
}
clickNode->SetForceLocal(true);
plLocation loc = clickNode->GetLocation();
plSceneObject *obj = clickNode->GetSceneObject();
// Create and register the VolumeGadget's logic component
plLogicModifier *logic = TRACKED_NEW plLogicModifier;
plKey logicKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), logic, clickNode->GetLocation());
hsgResMgr::ResMgr()->AddViaNotify(logicKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
fLogicModKeys[clickNode] = logicKey;
return true;
}
hsBool plClickableComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{
bool ignoreProxyRegion = (fCompPB->GetInt(kClickableIgnoreProxyRegion) != 0);
//
// Error checking
//
plMaxNode* clickProxyNode = node;
if (fCompPB->GetInt(kClickableUseProxy))
{
clickProxyNode = (plMaxNode*)fCompPB->GetINode(kClickableProxy);
if (!clickProxyNode || !clickProxyNode->CanConvert())
{
pErrMsg->Set(true,
"Clickable Error",
"The Clickable '%s' on node '%s' is set to use a proxy but doesn't have one, or it didn't convert.\n"
"The node the Clickable is attached to will be used instead.",
GetINode()->GetName(), node->GetName()).Show();
pErrMsg->Set(false);
clickProxyNode = node;
}
}
plMaxNode* detectNode = (plMaxNode*)fCompPB->GetINode(kClickableProxyRegion);
if ((!detectNode || !detectNode->CanConvert()) && (!ignoreProxyRegion))
{
pErrMsg->Set(true,
"Clickable Error",
"The Clickable '%s' on node '%s' has a required region that is missing, or didn't convert.\n"
"The export will be aborted.",
GetINode()->GetName(), node->GetName()).Show();
return false;
}
plLocation loc = clickProxyNode->GetLocation();
plSceneObject *obj = clickProxyNode->GetSceneObject();
plKey logicKey = fLogicModKeys[clickProxyNode];
plLogicModifier *logic = plLogicModifier::ConvertNoRef(logicKey->GetObjectPtr());
logic->fMyCursor = plCursorChangeMsg::kCursorPoised;
if (fCompPB->GetInt(kClickableOneShot))
logic->SetFlag(plLogicModBase::kOneShot);
hsTArray receivers;
IGetReceivers(node, receivers);
for (int i = 0; i < receivers.Count(); i++)
logic->AddNotifyReceiver(receivers[i]);
// Create the detector
plDetectorModifier *detector = nil;
detector = TRACKED_NEW plPickingDetector;
// Register the detector
plKey detectorKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), detector, loc);
hsgResMgr::ResMgr()->AddViaNotify(detectorKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
// create and register the CONDITIONS for the DETECTOR's Logic Modifier
plActivatorConditionalObject* activatorCond = TRACKED_NEW plActivatorConditionalObject;
plKey activatorKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), activatorCond, loc);
//
// Create required region
//
// need a player in box condition here...
// first a detector-any for the box
if (!ignoreProxyRegion)
{
plObjectInVolumeDetector* pCDet = TRACKED_NEW plObjectInVolumeDetector(plCollisionDetector::kTypeAny);
plKey pCDetKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), pCDet, loc);
hsgResMgr::ResMgr()->AddViaNotify(pCDetKey, TRACKED_NEW plObjRefMsg(detectNode->GetSceneObject()->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
pCDet->AddLogicObj(logicKey);
// then an object-in-box condition for the logic mod
plObjectInBoxConditionalObject* boxCond = TRACKED_NEW plObjectInBoxConditionalObject;
plKey boxCondKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), boxCond, loc);
logic->AddCondition(boxCond);
}
//
// How do we feel about player facing
//
plFacingConditionalObject* facingCond = TRACKED_NEW plFacingConditionalObject;
facingCond->SetDirectional(fCompPB->GetInt(kClickableDirectional));
int deg = fCompPB->GetInt(kClickableDegrees);
if (deg > 180)
deg = 180;
hsScalar rad = hsScalarDegToRad(deg);
facingCond->SetTolerance(hsCosine(rad));
plKey facingKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), facingCond, loc);
detector->AddLogicObj(logicKey); // send messages to this logic component
activatorCond->SetActivatorKey(detectorKey); // Tells the activator condition to look for stimulus from the detector
logic->AddCondition(activatorCond); // add this activator condition
logic->AddCondition(facingCond);
logic->SetDisabled(!fCompPB->GetInt(kClickableEnabled));
// 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);
return true;
}
//
// special physical you can walk through and click with mouse
//
class plNoBlkClickableComponent : public plComponent
{
public:
plNoBlkClickableComponent();
void DeleteThis() { delete this; }
hsBool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg);
hsBool Convert(plMaxNode *node, plErrorMsg *pErrMsg) { return true; }
hsBool PreConvert(plMaxNode *node, plErrorMsg *pErrMsg) { return true; }
virtual void CollectNonDrawables(INodeTab& nonDrawables) { AddTargetsToList(nonDrawables); }
};
OBSOLETE_CLASS_DESC(plNoBlkClickableComponent, gNoBlkClickableDesc, "(ex)Non Physical Clickable Proxy", "(ex)Non Physical Clickable Proxy", COMP_TYPE_PHYSICAL, Class_ID(0x66325afc, 0x253a3760))
ParamBlockDesc2 gNoBlkClickableBlock
(
plComponent::kBlkComp, _T("NonPhysicalClickableProxy"), 0, &gNoBlkClickableDesc, P_AUTO_CONSTRUCT, plComponent::kRefComp,
end
);
plNoBlkClickableComponent::plNoBlkClickableComponent()
{
fClassDesc = &gNoBlkClickableDesc;
fClassDesc->MakeAutoParamBlocks(this);
}
hsBool plNoBlkClickableComponent::SetupProperties(plMaxNode *pNode, plErrorMsg *pErrMsg)
{
return true;
}