00001 // 00002 // GameView.m 00003 // MacTrek 00004 // 00005 // Created by Aqua on 02/06/2006. 00006 // Copyright 2006 __MyCompanyName__. All rights reserved. 00007 // 00008 00009 #import "GameView.h" 00010 00011 00012 @implementation GameView 00013 00014 - (void) awakeFromNib { 00015 00016 [super awakeFromNib]; 00017 00018 step = GV_SCALE_STEP; 00019 keyMap = nil; 00020 //universe = nil; this will kill the universe that has been set 00021 // since i cannot predict the sequence in which the obects are awoken 00022 scale = 40; // default 00023 trigonometry = [LLTrigonometry defaultInstance]; 00024 // $$ temp linked to Netrek needs to go through selection mechanism.. 00025 painter = [[PainterFactoryForNetrek alloc] init]; 00026 angleConvertor = [[Entity alloc] init]; 00027 busyDrawing = NO; 00028 } 00029 00030 - (NSPoint) gamePointRepresentingCentreOfView { 00031 return [[universe playerThatIsMe] predictedPosition]; 00032 } 00033 00034 - (void) setScaleFullView { 00035 NSSize viewSize = [self bounds].size; 00036 00037 int minSize = (viewSize.height < viewSize.width ? viewSize.height : viewSize.width); 00038 00039 // minSize must cover UNIVERSE_PIXEL_SIZE thus zoom is 00040 scale = UNIVERSE_PIXEL_SIZE / minSize; 00041 } 00042 00043 - (void) setScale:(int)newScale { 00044 scale = newScale; 00045 } 00046 00047 - (int) scale { 00048 return scale; 00049 } 00050 00051 // draw the view 00052 - (void)drawRect:(NSRect)aRect { 00053 00054 // sometimes the drawing takes so long the timer invokes another go 00055 // maybe should use locks.. 00056 if (busyDrawing) { 00057 NSLog(@"GameView.drawRect busy drawing"); 00058 return; 00059 } 00060 busyDrawing = YES; 00061 00062 // during this draw i want to lock universal access (try for 200ms) 00063 if ([[universe synchronizeAccess] lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]) { 00064 00065 // first set up the gamebounds based on my position 00066 NSRect gameBounds = [painter gameRectAround:[self gamePointRepresentingCentreOfView] 00067 forView:[self bounds] 00068 withScale:scale]; 00069 // then draw it 00070 [painter drawRect:aRect 00071 ofViewBounds:[self bounds] 00072 whichRepresentsGameBounds:gameBounds 00073 withScale:scale]; 00074 00075 [[universe synchronizeAccess] unlock]; 00076 } else { 00077 NSLog(@"GameView.drawRect waited %d seconds for lock, discarding lock"); 00078 00079 // first set up the gamebounds based on my position 00080 NSRect gameBounds = [painter gameRectAround:[self gamePointRepresentingCentreOfView] 00081 forView:[self bounds] 00082 withScale:scale]; 00083 // then draw it 00084 [painter drawRect:aRect 00085 ofViewBounds:[self bounds] 00086 whichRepresentsGameBounds:gameBounds 00087 withScale:scale]; 00088 00089 // no lock obtained, so no need to unlock 00090 } 00091 busyDrawing = NO; 00092 } 00093 00094 - (void) setKeyMap:(MTKeyMap *)newKeyMap { 00095 keyMap = newKeyMap; 00096 } 00097 00098 // view functions 00099 - (void) keyDown:(NSEvent *)theEvent { 00100 00101 if (keyMap == nil) { 00102 NSLog(@"GameView.keyDown have no keymap"); 00103 [super keyDown:theEvent]; 00104 return; 00105 } 00106 00107 // check all characters in the event 00108 NSString *characters = [theEvent characters]; 00109 00110 for (int i = 0; i < [characters length]; i++) { 00111 unichar theChar = [characters characterAtIndex:i]; 00112 unsigned int modifierFlags = [theEvent modifierFlags]; 00113 00114 int action = [keyMap actionForKey:theChar withModifierFlags:modifierFlags]; 00115 00116 // only valid keys 00117 if (action == ACTION_UNKNOWN) { 00118 [super keyDown:theEvent]; 00119 } 00120 else { 00121 if ([self performAction: action] == NO) { 00122 [super keyDown:theEvent]; 00123 } 00124 } 00125 } 00126 } 00127 00128 // mouse events $$ currently static allocated 00129 - (void) mouseDown:(NSEvent *)theEvent { 00130 [self performAction:ACTION_FIRE_TORPEDO]; 00131 } 00132 00133 - (void) otherMouseDown:(NSEvent *)theEvent { 00134 [self performAction:ACTION_FIRE_PHASER]; 00135 } 00136 00137 - (void) dummyMouseAction { 00138 00139 // must become the first responder, 00140 // or a arbitrary view crashes... 00141 [[self window] makeKeyWindow]; 00142 [self becomeFirstResponder]; 00143 00144 NSEvent *evt = [NSEvent mouseEventWithType:NSRightMouseDown 00145 location:[painter centreOfRect:[self bounds]] 00146 modifierFlags:256 00147 timestamp:GetCurrentEventTime() 00148 windowNumber:[[self window] windowNumber] 00149 context:[NSGraphicsContext currentContext] 00150 eventNumber:0 00151 clickCount:1 00152 pressure:0]; 00153 00154 [[NSApplication sharedApplication] postEvent:evt atStart:YES]; 00155 } 00156 00157 - (void) rightMouseDown:(NSEvent *)theEvent { 00158 00159 [self performAction:ACTION_SET_COURSE]; 00160 } 00161 00162 - (void) scrollWheel:(NSEvent *)theEvent { 00163 float mouseRole = [theEvent deltaY]; 00164 // 1.0 means zoom in 00165 // -1.0 means zoom out 00166 if (mouseRole > 0) { 00167 // zoom in means smaller scale factor 00168 int newScale = scale - step*scale; 00169 if (newScale == scale) { 00170 newScale = scale - 1; // at least 1 00171 } 00172 // if scale is small it may not be possible to zoom in 00173 // scale*step = 0; 00174 if (newScale > [painter minScale]) { 00175 scale = newScale; 00176 } 00177 } else { 00178 // zoom out means larger factor 00179 // if scale is small it may not be possible to zoom out 00180 // scale*step = 0 this means you'll stay zoomed in 00181 int newScale = scale + step*scale; 00182 if (newScale == scale) { 00183 newScale = scale + 1; // at least 1 00184 } 00185 if (newScale < [painter maxScale]) { 00186 scale = newScale; 00187 } 00188 } 00189 NSLog(@"GameView.scrollWheel setting scale to %d", scale); 00190 } 00191 00192 00193 - (void) sendSpeedReq:(int)speed { 00194 if (universe == nil) { 00195 NSLog(@"GameView.sendSpeedReq have no universe?"); 00196 } 00197 00198 int maxSpeed = [[[universe playerThatIsMe] ship] maxSpeed]; 00199 if (speed > maxSpeed) { 00200 speed = maxSpeed; 00201 } 00202 00203 [[universe playerThatIsMe] setRequestedSpeed:speed]; // store here so we can track accelertaion in the future 00204 [notificationCenter postNotificationName:@"COMM_SEND_SPEED_REQ" userInfo:[NSNumber numberWithInt:speed]]; 00205 } 00206 00207 - (float) mouseDir { 00208 00209 NSPoint mouseLocation = [self mousePos]; 00210 00211 // we are at the center 00212 NSPoint ourLocation; 00213 NSRect bounds = [self bounds]; 00214 ourLocation.x = bounds.size.width / 2; 00215 ourLocation.y = bounds.size.height / 2; 00216 00217 // the direction is 00218 float dir = [trigonometry angleDegBetween:mouseLocation andPoint:ourLocation]; 00219 dir -= 90; // north is 0 deg 00220 if (dir < 0) { 00221 dir += 360; 00222 } 00223 //NSLog(@"GameView.mouseDir = %f", dir); 00224 00225 return dir; 00226 } 00227 00228 // $$ may need locks here if the code becomes multi threaded 00229 - (bool) performAction:(int) action { 00230 00231 //NSLog(@"GameView.performAction performing action %d", action); 00232 00233 int maxSpeed, speed; 00234 Player *target = nil; 00235 Planet *planet = nil; 00236 NSPoint targetGamePoint; 00237 Player *me = [universe playerThatIsMe]; // IS used sometimes, compiler just complains.. 00238 00239 switch (action) { 00240 case ACTION_UNKNOWN: 00241 NSLog(@"GameView.performAction unknown action %d", action); 00242 return NO; 00243 break; 00244 case ACTION_CLOAK: 00245 if ([[universe playerThatIsMe] flags] & PLAYER_CLOAK) { 00246 [notificationCenter postNotificationName:@"COMM_SEND_CLOAK_REQ" userInfo:[NSNumber numberWithBool:NO]]; 00247 } else { 00248 [notificationCenter postNotificationName:@"COMM_SEND_CLOAK_REQ" userInfo:[NSNumber numberWithBool:YES]]; 00249 } 00250 break; 00251 case ACTION_DET_ENEMY: 00252 // $$ JTrek checks if current time - last det time > 100 ms before sending this again 00253 // sounds sensible... 00254 [notificationCenter postNotificationName:@"COMM_SEND_DETONATE_REQ" userInfo:nil]; 00255 break; 00256 case ACTION_DET_OWN: // detting ALL 00257 [notificationCenter postNotificationName:@"COMM_SEND_DET_MINE_ALL_REQ" userInfo:nil]; 00258 break; 00259 case ACTION_FIRE_PLASMA: 00260 // $$ we can check if we are able to fire plasmas at all before poluting the network 00261 [angleConvertor setCourse:[self mouseDir]]; 00262 [notificationCenter postNotificationName:@"COMM_SEND_PLASMA_REQ" userInfo:[NSNumber numberWithChar:[angleConvertor netrekFormatCourse]]]; 00263 break; 00264 case ACTION_FIRE_TORPEDO: 00265 [angleConvertor setCourse:[self mouseDir]]; 00266 [notificationCenter postNotificationName:@"COMM_SEND_TORPS_REQ" userInfo:[NSNumber numberWithChar:[angleConvertor netrekFormatCourse]]]; 00267 break; 00268 case ACTION_FIRE_PHASER: 00269 [angleConvertor setCourse:[self mouseDir]]; 00270 [notificationCenter postNotificationName:@"COMM_SEND_PHASER_REQ" userInfo:[NSNumber numberWithChar:[angleConvertor netrekFormatCourse]]]; 00271 break; 00272 case ACTION_SHIELDS: 00273 if ([me flags] & PLAYER_SHIELD) { 00274 [notificationCenter postNotificationName:@"COMM_SEND_SHIELD_REQ" userInfo:[NSNumber numberWithBool:NO]]; 00275 } else { 00276 [notificationCenter postNotificationName:@"COMM_SEND_SHIELD_REQ" userInfo:[NSNumber numberWithBool:YES]]; 00277 } 00278 break; 00279 case ACTION_TRACTOR: 00280 // convert the mouse pointer to a point in the game grid 00281 targetGamePoint = [painter gamePointFromViewPoint:[self mousePos] 00282 viewRect:[self bounds] 00283 gamePosInCentreOfView:[self gamePointRepresentingCentreOfView] 00284 withScale:scale]; 00285 // find the nearest player 00286 target = [universe playerNearPosition:targetGamePoint ofType:UNIVERSE_TARG_PLAYER]; 00287 00288 // if we are already tracktoring/pressoring, disable 00289 if ([[universe playerThatIsMe] flags] & PLAYER_TRACT) { 00290 // $$ it is probaly not needed to figure out the correct playerId.. 00291 [notificationCenter postNotificationName:@"COMM_SEND_TRACTOR_OFF_REQ" 00292 userInfo:[NSNumber numberWithInt:[target playerId]]]; 00293 } else { 00294 [notificationCenter postNotificationName:@"COMM_SEND_TRACTOR_ON_REQ" 00295 userInfo:[NSNumber numberWithInt:[target playerId]]]; 00296 [[universe playerThatIsMe] setTractorTarget:target]; 00297 } 00298 break; 00299 case ACTION_PRESSOR: 00300 // convert the mouse pointer to a point in the game grid 00301 targetGamePoint = [painter gamePointFromViewPoint:[self mousePos] 00302 viewRect:[self bounds] 00303 gamePosInCentreOfView:[self gamePointRepresentingCentreOfView] 00304 withScale:scale]; 00305 // find the nearest player 00306 target = [universe playerNearPosition:targetGamePoint ofType:UNIVERSE_TARG_PLAYER]; 00307 00308 // if we are already tracktoring/pressoring, disable 00309 if ([[universe playerThatIsMe] flags] & PLAYER_PRESS) { 00310 // $$ it is probaly not needed to figure out the correct playerId.. 00311 [notificationCenter postNotificationName:@"COMM_SEND_REPRESSOR_OFF_REQ" 00312 userInfo:[NSNumber numberWithInt:[target playerId]]]; 00313 } else { 00314 [notificationCenter postNotificationName:@"COMM_SEND_REPRESSOR_ON_REQ" 00315 userInfo:[NSNumber numberWithInt:[target playerId]]]; 00316 // $$ bit strange, but i assume we cannot pressor one target and tractor another at 00317 // the same time 00318 [[universe playerThatIsMe] setTractorTarget:target]; 00319 } 00320 break; 00321 case ACTION_WARP_0: 00322 [self sendSpeedReq:0]; 00323 break; 00324 case ACTION_WARP_1: 00325 [self sendSpeedReq:1]; 00326 break; 00327 case ACTION_WARP_2: 00328 [self sendSpeedReq:2]; 00329 break; 00330 case ACTION_WARP_3: 00331 [self sendSpeedReq:3]; 00332 break; 00333 case ACTION_WARP_4: 00334 [self sendSpeedReq:4]; 00335 break; 00336 case ACTION_WARP_5: 00337 [self sendSpeedReq:5]; 00338 break; 00339 case ACTION_WARP_6: 00340 [self sendSpeedReq:6]; 00341 break; 00342 case ACTION_WARP_7: 00343 [self sendSpeedReq:7]; 00344 break; 00345 case ACTION_WARP_8: 00346 [self sendSpeedReq:8]; 00347 break; 00348 case ACTION_WARP_9: 00349 [self sendSpeedReq:9]; 00350 break; 00351 case ACTION_WARP_10: 00352 [self sendSpeedReq:10]; 00353 break; 00354 case ACTION_WARP_11: 00355 [self sendSpeedReq:11]; 00356 break; 00357 case ACTION_WARP_12: 00358 [self sendSpeedReq:0]; 00359 break; 00360 case ACTION_WARP_MAX: 00361 maxSpeed = [[[universe playerThatIsMe] ship] maxSpeed]; 00362 [self sendSpeedReq:maxSpeed]; 00363 break; 00364 case ACTION_WARP_HALF_MAX: 00365 maxSpeed = [[[universe playerThatIsMe] ship] maxSpeed]; 00366 [self sendSpeedReq:maxSpeed / 2]; 00367 break; 00368 case ACTION_WARP_INCREASE: 00369 speed = [[universe playerThatIsMe] speed]; 00370 [self sendSpeedReq:speed + 1]; 00371 break; 00372 case ACTION_WARP_DECREASE: 00373 speed = [[universe playerThatIsMe] speed]; 00374 [self sendSpeedReq:speed - 1]; 00375 break; 00376 case ACTION_SET_COURSE: 00377 [angleConvertor setCourse:[self mouseDir]]; 00378 [notificationCenter postNotificationName:@"COMM_SEND_DIR_REQ" userInfo:[NSNumber numberWithChar:[angleConvertor netrekFormatCourse]]]; 00379 // remove the planet lock 00380 [me setFlags:[me flags] & ~(PLAYER_PLOCK | PLAYER_PLLOCK)]; 00381 break; 00382 case ACTION_LOCK: 00383 // convert the mouse pointer to a point in the game grid 00384 targetGamePoint = [painter gamePointFromViewPoint:[self mousePos] 00385 viewRect:[self bounds] 00386 gamePosInCentreOfView:[self gamePointRepresentingCentreOfView] 00387 withScale:scale]; 00388 // find the nearest player 00389 target = [universe playerNearPosition:targetGamePoint ofType:UNIVERSE_TARG_PLAYER]; 00390 // find the nearest planet 00391 planet = [universe planetNearPosition:targetGamePoint]; 00392 // lock on the closest 00393 if ([universe entity:target closerToPos:targetGamePoint than:planet]) { 00394 // lock on player 00395 [notificationCenter postNotificationName:@"COMM_SEND_PLAYER_LOCK_REQ" 00396 userInfo:[NSNumber numberWithInt:[target playerId]]]; 00397 [me setPlayerLock:target]; 00398 } else { 00399 // lock on planet 00400 [notificationCenter postNotificationName:@"COMM_SEND_PLANET_LOCK_REQ" 00401 userInfo:[NSNumber numberWithInt:[planet planetId]]]; 00402 [me setPlanetLock:planet]; 00403 } 00404 break; 00405 case ACTION_PRACTICE_BOT: 00406 [notificationCenter postNotificationName:@"COMM_SEND_PRACTICE_REQ"]; 00407 break; 00408 case ACTION_TRANSWARP: 00409 // netrek uses the same message for this, it could lead to very 00410 // funny results now we seperate it. 00411 [notificationCenter postNotificationName:@"COMM_SEND_PRACTICE_REQ"]; 00412 break; 00413 case ACTION_BOMB: 00414 if (([me flags] & PLAYER_BOMB) == 0) { // already bombing? 00415 [notificationCenter postNotificationName:@"COMM_SEND_BOMB_REQ" 00416 userInfo:[NSNumber numberWithBool:YES]]; 00417 } 00418 break; 00419 case ACTION_ORBIT: 00420 [notificationCenter postNotificationName:@"COMM_SEND_ORBIT_REQ" 00421 userInfo:[NSNumber numberWithBool:YES]]; 00422 break; 00423 case ACTION_BEAM_DOWN: 00424 if (([me flags] & PLAYER_BEAMDOWN) == 0) { // already beaming? 00425 [notificationCenter postNotificationName:@"COMM_SEND_BEAM_REQ" 00426 userInfo:[NSNumber numberWithBool:NO]]; // no means down 00427 } 00428 break; 00429 case ACTION_BEAM_UP: 00430 if (([me flags] & PLAYER_BEAMUP) == 0) { // already beaming? 00431 [notificationCenter postNotificationName:@"COMM_SEND_BEAM_REQ" 00432 userInfo:[NSNumber numberWithBool:YES]]; // no means down 00433 } 00434 break; 00435 case ACTION_DISTRESS_CALL: 00436 NSLog(@"GameView.performAction Marcro's not implemented"); // $$ todo 00437 break; 00438 case ACTION_ARMIES_CARRIED_REPORT: 00439 NSLog(@"GameView.performAction Marcro's not implemented"); // $$ todo 00440 break; 00441 case ACTION_MESSAGE: 00442 NSLog(@"GameView.performAction MESSAGE not implemented"); // $$ todo 00443 break; 00444 case ACTION_DOCK_PERMISSION: 00445 if (([me flags] & PLAYER_DOCKOK) == 0) { // toggle 00446 [notificationCenter postNotificationName:@"COMM_SEND_DOCK_REQ" 00447 userInfo:[NSNumber numberWithBool:YES]]; 00448 } else { 00449 [notificationCenter postNotificationName:@"COMM_SEND_DOCK_REQ" 00450 userInfo:[NSNumber numberWithBool:NO]]; 00451 } 00452 break; 00453 case ACTION_INFO: 00454 // convert the mouse pointer to a point in the game grid 00455 targetGamePoint = [painter gamePointFromViewPoint:[self mousePos] 00456 viewRect:[self bounds] 00457 gamePosInCentreOfView:[self gamePointRepresentingCentreOfView] 00458 withScale:scale]; 00459 // find the nearest player 00460 target = [universe playerNearPosition:targetGamePoint ofType:(UNIVERSE_TARG_PLAYER | UNIVERSE_TARG_SELF)]; 00461 // find the nearest planet 00462 planet = [universe planetNearPosition:targetGamePoint]; 00463 // lock on the closest 00464 if ([universe entity:target closerToPos:targetGamePoint than:planet]) { 00465 // toggle info on player 00466 [target setShowInfo:![target showInfo]]; 00467 } else { 00468 [planet setShowInfo:![planet showInfo]]; 00469 } 00470 break; 00471 case ACTION_REFIT: 00472 // swich input do REFIT mode, probably need something similar for 00473 // message, we could generate a pop-up though... 00474 NSLog(@"GameView.performAction REFIT not implemented"); // $$ todo 00475 break; 00476 case ACTION_REPAIR: 00477 // $$ no toggle ? 00478 [notificationCenter postNotificationName:@"COMM_SEND_REPAIR_REQ" 00479 userInfo:[NSNumber numberWithBool:YES]]; 00480 break; 00481 case ACTION_QUIT: 00482 // $$ how do we quit ? send BYE, reset some data and flip to the outfit 00483 // or go through state machine to main menu... 00484 break; 00485 case ACTION_HELP: 00486 NSLog(@"GameView.performAction HELP not implemented"); // $$ todo 00487 case ACTION_DEBUG: 00488 [painter setDebugLabels:![painter debugLabels]]; 00489 break; 00490 default: 00491 NSLog(@"GameView.performAction unknown action %d", action); 00492 return NO; 00493 break; 00494 } 00495 return YES; 00496 } 00497 00498 @end