/*==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/>.

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 "hsTypes.h"
#include "hsStlUtils.h"
#include "hsTimer.h"
#include "plResponderModifier.h"
#include "plResponderSDLModifier.h"
#include "plgDispatch.h"
#include "hsResMgr.h"
#include "plPhysical.h"
#include "pnKeyedObject/plKey.h"
#include "pnKeyedObject/plFixedKey.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnMessage/plNotifyMsg.h"
#include "pnNetCommon/plNetApp.h"

// for localOnly cmd check
#include "plMessage/plLinkToAgeMsg.h"
#include "pnMessage/plCameraMsg.h"
#include "pnMessage/plSoundMsg.h"

#include "plMessage/plResponderMsg.h"
#include "plMessage/plAnimCmdMsg.h"
#include "plMessage/plLinkToAgeMsg.h"
#include "pnMessage/plSDLModifierMsg.h"
#include "pfMessage/plArmatureEffectMsg.h"

#include "plStatusLog/plStatusLog.h"

#include "plMessage/plTimerCallbackMsg.h"
#include "pnTimer/plTimerCallbackManager.h"

#include "plMessage/plSimStateMsg.h"
//#include "plHavok1\plHKPhysical.h"
//#include "plHavok1\plHKSubWorld.h"
#include "plAvatar/plArmatureMod.h"
#include "plAvatar/plAvatarMgr.h"


//#ifdef HS_DEBUGGING
#define STATUS_LOG
//#endif

#ifdef STATUS_LOG
#define ResponderLog(x) x
#else
#define ResponderLog(x)
#endif

void plResponderEnableMsg::Read(hsStream* stream, hsResMgr* mgr)
{
    plMessage::IMsgRead(stream, mgr);
    fEnable = stream->Readbool();
}

void plResponderEnableMsg::Write(hsStream* stream, hsResMgr* mgr)
{
    plMessage::IMsgWrite(stream, mgr);
    stream->Writebool(fEnable);
}

/////////////////////////////////////////////////////
/////////////////////////////////////////////////////

plResponderModifier::plResponderModifier() : 
    fCurState(0), 
    fCurCommand(-1), 
    fEnabled(true), 
    fFlags(0), 
    fEnter(false),
    fResponderSDLMod(nil), 
    fGotFirstLoad(false),
    fNotifyMsgFlags(0)
{
}

plResponderModifier::~plResponderModifier()
{
    delete fResponderSDLMod;
    fResponderSDLMod=nil;

    for (int i = 0; i < fStates.Count(); i++)
    {
        for (int j = 0; j < fStates[i].fCmds.Count(); j++ )
            hsRefCnt_SafeUnRef(fStates[i].fCmds[j].fMsg);
    }
}

hsBool plResponderModifier::MsgReceive(plMessage* msg)
{
    plNotifyMsg* pNMsg = plNotifyMsg::ConvertNoRef(msg);
    if (pNMsg)
    {
        if (pNMsg->fType == plNotifyMsg::kResponderFF)
        {
            ISetResponderStateFromNotify(pNMsg);
            IFastForward(true);
        }
        else if (pNMsg->fType == plNotifyMsg::kResponderChangeState)
        {
            ISetResponderStateFromNotify(pNMsg);
            DirtySynchState(kSDLResponder, 0);
        }
        else
        {
            // assumes state of 0 means untriggered and state of 1 is triggered
            if ((pNMsg->fState != 0 && (fFlags & kDetectTrigger)) ||
                (pNMsg->fState == 0 && (fFlags & kDetectUnTrigger)))
            {
                Trigger(pNMsg);
                DirtySynchState(kSDLResponder, 0);
            }
        }

        return true;
    }

    plResponderEnableMsg *pEnableMsg = plResponderEnableMsg::ConvertNoRef(msg);
    if (pEnableMsg)
    {
        fEnabled = pEnableMsg->fEnable;
        DirtySynchState(kSDLResponder, 0);
        return true;
    }

    plEventCallbackMsg *pEventMsg = plEventCallbackMsg::ConvertNoRef(msg);
    plTimerCallbackMsg *timerMsg = plTimerCallbackMsg::ConvertNoRef(msg);
    if (pEventMsg || timerMsg)
    {
        UInt32 waitID = pEventMsg ? pEventMsg->fUser : timerMsg->fID;

        if (waitID != -1)
        {
            // Flag that this callback completed and try sending in case any commands were waiting on this
            fCompletedEvents.SetBit(waitID);

            ResponderLog(ILog(plStatusLog::kWhite, "Got callback from command %d(id:%d)", ICmdFromWait((Int8)waitID)+1, waitID));

            IContinueSending();
            DirtySynchState(kSDLResponder, 0);
        }
        // The is one of the stop callbacks we generated for debug mode
        else if (fDebugAnimBox)
            IDebugAnimBox(false);

        return true;
    }

    // pass sdl msg to sdlMod
    plSDLModifierMsg* sdlMsg = plSDLModifierMsg::ConvertNoRef(msg);
    if (sdlMsg && fResponderSDLMod)
    {
        if (fResponderSDLMod->MsgReceive(sdlMsg))
            return true;    // msg handled
    }

    return plSingleModifier::MsgReceive(msg);
}

void plResponderModifier::AddCommand(plMessage* pMsg, int state)
{
    fStates[state].fCmds.Append(plResponderCmd(pMsg, -1));
}

void plResponderModifier::AddCallback(Int8 state, Int8 cmd, Int8 callback)
{
    fStates[state].fWaitToCmd[callback] = cmd;
}

//
// Decide if this cmd should only be run locally.
// If we are triggered remotely (netRequest==true), then
// we don't want to execute localOnly cmds (like cameraChanges)
//
bool plResponderModifier::IIsLocalOnlyCmd(plMessage* cmd)
{
    if (plLinkToAgeMsg::ConvertNoRef(cmd))  // don't want to page out any rooms
        return true;
    if (plCameraMsg::ConvertNoRef(cmd))     // don't want to change our camera
        return true;

    plSoundMsg *snd = plSoundMsg::ConvertNoRef( cmd );
    if( snd != nil && snd->Cmd( plSoundMsg::kIsLocalOnly ) )
        return true;

    return false;
}

void plResponderModifier::ISetResponderState(Int8 state)
{
    // make sure that it is a valid state to switch to
    if (state >= 0 && state < fStates.Count())
    {
        fCurState = state;
    }
    else
    {
        ResponderLog(ILog(plStatusLog::kRed, "Invalid state %d specified, will default to current state", state));
    }
}

void plResponderModifier::ISetResponderStateFromNotify(plNotifyMsg* msg)
{
    // set the state of the responder IF they want it to be
    proResponderStateEventData* event = (proResponderStateEventData*)msg->FindEventRecord(proEventData::kResponderState);
    if (event != nil)
        ISetResponderState((Int8)(event->fState));
}

void plResponderModifier::Trigger(plNotifyMsg *msg)
{
#if 0
    char str[256];
    sprintf(str, "RM: Responder %s is triggering, num cmds=%d, enabled=%d, curCmd=%d, t=%f\n", 
        GetKeyName(), fStates[fCurState].fCmds.GetCount(), 
        ((int)fEnabled), ((int)fCurCommand), hsTimer::GetSysSeconds());
    plNetClientApp::GetInstance()->DebugMsg(str);
#endif

    // If we're not in the middle of sending, reset and start sending commands
    if (fCurCommand == Int8(-1) && fEnabled)
    {
        ResponderLog(ILog(plStatusLog::kGreen, "Trigger"));

        fNotifyMsgFlags = msg->GetAllBCastFlags();
        fTriggerer = msg->GetSender();
        fPlayerKey = msg->GetAvatarKey();

        ISetResponderStateFromNotify(msg);

        proCollisionEventData *cEvent = (proCollisionEventData *)msg->FindEventRecord(proEventData::kCollision);
        fEnter = (cEvent ? cEvent->fEnter : false);

        fCompletedEvents.Reset();
        fCurCommand = 0;

        DirtySynchState(kSDLResponder, 0);

        IContinueSending();
    }
    else
    {
        ResponderLog(ILog(plStatusLog::kRed, "Rejected Trigger, %s", !fEnabled ? "responder disabled" : "responder is running"));
    }
}

bool plResponderModifier::IContinueSending()
{
    // If we haven't been started, exit
    if (fCurCommand == Int8(-1))
        return false;

    plResponderState& state = fStates[fCurState];

    while (fCurCommand < state.fCmds.Count())
    {
        plMessage *msg = state.fCmds[fCurCommand].fMsg;
        if (msg)
        {
            // If this command needs to wait, and it's condition hasn't been met yet, exit
            Int8 wait = state.fCmds[fCurCommand].fWaitOn;
            if (wait != -1 && !fCompletedEvents.IsBitSet(wait))
            {
                ResponderLog(ILog(plStatusLog::kWhite, "Command %d is waiting for command %d(id:%d)", Int8(fCurCommand)+1, ICmdFromWait(wait)+1, wait));
                return false;
            }

            if (!(fNotifyMsgFlags & plMessage::kNetNonLocal)|| !IIsLocalOnlyCmd(msg))
            {
                // make sure outgoing msgs inherit net flags as part of cascade
                UInt32 msgFlags = msg->GetAllBCastFlags();
                plNetClientApp::InheritNetMsgFlags(fNotifyMsgFlags, &msgFlags, true);
                msg->SetAllBCastFlags(msgFlags);

                // If this is a responder message, let it know which player triggered this
                if (plResponderMsg* responderMsg = plResponderMsg::ConvertNoRef(msg))
                {
                    responderMsg->fPlayerKey = fPlayerKey;
                }
                else if (plNotifyMsg* notifyMsg = plNotifyMsg::ConvertNoRef(msg))
                {
                    bool foundCollision = false;

                    // If we find a collision event, this message is meant to trigger a multistage
                    for (int i = 0; i < notifyMsg->GetEventCount(); i++)
                    {
                        proEventData* event = notifyMsg->GetEventRecord(i);
                        if (event->fEventType == proEventData::kCollision)
                        {
                            proCollisionEventData* collisionEvent = (proCollisionEventData*)event;
                            collisionEvent->fHitter = fPlayerKey;
                            foundCollision = true;
                        }
                    }

                    // No collision event, this message is for notifying the triggerer
                    if (!foundCollision)
                    {
                        notifyMsg->ClearReceivers();
                        notifyMsg->AddReceiver(fTriggerer);
                    }

                    notifyMsg->SetSender(GetKey());
                }
                else if (plLinkToAgeMsg* linkMsg = plLinkToAgeMsg::ConvertNoRef(msg))
                {
                    if (linkMsg->GetNumReceivers() == 0)
                    {
                        plUoid netUoid(kNetClientMgr_KEY);
                        plKey netKey = hsgResMgr::ResMgr()->FindKey(netUoid);
                        hsAssert(netKey,"NetClientMgr not found");
                        linkMsg->AddReceiver(netKey);
                    }
                }
                else if (plArmatureEffectStateMsg* stateMsg = plArmatureEffectStateMsg::ConvertNoRef(msg))
                {
                    stateMsg->ClearReceivers();
                    stateMsg->AddReceiver(fPlayerKey);
                    stateMsg->fAddSurface = fEnter;
                }
                else if (plSubWorldMsg* swMsg = plSubWorldMsg::ConvertNoRef(msg))
                {
                    plArmatureMod *avatar = plAvatarMgr::GetInstance()->GetLocalAvatar();
                    if(avatar)
                    {
                        swMsg->AddReceiver(avatar->GetKey());
                    }
                }

                // If we're in anim debug mode, check if this is an anim play
                // message so we can put up the cue
                if (fDebugAnimBox)
                {
                    plAnimCmdMsg* animMsg = plAnimCmdMsg::ConvertNoRef(msg);
                    if (animMsg && animMsg->Cmd(plAnimCmdMsg::kContinue))
                        IDebugPlayMsg(animMsg);
                }


                if (plTimerCallbackMsg *timerMsg = plTimerCallbackMsg::ConvertNoRef(msg))
                {
                    hsRefCnt_SafeRef(timerMsg);
                    plgTimerCallbackMgr::NewTimer(timerMsg->fTime, timerMsg);
                }
                else
                {
                    hsRefCnt_SafeRef(msg);
                    plgDispatch::MsgSend(msg);
                }
            }
        }

        fCurCommand++;
        DirtySynchState(kSDLResponder, 0);
    }

    // Make sure all callbacks we need to wait on are done before allowing a state switch or restart
    for (int i = 0; i < state.fNumCallbacks; i++)
    {
        if (!fCompletedEvents.IsBitSet(i))
        {
            ResponderLog(ILog(plStatusLog::kWhite, "Can't reset, waiting for command %d(id:%d)", ICmdFromWait(i)+1, i));
            return false;
        }
    }

    ResponderLog(ILog(plStatusLog::kGreen, "Reset"));

    fCurCommand = -1;
    ISetResponderState(state.fSwitchToState);
    DirtySynchState(kSDLResponder, 0);
    
    return true;
}

Int8 plResponderModifier::ICmdFromWait(Int8 waitIdx)
{
    WaitToCmd& waitToCmd = fStates[fCurState].fWaitToCmd;
    if (waitToCmd.find(waitIdx) != waitToCmd.end())
        return waitToCmd[waitIdx];
    return -1;
}

void plResponderModifier::Restore()
{
    // If we're the first player in and we're loading old state where this responder
    // was running, fast forward it
    if (plNetClientApp::GetInstance()->GetJoinOrder() == 0 && fCurCommand != -1 && !fGotFirstLoad)
    {
        fGotFirstLoad = true;
        IFastForward(false);
        return;
    }

    ResponderLog(ILog(plStatusLog::kGreen, "Load SDL State"));
    
    fGotFirstLoad = true;

    plResponderState& state = fStates[fCurState];
    for (int i = 0; i < state.fNumCallbacks; i++)
    {
        if (!fCompletedEvents[i])
        {
            int cmdIdx = state.fWaitToCmd[i];

            plResponderCmd& cmd = state.fCmds[cmdIdx];

            //
            // If it's a callback message (anim or sound), just send the callbacks again
            //
            plMessageWithCallbacks* callbackMsg = plMessageWithCallbacks::ConvertNoRef(cmd.fMsg);
            if (callbackMsg)
            {
                // Create a new message for just the callbacks
                plMessageWithCallbacks* newCallbackMsg = nil;

                if (plAnimCmdMsg* animMsg = plAnimCmdMsg::ConvertNoRef(callbackMsg))
                {
                    plAnimCmdMsg* newAnimMsg = TRACKED_NEW plAnimCmdMsg;
                    newAnimMsg->SetCmd(plAnimCmdMsg::kAddCallbacks);
                    newCallbackMsg = newAnimMsg;
                    ResponderLog(ILog(plStatusLog::kGreen, "Restoring anim callback"));
                }
                else if (plSoundMsg* soundMsg = plSoundMsg::ConvertNoRef(callbackMsg))
                {
                    plSoundMsg* newSoundMsg = TRACKED_NEW plSoundMsg;
                    newSoundMsg->SetCmd(plSoundMsg::kAddCallbacks);
                    newCallbackMsg = newSoundMsg;
                    ResponderLog(ILog(plStatusLog::kGreen, "Restoring sound callback"));
                }

                // Setup the sender and receiver
                newCallbackMsg->SetSender(callbackMsg->GetSender());
                for (int iReceiver = 0; i < callbackMsg->GetNumReceivers(); i++)
                    newCallbackMsg->AddReceiver(callbackMsg->GetReceiver(iReceiver));

                // Add the callbacks
                int numCallbacks = callbackMsg->GetNumCallbacks();
                for (int iCallback = 0; iCallback < numCallbacks; iCallback++)
                {
                    plMessage* callback = callbackMsg->GetCallback(iCallback);
//                  hsRefCnt_SafeRef(callback); AddCallback will ref this for us.
                    newCallbackMsg->AddCallback(callback);
                }

                newCallbackMsg->Send();
            }
        }
    }
}

#include "plCreatableIndex.h"

plMessage* plResponderModifier::IGetFastForwardMsg(plMessage* msg, bool python)
{
    if (!msg)
        return nil;

    if (plAnimCmdMsg* animMsg = plAnimCmdMsg::ConvertNoRef(msg))
    {
        if (animMsg->Cmd(plAnimCmdMsg::kContinue) ||
            animMsg->Cmd(plAnimCmdMsg::kAddCallbacks))
        {
            plAnimCmdMsg* newAnimMsg = TRACKED_NEW plAnimCmdMsg;
            newAnimMsg->fCmd                = animMsg->fCmd;
            newAnimMsg->fBegin              = animMsg->fBegin;
            newAnimMsg->fEnd                = animMsg->fEnd;
            newAnimMsg->fLoopEnd            = animMsg->fLoopEnd;
            newAnimMsg->fLoopBegin          = animMsg->fLoopBegin;
            newAnimMsg->fSpeed              = animMsg->fSpeed;
            newAnimMsg->fSpeedChangeRate    = animMsg->fSpeedChangeRate;
            newAnimMsg->fTime               = animMsg->fTime;
            newAnimMsg->SetAnimName(animMsg->GetAnimName());
            newAnimMsg->SetLoopName(animMsg->GetLoopName());

            // Remove the callbacks
            newAnimMsg->fCmd.SetBit(plAnimCmdMsg::kAddCallbacks, false);

            if (newAnimMsg->Cmd(plAnimCmdMsg::kContinue))
            {
                newAnimMsg->fCmd.SetBit(plAnimCmdMsg::kContinue, false);
                newAnimMsg->fCmd.SetBit(plAnimCmdMsg::kFastForward, true);
            }

            for (int i = 0; i < animMsg->GetNumReceivers(); i++)
                newAnimMsg->AddReceiver(animMsg->GetReceiver(i));

            ResponderLog(ILog(plStatusLog::kWhite, "FF Animation Play Msg"));
            return newAnimMsg;
        }

        ResponderLog(ILog(plStatusLog::kWhite, "FF Animation Non-Play Msg"));
        hsRefCnt_SafeRef(msg);
        return msg;
    }
    else if(plSoundMsg *soundMsg = plSoundMsg::ConvertNoRef(msg))
    {
        if( fFlags & kSkipFFSound )
        {
            return nil;
        }
        if(soundMsg->Cmd(plSoundMsg::kPlay) ||
            soundMsg->Cmd(plSoundMsg::kToggleState)  ||
            soundMsg->Cmd(plAnimCmdMsg::kAddCallbacks))
        {
            plSoundMsg *newSoundMsg = TRACKED_NEW plSoundMsg;
            newSoundMsg->fCmd = soundMsg->fCmd;
            newSoundMsg->fBegin = soundMsg->fBegin;
            newSoundMsg->fEnd = soundMsg->fEnd;
            newSoundMsg->fLoop = soundMsg->fLoop;
            newSoundMsg->fSpeed = soundMsg->fSpeed;
            newSoundMsg->fTime = soundMsg->fTime;
            newSoundMsg->fIndex = soundMsg->fIndex;
            newSoundMsg->fRepeats = soundMsg->fRepeats;
            newSoundMsg->fPlaying = soundMsg->fPlaying;
            newSoundMsg->fNameStr = soundMsg->fNameStr;
            newSoundMsg->fVolume = soundMsg->fVolume;

            // Remove the callbacks
            newSoundMsg->fCmd.SetBit(plSoundMsg::kAddCallbacks, false);

            if(newSoundMsg->Cmd(plSoundMsg::kPlay))
            {
                newSoundMsg->fCmd.SetBit(plSoundMsg::kPlay, false);
                newSoundMsg->fCmd.SetBit(plSoundMsg::kFastForwardPlay);
                ResponderLog(ILog(plStatusLog::kWhite, "FF Sound Play Msg"));
            }
            else if(newSoundMsg->Cmd(plSoundMsg::kToggleState))
            {
                newSoundMsg->fCmd.SetBit(plSoundMsg::kToggleState, false);
                newSoundMsg->fCmd.SetBit(plSoundMsg::kFastForwardToggle);
                ResponderLog(ILog(plStatusLog::kWhite, "FF Sound Toggle State Msg"));
            }
            for (int i = 0; i < soundMsg->GetNumReceivers(); i++)
                newSoundMsg->AddReceiver(soundMsg->GetReceiver(i));

            return newSoundMsg;
        }
        ResponderLog(ILog(plStatusLog::kWhite, "FF Sound Non-Play/Toggle Msg"));
        hsRefCnt_SafeRef(msg);
        return msg;
    }
    else if (msg->ClassIndex() == CLASS_INDEX_SCOPED(plExcludeRegionMsg))
    {
        ResponderLog(ILog(plStatusLog::kWhite, "FF Exclude Region Msg"));
        hsRefCnt_SafeRef(msg);
        return msg;
    }
    else if (msg->ClassIndex() == CLASS_INDEX_SCOPED(plEnableMsg))
    {
        ResponderLog(ILog(plStatusLog::kWhite, "FF Visibility/Detector Enable Msg"));
        hsRefCnt_SafeRef(msg);
        return msg;
    }
    else if (msg->ClassIndex() == CLASS_INDEX_SCOPED(plResponderEnableMsg))
    {
        ResponderLog(ILog(plStatusLog::kWhite, "FF Responder Enable Msg"));
        hsRefCnt_SafeRef(msg);
        return msg;
    }
    else if (msg->ClassIndex() == CLASS_INDEX_SCOPED(plSimSuppressMsg))
    {
        ResponderLog(ILog(plStatusLog::kWhite, "FF Physical Enable Msg"));
        hsRefCnt_SafeRef(msg);
        return msg;
    }

    return nil;
}

void plResponderModifier::IFastForward(bool python)
{
    ResponderLog(ILog(plStatusLog::kGreen, "Fast Forward"));

    fCurCommand = 0;

    plResponderState& state = fStates[fCurState];

    while (fCurCommand < state.fCmds.Count())
    {
        plMessage *msg = state.fCmds[fCurCommand].fMsg;
        msg = IGetFastForwardMsg(msg, python);
        if (msg)
            plgDispatch::MsgSend(msg);

        fCurCommand++;
    }

    ResponderLog(ILog(plStatusLog::kGreen, "Reset"));

    fCurCommand = -1;
    ISetResponderState(state.fSwitchToState);

    plSynchEnabler enable(true);
    DirtySynchState(kSDLResponder, 0);
}

void plResponderModifier::Read(hsStream* stream, hsResMgr* mgr)
{
    plSingleModifier::Read(stream, mgr);

    Int8 numStates = stream->ReadByte();
    fStates.SetCount(numStates);
    for (Int8 i = 0; i < numStates; i++)
    {
        plResponderState& state = fStates[i];
        state.fNumCallbacks = stream->ReadByte();
        state.fSwitchToState = stream->ReadByte();

        Int8 j;

        Int8 numCmds = stream->ReadByte();
        state.fCmds.SetCount(numCmds);
        for (j = 0; j < numCmds; j++)
        {
            plResponderCmd& cmd = state.fCmds[j];

            plMessage* pMsg = plMessage::ConvertNoRef(mgr->ReadCreatable(stream));
            cmd.fMsg = pMsg;
            cmd.fWaitOn = stream->ReadByte();
        }

        state.fWaitToCmd.clear();
        Int8 mapSize = stream->ReadByte();
        for (j = 0; j < mapSize; j++)
        {
            Int8 wait = stream->ReadByte();
            Int8 cmd = stream->ReadByte();
            state.fWaitToCmd[wait] = cmd;
        }
    }

    ISetResponderState(stream->ReadByte());
    fEnabled = stream->Readbool();
    fFlags = stream->ReadByte();

    // attach responderSDLMod
    delete fResponderSDLMod;
    fResponderSDLMod = TRACKED_NEW plResponderSDLModifier;
    fResponderSDLMod->SetResponder(this);
}

void plResponderModifier::Write(hsStream* stream, hsResMgr* mgr)
{
    plSingleModifier::Write(stream, mgr);

    Int8 numStates = fStates.GetCount();
    stream->WriteByte(numStates);
    for (int i = 0; i < numStates; i++)
    {
        plResponderState& state = fStates[i];
        stream->WriteByte(state.fNumCallbacks);
        stream->WriteByte(state.fSwitchToState);

        Int8 numCmds = state.fCmds.GetCount();
        stream->WriteByte(numCmds);
        for (int j = 0; j < numCmds; j++)
        {
            plResponderCmd& cmd = state.fCmds[j];

            mgr->WriteCreatable(stream, cmd.fMsg);
            stream->WriteByte(cmd.fWaitOn);
        }

        Int8 mapSize = state.fWaitToCmd.size();
        stream->WriteByte(mapSize);
        for (WaitToCmd::iterator it = state.fWaitToCmd.begin(); it != state.fWaitToCmd.end(); it++)
        {
            stream->WriteByte(it->first);
            stream->WriteByte(it->second);
        }
    }

    stream->WriteByte(fCurState);
    stream->Writebool(fEnabled);
    stream->WriteByte(fFlags);
}

#include "../plPipeline/plDebugText.h"

bool plResponderModifier::fDebugAnimBox = false;

void plResponderModifier::IDebugAnimBox(bool start)
{
    plDebugText &debugTxt = plDebugText::Instance();

    UInt32 scrnWidth, scrnHeight;
    debugTxt.GetScreenSize(&scrnWidth, &scrnHeight);

    // Box size is 1/8 screen size
    UInt32 boxSize = scrnHeight / 8;

    // Draw box in lower left corner
    if (start)
        debugTxt.DrawRect(0, (UInt16)(scrnHeight-boxSize), (UInt16)boxSize, (UInt16)scrnHeight, 0, 255, 0);
    else
        debugTxt.DrawRect((UInt16)boxSize, (UInt16)(scrnHeight-boxSize), (UInt16)(boxSize*2), (UInt16)scrnHeight, 255, 0, 0);
}

void plResponderModifier::IDebugPlayMsg(plAnimCmdMsg* msg)
{
    // Create a stop callback so we can do a cue for that too
    plEventCallbackMsg *eventMsg = TRACKED_NEW plEventCallbackMsg;
    eventMsg->AddReceiver(GetKey());
    eventMsg->fRepeats = 0;
    eventMsg->fUser = -1;
    eventMsg->fEvent = kStop;
    msg->SetCmd(plAnimCmdMsg::kAddCallbacks);
    msg->AddCallback(eventMsg);
    hsRefCnt_SafeUnRef(eventMsg);

    IDebugAnimBox(true);
}

////////////////////////////////////////////////////////////////////////////////

#ifdef STATUS_LOG
static plStatusLog *gLog = nil;
static std::vector<std::string> gNoLogStrings;
#endif // STATUS_LOG

void plResponderModifier::NoLogString(const char* str)
{
#ifdef STATUS_LOG
    gNoLogStrings.push_back(str);
#endif // STATUS_LOG
}

void plResponderModifier::ILog(UInt32 color, const char* format, ...)
{
#ifdef STATUS_LOG
    if (!gLog)
        gLog = plStatusLogMgr::GetInstance().CreateStatusLog(15, "Responder", plStatusLog::kFilledBackground | plStatusLog::kDeleteForMe | plStatusLog::kDontWriteFile | plStatusLog::kAlignToTop);

    if (!format || *format == '\0')
        return;

    const char* keyName = GetKeyName();

    // Make sure this key isn't in our list of keys to deny
    for (int i = 0; i < gNoLogStrings.size(); i++)
    {
        if (strncmp(gNoLogStrings[i].c_str(), keyName, gNoLogStrings[i].length()) == 0)
            return;
    }

    // Format the log text
    char buf[256];
    va_list args;
    va_start(args, format);
    int numWritten = _vsnprintf(buf, sizeof(buf), format, args);
    hsAssert(numWritten > 0, "Buffer too small");
    va_end(args);

    // Strip the redundant part off the key name
    char logLine[512];
    const char* modPos = strstr("_ResponderModifier", keyName);
    if (modPos)
        strncpy(logLine, keyName, modPos - keyName);
    else
        strcpy(logLine, keyName);

    strcat(logLine, ": ");
    strcat(logLine, buf);

    gLog->AddLine(logLine, color);
#endif // STATUS_LOG
}