NetStuff.cp

/*
    File:       NetStuff.cp
 
    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):
 
       <SP8>      3/9/99    cjd     Added new enumerations depending on whether you're the client or
                                    the host.
*/
 
//¥ ------------------------------------------------------------------------------------------  ¥
//¥
//¥ Copyright © 1996, 1997 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
//¥         Chris De Salvo
//¥
//¥ ------------------------------------------------------------------------------------------  ¥
 
//¥ ------------------------------  Includes
 
#include <Fonts.h>
#include <MacWindows.h>
#include <Menus.h>
#include <OpenTptInternet.h>
#include <Resources.h>
#include <TextUtils.h>
 
#include "NetStuff.h"
 
#include <PLStringFuncs.h>
#include <stdio.h>
#include <string.h>
 
//¥ ------------------------------  Private Definitions
 
//¥ If this is set to 1 then the player enumeration menu item iterates by calling NSpPlayer_GetInfo
//¥ rather than using the data in the PlayerEnumeration data structure.  This is merely for coverage
//¥ testing of the GetInfo call on the client side.
#define USE_PLAYER_GET_INFO         1
 
//¥ ------------------------------  Private Types
 
enum
{
    kStandardMessageSize    = 1500,
    kBufferSize             = 0, 
    kQElements              = 0,
    kTimeout                = 5000,
    kMaxPlayers             = 8
};
 
//¥ ------------------------------  Private Variables
 
static Str31                gameName;
static Str31                password;
static Str31                playerName;
static Str31                kJoinDialogLabel = "\pChoose a Game:";
static Boolean              gInCallback = false;
static UInt32               gLastUpdate = 0;
static UInt32               gUpdateFrequency = 2;
static PlayerInputMessage   gPlayerMessage;
static UInt32               gPlayerMessageSize = 400;
static GameStateMessage     gGameStateMessage;
static UInt32               gGameStateMessageSize = 1024;
static UInt32               gSendOptions = kNSpSendFlag_Normal;
static UInt32               gHostSendOptions = kNSpSendFlag_Registered | kNSpSendFlag_SelfSend;
static MenuHandle           gHostMenuH;
static UInt32               gHostUpdateFrequency = 2;
static UInt32               gLastHostUpdate = 0;
static WindowStuff          gWindowStuff[8];
static NSpPlayerID          gMyPlayerID = 0;
static Str255               gContextString = "\pNetSprocket Test Context!!";
 
//¥ ------------------------------  Private Functions
 
static WindowStuff *GetFreeWindowStuff(void);
static void ReleaseWindowStuff(WindowStuff *inStuff);
static WindowStuff *GetPlayersWindowStuff(NSpPlayerID inPlayer);
static WindowPtr FindPlayersWindow(NSpPlayerID inPlayer);
static void InvalPlayerWindow(NSpPlayerID inPlayer);
static pascal Boolean MessageHandler(NSpGameReference inGameRef, NSpMessageHeader *inMessage, void *inContext);
static void InitNetMenu(MenuHandle menu);
static void GetChooserName(Str255 name);
static void CloseAllWindows(void);
static void AddHostMenu(void);
static void DoAdjustMenu(MenuRef m, UInt32 options, UInt32 updateFrequency, UInt32 messageSize);
static void DoSendLeaveMessage(NSpPlayerID inID);
static void DoHandleMessage(NSpMessageHeader *inMessage);
static void GetMessages(void);
static void UpdateNetWindows(void);
 
static void GetInfoEnumeratePlayers(void);
static void EnumeratePlayers(void);
 
//¥ ------------------------------  Public Variables
 
Boolean             gHost = false;
NSpGameReference    gNetGame = nil;
 
//¥ --------------------    GetFreeWindowStuff
 
// this is not reentrant
static WindowStuff *
GetFreeWindowStuff(void)
{
    for (int i = 0; i < 8; i++)
    {
        if (gWindowStuff[i].id == 0)
            return &gWindowStuff[i];
    }
    
    return nil;
}
 
//¥ --------------------    ReleaseWindowStuff
 
static void
ReleaseWindowStuff(WindowStuff *inStuff)
{
    if (inStuff == nil)
        printf("passed in nill stuff pointer!\n");
    else
        memset(inStuff, 0, sizeof(WindowStuff));
}
 
//¥ --------------------    GetPlayersWindowStuff
 
static WindowStuff *
GetPlayersWindowStuff(NSpPlayerID inPlayer)
{
    for (int i = 0; i < 8; i++)
    {
        if (gWindowStuff[i].id == inPlayer)
            return &gWindowStuff[i];
    }
    
    return nil;
 
}
 
//¥ --------------------    FindPlayersWindow
//¥
//¥     Finds the window associated with the given player
static WindowPtr
FindPlayersWindow(NSpPlayerID inPlayer)
{
    WindowPtr   w;
    WindowStuff *stuff;
    w = FrontWindow();
 
    while (w != nil)
    {
        stuff = (WindowStuff *) GetWRefCon(w);  
        if (stuff->id == inPlayer)
            break;
        else
            w = GetNextWindow(w);
    }
    
    return w;
}
 
//¥ --------------------    InvalPlayerWindow
 
static void
InvalPlayerWindow(NSpPlayerID inPlayer)
{
WindowPtr   w = FindPlayersWindow(inPlayer);
 
    if (w)
    {
    GrafPtr     oldPort;
    
        GetPort(&oldPort);
        SetPort(w);
        InvalRect(&w->portRect);
        SetPort(oldPort);
    }
}
 
 
//¥ --------------------    MessageHandler
//¥
//¥ this function gets called every time NetSprocket receives a new message.
//¥ we use it to prevent our message q from getting too full if we don't idle enough
//¥ a good example is when the user has clicked in the menu bar and is reading menus
 
static pascal Boolean
MessageHandler(NSpGameReference inGameRef, NSpMessageHeader *inMessage, void *inContext)
{
    gInCallback = true;
//  if it's a system message, just leave it in the q, so that we can handle it later (when we're
//  not at interrupt time)
    if (inMessage->what & kNSpSystemMessagePrefix)
        return true;    // tells NetSprocket to put the message in the message Q
    else    // it's one of ours
    {
 
        if (inMessage->what == kPlayerInputMessage && gHost)
        {           
            WindowStuff *stuff = GetPlayersWindowStuff(inMessage->from);
            if (stuff)
            {
                stuff->lastMessage = inMessage->id;
                stuff->changed = true;
            }               
            return false;
        }
        else if (inMessage->what == kGameStateMessage && !gHost)
        {
            WindowStuff *stuff = GetPlayersWindowStuff(gMyPlayerID);
            if (stuff)
            {
                stuff->lastMessage = inMessage->id;
                stuff->changed = true;
            }               
            return false;
        }
    }
    gInCallback = false;
 
    //¥ If it's some state we haven't considered then better leave it in the queue
    return (true);
}
 
//¥ --------------------    GetChooserName
 
static void
GetChooserName(Str255 name)
{
StringHandle    userName;
    
    userName = GetString(-16096);
    if (userName == nil)
    {
        name[0] = 0;
    }
    else
    {   
        PLstrcpy(name, *userName);
        ReleaseResource ((Handle) userName);
    }
}
 
//¥ --------------------    RefreshWindow
 
void
RefreshWindow(WindowPtr inWindow)
{
    WindowStuff *stuff = (WindowStuff *) GetWRefCon(inWindow);
    
    if (stuff->lastMessage != 0)
        NumToString(stuff->lastMessage, stuff->text);
 
    EraseRect(&inWindow->portRect);
    
    MoveTo(20,20);
    DrawString(stuff->text);    
}
 
 
//¥ --------------------    InitNetMenu
 
static void
InitNetMenu(MenuHandle menu)
{
    SetItemMark(menu, iJunk, noMark);
    SetItemMark(menu, iNormal, noMark);
    SetItemMark(menu, iRegistered, noMark);
    SetItemMark(menu, iBlocking, noMark);
    SetItemMark(menu, i1X, noMark);
    SetItemMark(menu, i10X, noMark);
    SetItemMark(menu, i30X, noMark);
    SetItemMark(menu, iNoLimit, noMark);
    SetItemMark(menu, iLess500, noMark);
    SetItemMark(menu, i1K, noMark);
    SetItemMark(menu, i10K, noMark);
    SetItemMark(menu, i100K, noMark);
}
 
//¥ --------------------    InitNetworking
 
OSStatus
InitNetworking(NSpGameID inGameID)
{
    OSStatus    status;
    
    printf("Initializing NetSprocket... ");
    memset(gWindowStuff, 0, sizeof(WindowStuff) * 8);
    status = NSpInitialize(kStandardMessageSize, kBufferSize, kQElements, inGameID, kTimeout);
    if (status != noErr)
        printf("NSpInitialize returned error %d\n", status);
    else
        printf("Done\n");
        
//  Install an async message handler.  We do this so that we won't get hosed if we
//  don't call HandleNetworking often enough (such as a tight loop for mouse-down
    status = NSpInstallAsyncMessageHandler(MessageHandler, gContextString);
    if (status != noErr)
        printf("NSpInstallAsyncMessageHandler returned error %d\n", status);
    
    MenuHandle h = GetMenuHandle(131);
    if (h)
        InitNetMenu(h);
    
    return status;
}
 
 
//¥ --------------------    CloseAllWindows
 
static void
CloseAllWindows(void)
{
WindowPtr   w, nextWind;
 
    w = FrontWindow();
 
    while (w != nil)
    {
        nextWind = GetNextWindow(w);
        WindowStuff *stuff = (WindowStuff *) GetWRefCon(w);
        if (stuff)
        {
            DisposePtr((Ptr) stuff);
            DisposeWindow(w);
        }       
        w = nextWind;
    }
}
 
 
//¥ --------------------    ShutdownNetworking
 
void
ShutdownNetworking(void)
{
OSStatus    status;
    
    if (gNetGame)
    {
        status = NSpGame_Dispose( gNetGame, 0 );
        if (status != noErr)
        {
            printf("NSpGame_Dispose returned error %d\n", status);
//          I REALLY want it to shutdown
            status = NSpGame_Dispose( gNetGame, kNSpGameFlag_ForceTerminateGame );  
            if (status != noErr)
                printf("NSpGame_Dispose (force) returned error %d\n", status);
        }
        
        if (gHost)
        {
            DeleteMenu(132);
            ReleaseResource((Handle) gHostMenuH);
            DrawMenuBar();
        }
 
        gNetGame = NULL;
    }
 
//  if there are any windows left, kill them
    CloseAllWindows();
}
 
 
//¥ --------------------    AddHostMenu
 
static void
AddHostMenu(void)
{
    gHostMenuH = GetMenu(132);
    if (gHostMenuH == nil)
        DebugStr("\pCouldn't find menu resource!");
    else
        InsertMenu(gHostMenuH, 0);
        
    InitNetMenu(gHostMenuH);
    
    DrawMenuBar();
}
 
//¥ --------------------    HandleNetMenuChoice
 
void
HandleNetMenuChoice(short menu, short item)
{
    MenuHandle h = GetMenuHandle(menu);
    UInt32 *options;
    UInt32 *frequency;
    UInt32 *size;
 
    if (menu == 132)    // host menu
    {
        options = &gHostSendOptions;
        frequency = &gHostUpdateFrequency;
        size = &gGameStateMessageSize;
    }
    else
    {
        options = &gSendOptions;
        frequency = &gUpdateFrequency;
        size = &gPlayerMessageSize;
    }
                
    switch(item)
    {
        case iJunk:
            *options &= 0xFF0FFFFF;
            *options |= kNSpSendFlag_Junk;
        break;
        case iNormal:
            *options &= 0xFF0FFFFF;
            *options |= kNSpSendFlag_Normal;
        break;
        case iRegistered:
            *options &= 0xFF0FFFFF;
            *options |= kNSpSendFlag_Registered;
        break;
        case iBlocking:
            if (*options & kNSpSendFlag_Blocking)
                *options &= ~kNSpSendFlag_Blocking;
            else
                *options |= kNSpSendFlag_Blocking;
        break;
        case i1X:
            *frequency = 60;
            break;
            
        case i10X:
            *frequency = 6;
            break;
            
        case i30X:
            *frequency = 2;
            break;
            
        case iNoLimit:
            *frequency = 0;
            break;
            
        case iLess500:
            *size = 400;
            break;
            
        case i1K:
            *size = 1024;
            break;
            
        case i10K:
            *size = 10240;
            break;
            
        case i100K:
            *size = 102400;
            break;
            
        case iEnumerate:
#if USE_PLAYER_GET_INFO
            if (131 != menu)
                GetInfoEnumeratePlayers();
            else
#endif
                EnumeratePlayers();
            break;
            
        default:
        break;
    }
    
}
 
//¥ --------------------    DoAdjustMenu
 
static void
DoAdjustMenu(MenuRef m, UInt32 options, UInt32 updateFrequency, UInt32 messageSize)
{
    if (options & kNSpSendFlag_Junk)
        SetItemMark(m, iJunk, checkMark);
    else
        SetItemMark(m, iJunk, noMark);
 
    if (options & kNSpSendFlag_Normal)
        SetItemMark(m, iNormal, checkMark);
    else
        SetItemMark(m, iNormal, noMark);
 
    if (options & kNSpSendFlag_Registered)
        SetItemMark(m, iRegistered, checkMark);
    else
        SetItemMark(m, iRegistered, noMark);
        
    if (options & kNSpSendFlag_Blocking)
        SetItemMark(m, iBlocking, checkMark);
    else
        SetItemMark(m, iBlocking, noMark);
        
    SetItemMark(m, i1X, noMark);
    SetItemMark(m, i10X, noMark);
    SetItemMark(m, i30X, noMark);
    SetItemMark(m, iNoLimit, noMark);
    switch(updateFrequency)
    {
        case 60:
            SetItemMark(m, i1X, checkMark);
        break;
        case 6:
            SetItemMark(m, i10X, checkMark);
        break;
        case 2:
            SetItemMark(m, i30X, checkMark);
        break;
        default:
            SetItemMark(m, iNoLimit, checkMark);
        break;
    }
 
    SetItemMark(m, iLess500, noMark);
    SetItemMark(m, i1K, noMark);
    SetItemMark(m, i10K, noMark);
    SetItemMark(m, i100K, noMark);
    switch(messageSize)
    {
        case 400:
            SetItemMark(m, iLess500, checkMark);
        break;
        case 1024:
            SetItemMark(m, i1K, checkMark);
        break;
        case 10240:
            SetItemMark(m, i10K, checkMark);
        break;
        case 102400:
            SetItemMark(m, i100K, checkMark);
        break;
    }
    
}   
 
//¥ --------------------    AdjustNetMenus
 
void
AdjustNetMenus(void)
{
    MenuRef m = GetMenu(131);
    
    DoAdjustMenu(m, gSendOptions, gUpdateFrequency, gPlayerMessageSize);
 
    if (gHost)
    {
        m = GetMenu(132);
        if (m)
            DoAdjustMenu(m, gHostSendOptions, gHostUpdateFrequency, gGameStateMessageSize);
    }
}
 
//¥ --------------------    DoHost
 
OSStatus
DoHost(void)
{
    OSStatus    status;
    Str255      chooserName;
    NSpProtocolListReference    theList = NULL;
    Boolean okHit;
    
//  Create an empty protocol list
    status = NSpProtocolList_New(NULL, &theList);
    if (status != noErr)
    {
        printf("NSpProtocolList_New returned error: %d\n", status);
        goto failure;
    }
    
//  Now present a UI for the hosting
//  Note!  Do NOT pass in string constants, as the user can change these values
    GetChooserName(chooserName);
    PLstrncpy(playerName, chooserName, 31); 
    PLstrcpy(gameName, "\pNetSprocket Test");
    password[0] = 0;
    
    okHit = NSpDoModalHostDialog(theList, gameName, playerName, password, nil);
    if (!okHit)
    {
        status = kUserCancelled;
        goto failure;
    }
 
    //  Now host the game
    status = NSpGame_Host(&gNetGame, theList, kMaxPlayers, gameName,
                password, playerName, 0, kNSpClientServer, 0);
    if (status != noErr)
    {
        printf("NSpGame_Host returned error %d\n", status);
        goto failure;
    }
    
    gHost = true;
    
    
    if (status == noErr)
        AddHostMenu();
        
    return status;
    
failure:
    if (theList != nil)
        NSpProtocolList_Dispose(theList);
        
    return status;
}
 
//¥ --------------------    DoJoin
 
OSStatus
DoJoin(void)
{
    NSpAddressReference theAddress;
    OSStatus            status;
    Str255              chooserName;
    
    GetChooserName(chooserName);
    PLstrncpy(playerName, chooserName, 31); 
    password[0] = 0;
    
//  Present the UI for joining a game
//  passing an empty string (not nil) for the type causes NetSprocket to use
//  the game id passed in to initialize
    theAddress = NSpDoModalJoinDialog("\p", kJoinDialogLabel, playerName, password, NULL);
    if (theAddress == NULL)     // The user cancelled
        return kUserCancelled;
    
    
    status = NSpGame_Join(&gNetGame, theAddress, playerName, password, 0, NULL, 0, 0);
    if (status != noErr)
        printf("NSpGame_Join returned error %d\n", status);
    else        
        gHost = false;  
 
    return status;
}
 
//¥ --------------------    DoSendLeaveMessage
 
static void
DoSendLeaveMessage(NSpPlayerID inID)
{
NSpMessageHeader m;
OSStatus status;
    
    NSpClearMessageHeader(&m);
    
    m.what = kLeaveMessage;
    m.to = inID;
    m.messageLen = sizeof(NSpMessageHeader);
 
    status = NSpMessage_Send(gNetGame, &m, kNSpSendFlag_Registered);
 
}
 
//¥ --------------------    DoCloseNetWindow
 
void
DoCloseNetWindow(WindowPtr inWindow)
{
    if (gHost)
    {
        WindowStuff *stuff = (WindowStuff *) GetWRefCon(inWindow);  
        DoSendLeaveMessage(stuff->id);
    }
    else
        ShutdownNetworking();
    
}
 
//¥ --------------------    DoHandleMessage
 
static void
DoHandleMessage(NSpMessageHeader *inMessage)
{
    switch (inMessage->what)
    {
        case kLeaveMessage:
            printf("Got a message telling us to leave the game.");
            ShutdownNetworking();
        break;
        case kNSpPlayerJoined:
        {
            NSpPlayerJoinedMessage *theMessage = (NSpPlayerJoinedMessage *) inMessage;
            printf("Got player joined message: %d\n", theMessage->playerInfo.id);
 
            if (gHost || theMessage->playerInfo.id == NSpPlayer_GetMyID(gNetGame))
            {
                gMyPlayerID = NSpPlayer_GetMyID(gNetGame);
                WindowPtr wind = GetNewCWindow(128, nil, (WindowPtr) -1L);
                if (!wind)
                    printf("Failed to create a new window!\n");
                else
                {
                    WindowStuff *stuff = GetFreeWindowStuff();
                    if (stuff == nil)
                    {
                        printf("Couldn't alloc memory to show window.\n");
                        DisposeWindow(wind);
                    }
                    else
                    {
                        stuff->id = theMessage->playerInfo.id;
                        stuff->lastMessage = 0;
                        PLstrcpy(stuff->text, "\pNew Player");
                        
                        SetWTitle(wind,theMessage->playerInfo.name);
                        SetWRefCon(wind, (long) stuff);
                        InvalPlayerWindow(stuff->id);
                    }
                }
            }
        }   
        break;
            
        case kNSpPlayerLeft:
        {
        char    name[32];
        
            NSpPlayerLeftMessage *theMessage = (NSpPlayerLeftMessage *) inMessage;
 
            BlockMoveData(theMessage->playerName + 1, name, theMessage->playerName[0]);
            name[theMessage->playerName[0]] = '\0';
            
            printf("Got player left message: %s, player #%d\n", name, theMessage->playerID);
            
 
            if (gHost)
            {
                WindowPtr w = FindPlayersWindow(theMessage->playerID);
                if (w)
                {
                    WindowStuff *stuff = (WindowStuff *) GetWRefCon(w);
                    ReleaseWindowStuff(stuff);
                    DisposeWindow(w);
                }
                else
                    printf("Didn't find the window for the player that left!!\n");
            }   
        }
        break;
        case kNSpGameTerminated:
            printf("Got Game terminated message\n");
            ShutdownNetworking();
        break;
                        
        default:
            break;
    }
 
}
 
//¥ --------------------    GetMessages
 
static void
GetMessages(void)
{   
NSpMessageHeader    *theMessage;
    
    do 
    {
        theMessage = NSpMessage_Get(gNetGame);
        DoHandleMessage(theMessage);
        NSpMessage_Release(gNetGame, theMessage);
    } while (gNetGame && theMessage != NULL);
    
}
 
//¥ --------------------    UpdateNetWindows
 
static void
UpdateNetWindows(void)
{
WindowPtr   w;
 
    w = FrontWindow();
 
    while (w != nil)
    {
        WindowStuff *stuff = (WindowStuff *) GetWRefCon(w);
        if (stuff && stuff->changed)
        {
            GrafPtr     oldPort;
        
            GetPort(&oldPort);
            SetPort(w);
            RefreshWindow(w);
            SetPort(oldPort);
            
            stuff->changed = false;
        }
        w = GetNextWindow(w);       
    }
}
 
//¥ --------------------    HandleNetwork
 
void
HandleNetwork(void)
{
OSStatus    status;
 
    if (!gNetGame)
        return;
 
    GetMessages();
 
//  update any windows as necessary
    UpdateNetWindows();
    
    UInt32 time = TickCount();
    
    if (gNetGame && time > gLastUpdate + gUpdateFrequency)
    {
        NSpClearMessageHeader(&gPlayerMessage.h);
 
        gPlayerMessage.h.what = kPlayerInputMessage;
        gPlayerMessage.h.to = kNSpHostOnly;
        gPlayerMessage.h.messageLen = gPlayerMessageSize;
 
//      Send my info to the host
        status = NSpMessage_Send(gNetGame, &gPlayerMessage.h, gSendOptions);
        if (status != noErr)
            printf("NSpMessage_Send returned error: %d\n", status);
        
        if (gHost && time > gLastHostUpdate + gHostUpdateFrequency)
        {
            NSpClearMessageHeader(&gGameStateMessage.h);
 
            gGameStateMessage.h.what = kGameStateMessage;
            gGameStateMessage.h.to = kNSpAllPlayers;
            gGameStateMessage.h.messageLen = gGameStateMessageSize;
 
    //      Send my info to the host
            status = NSpMessage_Send(gNetGame, &gGameStateMessage.h, gHostSendOptions);
            if (status != noErr)
                printf("NSpMessage_Send returned error: %d\n", status);
            
            gLastHostUpdate = time;
        }
 
        gLastUpdate = time;
    }       
}
 
//¥ --------------------    GetInfoEnumeratePlayers
 
static void
GetInfoEnumeratePlayers(void)
{
OSStatus                theErr;
NSpPlayerEnumerationPtr thePlayers;
UInt32                  i;
 
    if (nil == gNetGame)
    {
        printf("gNetGame is nil.\n");
        return;
    }
 
    theErr = NSpPlayer_GetEnumeration(gNetGame, &thePlayers);
 
    if (noErr != theErr)
    {
        printf("NSpPlayer_GetEnumeration returned %ld\n", theErr);
        return;
    }
    
    printf("PlayerID    IP Address         Name\n");
    printf("--------    ---------------    ----------------\n");
 
    for (i = 0; i < thePlayers->count; i++)
    {
    char                name[sizeof (NSpPlayerName)];
    NSpPlayerInfoPtr    thePlayer;
    OTAddress           *address;
    OSStatus            theErr;
    
        NSpPlayer_GetInfo(gNetGame, thePlayers->playerInfo[i]->id, &thePlayer);
    
        BlockMoveData(thePlayer->name + 1, name, thePlayer->name[0]);
        name[thePlayer->name[0]] = '\0';
        printf("%0.8ld    ", thePlayer->id);
 
        theErr = NSpPlayer_GetAddress(gNetGame, thePlayer->id, &address);
        
        if ((noErr != theErr) || (NULL == address))
        {
            printf("Error %9ld", theErr);
        }
        else
        {
            if (AF_INET == address->fAddressType)
            {
            InetAddress *ip;
 
                ip = (InetAddress *) address;
                
                printf("%3d.%3d.%3d.%3d",
                    (ip->fHost & 0xFF000000) >> 24,
                    (ip->fHost & 0x00FF0000) >> 16,
                    (ip->fHost & 0x0000FF00) >>  8,
                    (ip->fHost & 0x000000FF) >>  0);
            }
            else
            {
                printf("N/A - AppleTalk");
            }
        }
        
        if (NULL != address)
            DisposePtr((Ptr) address);
        
        printf("    %s\n", name);
    }
}
 
//¥ --------------------    EnumeratePlayers
 
static void
EnumeratePlayers(void)
{
OSStatus    theErr;
NSpPlayerEnumerationPtr thePlayers;
UInt32      i;
 
    if (nil == gNetGame)
        printf("gNetGame is nil.\n");
 
    theErr = NSpPlayer_GetEnumeration(gNetGame, &thePlayers);
    if (noErr != theErr)
    {
        printf("NSpPlayer_GetEnumeration returned %ld\n", theErr);
        return;
    }
    
        printf("PlayerID    Name\n");
        printf("--------    -------------------------------\n");
    for (i = 0; i < thePlayers->count; i++)
    {
    char    name[sizeof (NSpPlayerName)];
    
        BlockMoveData(thePlayers->playerInfo[i]->name + 1, name, thePlayers->playerInfo[i]->name[0]);
        name[thePlayers->playerInfo[i]->name[0]] = '\0';
        printf("%0.8ld    %s\n", thePlayers->playerInfo[i]->id, name);
    }
}