This is EditorController.m in view mode; [Download] [Up]
//
// $Id: EditorController.m,v 1.11 1997/10/31 04:51:41 nygard Exp $
//
//
// This file is a part of Empire, a game of exploration and conquest.
// Copyright (C) 1996 Steve Nygard
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// You may contact the author by:
// e-mail: nygard@telusplanet.net
//
#import "Empire.h"
RCSID ("$Id: EditorController.m,v 1.11 1997/10/31 04:51:41 nygard Exp $");
#import "EditorController.h"
#import "Brain.h"
#import "Map.h"
#import "MapView.h"
#import "SNRandom.h"
#import "WorldMapController.h"
//======================================================================
// The Editor Controller provides simple "painting" of terrain in
// order to create maps. There are a couple of different ways of
// "growing" terrain to help.
//
// Additionally, there is some prototype code for trying to automate
// map generation. I was trying to parameterize it based on the size
// of the map, possibly with the option of being able to select between
// different sets of parameters (i.e. create a world with may small
// islands, or one with a few larger continents, etc.)
//
// I've also tried another method, basically draw a random line through
// the map, raise the elevation on one side, and repeat many times.
// Then create the land based on the elevation. Unfortunately, while
// this seems to produce nice coastlines, especially with larger maps,
// I end up with one large land mass... I've got a separate application
// that I was experimenting with this method.
//
// Automated city placement can happen once terrain generation is
// working.
//======================================================================
@implementation EditorController
- (void) awakeFromNib
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *imagePath;
NSImage *image;
BOOL okay;
id tmp;
[mapView setCursorEnabled:NO];
imagePath = [[NSBundle mainBundle] pathForImageResource:@"mwi_map_editor.tiff"];
NSAssert (imagePath != nil, @"Couldn't find mwi_map_editor.tiff");
image = [[[NSImage alloc] initWithContentsOfFile:imagePath] autorelease];
NSAssert (image != nil, @"Couldn't load mwi_map_editor.tiff");
[mapEditorWindow setMiniwindowImage:image];
okay = [mapEditorWindow setFrameAutosaveName:@"Map Editor"];
if (okay == NO)
NSLog (@"Could not set frame autosave name of editor window.");
okay = [newMapPanel setFrameAutosaveName:@"New Map Panel"];
if (okay == NO)
NSLog (@"Could not set frame autosave name of new map panel.");
tmp = [defaults stringForKey:DK_MapWidth];
[widthTextfield setStringValue:tmp];
tmp = [defaults stringForKey:DK_MapHeight];
[heightTextfield setStringValue:tmp];
}
//----------------------------------------------------------------------
- init
{
NSString *nibFile;
BOOL loaded;
[super init];
nibFile = @"MapEditor.nib";
loaded = [NSBundle loadNibNamed:nibFile owner:self];
if (loaded == NO)
{
NSLog (@"Could not load %@.", nibFile);
[super dealloc];
return nil;
}
map = nil;
brushType = 0;
tokenType = EMCreateMapToken (t_water, p_neutral, i_none, YES);
mapName = nil;
terrainCounts[0] = 0;
terrainCounts[1] = 0;
terrainCounts[2] = 0;
terrainCounts[3] = 0;
lastDirectory = nil;
rng = [[SNRandom instance] retain];
return self;
}
//----------------------------------------------------------------------
- (void) dealloc
{
SNRelease (mapEditorWindow);
SNRelease (map);
SNRelease (mapName);
SNRelease (worldMapController);
SNRelease (rng);
[super dealloc];
}
//----------------------------------------------------------------------
- (Map *) map
{
return map;
}
//----------------------------------------------------------------------
- (void) setMap:(Map *)newMap
{
SNRelease (map);
map = newMap;
[map retain];
[mapView setMap:newMap];
if (worldMapController != nil)
{
[worldMapController setMap:map];
}
}
//----------------------------------------------------------------------
- (void) takeBrushTypeFrom:sender
{
brushType = [sender state];
}
//----------------------------------------------------------------------
- (void) takeTokenTypeFrom:sender
{
static Terrain tokens[] = {t_water, t_land, t_city};
tokenType = EMCreateMapToken (tokens[[[sender selectedCell] tag]], p_neutral, i_none, YES);
}
//----------------------------------------------------------------------
- (BOOL) validateMenuItem:(NSMenuItem *)menuCell
{
SEL action = [menuCell action];
BOOL valid = NO;
if (action == @selector (showWorldMap:))
{
valid = YES;
}
else if (action == @selector (open:))
{
valid = YES;
}
else if (action == @selector (save:)
|| action == @selector (saveAs:)
|| action == @selector (saveTo:))
{
valid = YES;
}
return valid;
}
//----------------------------------------------------------------------
- (void) showWorldMap:sender
{
if (worldMapController == nil)
{
worldMapController = [[WorldMapController alloc] init];
[worldMapController setMap:map];
[worldMapController setDelegate:self];
[worldMapController setTitle:@"Map Editor World Map" autosaveFrame:YES];
}
[worldMapController showPanel];
}
//----------------------------------------------------------------------
- (void) newMapStopAction:sender
{
//[NSApp stopModal];
[newMapPanel orderOut:self];
}
//----------------------------------------------------------------------
- (void) okayAction:sender
{
EMMapSize mapSize;
Map *tmp;
int count;
mapSize.width = [widthTextfield intValue];
mapSize.height = [heightTextfield intValue];
if (mapSize.width < 1)
{
NSRunAlertPanel (@"New Map", @"The width of the map must be greater than zero.", @"OK", nil, nil);
return;
}
if (mapSize.height < 1)
{
NSRunAlertPanel (@"New Map", @"The height of the map must be greater than zero.", @"OK", nil, nil);
return;
}
[newMapPanel orderOut:self];
count = mapSize.width * mapSize.height;
if (count >= 1000000)
{
int alertValue;
alertValue = NSRunAlertPanel (@"Warning", @"Large map size (%d) selected.", @"Cancel", @"Create Map", NULL, count);
if (alertValue != NSAlertAlternateReturn)
return;
}
tmp = [[[Map alloc] initMapWithSize:mapSize] autorelease];
NSAssert (tmp != nil, @"New map is nil");
[tmp clearMapTo:EMCreateMapToken (t_water, p_neutral, i_none, YES)];
[self setMap:tmp];
[mapEditorWindow setTitleWithRepresentedFilename:@"UNTITLED.map"];
[mapEditorWindow makeKeyAndOrderFront:self];
}
//----------------------------------------------------------------------
- (void) takeLastDirectoryFromSavePanel:(NSSavePanel *)savePanel
{
if (lastDirectory != nil)
{
SNRelease (lastDirectory);
}
lastDirectory = [[savePanel directory] retain];
}
//----------------------------------------------------------------------
- (void) newMap
{
[newMapPanel makeKeyAndOrderFront:nil];
}
//----------------------------------------------------------------------
- (void) open:sender
{
NSArray *types = [NSArray arrayWithObject:@"map"];
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
NSString *filename;
Map *newMap;
[openPanel setDirectory:lastDirectory];
[openPanel setAllowsMultipleSelection:NO];
if ([openPanel runModalForTypes:types] == NSOKButton)
{
[self takeLastDirectoryFromSavePanel:openPanel];
filename = [openPanel filename];
newMap = [NSUnarchiver unarchiveObjectWithFile:filename];
NSAssert1 (newMap != nil, @"Error loading map '%@'\n", filename);
[self setMap:newMap];
[mapEditorWindow setTitleWithRepresentedFilename:filename];
[mapEditorWindow makeKeyAndOrderFront:self];
[mapEditorWindow setDocumentEdited:NO];
SNRelease (mapName);
mapName = [filename retain];
}
}
//----------------------------------------------------------------------
- (void) save:sender
{
[self saveMode:sm_save];
}
//----------------------------------------------------------------------
- (void) saveAs:sender
{
[self saveMode:sm_save_as];
}
//----------------------------------------------------------------------
- (void) saveTo:sender
{
[self saveMode:sm_save_to];
}
//----------------------------------------------------------------------
- (BOOL) saveMode:(SaveMode)sm
{
NSSavePanel *savePanel = [NSSavePanel savePanel];
NSString *targetFile;
BOOL rflag;
[savePanel setDirectory:lastDirectory];
[savePanel setRequiredFileType:@"map"];
if (sm == sm_save && mapName == nil)
sm = sm_save_as;
if (sm == sm_save)
{
targetFile = mapName;
}
else //(sm != sm_save)
{
if ([savePanel runModal] != NSOKButton)
return NO;
[self takeLastDirectoryFromSavePanel:savePanel];
targetFile = [savePanel filename];
}
rflag = [NSArchiver archiveRootObject:map toFile:targetFile];
if (sm == sm_save_as)
{
[mapEditorWindow setTitleWithRepresentedFilename:targetFile];
}
[mapEditorWindow setDocumentEdited:NO];
return rflag;
}
//----------------------------------------------------------------------
#define FLIP_Y 0x01
#define FLIP_X 0x02
#define FLIP_XY 0x04
- (void) growTerrain:(MapToken)token fromLocation:(EMMapLocation)source toLocation:(EMMapLocation)target
{
int dx;
int dy;
int d;
int increment_E;
int increment_SE;
int octant;
int row = source.row;
int col = source.column;
EMMapLocation tmp;
dx = target.column - source.column;
dy = target.row - source.row;
octant = 0;
if (dy < 0)
{
octant |= FLIP_Y;
dy = -dy;
row = -row;
target.row = -target.row;
}
if (dx < 0)
{
octant |= FLIP_X;
dx = -dx;
col = -col;
target.column = -target.column;
}
if (dx < dy)
{
int tmp;
octant |= FLIP_XY;
tmp = dx;
dx = dy;
dy = tmp;
tmp = row;
row = col;
col = tmp;
tmp = target.row;
target.row = target.column;
target.column = tmp;
}
d = 2 * dy - dx;
increment_E = 2 * dy;
increment_SE = 2 * (dy - dx);
if ([map tokenAtLocation:source] != token)
{
[map setToken:token atLocation:source];
}
else
{
while (col < target.column)
{
if (d <= 0)
{
d += increment_E;
col++;
}
else
{
d += increment_SE;
col++;
row++;
}
if (octant & FLIP_XY)
{
tmp.row = col;
tmp.column = row;
}
else
{
tmp.row = row;
tmp.column = col;
}
if (octant & FLIP_X)
tmp.column = -tmp.column;
if (octant & FLIP_Y)
tmp.row = -tmp.row;
if ([map tokenAtLocation:tmp] != token)
{
[map setToken:token atLocation:tmp];
break;
}
}
}
}
//----------------------------------------------------------------------
- (void) branchTerrain:(MapToken)token fromLocation:(EMMapLocation)source toLocation:(EMMapLocation)target
{
int dx;
int dy;
int d;
int increment_E;
int increment_SE;
int octant;
int row;
int col;
EMMapLocation tmp, foo;
EMMapSize mapSize;
//NSLog (@"source: (%d,%d), target: (%d,%d)", source.row, source.column, target.row, target.column);
// Seed the initial location
[map setToken:token atLocation:source];
#if 0
foo = source;
source = target;
target = foo;
#endif
mapSize = [map mapSize];
row = source.row;
col = source.column;
dx = target.column - source.column;
dy = target.row - source.row;
octant = 0;
if (dy < 0)
{
octant |= FLIP_Y;
dy = -dy;
row = -row;
target.row = -target.row;
}
if (dx < 0)
{
octant |= FLIP_X;
dx = -dx;
col = -col;
target.column = -target.column;
}
if (dx < dy)
{
int tmp;
octant |= FLIP_XY;
tmp = dx;
dx = dy;
dy = tmp;
tmp = row;
row = col;
col = tmp;
tmp = target.row;
target.row = target.column;
target.column = tmp;
}
d = 2 * dy - dx;
increment_E = 2 * dy;
increment_SE = 2 * (dy - dx);
while (col < target.column)
{
if (d <= 0)
{
d += increment_E;
col++;
}
else
{
d += increment_SE;
col++;
row++;
}
if (octant & FLIP_XY)
{
tmp.row = col;
tmp.column = row;
}
else
{
tmp.row = row;
tmp.column = col;
}
if (octant & FLIP_X)
tmp.column = -tmp.column;
if (octant & FLIP_Y)
tmp.row = -tmp.row;
if (tmp.row >= 0 && tmp.column >= 0 && tmp.row < mapSize.height && tmp.column < mapSize.width)
{
if ([self location:tmp adjacentToTerrain:EMTerrainComponent (token)] == NO)
{
//[map setToken:token atLocation:tmp];
break;
}
}
foo = tmp;
}
if (foo.row >= 0 && foo.column >= 0 && foo.row < mapSize.height && foo.column < mapSize.width)
{
[map setToken:token atLocation:foo];
}
}
//----------------------------------------------------------------------
- (void) growTerrain:(MapToken)token fromLocation:(EMMapLocation)source
{
EMMapLocation destination;
int theta = [rng randomNumberModulo:360];
double x = (theta * 2 * 3.14159265) / 360;
destination.row = source.row + 1000 * sin (x);
destination.column = source.column + 1000 * cos (x);
[self growTerrain:token fromLocation:source toLocation:destination];
}
//----------------------------------------------------------------------
- (void) branchTerrain:(MapToken)token fromLocation:(EMMapLocation)source
{
EMMapLocation destination;
int theta = [rng randomNumberModulo:360];
double x = (theta * 2 * 3.14159265) / 360;
destination.row = source.row + 1000 * sin (x);
destination.column = source.column + 1000 * cos (x);
[self branchTerrain:token fromLocation:source toLocation:destination];
}
//----------------------------------------------------------------------
- (BOOL) location:(EMMapLocation)target adjacentToTerrain:(Terrain)terrain
{
BOOL adjacent = NO;
MapToken tokens[9];
int l;
[map get3x3Tokens:tokens aroundLocation:target];
for (l = 0; l < 9; l++)
{
if (EMTerrainComponent (tokens[l]) == terrain)
{
adjacent = YES;
break;
}
}
return adjacent;
}
//----------------------------------------------------------------------
- (void) clearToCurrent:sender
{
[map clearMapTo:tokenType];
}
//----------------------------------------------------------------------
- (void) suspendMainMapUpdate
{
}
//----------------------------------------------------------------------
- (void) resumeMainMapUpdate
{
}
//======================================================================
// Build World
//======================================================================
- (void) recalculateTerrainDistribution:sender
{
EMMapSize mapSize;
terrainCounts[0] = 0;
terrainCounts[1] = 0;
terrainCounts[2] = 0;
terrainCounts[3] = 0;
if (map != nil)
{
mapSize = [map mapSize];
terrainCounts[0] = mapSize.width * mapSize.height;
terrainCounts[t_water] = [map countTerrainType:t_water];
terrainCounts[t_land] = [map countTerrainType:t_land];
terrainCounts[t_city] = [map countTerrainType:t_city];
}
//NSLog (@"%d, %d, %d, %d", terrainCounts[0], terrainCounts[1], terrainCounts[2], terrainCounts[3]);
[terrainSummaryTableview reloadData];
}
//----------------------------------------------------------------------
- (void) build:sender
{
EMMapSize mapSize = [map mapSize];
EMMapLocation source;
int islandCount;
int minimumSize;
int maximumSize;
int count;
int l, m, n;
int buildType;
//int lastIndex = 0;
islandCount = [islandCountTextfield intValue];
minimumSize = [minimumSizeTextfield intValue];
maximumSize = [maximumSizeTextfield intValue];
buildType = [[buildTypeMatrix selectedCell] tag];
[mapEditorWindow setDocumentEdited:YES];
[mapEditorWindow disableFlushWindow];
if (buildType == 0)
{
for (l = 0; l < islandCount; l++)
{
count = [rng randomNumberBetween:minimumSize:maximumSize];
source.row = [rng randomNumberModulo:mapSize.height];
source.column = [rng randomNumberModulo:mapSize.width];
for (m = 0; m < count; m++)
{
//source = [self meanderFromLocation:source lastDirectionIndex:&lastIndex];
for (n = 0; n < 2; n++)
[self growTerrain:tokenType fromLocation:source];
}
}
}
else
{
for (l = 0; l < islandCount; l++)
{
count = [rng randomNumberBetween:minimumSize:maximumSize];
source.row = [rng randomNumberModulo:mapSize.height];
source.column = [rng randomNumberModulo:mapSize.width];
for (m = 0; m < count; m++)
{
//source = [self meanderFromLocation:source lastDirectionIndex:&lastIndex];
for (n = 0; n < 2; n++)
[self branchTerrain:tokenType fromLocation:source];
}
}
}
[mapEditorWindow enableFlushWindow];
[self recalculateTerrainDistribution:nil];
}
//----------------------------------------------------------------------
- (void) clearSingleTerrains:sender
{
EMMapSize mapSize = [map mapSize];
MapToken tokens[9];
EMMapLocation target;
BOOL flag = YES;
int l;
int count = 0;
[mapEditorWindow setDocumentEdited:YES];
for (target.row = 0; target.row < mapSize.height; target.row++)
{
for (target.column = 0; target.column < mapSize.width; target.column++)
{
flag = YES;
[map get3x3Tokens:tokens aroundLocation:target];
if (tokens[4] != EMCreateMapToken (t_city, p_neutral, i_none, YES))
{
count++;
for (l = 0; l < 9; l++)
{
if (l != 4 && tokens[l] == tokens[4])
{
flag = NO;
break;
}
}
if (flag == YES)
{
//NSLog (@"tokens: %@", EMFormatNineComponents (tokens));
if (EMTerrainComponent (tokens[4]) == t_water)
[map setToken:EMCreateMapToken (t_land, p_neutral, i_none, YES) atLocation:target];
else
[map setToken:EMCreateMapToken (t_water, p_neutral, i_none, YES) atLocation:target];
//[map setToken:tokens[0] atLocation:target];
}
}
}
}
}
//----------------------------------------------------------------------
- (EMMapLocation) meanderFromLocation:(EMMapLocation)location
lastDirectionIndex:(int *)lastIndex
origin:(EMMapLocation)origin
size:(EMMapSize)size
{
EMMapLocation target;
target = [self meanderFromLocation:location lastDirectionIndex:lastIndex];
if (target.row < origin.row || target.column < origin.column
|| target.row > origin.row + size.height || target.column > origin.column + size.width)
{
*lastIndex = (*lastIndex + 4) % 9;
}
return target;
}
//----------------------------------------------------------------------
- (EMMapLocation) meanderFromLocation:(EMMapLocation)location lastDirectionIndex:(int *)lastIndex
{
//EMMapSize mapSize = [map mapSize];
Direction directions[8] = {d_northwest, d_north, d_northeast, d_east, d_southeast, d_south, d_southwest, d_west};
Direction dir;
int turn;
int dy[9] = {-1, -1, -1, 0, 0, 0, 1, 1, 1};
int dx[9] = {-1, 0, 1, -1, 0, 1, -1, 0, 1};
turn = [rng randomNumberBetween:-1:1];
turn += *lastIndex;
if (turn < 0)
turn += 8;
else if (turn > 7)
turn -= 8;
dir = directions[turn];
*lastIndex = turn;
location.row += dy[dir];
location.column += dx[dir];
return location;
}
//----------------------------------------------------------------------
- (void) meander:sender
{
EMMapSize mapSize = [map mapSize];
EMMapLocation source;
int buildType;
int m;
int lastIndex = 0;
[mapEditorWindow setDocumentEdited:YES];
buildType = [[buildTypeMatrix selectedCell] tag];
source.row = [rng randomNumberModulo:mapSize.height];
source.column = [rng randomNumberModulo:mapSize.width];
[map setToken:tokenType atLocation:source];
for (m = 0; m < 20; m++)
{
source = [self meanderFromLocation:source lastDirectionIndex:&lastIndex];
[map setToken:tokenType atLocation:source];
}
}
//----------------------------------------------------------------------
- (int) numberOfRowsInTableView:(NSTableView *)aTableView
{
return 4;
}
//----------------------------------------------------------------------
- tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
NSString *terrainNames[] = {
@"Total",
@"Water",
@"Land",
@"City"
};
id identifier;
id tmp = nil;
Terrain terrain;
identifier = [aTableColumn identifier];
terrain = (rowIndex >= 3) ? t_unknown : t_water + rowIndex;
if ([identifier isEqual:@"Terrain"] == YES)
{
tmp = terrainNames[terrain];
}
else if ([identifier isEqual:@"Count"] == YES)
{
tmp = [NSNumber numberWithInt:terrainCounts[terrain]];
}
else if ([identifier isEqual:@"Percent"] == YES)
{
if (terrainCounts[t_unknown] != 0)
tmp = [NSString stringWithFormat:@"%.2f", terrainCounts[terrain] * 100.0 / terrainCounts[t_unknown]];
}
return tmp;
}
//======================================================================
// MapView Delegate
//======================================================================
- (void) mouseDown:(unsigned int)modifierFlags atLocation:(EMMapLocation)target
{
EMMapLocation destination;
[mapEditorWindow setDocumentEdited:YES];
[mapView scrollLocationToVisible:target];
if ((modifierFlags & NSControlKeyMask) != 0)
{
int theta = [rng randomNumberModulo:360];
double x = (theta * 2 * 3.14159265) / 360;
destination.row = target.row + 1000 * sin (x);
destination.column = target.column + 1000 * cos (x);
[self growTerrain:tokenType fromLocation:target toLocation:destination];
//[self growTerrain:tokenType fromLocation:target toLocation:0:0];
//[self growTerrain:tokenType fromLocation:target toLocation:49:79];
//[self growTerrain:tokenType fromLocation:target toLocation:49:0];
//[self growTerrain:tokenType fromLocation:target toLocation:destrow:destcol];
}
else if ((modifierFlags & NSCommandKeyMask) != 0)
{
int theta = [rng randomNumberModulo:360];
double x = (theta * 2 * 3.14159265) / 360;
destination.row = target.row + 1000 * sin (x);
destination.column = target.column + 1000 * cos (x);
[self branchTerrain:tokenType fromLocation:target toLocation:destination];
//[self growTerrain:tokenType fromLocation:target toLocation:0:0];
//[self growTerrain:tokenType fromLocation:target toLocation:49:79];
//[self growTerrain:tokenType fromLocation:target toLocation:49:0];
//[self growTerrain:tokenType fromLocation:target toLocation:destrow:destcol];
}
else
{
switch (brushType)
{
case 0:
[map setToken:tokenType atLocation:target];
break;
default:
[map set3x3TokensTo:tokenType aroundLocation:target];
break;
}
}
}
//----------------------------------------------------------------------
- (void) mouseUp:(unsigned int)modifierFlags atLocation:(EMMapLocation)target
{
}
//----------------------------------------------------------------------
- (void) rightMouseDown:(unsigned int)modifierFlags atLocation:(EMMapLocation)target
{
}
//----------------------------------------------------------------------
- (void) rightMouseUp:(unsigned int)modifierFlags atLocation:(EMMapLocation)target
{
}
//----------------------------------------------------------------------
- (void) keyDown:(NSEvent *)theEvent
{
}
//======================================================================
// Window Delegate
//======================================================================
- (void) windowDidBecomeKey:(NSNotification *)notification
{
}
//----------------------------------------------------------------------
- (void) windowDidResignKey:(NSNotification *)notification
{
}
//----------------------------------------------------------------------
- (void) windowWillMiniaturize:(NSNotification *)notification
{
NSWindow *theWindow = [notification object];
[theWindow setAutodisplay:NO];
}
//----------------------------------------------------------------------
- (void) windowDidDeminiaturize:(NSNotification *)notification
{
NSWindow *theWindow = [notification object];
[theWindow setAutodisplay:YES];
[theWindow displayIfNeeded];
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.