Line data Source code
1 0 : /*
2 :
3 : WormholeEntity.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #import "WormholeEntity.h"
26 :
27 : #import "ShipEntity.h"
28 : #import "OOSunEntity.h"
29 : #import "OOPlanetEntity.h"
30 : #import "PlayerEntity.h"
31 : #import "ShipEntityLoadRestore.h"
32 :
33 : #import "Universe.h"
34 : #import "AI.h"
35 : #import "OORoleSet.h"
36 : #import "OOShipRegistry.h"
37 : #import "OOShipGroup.h"
38 : #import "OOStringParsing.h"
39 : #import "OOCollectionExtractors.h"
40 : #import "OOLoggingExtended.h"
41 : #import "OOSystemDescriptionManager.h"
42 :
43 0 : #define OO_WORMHOLE_COLOR_BOOST 25.0
44 0 : #define OO_WORMHOLE_COLOR_FVEC4 { 0.067, 0.067, 1.0, 0.25 }
45 :
46 : // Hidden interface
47 : @interface WormholeEntity (Private)
48 :
49 0 : -(id) init;
50 :
51 : @end
52 :
53 : // Static local functions
54 0 : static void DrawWormholeCorona(GLfloat inner_radius, GLfloat outer_radius, int step, GLfloat z_distance, GLfloat *col4v1);
55 :
56 :
57 : @implementation WormholeEntity (Private)
58 :
59 : -(id) init
60 : {
61 : if ((self = [super init]))
62 : {
63 : witch_mass = 0.0;
64 : shipsInTransit = [[NSMutableArray arrayWithCapacity:4] retain];
65 : collision_radius = 0.0;
66 : [self setStatus:STATUS_EFFECT];
67 : scanClass = CLASS_WORMHOLE;
68 : isWormhole = YES;
69 : scan_info = WH_SCANINFO_NONE;
70 : scan_time = 0;
71 : hasExitPosition = NO;
72 : containsPlayer = NO;
73 : exit_speed = 50.0;
74 : }
75 : return self;
76 : }
77 :
78 : @end // Private interface implementation
79 :
80 :
81 : //
82 : // Public Wormhole Implementation
83 : //
84 :
85 : @implementation WormholeEntity
86 :
87 : - (WormholeEntity*)initWithDict:(NSDictionary*)dict
88 : {
89 : assert(dict != nil);
90 :
91 : if ((self = [self init]))
92 : {
93 : NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
94 :
95 : // wormholes from pre-1.80 savegames using "origin_seed" and "dest_seed"
96 : // currently get defaults set; will probably disappear unnoticed
97 : origin = [dict oo_intForKey:@"origin_id" defaultValue:0];
98 : destination = [dict oo_intForKey:@"dest_id" defaultValue:255];
99 :
100 : originCoords = [[UNIVERSE systemManager] getCoordinatesForSystem:origin inGalaxy:[PLAYER galaxyNumber]];
101 : destinationCoords = [[UNIVERSE systemManager] getCoordinatesForSystem:destination inGalaxy:[PLAYER galaxyNumber]];
102 :
103 : // We only ever init from dictionary if we're loaded by the player, so
104 : // by definition we have been scanned
105 : scan_info = WH_SCANINFO_SCANNED;
106 :
107 : // Remember, times are stored as Ship Clock - but anything
108 : // saving/restoring wormholes from dictionaries should know this!
109 : expiry_time = [dict oo_doubleForKey:@"expiry_time"];
110 : arrival_time = [dict oo_doubleForKey:@"arrival_time"];
111 : // just in case an old save game has one with crossed times
112 : if (expiry_time > arrival_time)
113 : {
114 : expiry_time = arrival_time - 1.0;
115 : }
116 :
117 : // Since this is new for 1.75.1, we must give it a default values as we could be loading an old savegame
118 : estimated_arrival_time = [dict oo_doubleForKey:@"estimated_arrival_time" defaultValue:arrival_time];
119 : position = [dict oo_hpvectorForKey:@"position"];
120 : _misjump = [dict oo_boolForKey:@"misjump" defaultValue:NO];
121 :
122 :
123 : // Setup shipsInTransit
124 : NSArray * shipDictsArray = [dict oo_arrayForKey:@"ships"];
125 : NSEnumerator *shipDicts = [shipDictsArray objectEnumerator];
126 : NSDictionary *currShipDict = nil;
127 : [shipsInTransit removeAllObjects];
128 : NSMutableDictionary *restoreContext = [NSMutableDictionary dictionary];
129 :
130 : while ((currShipDict = [shipDicts nextObject]) != nil)
131 : {
132 : NSDictionary *shipInfo = [currShipDict oo_dictionaryForKey:@"ship_info"];
133 : if (shipInfo != nil)
134 : {
135 : ShipEntity *ship = [ShipEntity shipRestoredFromDictionary:shipInfo
136 : useFallback:YES
137 : context:restoreContext];
138 : if (ship != nil)
139 : {
140 : [shipsInTransit addObject:[NSDictionary dictionaryWithObjectsAndKeys:
141 : ship, @"ship",
142 : [currShipDict objectForKey:@"time_delta"], @"time",
143 : nil]];
144 : }
145 : else
146 : {
147 : OOLog(@"wormhole.load.warning", @"Wormhole ship \"%@\" failed to initialize - missing OXP or old-style saved wormhole data.", [shipInfo oo_stringForKey:@"ship_key"]);
148 : }
149 : }
150 : }
151 : [pool release];
152 : }
153 : return self;
154 : }
155 :
156 : - (WormholeEntity*) initWormholeTo:(OOSystemID) s fromShip:(ShipEntity *) ship
157 : {
158 : assert(ship != nil);
159 :
160 : if ((self = [self init]))
161 : {
162 : double now = [PLAYER clockTimeAdjusted];
163 : double distance;
164 : OOSunEntity *sun = [UNIVERSE sun];
165 :
166 : _misjump = NO;
167 : origin = [UNIVERSE currentSystemID];
168 : destination = s;
169 : originCoords = [PLAYER galaxy_coordinates];
170 : destinationCoords = [[UNIVERSE systemManager] getCoordinatesForSystem:destination inGalaxy:[PLAYER galaxyNumber]];
171 : distance = distanceBetweenPlanetPositions(originCoords.x, originCoords.y, destinationCoords.x, destinationCoords.y);
172 : distance = fmax(distance, 0.1);
173 : witch_mass = 200000.0; // MKW 2010.11.21 - originally the ship's mass was added twice - once here and once in suckInShip. Instead, we give each wormhole a minimum mass.
174 : if ([ship isPlayer])
175 : witch_mass += [ship mass]; // The player ship never gets sucked in, so add its mass here.
176 :
177 : if (sun && ([sun willGoNova] || [sun goneNova]) && [ship mass] > 240000)
178 : shrink_factor = [ship mass] / 240000; // don't allow longstanding wormholes in nova systems. (60 sec * WORMHOLE_SHRINK_RATE = 240 000)
179 : else
180 : shrink_factor = 1;
181 :
182 : collision_radius = 0.5 * M_PI * pow(witch_mass, 1.0/3.0);
183 : expiry_time = now + (witch_mass / WORMHOLE_SHRINK_RATE / shrink_factor);
184 : travel_time = (distance * distance * 3600); // Taken from PlayerEntity.h
185 : arrival_time = now + travel_time;
186 : estimated_arrival_time = arrival_time;
187 :
188 : /* There are a number of bugs where the arrival time is < than the
189 : * expiry time (i.e. both ends open at once).
190 : * Rather than try to flatten all of them, having been unsuccessful twice
191 : * it seems easier to declare as a matter of wormhole physics that it
192 : * _can't_ be open at both ends at once. - CIM: 13/12/12 */
193 : if (expiry_time > arrival_time)
194 : {
195 : expiry_time = arrival_time - 1.0;
196 : }
197 : position = [ship position];
198 : zero_distance = HPdistance2([PLAYER position], position);
199 : }
200 : return self;
201 : }
202 :
203 :
204 : - (void) setMisjump
205 : {
206 : // Test for misjump first - it's entirely possibly that the wormhole
207 : // has already been marked for misjumping when another ship enters it.
208 : if (!_misjump)
209 : {
210 : double distance = distanceBetweenPlanetPositions(originCoords.x, originCoords.y, destinationCoords.x, destinationCoords.y);
211 : double time_adjust = distance * distance * (3600 - 2700); // NB: Time adjustment is calculated using original distance. Formula matches the one in [PlayerEntity witchJumpTo]
212 : arrival_time -= time_adjust;
213 : travel_time -= time_adjust;
214 : destinationCoords.x = (originCoords.x + destinationCoords.x) / 2;
215 : destinationCoords.y = (originCoords.y + destinationCoords.y) / 2;
216 : _misjump = YES;
217 : }
218 : }
219 :
220 :
221 : - (void) setMisjumpWithRange:(GLfloat)range
222 : {
223 : if (range <= 0.0 || range >= 1.0)
224 : {
225 : range = 0.5; // for safety, though nothing should be setting this
226 : }
227 : _misjumpRange = range;
228 : // Test for misjump first - it's entirely possibly that the wormhole
229 : // has already been marked for misjumping when another ship enters it.
230 : if (!_misjump)
231 : {
232 : double distance = distanceBetweenPlanetPositions(originCoords.x, originCoords.y, destinationCoords.x, destinationCoords.y);
233 : double time_adjust = (distance * (1-_misjumpRange))*(distance * (1-_misjumpRange))*3600.0;
234 : // time adjustment ensures that misjumps not faster than normal jumps
235 : // formulae for time and distance by mwerle at http://developer.berlios.de/pm/task.php?func=detailtask&project_task_id=4703&group_id=3577&group_project_id=1753
236 : arrival_time -= time_adjust;
237 : travel_time -= time_adjust;
238 :
239 : destinationCoords.x = (originCoords.x * (1-_misjumpRange)) + (destinationCoords.x * _misjumpRange);
240 : destinationCoords.y = (originCoords.y * (1-_misjumpRange)) + (destinationCoords.y * _misjumpRange);
241 : _misjump = YES;
242 : }
243 : }
244 :
245 :
246 : - (BOOL) withMisjump
247 : {
248 : return _misjump;
249 : }
250 :
251 :
252 : - (GLfloat) misjumpRange
253 : {
254 : return _misjumpRange;
255 : }
256 :
257 :
258 : - (BOOL) suckInShip:(ShipEntity *) ship
259 : {
260 : if (!ship || [ship status] == STATUS_ENTERING_WITCHSPACE)
261 : {
262 : return NO;
263 : }
264 : if (origin != [UNIVERSE currentSystemID])
265 : {
266 : // if we're no longer in the origin system, can't suck in
267 : return NO;
268 : }
269 : if ([PLAYER galaxy_coordinates].x != originCoords.x || [PLAYER galaxy_coordinates].y != originCoords.y)
270 : {
271 : // if we're no longer at the origin coordinates, can't suck in (handles interstellar space case)
272 : return NO;
273 : }
274 : double now = [PLAYER clockTimeAdjusted];
275 :
276 : /* CIM: removed test. Not valid for wormholes which last longer than their travel time. Most likely for short distances e.g. zero-distance doubles. equal_seeds test above should cover it, with expiry_time test for safety. */
277 : /* if (now > arrival_time)
278 : return NO; // far end of the wormhole! */
279 : if( now > expiry_time )
280 : return NO;
281 : // MKW 2010.11.18 - calculate time it takes for ship to reach wormhole
282 : // This is for AI ships which get told to enter the wormhole even though they
283 : // may still be some distance from it when the player exits the system
284 : float d = HPdistance(position, [ship position]);
285 : d -= [ship collisionRadius] + [self collisionRadius];
286 : if (d > 0.0f)
287 : {
288 : float afterburnerFactor = [ship hasFuelInjection] && [ship fuel] > MIN_FUEL ? [ship afterburnerFactor] : 1.0;
289 : float shipSpeed = [ship maxFlightSpeed] * afterburnerFactor;
290 : // MKW 2011.02.27 - calculate speed based on group leader, if any, to
291 : // try and prevent escorts from entering the wormhole before their mother.
292 : ShipEntity *leader = [[ship group] leader];
293 : if (leader && (leader != ship))
294 : {
295 : afterburnerFactor = [leader hasFuelInjection] && [leader fuel] > MIN_FUEL ? [leader afterburnerFactor] : 1.0;
296 : float leaderShipSpeed = [leader maxFlightSpeed] * afterburnerFactor;
297 : if (leaderShipSpeed < shipSpeed ) shipSpeed = leaderShipSpeed;
298 : }
299 : if (shipSpeed <= 0.0f ) shipSpeed = 0.1f;
300 : now += d / shipSpeed;
301 : if( now > expiry_time )
302 : {
303 : return NO;
304 : }
305 : }
306 :
307 : [shipsInTransit addObject:[NSDictionary dictionaryWithObjectsAndKeys:
308 : ship, @"ship",
309 : [NSNumber numberWithDouble: now + travel_time - arrival_time], @"time",
310 : [ship beaconCode], @"shipBeacon", // in case a beacon code has been set, nil otherwise
311 : nil]];
312 : witch_mass += [ship mass];
313 : expiry_time = now + (witch_mass / WORMHOLE_SHRINK_RATE / shrink_factor);
314 : // and, again, cap to be earlier than arrival time
315 : if (expiry_time > arrival_time)
316 : {
317 : expiry_time = arrival_time - 1.0;
318 : }
319 :
320 : collision_radius = 0.5 * M_PI * pow(witch_mass, 1.0/3.0);
321 :
322 : [UNIVERSE addWitchspaceJumpEffectForShip:ship];
323 :
324 : // Should probably pass the wormhole, but they have no JS representation
325 : [ship setStatus:STATUS_ENTERING_WITCHSPACE];
326 : [ship doScriptEvent:OOJSID("shipWillEnterWormhole")];
327 : [[ship getAI] message:@"ENTERED_WITCHSPACE"];
328 :
329 : [UNIVERSE removeEntity:ship];
330 : [[ship getAI] clearStack]; // get rid of any preserved states
331 :
332 : if ([ship isStation])
333 : {
334 : if ([PLAYER dockedStation] == (StationEntity*)ship)
335 : {
336 : // the carrier has jumped while the player is docked
337 : [ship retain];
338 : [UNIVERSE carryPlayerOn:(StationEntity*)ship inWormhole:self];
339 : [ship release];
340 : }
341 : }
342 :
343 : return YES;
344 : }
345 :
346 :
347 : - (void) disgorgeShips
348 : {
349 : double now = [PLAYER clockTimeAdjusted];
350 : NSMutableArray* shipsStillInTransit = [[NSMutableArray alloc] initWithCapacity:[shipsInTransit count]];
351 : BOOL hasShiftedExitPosition = NO;
352 : BOOL useExitXYScatter = NO;
353 :
354 : NSDictionary *shipInfo = nil;
355 : foreach (shipInfo, shipsInTransit)
356 : {
357 : ShipEntity *ship = [shipInfo objectForKey:@"ship"];
358 : NSString *shipBeacon = [shipInfo objectForKey:@"shipBeacon"];
359 : double ship_arrival_time = arrival_time + [shipInfo oo_doubleForKey:@"time"];
360 : double time_passed = now - ship_arrival_time;
361 :
362 : if ([ship status] == STATUS_DEAD) continue; // skip dead ships.
363 :
364 : if (ship_arrival_time > now)
365 : {
366 : [shipsStillInTransit addObject:shipInfo];
367 : }
368 : else
369 : {
370 : // Only calculate exit position once so that all ships arrive from the same point
371 : if (!hasExitPosition)
372 : {
373 : position = [UNIVERSE getWitchspaceExitPosition]; // no need to reset PRNG.
374 : GLfloat min_d1 = [UNIVERSE safeWitchspaceExitDistance];
375 : Quaternion q1;
376 : quaternion_set_random(&q1);
377 : double d1 = SCANNER_MAX_RANGE*((ranrot_rand() % 256)/256.0 - 0.5);
378 : Vector v1 = vector_forward_from_quaternion(q1);
379 : if (dot_product(v1,kBasisZVector) < -0.99)
380 : {
381 : // a bit more safe distance if right behind the buoy
382 : min_d1 *= 3.0;
383 : }
384 :
385 : if (fabs(d1) < min_d1) // no closer than 750m to edge of buoy
386 : {
387 : d1 += ((d1 > 0.0)? min_d1: -min_d1);
388 : }
389 : position.x += v1.x * d1; // randomise exit position
390 : position.y += v1.y * d1;
391 : position.z += v1.z * d1;
392 : }
393 :
394 : if (hasExitPosition && (!containsPlayer || useExitXYScatter))
395 : {
396 : HPVector shippos;
397 : Vector exit_vector_x = vector_right_from_quaternion([UNIVERSE getWitchspaceExitRotation]);
398 : Vector exit_vector_y = vector_up_from_quaternion([UNIVERSE getWitchspaceExitRotation]);
399 : // entry wormhole has a radius of around 100m (or perhaps more)
400 : // so randomise exit positions slightly too for second and subsequent ships
401 : // helps avoid collisions when two ships enter wormhole at same time
402 : double offset_x = randf()*150.0-75.0;
403 : double offset_y = randf()*150.0-75.0;
404 : shippos.x = position.x + (offset_x*exit_vector_x.x)+(offset_y*exit_vector_y.x);
405 : shippos.y = position.y + (offset_x*exit_vector_x.y)+(offset_y*exit_vector_y.y);
406 : shippos.z = position.z + (offset_x*exit_vector_x.z)+(offset_y*exit_vector_y.z);
407 : [ship setPosition:shippos];
408 : }
409 : else
410 : {
411 : // this is the first ship out of the wormhole
412 : [self setExitSpeed:[ship maxFlightSpeed]*WORMHOLE_LEADER_SPEED_FACTOR];
413 : if (containsPlayer)
414 : { // reset the player's speed to the new speed
415 : [PLAYER setSpeed:exit_speed];
416 : }
417 : useExitXYScatter = YES;
418 : [ship setPosition:position];
419 : }
420 :
421 : if (shipBeacon != nil)
422 : {
423 : [ship setBeaconCode:shipBeacon];
424 : }
425 :
426 : // Don't reduce bounty on misjump. Fixes #17992
427 : // - MKW 2011.03.10
428 : if (!_misjump) [ship setBounty:[ship bounty]/2 withReason:kOOLegalStatusReasonNewSystem]; // adjust legal status for new system
429 :
430 : // now the cargo is defined in advance, this is unnecessary
431 : /* if ([ship cargoFlag] == CARGO_FLAG_FULL_PLENTIFUL)
432 : {
433 : [ship setCargoFlag: CARGO_FLAG_FULL_SCARCE];
434 : }*/
435 :
436 : if (time_passed < 2.0)
437 : {
438 : [ship witchspaceLeavingEffects]; // adds the ship to the universe with effects.
439 : }
440 : else
441 : {
442 : // arrived 2 seconds or more before the player. Rings have faded out.
443 : [ship setOrientation: [UNIVERSE getWitchspaceExitRotation]];
444 : [ship setPitch: 0.0];
445 : [ship setRoll: 0.0];
446 : [ship setVelocity: kZeroVector];
447 : [UNIVERSE addEntity:ship]; // AI and status get initialised here
448 : }
449 : [ship setSpeed:[self exitSpeed]]; // all ships from this wormhole have same velocity
450 :
451 : // awaken JS-based AIs
452 : [ship doScriptEvent:OOJSID("aiStarted")];
453 :
454 : // Wormholes now have a JS representation, so we could provide it
455 : // but is it worth it for the exit wormhole?
456 : [ship doScriptEvent:OOJSID("shipExitedWormhole") andReactToAIMessage:@"EXITED WITCHSPACE"];
457 :
458 : // update the ships's position
459 : if (!hasExitPosition)
460 : {
461 : hasExitPosition = YES;
462 : hasShiftedExitPosition = YES; // exitPosition is shifted towards the lead ship update position.
463 : [ship update: time_passed]; // do this only for one ship or the next ships might appear at very different locations.
464 : position = [ship position]; // e.g. when the player docks first before following, time_passed is already > 10 minutes.
465 : }
466 : else if (time_passed > 1) // Only update the ship position if it was some time ago, otherwise we're in 'real time'.
467 : {
468 : if (hasShiftedExitPosition)
469 : {
470 : // only update the time delay to the lead ship. Sign is not correct but updating gives a small spacial distribution.
471 : [ship update: (ship_arrival_time - arrival_time)];
472 : }
473 : else
474 : {
475 : // Exit position was externally set, e.g. by player ship following through this wormhole.
476 : // Use the real time difference.
477 : [ship update:time_passed];
478 : }
479 : }
480 : }
481 : }
482 : [shipsInTransit release];
483 : shipsInTransit = shipsStillInTransit;
484 :
485 : if (containsPlayer)
486 : {
487 : // ships exiting the wormhole after now are following the player
488 : // so appear behind them
489 : position = HPvector_add([PLAYER position], vectorToHPVector(vector_multiply_scalar([PLAYER forwardVector], -500.0f)));
490 : containsPlayer = NO;
491 : }
492 : // else, the wormhole doesn't now (or never) contained the player, so
493 : // no need to move it
494 : }
495 :
496 :
497 : - (void) setContainsPlayer:(BOOL)val
498 : {
499 : containsPlayer = val;
500 : }
501 :
502 :
503 : - (void) setExitPosition:(HPVector)pos
504 : {
505 : [self setPosition: pos];
506 : hasExitPosition = YES;
507 : }
508 :
509 : - (OOSystemID) origin
510 : {
511 : return origin;
512 : }
513 :
514 : - (OOSystemID) destination
515 : {
516 : return destination;
517 : }
518 :
519 : - (NSPoint) originCoordinates
520 : {
521 : return originCoords;
522 : }
523 :
524 : - (NSPoint) destinationCoordinates
525 : {
526 : return destinationCoords;
527 : }
528 :
529 : - (double) exitSpeed
530 : {
531 : return exit_speed;
532 : }
533 :
534 :
535 : - (void) setExitSpeed:(double) speed
536 : {
537 : exit_speed = speed;
538 : }
539 :
540 :
541 : - (double) expiryTime
542 : {
543 : return expiry_time;
544 : }
545 :
546 : - (double) arrivalTime
547 : {
548 : return arrival_time;
549 : }
550 :
551 : - (double) estimatedArrivalTime
552 : {
553 : return estimated_arrival_time;
554 : }
555 :
556 : - (double) travelTime
557 : {
558 : return travel_time;
559 : }
560 :
561 : - (double) scanTime
562 : {
563 : return scan_time;
564 : }
565 :
566 : - (BOOL) isScanned
567 : {
568 : return scan_info > WH_SCANINFO_NONE;
569 : }
570 :
571 : - (void) setScannedAt:(double)p_scanTime
572 : {
573 : if( scan_info == WH_SCANINFO_NONE )
574 : {
575 : scan_time = p_scanTime;
576 : scan_info = WH_SCANINFO_SCANNED;
577 : }
578 : // else we previously scanned this wormhole
579 : }
580 :
581 : - (WORMHOLE_SCANINFO) scanInfo
582 : {
583 : return scan_info;
584 : }
585 :
586 : - (void) setScanInfo:(WORMHOLE_SCANINFO)p_scanInfo
587 : {
588 : scan_info = p_scanInfo;
589 : }
590 :
591 : - (NSArray*) shipsInTransit
592 : {
593 : return shipsInTransit;
594 : }
595 :
596 0 : - (void) dealloc
597 : {
598 : [shipsInTransit release];
599 :
600 : [super dealloc];
601 : }
602 :
603 :
604 0 : - (NSString *) descriptionComponents
605 : {
606 : double now = [PLAYER clockTime];
607 : return [NSString stringWithFormat:@"destination: %@ ttl: %.2fs arrival: %@",
608 : _misjump ? (NSString *)@"Interstellar Space" : [UNIVERSE getSystemName:destination],
609 : expiry_time - now,
610 : ClockToString(arrival_time, false)];
611 : }
612 :
613 :
614 : - (NSString *) identFromShip:(ShipEntity*)ship
615 : {
616 : if ([ship hasEquipmentItem:@"EQ_WORMHOLE_SCANNER"])
617 : {
618 : if ([self scanInfo] >= WH_SCANINFO_DESTINATION)
619 : {
620 : return [NSString stringWithFormat:DESC(@"wormhole-to-@"), [UNIVERSE getSystemName:destination]];
621 : }
622 : else
623 : {
624 : return DESC(@"wormhole-desc");
625 : }
626 : }
627 : else
628 : {
629 : OOLogERR(kOOLogInconsistentState, @"%@", @"Wormhole identified when ship has no EQ_WORMHOLE_SCANNER.");
630 : /*
631 : This was previously an assertion, but a player reported hitting it.
632 : http://aegidian.org/bb/viewtopic.php?p=128110#p128110
633 : -- Ahruman 2011-01-27
634 : */
635 : return nil;
636 : }
637 :
638 : }
639 :
640 :
641 0 : - (BOOL) canCollide
642 : {
643 : /* Correct test for far end of wormhole */
644 : if (origin != [UNIVERSE currentSystemID])
645 : {
646 : // if we're no longer in the origin system, can't suck in
647 : return NO;
648 : }
649 : if ([PLAYER galaxy_coordinates].x != originCoords.x || [PLAYER galaxy_coordinates].y != originCoords.y)
650 : {
651 : // if we're no longer at the origin coordinates, can't suck in (handles interstellar space case)
652 : return NO;
653 : }
654 :
655 : return (witch_mass > 0.0);
656 : }
657 :
658 :
659 0 : - (BOOL) checkCloseCollisionWith:(Entity *)other
660 : {
661 : return ![other isEffect];
662 : }
663 :
664 :
665 0 : - (void) update:(OOTimeDelta) delta_t
666 : {
667 : [super update:delta_t];
668 :
669 : PlayerEntity *player = PLAYER;
670 : assert(player != nil);
671 : rotMatrix = OOMatrixForBillboard(position, [player viewpointPosition]);
672 : double now = [player clockTimeAdjusted];
673 :
674 : if (witch_mass > 0.0)
675 : {
676 :
677 : witch_mass -= WORMHOLE_SHRINK_RATE * delta_t * shrink_factor;
678 : witch_mass = fmax(witch_mass, 0.0);
679 : collision_radius = 0.5 * M_PI * pow(witch_mass, 1.0/3.0);
680 : no_draw_distance = collision_radius * collision_radius * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR;
681 : }
682 :
683 : scanClass = (witch_mass > 0.0)? CLASS_WORMHOLE : CLASS_NO_DRAW;
684 :
685 : if (now > expiry_time)
686 : {
687 : scanClass = CLASS_NO_DRAW; // witch_mass not certain to be limiting factor on extremely short jumps, so make sure now
688 :
689 : // If we're a saved wormhole waiting to disgorge more ships, it's safe
690 : // to remove self from UNIVERSE, but we need the current position!
691 : [UNIVERSE removeEntity: self];
692 : }
693 : }
694 :
695 :
696 0 : - (void) drawImmediate:(bool)immediate translucent:(bool)translucent
697 : {
698 : if ([UNIVERSE breakPatternHide])
699 : return; // DON'T DRAW DURING BREAK PATTERN
700 :
701 : if (cam_zero_distance > no_draw_distance)
702 : return; // TOO FAR AWAY TO SEE
703 :
704 : if (witch_mass <= 0.0)
705 : return;
706 :
707 : if (collision_radius <= 0.0)
708 : return;
709 :
710 : if ([self scanClass] == CLASS_NO_DRAW)
711 : return;
712 :
713 : if (translucent)
714 : {
715 : // for now, a simple copy of the energy bomb draw routine
716 : float srzd = sqrt(cam_zero_distance);
717 :
718 : GLfloat color_fv[4] = OO_WORMHOLE_COLOR_FVEC4;
719 :
720 : OOSetOpenGLState(OPENGL_STATE_TRANSLUCENT_PASS);
721 : OOGL(glDisable(GL_CULL_FACE));
722 : OOGL(glEnable(GL_BLEND));
723 :
724 : OOGL(glColor4fv(color_fv));
725 : OOGLBEGIN(GL_TRIANGLE_FAN);
726 : GLDrawBallBillboard(0.45 * collision_radius, 4, srzd);
727 : OOGLEND();
728 :
729 : color_fv[3] = fmin(color_fv[3] * 2.0, 1.0);
730 : DrawWormholeCorona(0.45 * collision_radius, collision_radius, 4, srzd, color_fv);
731 :
732 : OOGL(glEnable(GL_CULL_FACE));
733 : OOGL(glDisable(GL_BLEND));
734 : }
735 :
736 : OOVerifyOpenGLState();
737 : OOCheckOpenGLErrors(@"WormholeEntity after drawing %@", self);
738 : }
739 :
740 :
741 0 : static void DrawWormholeCorona(GLfloat inner_radius, GLfloat outer_radius, int step, GLfloat z_distance, GLfloat *col4v1)
742 : {
743 : if (outer_radius >= z_distance) // inside the sphere
744 : return;
745 : int i;
746 :
747 : NSRange activity = { 0.34, 1.0 };
748 :
749 : GLfloat s0, c0, s1, c1;
750 :
751 : GLfloat r0, r1;
752 : GLfloat rv0, rv1, q;
753 :
754 : GLfloat theta, delta, halfStep;
755 :
756 : r0 = outer_radius * z_distance / sqrt(z_distance * z_distance - outer_radius * outer_radius);
757 : r1 = inner_radius * z_distance / sqrt(z_distance * z_distance - inner_radius * inner_radius);
758 :
759 : delta = step * M_PI / 180.0f;
760 : halfStep = 0.5f * delta;
761 : theta = 0.0f;
762 :
763 : OOGLBEGIN(GL_TRIANGLE_STRIP);
764 : for (i = 0; i < 360; i += step )
765 : {
766 : rv0 = randf();
767 : rv1 = randf();
768 :
769 : q = activity.location + rv0 * activity.length;
770 :
771 : s0 = r0 * sin(theta);
772 : c0 = r0 * cos(theta);
773 : glColor4f(col4v1[0] * q, col4v1[1] * q, col4v1[2] * q, col4v1[3] * rv0);
774 : glVertex3f(s0, c0, 0.0);
775 :
776 : s1 = r1 * sin(theta - halfStep) * 0.5 * (1.0 + rv1);
777 : c1 = r1 * cos(theta - halfStep) * 0.5 * (1.0 + rv1);
778 : glColor4f(col4v1[0] * OO_WORMHOLE_COLOR_BOOST, col4v1[1] * OO_WORMHOLE_COLOR_BOOST, col4v1[2] * OO_WORMHOLE_COLOR_BOOST, col4v1[3] * rv0);
779 : glVertex3f(s1, c1, 0.0);
780 :
781 : theta += delta;
782 : }
783 : // repeat last values to close
784 : rv0 = randf();
785 : rv1 = randf();
786 :
787 : q = activity.location + rv0 * activity.length;
788 :
789 : s0 = 0.0f; // r0 * sin(0);
790 : c0 = r0; // r0 * cos(0);
791 : glColor4f(col4v1[0] * q, col4v1[1] * q, col4v1[2] * q, col4v1[3] * rv0);
792 : glVertex3f(s0, c0, 0.0);
793 :
794 : s1 = r1 * sin(halfStep) * 0.5 * (1.0 + rv1);
795 : c1 = r1 * cos(halfStep) * 0.5 * (1.0 + rv1);
796 : glColor4f(col4v1[0] * OO_WORMHOLE_COLOR_BOOST, col4v1[1] * OO_WORMHOLE_COLOR_BOOST, col4v1[2] * OO_WORMHOLE_COLOR_BOOST, col4v1[3] * rv0);
797 : glVertex3f(s1, c1, 0.0);
798 : OOGLEND();
799 : }
800 :
801 : - (NSDictionary *) getDict
802 : {
803 : NSMutableDictionary *myDict = [NSMutableDictionary dictionary];
804 :
805 : [myDict oo_setInteger:origin forKey:@"origin_id"];
806 : [myDict oo_setInteger:destination forKey:@"dest_id"];
807 : [myDict setObject:StringFromPoint(originCoords) forKey:@"origin_coords"];
808 : [myDict setObject:StringFromPoint(destinationCoords) forKey:@"dest_coords"];
809 : // Anything converting a wormhole to a dictionary should already have
810 : // modified its time to shipClock time
811 : [myDict oo_setFloat:(expiry_time) forKey:@"expiry_time"];
812 : [myDict oo_setFloat:(arrival_time) forKey:@"arrival_time"];
813 : [myDict oo_setFloat:(estimated_arrival_time) forKey:@"estimated_arrival_time"];
814 : [myDict oo_setHPVector:position forKey:@"position"];
815 : [myDict oo_setBool:_misjump forKey:@"misjump"];
816 :
817 : NSMutableArray * shipArray = [NSMutableArray arrayWithCapacity:[shipsInTransit count]];
818 : NSEnumerator * ships = [shipsInTransit objectEnumerator];
819 : NSDictionary * currShipDict = nil;
820 : NSMutableDictionary *context = [NSMutableDictionary dictionary];
821 : while ((currShipDict = [ships nextObject]) != nil)
822 : {
823 : id ship = [currShipDict objectForKey:@"ship"];
824 : [shipArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:
825 : [NSNumber numberWithDouble:[currShipDict oo_doubleForKey:@"time"]], @"time_delta",
826 : [ship savedShipDictionaryWithContext:context], @"ship_info",
827 : nil]];
828 : }
829 : [myDict setObject:shipArray forKey:@"ships"];
830 :
831 : return myDict;
832 : }
833 :
834 0 : - (NSString *) scanInfoString
835 : {
836 : switch(scan_info)
837 : {
838 : case WH_SCANINFO_NONE: return @"WH_SCANINFO_NONE";
839 : case WH_SCANINFO_SCANNED: return @"WH_SCANINFO_SCANNED";
840 : case WH_SCANINFO_COLLAPSE_TIME: return @"WH_SCANINFO_COLLAPSE_TIME";
841 : case WH_SCANINFO_ARRIVAL_TIME: return @"WH_SCANINFO_ARRIVAL_TIME";
842 : case WH_SCANINFO_DESTINATION: return @"WH_SCANINFO_DESTINATION";
843 : case WH_SCANINFO_SHIP: return @"WH_SCANINFO_SHIP";
844 : }
845 : return @"WH_SCANINFO_UNDEFINED"; // should never get here
846 : }
847 :
848 0 : - (void)dumpSelfState
849 : {
850 : [super dumpSelfState];
851 : OOLog(@"dumpState.wormholeEntity", @"Origin : %@", [UNIVERSE getSystemName:origin]);
852 : OOLog(@"dumpState.wormholeEntity", @"Destination : %@", [UNIVERSE getSystemName:destination]);
853 : OOLog(@"dumpState.wormholeEntity", @"Expiry Time : %@", ClockToString(expiry_time, false));
854 : OOLog(@"dumpState.wormholeEntity", @"Arrival Time : %@", ClockToString(arrival_time, false));
855 : OOLog(@"dumpState.wormholeEntity", @"Projected Arrival Time : %@", ClockToString(estimated_arrival_time, false));
856 : OOLog(@"dumpState.wormholeEntity", @"Scanned Time : %@", ClockToString(scan_time, false));
857 : OOLog(@"dumpState.wormholeEntity", @"Scanned State : %@", [self scanInfoString]);
858 :
859 : OOLog(@"dumpState.wormholeEntity", @"Mass : %.2lf", witch_mass);
860 : OOLog(@"dumpState.wormholeEntity", @"Ships : %ld", [shipsInTransit count]);
861 : unsigned i;
862 : for (i = 0; i < [shipsInTransit count]; ++i)
863 : {
864 : NSDictionary *shipDict = [shipsInTransit oo_dictionaryAtIndex:i];
865 : ShipEntity* ship = (ShipEntity*)[shipDict objectForKey:@"ship"];
866 : double ship_arrival_time = arrival_time + [shipDict oo_doubleForKey:@"time"];
867 : OOLog(@"dumpState.wormholeEntity.ships", @"Ship %d: %@ mass %.2f arrival time %@", i+1, ship, [ship mass], ClockToString(ship_arrival_time, false));
868 : }
869 : }
870 :
871 : @end
|