ftp.nice.ch/Attic/openStep/games/Empire.0.6.m.NIS.bs.tgz#/Empire.0.6/src/MapView.m

This is MapView.m in view mode; [Download] [Up]

//
// $Id: MapView.m,v 1.8 1997/10/31 04:51:53 nygard Exp $
//

//
//  This file is a part of Empire, a game of exploration and conquest.
//  Copyright (C) 1996  Steve Nygard
//
//  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 2 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, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//  You may contact the author by:
//     e-mail:  nygard@telusplanet.net
//

#import "Empire.h"

RCSID ("$Id: MapView.m,v 1.8 1997/10/31 04:51:53 nygard Exp $");

#import <AppKit/NSEvent.h>
#import <AppKit/psopsNeXT.h>

#import "EmpireImageVendor.h"
#import "Map.h"
#import "MapView.h"

#import "EmpireProtocols.h"

static NSImage *map_images[256];
static NSImage *explosion;
static NSImage *default_cursor;
static NSImage *direction_cursor;
static NSImage **oddballs[4];

//======================================================================
// The Map View is the main interface to the game.  It displays the
// terrain, cities, and units of a Map, and forwards messages of user
// actions to a delegate.
//
// This should be reasonable fast, as it was developed on a machine
// with a rather slow display.
//======================================================================

@implementation MapView

+ (void) initialize
{
    if (self == [MapView class])
    {
        NSImage *city;
        NSImage **onLand;
        NSImage **onWater;
        NSImage **onCity;
        NSImage **od;
        EmpireImageVendor *vendor = [EmpireImageVendor instance];
        int a, b;
        int i;
        id tmp;

        [vendor player:p_player1 :&city :&onLand :&onWater :&onCity :&od];

        // Default all values to invalid
        for (i = 0; i < 256; i++)
            map_images[i] = nil;
    
        // Set default cursor : player 1,2,3 (icon:15) terrain:0,1,2,3
        default_cursor = tmp = [vendor cursor];

        for (a = p_player1; a <= p_player3; a++)
        {
            for (b = t_unknown; b <= t_city; b++)
                map_images[EMCreateMapToken (b, a, 15, YES)] = tmp;
        }
    
        // Set direction cursor : player 1,2,3 (icon:14) terrain:0,1,2,3
        direction_cursor = tmp = [vendor directionCursor];

        for (a = p_neutral; a <= p_player3; a++)
        {
            for (b = t_unknown; b <= t_city; b++)
                map_images[EMCreateMapToken (b, a, 14, YES)] = tmp;
        }

        // Player 0 - Neutral.
        map_images[EMCreateMapToken (t_unknown, p_neutral, i_none, YES)] = [vendor unknown];
        map_images[EMCreateMapToken (t_water, p_neutral, i_none, YES)] = [vendor water];
        map_images[EMCreateMapToken (t_land, p_neutral, i_none, YES)] = [vendor land];
        map_images[EMCreateMapToken (t_city, p_neutral, i_none, YES)] = [vendor neutralCity];

        for (a = p_player1; a <= p_player3; a++)
        {
            [vendor player:a :&city :&onLand :&onWater :&onCity :&oddballs[a]];
      
            map_images[EMCreateMapToken (t_city, a, i_none, YES)] = city;

            // on water:
            for (b = i_fighter; b <= i_hovercraft; b++)
            {
                i = EMCreateMapToken (t_water, a, b, YES);
                map_images[i] = onWater[b - 3];
            }

            // on land:
            map_images[EMCreateMapToken (t_land, a, i_army, YES)] = onLand[0];
            map_images[EMCreateMapToken (t_land, a, i_sentry, YES)] = onLand[1];
            map_images[EMCreateMapToken (t_land, a, i_fighter, YES)] = onLand[2];
            map_images[EMCreateMapToken (t_land, a, i_hovercraft, YES)] = onLand[3];

            // on city
            for (b = i_army; b <= i_hovercraft; b++)
            {
                i = EMCreateMapToken (t_city, a, b, YES);
                map_images[i] = onCity[b - 1];
            }
        }

        explosion = [vendor explosion];
    }
}

//----------------------------------------------------------------------

- initWithFrame:(NSRect)frameRect
{
    [super initWithFrame:frameRect];

    map = nil;
    delegate = nil;

    compressEvents = NO; // Do we need this?

    cursorEnabled = NO;
    cursorOn = NO;
    fastCursor = NO;
    cursorLocation.row = 5;
    cursorLocation.column = 10;
    cursorTimer = nil;
    cursorImage = default_cursor;

    cursorIcon = i_none;
    cursorPlayer = p_neutral;

    [[EmpireImageVendor instance] attach:self];

    [self setCursorEnabled:NO];

    return self;
}

//----------------------------------------------------------------------

- (void) dealloc
{
    if (map != nil)
        [map detach:self];

    // Make sure timer is finished.
    [self setCursorEnabled:NO];

    [super dealloc];
}

//----------------------------------------------------------------------

- (BOOL) acceptsFirstResponder
{
    return YES;
}

//----------------------------------------------------------------------

- (BOOL) acceptsFirstMouse:(NSEvent *)theEvent
{
    return YES;
}

//----------------------------------------------------------------------

- (BOOL) isOpaque
{
    return YES;
}

//----------------------------------------------------------------------

- (void) drawRect:(NSRect)rect
{
    EMMapSize mapSize;
    int row, column;
    EMMapLocation location1, location2;
    NSPoint aPoint;
    MapToken **mapPtrs;
    MapToken tmp;

    PSsetgray (NSDarkGray);
    NSRectFill (rect);

    if (map != nil)
    {
        mapPtrs = [map mapPtrs];
        mapSize = [map mapSize];

        aPoint.x = NSMinX (rect);
        aPoint.y = NSMinY (rect);
        location1 = [self getLocationForPoint:aPoint];
        
        aPoint.x += NSWidth (rect);
        aPoint.y += NSHeight (rect);
        location2 = [self getLocationForPoint:aPoint];

        //NSLog (@"%d,%d -> %d,%d", location2.row, location1.column, location1.row, location2.column);

        for (row = location2.row; row <= location1.row; row++)
        {
            aPoint.y = 16.0 * (float)(mapSize.height - 1 - row);

            for (column = location1.column; column <= location2.column; column++)
            {
                aPoint.x = 16.0 * (float)column;
                tmp = mapPtrs[row][column];

                if (tmp > 255 && map_images[0] != nil)
                    [map_images[0] compositeToPoint:aPoint operation:NSCompositeSourceOver];
                else if (map_images[tmp] != nil)
                    [map_images[tmp] compositeToPoint:aPoint operation:NSCompositeSourceOver];
                else
                {
                    PSsetgray (NSDarkGray);
                    NSRectFill (NSMakeRect(aPoint.x, aPoint.y, 16, 16));
                }
            }
        }
    }
}

//----------------------------------------------------------------------

- (Map *) map
{
    return map;
}

//----------------------------------------------------------------------

- (void) setMap:(Map *)aMap
{
    EMMapSize mapSize;
    
    if (aMap == nil)
        return;

    if (map != nil)
    {
        [map detach:self];
        [map release];
    }
  
    map = aMap;
    [map attach:self];
    [map retain];

    mapSize = [map mapSize];
    [self setFrameSize:NSMakeSize (16.0*(float)mapSize.width, 16.0*(float)mapSize.height)];

    [self setNeedsDisplay:YES];
}

//----------------------------------------------------------------------

- (EMMapLocation) getLocationForPoint:(NSPoint)point
{
    EMMapSize mapSize;
    EMMapLocation target;

    NSAssert (map != nil, @"Map was nil.");

    mapSize = [map mapSize];

    target.row = mapSize.height - (int)(point.y / 16.0) - 1;
    target.column = point.x / 16.0;

    target.row = (target.row >= mapSize.height) ? mapSize.height - 1 : target.row;
    target.row = (target.row <= 0) ? 0 : target.row;

    target.column = (target.column >= mapSize.width) ? mapSize.width - 1 : target.column;
    target.column = (target.column < 0) ? 0 : target.column;

    return target;
}

//----------------------------------------------------------------------

- (NSPoint) getPointForLocation:(EMMapLocation)target
{
    EMMapSize mapSize;

    NSAssert (map != nil, @"Map was nil.");

    mapSize = [map mapSize];
    
    return NSMakePoint (16.0 * (float)target.column, (mapSize.height - 1 - target.row) * 16.0);
}

//----------------------------------------------------------------------

- (NSPoint) getCenterPointForLocation:(EMMapLocation)target
{
    NSRect cellRect = [self getRectForLocation:target];

    return NSMakePoint (NSMidX (cellRect), NSMidY (cellRect));
}

//----------------------------------------------------------------------

- (NSRect) getRectForLocation:(EMMapLocation)target
{
    EMMapSize mapSize;

    NSAssert (map != nil, @"Map was nil.");

    mapSize = [map mapSize];
    
    return NSMakeRect (16.0 * (float)target.column, (mapSize.height - 1 - target.row) * 16.0, 16.0, 16.0);
}

//----------------------------------------------------------------------

- (NSRect) getRectAround3x3Location:(EMMapLocation)target
{
    EMMapLocation location1, location2;
    EMMapSize mapSize;

    NSAssert (map != nil, @"Map was nil.");
    mapSize = [map mapSize];

    location1.row = target.row - 1;
    location1.row = (location1.row < 0) ? 0 : location1.row;
    location1.row = (location1.row >= mapSize.height) ? mapSize.height - 1 : location1.row;
    location2.row = target.row + 1;
    location2.row = (location2.row < 0) ? 0 : location2.row;
    location2.row = (location2.row >= mapSize.height) ? mapSize.height - 1 : location2.row;

    location1.column = target.column - 1;
    location1.column = (location1.column < 0) ? 0 : location1.column;
    location1.column = (location1.column >= mapSize.width) ? mapSize.width - 1 : location1.column;
    location2.column = target.column + 1;
    location2.column = (location2.column < 0) ? 0 : location2.column;
    location2.column = (location2.column >= mapSize.width) ? mapSize.width - 1 : location2.column;

    return NSMakeRect ((float)location1.column * 16.0, (mapSize.height - 1 - location2.row) * 16.0,
                       (float)(location2.column - location1.column + 1) * 16.0, (float)(location2.row - location1.row + 1) * 16.0);
}

//----------------------------------------------------------------------
// notVisibleFlag == NO:  always center screen around the location.
// notVisibleFlag == YES: only center the screen if the area around location is not entirely visible
//----------------------------------------------------------------------

- (void) centerLocation:(EMMapLocation)target ifNotVisible:(BOOL)notVisibleFlag
{
    NSRect cellRect;
    NSRect visibleRect;
    BOOL needToScroll;
  
    cellRect = [self getRectAround3x3Location:target];
    visibleRect = [self visibleRect];

    // Remind me what we use the notVisibleFlag for...
    needToScroll = (notVisibleFlag == NO) || (NSContainsRect (visibleRect, cellRect) == NO);
    //needToScroll = (NSContainsRect (visibleRect, cellRect) == NO);
#if 0
    // What is this doing?
    cellRect.origin.x = NSMinX (cellRect) + (NSWidth (cellRect) / 2) - (NSWidth (visibleRect) / 2);
    cellRect.origin.y = NSMinY (cellRect) + (NSHeight (cellRect) / 2) - (NSHeight (visibleRect) / 2);
    cellRect.size.width = NSWidth (visibleRect);
    cellRect.size.height = NSHeight (visibleRect);

    cellRect = NSIntersectionRect ([self bounds] , cellRect);
#endif
    // Redundant...
    if (needToScroll == YES)
    {
        [self scrollRectToVisible:cellRect];
        //[self displayRect:cellRect];
    }
}

//----------------------------------------------------------------------

- (void) scrollLocationToVisible:(EMMapLocation)target
{
    NSRect aRect = [self getRectAround3x3Location:target];

    [self scrollRectToVisible:aRect];
    //[self displayRect:aRect];
}

//----------------------------------------------------------------------

- (void) displayLocation:(EMMapLocation)target
{
    EMMapSize mapSize;
    NSRect aRect;

    NSAssert (map != nil, @"Map was nil.");
    mapSize = [map mapSize];

    NSAssert (target.row >= 0 && target.column >= 0
              && target.row < mapSize.height && target.column < mapSize.width, @"Cell out of range.");
    
    aRect = [self getRectForLocation:target];
    [self scrollRectToVisible:aRect];
    //[self displayRect:aRect];
    [self setNeedsDisplayInRect:aRect];
    [self displayIfNeeded];
    //[self setNeedsDisplayInRect:aRect];
    //[self displayRect:aRect];
}

//----------------------------------------------------------------------

- (void) displayAround3x3Location:(EMMapLocation)target
{
    EMMapSize mapSize;
    NSRect aRect;

    NSAssert (map != nil, @"Map was nil.");
    mapSize = [map mapSize];

    NSAssert (target.row >= 0 && target.column >= 0
              && target.row < mapSize.height && target.column < mapSize.width, @"Cell out of range.");
    
    aRect = [self getRectAround3x3Location:target];
    [self scrollRectToVisible:aRect];
    //[self displayRect:aRect];
    [self setNeedsDisplayInRect:aRect];
    [self displayIfNeeded];
    //[self setNeedsDisplayInRect:aRect];

    // This also marks it as not needing display, which breaks when doing
    // diagonal drawing, since only this rect is displayed while a larger rect
    // actually needs display.
    //[self displayRect:aRect];
}

//----------------------------------------------------------------------

- (void) mouseDown:(NSEvent *)theEvent 
{
    NSPoint aPoint;
    EMMapLocation mapLocation;
    NSRect visibleRect;
    BOOL scrolled = NO;

    NSPoint mouseLocation;

    EMMapLocation lastLocation;

    BOOL periodicOn = NO;
    unsigned int originalModifierFlags = [theEvent modifierFlags];
    unsigned int modifierFlags;

    lastLocation.row = -1;
    lastLocation.column = -1;

    if (delegate == nil)
    {
        [super mouseDown:theEvent];
        return;
    }

    mouseLocation = [theEvent locationInWindow];

    while ([theEvent type] != NSLeftMouseUp)
    {
        visibleRect = [self visibleRect];
        switch ([theEvent type])
        {
          case NSLeftMouseUp:
              break;

          case NSPeriodic:
          case NSLeftMouseDown:
          case NSLeftMouseDragged:
              if ([theEvent type] == NSPeriodic)
              {
                  mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
                  modifierFlags = originalModifierFlags;
              }
              else
              {
                  mouseLocation = [theEvent locationInWindow];
                  modifierFlags = [theEvent modifierFlags];
              }

              aPoint = [self convertPoint:mouseLocation fromView:nil];

              mapLocation = [self getLocationForPoint:aPoint];

              if (compressEvents == NO
                  || mapLocation.row != lastLocation.row || mapLocation.column != lastLocation.column
                  || [theEvent type] == NSPeriodic
                  || (originalModifierFlags & NSControlKeyMask != 0))
              {
                  lastLocation = mapLocation;

                  [delegate mouseDown:modifierFlags atLocation:mapLocation];
              }

              //if (compressEvents == NO)
              {
                  if (periodicOn == NO)
                  {
                      //NSLog (@"Starting periodic events. (1)");
                      [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05];
                      periodicOn = YES;
                  }
              }

              if (NSPointInRect(mouseLocation, visibleRect) == NO)
              {
                  scrolled = YES;
                  if (periodicOn == NO)
                  {
                      //NSLog (@"Starting periodic events. (2)");
                      [NSEvent startPeriodicEventsAfterDelay:0.1 withPeriod:0.05];
                      periodicOn = YES;
                  }
              }
#if 0
              else if (compressEvents == YES)
              {
                  //NSLog (@"compress events is YES, stopping periodic events.");
                  [NSEvent stopPeriodicEvents];
                  periodicOn = NO;
              }
#endif
              break;

          default:
              break;
        }

        if (scrolled)
            scrolled = NO;

        theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask)];
    }

    [NSEvent stopPeriodicEvents];

    // Mouse up event has been consumed by this point.
    [self mouseUp:theEvent];
}

//----------------------------------------------------------------------

- (void) mouseUp:(NSEvent *)theEvent 
{
    NSPoint point;
    EMMapLocation mapLocation;
  
    if ([theEvent type] == NSLeftMouseUp && delegate != nil)
    {
        point = [self convertPoint:[theEvent locationInWindow] fromView:nil];

        mapLocation = [self getLocationForPoint:point];

        [delegate mouseUp:[theEvent modifierFlags] atLocation:mapLocation];
    }
}

//----------------------------------------------------------------------

- (void) rightMouseDown:(NSEvent *)theEvent 
{
    NSPoint point;
    EMMapLocation mapLocation;
  
    if ([theEvent type] == NSRightMouseDown && delegate != nil)
    {
        point = [self convertPoint:[theEvent locationInWindow] fromView:nil];

        mapLocation = [self getLocationForPoint:point];

        [delegate rightMouseDown:[theEvent modifierFlags] atLocation:mapLocation];
    }
}

//----------------------------------------------------------------------

- (void) rightMouseUp:(NSEvent *)theEvent 
{
    NSPoint point;
    EMMapLocation mapLocation;
  
    if ([theEvent type] == NSRightMouseUp && delegate != nil)
    {
        point = [self convertPoint:[theEvent locationInWindow] fromView:nil];

        mapLocation = [self getLocationForPoint:point];

        [delegate rightMouseUp:[theEvent modifierFlags] atLocation:mapLocation];
    }
}

//----------------------------------------------------------------------

- (void) keyDown:(NSEvent *)theEvent 
{
    if (delegate != nil)
    {
        [delegate keyDown:theEvent];
    }
    else
    {
        [super keyDown:theEvent];
    }
}

//----------------------------------------------------------------------

- (void) enableCompressedEvents:(BOOL)flag
{
    compressEvents = flag;
}

//----------------------------------------------------------------------

- (void) vendorImagesUpdated:(BOOL)player1:(BOOL)player2:(BOOL)player3:(BOOL)other
{
    if (other == YES)
    {
        [self setNeedsDisplay:YES];
    }
    else
    {
#if 0
        if (player1 == YES)
            [self updateVisiblePlayer:p_player1];
        if (player2 == YES)
            [self updateVisiblePlayer:p_player2];
        if (player3 == YES)
            [self updateVisiblePlayer:p_player3];
#endif
        [self setNeedsDisplay:YES];
    }
}

//----------------------------------------------------------------------
// This is really just to tweak for speed.
//----------------------------------------------------------------------

- (void) updateVisiblePlayer:(Player)number
{
    NSRect visibleRect = [self visibleRect];
    EMMapSize mapSize;
    EMMapLocation current;
    EMMapLocation location1, location2;
    NSPoint aPoint;
    MapToken **mapPtrs;
    MapToken mapToken;

    if (map != nil)
    {
        mapPtrs = [map mapPtrs];
        mapSize = [map mapSize];

        aPoint.x = NSMinX (visibleRect);
        aPoint.y = NSMinY (visibleRect);
        location1 = [self getLocationForPoint:aPoint];
        
        aPoint.x += NSWidth (visibleRect);
        aPoint.y += NSHeight (visibleRect);
        location2 = [self getLocationForPoint:aPoint];

        for (current.row = location2.row; current.row <= location1.row; current.row++)
        {
            for (current.column = location1.column; current.column <= location2.column; current.column++)
            {
                mapToken = mapPtrs[current.row][current.column];

                if (EMPlayerComponent (mapToken) == number)
                    [self displayLocation:current];
            }
        }
    }
}

//----------------------------------------------------------------------

- (void) refreshMap
{
    [self setNeedsDisplay:YES];
}

//----------------------------------------------------------------------

- (void) refreshLocation:(EMMapLocation)target
{
    //NSLog (@"target: (%d,%d) is %@", target.row, target.column, EMFormatComponents ([map tokenAtLocation:target]));
    [self displayLocation:target];
    [self displayIfNeeded];//?
    //[self updateCursorImage];
}

//----------------------------------------------------------------------

- (void) refresh3x3Location:(EMMapLocation)target
{
    //NSLog (@"target: (%d,%d)", target.row, target.column);
    [self displayAround3x3Location:target];
    //[self updateCursorImage];
}

//----------------------------------------------------------------------

- (void) showExplosions:(int)count atLocation:(EMMapLocation)target
{
    NSRect aRect;
    NSPoint aPoint;
    int l;

    if ([[self window] isVisible] == YES)
    {
        [self lockFocus];
  
        aRect = [self getRectForLocation:target];
        aPoint.x = NSMinX (aRect);
        aPoint.y = NSMinY (aRect);

        for (l = 0; l < count; l++)
        {
            PSsetinstance (YES);
            [explosion compositeToPoint:aPoint operation:NSCompositeSourceOver];
            PSsetinstance (NO);
            PSWait ();
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:(100000)/1000000.0]];
            PSnewinstance ();
            PSWait ();
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:(100000)/1000000.0]];
        }
    
        [self unlockFocus];
    }
}

//======================================================================
// Cursor Handling
//======================================================================

- (void) centerScreenAroundCursor
{
    [self centerLocation:cursorLocation ifNotVisible:NO];
}

//----------------------------------------------------------------------

- (void) centerCursor
{
}

//----------------------------------------------------------------------

- (BOOL) cursorEnabled
{
    return cursorEnabled;
}

//----------------------------------------------------------------------

- (void) setCursorEnabled:(BOOL)flag
{
    if (cursorEnabled == YES && flag == NO)
    {
        if (cursorTimer != nil)
        {
            [cursorTimer invalidate];
            SNRelease (cursorTimer);
        }

        if (cursorOn == YES)
            [self updateCursor:NO];
    }
    else if (cursorEnabled == NO && flag == YES)
    {
        float blink_speed = (fastCursor == YES) ? 0.2 : 0.5;

        cursorTimer = [[NSTimer scheduledTimerWithTimeInterval:blink_speed
                                target:self
                                selector:@selector (cursorTimer:)
                                userInfo:self
                                repeats:YES] retain];

        NSAssert (cursorTimer != nil, @"Cursor timer is nil.");
    }

    cursorEnabled = flag;
}

//----------------------------------------------------------------------

- (void) setFastCursor:(BOOL)flag
{
    if (fastCursor != flag)
    {
        fastCursor = flag;
        if (cursorEnabled == YES)
        {
            [self setCursorEnabled:NO];
            [self setCursorEnabled:YES];
        }
    }
}

//----------------------------------------------------------------------

- (EMMapLocation) cursorLocation
{
    return cursorLocation;
}

//----------------------------------------------------------------------

- (void) positionCursorAtLocation:(EMMapLocation)target
{
    EMMapSize mapSize;
    EMMapLocation newLocation;
    
    NSAssert (map != nil, @"Map was nil.");

    mapSize = [map mapSize];
    
    //NSLog (@"cursor location: %d,%d", target.row, target.column);

    newLocation.row = (target.row < 0) ? 0 : (target.row >= mapSize.height) ? mapSize.height - 1 : target.row;
    newLocation.column = (target.column < 0) ? 0 : (target.column >= mapSize.width) ? mapSize.width - 1 : target.column;

    //[self scrollLocationToVisible:target];
    //[self centerScreenAroundCursor];
    [self centerLocation:target ifNotVisible:YES];

    if (newLocation.row != cursorLocation.row || newLocation.column != cursorLocation.column)
    {
        cursorLocation = newLocation;

        [self updateCursor:NO];
        if (cursorOn == NO)
            [self updateCursor:NO];
    }
}

//----------------------------------------------------------------------

- (void) setCursor:(Icon)icon player:(Player)aPlayer
{
    NSAssert (icon <= i_hovercraft, @"Icon out of range.");

    cursorIcon = icon;
    cursorPlayer = aPlayer;

    [self updateCursorImage];
}

//----------------------------------------------------------------------

- (void) updateCursorImage
{
    if (cursorIcon != i_none)
    {
        Terrain terrain;
        Icon icon;

        EMMapTokenComponents ([map tokenAtLocation:cursorLocation], &terrain, NULL, &icon, NULL);
    
        if (terrain == t_city)
        {
            cursorImage = map_images[EMCreateMapToken (t_city, cursorPlayer, cursorIcon, YES)];
        }
        else if (icon == cursorIcon)
        {
            cursorImage = map_images[EMCreateMapToken (terrain, p_neutral, i_none, YES)];
        }
        else if (cursorIcon == i_army && icon == i_loaded_transport)
        {
            cursorImage = oddballs[cursorPlayer][0];
        }
        else if (cursorIcon == i_fighter && icon == i_loaded_carrier)
        {
            cursorImage = oddballs[cursorPlayer][1];
        }
        else
        {
            cursorImage = map_images[EMCreateMapToken (terrain, p_neutral, i_none, YES)];
        }
    }

    // Otherwise, it is default/direction...
}

//----------------------------------------------------------------------

- (void) cursorTimer:(NSTimer *)aTimer
{
    [self updateCursor:NO];
}

//----------------------------------------------------------------------

- (void) updateCursor:(BOOL)scroll
{
    NSPoint aPoint;

    if (cursorEnabled == NO)
        return;

    if (scroll == YES)
    {
        //NSLog (@"Scroll.");
        [self scrollLocationToVisible:cursorLocation];
    }

    if (cursorOn == NO)
    {
        cursorOn = YES;

        aPoint = [self getPointForLocation:cursorLocation];

        if ([self canDraw] == YES)
        {
            [self lockFocus];
            // Caching while instance drawing! Argh!!!!!!
            //[default_cursor isValid];
            PSsetinstance (YES);
            [cursorImage compositeToPoint:aPoint operation:NSCompositeSourceOver];
            PSsetinstance (NO);
            [self unlockFocus];
        }
    }
    else
    {
        cursorOn = NO;
        if ([self canDraw] == YES)
        {
            [self lockFocus];
            PSnewinstance();
            [self unlockFocus];
        }
    }
    //[[self window] flushWindow];
}

//----------------------------------------------------------------------

- (void) useDefaultCursor
{
    cursorImage = default_cursor;
    cursorIcon = i_none;
    cursorPlayer = p_neutral;
}

//----------------------------------------------------------------------

- (void) useDirectionCursor
{
    cursorImage = direction_cursor;
    cursorIcon = i_none;
    cursorPlayer = p_neutral;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.