OSDN Git Service

Made beginGame() an instance method of AngbandAppDelegate rather than a class method...
[hengbandforosx/hengbandosx.git] / src / main-cocoa.m
index db5953e..d6ea506 100644 (file)
@@ -23,7 +23,7 @@
 #if defined(MACH_O_COCOA)
 
 /* Mac headers */
-#include <Cocoa/Cocoa.h>
+#include <cocoa/AppDelegate.h>
 //#include <Carbon/Carbon.h> /* For keycodes */
 /* Hack - keycodes to enable compiling in macOS 10.14 */
 #define kVK_Return 0x24
@@ -41,6 +41,7 @@ static NSString * const AngbandTerminalRowsDefaultsKey = @"Rows";
 static NSString * const AngbandTerminalColumnsDefaultsKey = @"Columns";
 static NSString * const AngbandTerminalVisibleDefaultsKey = @"Visible";
 static NSString * const AngbandGraphicsDefaultsKey = @"GraphicsID";
+static NSString * const AngbandBigTileDefaultsKey = @"UseBigTiles";
 static NSString * const AngbandFrameRateDefaultsKey = @"FramesPerSecond";
 static NSString * const AngbandSoundDefaultsKey = @"AllowSound";
 static NSInteger const AngbandWindowMenuItemTagBase = 1000;
@@ -395,6 +396,9 @@ static int resize_pending_changes(struct PendingChanges* pc, int nrow)
      */
     int ncol_pre, ncol_post;
 
+    /* Flags whether or not a fullscreen transition is in progress. */
+    BOOL in_fullscreen_transition;
+
 @private
 
     BOOL _hasSubwindowFlags;
@@ -463,8 +467,12 @@ static int resize_pending_changes(struct PendingChanges* pc, int nrow)
  * defaults */
 - (void)resizeTerminalWithContentRect: (NSRect)contentRect saveToDefaults: (BOOL)saveToDefaults;
 
-/* Change the minimum size for the window associated with the context. */
-- (void)setMinimumWindowSize;
+/*
+ * Change the minimum size for the window associated with the context.
+ * termIdx is the index for the terminal:  pass it so this function can be
+ * used when self->terminal has not yet been set.
+ */
+- (void)setMinimumWindowSize:(int)termIdx;
 
 /* Called from the view to indicate that it is starting or ending live resize */
 - (void)viewWillStartLiveResize:(AngbandView *)view;
@@ -472,14 +480,6 @@ static int resize_pending_changes(struct PendingChanges* pc, int nrow)
 - (void)saveWindowVisibleToDefaults: (BOOL)windowVisible;
 - (BOOL)windowVisibleUsingDefaults;
 
-/* Class methods */
-
-/* Begins an Angband game. This is the entry point for starting off. */
-+ (void)beginGame;
-
-/* Ends an Angband game. */
-+ (void)endGame;
-
 /* Internal method */
 - (AngbandView *)activeView;
 
@@ -622,6 +622,10 @@ static Boolean game_in_progress = FALSE;
 static void wakeup_event_loop(void);
 static void hook_plog(const char *str);
 static void hook_quit(const char * str);
+static NSString* get_lib_directory(void);
+static NSString* get_doc_directory(void);
+static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath);
+static void prepare_paths_and_directories(void);
 static void load_prefs(void);
 static void load_sounds(void);
 static void init_windows(void);
@@ -668,7 +672,7 @@ static bool initialized = FALSE;
 /* The NSView subclass that draws our Angband image */
 @interface AngbandView : NSView
 {
-    IBOutlet AngbandContext *angbandContext;
+    AngbandContext *angbandContext;
 }
 
 - (void)setAngbandContext:(AngbandContext *)context;
@@ -1088,7 +1092,7 @@ static int compare_advances(const void *ap, const void *bp)
                 * and rows since they could be changed */
         NSRect contentRect = [self->primaryWindow contentRectForFrameRect: [self->primaryWindow frame]];
 
-       [self setMinimumWindowSize];
+       [self setMinimumWindowSize:[self terminalIndex]];
        NSSize size = self->primaryWindow.contentMinSize;
        BOOL windowNeedsResizing = NO;
        if (contentRect.size.width < size.width) {
@@ -1109,9 +1113,6 @@ static int compare_advances(const void *ap, const void *bp)
 
     /* Update our image */
     [self updateImage];
-    
-    /* Get redrawn */
-    [self requestRedraw];
 }
 
 - (id)init
@@ -1135,6 +1136,8 @@ static int compare_advances(const void *ap, const void *bp)
        self->ncol_pre = 0;
        self->ncol_post = 0;
 
+       self->in_fullscreen_transition = NO;
+
         /* Make the image. Since we have no views, it'll just be a puny 1x1 image. */
         [self updateImage];
 
@@ -1182,116 +1185,6 @@ static int compare_advances(const void *ap, const void *bp)
     [super dealloc];
 }
 
-
-
-#pragma mark -
-#pragma mark Directories and Paths Setup
-
-/**
- * Return the path for Angband's lib directory and bail if it isn't found. The
- * lib directory should be in the bundle's resources directory, since it's
- * copied when built.
- */
-+ (NSString *)libDirectoryPath
-{
-    NSString *bundleLibPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: AngbandDirectoryNameLib];
-    BOOL isDirectory = NO;
-    BOOL libExists = [[NSFileManager defaultManager] fileExistsAtPath: bundleLibPath isDirectory: &isDirectory];
-
-    if( !libExists || !isDirectory )
-    {
-        NSLog( @"[%@ %@]: can't find %@/ in bundle: isDirectory: %d libExists: %d", NSStringFromClass( [self class] ), NSStringFromSelector( _cmd ), AngbandDirectoryNameLib, isDirectory, libExists );
-
-       NSString *msg = NSLocalizedStringWithDefaultValue(
-           @"Error.MissingResources",
-           AngbandMessageCatalog,
-           [NSBundle mainBundle],
-           @"Missing Resources",
-           @"Alert text for missing resources");
-       NSString *info = NSLocalizedStringWithDefaultValue(
-           @"Error.MissingAngbandLib",
-           AngbandMessageCatalog,
-           [NSBundle mainBundle],
-           @"Hengband was unable to find required resources and must quit. Please report a bug on the Angband forums.",
-           @"Alert informative message for missing Angband lib/ folder");
-       NSString *quit_label = NSLocalizedStringWithDefaultValue(
-           @"Label.Quit", AngbandMessageCatalog, [NSBundle mainBundle],
-           @"Quit", @"Quit");
-       NSAlert *alert = [[NSAlert alloc] init];
-
-       /*
-        * Note that NSCriticalAlertStyle was deprecated in 10.10.  The
-        * replacement is NSAlertStyleCritical.
-        */
-       alert.alertStyle = NSCriticalAlertStyle;
-       alert.messageText = msg;
-       alert.informativeText = info;
-       [alert addButtonWithTitle:quit_label];
-       NSModalResponse result = [alert runModal];
-       [alert release];
-        exit( 0 );
-    }
-
-       return bundleLibPath;
-}
-
-/**
- * Return the path for the directory where Angband should look for its standard
- * user file tree.
- */
-+ (NSString *)angbandDocumentsPath
-{
-       NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
-
-#if defined(SAFE_DIRECTORY)
-       NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
-       return [documents stringByAppendingPathComponent: versionedDirectory];
-#else
-       return [documents stringByAppendingPathComponent: AngbandDirectoryNameBase];
-#endif
-}
-
-/**
- * Adjust directory paths as needed to correct for any differences needed by
- * Angband. \c init_file_paths() currently requires that all paths provided have
- * a trailing slash and all other platforms honor this.
- *
- * \param originalPath The directory path to adjust.
- * \return A path suitable for Angband or nil if an error occurred.
- */
-static NSString *AngbandCorrectedDirectoryPath(NSString *originalPath)
-{
-       if ([originalPath length] == 0) {
-               return nil;
-       }
-
-       if (![originalPath hasSuffix: @"/"]) {
-               return [originalPath stringByAppendingString: @"/"];
-       }
-
-       return originalPath;
-}
-
-/**
- * Give Angband the base paths that should be used for the various directories
- * it needs. It will create any needed directories.
- */
-+ (void)prepareFilePathsAndDirectories
-{
-       char libpath[PATH_MAX + 1] = "\0";
-       NSString *libDirectoryPath = AngbandCorrectedDirectoryPath([self libDirectoryPath]);
-       [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
-
-       char basepath[PATH_MAX + 1] = "\0";
-       NSString *angbandDocumentsPath = AngbandCorrectedDirectoryPath([self angbandDocumentsPath]);
-       [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
-
-       init_file_paths(libpath, libpath, basepath);
-       create_needed_dirs();
-}
-
-#pragma mark -
-
 #if 0
 /* From the Linux mbstowcs(3) man page:
  *   If dest is NULL, n is ignored, and the conversion  proceeds  as  above,
@@ -1343,97 +1236,6 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
 }
 #endif
 
-/**
- * Entry point for initializing Angband
- */
-+ (void)beginGame
-{
-    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-    
-    /* Hooks in some "z-util.c" hooks */
-    plog_aux = hook_plog;
-    quit_aux = hook_quit;
-    
-    /* Initialize file paths */
-    [self prepareFilePathsAndDirectories];
-
-    /* Note the "system" */
-    ANGBAND_SYS = "coc";
-
-    /* Load preferences */
-    load_prefs();
-    
-    /* Prepare the windows */
-    init_windows();
-    
-    /* Set up game event handlers */
-    /* init_display(); */
-    
-    /* Register the sound hook */
-    /* sound_hook = play_sound; */
-    
-    /* Initialise game */
-    init_angband();
-
-    /* This is not incorporated into Hengband's init_angband() yet. */
-    init_graphics_modes();
-
-    /* Initialize some save file stuff */
-    player_egid = getegid();
-    
-    /* We are now initialized */
-    initialized = TRUE;
-    
-    /* Handle "open_when_ready" */
-    handle_open_when_ready();
-    
-    /* Handle pending events (most notably update) and flush input */
-    Term_flush();
-
-    /* Prompt the user. */
-    int message_row = (Term->hgt - 23) / 5 + 23;
-    Term_erase(0, message_row, 255);
-    put_str(
-#ifdef JP
-       "['ファイル' メニューから '新' または '開く' を選択します]",
-       message_row, (Term->wid - 57) / 2
-#else
-       "[Choose 'New' or 'Open' from the 'File' menu]",
-       message_row, (Term->wid - 45) / 2
-#endif
-    );
-    Term_fresh();
-
-    /*
-     * Play a game -- "new_game" is set by "new", "open" or the open document
-     * even handler as appropriate
-     */
-        
-    [pool drain];
-    
-    while (!game_in_progress) {
-        NSAutoreleasePool *splashScreenPool = [[NSAutoreleasePool alloc] init];
-        NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
-        if (event) [NSApp sendEvent:event];
-        [splashScreenPool drain];
-    }
-
-    Term_fresh();
-    play_game(new_game);
-
-    quit(NULL);
-}
-
-+ (void)endGame
-{    
-    /* Hack -- Forget messages */
-    msg_flag = FALSE;
-    
-    p_ptr->playing = FALSE;
-    p_ptr->leaving = TRUE;
-    quit_when_ready = TRUE;
-}
-
 - (void)addAngbandView:(AngbandView *)view
 {
     if (! [angbandViews containsObject:view])
@@ -1497,30 +1299,6 @@ static size_t Term_mbcs_cocoa(wchar_t *dest, const char *src, int n)
 }
 
 
-static NSMenuItem *superitem(NSMenuItem *self)
-{
-    NSMenu *supermenu = [[self menu] supermenu];
-    int index = [supermenu indexOfItemWithSubmenu:[self menu]];
-    if (index == -1) return nil;
-    else return [supermenu itemAtIndex:index];
-}
-
-
-- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
-{
-    int tag = [menuItem tag];
-    SEL sel = [menuItem action];
-    if (sel == @selector(setGraphicsMode:))
-    {
-        [menuItem setState: (tag == graf_mode_req)];
-        return YES;
-    }
-    else
-    {
-        return YES;
-    }
-}
-
 - (NSWindow *)makePrimaryWindow
 {
     if (! primaryWindow)
@@ -1675,11 +1453,11 @@ static NSMenuItem *superitem(NSMenuItem *self)
     Term_activate( old );
 }
 
-- (void)setMinimumWindowSize
+- (void)setMinimumWindowSize:(int)termIdx
 {
     NSSize minsize;
 
-    if ([self terminalIndex] == 0) {
+    if (termIdx == 0) {
        minsize.width = 80;
        minsize.height = 24;
     } else {
@@ -1750,24 +1528,36 @@ static NSMenuItem *superitem(NSMenuItem *self)
 {
     NSWindow *window = [notification object];
     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
-    [self resizeTerminalWithContentRect: contentRect saveToDefaults: YES];
+    [self resizeTerminalWithContentRect: contentRect saveToDefaults: !(self->in_fullscreen_transition)];
 }
 
 /*- (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
 {
 } */
 
+- (void)windowWillEnterFullScreen: (NSNotification *)notification
+{
+    self->in_fullscreen_transition = YES;
+}
+
 - (void)windowDidEnterFullScreen: (NSNotification *)notification
 {
     NSWindow *window = [notification object];
     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+    self->in_fullscreen_transition = NO;
     [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
 }
 
+- (void)windowWillExitFullScreen: (NSNotification *)notification
+{
+    self->in_fullscreen_transition = YES;
+}
+
 - (void)windowDidExitFullScreen: (NSNotification *)notification
 {
     NSWindow *window = [notification object];
     NSRect contentRect = [window contentRectForFrameRect: [window frame]];
+    self->in_fullscreen_transition = NO;
     [self resizeTerminalWithContentRect: contentRect saveToDefaults: NO];
 }
 
@@ -2007,6 +1797,7 @@ static void Term_init_cocoa(term *t)
 
     /* Get the window */
     NSWindow *window = [context makePrimaryWindow];
+
     /* Set its title and, for auxiliary terms, tentative size */
     NSString *title = [NSString stringWithCString:angband_term_name[termIdx]
 #ifdef JP
@@ -2016,8 +1807,8 @@ static void Term_init_cocoa(term *t)
 #endif
     ];
     [window setTitle:title];
-    [context setMinimumWindowSize];
-    
+    [context setMinimumWindowSize:termIdx];
+
     /* If this is the first term, and we support full screen (Mac OS X Lion or
         * later), then allow it to go full screen (sweet). Allow other terms to be
         * FullScreenAuxilliary, so they can at least show up. Unfortunately in
@@ -2203,14 +1994,16 @@ static CGImageRef create_angband_image(NSString *path)
 
         /* Draw the source image flipped, since the view is flipped */
         CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, CGImageGetBitsPerComponent(decodedImage), CGImageGetBytesPerRow(decodedImage), CGImageGetColorSpace(decodedImage), contextBitmapInfo);
-        CGContextSetBlendMode(ctx, kCGBlendModeCopy);
-        CGContextTranslateCTM(ctx, 0.0, height);
-        CGContextScaleCTM(ctx, 1.0, -1.0);
-        CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), decodedImage);
-        result = CGBitmapContextCreateImage(ctx);
-
-        /* Done with these things */
-        CFRelease(ctx);
+       if (ctx) {
+           CGContextSetBlendMode(ctx, kCGBlendModeCopy);
+           CGContextTranslateCTM(ctx, 0.0, height);
+           CGContextScaleCTM(ctx, 1.0, -1.0);
+           CGContextDrawImage(
+               ctx, CGRectMake(0, 0, width, height), decodedImage);
+           result = CGBitmapContextCreateImage(ctx);
+           CFRelease(ctx);
+       }
+
         CGImageRelease(decodedImage);
     }
     return result;
@@ -2249,10 +2042,36 @@ static errr Term_xtra_cocoa_react(void)
             NSString *img_path = [NSString stringWithFormat:@"%s/%s", new_mode->path, new_mode->file];
             pict_image = create_angband_image(img_path);
 
-            /* If we failed to create the image, set the new desired mode to
-                        * NULL */
-            if (! pict_image)
+            /* If we failed to create the image, revert to ASCII. */
+            if (! pict_image) {
                 new_mode = NULL;
+               if (use_bigtile) {
+                   arg_bigtile = FALSE;
+               }
+               [[NSUserDefaults angbandDefaults]
+                   setInteger:GRAPHICS_NONE
+                   forKey:AngbandGraphicsDefaultsKey];
+               [[NSUserDefaults angbandDefaults] synchronize];
+
+               NSString *msg = NSLocalizedStringWithDefaultValue(
+                   @"Error.TileSetLoadFailed",
+                   AngbandMessageCatalog,
+                   [NSBundle mainBundle],
+                   @"Failed to Load Tile Set",
+                   @"Alert text for failed tile set load");
+               NSString *info = NSLocalizedStringWithDefaultValue(
+                   @"Error.TileSetRevertToASCII",
+                   AngbandMessageCatalog,
+                   [NSBundle mainBundle],
+                   @"Could not load the tile set.  Switched back to ASCII.",
+                   @"Alert informative message for failed tile set load");
+               NSAlert *alert = [[NSAlert alloc] init];
+
+               alert.messageText = msg;
+               alert.informativeText = info;
+               NSModalResponse result = [alert runModal];
+               [alert release];
+           }
         }
         
         /* Record what we did */
@@ -2278,11 +2097,20 @@ static errr Term_xtra_cocoa_react(void)
         }
         
         /* Reset visuals */
-        if (initialized && game_in_progress)
+        if (arg_bigtile == use_bigtile)
         {
             reset_visuals();
         }
     }
+
+    if (arg_bigtile != use_bigtile) {
+       /* Reset visuals */
+       reset_visuals();
+
+       Term_activate(angband_term[0]);
+       Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+    }
+
     [pool drain];
     
     /* Success */
@@ -2344,7 +2172,11 @@ static void query_before_text(
             */
            break;
        } else if (prc->cell_changes[i].change_type == CELL_CHANGE_NONE) {
-           /* It has not changed so inquire what it is. */
+           /*
+            * It has not changed (or using big tile mode and it is within
+            * a changed tile but is not the left cell for that tile) so
+            * inquire what it is.
+            */
            TERM_COLOR a[2];
            char c[2];
 
@@ -2357,6 +2189,17 @@ static void query_before_text(
                 */
                break;
            }
+           if (use_bigtile && i > 0) {
+               Term_what(i - 1, iy, a, c);
+               if (use_graphics && (a[0] & 0x80) && (c[0] & 0x80)) {
+                   /*
+                    * It is the right cell of a location rendered with a tile.
+                    * Do not want to modify its contents so the clipping and
+                    * rendering region can not be exteded.
+                    */
+                   break;
+               }
+           }
            /*
             * It is unchanged text.  A character from the changed region
             * may have extended into it so render it to clear that.
@@ -2525,15 +2368,22 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
     int graf_width, graf_height, alphablend;
 
     if (angbandContext->changes->has_pict) {
+       CGImageAlphaInfo ainfo = CGImageGetAlphaInfo(pict_image);
+
        graf_width = current_graphics_mode->cell_width;
        graf_height = current_graphics_mode->cell_height;
        /*
-        * Transparency effect. We really want to check
-        * current_graphics_mode->alphablend, but as of this writing
-        * that's  never set, so we do something lame.
+        * As of this writing, a value of zero for
+        * current_graphics_mode->alphablend can mean either that the tile set
+        * doesn't have an alpha channel or it does but it only takes on values
+        * of 0 or 255.  For main-cocoa.m's purposes, the latter is rendered
+        * using the same procedure as if alphablend was nonzero.  The former
+        * is handled differently, but alphablend doesn't distinguish it from
+        * the latter.  So ignore alphablend and directly test whether an
+        * alpha channel is present.
         */
-       /* alphablend = current_graphics_mode->alphablend */
-       alphablend = (graf_width > 8 || graf_height > 8);
+       alphablend = (ainfo & (kCGImageAlphaPremultipliedFirst |
+                              kCGImageAlphaPremultipliedLast)) ? 1 : 0;
     } else {
        graf_width = 0;
        graf_height = 0;
@@ -2582,6 +2432,7 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
                    NSGraphicsContext *nsContext =
                        [NSGraphicsContext currentContext];
                    NSCompositingOperation op = nsContext.compositingOperation;
+                   int step = (use_bigtile) ? 2 : 1;
 
                    jx = ix;
                    while (jx <= prc->xmax &&
@@ -2591,6 +2442,7 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
                            [angbandContext rectInImageForTileAtX:jx Y:iy];
                        NSRect sourceRect, terrainRect;
 
+                       destinationRect.size.width *= step;
                        sourceRect.origin.x = graf_width *
                            prc->cell_changes[jx].c.c;
                        sourceRect.origin.y = graf_height *
@@ -2611,13 +2463,20 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
                                terrainRect,
                                destinationRect,
                                NSCompositeCopy);
-                           draw_image_tile(
-                               nsContext,
-                               ctx,
-                               pict_image,
-                               sourceRect,
-                               destinationRect,
-                               NSCompositeSourceOver);
+                           /*
+                            * Skip drawing the foreground if it is the same
+                            * as the background.
+                            */
+                           if (sourceRect.origin.x != terrainRect.origin.x ||
+                               sourceRect.origin.y != terrainRect.origin.y) {
+                               draw_image_tile(
+                                   nsContext,
+                                   ctx,
+                                   pict_image,
+                                   sourceRect,
+                                   destinationRect,
+                                   NSCompositeSourceOver);
+                           }
                        } else {
                            draw_image_tile(
                                nsContext,
@@ -2627,7 +2486,7 @@ static void Term_xtra_cocoa_fresh(AngbandContext* angbandContext)
                                destinationRect,
                                NSCompositeCopy);
                        }
-                       ++jx;
+                       jx += step;
                    }
 
                    [nsContext setCompositingOperation:op];
@@ -2901,7 +2760,7 @@ static errr Term_xtra_cocoa(int n, int v)
     return result;
 }
 
-static errr Term_curs_cocoa(int x, int y)
+static errr Term_curs_cocoa(TERM_LEN x, TERM_LEN y)
 {
     AngbandContext *angbandContext = Term->data;
 
@@ -2922,7 +2781,7 @@ static errr Term_curs_cocoa(int x, int y)
  * the cursor points at a kanji character, irregardless of whether operating
  * in big tile mode.
  */
-static errr Term_bigcurs_cocoa(int x, int y)
+static errr Term_bigcurs_cocoa(TERM_LEN x, TERM_LEN y)
 {
     AngbandContext *angbandContext = Term->data;
 
@@ -2943,7 +2802,7 @@ static errr Term_bigcurs_cocoa(int x, int y)
  *
  * Erase "n" characters starting at (x,y)
  */
-static errr Term_wipe_cocoa(int x, int y, int n)
+static errr Term_wipe_cocoa(TERM_LEN x, TERM_LEN y, int n)
 {
     AngbandContext *angbandContext = Term->data;
     struct PendingCellChange *pc;
@@ -2984,9 +2843,9 @@ static errr Term_wipe_cocoa(int x, int y, int n)
     return (0);
 }
 
-static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
-                            const char *cp, const TERM_COLOR *tap,
-                            const char *tcp)
+static errr Term_pict_cocoa(TERM_LEN x, TERM_LEN y, int n,
+                           TERM_COLOR *ap, concptr cp,
+                           const TERM_COLOR *tap, concptr tcp)
 {
     
     /* Paranoia: Bail if we don't have a current graphics mode */
@@ -2994,12 +2853,18 @@ static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
     
     AngbandContext* angbandContext = Term->data;
     int any_change = 0;
+    int step = (use_bigtile) ? 2 : 1;
     struct PendingCellChange *pc;
 
     if (angbandContext->changes == 0) {
        /* Bail out; there was an earlier memory allocation failure. */
        return 1;
     }
+    /*
+     * In bigtile mode, it is sufficient that the bounds for the modified
+     * region only encompass the left cell for the region affected by the
+     * tile and that only that cell has to have the details of the changes.
+     */
     if (angbandContext->changes->rows[y] == 0) {
        angbandContext->changes->rows[y] =
            create_row_change(angbandContext->cols);
@@ -3018,12 +2883,12 @@ static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
     if (angbandContext->changes->rows[y]->xmin > x) {
        angbandContext->changes->rows[y]->xmin = x;
     }
-    if (angbandContext->changes->rows[y]->xmax < x + n - 1) {
-       angbandContext->changes->rows[y]->xmax = x + n - 1;
+    if (angbandContext->changes->rows[y]->xmax < x + step * (n - 1)) {
+       angbandContext->changes->rows[y]->xmax = x + step * (n - 1);
     }
     for (pc = angbandContext->changes->rows[y]->cell_changes + x;
-        pc != angbandContext->changes->rows[y]->cell_changes + x + n;
-        ++pc) {
+        pc != angbandContext->changes->rows[y]->cell_changes + x + step * n;
+        pc += step) {
        TERM_COLOR a = *ap++;
        char c = *cp++;
        TERM_COLOR ta = *tap++;
@@ -3051,7 +2916,8 @@ static errr Term_pict_cocoa(int x, int y, int n, TERM_COLOR *ap,
  *
  * Draw several ("n") chars, with an attr, at a given location.
  */
-static errr Term_text_cocoa(int x, int y, int n, byte_hack a, concptr cp)
+static errr Term_text_cocoa(
+    TERM_LEN x, TERM_LEN y, int n, TERM_COLOR a, concptr cp)
 {
     AngbandContext* angbandContext = Term->data;
     struct PendingCellChange *pc;
@@ -3268,6 +3134,7 @@ static void load_prefs()
                               [NSNumber numberWithInt:60], AngbandFrameRateDefaultsKey,
                               [NSNumber numberWithBool:YES], AngbandSoundDefaultsKey,
                               [NSNumber numberWithInt:GRAPHICS_NONE], AngbandGraphicsDefaultsKey,
+                              [NSNumber numberWithBool:YES], AngbandBigTileDefaultsKey,
                               defaultTerms, AngbandTerminalsDefaultsKey,
                               nil];
     [defs registerDefaults:defaults];
@@ -3276,7 +3143,16 @@ static void load_prefs()
     
     /* Preferred graphics mode */
     graf_mode_req = [defs integerForKey:AngbandGraphicsDefaultsKey];
-    
+    if (graf_mode_req != GRAPHICS_NONE &&
+       get_graphics_mode(graf_mode_req)->grafID != GRAPHICS_NONE &&
+       [defs boolForKey:AngbandBigTileDefaultsKey] == YES) {
+       use_bigtile = TRUE;
+       arg_bigtile = TRUE;
+    } else {
+       use_bigtile = FALSE;
+       arg_bigtile = FALSE;
+    }
+
     /* Use sounds; set the Angband global */
     use_sound = ([defs boolForKey:AngbandSoundDefaultsKey] == YES) ? TRUE : FALSE;
     
@@ -3855,32 +3731,114 @@ static void hook_quit(const char * str)
 }
 
 /**
- * ------------------------------------------------------------------------
- * Main program
- * ------------------------------------------------------------------------ */
+ * Return the path for Angband's lib directory and bail if it isn't found. The
+ * lib directory should be in the bundle's resources directory, since it's
+ * copied when built.
+ */
+static NSString* get_lib_directory(void)
+{
+    NSString *bundleLibPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: AngbandDirectoryNameLib];
+    BOOL isDirectory = NO;
+    BOOL libExists = [[NSFileManager defaultManager] fileExistsAtPath: bundleLibPath isDirectory: &isDirectory];
+
+    if( !libExists || !isDirectory )
+    {
+       NSLog( @"Hengband: can't find %@/ in bundle: isDirectory: %d libExists: %d", AngbandDirectoryNameLib, isDirectory, libExists );
+
+       NSString *msg = NSLocalizedStringWithDefaultValue(
+           @"Error.MissingResources",
+           AngbandMessageCatalog,
+           [NSBundle mainBundle],
+           @"Missing Resources",
+           @"Alert text for missing resources");
+       NSString *info = NSLocalizedStringWithDefaultValue(
+           @"Error.MissingAngbandLib",
+           AngbandMessageCatalog,
+           [NSBundle mainBundle],
+           @"Hengband was unable to find required resources and must quit. Please report a bug on the Angband forums.",
+           @"Alert informative message for missing Angband lib/ folder");
+       NSString *quit_label = NSLocalizedStringWithDefaultValue(
+           @"Label.Quit", AngbandMessageCatalog, [NSBundle mainBundle],
+           @"Quit", @"Quit");
+       NSAlert *alert = [[NSAlert alloc] init];
+
+       /*
+        * Note that NSCriticalAlertStyle was deprecated in 10.10.  The
+        * replacement is NSAlertStyleCritical.
+        */
+       alert.alertStyle = NSCriticalAlertStyle;
+       alert.messageText = msg;
+       alert.informativeText = info;
+       [alert addButtonWithTitle:quit_label];
+       NSModalResponse result = [alert runModal];
+       [alert release];
+       exit( 0 );
+    }
+
+    return bundleLibPath;
+}
+
+/**
+ * Return the path for the directory where Angband should look for its standard
+ * user file tree.
+ */
+static NSString* get_doc_directory(void)
+{
+       NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
 
-@interface AngbandAppDelegate : NSObject {
-    IBOutlet NSMenu *terminalsMenu;
-    NSMenu *_graphicsMenu;
-    NSMenu *_commandMenu;
-    NSDictionary *_commandMenuTagMap;
+#if defined(SAFE_DIRECTORY)
+       NSString *versionedDirectory = [NSString stringWithFormat: @"%@-%s", AngbandDirectoryNameBase, VERSION_STRING];
+       return [documents stringByAppendingPathComponent: versionedDirectory];
+#else
+       return [documents stringByAppendingPathComponent: AngbandDirectoryNameBase];
+#endif
 }
 
-@property (nonatomic, retain) IBOutlet NSMenu *graphicsMenu;
-@property (nonatomic, retain) IBOutlet NSMenu *commandMenu;
-@property (nonatomic, retain) NSDictionary *commandMenuTagMap;
+/**
+ * Adjust directory paths as needed to correct for any differences needed by
+ * Angband.  init_file_paths() currently requires that all paths provided have
+ * a trailing slash and all other platforms honor this.
+ *
+ * \param originalPath The directory path to adjust.
+ * \return A path suitable for Angband or nil if an error occurred.
+ */
+static NSString* AngbandCorrectedDirectoryPath(NSString *originalPath)
+{
+       if ([originalPath length] == 0) {
+               return nil;
+       }
+
+       if (![originalPath hasSuffix: @"/"]) {
+               return [originalPath stringByAppendingString: @"/"];
+       }
 
-- (IBAction)newGame:sender;
-- (IBAction)openGame:sender;
+       return originalPath;
+}
 
-- (IBAction)editFont:sender;
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender;
-- (IBAction)toggleSound:(NSMenuItem *)sender;
+/**
+ * Give Angband the base paths that should be used for the various directories
+ * it needs. It will create any needed directories.
+ */
+static void prepare_paths_and_directories(void)
+{
+       char libpath[PATH_MAX + 1] = "\0";
+       NSString *libDirectoryPath =
+           AngbandCorrectedDirectoryPath(get_lib_directory());
+       [libDirectoryPath getFileSystemRepresentation: libpath maxLength: sizeof(libpath)];
 
-- (IBAction)setRefreshRate:(NSMenuItem *)menuItem;
-- (IBAction)selectWindow: (id)sender;
+       char basepath[PATH_MAX + 1] = "\0";
+       NSString *angbandDocumentsPath =
+           AngbandCorrectedDirectoryPath(get_doc_directory());
+       [angbandDocumentsPath getFileSystemRepresentation: basepath maxLength: sizeof(basepath)];
 
-@end
+       init_file_paths(libpath, basepath);
+       create_needed_dirs();
+}
+
+/**
+ * ------------------------------------------------------------------------
+ * Main program
+ * ------------------------------------------------------------------------ */
 
 @implementation AngbandAppDelegate
 
@@ -3956,6 +3914,14 @@ static void hook_quit(const char * str)
     [(id)angbandContext setSelectionFont:newFont adjustTerminal: YES];
     
     NSEnableScreenUpdates();
+
+    if (mainTerm == 0 && game_in_progress) {
+       /* Mimics the logic in setGraphicsMode(). */
+       do_cmd_redraw();
+       wakeup_event_loop();
+    } else {
+       [(id)angbandContext requestRedraw];
+    }
 }
 
 - (IBAction)openGame:sender
@@ -4020,6 +3986,86 @@ static void hook_quit(const char * str)
 }
 
 /**
+ * Entry point for initializing Angband
+ */
+- (void)beginGame
+{
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+    /* Hooks in some "z-util.c" hooks */
+    plog_aux = hook_plog;
+    quit_aux = hook_quit;
+
+    /* Initialize file paths */
+    prepare_paths_and_directories();
+
+    /* Note the "system" */
+    ANGBAND_SYS = "coc";
+
+    /* Load possible graphics modes */
+    init_graphics_modes();
+
+    /* Load preferences */
+    load_prefs();
+
+    /* Prepare the windows */
+    init_windows();
+
+    /* Set up game event handlers */
+    /* init_display(); */
+
+    /* Register the sound hook */
+    /* sound_hook = play_sound; */
+
+    /* Initialise game */
+    init_angband();
+
+    /* Initialize some save file stuff */
+    player_egid = getegid();
+
+    /* We are now initialized */
+    initialized = TRUE;
+
+    /* Handle "open_when_ready" */
+    handle_open_when_ready();
+
+    /* Handle pending events (most notably update) and flush input */
+    Term_flush();
+
+    /* Prompt the user. */
+    int message_row = (Term->hgt - 23) / 5 + 23;
+    Term_erase(0, message_row, 255);
+    put_str(
+#ifdef JP
+       "['ファイル' メニューから '新' または '開く' を選択します]",
+       message_row, (Term->wid - 57) / 2
+#else
+       "[Choose 'New' or 'Open' from the 'File' menu]",
+       message_row, (Term->wid - 45) / 2
+#endif
+    );
+    Term_fresh();
+
+    [pool drain];
+
+    while (!game_in_progress) {
+        NSAutoreleasePool *splashScreenPool = [[NSAutoreleasePool alloc] init];
+        NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES];
+        if (event) [NSApp sendEvent:event];
+        [splashScreenPool drain];
+    }
+
+    /*
+     * Play a game -- "new_game" is set by "new", "open" or the open document
+     * even handler as appropriate
+     */
+    Term_fresh();
+    play_game(new_game);
+
+    quit(NULL);
+}
+
+/**
  * Implement NSObject's validateMenuItem() method to override enabling or
  * disabling a menu item.  Note that, as of 10.14, validateMenuItem() is
  * deprecated in NSObject - it will be removed at some point and  the
@@ -4066,7 +4112,8 @@ static void hook_quit(const char * str)
     {
         return ! game_in_progress;
     }
-    else if (sel == @selector(setRefreshRate:) && [superitem(menuItem) tag] == 150)
+    else if (sel == @selector(setRefreshRate:) &&
+            [[menuItem parentItem] tag] == 150)
     {
         NSInteger fps = [[NSUserDefaults standardUserDefaults] integerForKey:AngbandFrameRateDefaultsKey];
         [menuItem setState: ([menuItem tag] == fps)];
@@ -4086,10 +4133,14 @@ static void hook_quit(const char * str)
        [menuItem setState: ((is_on) ? NSOnState : NSOffState)];
        return YES;
     }
-    else if( sel == @selector(sendAngbandCommand:) )
+    else if( sel == @selector(sendAngbandCommand:) ||
+            sel == @selector(saveGame:) )
     {
-        /* we only want to be able to send commands during an active game */
-        return !!game_in_progress;
+        /*
+         * we only want to be able to send commands during an active game
+         * after the birth screens
+         */
+        return !!game_in_progress && character_generated;
     }
     else return YES;
 }
@@ -4101,7 +4152,7 @@ static void hook_quit(const char * str)
     [[NSUserDefaults angbandDefaults] setInteger:frames_per_second forKey:AngbandFrameRateDefaultsKey];
 }
 
-- (IBAction)selectWindow: (id)sender
+- (void)selectWindow: (id)sender
 {
     NSInteger subwindowNumber = [(NSMenuItem *)sender tag] - AngbandWindowMenuItemTagBase;
     AngbandContext *context = angband_term[subwindowNumber]->data;
@@ -4148,7 +4199,7 @@ static void hook_quit(const char * str)
     }
 }
 
-- (IBAction)setGraphicsMode:(NSMenuItem *)sender
+- (void)setGraphicsMode:(NSMenuItem *)sender
 {
     /* We stashed the graphics mode ID in the menu item's tag */
     graf_mode_req = [sender tag];
@@ -4157,8 +4208,23 @@ static void hook_quit(const char * str)
     [[NSUserDefaults angbandDefaults] setInteger:graf_mode_req forKey:AngbandGraphicsDefaultsKey];
     [[NSUserDefaults angbandDefaults] synchronize];
     
+    if (graf_mode_req == GRAPHICS_NONE ||
+       get_graphics_mode(graf_mode_req) == GRAPHICS_NONE) {
+       if (use_bigtile) {
+           arg_bigtile = FALSE;
+       }
+    } else if ([[NSUserDefaults angbandDefaults] boolForKey:AngbandBigTileDefaultsKey] == YES &&
+              ! use_bigtile) {
+       arg_bigtile = TRUE;
+    }
+
     if (game_in_progress)
     {
+       if (arg_bigtile != use_bigtile) {
+           Term_activate(angband_term[0]);
+           Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+       }
+
         /* Hack -- Force redraw */
         do_cmd_redraw();
         
@@ -4178,6 +4244,27 @@ static void hook_quit(const char * str)
                                      forKey:AngbandSoundDefaultsKey];
 }
 
+- (IBAction)toggleWideTiles:(NSMenuItem *) sender
+{
+    BOOL is_on = (sender.state == NSOnState);
+
+    /* Toggle the state and update the Angband globals and preferences. */
+    sender.state = (is_on) ? NSOffState : NSOnState;
+    [[NSUserDefaults angbandDefaults] setBool:(! is_on)
+                                     forKey:AngbandBigTileDefaultsKey];
+    [[NSUserDefaults angbandDefaults] synchronize];
+    if (graphics_are_enabled()) {
+       arg_bigtile = (is_on) ? FALSE : TRUE;
+       /* Mimics the logic in setGraphicsMode(). */
+       if (game_in_progress && arg_bigtile != use_bigtile) {
+           Term_activate(angband_term[0]);
+           Term_resize(angband_term[0]->wid, angband_term[0]->hgt);
+           do_cmd_redraw();
+           wakeup_event_loop();
+       }
+    }
+}
+
 /**
  *  Send a command to Angband via a menu item. This places the appropriate key
  * down events into the queue so that it seems like the user pressed them
@@ -4269,7 +4356,7 @@ static void hook_quit(const char * str)
 
 - (void)applicationDidFinishLaunching:sender
 {
-    [AngbandContext beginGame];
+    [self beginGame];
     
     /* Once beginGame finished, the game is over - that's how Angband works,
         * and we should quit */
@@ -4358,27 +4445,39 @@ static void hook_quit(const char * str)
 /**
  * Delegate method that gets called if we're asked to open a file.
  */
-- (BOOL)application:(NSApplication *)sender openFiles:(NSArray *)filenames
+- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
 {
     /* Can't open a file once we've started */
-    if (game_in_progress) return NO;
-    
+    if (game_in_progress) {
+       [[NSApplication sharedApplication]
+           replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+       return;
+    }
+
     /* We can only open one file. Use the last one. */
     NSString *file = [filenames lastObject];
-    if (! file) return NO;
-    
+    if (! file) {
+       [[NSApplication sharedApplication]
+           replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+       return;
+    }
+
     /* Put it in savefile */
-    if (! [file getFileSystemRepresentation:savefile maxLength:sizeof savefile])
-               return NO;
-    
+    if (! [file getFileSystemRepresentation:savefile maxLength:sizeof savefile]) {
+       [[NSApplication sharedApplication]
+           replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
+       return;
+    }
+
     game_in_progress = TRUE;
     new_game = FALSE;
 
     /* Wake us up in case this arrives while we're sitting at the Welcome
         * screen! */
     wakeup_event_loop();
-    
-    return YES;
+
+    [[NSApplication sharedApplication]
+       replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
 }
 
 @end