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.