/* Main program of the Mac interface to Xconq.
   Copyright (C) 1992-1999 Stanley T. Shebs.

Xconq 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, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "macconq.h"

#include <errors.h>
#include <time.h>

/* Include Profile.h if profiling is enabled in CodeWarrior. */
#if __profile__
#include <Profiler.h>
#endif

#ifdef __MWERKS__
#include <Sound.h>
#endif

#ifdef THINK_C
#define TETextBox TextBox
#endif

#define keyReplyErr 'errn'

#define PREFSIZE 2048		/* Size of prefs buffer in lisp format. Must be bigger than the default */
						/* BUFSIZE = 256 to make room for all the new preferences */

extern int ok_to_exit;
extern short initialvrefnum;

char *connection_method_name;

/* Function prototypes. */

static int splash_dialog(void);
static void init_toolbox(void);
static void recalc_depths(void);
static void init_fonts(void);
static void init_rects(void);
static void init_ae(void);
static void get_files(void);

static void event_loop(void);
static void handle_keystroke(EventRecord *event);
static void set_end_of_game_interaction_modes(void);
static void adjust_cursor(Point mouse, RgnHandle region);
static void handle_event(EventRecord *event);

static void drag_window(WindowPtr win, EventRecord *event);
static void grow_window(WindowPtr win, Point where);
static void zoom_window(WindowPtr win, Point where, int part);
static void do_mouse_down(WindowPtr window, EventRecord *event);
static void activate_window(WindowPtr win, int activate);

static void maybe_select_next_unit(void);
static int select_new_unit(Map *map, Unit *unit);

static Boolean missed_any_parameters(AppleEvent *message);
static pascal OSErr do_ae_open_application(AppleEvent *message, AppleEvent *reply, long refcon);
static pascal OSErr do_ae_open_documents(AppleEvent *message, AppleEvent *reply, long refcon);
static pascal OSErr do_ae_print_documents(AppleEvent *message, AppleEvent *reply, long refcon);
static pascal OSErr do_ae_quit_application(AppleEvent *message, AppleEvent *reply, long refcon);
static pascal OSErr do_ae_join_game(AppleEvent *message, AppleEvent *reply, long refcon);

static pascal Boolean filter_warning_alert(DialogPtr dialog, EventRecord *evt, short *itemhit);

static int is_da_window(WindowPtr win);
static int is_app_window(WindowPtr win);

static void update_unit_in_maps(Unit *unit);

static void won_game_dialog(void);
static void lost_game_dialog(void);
static void game_over_dialog(void);

static void play_sound(char *soundname);
static int position_on_screen(int h, int v);
static int position_already_used(int h, int v);


/* Global variables. */

/* This is the id of any map modal tool currently in effect. */

int map_modal = 0;

int inbackground;

/* This is the list of maps that we're using. */

struct a_map *maplist;

/* This is the list of lists. */

struct a_list *listlist;

/* This is the list of unit closeups. */

struct a_unit_closeup *unitcloseuplist;

/* This indicates whether the general game resource files was found. */

int foundresourcesfile = FALSE;

/* This flag indicates whether the image etc resource file(s) were found. */

int foundimagesfile = FALSE;

/* The usual width of a scrollbar. */

int sbarwid = 15;

/* The width of a floating window scrollbar. */

int floatsbarwid = 10;

/* Info about various fonts we use. */

short small_font_id;
short small_font_size;
short small_line_spacing;

short large_font_id;
short large_font_size;
short large_line_spacing;

short title_font_id;
short title_font_size;
short title_line_spacing;

/* True if we're going to use WaitNextEvent. */

int useWNE = FALSE;

/* Rectangle that constrains window dragging. */

Rect dragrect;

/* Rectangle that constrains window resizing. */

Rect sizerect;

/* This is the side that is using this Mac as its display. */

Side *dside = NULL;

/* This points to a spare block of memory that is freed so shutdown code can
   use it (no guarantee that it will tho). */

Handle spare;

/* This is true when a single click suffices to move a unit. */

int defaultmoveonclick = TRUE;

int defaultautoselect = TRUE;

int wasingame = TRUE;

/* Set to true if we should play the sound effects. */

int playsounds = TRUE;

/* Set to true if Color QuickDraw is installed. */

int hasColorQD;

/* The range of screen pixel depths that the display has to cope with. */

int minscreendepth = -1;

int maxscreendepth = -1;

/* This is true if AppleEvents are available. */

int hasAppleEvents;

/* This is true if the PPC toolbox is available. */

int hasPPCToolbox;

/* Flag that governs exit from the main event loop.  Set by AE handlers
   and such. */

int eventloopdone = FALSE;

/* True when a won or lost dialog has been put up on the screen already. */

static int told_outcome = FALSE;

/* This is set when the user has requested exit from the program, used
   when unwinding the game state while getting out. */

static int want_to_exit = FALSE;

static int last_tick_count = 0;

enum movie_type {
  movie_null,
  movie_miss,
  movie_hit,
  movie_death,
  movie_nuke,
  movie_sound,
  movie_flash
};

struct a_movie {
  char *type;
  enum movie_type itype;
  int args[5];
};

/* Number of movies waiting to be played. */

int numscheduled;

/* Movies to be played. */

struct a_movie *movies;

ModalFilterUPP filter_warning_alert_proc;

AEEventHandlerUPP do_ae_open_application_proc;
AEEventHandlerUPP do_ae_open_documents_proc;
AEEventHandlerUPP do_ae_print_documents_proc;
AEEventHandlerUPP do_ae_quit_application_proc;
AEEventHandlerUPP do_ae_join_game_proc;

/* The main Mac program. */

int
main()
{
	/* Do the most basic Macintosh setup. */
	init_toolbox();
	init_cursors();
	init_patterns();
	init_icons();
	init_fonts();
	init_menus();
	init_rects();
	imf_load_hook = mac_load_imf;
	imf_interp_hook = mac_interp_imf;
	/* Put the Xconq kernel into a known state. */
	clear_game_modules();
	init_data_structures();
	init_library_path(NULL);
	init_ae();
	/* Acquire Mac-specific files (preferences and resources). */
	/* Load colors used in absence of saved preferences. */
	set_default_colors();
	get_files();
	/* Check if any screen has more than 256 colors and propose reduction. */	
	/* Don't check screen if this function has been turned off. */
	if (alert_256_colors)
		check_screen_depths();
	/* A hack to ensure some memory available for error handling. */
	spare = NewHandle(2000);

#if __profile__

	/* Prepare profiling for CodeWarrior. */
	ProfilerInit(collectDetailed, bestTimeBase, 1250, 36);
	ProfilerSetStatus(false);

#endif

	/* If no Apple Events, go to the splash screen now, otherwise we'll wait
	   for an oapp/odoc/pdoc event to decide what to do. */
	if (!hasAppleEvents) {
		if (splash_dialog() == diSplashQuit)
		  return 0;
	}
	/* All essential init done, jump into the main event loop. */
	event_loop();
	/* Will exit here, or perhaps via error. */
	return 0;
}

/* This is the first dialog that the user sees.  It doesn't do much
   besides provide the initial choice of how to get into the program
   proper. */

int
splash_dialog()
{
	switch (do_splash_box()) {
		case diSplashNew:
			new_game_dialog();
			break;
		case diSplashOpen:
			open_game_dialog();
			break;
		case diSplashConnect:
			hosting = FALSE;
			connect_game_dialog();
			break;
		case diSplashQuit:
			return diSplashQuit;
	}
	return -1;
}

/* Do the usual Mac setup calls. */

static void
init_toolbox()
{
	SysEnvRec se;
	
#if  __MC68K__

	/* Increase stack size from default 24K (ColorQD) to 64K by adding 40K. */

	SetApplLimit(GetApplLimit() - 40960);
	
#endif

	MaxApplZone();

	InitGraf(&QD(thePort));
	InitFonts();
	FlushEvents(everyEvent, 0);
	InitWindows();
	InitFloats();
	InitMenus();
	TEInit();
	InitDialogs(NULL);
	InitCursor();

	/* We need to set this one up early, since may be used in error
	   reporting. */
    filter_warning_alert_proc = NewModalFilterProc(filter_warning_alert);

	SysEnvirons(2, &se);
	hasColorQD = se.hasColorQD;
#if 0 /* use this to test no-CQD support */
	hasColorQD = FALSE;
#endif
	DGprintf("%s Color QuickDraw\n", (hasColorQD ? "Using" : "Not using"));
	recalc_depths();
}

/* Look at all the devices and compute the range of screen depths. */

void
recalc_depths()
{
	int depth, oldmin = minscreendepth, oldmax = maxscreendepth;
	GDHandle gdev;

	if (hasColorQD) {
		gdev = GetDeviceList();
		minscreendepth = maxscreendepth = (*((*gdev)->gdPMap))->pixelSize;
		while ((gdev = GetNextDevice(gdev)) != nil) {
			depth = (*((*gdev)->gdPMap))->pixelSize;
			if (depth < minscreendepth)
			  minscreendepth = depth;
			if (depth > maxscreendepth)
			  maxscreendepth = depth;
		}
	} else {
		minscreendepth = maxscreendepth = 1;
	}
	if (minscreendepth != oldmin || maxscreendepth != oldmax) {
		DGprintf("Screen depths range from %d to %d\n", minscreendepth, maxscreendepth);
	}
}

/* Do some basic font setup.  We have three main uses for text that should be
   distinguished by font: general textual data, headings, and large titles,
   such as for help topics. */

static void
init_fonts()
{
    /* (should collect these from resources) */

	small_font_id = geneva;
	small_font_size = 9;
	large_font_id = geneva;
	large_font_size = 12;
	title_font_id = times;
	title_font_size = 18;
	recalc_spacing();
}

/* This calculates and saves sizes relating to fonts, so that graphics code
   can leave the right amount of space for text. */

void
recalc_spacing()
{
	FontInfo fontinfo;
	GrafPtr oldport;
	GrafPort tmpportrec;

	GetPort(&oldport);
	OpenPort(&tmpportrec);
	TextFont(small_font_id);
	TextSize(small_font_size);
	GetFontInfo(&fontinfo);
	small_line_spacing = fontinfo.ascent + fontinfo.descent + fontinfo.leading;
	/* Also calculate new values for globals that depend on font size. */
	tophgt = small_line_spacing + 4;
	topunithgt = 5 * (small_line_spacing + 1) + 4;
	TextFont(large_font_id);
	TextSize(large_font_size);
	GetFontInfo(&fontinfo);
	large_line_spacing = fontinfo.ascent + fontinfo.descent + fontinfo.leading;
	TextFont(title_font_id);
	TextSize(title_font_size);
	GetFontInfo(&fontinfo);
	title_line_spacing = fontinfo.ascent + fontinfo.descent + fontinfo.leading;
	ClosePort(&tmpportrec);
	SetPort(oldport);
}

/* Set up the generic dragging and sizing rects. */

static void
init_rects()
{
	RgnHandle screenrgn;

	screenrgn = GetGrayRgn();
	dragrect = (*screenrgn)->rgnBBox;
	SetRect(&sizerect, 50, 50, (*screenrgn)->rgnBBox.right,  (*screenrgn)->rgnBBox.bottom);
}

/* Basic Apple Event handling. */

static void
init_ae()
{
	OSErr err;
	long rslt;

	hasPPCToolbox  = (Gestalt(gestaltPPCToolboxAttr, &rslt) ? false : (rslt != 0));
	hasAppleEvents = (Gestalt(gestaltAppleEventsAttr, &rslt) ? false : (rslt != 0));

	if (hasAppleEvents) {
		do_ae_open_application_proc = NewAEEventHandlerProc(do_ae_open_application);
		do_ae_open_documents_proc = NewAEEventHandlerProc(do_ae_open_documents);
		do_ae_print_documents_proc = NewAEEventHandlerProc(do_ae_print_documents);
		do_ae_quit_application_proc = NewAEEventHandlerProc(do_ae_quit_application);
		do_ae_join_game_proc = NewAEEventHandlerProc(do_ae_join_game);

		err = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
									do_ae_open_application_proc, 0L, false);
		if (err) {
			init_warning("AppleEvent handler could not be installed, skipping others");
			return;
		}
		err = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
									do_ae_open_documents_proc, 0L, false);
		if (err) {
			init_warning("AppleEvent handler could not be installed, skipping others");
			return;
		}
		err = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments,
									do_ae_print_documents_proc, 0L, false);
		if (err) {
			init_warning("AppleEvent handler could not be installed, skipping others");
			return;
		}
		err = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
									do_ae_quit_application_proc, 0L, false);
		if (err) {
			init_warning("AppleEvent handler could not be installed, skipping others");
			return;
		}
		err = AEInstallEventHandler('xcnq', 'join',
									do_ae_join_game_proc, 0L, false);
		if (err) {
			init_warning("AppleEvent handler could not be installed, skipping others");
			return;
		}
	}
}

short prefs_refnum = -1;

short images_refnum = -1;

/* Open and/or load any files that we might need, such as preferences
   and resources. */

void
get_files()
{
	int imagesfilenameres;
	Str255 filename;

	/* Capture the current vrefnum. */
	GetVol(NULL, &initialvrefnum);
	/* Load up any preferences. */
	get_preferences();
	/* Look for and open game library resource files. */
	foundresourcesfile = FALSE;
	GetIndString(filename, sFilenames, siResources);
	if (OpenResFile(filename) != -1) {
		foundresourcesfile = TRUE;
	}
	foundimagesfile = FALSE;
	imagesfilenameres = (hasColorQD ? sImagesColorFilenames : sImagesBWFilenames);
	/* (should find and open all the strings in this resource) */
	GetIndString(filename, imagesfilenameres, 1);
	
	images_refnum = OpenResFile(filename);
	if (images_refnum != -1) {
		foundimagesfile = TRUE;
	}
	GetIndString(filename, imagesfilenameres, 2);
	OpenResFile(filename);
	GetIndString(filename, sFilenames, siSounds);
	if (OpenResFile(filename) != -1) {
		/* (need to do anything special if sounds not found??) */
	}
	/* Note that we don't complain yet if the resource/image files are missing,
	   since we don't yet know whether we actually need anything from them.
	   (Images etc might be built into app or game module, for instance.) */
}

/* Since Mac programs effectively take over the entire machine, we depend on
   this event loop to handle everything that might come along.  */

void
event_loop()
{
	int 			done = FALSE;
	Boolean		gotevent;
	Point 		mouse;
	EventRecord	event;
	RgnHandle 		cursorRgn;
	short 		itemhit;
	DialogPtr 		dialog;

	/* Figure out if the WaitNextEvent Trap is available. */
	useWNE = (NGetTrapAddress(0x60, ToolTrap) != NGetTrapAddress(0x9f, ToolTrap));
	/* Pass WNE an empty region the 1st time thru. */
	cursorRgn = NewRgn();
	/* Loop (almost) forever. */
	while (!eventloopdone) {
		get_global_mouse(&mouse);
		adjust_cursor(mouse, cursorRgn);
		/* Use WaitNextEvent if it is available, otherwise GetNextEvent. */
		if (useWNE) {
			gotevent = WaitNextEvent(everyEvent, &event, 0L, cursorRgn);
		} else {
			SystemTask();
			gotevent = GetNextEvent(everyEvent, &event);
		}
		if (gotevent) {
			handle_event(&event);
		} else if (connection_method > 0 && numremotewaiting > 0) {
			receive_data(0);
		} else if (!beforestart && !endofgame) {
			/* On null events, give the kernel a chance to run something. */
			/* DON'T automatically go to a watch cursor, since run_game often
			   returns very quickly.  Instead, long-running subroutines should
			   call back to put a watch cursor up. */
#if __profile__
	ProfilerSetStatus(true);
#endif
			run_local_ai(1, 20);
			net_run_game(1);
#if __profile__
	ProfilerSetStatus(false);
#endif
			maybe_select_next_unit();

			/* If the game ended, force various changes in interaction. */
			if (endofgame)
			  set_end_of_game_interaction_modes();
		}
		/* Check for any input from remotes. */
		if (connection_method > 0 && my_rid > 0) {
			receive_data(0);
		}
		/* Blink TE cursors regularly. */
		if (commandwin != nil && topWindow == commandwin) {
			TEIdle(command_text);
		}
		if (ok_to_exit && want_to_exit) {
			exit_macconq();
		}
	}
}

void
handle_keystroke(EventRecord *event)
{
	char	charCode = event->message & charCodeMask;
	char	keyCode = (event->message & keyCodeMask) >> 8;
	int	mods = event->modifiers;
	int	handled = FALSE;
	UnitCloseup *closeup;
	List	*list;
	
	/* Menu key commands. */
	if (mods & cmdKey
	/* Don't interpret cmd + keypad as menu command. */
	    && keyCode < 0x40) {
		adjust_menus();
		do_menu_command(MenuKey(charCode));
		return;
	}
	/* Alt-w and Escape closes topFloat. */
	if (charCode == '½' || charCode == 27) {
		close_window(topFloat);
		return;
	} 
	/* Key commands for the player setup dialog. */ 
	if (FrontWindow() == playersetupwin) {
		do_key_down_setup(event);
		return;
	}		
	/* Key commands for floating windows. */	
	if (topFloat) {
		closeup = unit_closeup_from_window(topFloat);
		if (closeup) {
			handled = do_key_down_closeup(closeup, keyCode, charCode, mods);
		} else if (topFloat == helpwin) {
			handled = do_key_down_help(charCode);		
		} else if (topFloat == instructionswin) {
			handled = do_key_down_instructions(charCode);		
		} else	if (topFloat == constructionwin) {
			handled = do_key_down_construction(charCode);
		} else if (topFloat == researchwin) {
			handled = do_key_down_research(charCode);		
		} else if (topFloat == commandwin) {
			handled = do_key_down_command(keyCode, charCode);
		} else if (topFloat == buildwin) {
			handled = do_key_down_build(keyCode, charCode, mods);
		}
	}
	/* Key commands for normal windows. */
	if (!handled && topWindow) {
		list = list_from_window(topWindow);
		if (list) {
			handled = do_key_down_list(list, charCode);
		}
	}
	/* Special use of the numeric keypad and arrow keys for 
	scrolling of maps. */
	if (!handled && (frontmap || worldmap)) {
		handled = handle_numeric_keypad(event);
	}
	/* All other keys are interpreted as keyboard commands. */
	if (!handled) {
		do_keyboard_command(charCode);
	}
	return;
}

void
get_global_mouse(Point *mouse)
{
	EventRecord	evt;
	
	OSEventAvail(0, &evt);
	*mouse = evt.where;
}

void
set_end_of_game_interaction_modes()
{
	Map *map;

	for_all_maps(map) {
		map->moveonclick = map->autoselect = FALSE;
		force_update(map->window);
	}
}

Point lastmouse;

char mouseoverbuf[BUFSIZE];

/* Change the cursor to reflect the context. */

CursHandle current_cursor;

/* Delay before autoscrolling starts. */

static int delay;

void
adjust_cursor(Point mouse, RgnHandle region)
{
	int x, y, approxdir = 1, usual = TRUE;
	Unit *unit = NULL;
	extern char *mouseover;
	Map *map;
	WindowPtr win;
	GrafPtr oldport;

	/* Work around of Appearance manager bug. Since clicks in the collapse box fail
	to generate an application level event, and since the system fails to update dialogs
	that are uncollapsed, it is necessary to force dialog updates in some other way. */
	for_all_windows(win) {
		if (IsDialog(win) 
		    && PtInRgn(mouse, ((WindowPeek) win)->strucRgn) == TRUE
		    && PtInRgn(mouse, ((WindowPeek) win)->contRgn) == FALSE) {
			update_window(win);
			return;
		}
	}
	/* Check if we are inside the worldmap window. */
	if (worldmap && PtInRgn(mouse, ((WindowPeek) worldmap->window)->contRgn))
		map = worldmap;
	else	map = frontmap;

	if (map != NULL) {
		GetPort(&oldport);
		SetPort(map->window);
		GlobalToLocal(&mouse);

		/* Don't do cursor adjustment for frontmap if we are in a floating window! */ 
		if (PtInRgn(mouse, map->window->visRgn) != TRUE) {
	  		SetCursor(&QD(arrow));
			return;
		}

		if (mouse.h > map->conw 
		    && mouse.v > map->toph 
		    && mouse.h < map->window->portRect.right - map->sbarwid
		    && mouse.v < map->window->portRect.bottom - map->sbarwid) {

			if (mouse.h < map->conw + 40
			    && map->vp->sx > map->vp->sxmin) {
				current_cursor = leftcursor;				
				usual = FALSE;
				/* Wait half second before autoscrolling. */
				if (TickCount() > delay + 30)
					scroll_map_window(map, -4, 0);
			} else if (mouse.h > map->window->portRect.right - map->sbarwid - 40
			    && map->vp->sx < map->vp->sxmax) {
				current_cursor = rightcursor;
				usual = FALSE;
				/* Wait half second before autoscrolling. */
				if (TickCount() > delay + 30)
					scroll_map_window(map, 4, 0);
			} else if (mouse.v < map->toph + 40
			    && map->vp->sy > map->vp->symin) {
				current_cursor = upcursor;
				usual = FALSE;
				/* Wait half second before autoscrolling. */
				if (TickCount() > delay + 30)
					scroll_map_window(map, 0, -4);
			} else if (mouse.v > map->window->portRect.bottom - map->sbarwid - 40
			    && map->vp->sy < map->vp->symax) {
				current_cursor = downcursor;
				usual = FALSE;
				/* Wait half second before autoscrolling. */
				if (TickCount() > delay + 30)
					scroll_map_window(map, 0, 4);
			} else	 {
				/* Reset autoscrolling delay. */
				delay = TickCount();		

				if (map_modal != NO_MODAL) {
					switch (map_modal) {
						case ATTACK_MODAL:
						case FIRE_MODAL:
						case FIRE_INTO_MODAL:
							current_cursor = firecursor;
							break;
						case MOVE_TO_MODAL:
						case SET_FORMATION_MODAL:
						case ADD_TERRAIN_MODAL:
						case REMOVE_TERRAIN_MODAL:
						case DISTANCE_MODAL:
						case ZOOM_MODAL:
						case GENERIC_MODAL:
							current_cursor = opencrosscursor;
							break;
						default:
							run_error("unknown modal tool %d", map_modal);
							break;
					}
					usual = FALSE;
	#ifdef DESIGNERS
				} else if (dside->designer && tooltype != notool) {
					current_cursor = adjust_designer_cursor(mouse, region);  usual = FALSE;
	#endif DESIGNERS
				} else if (map->moveonclick) {
					if (map->numselections == 1
					    && (unit = map->selections[0]) != NULL) {
						/* Calculate the approx dir to here from selected unit. */
						m_nearest_cell(map, mouse.h, mouse.v, &x, &y);
						/* Note that we allow x,y here to be outside the world. */
						if (mobile(unit->type)
						    && (approxdir = approx_dir(x - unit->x, y - unit->y)) >= 0) {
							current_cursor = movecursors[approxdir];  usual = FALSE;
						} else {
							current_cursor = nomovecursor;  usual = FALSE;
						}
					} else if (map->numselections > 1) {
						current_cursor = allmovecursor;  usual = FALSE;
					} else {
						/* (this is a little confusing here if no units are selected, since
					   	will just be arrow cursor) */
					}
				}
			}
			/* This isn't really "cursor adjustment", but this is the right place
			   to do it - change the topline of the map to indicate what the cursor
			   is over. */
			/* Scrolling, zooming or changing angle alters mouseover even though
			   mouse == lastmouse. */
			/* (should flag the change of state and or that with the !EqualPt,
			   for efficiency) */
			if (map->toplineh > 0 /* && !EqualPt(mouse, lastmouse) */ ) {
				oneliner(dside, map->vp, mouse.h - map->conw, mouse.v - map->toph);
				if (strcmp(tmpbuf, mouseoverbuf) != 0) {
					strcpy(mouseoverbuf, tmpbuf);
					mouseover = mouseoverbuf;
					draw_top_line(map);
				}
				lastmouse = mouse;
			}
		} else {
			/* Reset autoscrolling delay. */
			delay = TickCount();
			if (map->toplineh > 0) {
				if (mouseover != NULL) {
					mouseover = NULL;
					draw_top_line(map);
				}
			}
		}
		SetPort(oldport);
	}
	
	if (endofgame || (!beforestart && dside && !dside->ingame)) {
		current_cursor = grayarrowcursor;  usual = FALSE;
	}
	/* If we got here and no cursor has been set already, go with the basic arrow. */
	if (usual)
	  current_cursor = nil;
	if (current_cursor != nil)
	  SetCursor(*current_cursor);
	else
	  SetCursor(&QD(arrow));
}

/* Decipher an event. */

void
handle_event(EventRecord *event)
{
	short part, err;
	WindowPtr win, win2;
	Point pnt;

	switch (event->what) {
		case mouseDown:
			/* See if the click happened in a special part of the screen. */
			part = MacFindWindow(event->where, &win);
			switch (part) {
				case inMenuBar:
					adjust_menus();
					do_menu_command(MenuSelect(event->where));
					break;
				case inSysWindow:
					SystemClick(event, win);
					break;
				case inContent:
					if (win != topWindow && win != topFloat) {
						/* Bring the clicked-on window to the front. */
						SelectTheWindow(win);
						update_window(win);
						/* Fix the menu to match the new front window. */
						adjust_menus();
						/* Permit direct click action in non-top floating windows.
						This is not exactly in line with Apple's interface guidelines
						but it does make things easier when more than one float is up
						since you don't have to worry about which one is in front. */
						if (IsFloating(win) || IsDialog(win)) {
						    do_mouse_down(win, event);
						}
						/* We always want to discard the event now, since clicks in a
						   windows are often irreversible actions. */
					} else {
						/* Mouse clicks in the front window do something useful. */
						do_mouse_down(win, event);
					}
					break;
				case inDrag:
					drag_window(win, event);
					break;
				case inGrow:
					grow_window(win, event->where);
					break;
				case inZoomIn:
				case inZoomOut:
					zoom_window(win, event->where, part);
					break;
				case inGoAway:
					if (TrackGoAway(win, event->where))
					  close_window(win);
					break;
				case inCollapseBox:
					/* Uncollapsed dialogs don't get redrawn by the system. */ 
					if (IsDialog(win)) {
					    EraseRect(&win->portRect);
					    force_update(win);
					}
					break;					
			}
			break;
		case keyDown:
		case autoKey:
			handle_keystroke(event);
			break;
		case activateEvt:
			activate_window((WindowPtr) event->message, event->modifiers & activeFlag);
			break;
		case updateEvt:
			update_window((WindowPtr) event->message);
			break;
		case diskEvt:
			/*	Call DIBadMount in response to a diskEvt, so that the user can format
				 a floppy. (from DTS Sample) */
			if (HiWord(event->message) != noErr) {
				SetPt(&pnt, 50, 50);
				err = DIBadMount(pnt, event->message);
			}
			break;
		case osEvt:
		    /* Grab only a single byte. */
			switch ((event->message >> 24) & 0xFF) {
				/* Mouse moved evt - do nothing. */
				case 0xfa:
					break;
				/* Suspend/resume evt. */
				case 1:
					/* Check for suspend (0) or resume (1). */
					inbackground = !(event->message & 1);
					/* Activate or deactivate the top window. */
					ActivateWindow(topWindow, !inbackground);
					/* The floats should also be activated/deactivated! */
					for_all_windows(win) {
						if (IsFloating(win) || IsDialog(win)) {
							ActivateWindow(win, !inbackground);
						}							
					}
					break;
			}
			break;
		case kHighLevelEvent:
			AEProcessAppleEvent(event);
			break;
#if 0 /* null events don't come through here */
		case nullEvent:
			break;
#endif
		default:
			break;
	}
#ifdef DEBUGGING
	/* This just forces output into the file. */
	update_debugging();
#endif
}

void
drag_window(WindowPtr win, EventRecord *event)
{
	DragTheWindow(win, event);
	/* Necessary to redraw some modeless dialogs. */
	update_all_visible_dialogs();
}

/* Handle window growing by mindlessly tracking via GrowWindow,
   then passing the chosen size to specific window resize handlers
   or else doing the generic resize. */

void
grow_window(WindowPtr win, Point where)
{
	long winsize;
	short winh, winv;
	GrafPtr oldport;

	if ((winsize = GrowWindow(win, where, &sizerect)) != 0) {
		GetPort(&oldport);
		SetPort(win);
		winh = LoWord(winsize);  winv = HiWord(winsize);
		if (map_from_window(win)) {
			grow_map(map_from_window(win), winh, winv);
		} else if (list_from_window(win)) {
			grow_list(list_from_window(win), winh, winv);
		} else if (win == historywin) {
			grow_history(winh, winv);
		} else if (win == constructionwin) {
			grow_construction(winh, winv);
		} else if (win == helpwin) {
			grow_help(winh, winv);
		} else if (win == noticewin) {
			grow_notice(winh, winv);
		} else if (win == scoreswin) {
			grow_scores(winh, winv);
		}
		SetPort(oldport);
		/* Necessary to redraw some modeless dialogs. */
		update_all_visible_dialogs();
	}
}

void
zoom_window(WindowPtr win, Point where, int part)
{
	GrafPtr oldport;

	if (TrackBox(win, where, part)) {
		GetPort(&oldport);
		/* The window must be the current port. (ZoomWindow bug) */
		SetPort(win);
		if (map_from_window(win)) {
			zoom_map(map_from_window(win), part);
		} else if (list_from_window(win)) {
			zoom_list(list_from_window(win), part);
		} else if (win == constructionwin) {
			zoom_construction(part);
		} else if (win == historywin) {
			zoom_history(part);
		} else if (win == helpwin) {
			zoom_help(part);
		} else if (win == noticewin) {
			zoom_notice(part);
		} else if (win == scoreswin) {
			zoom_scores(part);
		} else {
			/* Generic window zooming. */
			EraseRect(&win->portRect);
			ZoomWindow(win, part, false);
			InvalRect(&win->portRect);
		}
		SetPort(oldport);
		/* Necessary to redraw some modeless dialogs. */
		update_all_visible_dialogs();
	}
}

void
close_window(WindowPtr win)
{
	if (is_da_window(win)) {
		CloseDeskAcc(((WindowPeek) win)->windowKind);
	} else if (is_app_window(win)) {
		/* Remove from the windows menu (OK to call even if window not in menu). */
		remove_window_menu_item(win);
		/* Do special activities for some window subtypes. */
		if (map_from_window(win)) {
			destroy_map(map_from_window(win));
		} else if (list_from_window(win)) {
			destroy_list(list_from_window(win));
		} else if (unit_closeup_from_window(win)) {
			destroy_unit_closeup(unit_closeup_from_window(win));
		}
		/* Remove the window from our sight, will provoke update events. */
		if (win)
		HideTheWindow(win);
		/* At least for now, don't actually dispose of the window. */
	}
	/* Necessary to redraw some modeless dialogs and to ensure that autoscrolling
	does not create a white rectangle where a floating window used to be. */
	update_all_visible_windows();
}

/* This just dispatches to the appropriate window handler. */

void
do_mouse_down(WindowPtr window, EventRecord *event)
{
	Point mouse;
	Map *map;
	List *list;
	UnitCloseup *unitcloseup;

	if (is_app_window(window)) {
		SetPort(window);
		mouse = event->where;
		GlobalToLocal(&mouse);
		/* Locate the interface object that this is on. */
		if ((map = map_from_window(window)) != NULL) {
			do_mouse_down_map(map, mouse, event->modifiers); 
		} else if ((list = list_from_window(window)) != NULL) {
			do_mouse_down_list(list, mouse, event->modifiers); 
		} else if ((unitcloseup = unit_closeup_from_window(window)) != NULL) {
			do_mouse_down_unit_closeup(unitcloseup, mouse, event->modifiers); 
		} else if (window == buildwin) {
			do_mouse_down_build(mouse, event->modifiers); 
		} else if (window == playersetupwin) {
			do_mouse_down_setup(mouse, event->modifiers);
		} else if (window == gamewin) {
			do_mouse_down_game(mouse, event->modifiers);
		} else if (window == historywin) {
			do_mouse_down_history(mouse, event->modifiers);
		} else if (window == constructionwin) {
			do_mouse_down_construction(mouse, event->modifiers);
		} else if (window == researchwin) {
			do_mouse_down_research(mouse, event->modifiers);
		} else if (window == helpwin) {
			do_mouse_down_help(mouse, event->modifiers);
		} else if (window == noticewin) {
			do_mouse_down_notice(mouse, event->modifiers);
		} else if (window == commandwin) {
			do_mouse_down_command(mouse, event->modifiers);
		} else if (window == scoreswin) {
			do_mouse_down_scores(mouse, event->modifiers);
		} else if (window == instructionswin) {
			do_mouse_down_instructions(mouse, event->modifiers);
#ifdef DESIGNERS
		} else if (window == designwin) {
			do_mouse_down_design(mouse, event->modifiers);
#endif /* DESIGNERS */
		}
	} else {
		/* ??? */
	}
}

/* Bringing a window to the front may entail messing with the menu. */

void
activate_window(WindowPtr win, int activate)
{
	Map *map;
	List *list;

	if (win == nil)
	  return;
	if (activate) {
		/* It's convenient to make the activated window also be the current GrafPort. */
		SetPort(win);
	}
	adjust_menus();
	if ((map = map_from_window(win)) != NULL) {
		activate_map(map, activate);
	} else if ((list = list_from_window(win)) != NULL) {
		activate_list(list, activate);
	} else if (win == constructionwin) {
		activate_construction(activate);
	} else if (win == helpwin) {
		activate_help(activate);
	} else if (win == instructionswin) {
		activate_instructions(activate);
	} else if (win == noticewin) {
		activate_notice(activate);
	} else if (win == commandwin) {
		activate_command(activate);
	} else if (win == scoreswin) {
		activate_scores(activate);
	} else {
		DGprintf("%sactivating random window\n", (activate ? "" : "de"));
	}
}

/* Update a given window.  This is the main routine that causes drawing into
   all the different kinds of windows. */

void
update_window(WindowPtr win)
{
	int controls = TRUE, growbox = FALSE;
	GrafPtr oldport;
	Map *map;
	List *list;
	UnitCloseup *unitcloseup;

	/* Set the updating window to be the current grafport. */
	GetPort(&oldport);
	SetPort(win);
	recalc_depths();
	BeginUpdate(win);
	if ((map = map_from_window(win)) != NULL) {
		/* Draw the map decor. */
		if (map->conw > 0)
		  draw_control_panel(map);
		if (map->toplineh > 0)
		  draw_top_line(map);
		if (map->topunith > 0)
		  draw_unit_info(map);
		/* Then copy map content from the gworld. */
		copy_from_gworld(map, map->contentrect);	   
		/* Don't draw scrollbars if they are zero width. */
		if (map->sbarwid == 0)
	             controls = FALSE;
		growbox = TRUE;
	} else if ((list = list_from_window(win)) != NULL) {
		draw_list(list);
		growbox = TRUE;
	} else if ((unitcloseup = unit_closeup_from_window(win)) != NULL) {
		draw_unit_closeup(unitcloseup);
	} else if (win == buildwin) {
		draw_unit_build_dialog(true);
		controls = FALSE;
	} else if (win == gamewin) {
		draw_game();
		controls = FALSE;
	} else if (win == historywin) {
		draw_history();
		growbox = TRUE;
	} else if (win == constructionwin) {
		draw_construction();
		growbox = TRUE;
	} else if (win == helpwin) {
		draw_help();
		growbox = TRUE;
	} else if (win == instructionswin) {
		draw_instructions();
	} else if (win == noticewin) {
		draw_notice();
		growbox = TRUE;
	} else if (win == commandwin) {
		draw_command();
	} else if (win == scoreswin) {
		draw_scores();
		growbox = TRUE;
	} else if (win == researchwin) {
		/* Update events don't always work with dialogs, but setting the force 
		flag forces redrawing. */
		draw_research_dialog(true);
		controls = FALSE;
#ifdef DESIGNERS
	} else if (win == designwin) {
		draw_design_window();
		controls = FALSE;
#endif /* DESIGNERS */
	} else {
		controls = FALSE;
	}
	if (controls) {
		UpdateControls(win, win->visRgn);
	}
	if (growbox) {
		/* We want scrollbar lines only when there are scrollbars! */
		if(controls)
			DrawGrowIcon(win);
		else 	draw_growicon_nolines(win);
	}
	EndUpdate(win);
	SetPort(oldport);
}

/* Fixes a bug in the Toolbox which causes DrawGrowIcon to draw scrollbar
lines even if there are no scrollbars in the window. */

void 
draw_growicon_nolines(WindowPtr win)
{
	Rect 		iconrect;
	RgnHandle 	newclip;
	RgnHandle 	oldclip;
	
	newclip = NewRgn();
	oldclip = NewRgn();
	GetClip(oldclip);

	/* Set new clip rgn to GrowIcon. */
	iconrect = win->portRect;
	iconrect.left = iconrect.right-15; 
	iconrect.top = iconrect.bottom-15;
	SetRectRgn(newclip, iconrect.left, iconrect.top, 
				       iconrect.right, iconrect.bottom);

	/* Intersect it with old clip rgn. */
	SectRgn(oldclip, newclip, newclip);

	SetClip(newclip);
	DrawGrowIcon(win);
	SetClip(oldclip);
	DisposeRgn(newclip);
	DisposeRgn(oldclip);

}

void
maybe_select_next_unit()
{
	int	ticks = TickCount();
	Map	*map = frontmap;
	Unit	*unit;
	
	if (beforestart 
	    || endofgame
	    || map == NULL
	    || map->autoselect != TRUE) {
		return;
	}
	
	/* First check if we have a valid curunit. */
	unit = map->curunit;
	if (could_be_next_unit(unit)) {
		/* If so, make sure it is selected. */
		if (!unit_is_selected(map, unit)) {
	    		select_unit_on_map(map, unit);
		}		 	
		goto blink;
	}
	/* Else look nearby for a possible new curunit. */	
	unit = autonext_unit_inbox(dside, map->curunit, map->vp);
	if (select_new_unit(map, unit)) {
		goto blink;
	}
	/* Look for the next possible curunit in the list. */
	unit = find_next_awake_mover(dside, map->curunit);
	if (select_new_unit(map, unit)) {
		goto blink;
	}
	/* Start over from beginning of list. */
	unit = find_next_awake_mover(dside, NULL);
	if (select_new_unit(map, unit)) {
		goto blink;
	}
	/* We failed. Unselect any old non-valid curunit ... */
	if (map->curunit) {
		unselect_unit_on_map(map, map->curunit);
		update_cell(map, map->curunit->x, map->curunit->y);
		map->curunit = NULL;
		/* Make sure buildwin is closed. */
		close_window(buildwin);
	}
	/* ... and get out of here. */
	return;

	blink:
	/* Scroll to curunit if asked to. */
    	if (map->scrolltocurunit) {
			scroll_to_unit(map, map->curunit);
			map->scrolltocurunit = FALSE;
		}
	/* Use blinking current unit if asked to. */
	if (map->blinking_curunit == TRUE
	      && ticks - last_tick_count >= 20) {
		int curcp = map->curunit->cp;

		last_tick_count = ticks;
		/* Make sure we have a nonzero state that can switch sign. */
    		if (animation_pattern_state == 0)
		    animation_pattern_state = -1;
		/* Blink. */
		animation_pattern_state = -animation_pattern_state;	    	    
		/* Set cp to bogus zero in order to force drawing as a gray
		incomplete unit. */
		if (animation_pattern_state < 0)
		    map->curunit->cp = 0;
		update_cell(map, map->curunit->x, map->curunit->y);
		/* Restore correct cp. */
		map->curunit->cp = curcp;
		/* Else use animated selection box. */
	} else if (map->blinking_curunit != TRUE
      	      && ticks - last_tick_count >= 10) {
		last_tick_count = ticks;
		animation_pattern_state = (animation_pattern_state + 1) % 8;
		draw_selection_animation(map, map->curunit);
	}
}

/* Try to select the next unit. Return true if successful. */

int
select_new_unit(Map *map, Unit *unit)
{
	int 		sx, sy;
	Point		point;
	
	if (could_be_next_unit(unit)) {
		map->curunit = unit;
		select_exactly_one_unit(map, unit);
		xform(map, unit->x, unit->y, &sx, &sy);
		point.h = sx + map->vp->hw/2; 
		point.v = sy + map->vp->hh/2; ;
		/* Scroll to the new curunit if it is hidden from view. */
	    	if (PtInRgn(point, map->window->visRgn) != TRUE)
		    scroll_to_unit(map, map->curunit);
		/* If something can build but cannot move, we assume
		it is waiting for a build task (should be more refined
		test). */
		if (can_build(unit) && !mobile(unit->type))
		    show_unit_build_dialog(unit);
		return TRUE;
	}
	return FALSE;	
}

/* Used to check for any unread required parameters. Returns true if we
   missed at least one. */

Boolean
missed_any_parameters(AppleEvent *message)
{
	OSErr err;
	DescType ignoredActualType;
	AEKeyword missedKeyword;
	Size ignoredActualSize;
	EventRecord	event;

	err = AEGetAttributePtr(message, keyMissedKeywordAttr, typeKeyword, &ignoredActualType,
						    (Ptr) &missedKeyword, sizeof(missedKeyword), &ignoredActualSize);
	/* No error means that we found some unused parameters. */
	if (err == noErr) {
		event.message = *(long *) &ignoredActualType;
		event.where = *(Point *) &missedKeyword;
		err = errAEEventNotHandled;
	}
	/* errAEDescNotFound means that there are no more parameters.  If we get
	   an error code other than that, flag it. */
	return (err != errAEDescNotFound);
}

static pascal OSErr
do_ae_open_application(AppleEvent *message, AppleEvent *reply, long refcon)
{
#pragma unused (message, refcon)
	OSErr err;

	if (splash_dialog() == diSplashQuit) {
		/* Set the global that lets the whole program exit. */
		eventloopdone = TRUE;
	}
	AEPutParamPtr(reply, keyReplyErr, typeShortInteger, (Ptr) &err, sizeof(short));
	return err;
}

/* Called when we receive an AppleEvent with an ID of "kAEOpenDocuments".
   This routine gets the direct parameter, parses it up into little FSSpecs,
   and opens the first indicated file.  It also shows the technique to be used in
   determining if you are doing everything the AppleEvent record is telling
   you.  Parameters can be divided up into two groups: required and optional.
   Before executing an event, you must make sure that you've read all the
   required events.  This is done by making an "any more?" call to the
   AppleEvent manager. */

static pascal OSErr
do_ae_open_documents(AppleEvent *message, AppleEvent *reply, long refcon)
{
#pragma unused (refcon)

	OSErr err, err2;
	AEDesc theDesc;
	FSSpec fsspec;
	short loop;
	long numFilesToOpen;
	AEKeyword ignoredKeyWord;
	DescType ignoredType;
	Size ignoredSize;

	theDesc.dataHandle = nil;

	err = AEGetParamDesc(message, keyDirectObject, typeAEList, &theDesc);
	if (err)
	  return err;
	if (!missed_any_parameters(message)) {
		/* Got all the parameters we need.  Now, go through the direct object,
		   see what type it is, and parse it up. */
		err = AECountItems(&theDesc, &numFilesToOpen);
		if (!err) {
			/* We have numFilesToOpen that need opening, as either a window
			   or to be printed.  Go to it... */
			for (loop = 1; ((loop <= numFilesToOpen) && (!err)); ++loop) {
				err = AEGetNthPtr(&theDesc, loop, typeFSS, &ignoredKeyWord, &ignoredType,
								  (Ptr) &fsspec, sizeof(fsspec), &ignoredSize);
				if (err)
				  break;
				if (open_game_from_fsspec(&fsspec))
				  break;
			}
		}
	}
	err2 = AEDisposeDesc(&theDesc);
	err = (err ? err : err2);
	AEPutParamPtr(reply, keyReplyErr, typeShortInteger, (Ptr) &err, sizeof(short));
	return err;
}

static pascal OSErr
do_ae_print_documents(AppleEvent *message, AppleEvent *reply, long refcon)
{
	OSErr err;

	AEPutParamPtr(reply, keyReplyErr, typeShortInteger, (Ptr) &err, sizeof(short));
	return err;
}

static pascal OSErr
do_ae_quit_application(AppleEvent *message, AppleEvent *reply, long refcon)
{
	OSErr err = noErr;

	/* Set the global that lets the whole program exit. */
	/* (should end the game reasonably first?) */
	eventloopdone = TRUE;
	AEPutParamPtr(reply, keyReplyErr, typeShortInteger, (Ptr) &err, sizeof(short));
	return noErr;
}

static pascal OSErr
do_ae_join_game(AppleEvent *message, AppleEvent *reply, long refcon)
{
#pragma unused (message, refcon)
	OSErr err = noErr;

	if (playersetupwin != nil) {
		add_remote_player(NULL);
	} else {
		beep();
		beep();
		beep();
	}
	return err;
}

void
connect_game_dialog()
{
	int rslt;

	hosting = FALSE;
	connection_method_dialog();
	open_remote_connection(connection_method_name, hosting);
	if (connection_method > 0) {
		rslt = send_join("mac");
		if (!rslt) {
			close_remote_connection(0);
			/* Use of connection_method this way is weak; means
			   having to reselect everything up reconnection. */
			connection_method = 0;
		}
	}
}

void
connection_method_dialog()
{
	int done = FALSE, newmethod;
	char *newname;
	short ditem;
	WindowPtr win;
	PicHandle pic;
	short itemtype;  Handle itemhandle;  Rect itemrect;

	newmethod = 1;
	newname = "serial";
	win = GetNewDialog(dConnectionMethod, NULL, (DialogPtr) -1L);
	ShowWindow(win);
	SelectWindow(win);
	while (!done) {
		GetDItem(win, diConnectionMethodSerial, &itemtype, &itemhandle, &itemrect);
		SetCtlValue((ControlHandle) itemhandle, (newmethod == 1));
		GetDItem(win, diConnectionMethodAppleTalk, &itemtype, &itemhandle, &itemrect);
		SetCtlValue((ControlHandle) itemhandle, (newmethod == 2));
		GetDItem(win, diConnectionMethodTCPIP, &itemtype, &itemhandle, &itemrect);
		SetCtlValue((ControlHandle) itemhandle, (newmethod == 3));
		GetDItem(win, diConnectionMethodFile, &itemtype, &itemhandle, &itemrect);
		SetCtlValue((ControlHandle) itemhandle, (newmethod == 4));
		ModalDialog(NULL, &ditem);
		switch (ditem) {
			case diConnectionMethodOK:
				connection_method = newmethod;
				connection_method_name = newname;
			case diConnectionMethodCancel:
				done = TRUE;
				break;
			case diConnectionMethodSerial:
				newmethod = 1;
				newname = "serial";
				break;
			case diConnectionMethodAppleTalk:
				newmethod = 2;
				newname = "appletalk";
				break;
			case diConnectionMethodTCPIP:
				newmethod = 3;
				newname = "tcp";
				break;
			case diConnectionMethodFile:
				newmethod = 4;
				newname = "file";
				break;
			default:
				break;
		}
	}
	DisposeDialog(win);
	update_all_map_windows();
}

int
serial_port_dialog()
{
	int done = FALSE, newport, rslt = -1;
	short ditem;
	WindowPtr win;
	PicHandle pic;
	short itemtype;  Handle itemhandle;  Rect itemrect;

	newport = 0;
	win = GetNewDialog(dSerialMethod, NULL, (DialogPtr) -1L);
	ShowWindow(win);
	SelectWindow(win);
	while (!done) {
		GetDItem(win, diSerialMethodModem, &itemtype, &itemhandle, &itemrect);
		SetCtlValue((ControlHandle) itemhandle, (newport == 0));
		GetDItem(win, diSerialMethodPrinter, &itemtype, &itemhandle, &itemrect);
		SetCtlValue((ControlHandle) itemhandle, (newport == 1));
		ModalDialog(NULL, &ditem);
		switch (ditem) {
			case diSerialMethodOK:
				rslt = newport;
			case diSerialMethodCancel:
				done = TRUE;
				break;
			case diSerialMethodModem:
				newport = 0;
				break;
			case diSerialMethodPrinter:
				newport = 1;
				break;
			default:
				break;
		}
	}
	DisposeDialog(win);
	update_all_map_windows();
	return rslt;
}

static pascal Boolean
filter_warning_alert(DialogPtr dialog, EventRecord *evt, short *itemhit)
{
	char ch;

	/* Look for the right kind of event. */
	switch (evt->what) {
		case keyDown:
			ch = evt->message & charCodeMask;
			if (ch == 3 || ch == 13) {
				*itemhit = 1;
				return TRUE;
			}
			break;
	}
	if (evt->modifiers & optionKey)
	  warnings_suppressed = TRUE;
	return FALSE;
}

/* A warning just gets displayed, no other action is taken. */

void
low_init_warning(char *str)
{
	Str255 buf;

	Dprintf("INIT WARNING %s INIT WARNING\n", str);
	/* Cursor may be weird from loading, reset it. */
	SetCursor(&QD(arrow));
	c2p(str, buf);
	ParamText(buf, "\p", "\p", "\p");
	switch (CautionAlert(aInitWarning, filter_warning_alert_proc)) {
		case 1:
			/* Just keep going, player considers warning a false alarm. */
			break;
		case 2:
			/* It would be better to undo everything and blast back to initial choices,
			   but that would be pretty hard to implement, and should be a rare occurrence
			   anyway. */
			exit_macconq();
			break;
	}
}

/* An init error is not necessarily fatal, but we still have to start over. */

void
low_init_error(char *str)
{
	Str255 buf;

	Dprintf("INIT ERROR %s INIT ERROR\n", str);
	/* Cursor may be weird from loading, reset it. */
	SetCursor(&QD(arrow));
	c2p(str, buf);
	ParamText(buf, "\p", "\p", "\p");
	StopAlert(aInitError, nil);
	/* This is a bad time to choke, no way to recover.  Fortunately,
	   it's not a big loss, since there's no game yet to lose,
	   and so we can just exit directly. */
	exit_macconq();
}

/* Runtime warnings are for when it's important to bug the players,
   but doesn't necessarily mean imminent danger of a crash. */

void
low_run_warning(char *str)
{
	Str255 buf;

	/* If we're not actually in the game yet, make an init warning instead. */
	if (beforestart) {
		low_init_warning(str);
		return;
	}
	Dprintf("RUN WARNING %s RUN WARNING\n", str);
	c2p(str, buf);
	ParamText(buf, "\p", "\p", "\p");
	switch (CautionAlert(aRunWarning, filter_warning_alert_proc)) {
		case 1:
			/* Just keep going, player considers warning a false alarm. */
			break;
		case 2:
			save_the_game(TRUE, TRUE);
			exit_macconq();
			break;
		case 3:
			/* Just blast out of here. */
			exit_macconq();
			break;
	}
}

/* An run error is fatal, but allow an emergency save, might be able to salvage. */

void
low_run_error(char *str)
{
	Str255 buf;

	/* If we're not actually in the game yet, make an init error instead. */
	if (beforestart) {
		low_init_error(str);
		return;
	}
	/* Make some space available, in case this is a memory exhaustion error. */
	if (spare != nil) {
		DisposHandle(spare);
		spare = nil;
	}
	Dprintf("RUN ERROR %s RUN ERROR\n", str);
	c2p(str, buf);
	ParamText(buf, "\p", "\p", "\p");
	switch (StopAlert(aRunError, nil)) {
		case 1:
			break;
		case 2:
			save_the_game(TRUE, TRUE);
			break;
	}
	/* We're outta here - just ahead of scrambled heaps and dangling ptrs! */
	exit_macconq();
}

static FILE *pffp;

static int first_print = TRUE;

void
print_form(form)
Obj *form;
{
	if (pffp == NULL) {
		pffp = fopen("Xconq.PrintOut", (first_print ? "w" : "a"));
		first_print = FALSE;
	}
	print_form_and_value(pffp, form);
	fflush(pffp);
}

void
end_printing_forms()
{
	if (pffp != NULL) {
		fclose(pffp);
		pffp = NULL;
	}
}

/* This is true when a side has a display that may be safely written to. */

int
active_display(Side *side)
{
	return (side && side->ui && side->ui->active);
}

/* The Mac never has any display buffers to flush. */

void
flush_display_buffers(Side *side)
{
}

/* Detect types of windows. */

int
is_da_window(WindowPtr win)
{
	return (win != nil && ((WindowPeek) win)->windowKind < 0);
}

int
is_app_window(WindowPtr win)
{
	return (win != nil && ((WindowPeek) win)->windowKind >= 0);
}

void
low_notify(Side *side, char *str)
{
	if (!active_display(side))
	  return;
	append_notice(str);
}

/* Kernel callback to update info about the given side. */

void
update_area_display(Side *side)
{
}

void
update_side_display(Side *side, Side *side2, int rightnow)
{
	GrafPtr oldport;
	int newnumsides;
	extern int gamenumsides;

	if (active_display(side)) {
		if (gamewin != nil
		    &&  !inactive_indepside(side2) 
		    && ((WindowPeek) gamewin)->visible) {
			if (side_has_ai(indepside) || side_has_display(indepside)) {
				newnumsides = numtotsides;
			} else {
				newnumsides = numsides;
			}
			if (gamenumsides == newnumsides) {
				GetPort(&oldport);
				SetPort(gamewin);
				draw_side_status(side2);
				SetPort(oldport);		
			} else {
				/* A side must have been added recently; redraw
				   the entire window this time. */
				if (eimages[side_number(side2)] == NULL)
				  init_emblem_images();
				draw_game();
			}
		}
		/* Handle the research window if necessary. */
		if (numatypes > 0 
		    && g_side_can_research()) {
			/* Research is under human control and we need a new topic. */
			if (!side_has_ai(dside) 
			    && !dside->autoresearch
			    && dside->research_topic == NOADVANCE) {
				show_research_dialog();
			/* else update the window, but only if it exists. */
			} else if (researchwin) {
				/* We call draw_research_dialog here passing false instead of 
				true, which would force redrawing. Now the dialog tests if the text
				has changed before deciding on a redraw. Note: update_window does
				not work with dialogs. */
				draw_research_dialog(false);
			}
		}
		if (side2 == dside && !side->ingame && side->status == 0 && wasingame) {
			/* (should be able to quit from here?) */
			CautionAlert(aOutOfGame, nil); 
			wasingame = FALSE;
		}
	}
}

/* Kernel callback to show the current turn. */

void
update_turn_display(Side *side, int rightnow)
{
	GrafPtr oldport;
	Map *map;

	if (active_display(side)) {
		GetPort(&oldport);
		if (gamewin != nil && ((WindowPeek) gamewin)->visible) {
			SetPort(gamewin);
			draw_game_date();
		}
		for_all_maps(map) {
			if (map->toplineh > 0) {
				SetPort(map->window);
				draw_top_line(map);
			}
			if (map->topunith > 0) {
				SetPort(map->window);
				draw_unit_info(map);
			}
		}
		/* This routine might have been called because the game is over. */
		if (endofgame && !told_outcome && side == dside) {
		    draw_game();
		    if (!side->see_all) {
		    	/* Can't do update forcing here because modal dialogs will
		    	   come up before windows actually get to update. */
		    	for_all_maps(map) {
					map->see_all = TRUE;
					map->vp->show_all = TRUE;
					draw_map(map);
				}
		    }
		    if (side_won(dside)) {
		    	won_game_dialog();
		    } else if (side_lost(dside)) {
		    	lost_game_dialog();
		    } else {
			game_over_dialog();
			}
			told_outcome = TRUE;
		}
		SetPort(oldport);
	}
}

/* Callback that gets run once after all turn setup is done but before any movement. */

void
update_action_display(Side *side, int rightnow)
{
	GrafPtr oldport;
	Map *map;
	List *list;
	UnitCloseup *unitcloseup;

	if (active_display(side)) {
		GetPort(&oldport);
		for_all_maps(map) {
			draw_selections(map);
			if (map->autoselect) {
				unselect_all(map);
				map->curunit = NULL;
			}
		}
		for_all_lists(list) {
			reorganize_list(list);
		}
		for_all_unit_closeups(unitcloseup) {
			force_update(unitcloseup->window);
		}
		SetPort(oldport);
	}
}

void
update_action_result_display(Side *side, Unit *unit, int rslt, int rightnow)
{
	Action *action;

    if (active_display(side)) {
    	action = (unit->act ? &(unit->act->nextaction) : NULL);
    	if (action == NULL)
    	  return;
    	DGprintf("%s %s result is %s\n",
    			unit_desig(unit), action_desig(action), hevtdefns[rslt].name);
    	switch (action->type) {
    		case ACTION_CREATE_IN:
    		case ACTION_CREATE_AT:
    		case ACTION_BUILD:
 				/* If any construction-type action succeeded, we should update
 				   the list of types in the construction window, because the
 				   counts of types might have changed. */
    			if (rslt == A_ANY_DONE) {
    				update_construction_type_list();
    			}
    			break;
    	}
    	update_unit_in_maps(unit);
    }
}

void
update_event_display(Side *side, HistEvent *hevt, int rightnow)
{
	if (active_display(side)) {
		/* Tweak the history window if it's up. */
		if (historywin != nil && ((WindowPeek) historywin)->visible) {
			update_history_window(hevt);
		}
	}
}

void
update_fire_at_display(Side *side, Unit *unit, Unit *unit2, int m, int rightnow)
{
	int i, sx1, sy1, sw1, sh1, sx2, sy2, sw2, sh2, dx, dy, xx, yy;
	int startticks, innerticks;
	Map *map;
	GrafPtr oldport, curport = NULL;

	if (active_display(side)) {
		GetPort(&oldport);
		startticks = TickCount();
		i = 0;
		/* Tweak the pen modes of all the maps. */
		for_all_maps(map) {
			SetPort(map->window);
			PenMode(patXor);
			if (map->vp->hw > 10)
			  PenSize(2, 2);
			else
			  PenSize(1, 1);
		}
		while (TickCount() < startticks + 32) {
			innerticks = TickCount();
			for_all_maps(map) {
				if (curport != map->window) {
					SetPort(map->window);
					curport = map->window;
				}
				m_xform_unit_self(map, unit, &sx1, &sy1, &sw1, &sh1);
				m_xform_unit_self(map, unit2, &sx2, &sy2, &sw2, &sh2);
				compute_fire_line_segment(sx1 + sw1 / 2, sy1 + sh1 / 2,
										  sx2 + sw2 / 2, sy2 + sh2 / 2,
										  i, 4, &xx, &yy, &dx, &dy);
				MoveTo(xx, yy);  Line(dx, dy);
			}
			/* 2 here seems a bit slowish */
			while (TickCount() < innerticks + 1)
			  ;
			++i;
		}
		/* Restore the pen modes of all the maps. */
		for_all_maps(map) {
			SetPort(map->window);
			PenNormal();
		}
		SetPort(oldport);
	}
}

void
update_fire_into_display(Side *side, Unit *unit, int x2, int y2, int z2, int m, int rightnow)
{
	int i, sx1, sy1, sw1, sh1, sx2, sy2, sw2, sh2, dx, dy, xx, yy;
	int startticks, innerticks;
	Map *map;
	GrafPtr oldport, curport = NULL;

	if (active_display(side)) {
		GetPort(&oldport);
		startticks = TickCount();
		i = 0;
		/* Tweak the pen modes of all the maps. */
		for_all_maps(map) {
			SetPort(map->window);
			PenMode(patXor);
			if (map->vp->hw > 10)
			  PenSize(2, 2);
			else
			  PenSize(1, 1);
		}
		while (TickCount() < startticks + 32) {
			innerticks = TickCount();
			for_all_maps(map) {
				if (curport != map->window) {
					SetPort(map->window);
					curport = map->window;
				}
				m_xform_unit_self(map, unit, &sx1, &sy1, &sw1, &sh1);
				xform(map, x2, y2, &sx2, &sy2);
				compute_fire_line_segment(sx1 + sw1 / 2, sy1 + sh1 / 2,
										  sx2 + map->vp->hw / 2, sy2 + map->vp->hh / 2,
										  i, 4, &xx, &yy, &dx, &dy);
				MoveTo(xx, yy);  Line(dx, dy);
			}
			/* 2 here seems a bit slowish */
			while (TickCount() < innerticks + 1)
			  ;
			++i;
		}
		/* Restore the pen modes of all the maps. */
		for_all_maps(map) {
			SetPort(map->window);
			PenNormal();
		}
		SetPort(oldport);
	}
}

/* Update any displayed info about the given unit. */

void
update_unit_display(Side *side, Unit *unit, int rightnow)
{
	UnitCloseup *unitcloseup = find_unit_closeup(unit);

	if (active_display(side) && unit != NULL) {
		update_unit_in_maps(unit);
		if (1 /* unit visible to side in any way */ && inside_area(unit->x, unit->y)) {
			update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
		}
		update_unit_in_lists(unit);
		if (unitcloseup
		    && ((WindowPeek) (unitcloseup->window))->visible) {
			update_window(unitcloseup->window);
		}
		if (unit->side != NULL && unit->act != NULL) {
			update_side_display(side, unit->side, rightnow);
		}
		if (constructionwin != nil
		    && ((WindowPeek) constructionwin)->visible) {
			update_construction_unit_list(unit);
		}
	}
}

void
update_unit_acp_display(Side *side, Unit *unit, int rightnow)
{
	UnitCloseup *unitcloseup;

	if (active_display(side) && unit != NULL) {
		update_unit_in_maps(unit);
#if 0  /* maybe add later, maybe not - acp change not usually visible tho */
		if (1 /* unit visible to side in any way */ && inside_area(unit->x, unit->y)) {
			update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
		}
#endif
		update_unit_in_lists(unit);
		unitcloseup = find_unit_closeup(unit);
		if (unitcloseup
		    && ((WindowPeek) (unitcloseup->window))->visible) {
			update_window(unitcloseup->window);
		}
		if (buildwin != nil
		    && ((WindowPeek) buildwin)->visible
		    && unit == buildwin_unit) {
			/* We call draw_unit_build_dialog here passing false instead of 
			true, which would force redrawing. Now the dialog tests if the text
			has changed before deciding on a redraw. Note: update_window does
			not work with dialogs. */
			draw_unit_build_dialog(false);
		}
	}
}

void
update_unit_in_maps(Unit *unit)
{
	Map *map;

	if (!side_controls_unit(dside, unit) || !alive(unit)) {
		for_all_maps(map) {
			unselect_unit_on_map(map, unit);
			if (unit == map->curunit) {
				map->curunit = NULL;
			}
		}
		return;
	}
	if (side_controls_unit(dside, unit)) {
		for_all_maps(map) {
			if (map->topunith > 0 && map->numselections == 1 && unit == map->selections[0]) {
				draw_unit_info(map);
			}
		}
	}
}

void
update_clock_display(Side *side, int rightnow)
{
	GrafPtr oldport;
	time_t now;
	extern time_t lastnow;

	if (active_display(side)) {
		time(&now);
		/* If no changes since the last draw, jump out of here. */
		if (now == lastnow)
		  return;
		GetPort(&oldport);
		if (gamewin != nil && ((WindowPeek) gamewin)->visible) {
			SetPort(gamewin);
			draw_game_clocks();
		}
		SetPort(oldport);
	}
}

void
update_all_progress_displays(char *str, int s)
{
	GrafPtr oldport;
	extern char *game_progress_str;

	if (!active_display(dside))
	  return;
	game_progress_str = str;
	GetPort(&oldport);
	if (gamewin != nil && ((WindowPeek) gamewin)->visible) {
		SetPort(gamewin);
		draw_game_progress();
	}
	SetPort(oldport);
}

/* Bring up a modal dialog saying that the player has won. */

void
won_game_dialog()
{
	int done = FALSE;
	short ditem;
	WindowPtr win;

	win = GetNewDialog(dWinGame, NULL, (DialogPtr) -1L);
	ShowWindow(win);
	while (!done) {
		draw_default_button(win, diWinGameQuit);
		SetCursor(&QD(arrow));
		ModalDialog(NULL, &ditem);
		switch (ditem) {
			case diWinGameQuit:
				want_to_exit = TRUE;
			case diWinGameContinue:
				done = TRUE;
				break;
			default:
				break;
		}
	}
	DisposeDialog(win);
	force_overall_update();
}

/* Bring up a modal dialog saying that the player has lost. */

void
lost_game_dialog()
{
	int done = FALSE;
	short ditem;
	WindowPtr win;
	extern int forcedtoresign;

	/* If quitting required a resignation, the player just
	   wants out, with no dwelling on final position, so
	   bypass the dialog here. */
	if (forcedtoresign) {
		want_to_exit = TRUE;
		return;
	}
	win = GetNewDialog(dLoseGame, NULL, (DialogPtr) -1L);
	ShowWindow(win);
	while (!done) {
		draw_default_button(win, diLoseGameQuit);
		SetCursor(&QD(arrow));
		ModalDialog(NULL, &ditem);
		switch (ditem) {
			case diLoseGameQuit:
				want_to_exit = TRUE;
			case diLoseGameContinue:
				done = TRUE;
				break;
			default:
				break;
		}
	}
	DisposeDialog(win);
	force_overall_update();
}

/* Bring up a modal dialog saying that the game has ended, with no
   implication of the player having either won or lost. */

void
game_over_dialog()
{
	int done = FALSE;
	short ditem;
	WindowPtr win;

	win = GetNewDialog(dGameOver, NULL, (DialogPtr) -1L);
	ShowWindow(win);
	while (!done) {
		draw_default_button(win, diGameOverQuit);
		SetCursor(&QD(arrow));
		ModalDialog(NULL, &ditem);
		switch (ditem) {
			case diGameOverQuit:
				want_to_exit = TRUE;
			case diGameOverContinue:
				done = TRUE;
				break;
			default:
				break;
		}
	}
	DisposeDialog(win);
	force_overall_update();
}

/* Update the displays to reflect the arrival of a message from another
   side. */

void
update_message_display(Side *side, Side *sender, char *str, int rightnow)
{
	if (active_display(side)) {
		if (str == NULL)
		  str = "";
		notify(side, "From %s: \"%s\"", (sender ? short_side_title(sender) : "somebody"), str);
	}
}

void
action_point(Side *side, int x, int y)
{
	int	oldsx, oldsy, newsx, newsy;
	Map *map;

	if (side != dside || !inside_area(x, y))
	  return;

	for_all_maps(map) {
		if (map->follow_action && !in_middle(map, x, y)) {
			/* Save the old vp. */
			oldsx = map->vp->sx;  oldsy = map->vp->sy;

			/* Find the new vp. */
			set_view_focus(map->vp, x, y);
			m_center_on_focus(map);
			newsx = map->vp->sx;  newsy = map->vp->sy;

			/* Restore the old vp. */	
			map->vp->sx = oldsx;  map->vp->sy = oldsy;

			/* Let scroll_map_window do the job instead. */
			scroll_map_window(map, newsx - oldsx, newsy - oldsy);
		}
	}    
}

/* Support for movie display. */

int
schedule_movie(Side *side, char *movie, ...)
{
	int i, itype;
	va_list ap;

	if (numscheduled >= 10)
	  return FALSE;
	if (side != dside)
	  return FALSE;
	if (empty_string(movie))
	  return FALSE;
	if (movies == NULL)
	  movies = (struct a_movie *) xmalloc(10 * sizeof(struct a_movie));
	memset(&(movies[numscheduled]), 0, sizeof(struct a_movie));
	movies[numscheduled].type = movie;
	itype = movie_null;
	if (strcmp(movie, "miss") == 0)
	  itype = movie_miss;
	else if (strcmp(movie, "hit") == 0)
	  itype = movie_hit;
	else if (strcmp(movie, "death") == 0)
	  itype = movie_death;
	else if (strcmp(movie, "nuke") == 0)
	  itype = movie_nuke;
	else if (strcmp(movie, "sound") == 0)
	  itype = movie_sound;
	else if (strcmp(movie, "flash") == 0)
	  itype = movie_flash;
	movies[numscheduled].itype = itype;
	va_start(ap, movie);
	for (i = 0; i < 5; ++i)
	  movies[numscheduled].args[i] = va_arg(ap, int);
	va_end(ap);
	++numscheduled;
	return TRUE;
}

void
play_movies(SideMask sidemask)
{
	int j, unitid, startticks, innerticks;
	int blasttype;
	Map *map;
	Unit *unit;
	GrafPtr oldport, curport = NULL;
	long startcount;
	int play = FALSE;

    if (!side_in_set(dside, sidemask))
      return;
    if (movies == NULL)
      return;

    /* First test if we really need to play any movies. */
    for (j = 0; j < numscheduled; ++j) {
	/* We just decided that we need to play at least one movie. */
         if (play)
             break;
	switch (movies[j].itype) {
		case movie_null:
			break;
		case movie_miss:
		case movie_hit:
		case movie_death:
			unitid = movies[j].args[0];
			unit = find_unit(unitid);
			if (unit == NULL
			    || !in_area(unit->x, unit->y)
			    || !units_visible(dside, unit->x, unit->y))
			  break;
			for_all_maps(map) {
				/* Only play movies that will be drawn. Skip the world map. */
				if (map != worldmap
				    && cell_is_in_gworld(map, unit->x, unit->y)) {
					play = TRUE;
					break;
				}
			}
			break;
		case movie_nuke:
		case movie_flash:
			play = TRUE;	/* Should test if visible on any map. */
			break;
		case movie_sound:
			play = TRUE;	/* Should test that the units involved are visible. */
			break;
		default:
			break;
		}
	}

	/* Then proceed if necessary to waste those precious cycles. */
	if (play) {
		GetPort(&oldport);
		startticks = TickCount();
		while (TickCount() < startticks + 32) {
			innerticks = TickCount();
			for (j = 0; j < numscheduled; ++j) {
				switch (movies[j].itype) {
					case movie_null:
						break;
					case movie_miss:
					case movie_hit:
					case movie_death:
						unitid = movies[j].args[0];
						unit = find_unit(unitid);
						if (unit == NULL 
						    || !in_area(unit->x, unit->y)
						    || !units_visible(dside, unit->x, unit->y))
						  continue;
						blasttype = (movies[j].itype == movie_miss ? 0 :
									 (movies[j].itype == movie_hit ? 1 : 2));
						for_all_maps(map) {
							/* Only play movies that will be drawn. Skip the world map. */
							if (map != worldmap
							    && cell_is_in_gworld(map, unit->x, unit->y)) {
							draw_unit_blast(map, unit, blasttype);
						}
						}
						startcount = TickCount();
						play_sound("crunch");
						if (blasttype > 0)
						  play_sound("boom");
						/* Delay for part of a second (should relinquish cpu tho) */
						while ((TickCount() - startcount) < 10)
						  ;
						break;
					case movie_nuke:	/* Temporary solution - use flash also for nukes. */
					case movie_flash:
						for_all_maps(map) {
							invert_map(map, movies[j].args[0], movies[j].args[1], area.maxdim);
						}
						startcount = TickCount();
						/* Delay for part of a second (should relinquish cpu tho?) */
						while ((TickCount() - startcount) < 10)
						  ;
						for_all_maps(map) {
							invert_map(map, movies[j].args[0], movies[j].args[1], area.maxdim);
						}
						break;
					case movie_sound:
						/* Just play the given sound. */
						play_sound((char *) movies[j].args[0]);
						break;
					default:
						break;
				}
			}
			while (TickCount() < innerticks + 32)
			  ;
		}
		/* Do cleanups. */
		for (j = 0; j < numscheduled; ++j) {
			switch (movies[j].itype) {
				case movie_null:
				case movie_nuke:
				case movie_flash:
				case movie_sound:
					break;
				case movie_miss:
				case movie_hit:
				case movie_death:
					unitid = movies[j].args[0];
					unit = find_unit(unitid);
					if (unit == NULL 
					    || !in_area(unit->x, unit->y)
					    || !units_visible(dside, unit->x, unit->y))
					  continue;
					for_all_maps(map) {
						/* Only erase blasts that really were drawn. */
						if (map != worldmap
						    && cell_is_in_gworld(map, unit->x, unit->y)) {
					  update_cell(map, unit->x, unit->y);
						}
					}
					break;
				default:
					break;
			}
		}
		SetPort(oldport);
	}
	numscheduled = 0;
}

void
play_sound(char *soundname)
{
	Str255 sndnamebuf;
	Handle sound;

    if (!playsounds || empty_string(soundname) || numsoundplays >= 5)
      return;
	c2p(soundname, sndnamebuf);
	sound = GetNamedResource('snd ', sndnamebuf);
	if (sound != nil) {
		HLock(sound);
		/* We play the sound synchronously, because the time delay is needed to
		   make sure the graphics are visible. */
        SndPlay(nil, (SndListHandle) sound, false);
        HUnlock(sound);
        ReleaseResource(sound);
        ++numsoundplays;
    } else {
		run_warning("No sound named \"%s\" available!", soundname);
		/* (should only complain about each sound once) */
    }
}

/* Move the window to a position staggered from the given last position. */

void
stagger_window(WindowPtr win, int *lasthp, int *lastvp)
{
	int h, v, retry = 0;
	Rect winrect;
	GrafPtr oldport;

	if (*lasthp > 0 && *lastvp > 0) {
		while (1) {
			/* Prevent excessive staggering of new windows. */
			if (!position_already_used(*lasthp, *lastvp)) {
				h = *lasthp;  v = *lastvp;
				break;
			}
			h = *lasthp + 20;  v = *lastvp + 20;
			/* Let windows go partly off the screen, but keep at least the top
			   40x40 pixels visible. */
			if (!position_on_screen(h + 40, v + 40)) {
				if (retry < 20) {
					h = retry * 20; v = 40;
					++retry;
				} else {
					/* This is getting out of hand - just pick something. */
					h = 20;  v = 40;
					break;
				}
			} else if (!position_already_used(h, v)) {
				break;
			}
			/* Moving this inside the while loop fixed infinite loop hangs with certain values. */
			*lasthp = h;  *lastvp = v;
		}
		MoveWindow(win, h, v, FALSE);
	} else {
		/* Do move to accomodate Apple Platinum frames (would be more correct 
		to use strucRgn->rgnBBox here, but it is not yet defined at this point). */
		MoveWindow(win, 5, GetMBarHeight() + 21, FALSE);

		/* Don't move the first window, but do record its position. */
		GetPort(&oldport);
		SetPort(win);
		winrect = win->portRect;
		LocalToGlobal(&top_left(winrect));
		*lasthp = winrect.left;  *lastvp = winrect.top;
		SetPort(oldport);
	}
}

/* Return true if the given position is visible on a screen somewhere. */

int
position_on_screen(int h, int v)
{
	Point pnt;
	GDHandle screen;

	pnt.h = h;  pnt.v = v;
	if (hasColorQD) {	
		for (screen = GetDeviceList(); screen != nil; screen = GetNextDevice(screen)) {
			if (TestDeviceAttribute(screen, screenDevice)
				&& TestDeviceAttribute(screen, screenActive)) {
				if (PtInRect(pnt, &((*screen)->gdRect)))
				  return TRUE;
			}
		}
	} else {
		if (PtInRect(pnt, &(QD(screenBits).bounds)))
		  return TRUE;
	}
	return FALSE;
}

/* (should make this more efficient?) */

int
position_already_used(int h, int v)
{
	int i;
	Rect winrect;
	WindowPtr win;
	GrafPtr oldport;

	for (i = 0; i < numwindows; ++i) {
		win = winmenuwins[i];
		GetPort(&oldport);
		SetPort(win);
		winrect = win->portRect;
		LocalToGlobal(&top_left(winrect));
		SetPort(oldport);
		if (h == winrect.left && v == winrect.top)
		  return TRUE;
	}
	return FALSE;
}

/* (This may only be called if Color Quickdraw is present.) */

GDHandle
best_zoom_screen(Rect *rectptr)
{
	int greatestarea = 0, sectarea;
	Rect srect;
	GDHandle screen = GetDeviceList(), bestscreen = GetMainDevice();

	while (screen != nil) {
		if (TestDeviceAttribute(screen, screenDevice)
			&& TestDeviceAttribute(screen, screenActive)) {
			SectRect(rectptr, &((*screen)->gdRect), &srect);
			sectarea = (srect.right - srect.left) * (srect.bottom - srect.top);
			if (sectarea > greatestarea) {
				greatestarea = sectarea;
				bestscreen = screen;
			}
		}
		screen = GetNextDevice(screen);
	}
	return bestscreen;
}

void
set_standard_state(WindowPtr win, int fullw, int fullh)
{
	int screenw, screenh, wintitlehgt, mbaradj = 0;
	Rect winrect, gdrect, zoomrect;
	GDHandle bestscreen;

	winrect = win->portRect;
	LocalToGlobal((Point *) &(winrect.top));
	LocalToGlobal((Point *) &(winrect.bottom));
	wintitlehgt = winrect.top - 1 - (*(((WindowPeek) win)->strucRgn))->rgnBBox.top;
	if (hasColorQD) {
		/* Get the best screen to zoom on. */
		bestscreen = best_zoom_screen(&winrect);
		gdrect = (*bestscreen)->gdRect;
		/* Adjust to the actual subarea that we can use. */
		if (bestscreen == GetMainDevice()) {
			gdrect.top += GetMBarHeight();
		}
		/* Modified for Apple Platinum. */
		InsetRect(&gdrect, 5, 5);
		gdrect.top -= 5;
		gdrect.top += wintitlehgt;
		screenw = gdrect.right - gdrect.left;  screenh = gdrect.bottom - gdrect.top;
		if (winrect.left + fullw <= gdrect.right
			&& winrect.top + fullh <= gdrect.bottom) {
			SetRect(&zoomrect, winrect.left, winrect.top, winrect.left + fullw, winrect.top + fullh);
		} else if (fullw <= screenw || fullh <= screenh) {
			SetRect(&zoomrect, gdrect.left, gdrect.top, gdrect.left + fullw, gdrect.top + fullh);
			if (fullw > screenw)
			  zoomrect.right = gdrect.right;
			if (fullh > screenh)
			  zoomrect.bottom = gdrect.bottom;
		} else {
			zoomrect = gdrect;
		}
	} else {
		zoomrect = QD(screenBits).bounds;
		zoomrect.top += GetMBarHeight();
		InsetRect(&zoomrect, 4, 4);
		zoomrect.top += wintitlehgt;
	}
	((WStateDataPtr) *(((WindowPeek) win)->dataHandle))->stdState = zoomrect;
}

void
get_main_screen_size(int *widp, int *hgtp)
{
	Rect rect;
	GrafPtr mainport;
	GDHandle mainscreen;

	if (hasColorQD) {
		mainscreen = GetMainDevice();
		rect = (*mainscreen)->gdRect;
	} else {
		GetWMgrPort(&mainport);
		rect = mainport->portRect;
	}
	if (widp)
	  *widp = rect.right - rect.left;
	if (hgtp)
	  *hgtp = rect.bottom - rect.top;
}

/* General routine to outline the given item of a given dialog. */

void
draw_default_button(DialogPtr dialog, short ditem)
{
	GrafPtr oldport;
	short itemtype;  Handle itemhandle;  Rect itemrect;

	GetPort(&oldport);
	SetPort(dialog);
	GetDItem(dialog, ditem, &itemtype, &itemhandle, &itemrect);
	PenSize(3, 3);
	InsetRect(&itemrect, -4, -4);
	FrameRoundRect(&itemrect, 16, 16);
	PenNormal();
	SetPort(oldport);
}

char *
get_string_from_item(Handle itemhandle)
{
	char tmpbuf[PREFSIZE];

	Str255 tmpstr;
	
	GetIText(itemhandle, tmpstr);
	p2c(tmpstr, tmpbuf);
	return copy_string(tmpbuf);
}

/* Cause an update of a window's entire contents. */

void
force_update(WindowPtr win)
{
	GrafPtr oldport;
	Map *map;

	if (win == nil)
	  return;
	GetPort(&oldport);
	SetPort(win);
	
	map = map_from_window(win);
	if (map) {
		/* Do this stuff first so that the user has something to look at. */
		set_content_rect(map);
		set_map_scrollbars(map);

		/* Draw the map decor. */
		if (map->conw > 0)
		  draw_control_panel(map);
		if (map->toplineh > 0)
		  draw_top_line(map);
		if (map->topunith > 0)
		  draw_unit_info(map);

 		update_gworld(map);
	} else {
		/* For non-maps invalidate and update the window. Don't EraseRect
		since it messes up the modeless dialogs. */
		InvalRect(&win->portRect);
		update_window(win);
	}
	SetPort(oldport);
}

void
force_overall_update()
{
	Map *map;
	List *list;
	UnitCloseup *unitcloseup;

	force_update(gamewin);
	force_update(historywin);
	force_update(constructionwin);
	force_update(helpwin);
	force_update(noticewin);
	force_update(commandwin);
	force_update(scoreswin);
	for_all_maps(map) {
		force_map_update(map);
	}
	for_all_lists(list) {
		force_update(list->window);
	}
	for_all_unit_closeups(unitcloseup) {
		force_update(unitcloseup->window);
	}
}

void
beep()
{
	SysBeep(20);
}

void
update_everything()
{
	if (active_display(dside)) {
		force_overall_update();
	}
}

/* Set the type and creator of the file to be what is expected
   for a game design. */

void
set_game_file_type(char *name)
{
	FileParam pb;
	Str255 tmpstr;
	
	c2p(name, tmpstr);
	pb.ioNamePtr = tmpstr;
	pb.ioVRefNum = 0;
	pb.ioFVersNum = 0;
	pb.ioFDirIndex = 0;
	if (PBGetFInfoSync((ParmBlkPtr) &pb) == noErr) {
		pb.ioFlFndrInfo.fdType = 'TEXT';
		pb.ioFlFndrInfo.fdCreator = XconqSignature;
		PBSetFInfoSync((ParmBlkPtr) &pb);
	}
}

void
exit_macconq()
{
	close_remote_connection(0);

#if __profile__

	/* Make sure we use the Xconq directory. */
	SetVol(NULL, initialvrefnum);
	/* Dump any unsaved profiler output for CodeWarrior. */
	ProfilerDump("\pXconq.Profile");
	ProfilerTerm();

#endif

	ExitToShell();
}

#ifdef __MWERKS__

/* Empty definitions for Metrowerks' SIOUX console library. */

#ifndef __CONSOLE__
#include <console.h>
#endif

short
InstallConsole(short fd)
{
#pragma unused (fd)
	return 0;
}

void
RemoveConsole(void)
{
}

long
WriteCharsToConsole(char *buf, long n)
{
#pragma unused (buf, n)
	return 0;
}

long ReadCharsFromConsole(char *buf, long n)
{
#pragma unused (buf, n)
	return 0;
}

extern char *
__ttyname(long fd)
{
	static char *__devicename = "null device";

	if (fd >= 0 && fd <= 2)
	  return (__devicename);
	return NULL;
}

#endif /* __MWERKS__ */

void
add_remote_locally(int rid, char *str)
{
}

void
send_chat(int rid, char *str)
{
}
