You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

822 lines
20 KiB

/*==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 <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
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==*/
// singular
#include "plAGMasterMod.h"
// local
#include "plAGAnim.h"
#include "plAGAnimInstance.h"
#include "plAGModifier.h"
// #include "plAvatarAnim.h"
#include "plAGMasterSDLModifier.h"
#include "plMatrixChannel.h"
// global
#include "hsResMgr.h"
#include "plgDispatch.h"
// other
// #include "../pnMessage/plRefMsg.h"
#include "../plMessage/plAnimCmdMsg.h"
#include "../pnMessage/plSDLModifierMsg.h"
#include "../pnMessage/plSDLNotificationMsg.h"
#include "../pnMessage/plTimeMsg.h"
#include "../pnSceneObject/plSceneObject.h"
#include "../pnSceneObject/plCoordinateInterface.h"
////////////////
// PLAGMASTERMOD
////////////////
// Coordinates the activities of a bunch of plAGModifiers
// std::map<char *, plAGMasterMod *, stringISorter> plAGMasterMod::fInstances;
// CTOR
plAGMasterMod::plAGMasterMod()
: fTarget(nil),
fNeedEval(false),
fFirstEval(true),
fAGMasterSDLMod(nil),
fNeedCompile(false),
fIsGrouped(false),
fIsGroupMaster(false),
fMsgForwarder(nil)
{
}
// DTOR
plAGMasterMod::~plAGMasterMod()
{
}
void plAGMasterMod::Write(hsStream *stream, hsResMgr *mgr)
{
plModifier::Write(stream, mgr);
int length = 0;
stream->WriteSwap32(length); // backwards compatability. Nuke on next format change.
stream->WriteSwap32(fPrivateAnims.size());
int i;
for (i = 0; i < fPrivateAnims.size(); i++)
{
mgr->WriteKey(stream, fPrivateAnims[i]->GetKey());
}
stream->Writebool(fIsGrouped);
stream->Writebool(fIsGroupMaster);
if (fIsGroupMaster)
mgr->WriteKey(stream, fMsgForwarder->GetKey());
// maybe later... WriteCachedMessages(stream, mgr);
}
void plAGMasterMod::Read(hsStream * stream, hsResMgr *mgr)
{
plModifier::Read(stream, mgr);
//////////////////////////////////////////
int nameLength = stream->ReadSwap32(); // Unused. Nuke next format change.
char *junk = TRACKED_NEW char[nameLength+1]; //
stream->Read(nameLength, junk); //
junk[nameLength] = 0; //
delete [] junk; //
//////////////////////////////////////////
int numPrivateAnims = stream->ReadSwap32();
fPrivateAnims.reserve(numPrivateAnims); // pre-allocate for performance
int i;
for (i = 0; i < numPrivateAnims; i++)
{
plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kPrivateAnim);
mgr->ReadKeyNotifyMe(stream, msg, plRefFlags::kActiveRef);
}
fIsGrouped = stream->Readbool();
fIsGroupMaster = stream->Readbool();
if (fIsGroupMaster)
{
plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, 0);
mgr->ReadKeyNotifyMe(stream, msg, plRefFlags::kActiveRef);
}
// maybe later... ReadCachedMessages(stream, mgr);
}
// ADDTARGET
// Collect all the plAGModifiers from our children and attach private anims.
void plAGMasterMod::AddTarget(plSceneObject * object)
{
plSynchEnabler p(false); // turn off dirty tracking while in this function
fTarget = object;
int autoIdx = -1;
int initialIdx = -1;
int timeIdx = 0;
int i;
for (i = 0; i < fPrivateAnims.size(); i++)
{
plATCAnim *atcAnim = plATCAnim::ConvertNoRef(fPrivateAnims[i]);
if (!atcAnim)
continue;
if (atcAnim->GetAutoStart())
autoIdx = i;
if (atcAnim->GetInitial() != -1)
initialIdx = i;
if (atcAnim->GetStart() < fPrivateAnims[timeIdx]->GetStart())
timeIdx = i;
}
int masterIdx;
if (autoIdx != -1)
masterIdx = autoIdx; // If something autostarts, it wins.
else if (initialIdx != -1)
masterIdx = initialIdx; // Otherwise, the fellow with the @initial point wins
else
masterIdx = timeIdx; // Default case: the earliest anim wins
for (i = 0; i < fPrivateAnims.size(); i++)
{
AttachAnimationBlended(fPrivateAnims[i], i == masterIdx ? 1.f : 0.f);
}
// Force one eval after we init
plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
if (!fIsGrouped || fIsGroupMaster)
{
// add sdl modifier
delete fAGMasterSDLMod;
fAGMasterSDLMod = TRACKED_NEW plAGMasterSDLModifier;
object->AddModifier(fAGMasterSDLMod);
}
}
void plAGMasterMod::RemoveTarget(plSceneObject* o)
{
hsAssert(o == fTarget, "Removing target I don't have");
DetachAllAnimations();
// remove sdl modifier
if (o)
{
if (fAGMasterSDLMod)
o->RemoveModifier(fAGMasterSDLMod);
}
delete fAGMasterSDLMod;
fAGMasterSDLMod=nil;
fTarget = nil;
}
#include "plProfile.h"
plProfile_CreateTimer("ApplyAnimation", "Animation", ApplyAnimation);
plProfile_CreateTimer(" AffineValue", "Animation", AffineValue);
plProfile_CreateTimer(" AffineInterp", "Animation", AffineInterp);
plProfile_CreateTimer(" AffineBlend", "Animation", AffineBlend);
plProfile_CreateTimer(" AffineCompose", "Animation", AffineCompose);
plProfile_CreateTimer(" AffineApplicator", "Animation", MatrixApplicator);
plProfile_CreateTimer("AnimatingPhysicals", "Animation", AnimatingPhysicals);
plProfile_CreateTimer("StoppedAnimPhysicals", "Animation", StoppedAnimPhysicals);
// IEVAL
hsBool plAGMasterMod::IEval(double secs, hsScalar del, UInt32 dirty)
{
if (fFirstEval)
{
int i;
for (i = 0; i < fAnimInstances.size(); i++)
fAnimInstances[i]->SearchForGlobals();
fFirstEval = false;
}
ApplyAnimations(secs, del);
// We might get registered for just a single eval. If we don't need to eval anymore, unregister
if (!fNeedEval)
plgDispatch::Dispatch()->UnRegisterForExactType(plEvalMsg::Index(), GetKey());
return true;
}
// APPLYANIMATIONS
void plAGMasterMod::ApplyAnimations(double time, hsScalar elapsed)
{
plProfile_BeginLap(ApplyAnimation, this->GetKey()->GetUoid().GetObjectName());
// update any fades
for (int i = 0; i < fAnimInstances.size(); i++)
{
fAnimInstances[i]->ProcessFade(elapsed);
}
AdvanceAnimsToTime(time);
plProfile_EndLap(ApplyAnimation,this->GetKey()->GetUoid().GetObjectName());
}
void plAGMasterMod::AdvanceAnimsToTime(double time)
{
if(fNeedCompile)
Compile(time);
for(plChannelModMap::iterator j = fChannelMods.begin(); j != fChannelMods.end(); j++)
{
plAGModifier *mod = (*j).second;
mod->Apply(time);
}
}
void plAGMasterMod::SetNeedCompile(bool needCompile)
{
fNeedCompile = true;
}
void plAGMasterMod::Compile(double time)
{
plChannelModMap::iterator end = fChannelMods.end();
fNeedCompile = false;
for(plChannelModMap::iterator j = fChannelMods.begin(); j != end; j++)
{
plAGModifier *mod = (*j).second;
plAGApplicator *app = mod->GetApplicator(kAGPinTransform);
if(app) {
plAGChannel *channel = app->GetChannel();
if(channel)
{
plMatrixChannel *topChannel = plMatrixChannel::ConvertNoRef(channel);
if(topChannel)
topChannel->Optimize(time);
}
}
}
}
void plAGMasterMod::DumpAniGraph(const char *justThisChannel, bool optimized, double time)
{
plChannelModMap::iterator end = fChannelMods.end();
fNeedCompile = false;
for(plChannelModMap::iterator j = fChannelMods.begin(); j != end; j++)
{
plAGModifier *mod = (*j).second;
if(!justThisChannel || stricmp(justThisChannel, mod->GetChannelName()) == 0)
{
plAGApplicator *app = mod->GetApplicator(kAGPinTransform);
if(app) {
plAGChannel *channel = app->GetChannel();
if(channel)
{
plMatrixChannel *topChannel = plMatrixChannel::ConvertNoRef(channel);
if(topChannel)
{
hsStatusMessageF("AGModifier: <%s>", mod->GetChannelName());
topChannel->Dump(1, optimized, time);
}
}
}
if(justThisChannel)
break;
}
}
}
// GETCHANNELMOD(name)
// Get the modifier that controls the channel with the given name
plAGModifier * plAGMasterMod::GetChannelMod(const char * name, hsBool dontCache ) const
{
plAGModifier * result = nil;
std::map<const char *, plAGModifier *, stringSorter>::const_iterator i = fChannelMods.find(name);
if (i != fChannelMods.end()) {
result = (*i).second;
} else {
plSceneObject *SO = GetTarget(0);
if(SO) {
result = IFindChannelMod(SO, name);
if(result && !dontCache) {
ICacheChannelMod(result);
}
}
}
return result;
}
// CACHECHANNELMOD
plAGModifier * plAGMasterMod::ICacheChannelMod(plAGModifier *mod) const
{
plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, 0);
hsgResMgr::ResMgr()->SendRef(mod, msg, plRefFlags::kActiveRef);
return mod;
}
// IFINDAGMOD (sceneObject)
// See if there's an ag modifier on this sceneobject.
// Doesn't check for multiples; just returns the first one.
plAGModifier * plAGMasterMod::IFindChannelMod(const plSceneObject *SO, const char *name) const
{
const plCoordinateInterface * CI = SO->GetCoordinateInterface();
const plAGModifier * constMod = static_cast<const plAGModifier *>(FindModifierByClass(SO, plAGModifier::Index()));
plAGModifier * mod = const_cast<plAGModifier *>(constMod);
if(mod)
{
const char *modName = mod->GetChannelName();
if(stricmp(name, modName) == 0)
return mod;
}
if(CI)
{
int childCount = CI->GetNumChildren();
for (int i = 0; i < childCount; i++)
{
const plSceneObject * subChild = CI->GetChild(i)->GetOwner();
plAGModifier * mod = IFindChannelMod(subChild, name);
if(mod)
return mod;
}
}
return nil;
}
// ATTACHANIMATIONBLENDED(anim, blend)
plAGAnimInstance * plAGMasterMod::AttachAnimationBlended(plAGAnim *anim,
hsScalar blendFactor /* = 0 */,
UInt16 blendPriority /* plAGMedBlendPriority */,
hsBool cache /* = false */)
{
plAGAnimInstance *instance = nil;
plAnimVector::iterator i;
if(anim)
{
fNeedCompile = true; // need to recompile the graph since we're editing it...
for (i = fPrivateAnims.begin(); i != fPrivateAnims.end(); i++)
{
if (*i == anim)
break;
}
if (i == fPrivateAnims.end()) // Didn't find it. Ref it!
{
plGenRefMsg* msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kPublicAnim);
hsgResMgr::ResMgr()->SendRef(anim, msg, plRefFlags::kActiveRef);
}
instance = TRACKED_NEW plAGAnimInstance(anim, this, blendFactor, blendPriority, cache, false);
fAnimInstances.push_back(instance);
plATCAnim *atcAnim = plATCAnim::ConvertNoRef(anim);
if (atcAnim)
{
fATCAnimInstances.push_back(instance);
ISetupMarkerCallbacks(atcAnim, instance->GetTimeConvert());
}
IRegForEval(HasRunningAnims());
}
return instance;
}
// ATTACHANIMATIONBLENDED(name, blend)
plAGAnimInstance * plAGMasterMod::AttachAnimationBlended(const char *name, hsScalar blendFactor /* = 0 */, UInt16 blendPriority, hsBool cache /* = false */)
{
plAGAnimInstance *instance = nil;
plAGAnim *anim = plAGAnim::FindAnim(name);
if(anim)
{
instance = AttachAnimationBlended(anim, blendFactor, blendPriority, cache);
}
return instance;
}
void plAGMasterMod::PlaySimpleAnim(const char *name)
{
plATCAnim *anim = plATCAnim::ConvertNoRef(plAGAnim::FindAnim(name));
plAGAnimInstance *instance = nil;
if (anim)
{
if (FindAnimInstance(name))
return;
instance = AttachAnimationBlended(anim, 1.f, (UInt16)kAGMaxBlendPriority, false);
}
if (instance)
{
instance->SetLoop(false);
instance->Start();
plAGDetachCallbackMsg *msg = TRACKED_NEW plAGDetachCallbackMsg(GetKey(), kStop);
msg->SetAnimName(name);
instance->GetTimeConvert()->AddCallback(msg);
hsRefCnt_SafeUnRef(msg);
}
}
// FINDANIMINSTANCE
// Look for an animation instance of the given name on the modifier.
// If we need this to be fast, should make it a map rather than a vector
plAGAnimInstance * plAGMasterMod::FindAnimInstance(const char *name)
{
plAGAnimInstance *result = nil;
if (name)
{
for (int i = 0; i < fAnimInstances.size(); i++)
{
plAGAnimInstance *act = fAnimInstances[i];
const char *eachName = act->GetName();
if( stricmp(eachName, name) == 0)
{
result = act;
break;
}
}
}
return result;
}
// FINDORATTACHINSTANCE
plAGAnimInstance * plAGMasterMod::FindOrAttachInstance(const char *name, hsScalar blendFactor)
{
plAGAnimInstance *result = FindAnimInstance(name);
if(result)
{
// if it's already attached, we need to set the blend
result->SetBlend(blendFactor);
} else {
result = AttachAnimationBlended(name, blendFactor);
}
return result;
}
// GETANIMINSTANCE
plAGAnimInstance * plAGMasterMod::GetAnimInstance(int i)
{
return fAnimInstances[i];
}
// GETNUMANIMATIONS
int plAGMasterMod::GetNumAnimations()
{
return fAnimInstances.size();
}
// GETNUMPRIVATEANIMATIONS
int plAGMasterMod::GetNumPrivateAnimations()
{
return fPrivateAnims.size();
}
int plAGMasterMod::GetNumATCAnimations()
{
return fATCAnimInstances.size();
}
plAGAnimInstance *plAGMasterMod::GetATCAnimInstance(int i)
{
return fATCAnimInstances[i];
}
void plAGMasterMod::DetachAllAnimations()
{
int nInstances = fAnimInstances.size();
for (int i = nInstances - 1; i >= 0; i--)
{
plAGAnimInstance * instance = fAnimInstances[i];
if(instance)
{
DetachAnimation(instance);
// delete instance;
}
}
fAnimInstances.clear();
fPrivateAnims.clear();
fATCAnimInstances.clear();
}
// DETACHANIMATION(plAGAnimInstance *)
void plAGMasterMod::DetachAnimation(plAGAnimInstance *anim)
{
plInstanceVector::iterator i;
plAnimVector::iterator j;
fNeedCompile = true; // need to recompile the graph since we're editing it...
for ( i = fAnimInstances.begin(); i != fAnimInstances.end(); i++)
{
plAGAnimInstance *instance = *i;
if(instance == anim)
{
// DetachAnimation(instance);
instance->DetachChannels();
// Need to release it if it's not a private anim
const plAGAnim *agAnim = instance->GetAnimation();
for (j = fPrivateAnims.begin(); j != fPrivateAnims.end(); j++)
{
if (*j == agAnim)
break;
}
if (j == fPrivateAnims.end()) // We didn't find it
GetKey()->Release(agAnim->GetKey());
delete instance;
i = fAnimInstances.erase(i);
break;
}
}
for ( i = fATCAnimInstances.begin(); i != fATCAnimInstances.end(); i++)
{
plAGAnimInstance *instance = *i;
if(instance == anim)
{
i = fATCAnimInstances.erase(i);
break;
}
}
}
// DETACHANIMATION(name)
void plAGMasterMod::DetachAnimation(const char *name)
{
plAGAnimInstance *anim = FindAnimInstance(name);
if(anim) {
DetachAnimation(anim);
}
}
void plAGMasterMod::DumpCurrentAnims(const char *header)
{
if(header)
hsStatusMessageF("Dumping Armature Anim Stack: %s", header);
int nAnims = fAnimInstances.size();
for(int i = nAnims - 1; i >= 0; i--)
{
plAGAnimInstance *inst = fAnimInstances[i];
const char *name = inst->GetName();
float blend = inst->GetBlend();
hsStatusMessageF("%d: %s with blend of %f\n", i, name, blend);
}
}
// MSGRECEIVE
// receive trigger messages
hsBool plAGMasterMod::MsgReceive(plMessage* msg)
{
plAnimCmdMsg* cmdMsg;
plAGCmdMsg* agMsg;
int i;
plSDLNotificationMsg* nMsg = plSDLNotificationMsg::ConvertNoRef(msg);
if (nMsg)
{
// Force a single eval
plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
return true;
}
if (cmdMsg = plAnimCmdMsg::ConvertNoRef(msg))
{
const char *targetName = cmdMsg->GetAnimName();
if (!targetName)
targetName = ENTIRE_ANIMATION_NAME;
plAGAnimInstance *inst = FindAnimInstance(targetName);
if (inst != nil)
{
if (cmdMsg->CmdChangesAnimTime())
{
for (i = 0; i < GetNumAnimations(); i++)
{
plAGAnimInstance *currInst = GetAnimInstance(i);
if (currInst != inst && currInst->GetAnimation()->SharesPinsWith(inst->GetAnimation()))
currInst->SetBlend(0);
}
inst->SetBlend(1);
}
inst->HandleCmd(cmdMsg);
}
return true;
}
if (agMsg = plAGCmdMsg::ConvertNoRef(msg))
{
if (agMsg->Cmd(plAGCmdMsg::kSetAnimTime))
{
for (int i = 0; i < fAnimInstances.size(); i++)
{
plAGAnimInstance *inst = fAnimInstances[i];
inst->SetCurrentTime(agMsg->fAnimTime, true);
}
return true;
}
plAGAnimInstance *inst = FindAnimInstance(agMsg->GetAnimName());
if (inst != nil)
{
if (agMsg->Cmd(plAGCmdMsg::kSetBlend))
inst->Fade(agMsg->fBlend, agMsg->fBlendRate, plAGAnimInstance::kFadeBlend);
if (agMsg->Cmd(plAGCmdMsg::kSetAmp))
inst->Fade(agMsg->fAmp, agMsg->fAmpRate, plAGAnimInstance::kFadeAmp);
}
return true;
}
plAGInstanceCallbackMsg *agicMsg = plAGInstanceCallbackMsg::ConvertNoRef(msg);
if (agicMsg != nil)
{
if (agicMsg->fEvent == kStart)
{
IRegForEval(true);
}
else if (agicMsg->fEvent == kStop)
{
if (!HasRunningAnims())
IRegForEval(false);
}
else // Just force a single eval
{
plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
}
return true;
}
plAGDetachCallbackMsg *detachMsg = plAGDetachCallbackMsg::ConvertNoRef(msg);
if (detachMsg != nil)
{
DetachAnimation(detachMsg->GetAnimName());
}
plGenRefMsg *genRefMsg;
if (genRefMsg = plGenRefMsg::ConvertNoRef(msg))
{
plAGAnim *anim;
if (anim = plAGAnim::ConvertNoRef(genRefMsg->GetRef()))
{
if (genRefMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest))
{
if (genRefMsg->fType == kPrivateAnim)
fPrivateAnims.push_back(anim);
}
else
{
if (genRefMsg->fType == kPrivateAnim)
{
plAnimVector::iterator i = fPrivateAnims.begin();
for (i; i != fPrivateAnims.end(); i++)
{
plAGAnim *currAnim = *i;
if(currAnim == anim)
{
i = fPrivateAnims.erase(i);
break;
}
}
}
}
return true;
}
plAGModifier *agmod;
if (agmod = plAGModifier::ConvertNoRef(genRefMsg->GetRef()))
{
if (genRefMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest))
fChannelMods[agmod->GetChannelName()] = agmod;
else
fChannelMods.erase(agmod->GetChannelName());
return true;
}
plMsgForwarder *msgfwd;
if (msgfwd = plMsgForwarder::ConvertNoRef(genRefMsg->GetRef()))
{
if (genRefMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest))
fMsgForwarder = msgfwd;
else
fMsgForwarder = nil;
return true;
}
}
plRefMsg* refMsg = plRefMsg::ConvertNoRef(msg);
if( refMsg )
{
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
AddTarget(plSceneObject::ConvertNoRef(refMsg->GetRef()));
else
RemoveTarget(plSceneObject::ConvertNoRef(refMsg->GetRef()));
return true;
}
return plModifier::MsgReceive(msg);
}
void plAGMasterMod::IRegForEval(hsBool val)
{
if (fNeedEval == val)
return;
fNeedEval = val;
if (val)
plgDispatch::Dispatch()->RegisterForExactType(plEvalMsg::Index(), GetKey());
else
plgDispatch::Dispatch()->UnRegisterForExactType(plEvalMsg::Index(), GetKey());
}
hsBool plAGMasterMod::HasRunningAnims()
{
int i;
hsBool needEval = false;
for (i = 0; i < fAnimInstances.size(); i++)
{
if (!fAnimInstances[i]->IsFinished())
{
needEval = true;
break;
}
}
return needEval;
}
//
// Send SDL sendState msg to object's plAGMasterSDLModifier
//
hsBool plAGMasterMod::DirtySynchState(const char* SDLStateName, UInt32 synchFlags)
{
if(GetNumTargets() > 0 && (!fIsGrouped || fIsGroupMaster))
{
plSceneObject *sObj = GetTarget(0);
if(sObj)
return sObj->DirtySynchState(SDLStateName, synchFlags);
}
return false;
}
void plAGMasterMod::SetIsGroupMaster(bool master, plMsgForwarder* msgForwarder)
{
fIsGroupMaster = master;
fMsgForwarder = msgForwarder;
}
void plAGMasterMod::SetIsGrouped(bool grouped)
{
fIsGrouped = true;
}