Source/NetSprocketSupport.c

/*
    File:       NetSprocketSupport.c
 
    Contains:   xxx put contents here xxx
 
    Version:    xxx put version here xxx
 
    Copyright:  © 1999 by Apple Computer, Inc., all rights reserved.
 
    File Ownership:
 
        DRI:                xxx put dri here xxx
 
        Other Contact:      xxx put other contact here xxx
 
        Technology:         xxx put technology here xxx
 
    Writers:
 
        (cjd)   Chris De Salvo
 
    Change History (most recent first):
 
       <SP7>      3/3/99    cjd     Updated chat window UI code
*/
 
//¥ ------------------------------------------------------------------------------------------  ¥
//¥
//¥ Copyright © 1996 Apple Computer, Inc., All Rights Reserved
//¥
//¥
//¥     You may incorporate this sample code into your applications without
//¥     restriction, though the sample code has been provided "AS IS" and the
//¥     responsibility for its operation is 100% yours.  However, what you are
//¥     not permitted to do is to redistribute the source as "DSC Sample Code"
//¥     after having made changes. If you're going to re-distribute the source,
//¥     we require that you make it clear in the source that the code was
//¥     descended from Apple Sample Code, but that you've made changes.
//¥
//¥     Authors:
//¥         Jamie Osborne
//¥
//¥ ------------------------------------------------------------------------------------------  ¥
 
//¥ ------------------------------  Includes
 
#include <ControlDefinitions.h>
#include <Dialogs.h>
#include <Fonts.h>
#include <Lists.h>
#include <QDOffScreen.h>
#include <Resources.h>
#include <Sound.h>
#include <TextEdit.h>
#include <TextUtils.h>
 
#include "CommonStuff.h"
#include "EventHandler.h"
#include "NetSprocketSupport.h"
 
//¥ ------------------------------  Private Definitions
//¥ ------------------------------  Private Types
 
enum
{
    kWaitForPlayersDialogID = 1000
};
 
enum
{
    kOKButton = 1, 
    kCancelButton,
    kPlayerListLabel,
    kPlayerList,
    kChatWindow,
    kChatTextEntry,
    kStatus
};
        
typedef struct DialogInfo
{
    ListHandle  playerList;
    TEHandle    teHandle;
    Str32       nameListBuffer[kMaxPlayers];
} DialogInfo;
 
//¥ ------------------------------  Private Variables
//¥ ------------------------------  Private Functions
 
static void GetChooserName(Str255 name);
static void SendChatText(StringPtr inText, NSpPlayerID inTo);
static void DoHandleMessage(NSpMessageHeader *inMessage);
static pascal void PlayerListProc(WindowPtr dialog, SInt16 itemNo);
static pascal void ChatViewProc(WindowPtr dialog, SInt16 itemNo);
static void SetDialogtemUserProc(DialogPtr dialog, SInt16 item, UserItemUPP theProc);
static void WatchForPlayers(DialogPtr dialog);
static OSStatus SendWelcomeMessage(NSpPlayerID inID);
static void MakePlayerList(DialogPtr dialog);
static void SendText(DialogPtr dialog);
 
static pascal Boolean DialogEventFilter(DialogPtr dialog, EventRecord *event, SInt16 *itemHit);
 
static void DoHandleChatTextMessage(ChatTextMessage *inMessage, DialogPtr dialog);
static void PrintPlayerText(StringPtr inText, NSpPlayerID inPlayer, TEHandle inTEHandle);
 
static void DoHandlePlayerInput(PlayerInputMessage *inMessage);
static void DoHandleEndGameMessage(void);
static void DoHandleStartGame(StartGameMessage *inMessage);
static void DisableDialogItem(DialogPtr theDialog, SInt16 theItem);
 
//¥ ------------------------------  Public Variables
 
NSpGameReference    gNetGame = NULL;
Boolean             gIAmHost = false;
Str31               kNetworkGameName = "\pNetSprocket Game";
Str31               kJoinDialogLabel = "\pChoose a Game:";
Str31               kNBPType = "\pInvd";        // This should be the same as kGameID
NetworkState        gNetState;
Str31               kSeparatorString = "\p: ";
SInt8               kReturn = '\r';
Boolean             gGotEndGameMessage = false;
Boolean             gReceivedInput;
Boolean             gNetSprocketPresent;
 
#pragma mark === Utility functions ===
 
//¥ --------------------    NetSprocketPresent
//¥
//¥ returns TRUE if networking is present, FALSE otherwise
 
Boolean
NetSprocketPresent(void)
{
    return (NSpInitialize != nil);
}
 
//¥ --------------------    GetChooserName
 
static void
GetChooserName(Str255 name)
{
StringHandle    userName;
    
    userName = GetString(-16096);
    FAIL_NIL(userName, "GetString for Chooser Name failed");
    
    CopyPStr(*userName, name);
    ReleaseResource ((Handle) userName);
 
error:
    return;
}
 
#pragma mark === Networking stuff ===
 
//¥ --------------------    InitNetworking
 
Boolean
InitNetworking(void)
{
OSStatus    status;
    
    if (! NetSprocketPresent())
        return (false);
        
    //¥ Initialize NetSprocket
    status = NSpInitialize(kStandardMessageSize, kBufferSize, kQElements, kGameID, kTimeout);
    FAIL_OSSTATUS(status, "NSpInitialize failed");
    if (status != noErr)
        return (false);
 
    gNetState = kSlacking;
    
    return (true);
    
error:
    return (false);
}
 
//¥ --------------------    ShutdownNetworking
 
void
ShutdownNetworking(void)
{
OSStatus    status;
    
    if (gNetGame)
        status = NSpGame_Dispose( gNetGame, kNSpGameFlag_ForceTerminateGame );
 
    gNetState = kSlacking;
    gNetGame = NULL;
}
 
 
//¥ --------------------    DoHostGame
//¥
//¥ Returns TRUE if the user says OK (and NetSprocket doesn't return an error), false otherwise
 
Boolean
DoHostGame(void)
{
OSStatus                    status = noErr;
NSpProtocolListReference    theList = NULL;
Str31                       playerName, gameName, password;
Boolean                     OKHit;
    
    status = NSpProtocolList_New(NULL, &theList);
    FAIL_OSSTATUS(status, "Couldn't create a protocol list");
        
    //  Do the host dialog
    GetChooserName(playerName);
    CopyPStr(kNetworkGameName, gameName);
    password[0] = 0;
 
    OKHit = NSpDoModalHostDialog(theList, gameName, playerName, password, NULL);
    if (!OKHit)
        return (false);
        
    //  Now host the game
    status = NSpGame_Host(&gNetGame, theList, kMaxPlayers, gameName,
                password, playerName, 0, kNSpClientServer, 0);
 
    FAIL_OSSTATUS(status, "NSpGame_Host returned an error");
    
    NSpProtocolList_Dispose(theList);
    gIAmHost = true;
    gNetState = kHosting;
    
    // let all the players join
    OKHit = WaitForAllPlayers();
 
    if (OKHit == false)
    {
        ShutdownNetworking();
        return (false);     
    }
    else
    {
        gNetState = kStarting;
        SendStartGame();
    }
    
    return (true);
    
error:
 
    if (theList)
        NSpProtocolList_Dispose(theList);
        
    return (false);
}
 
//¥ --------------------    DoJoinGame
//¥
//¥ Returns TRUE if the user says OK (and NetSprocket doesn't return an error), false otherwise
 
Boolean
DoJoinGame(void)
{
NSpAddressReference theAddress;
Str31               name;
Str31               password;
OSStatus            status;
Boolean             OKHit = true;
    
    GetChooserName(name);
    password[0] = 0;
    
    gIAmHost = false;
    
    theAddress = NSpDoModalJoinDialog(kNBPType, kJoinDialogLabel, name, password, NULL);
    if (theAddress == NULL)     // The user cancelled
        return (false);
        
    status = NSpGame_Join(&gNetGame, theAddress, name, password, 0, NULL, 0, 0);
    FAIL_OSSTATUS(status, "NSpGame_Join returned an error");
    
    gNetState = kJoining;
 
    OKHit = WaitForAllPlayers();
    if (OKHit == false)
    {
        gNetState = kSlacking;
        ShutdownNetworking();
    }
    
    return OKHit;
    
error:
    return (false);
}
 
//¥ --------------------    HandleNetworking
 
void
HandleNetworking(void)
{
NSpMessageHeader    *theMessage;
    
    if (gNetGame == NULL)
        return;
 
    theMessage = NSpMessage_Get(gNetGame);
    if (theMessage != NULL)
    {
        DoHandleMessage(theMessage);
        NSpMessage_Release(gNetGame, theMessage);
    }
}
 
//¥ --------------------    DoHandleMessage
 
static void
DoHandleMessage(NSpMessageHeader *inMessage)
{
    switch (inMessage->what)
    {
        case kNSpPlayerJoined:
            break;
            
        case kNSpPlayerLeft:
            break;
            
        case kChatTextMessage:
            DoHandleChatTextMessage((ChatTextMessage *)inMessage, nil);
            break;
            
        case kStartGameMessage:
            DoHandleStartGame((StartGameMessage *) inMessage);
            break;
            
        case kInputMessage:
            DoHandlePlayerInput((PlayerInputMessage *) inMessage);
            break;
            
        case kEndGameMessage:
            DoHandleEndGameMessage();
            break;
            
        default:
            break;
    }
}
 
//¥ --------------------    WatchForPlayers
 
static void
WatchForPlayers(DialogPtr dialog)
{
NSpMessageHeader    *theMessage;
    
    if (gNetGame == NULL)
        return;
 
    while ((theMessage = NSpMessage_Get(gNetGame)) != NULL)
    {
        if (theMessage->what == kNSpPlayerJoined)
        {
            if (gNetState == kHosting)
                SendWelcomeMessage(((NSpPlayerJoinedMessage *) theMessage)->playerInfo.id);
            
            MakePlayerList(dialog); 
        }
        else if (theMessage->what == kNSpPlayerLeft) 
        {
            MakePlayerList(dialog); 
        }
        else if (theMessage->what == kChatTextMessage)
        {
            DoHandleChatTextMessage((ChatTextMessage *)theMessage, dialog);
        }
        else
        {
            DoHandleMessage(theMessage);
        }
            
        NSpMessage_Release(gNetGame, theMessage);
    }
}
 
//¥ --------------------    SendWelcomeMessage
 
static OSStatus
SendWelcomeMessage(NSpPlayerID inID)
{
#pragma unused (inID)
OSStatus    status = noErr;
    
    return (status);
}
 
#pragma mark === Dialog Manager Cruft
 
//¥ --------------------    SetDialogtemUserProc
 
static void
SetDialogtemUserProc(DialogPtr dialog, SInt16 item, UserItemUPP theProc)
{
Rect    r;
SInt16  itemType;
Handle  itemH;
 
    GetDialogItem(dialog, item, &itemType, &itemH, &r);
    itemH = (Handle) theProc;
    SetDialogItem(dialog, item, itemType, itemH, &r);
}
 
//¥ --------------------    MakePlayerList
 
static void
MakePlayerList(DialogPtr dialog)
{
SInt16                  i;
Cell                    theCell;
NSpPlayerEnumerationPtr players;
OSStatus                status;
DialogInfo              *theInfo = (DialogInfo *) GetWRefCon(dialog);
ListHandle              theList = theInfo->playerList;
 
    LSetDrawingMode(true, theList);                         // turn on updating
 
    status = NSpPlayer_GetEnumeration(gNetGame, &players);
    FAIL_OSSTATUS(status, "NSpPlayer_GetEnumeration returned an error");
        
    for (i = 0; i < players->count; i++)
        CopyPStr(players->playerInfo[i]->name, theInfo->nameListBuffer[i]);
 
    /* DELETE ALL EXISTING ROWS IN LIST */
    LDelRow(0, 0, theList);
 
    /* ADD NAMES TO LIST */
    if (players->count > 0)
        LAddRow(players->count, 0, theList);        // create rows
 
    for (i = 0; i < players->count; i++)
    {
        theCell.h = 0;
        theCell.v = i;
        LSetCell(&theInfo->nameListBuffer[i][1], theInfo->nameListBuffer[i][0], theCell, theList);
    }
 
    NSpPlayer_ReleaseEnumeration(gNetGame, players);
    
error:
    return;
}
 
//¥ --------------------    WaitForAllPlayers
 
Boolean
WaitForAllPlayers(void)
{
DialogPtr       theDialog = GetNewDialog(kWaitForPlayersDialogID, NULL, (WindowPtr) -1);
ListHandle      playerList;
SInt16          itemHit;
Boolean         okHit = false;
Rect            r, dataBounds = {0,0,0,1};
Point           cellSize = {0,0};
SInt16          itemType;
Handle          itemH;
DialogInfo      info;
TextStyle       theStyle;
ControlHandle   theControl;
    
    ModalFilterUPP theFilter = NewModalFilterProc(DialogEventFilter);
    UserItemUPP playerListProc = NewUserItemProc(PlayerListProc);
    UserItemUPP chatViewProc = NewUserItemProc(ChatViewProc);
    
    // NOTE: check okUserItemProcUPP != NULL
    
    if (theDialog == NULL)
        return (false);
    
    SetPort(theDialog);
    SetDialogDefaultItem(theDialog, kOKButton);
    SetDialogCancelItem(theDialog, kCancelButton);
 
    SetDialogtemUserProc(theDialog, kPlayerList, playerListProc);
    SetDialogtemUserProc(theDialog, kChatWindow, chatViewProc);
 
    (void) GetDialogItemAsControl(theDialog, kChatTextEntry, &theControl);
    (void) SetKeyboardFocus(theDialog, theControl, kControlLabelPart);
 
 
    //  Now create the list of players
    GetDialogItem(theDialog, kPlayerList, &itemType, &itemH, &r);
    r.right -= 15;
    
    playerList = LNew(&r, &dataBounds, cellSize, 0, theDialog, true,
                    false, false, true);
    
    info.playerList = playerList;
 
    //  Set up the text edut stuff
    GetDialogItem(theDialog, kChatWindow, &itemType, &itemH, &r);
    InsetRect(&r, 2, 2);
    
    info.teHandle = TEStyleNew(&r, &r);
    TEAutoView(true, info.teHandle);
 
    //  Set up the default drawing info
    theStyle.tsFont = kFontIDMonaco;
    theStyle.tsSize = 9;
    theStyle.tsColor.red = 0;
    theStyle.tsColor.green = 0;
    theStyle.tsColor.blue = 0;
    theStyle.tsFace = normal;
    TESetStyle(doAll, &theStyle, true, info.teHandle);
    
    //  If we're the joiner, disable the OK button
    if (gNetState == kJoining)
        DisableDialogItem(theDialog, kOKButton);
    
    //  Save our custom stuff in the refcon
    SetWRefCon(theDialog, (SInt32) &info);
    
    ShowWindow(theDialog);
    DrawControls(theDialog);
        
    do
    {
        ModalDialog(theFilter, &itemHit);
    } while (itemHit != kOKButton && itemHit != kCancelButton);
    
    LDispose(playerList);
    DisposeDialog(theDialog);
    
    DisposeRoutineDescriptor(theFilter);
    DisposeRoutineDescriptor(playerListProc);
    DisposeRoutineDescriptor(chatViewProc);
    
    okHit = (itemHit == kOKButton || gNetState == kRunning);
 
    return (okHit);
}
 
//¥ --------------------    DisableDialogItem
 
static void
DisableDialogItem(DialogPtr theDialog, SInt16 theItem)
{
SInt16  theType;
Rect    theRect;
Handle  theHandle;
 
    if (! theDialog)
        return;
 
    GetDialogItem(theDialog, theItem, &theType, &theHandle, &theRect);
 
    if (! theHandle)
        return;
 
    HiliteControl((ControlHandle) theHandle, 255);
    theType |= kItemDisableBit;
    SetDialogItem(theDialog, theItem, theType, theHandle, &theRect);
}
 
#pragma mark === Callback Procs ===
 
//¥ --------------------    PlayerListProc
 
static pascal void
PlayerListProc(WindowPtr dialog, SInt16 itemNo)
{
Rect        r;
SInt16      itemType;
Handle      item;
GrafPtr     theWindow = (GrafPtr) dialog;
DialogInfo  *theInfo = (DialogInfo *)GetWRefCon(dialog);
ListHandle  list = theInfo->playerList;
 
    if (itemNo == kPlayerList)
    {   
        GetDialogItem(dialog, itemNo, &itemType, &item, &r);
        InsetRect(&r, -1, -1);
        FrameRect(&r);
        LUpdate(theWindow->visRgn, list);
    }
}
 
//¥ --------------------    ChatViewProc
 
static pascal void
ChatViewProc(WindowPtr dialog, SInt16 itemNo)
{
Rect        r;
SInt16      itemType;
Handle      item;
DialogInfo *theInfo;
    
    GetDialogItem(dialog, itemNo, &itemType, &item, &r);
    theInfo = (DialogInfo *)GetWRefCon(dialog);
 
    FrameRect(&r);
    InsetRect(&r, 2, 2);
    TEUpdate(&r, theInfo->teHandle);
    
}
 
//¥ --------------------    DialogEventFilter
 
static pascal Boolean
DialogEventFilter(DialogPtr dialog, EventRecord *event, SInt16 *itemHit)
{
OSErr               err;
CGrafPtr            oldPort;
GDHandle            oldGD;
WindowRef           window;
ModalFilterUPP      modalProc;
Rect                itemRect;
SInt16              key;
SInt16              thePart;
Boolean             accepted = false;
Point theP;
Boolean             hit;
DialogInfo          *theInfo;
    
    GetGWorld(&oldPort, &oldGD);
    SetGWorld((CGrafPtr)dialog, nil);
 
    IdleControls(dialog);
 
    WatchForPlayers(dialog);
    if (gNetState == kRunning)
    {
        *itemHit = kOKButton;
        return (true);
    }
 
    switch(event->what) 
    {
        case keyDown:
        case autoKey:
            key = event->message & charCodeMask;
            if (key == 0x0D)
            {
                SendText(dialog);
                accepted = true;
            }
            else if (key == '\t')
            {
                if (event->modifiers & shiftKey)
                    ReverseKeyboardFocus(dialog);
                else
                    AdvanceKeyboardFocus(dialog);
            }
            else
            {
                accepted = false;
            }
                
            break;
    
        case updateEvt:
            if (event->message == (SInt32)dialog) 
            {
                // in case a screen saver activates.
                UpdateDialog(dialog, dialog->visRgn);
 
                SetCursor(&qd.arrow);
            }   
            break;
 
        case activateEvt:
            accepted = true;
            break;
            
        case mouseDown: 
            thePart = FindWindow(event->where,&window);
    
            switch (thePart) 
            {
                case inContent:
                    theP = event->where;
                    GlobalToLocal(&theP);
 
                    theInfo = (DialogInfo *)GetWRefCon(dialog);
                    itemRect = (*theInfo->playerList)->rView;
                    
                    // add the scroll bar back in for hit testing (remember we took it out earlier)
                    itemRect.right += 16;
                    
                    // See if they clicked in our list!
                    if (PtInRect(theP, &itemRect))
                    {
                        hit = LClick(theP, nil, theInfo->playerList);
                        // if they double-clicked the list, return 1, as if the OK button had been pressed
                        if (hit)
                            *itemHit = kOKButton;
                        else
                            *itemHit = kPlayerList;
                        
                        // tell the Dialog Manager that we handled this click, it can stop searching for a click-owner
                        accepted = true;
                    }
                    break;
    
                case inSysWindow:
                    SysBeep(30);
                    break;
    
                case inDrag:
                    if (window == dialog) 
                    {
                        DragWindow(dialog, event->where,&qd.screenBits.bounds);
                        accepted = true;
                    }
                    break;
            }
            break;      
    }
    
    if (false == accepted) 
    {
        err = GetStdFilterProc(&modalProc);
        if (err == noErr)
            accepted = CallModalFilterProc(modalProc, dialog, event, itemHit);
    }
    
    SetGWorld(oldPort, oldGD);
 
    return (accepted);
}
 
//¥ --------------------    SendText
 
static void
SendText(DialogPtr dialog)
{
ControlHandle   item;
Str255          theText;
    
    (void) GetDialogItemAsControl(dialog, kChatTextEntry, &item);
    GetDialogItemText((Handle) item, theText);
    SetDialogItemText((Handle) item, "\p");
    DrawOneControl(item);
    
    if (theText[0] > 0)
        SendChatText(theText, kNSpAllPlayers);
 
}
 
#pragma mark =====
 
//¥ --------------------    SendChatText
 
static void
SendChatText(StringPtr inText, NSpPlayerID inTo)
{
ChatTextMessage theMessage;
OSStatus        status;
    
    theMessage.h.to = inTo;
    theMessage.h.what = kChatTextMessage;
    theMessage.h.messageLen = sizeof(ChatTextMessage);
    CopyPStr(inText, theMessage.text);
    
    status = NSpMessage_Send(gNetGame, &theMessage.h, kNSpSendFlag_Junk | kNSpSendFlag_SelfSend);
}
 
//¥ --------------------    SendStartGame
 
void
SendStartGame(void)
{
StartGameMessage    theMessage;
OSStatus            status;
    
    NSpClearMessageHeader(&theMessage.h);
    theMessage.h.to = kNSpAllPlayers;
    theMessage.h.what = kStartGameMessage;
    theMessage.h.messageLen = sizeof(StartGameMessage);
    theMessage.seed = qd.randSeed;
    
    status = NSpMessage_Send(gNetGame, &theMessage.h, kNSpSendFlag_Registered | kNSpSendFlag_SelfSend);
}
 
//¥ --------------------    SendInputState
 
void
SendInputState(Boolean left, Boolean right, Boolean fire)
{
PlayerInputMessage  theMessage;
OSStatus    status;
    
    NSpClearMessageHeader(&theMessage.h);
    theMessage.h.to = kNSpAllPlayers;
    theMessage.h.what = kInputMessage;
    theMessage.h.messageLen = sizeof(PlayerInputMessage);
    theMessage.left = left;
    theMessage.right = right;
    theMessage.fire = fire;
 
    status = NSpMessage_Send(gNetGame, &theMessage.h, kNSpSendFlag_Registered);
}
 
//¥ --------------------    SendEndGame
 
void
SendEndGame(void)
{
OSStatus    status;
NSpMessageHeader theMessage;
    
    NSpClearMessageHeader(&theMessage);
    theMessage.to = kNSpAllPlayers;
    theMessage.what = kEndGameMessage;
    theMessage.messageLen = sizeof(NSpMessageHeader);
    
    status = NSpMessage_Send(gNetGame, &theMessage, kNSpSendFlag_Registered);
}
 
//¥ --------------------    DoHandleChatTextMessage
 
static void
DoHandleChatTextMessage(ChatTextMessage *inMessage, DialogPtr dialog)
{
DialogInfo  *theInfo;
    
    if (! dialog)
        return;
        
    theInfo = (DialogInfo *)GetWRefCon(dialog);
    PrintPlayerText(inMessage->text, inMessage->h.from, theInfo->teHandle);
}
 
//¥ --------------------    DoHandleStartGame
 
static void
DoHandleStartGame(StartGameMessage *inMessage)
{
    gNetState = kRunning;
    qd.randSeed = inMessage->seed;
}
 
//¥ --------------------    DoHandlePlayerInput
 
static void
DoHandlePlayerInput(PlayerInputMessage *inMessage)
{
    if (gIAmHost)
    {
        gGameKeys.redLeft = inMessage->left;
        gGameKeys.redRight = inMessage->right;
        gGameKeys.redFire = inMessage->fire;
    }
    else
    {
        gGameKeys.greenLeft = inMessage->left;
        gGameKeys.greenRight = inMessage->right;
        gGameKeys.greenFire = inMessage->fire;
    }   
    gReceivedInput = true;
}
 
//¥ --------------------    DoHandleEndGameMessage
 
static void
DoHandleEndGameMessage(void)
{
    gGotEndGameMessage = true;
}
 
//¥ --------------------    PrintPlayerText
 
static void
PrintPlayerText(StringPtr inText, NSpPlayerID inPlayer, TEHandle inTEHandle)
{
NSpPlayerInfoPtr    info;
    
    NSpPlayer_GetInfo(gNetGame, inPlayer, &info);
    
    TEInsert(&info->name[1], info->name[0], inTEHandle);
    TEInsert(&kSeparatorString[1], kSeparatorString[0], inTEHandle);
    TEInsert(&inText[1], inText[0], inTEHandle);
    TEInsert(&kReturn, 1, inTEHandle);
 
    //¥ Select end of the TE text
    TESetSelect(32767L, 32767L, inTEHandle);
 
    //¥ Scroll it into view if necessary
    TESelView(inTEHandle);
}