/*==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 "../plAvatar/plAvCallbackAction.h" #include "hsTypes.h" // singular #include "plAvLadderModifier.h" // local #include "plArmatureMod.h" #include "plAvatarMgr.h" #include "plAvBrainGeneric.h" #include "plAGAnim.h" #include "plAnimStage.h" // global #include "plCreatableIndex.h" // #include "plgDispatch.h" // Message Dependencies #include "hsStream.h" //other #include "../plMessage/plCollideMsg.h" #include "../plMessage/plAvatarMsg.h" #include "../pnMessage/plNotifyMsg.h" #include "../plStatusLog/plStatusLog.h" #include "../pnKeyedObject/plKey.h" #include "../pnMessage/plEnableMsg.h" #include "../pnMessage/plTimeMsg.h" #include "plgDispatch.h" #include "../pnNetCommon/plNetApp.h" #include "../pnSceneObject/plCoordinateInterface.h" #include "../plAvatar/plAvBrainHuman.h" #include "../plModifier/plDetectorLog.h" enum NotifyType { kNotifyTrigger, kNotifyAvatarOnLadder, }; // CTOR default plAvLadderMod::plAvLadderMod() : fGoingUp(true), fType(kBig), fLoops(0), fEnabled(true), fAvatarInBox(false), fLadderView(0,0,0), fAvatarMounting(false) { fTarget = nil; } // CTOR goingUp, type, loops plAvLadderMod::plAvLadderMod(bool goingUp, int type, int loops, bool enabled, hsVector3& ladderView) : fGoingUp(goingUp), fType(type), fLoops(loops), fEnabled(enabled), fAvatarInBox(false), fLadderView(ladderView) { fTarget = nil; } // Must be facing within 45 degrees of the ladder static const hsScalar kTolerance = hsCosine(hsScalarDegToRad(45)); bool plAvLadderMod::IIsReadyToClimb() { if (fAvatarMounting) return false; plArmatureMod* armMod = plAvatarMgr::GetInstance()->GetLocalAvatar(); plSceneObject* avatar = armMod->GetTarget(0); if (avatar) { hsVector3 playerView = avatar->GetCoordinateInterface()->GetLocalToWorld().GetAxis(hsMatrix44::kView); playerView.fZ = 0; // Are we facing towards the ladder? hsScalar dot = playerView * fLadderView; bool movingForward = false; // And are we walking towards it? hsAssert(armMod, "Avatar doesn't have an armature mod"); if (armMod) { plAvBrainHuman* brain = plAvBrainHuman::ConvertNoRef(armMod->GetCurrentBrain()); if (brain && brain->IsMovingForward() && brain->fCallbackAction->IsOnGround()) movingForward = true; } if (dot >= kTolerance && movingForward) { DetectorLogSpecial("%s: Ladder starting climb (%f)", GetKeyName(), hsScalarRadToDeg(hsACosine(dot))); return true; } else if (movingForward) { // DetectorLog("%s: Ladder rejecting climb (%f)", GetKeyName(), hsScalarRadToDeg(hsACosine(dot))); return false; } } return false; } // use a plNotify (to ourself) to propagate across the network void plAvLadderMod::ITriggerSelf(plKey avKey) { if (fEnabled) { plKey avPhysKey = avKey; // I'm going to lie and pretend it's from the avatar. the alternative is lengthy and unreadable. plNotifyMsg *notifyMsg = TRACKED_NEW plNotifyMsg(avPhysKey, GetKey()); notifyMsg->fID = kNotifyTrigger; notifyMsg->Send(); fAvatarMounting = true; } } // MSGRECEIVE hsBool plAvLadderMod::MsgReceive(plMessage* msg) { // Avatar is entering or exiting our detector box plCollideMsg* collMsg = plCollideMsg::ConvertNoRef(msg); if (collMsg) { // make sure this is the local player... the notify will be the thing that propagates over the network if (plNetClientApp::GetInstance()->GetLocalPlayerKey() != collMsg->fOtherKey) return true; fAvatarInBox = (collMsg->fEntering != 0); // If entering, check if ready to climb. If not, register for eval so // we can check every frame if (fAvatarInBox) { DetectorLogSpecial("%s: Avatar entered ladder region", GetKeyName()); if (IIsReadyToClimb()) ITriggerSelf(collMsg->fOtherKey); else plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey()); } else { DetectorLogSpecial("%s: Avatar exited ladder region", GetKeyName()); plgDispatch::Dispatch()->UnRegisterForExactType(plEvalMsg::Index(), GetKey()); } return true; } // Avatar is inside our detector box, so every frame we check if he's ready to climb plEvalMsg* evalMsg = plEvalMsg::ConvertNoRef(msg); if (evalMsg) { if (IIsReadyToClimb()) ITriggerSelf(plNetClientApp::GetInstance()->GetLocalPlayerKey()); return true; } plNotifyMsg *notifyMsg = plNotifyMsg::ConvertNoRef(msg); if (notifyMsg) { if (notifyMsg->fID == kNotifyTrigger && fEnabled) { const plKey avPhysKey = notifyMsg->GetSender(); EmitCommand(avPhysKey); } else if (notifyMsg->fID == kNotifyAvatarOnLadder) { DetectorLogSpecial("%s: Avatar mounted ladder", GetKeyName()); fAvatarMounting = false; } return true; } plEnableMsg *enableMsg = plEnableMsg::ConvertNoRef(msg); if (enableMsg) { if (enableMsg->Cmd(plEnableMsg::kDisable)) fEnabled = false; else if (enableMsg->Cmd(plEnableMsg::kEnable)) fEnabled = true; } return plSingleModifier::MsgReceive(msg); } // EMITCOMMAND void plAvLadderMod::EmitCommand(const plKey receiver) { hsKeyedObject *object = receiver->ObjectIsLoaded(); plSceneObject *SO = plSceneObject::ConvertNoRef(object); if(SO) { const plArmatureMod *constAvMod = (plArmatureMod*)SO->GetModifierByType(plArmatureMod::Index()); if(constAvMod) { plAvBrainGeneric *curGenBrain = (plAvBrainGeneric *)constAvMod->FindBrainByClass(plAvBrainGeneric::Index()); // *** warning; if there's more than one generic brain active, this will only look at the first bool alreadyOnLadder = ( curGenBrain && curGenBrain->GetType() == plAvBrainGeneric::kLadder ); if( ! alreadyOnLadder) { plSceneObject *seekObj = GetTarget(); plKey seekKey = seekObj->GetKey(); // this modifier's target is the seek object char *mountName, *dismountName, *traverseName; if(fGoingUp) { mountName = "LadderUpOn"; dismountName = "LadderUpOff"; traverseName = "LadderUp"; } else { mountName = "LadderDownOn"; dismountName = "LadderDownOff"; traverseName = "LadderDown"; } plAnimStageVec *v = TRACKED_NEW plAnimStageVec; plAnimStage *s1 = TRACKED_NEW plAnimStage(mountName, 0, plAnimStage::kForwardAuto, plAnimStage::kBackAuto, plAnimStage::kAdvanceAuto, plAnimStage::kRegressAuto, 0); if( ! fGoingUp) s1->SetReverseOnIdle(true); v->push_back(s1); // if loops is zero, we don't need the traverse animation at all. if(fLoops) { plAnimStage *s2 = TRACKED_NEW plAnimStage(traverseName, 0, plAnimStage::kForwardKey, plAnimStage::kBackKey, plAnimStage::kAdvanceAuto, plAnimStage::kRegressAuto, fLoops - 1 // first loop is implied; zero loops means // play this anim once, 1 loop means "loop // once after reaching the end." ); if( ! fGoingUp) s2->SetReverseOnIdle(true); v->push_back(s2); } plAnimStage *s3 = TRACKED_NEW plAnimStage(dismountName, 0, plAnimStage::kForwardAuto, plAnimStage::kBackAuto, plAnimStage::kAdvanceAuto, plAnimStage::kRegressAuto, 0); if( ! fGoingUp) s3->SetReverseOnIdle(true); v->push_back(s3); plNotifyMsg* enterNotify = TRACKED_NEW plNotifyMsg(GetKey(), GetKey()); enterNotify->fID = kNotifyAvatarOnLadder; UInt32 exitFlags = plAvBrainGeneric::kExitNormal; plAvBrainGeneric *ladBrain = TRACKED_NEW plAvBrainGeneric(v, enterNotify, nil, nil, exitFlags, plAvBrainGeneric::kDefaultFadeIn, plAvBrainGeneric::kDefaultFadeOut, plAvBrainGeneric::kMoveRelative); ladBrain->SetType(plAvBrainGeneric::kLadder); ladBrain->SetReverseFBControlsOnRelease(!fGoingUp); plKey avKey = constAvMod->GetKey(); // Very important that we dumb seek here. Otherwise you can run off the edge of a ladder, and seek will be helpless // until you hit the ground, at which point you have no hope of successfully seeking. plAvSeekMsg *seeker = TRACKED_NEW plAvSeekMsg(nil, avKey, seekKey, 1.0f, false); seeker->Send(); plAvPushBrainMsg *brainer = TRACKED_NEW plAvPushBrainMsg(nil, avKey, ladBrain); brainer->Send(); } } } } void plAvLadderMod::Read(hsStream *stream, hsResMgr *mgr) { plSingleModifier::Read(stream, mgr); fType = stream->ReadSwap32(); fLoops = stream->ReadSwap32(); fGoingUp = stream->Readbool(); fEnabled = stream->Readbool(); fLadderView.fX = stream->ReadSwapScalar(); fLadderView.fY = stream->ReadSwapScalar(); fLadderView.fZ = stream->ReadSwapScalar(); } void plAvLadderMod::Write(hsStream *stream, hsResMgr *mgr) { plSingleModifier::Write(stream, mgr); stream->WriteSwap32(fType); stream->WriteSwap32(fLoops); stream->Writebool(fGoingUp); stream->WriteBool(fEnabled); stream->WriteSwapScalar(fLadderView.fX); stream->WriteSwapScalar(fLadderView.fY); stream->WriteSwapScalar(fLadderView.fZ); } // true is up; false is down bool plAvLadderMod::GetGoingUp() const { return fGoingUp; } void plAvLadderMod::SetGoingUp(bool up) { fGoingUp = up; } int plAvLadderMod::GetLoops() const { return fLoops; } void plAvLadderMod::SetLoops(int loops) { fLoops = loops; } int plAvLadderMod::GetType() const { return fType; } void plAvLadderMod::SetType(int type) { if(type >= kNumOfTypeFields || type < 0) { hsStatusMessage("Invalid param to plAvLadderMod::SetType: defaulting to kBig"); fType = kBig; } else { fType = type; } }