Oolite
Loading...
Searching...
No Matches
PlayerEntity.m
Go to the documentation of this file.
1/*
2
3PlayerEntity.m
4
5Oolite
6Copyright (C) 2004-2013 Giles C Williams and contributors
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21MA 02110-1301, USA.
22
23*/
24
25#include <assert.h>
26
27#import "PlayerEntity.h"
31#import "PlayerEntitySound.h"
33
34#import "StationEntity.h"
35#import "OOSunEntity.h"
36#import "OOPlanetEntity.h"
37#import "WormholeEntity.h"
38#import "ProxyPlayerEntity.h"
40#import "OOLaserShotEntity.h"
41#import "OOMesh.h"
42
43#import "OOMaths.h"
44#import "GameController.h"
45#import "ResourceManager.h"
46#import "Universe.h"
47#import "AI.h"
48#import "ShipEntityAI.h"
49#import "MyOpenGLView.h"
50#import "OOTrumble.h"
52#import "OOSound.h"
53#import "OOColor.h"
54#import "Octree.h"
55#import "OOCacheManager.h"
56#import "OOOXZManager.h"
57#import "OOStringExpander.h"
58#import "OOStringParsing.h"
59#import "OOPListParsing.h"
61#import "OOConstToString.h"
62#import "OOTexture.h"
63#import "OORoleSet.h"
64#import "HeadUpDisplay.h"
66#import "OOMusicController.h"
68#import "OOShipRegistry.h"
69#import "OOEquipmentType.h"
72#import "OODebugSupport.h"
73
74#import "CollisionRegion.h"
75
76#import "OOJSScript.h"
77#import "OOScriptTimer.h"
81#import "OOConstToJSString.h"
82
83#import "OOJoystickManager.h"
88
89
90#define PLAYER_DEFAULT_NAME @"Jameson"
91
92enum
93{
94 // If comm log is kCommLogTrimThreshold or more lines long, it will be cut to kCommLogTrimSize.
97};
98
99
100static NSString * const kOOLogBuyMountedOK = @"equip.buy.mounted";
101static NSString * const kOOLogBuyMountedFailed = @"equip.buy.mounted.failed";
102static float const kDeadResetTime = 30.0f;
103
105static GLfloat sBaseMass = 0.0;
106
107NSComparisonResult marketSorterByName(id a, id b, void *market);
108NSComparisonResult marketSorterByPrice(id a, id b, void *market);
109NSComparisonResult marketSorterByQuantity(id a, id b, void *market);
110NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
111
112
113@interface PlayerEntity (OOPrivate)
114
116- (void) doTradeIn:(OOCreditsQuantity)tradeInValue forPriceFactor:(double)priceFactor;
117
118// Subs of update:
119- (void) updateMovementFlags;
120- (void) updateAlertCondition;
121- (void) updateFuelScoops:(OOTimeDelta)delta_t;
122- (void) updateClocks:(OOTimeDelta)delta_t;
124- (void) updateTrumbles:(OOTimeDelta)delta_t;
125- (void) performAutopilotUpdates:(OOTimeDelta)delta_t;
126- (void) performInFlightUpdates:(OOTimeDelta)delta_t;
127- (void) performWitchspaceCountdownUpdates:(OOTimeDelta)delta_t;
128- (void) performWitchspaceExitUpdates:(OOTimeDelta)delta_t;
129- (void) performLaunchingUpdates:(OOTimeDelta)delta_t;
130- (void) performDockingUpdates:(OOTimeDelta)delta_t;
131- (void) performDeadUpdates:(OOTimeDelta)delta_t;
132- (void) gameOverFadeToBW;
133- (void) updateTargeting;
134- (void) showGameOver;
135- (void) updateWormholes;
136
138- (BOOL) checkEntityForMassLock:(Entity *)ent withScanClass:(int)scanClass;
139
140
141// Shopping
143- (void) showMarketScreenDataLine:(OOGUIRow)row forGood:(OOCommodityType)good inMarket:(OOCommodityMarket *)localMarket holdQuantity:(OOCargoQuantity)quantity;
145
146
147- (BOOL) tryBuyingItem:(NSString *)eqKey;
148
149// Cargo & passenger contracts
150- (NSArray*) contractsListForScriptingFromArray:(NSArray *)contractsArray forCargo:(BOOL)forCargo;
151
152
153- (void) prepareMarkedDestination:(NSMutableDictionary *)markers :(NSDictionary *)marker;
154
155- (void) witchStart;
156- (void) witchJumpTo:(OOSystemID)sTo misjump:(BOOL)misjump;
157- (void) witchEnd;
158
159// Jump distance/cost calculations for selected target.
160- (double) hyperspaceJumpDistance;
162
163- (void) noteCompassLostTarget;
164
165
166
167@end
168
169
170@interface ShipEntity (Hax)
171
172- (id) initBypassForPlayer;
173
174@end
175
176
177@implementation PlayerEntity
178
179+ (PlayerEntity *) sharedPlayer
180{
181 if (EXPECT_NOT(gOOPlayer == nil))
182 {
183 gOOPlayer = [[PlayerEntity alloc] init];
184 }
185 return gOOPlayer;
186}
187
188
189- (void) setName:(NSString *)inName
190{
191 // Block super method; player ship can't be renamed.
192}
193
194
195- (GLfloat) baseMass
196{
197 if (sBaseMass <= 0.0)
198 {
199 // First call with initialised mass (in [UNIVERSE setUpInitialUniverse]) is always to the cobra 3, even when starting with a savegame.
200 if ([self mass] > 0.0) // bootstrap the base mass.
201 {
202 OOLog(@"fuelPrices", @"Setting Cobra3 base mass to: %.2f ", [self mass]);
203 sBaseMass = [self mass];
204 }
205 else
206 {
207 // This happened on startup when [UNIVERSE setUpSpace] was called before player init, inside [UNIVERSE setUpInitialUniverse].
208 OOLog(@"fuelPrices", @"%@", @"Player ship not initialised properly yet, using precalculated base mass.");
209 return 185580.0;
210 }
211 }
212
213 return sBaseMass;
214}
215
216
217- (void) unloadAllCargoPodsForType:(OOCommodityType)type toManifest:(OOCommodityMarket *) manifest
218{
219 NSInteger i, cargoCount = [cargo count];
220 if (cargoCount == 0) return;
221
222 // step through the cargo pods adding in the quantities
223 for (i = cargoCount - 1; i >= 0 ; i--)
224 {
225 ShipEntity *cargoItem = [cargo objectAtIndex:i];
226 NSString * commodityType = [cargoItem commodityType];
227 if (commodityType == nil || [commodityType isEqualToString:type])
228 {
229 if ([commodityType isEqualToString:type])
230 {
231 // transfer
232 [manifest addQuantity:[cargoItem commodityAmount] forGood:type];
233 }
234 else // undefined
235 {
236 OOLog(@"player.badCargoPod", @"Cargo pod %@ has bad commodity type, rejecting.", cargoItem);
237 continue;
238 }
239 [cargo removeObjectAtIndex:i];
240 }
241 }
242}
243
244
245- (void) unloadCargoPodsForType:(OOCommodityType)type amount:(OOCargoQuantity)quantity
246{
247 NSInteger i, n_cargo = [cargo count];
248 if (n_cargo == 0) return;
249
250 ShipEntity *cargoItem = nil;
251 OOCommodityType co_type;
252 OOCargoQuantity amount;
253 OOCargoQuantity cargoToGo = quantity;
254
255 // step through the cargo pods removing pods or quantities
256 for (i = n_cargo - 1; (i >= 0 && cargoToGo > 0) ; i--)
257 {
258 cargoItem = [cargo objectAtIndex:i];
259 co_type = [cargoItem commodityType];
260 if (co_type == nil || [co_type isEqualToString:type])
261 {
262 if ([co_type isEqualToString:type])
263 {
264 amount = [cargoItem commodityAmount];
265 if (amount <= cargoToGo)
266 {
267 [cargo removeObjectAtIndex:i];
268 cargoToGo -= amount;
269 }
270 else
271 {
272 // we only need to remove a part of the cargo to meet our target
273 [cargoItem setCommodity:co_type andAmount:(amount - cargoToGo)];
274 cargoToGo = 0;
275
276 }
277 }
278 else // undefined
279 {
280 OOLog(@"player.badCargoPod", @"Cargo pod %@ has bad commodity type (COMMODITY_UNDEFINED), rejecting.", cargoItem);
281 continue;
282 }
283 }
284 }
285
286 // now check if we are ready. When not, proceed with quantities in the manifest.
287 if (cargoToGo > 0)
288 {
289 [shipCommodityData removeQuantity:cargoToGo forGood:type];
290 }
291}
292
293
294- (void) unloadCargoPods
295{
296 NSAssert([self isDocked], @"Cannot unload cargo pods unless docked.");
297
298 /* loads commodities from the cargo pods onto the ship's manifest */
299 NSString *good = nil;
300 foreach (good, [shipCommodityData goods])
301 {
302 [self unloadAllCargoPodsForType:good toManifest:shipCommodityData];
303 }
304#ifndef NDEBUG
305 if ([cargo count] > 0)
306 {
307 OOLog(@"player.unloadCargo",@"Cargo remains in pods after unloading - %@",cargo);
308 }
309#endif
310
311 [self calculateCurrentCargo]; // work out the correct value for current_cargo
312}
313
314
315// TODO: better feedback on the log as to why failing to create player cargo pods causes a CTD?
316- (void) createCargoPodWithType:(OOCommodityType)type andAmount:(OOCargoQuantity)amount
317{
318 ShipEntity *container = [UNIVERSE newShipWithRole:@"1t-cargopod"];
319 if (container)
320 {
321 [container setScanClass: CLASS_CARGO];
322 [container setStatus:STATUS_IN_HOLD];
323 [container setCommodity:type andAmount:amount];
324 [cargo addObject:container];
325 [container release];
326 }
327 else
328 {
329 OOLogERR(@"player.loadCargoPods.noContainer", @"%@", @"couldn't create a container in [PlayerEntity loadCargoPods]");
330 // throw an exception here...
331 [NSException raise:OOLITE_EXCEPTION_FATAL
332 format:@"[PlayerEntity loadCargoPods] failed to create a container for cargo with role 'cargopod'"];
333 }
334}
335
336
337- (void) loadCargoPodsForType:(OOCommodityType)type fromManifest:(OOCommodityMarket *) manifest
338{
339 // load commodities from the ships manifest into individual cargo pods
340 unsigned j;
341
342 OOCargoQuantity quantity = [manifest quantityForGood:type];
343 OOMassUnit units = [manifest massUnitForGood:type];
344
345 if (quantity > 0)
346 {
347 if (units == UNITS_TONS)
348 {
349 // easy case
350 for (j = 0; j < quantity; j++)
351 {
352 [self createCargoPodWithType:type andAmount:1]; // or CTD if unsuccesful (!)
353 }
354 [manifest setQuantity:0 forGood:type];
355 }
356 else
357 {
358 OOCargoQuantity podsRequiredForQuantity, amountToLoadInCargopod, tmpQuantity;
359 // reserve up to 1/2 ton of each commodity for the safe
360 if (units == UNITS_KILOGRAMS)
361 {
362 if (quantity <= MAX_KILOGRAMS_IN_SAFE)
363 {
364 tmpQuantity = quantity;
365 quantity = 0;
366 }
367 else
368 {
369 tmpQuantity = MAX_KILOGRAMS_IN_SAFE;
370 quantity -= tmpQuantity;
371 }
372 amountToLoadInCargopod = KILOGRAMS_PER_POD;
373 }
374 else
375 {
376 if (quantity <= MAX_GRAMS_IN_SAFE) {
377 tmpQuantity = quantity;
378 quantity = 0;
379 }
380 else
381 {
382 tmpQuantity = MAX_GRAMS_IN_SAFE;
383 quantity -= tmpQuantity;
384 }
385 amountToLoadInCargopod = GRAMS_PER_POD;
386 }
387 if (quantity > 0)
388 {
389 podsRequiredForQuantity = 1 + (quantity/amountToLoadInCargopod);
390 // this check is needed so that initial quantities like 1499kg or 1499999g
391 // do not result in generation of an empty cargopod
392 if (quantity % amountToLoadInCargopod == 0) podsRequiredForQuantity--;
393
394 // put each ton or part-ton beyond that in a separate container
395 for (j = 0; j < podsRequiredForQuantity; j++)
396 {
397 if (amountToLoadInCargopod > quantity)
398 {
399 // last pod gets the dregs. :)
400 amountToLoadInCargopod = quantity;
401 }
402 [self createCargoPodWithType:type andAmount:amountToLoadInCargopod]; // or CTD if unsuccesful (!)
403 quantity -= amountToLoadInCargopod;
404 }
405 // adjust manifest for this commodity
406 [manifest setQuantity:tmpQuantity forGood:type];
407 }
408 }
409 }
410}
411
412
413- (void) loadCargoPodsForType:(OOCommodityType)type amount:(OOCargoQuantity)quantity
414{
415 OOMassUnit unit = [shipCommodityData massUnitForGood:type];
416
417 while (quantity)
418 {
419 if (unit != UNITS_TONS)
420 {
421 int amount_per_container = (unit == UNITS_KILOGRAMS)? KILOGRAMS_PER_POD : GRAMS_PER_POD;
422 while (quantity > 0)
423 {
424 int smaller_quantity = 1 + ((quantity - 1) % amount_per_container);
425 if ([cargo count] < [self maxAvailableCargoSpace])
426 {
427 ShipEntity* container = [UNIVERSE newShipWithRole:@"1t-cargopod"];
428 if (container)
429 {
430 // the cargopod ship is just being set up. If ejected, will call UNIVERSE addEntity
431 [container setStatus:STATUS_IN_HOLD];
432 [container setScanClass: CLASS_CARGO];
433 [container setCommodity:type andAmount:smaller_quantity];
434 [cargo addObject:container];
435 [container release];
436 }
437 }
438 else
439 {
440 // try to squeeze any surplus, up to half a ton, in the manifest.
441 int amount = [shipCommodityData quantityForGood:type] + smaller_quantity;
442 if (amount > MAX_GRAMS_IN_SAFE && unit == UNITS_GRAMS) amount = MAX_GRAMS_IN_SAFE;
443 else if (amount > MAX_KILOGRAMS_IN_SAFE && unit == UNITS_KILOGRAMS) amount = MAX_KILOGRAMS_IN_SAFE;
444
445 [shipCommodityData setQuantity:amount forGood:type];
446 }
447 quantity -= smaller_quantity;
448 }
449 }
450 else
451 {
452 // put each ton in a separate container
453 while (quantity)
454 {
455 if ([cargo count] < [self maxAvailableCargoSpace])
456 {
457 ShipEntity* container = [UNIVERSE newShipWithRole:@"1t-cargopod"];
458 if (container)
459 {
460 // the cargopod ship is just being set up. If ejected, will call UNIVERSE addEntity
461 [container setScanClass: CLASS_CARGO];
462 [container setStatus:STATUS_IN_HOLD];
463 [container setCommodity:type andAmount:1];
464 [cargo addObject:container];
465 [container release];
466 }
467 }
468 quantity--;
469 }
470 }
471 }
472}
473
474
475- (void) loadCargoPods
476{
477 /* loads commodities from the ships manifest into individual cargo pods */
478 NSString *good = nil;
479 foreach (good, [shipCommodityData goods])
480 {
481 [self loadCargoPodsForType:good fromManifest:shipCommodityData];
482 }
483 [self calculateCurrentCargo]; // work out the correct value for current_cargo
484 cargo_dump_time = 0;
485}
486
487
488- (OOCommodityMarket *) shipCommodityData
489{
490 return shipCommodityData;
491}
492
493
494- (OOCreditsQuantity) deciCredits
495{
496 return credits;
497}
498
499
500- (int) random_factor
501{
502 return market_rnd;
503}
504
505
506- (void) setRandom_factor:(int)rf
507{
508 market_rnd = rf;
509}
510
511
512- (OOGalaxyID) galaxyNumber
513{
514 return galaxy_number;
515}
516
517
518- (NSPoint) galaxy_coordinates
519{
520 return galaxy_coordinates;
521}
522
523
524- (void) setGalaxyCoordinates:(NSPoint)newPosition
525{
526 galaxy_coordinates.x = newPosition.x;
527 galaxy_coordinates.y = newPosition.y;
528}
529
530
531- (NSPoint) cursor_coordinates
532{
533 return cursor_coordinates;
534}
535
536
537- (NSPoint) chart_centre_coordinates
538{
539 return chart_centre_coordinates;
540}
541
542
543- (OOScalar) chart_zoom
544{
545 if(_missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_SHORT ||
546 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_SHORT_ANA_QUICKEST ||
547 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_SHORT_ANA_SHORTEST)
548 {
549 return 1.0;
550 }
551 else if(_missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG ||
552 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST ||
553 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST)
554 {
555 return CHART_MAX_ZOOM;
556 }
557 else if(_missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_CUSTOM ||
558 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_QUICKEST ||
559 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_SHORTEST)
560 {
561 return custom_chart_zoom;
562 }
563 return chart_zoom;
564}
565
566- (OOScalar) custom_chart_zoom
567{
568 return custom_chart_zoom;
569}
570
571- (void) setCustomChartZoom:(OOScalar)zoom
572{
573 custom_chart_zoom = zoom;
574}
575
576
577- (NSPoint) custom_chart_centre_coordinates
578{
579 return custom_chart_centre_coordinates;
580}
581
582
583- (void) setCustomChartCentre:(NSPoint)coords
584{
585 custom_chart_centre_coordinates.x = coords.x;
586 custom_chart_centre_coordinates.y = coords.y;
587}
588
589
590- (NSPoint) adjusted_chart_centre
591{
592 NSPoint acc; // adjusted chart centre
593 double scroll_pos; // cursor coordinate at which we'd want to scoll chart in the direction we're currently considering
594 double ecc; // chart centre coordinate we'd want if the cursor was on the edge of the galaxy in the current direction
595
596 if(_missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_SHORT ||
597 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_SHORT_ANA_QUICKEST ||
598 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_SHORT_ANA_SHORTEST)
599 {
600 return galaxy_coordinates;
601 }
602 else if(_missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG ||
603 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST ||
604 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST)
605 {
606 return NSMakePoint(128.0, 128.0);
607 }
608 else if (_missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_CUSTOM ||
609 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_QUICKEST ||
610 _missionBackgroundSpecial == GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_SHORTEST)
611 {
612 return custom_chart_centre_coordinates;
613 }
614 // When fully zoomed in we want to centre chart on chart_centre_coordinates. When zoomed out we want the chart centred on
615 // (128.0, 128.0) so the galaxy fits the screen width. For intermediate zoom we interpolate.
616 acc.x = chart_centre_coordinates.x + (128.0 - chart_centre_coordinates.x) * (chart_zoom - 1.0) / (CHART_MAX_ZOOM - 1.0);
617 acc.y = chart_centre_coordinates.y + (128.0 - chart_centre_coordinates.y) * (chart_zoom - 1.0) / (CHART_MAX_ZOOM - 1.0);
618
619 // If the cursor is out of the centre non-scrolling part of the screen adjust the chart centre. If the cursor is just at scroll_pos
620 // we want to return the chart centre as it is, but if it's at the edge of the galaxy we want the centre positioned so the cursor is
621 // at the edge of the screen
622 if (chart_focus_coordinates.x - acc.x <= -CHART_SCROLL_AT_X*chart_zoom)
623 {
624 scroll_pos = acc.x - CHART_SCROLL_AT_X*chart_zoom;
625 ecc = CHART_WIDTH_AT_MAX_ZOOM*chart_zoom / 2.0;
626 if (scroll_pos <= 0)
627 {
628 acc.x = ecc;
629 }
630 else
631 {
632 acc.x = ((scroll_pos-chart_focus_coordinates.x)*ecc + chart_focus_coordinates.x*acc.x)/scroll_pos;
633 }
634 }
635 else if (chart_focus_coordinates.x - acc.x >= CHART_SCROLL_AT_X*chart_zoom)
636 {
637 scroll_pos = acc.x + CHART_SCROLL_AT_X*chart_zoom;
638 ecc = 256.0 - CHART_WIDTH_AT_MAX_ZOOM*chart_zoom / 2.0;
639 if (scroll_pos >= 256.0)
640 {
641 acc.x = ecc;
642 }
643 else
644 {
645 acc.x = ((chart_focus_coordinates.x-scroll_pos)*ecc + (256.0 - chart_focus_coordinates.x)*acc.x)/(256.0 - scroll_pos);
646 }
647 }
648 if (chart_focus_coordinates.y - acc.y <= -CHART_SCROLL_AT_Y*chart_zoom)
649 {
650 scroll_pos = acc.y - CHART_SCROLL_AT_Y*chart_zoom;
651 ecc = CHART_HEIGHT_AT_MAX_ZOOM*chart_zoom / 2.0;
652 if (scroll_pos <= 0)
653 {
654 acc.y = ecc;
655 }
656 else
657 {
658 acc.y = ((scroll_pos-chart_focus_coordinates.y)*ecc + chart_focus_coordinates.y*acc.y)/scroll_pos;
659 }
660 }
661 else if (chart_focus_coordinates.y - acc.y >= CHART_SCROLL_AT_Y*chart_zoom)
662 {
663 scroll_pos = acc.y + CHART_SCROLL_AT_Y*chart_zoom;
664 ecc = 256.0 - CHART_HEIGHT_AT_MAX_ZOOM*chart_zoom / 2.0;
665 if (scroll_pos >= 256.0)
666 {
667 acc.y = ecc;
668 }
669 else
670 {
671 acc.y = ((chart_focus_coordinates.y-scroll_pos)*ecc + (256.0 - chart_focus_coordinates.y)*acc.y)/(256.0 - scroll_pos);
672 }
673 }
674 return acc;
675}
676
677
678- (OORouteType) ANAMode
679{
680 return ANA_mode;
681}
682
683
684- (OOSystemID) systemID
685{
686 return system_id;
687}
688
689
690- (void) setSystemID:(OOSystemID) sid
691{
692 system_id = sid;
693 galaxy_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:sid inGalaxy:galaxy_number]);
694 chart_centre_coordinates = galaxy_coordinates;
695 target_chart_centre = chart_centre_coordinates;
696}
697
698
699- (OOSystemID) previousSystemID
700{
701 return previous_system_id;
702}
703
704
705- (void) setPreviousSystemID:(OOSystemID) sid
706{
707 previous_system_id = sid;
708}
709
710
711- (OOSystemID) targetSystemID
712{
713 return target_system_id;
714}
715
716
717- (void) setTargetSystemID:(OOSystemID) sid
718{
719 target_system_id = sid;
720 cursor_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystemKey:[UNIVERSE keyForPlanetOverridesForSystem:sid inGalaxy:galaxy_number]]);
721}
722
723
724// just return target system id if no valid next hop
725- (OOSystemID) nextHopTargetSystemID
726{
727 // not available if no ANA
728 if (![self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"])
729 {
730 return target_system_id;
731 }
732 // not available if ANA is turned off
733 if (ANA_mode == OPTIMIZED_BY_NONE)
734 {
735 return target_system_id;
736 }
737 // easy case
738 if (system_id == target_system_id)
739 {
740 return system_id; // no need to calculate
741 }
742 NSDictionary *routeInfo = nil;
743 routeInfo = [UNIVERSE routeFromSystem:system_id toSystem:target_system_id optimizedBy:ANA_mode];
744 // no route to destination
745 if (routeInfo == nil)
746 {
747 return target_system_id;
748 }
749 return [[routeInfo oo_arrayForKey:@"route"] oo_intAtIndex:1];
750}
751
752
753- (OOSystemID) infoSystemID
754{
755 return info_system_id;
756}
757
758
759- (void) setInfoSystemID: (OOSystemID) sid moveChart: (BOOL) moveChart
760{
761 if (sid != info_system_id)
762 {
763 OOSystemID old = info_system_id;
764 info_system_id = sid;
765 JSContext *context = OOJSAcquireContext();
766 ShipScriptEvent(context, self, "infoSystemWillChange", INT_TO_JSVAL(info_system_id), INT_TO_JSVAL(old));
767 if (gui_screen == GUI_SCREEN_LONG_RANGE_CHART || gui_screen == GUI_SCREEN_SHORT_RANGE_CHART)
768 {
769 if(moveChart)
770 {
771 target_chart_focus = [[UNIVERSE systemManager] getCoordinatesForSystem:info_system_id inGalaxy:galaxy_number];
772 }
773 }
774 else
775 {
776 if(gui_screen == GUI_SCREEN_SYSTEM_DATA)
777 {
778 [self setGuiToSystemDataScreenRefreshBackground: YES];
779 }
780 if(moveChart)
781 {
782 chart_centre_coordinates = [[UNIVERSE systemManager] getCoordinatesForSystem:info_system_id inGalaxy:galaxy_number];
783 target_chart_centre = chart_centre_coordinates;
784 chart_focus_coordinates = chart_centre_coordinates;
785 target_chart_focus = chart_focus_coordinates;
786 }
787 }
788 ShipScriptEvent(context, self, "infoSystemChanged", INT_TO_JSVAL(info_system_id), INT_TO_JSVAL(old));
789 OOJSRelinquishContext(context);
790 }
791}
792
793
794- (void) nextInfoSystem
795{
796 if (ANA_mode == OPTIMIZED_BY_NONE)
797 {
798 [self setInfoSystemID: target_system_id moveChart: YES];
799 return;
800 }
801 NSArray *route = [[[UNIVERSE routeFromSystem:system_id toSystem:target_system_id optimizedBy:ANA_mode] oo_arrayForKey: @"route"] retain];
802 NSUInteger i;
803 if (route == nil)
804 {
805 [self setInfoSystemID: target_system_id moveChart: YES];
806 return;
807 }
808 for (i = 0; i < [route count]; i++)
809 {
810 if ([[route objectAtIndex: i] intValue] == info_system_id)
811 {
812 if (i + 1 < [route count])
813 {
814 [self setInfoSystemID:[[route objectAtIndex:i + 1] unsignedIntValue] moveChart: YES];
815 [route release];
816 return;
817 }
818 break;
819 }
820 }
821 [route release];
822 [self setInfoSystemID: target_system_id moveChart: YES];
823 return;
824}
825
826
827- (void) previousInfoSystem
828{
829 if (ANA_mode == OPTIMIZED_BY_NONE)
830 {
831 [self setInfoSystemID: system_id moveChart: YES];
832 return;
833 }
834 NSArray *route = [[[UNIVERSE routeFromSystem:system_id toSystem:target_system_id optimizedBy:ANA_mode] oo_arrayForKey: @"route"] retain];
835 NSUInteger i;
836 if (route == nil)
837 {
838 [self setInfoSystemID: system_id moveChart: YES];
839 return;
840 }
841 for (i = 0; i < [route count]; i++)
842 {
843 if ([[route objectAtIndex: i] intValue] == info_system_id)
844 {
845 if (i > 0)
846 {
847 [self setInfoSystemID: [[route objectAtIndex: i - 1] unsignedIntValue] moveChart: YES];
848 [route release];
849 return;
850 }
851 break;
852 }
853 }
854 [route release];
855 [self setInfoSystemID: system_id moveChart: YES];
856 return;
857}
858
859
860- (void) homeInfoSystem
861{
862 [self setInfoSystemID: system_id moveChart: YES];
863 return;
864}
865
866
867- (void) targetInfoSystem
868{
869 [self setInfoSystemID: target_system_id moveChart: YES];
870 return;
871}
872
873
874- (BOOL) infoSystemOnRoute
875{
876 NSArray *route = [[UNIVERSE routeFromSystem:system_id toSystem:target_system_id optimizedBy:ANA_mode] oo_arrayForKey: @"route"];
877 NSUInteger i;
878 if (route == nil)
879 {
880 return NO;
881 }
882 for (i = 0; i < [route count]; i++)
883 {
884 if ([[route objectAtIndex: i] intValue] == info_system_id)
885 {
886 return YES;
887 }
888 }
889 return NO;
890}
891
892
893- (WormholeEntity *) wormhole
894{
895 return wormhole;
896}
897
898
899- (void) setWormhole:(WormholeEntity*)newWormhole
900{
901 [wormhole release];
902 if (newWormhole != nil)
903 {
904 wormhole = [newWormhole retain];
905 }
906 else
907 {
908 wormhole = nil;
909 }
910}
911
912
913- (NSDictionary *) commanderDataDictionary
914{
915 int i;
916
917 NSMutableDictionary *result = [NSMutableDictionary dictionary];
918
919 [result setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] forKey:@"written_by_version"];
920
921 NSString *gal_id = [NSString stringWithFormat:@"%u", galaxy_number];
922 NSString *sys_id = [NSString stringWithFormat:@"%d", system_id];
923 NSString *tgt_id = [NSString stringWithFormat:@"%d", target_system_id];
924 NSString *prv_id = [NSString stringWithFormat:@"%d", previous_system_id];
925
926 // Variable requiredCargoSpace not suitable for Oolite as it currently stands: it retroactively changes a savegame cargo space.
927 //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace];
928 //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE;
929
930 [result setObject:gal_id forKey:@"galaxy_id"];
931 [result setObject:sys_id forKey:@"system_id"];
932 [result setObject:tgt_id forKey:@"target_id"];
933 [result setObject:prv_id forKey:@"previous_system_id"];
934 [result setObject:[NSNumber numberWithFloat:saved_chart_zoom] forKey:@"chart_zoom"];
935 [result setObject:[NSNumber numberWithInt:ANA_mode] forKey:@"chart_ana_mode"];
936 [result setObject:[NSNumber numberWithInt:longRangeChartMode] forKey:@"chart_colour_mode"];
937
938
939 if (found_system_id >= 0)
940 {
941 NSString *found_id = [NSString stringWithFormat:@"%d", found_system_id];
942 [result setObject:found_id forKey:@"found_system_id"];
943 }
944
945 // Write the name of the current system. Useful for looking up saved game information and for overlapping systems.
946 if (![UNIVERSE inInterstellarSpace])
947 {
948 [result setObject:[UNIVERSE getSystemName:[self currentSystemID]] forKey:@"current_system_name"];
949 OOGovernmentID government = [[UNIVERSE currentSystemData] oo_intForKey:KEY_GOVERNMENT];
950 OOTechLevelID techlevel = [[UNIVERSE currentSystemData] oo_intForKey:KEY_TECHLEVEL];
951 OOEconomyID economy = [[UNIVERSE currentSystemData] oo_intForKey:KEY_ECONOMY];
952 [result setObject:[NSNumber numberWithUnsignedShort:government] forKey:@"current_system_government"];
953 [result setObject:[NSNumber numberWithUnsignedInteger:techlevel] forKey:@"current_system_techlevel"];
954 [result setObject:[NSNumber numberWithUnsignedShort:economy] forKey:@"current_system_economy"];
955 }
956
957 [result setObject:[self commanderName] forKey:@"player_name"];
958 [result setObject:[self lastsaveName] forKey:@"player_save_name"];
959 [result setObject:[self shipUniqueName] forKey:@"ship_unique_name"];
960 [result setObject:[self shipClassName] forKey:@"ship_class_name"];
961
962 /*
963 BUG: GNUstep truncates integer values to 32 bits when loading XML plists.
964 Workaround: store credits as a double. 53 bits of precision ought to
965 be good enough for anybody. Besides, we display credits with double
966 precision anyway.
967 -- Ahruman 2011-02-15
968 */
969 [result oo_setFloat:credits forKey:@"credits"];
970 [result oo_setUnsignedInteger:fuel forKey:@"fuel"];
971
972 [result oo_setInteger:galaxy_number forKey:@"galaxy_number"];
973
974 [result oo_setBool:[self weaponsOnline] forKey:@"weapons_online"];
975
976 if (forward_weapon_type != nil)
977 {
978 [result setObject:[forward_weapon_type identifier] forKey:@"forward_weapon"];
979 }
980 if (aft_weapon_type != nil)
981 {
982 [result setObject:[aft_weapon_type identifier] forKey:@"aft_weapon"];
983 }
984 if (port_weapon_type != nil)
985 {
986 [result setObject:[port_weapon_type identifier] forKey:@"port_weapon"];
987 }
988 if (starboard_weapon_type != nil)
989 {
990 [result setObject:[starboard_weapon_type identifier] forKey:@"starboard_weapon"];
991 }
992 [result setObject:[self serializeShipSubEntities] forKey:@"subentities_status"];
993 if (hud != nil && [hud nonlinearScanner])
994 {
995 [result oo_setFloat: [hud scannerZoom] forKey:@"ship_scanner_zoom"];
996 }
997
998 [result oo_setInteger:max_cargo + PASSENGER_BERTH_SPACE * max_passengers forKey:@"max_cargo"];
999
1000 [result setObject:[shipCommodityData savePlayerAmounts] forKey:@"shipCommodityData"];
1001
1002
1003 NSMutableArray *missileRoles = [NSMutableArray arrayWithCapacity:max_missiles];
1004
1005 for (i = 0; i < (int)max_missiles; i++)
1006 {
1007 if (missile_entity[i])
1008 {
1009 [missileRoles addObject:[missile_entity[i] primaryRole]];
1010 }
1011 else
1012 {
1013 [missileRoles addObject:@"NONE"];
1014 }
1015 }
1016 [result setObject:missileRoles forKey:@"missile_roles"];
1017
1018 [result oo_setInteger:missiles forKey:@"missiles"];
1019
1020 [result oo_setInteger:legalStatus forKey:@"legal_status"];
1021 [result oo_setInteger:market_rnd forKey:@"market_rnd"];
1022 [result oo_setInteger:ship_kills forKey:@"ship_kills"];
1023
1024 // ship depreciation
1025 [result oo_setInteger:ship_trade_in_factor forKey:@"ship_trade_in_factor"];
1026
1027 // mission variables
1028 if (mission_variables != nil)
1029 {
1030 [result setObject:[NSDictionary dictionaryWithDictionary:mission_variables] forKey:@"mission_variables"];
1031 }
1032
1033 // communications log
1034 NSArray *log = [self commLog];
1035 if (log != nil) [result setObject:log forKey:@"comm_log"];
1036
1037 [result oo_setUnsignedInteger:entity_personality forKey:@"entity_personality"];
1038
1039 // extra equipment flags
1040 NSMutableDictionary *equipment = [NSMutableDictionary dictionary];
1041 NSEnumerator *eqEnum = nil;
1042 NSString *eqDesc = nil;
1043 for (eqEnum = [self equipmentEnumerator]; (eqDesc = [eqEnum nextObject]); )
1044 {
1045 [equipment oo_setInteger:[self countEquipmentItem:eqDesc] forKey:eqDesc];
1046 }
1047 if ([equipment count] != 0)
1048 {
1049 [result setObject:equipment forKey:@"extra_equipment"];
1050 }
1051 if (primedEquipment < [eqScripts count]) [result setObject:[[eqScripts oo_arrayAtIndex:primedEquipment] oo_stringAtIndex:0] forKey:@"primed_equipment"];
1052
1053 [result setObject:[self fastEquipmentA] forKey:@"primed_equipment_a"];
1054 [result setObject:[self fastEquipmentB] forKey:@"primed_equipment_b"];
1055
1056 // roles
1057 [result setObject:roleWeights forKey:@"role_weights"];
1058
1059 // role information
1060 [result setObject:roleWeightFlags forKey:@"role_weight_flags"];
1061
1062 // role information
1063 [result setObject:roleSystemList forKey:@"role_system_memory"];
1064
1065 // reputation
1066 [result setObject:reputation forKey:@"reputation"];
1067
1068 // initialise parcel reputations in dictionary if not set
1069 int pGood = [reputation oo_intForKey:PARCEL_GOOD_KEY];
1070 int pBad = [reputation oo_intForKey:PARCEL_BAD_KEY];
1071 int pUnknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY];
1072 if (pGood+pBad+pUnknown != MAX_CONTRACT_REP)
1073 {
1074 [reputation oo_setInteger:0 forKey:PARCEL_GOOD_KEY];
1075 [reputation oo_setInteger:0 forKey:PARCEL_BAD_KEY];
1076 [reputation oo_setInteger:MAX_CONTRACT_REP forKey:PARCEL_UNKNOWN_KEY];
1077 }
1078
1079 // passengers
1080 [result oo_setInteger:max_passengers forKey:@"max_passengers"];
1081 [result setObject:passengers forKey:@"passengers"];
1082 [result setObject:passenger_record forKey:@"passenger_record"];
1083
1084 // parcels
1085 [result setObject:parcels forKey:@"parcels"];
1086 [result setObject:parcel_record forKey:@"parcel_record"];
1087
1088 //specialCargo
1089 if (specialCargo) [result setObject:specialCargo forKey:@"special_cargo"];
1090
1091 // contracts
1092 [result setObject:contracts forKey:@"contracts"];
1093 [result setObject:contract_record forKey:@"contract_record"];
1094
1095 [result setObject:missionDestinations forKey:@"mission_destinations"];
1096
1097 //shipyard
1098 [result setObject:shipyard_record forKey:@"shipyard_record"];
1099
1100 //ship's clock
1101 [result setObject:[NSNumber numberWithDouble:ship_clock] forKey:@"ship_clock"];
1102
1103 //speech
1104 [result setObject:[NSNumber numberWithInt:isSpeechOn] forKey:@"speech_on"];
1105#if OOLITE_ESPEAK
1106 [result setObject:[UNIVERSE voiceName:voice_no] forKey:@"speech_voice"];
1107 [result setObject:[NSNumber numberWithBool:voice_gender_m] forKey:@"speech_gender"];
1108#endif
1109
1110 // docking clearance
1111 [result setObject:[NSNumber numberWithBool:[UNIVERSE dockingClearanceProtocolActive]] forKey:@"docking_clearance_protocol"];
1112
1113 //base ship description
1114 [result setObject:[self shipDataKey] forKey:@"ship_desc"];
1115 [result setObject:[[self shipInfoDictionary] oo_stringForKey:KEY_NAME] forKey:@"ship_name"];
1116
1117 //custom view no.
1118 [result oo_setUnsignedInteger:_customViewIndex forKey:@"custom_view_index"];
1119
1120 // escape pod rescue time
1121 [result oo_setFloat:[self escapePodRescueTime] forKey:@"escape_pod_rescue_time"];
1122
1123 //local market for main station
1124 if ([[UNIVERSE station] localMarket]) [result setObject:[[[UNIVERSE station] localMarket] saveStationAmounts] forKey:@"localMarket"];
1125
1126 // Scenario restriction on OXZs
1127 [result setObject:[UNIVERSE useAddOns] forKey:@"scenario_restriction"];
1128
1129 [result setObject:[[UNIVERSE systemManager] exportScriptedChanges] forKey:@"scripted_planetinfo_overrides"];
1130
1131 // trumble information
1132 [result setObject:[self trumbleValue] forKey:@"trumbles"];
1133
1134 // wormhole information
1135 NSMutableArray *wormholeDicts = [NSMutableArray arrayWithCapacity:[scannedWormholes count]];
1136 WormholeEntity *wh = nil;
1137 foreach (wh, scannedWormholes)
1138 {
1139 [wormholeDicts addObject:[wh getDict]];
1140 }
1141 [result setObject:wormholeDicts forKey:@"wormholes"];
1142
1143 // docked station
1144 StationEntity *dockedStation = [self dockedStation];
1145 [result setObject:dockedStation != nil ? [dockedStation primaryRole]:(NSString *)@"" forKey:@"docked_station_role"];
1146 if (dockedStation)
1147 {
1148 HPVector dpos = [dockedStation position];
1149 [result setObject:ArrayFromHPVector(dpos) forKey:@"docked_station_position"];
1150 }
1151 else
1152 {
1153 [result setObject:[NSArray array] forKey:@"docked_station_position"];
1154 }
1155 [result setObject:[UNIVERSE getStationMarkets] forKey:@"station_markets"];
1156
1157 // scenario information
1158 if (scenarioKey != nil)
1159 {
1160 [result setObject:scenarioKey forKey:@"scenario"];
1161 }
1162
1163 // create checksum
1165// TODO: should checksum checks be removed?
1166// munge_checksum(galaxy_seed.a); munge_checksum(galaxy_seed.b); munge_checksum(galaxy_seed.c);
1167// munge_checksum(galaxy_seed.d); munge_checksum(galaxy_seed.e); munge_checksum(galaxy_seed.f);
1168 munge_checksum(galaxy_coordinates.x); munge_checksum(galaxy_coordinates.y);
1169 munge_checksum(credits); munge_checksum(fuel);
1170 munge_checksum(max_cargo); munge_checksum(missiles);
1171 munge_checksum(legalStatus); munge_checksum(market_rnd); munge_checksum(ship_kills);
1172
1173 if (mission_variables != nil)
1174 {
1175 munge_checksum([[mission_variables description] length]);
1176 }
1177 if (equipment != nil)
1178 {
1179 munge_checksum([[equipment description] length]);
1180 }
1181
1182 int final_checksum = munge_checksum([[self shipDataKey] length]);
1183
1184 //set checksum
1185 [result oo_setInteger:final_checksum forKey:@"checksum"];
1186
1187 return result;
1188}
1189
1190
1191- (BOOL)setCommanderDataFromDictionary:(NSDictionary *) dict
1192{
1193 // multi-function displays
1194 // must be reset before ship setup
1195 [multiFunctionDisplayText release];
1196 multiFunctionDisplayText = [[NSMutableDictionary alloc] init];
1197
1198 [multiFunctionDisplaySettings release];
1199 multiFunctionDisplaySettings = [[NSMutableArray alloc] init];
1200
1201 [customDialSettings release];
1202 customDialSettings = [[NSMutableDictionary alloc] init];
1203
1204 [[UNIVERSE gameView] resetTypedString];
1205
1206 // Required keys
1207 if ([dict oo_stringForKey:@"ship_desc"] == nil) return NO;
1208 // galaxy_seed is used is 1.80 or earlier
1209 if ([dict oo_stringForKey:@"galaxy_seed"] == nil && [dict oo_stringForKey:@"galaxy_id"] == nil) return NO;
1210 // galaxy_coordinates is used is 1.80 or earlier
1211 if ([dict oo_stringForKey:@"galaxy_coordinates"] == nil && [dict oo_stringForKey:@"system_id"] == nil) return NO;
1212
1213 NSString *scenarioRestrict = [dict oo_stringForKey:@"scenario_restriction" defaultValue:nil];
1214 if (scenarioRestrict == nil)
1215 {
1216 // older save game - use the 'strict' key instead
1217 BOOL strict = [dict oo_boolForKey:@"strict" defaultValue:NO];
1218 if (strict)
1219 {
1220 scenarioRestrict = SCENARIO_OXP_DEFINITION_NONE;
1221 }
1222 else
1223 {
1224 scenarioRestrict = SCENARIO_OXP_DEFINITION_ALL;
1225 }
1226 }
1227
1228 if (![UNIVERSE setUseAddOns:scenarioRestrict fromSaveGame:YES])
1229 {
1230 return NO;
1231 }
1232
1233
1234 //base ship description
1235 [self setShipDataKey:[dict oo_stringForKey:@"ship_desc"]];
1236
1237 NSDictionary *shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:[self shipDataKey]];
1238 if (shipDict == nil) return NO;
1239 if (![self setUpShipFromDictionary:shipDict]) return NO;
1240 OOLog(@"fuelPrices", @"Got \"%@\", fuel charge rate: %.2f", [self shipDataKey],[self fuelChargeRate]);
1241
1242 // ship depreciation
1243 ship_trade_in_factor = [dict oo_intForKey:@"ship_trade_in_factor" defaultValue:95];
1244
1245 // newer savegames use galaxy_id
1246 if ([dict oo_stringForKey:@"galaxy_id"] != nil)
1247 {
1248 galaxy_number = [dict oo_unsignedIntegerForKey:@"galaxy_id"];
1249 if (galaxy_number >= OO_GALAXIES_AVAILABLE)
1250 {
1251 return NO;
1252 }
1253 [UNIVERSE setGalaxyTo:galaxy_number andReinit:YES];
1254
1255 system_id = [dict oo_intForKey:@"system_id"];
1256 if (system_id < 0 || system_id >= OO_SYSTEMS_PER_GALAXY)
1257 {
1258 return NO;
1259 }
1260
1261 [UNIVERSE setSystemTo:system_id];
1262
1263 NSArray *coord_vals = ScanTokensFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:system_id inGalaxy:galaxy_number]);
1264 galaxy_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0];
1265 galaxy_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1];
1266 chart_centre_coordinates = galaxy_coordinates;
1267 target_chart_centre = chart_centre_coordinates;
1268 cursor_coordinates = galaxy_coordinates;
1269 chart_zoom = [dict oo_floatForKey:@"chart_zoom" defaultValue:1.0];
1270 target_chart_zoom = chart_zoom;
1271 saved_chart_zoom = chart_zoom;
1272 ANA_mode = [dict oo_intForKey:@"chart_ana_mode" defaultValue:OPTIMIZED_BY_NONE];
1273 longRangeChartMode = [dict oo_intForKey:@"chart_colour_mode" defaultValue:OOLRC_MODE_SUNCOLOR];
1274 if (longRangeChartMode == OOLRC_MODE_UNKNOWN) longRangeChartMode = OOLRC_MODE_SUNCOLOR;
1275
1276 target_system_id = [dict oo_intForKey:@"target_id" defaultValue:system_id];
1277 previous_system_id = [dict oo_intForKey:@"previous_system_id" defaultValue:system_id];
1278 info_system_id = target_system_id;
1279 coord_vals = ScanTokensFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:target_system_id inGalaxy:galaxy_number]);
1280 cursor_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0];
1281 cursor_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1];
1282
1283 chart_focus_coordinates = chart_centre_coordinates;
1284 target_chart_focus = chart_focus_coordinates;
1285
1286 found_system_id = [dict oo_intForKey:@"found_system_id" defaultValue:-1];
1287 }
1288 else
1289 // compatibility for loading 1.80 savegames
1290 {
1291 galaxy_number = [dict oo_unsignedIntegerForKey:@"galaxy_number"];
1292
1293 [UNIVERSE setGalaxyTo: galaxy_number andReinit:YES];
1294
1295 NSArray *coord_vals = ScanTokensFromString([dict oo_stringForKey:@"galaxy_coordinates"]);
1296 galaxy_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0];
1297 galaxy_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1];
1298 chart_centre_coordinates = galaxy_coordinates;
1299 target_chart_centre = chart_centre_coordinates;
1300 cursor_coordinates = galaxy_coordinates;
1301 chart_zoom = 1.0;
1302 target_chart_zoom = 1.0;
1303 saved_chart_zoom = 1.0;
1304 ANA_mode = OPTIMIZED_BY_NONE;
1305
1306 NSString *keyStringValue = [dict oo_stringForKey:@"target_coordinates"];
1307
1308 if (keyStringValue != nil)
1309 {
1310 coord_vals = ScanTokensFromString(keyStringValue);
1311 cursor_coordinates.x = [coord_vals oo_unsignedCharAtIndex:0];
1312 cursor_coordinates.y = [coord_vals oo_unsignedCharAtIndex:1];
1313 }
1314 chart_focus_coordinates = chart_centre_coordinates;
1315 target_chart_focus = chart_focus_coordinates;
1316
1317 // calculate system ID, target ID
1318 if ([dict objectForKey:@"current_system_name"])
1319 {
1320 system_id = [UNIVERSE findSystemFromName:[dict oo_stringForKey:@"current_system_name"]];
1321 if (system_id == -1) system_id = [UNIVERSE findSystemNumberAtCoords:galaxy_coordinates withGalaxy:galaxy_number includingHidden:YES];
1322 }
1323 else
1324 {
1325 // really old save games don't have system name saved
1326 // use coordinates instead - unreliable in zero-distance pairs.
1327 system_id = [UNIVERSE findSystemNumberAtCoords:galaxy_coordinates withGalaxy:galaxy_number includingHidden:YES];
1328 }
1329 // and current_system_name and target_system_name
1330 // were introduced at different times, too
1331 if ([dict objectForKey:@"target_system_name"])
1332 {
1333 target_system_id = [UNIVERSE findSystemFromName:[dict oo_stringForKey:@"target_system_name"]];
1334 if (target_system_id == -1) target_system_id = [UNIVERSE findSystemNumberAtCoords:cursor_coordinates withGalaxy:galaxy_number includingHidden:YES];
1335 }
1336 else
1337 {
1338 target_system_id = [UNIVERSE findSystemNumberAtCoords:cursor_coordinates withGalaxy:galaxy_number includingHidden:YES];
1339 }
1340 info_system_id = target_system_id;
1341 found_system_id = -1;
1342 }
1343
1344 NSString *cname = [dict oo_stringForKey:@"player_name" defaultValue:PLAYER_DEFAULT_NAME];
1345 [self setCommanderName:cname];
1346 [self setLastsaveName:[dict oo_stringForKey:@"player_save_name" defaultValue:cname]];
1347
1348 [self setShipUniqueName:[dict oo_stringForKey:@"ship_unique_name" defaultValue:@""]];
1349 [self setShipClassName:[dict oo_stringForKey:@"ship_class_name" defaultValue:[shipDict oo_stringForKey:@"name"]]];
1350
1351 [shipCommodityData loadPlayerAmounts:[dict oo_arrayForKey:@"shipCommodityData"]];
1352
1353 // extra equipment flags
1354 [self removeAllEquipment];
1355 NSMutableDictionary *equipment = [NSMutableDictionary dictionaryWithDictionary:[dict oo_dictionaryForKey:@"extra_equipment"]];
1356
1357 // Equipment flags (deprecated in favour of equipment dictionary, keep for compatibility)
1358 if ([dict oo_boolForKey:@"has_docking_computer"]) [equipment oo_setInteger:1 forKey:@"EQ_DOCK_COMP"];
1359 if ([dict oo_boolForKey:@"has_galactic_hyperdrive"]) [equipment oo_setInteger:1 forKey:@"EQ_GAL_DRIVE"];
1360 if ([dict oo_boolForKey:@"has_escape_pod"]) [equipment oo_setInteger:1 forKey:@"EQ_ESCAPE_POD"];
1361 if ([dict oo_boolForKey:@"has_ecm"]) [equipment oo_setInteger:1 forKey:@"EQ_ECM"];
1362 if ([dict oo_boolForKey:@"has_scoop"]) [equipment oo_setInteger:1 forKey:@"EQ_FUEL_SCOOPS"];
1363 if ([dict oo_boolForKey:@"has_energy_bomb"]) [equipment oo_setInteger:1 forKey:@"EQ_ENERGY_BOMB"];
1364 if ([dict oo_boolForKey:@"has_fuel_injection"]) [equipment oo_setInteger:1 forKey:@"EQ_FUEL_INJECTION"];
1365
1366
1367 // Legacy energy unit type -> energy unit equipment item
1368 if ([dict oo_boolForKey:@"has_energy_unit"] && [self installedEnergyUnitType] == ENERGY_UNIT_NONE)
1369 {
1370 OOEnergyUnitType eType = [dict oo_intForKey:@"energy_unit" defaultValue:ENERGY_UNIT_NORMAL];
1371 switch (eType)
1372 {
1373 // look for NEU first!
1375 [equipment oo_setInteger:1 forKey:@"EQ_NAVAL_ENERGY_UNIT"];
1376 break;
1377
1379 [equipment oo_setInteger:1 forKey:@"EQ_ENERGY_UNIT"];
1380 break;
1381
1382 default:
1383 break;
1384 }
1385 }
1386
1387 custom_chart_zoom = 1.0;
1388 custom_chart_centre_coordinates = NSMakePoint(galaxy_coordinates.y, galaxy_coordinates.y);
1389
1390 /* Energy bombs are no longer supported without OXPs. As compensation,
1391 we'll award either a Q-mine or some cash. We can't determine what to
1392 award until we've handled missiles later on, though.
1393 */
1394 BOOL energyBombCompensation = NO;
1395 if ([equipment oo_boolForKey:@"EQ_ENERGY_BOMB"] && [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_ENERGY_BOMB"] == nil)
1396 {
1397 energyBombCompensation = YES;
1398 [equipment removeObjectForKey:@"EQ_ENERGY_BOMB"];
1399 }
1400
1401 eqScripts = [[NSMutableArray alloc] init];
1402 [self addEquipmentFromCollection:equipment];
1403 primedEquipment = [self eqScriptIndexForKey:[dict oo_stringForKey:@"primed_equipment"]]; // if key not found primedEquipment is set to primed-none
1404
1405 [self setFastEquipmentA:[dict oo_stringForKey:@"primed_equipment_a" defaultValue:@"EQ_CLOAKING_DEVICE"]];
1406 [self setFastEquipmentB:[dict oo_stringForKey:@"primed_equipment_b" defaultValue:@"EQ_ENERGY_BOMB"]]; // even though there isn't one, for compatibility.
1407
1408 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"]) compassMode = COMPASS_MODE_PLANET;
1409 else compassMode = COMPASS_MODE_BASIC;
1410 DESTROY(compassTarget);
1411
1412 // speech
1413 isSpeechOn = [dict oo_intForKey:@"speech_on"];
1414#if OOLITE_ESPEAK
1415 voice_gender_m = [dict oo_boolForKey:@"speech_gender" defaultValue:YES];
1416 voice_no = [UNIVERSE setVoice:[UNIVERSE voiceNumber:[dict oo_stringForKey:@"speech_voice" defaultValue:nil]] withGenderM:voice_gender_m];
1417#endif
1418
1419 // reputation
1420 [reputation release];
1421 reputation = [[dict oo_dictionaryForKey:@"reputation"] mutableCopy];
1422 if (reputation == nil) reputation = [[NSMutableDictionary alloc] init];
1423 [self normaliseReputation];
1424
1425 // passengers and contracts
1426 [parcels release];
1427 [parcel_record release];
1428 [passengers release];
1429 [passenger_record release];
1430 [contracts release];
1431 [contract_record release];
1432
1433 max_passengers = [dict oo_intForKey:@"max_passengers" defaultValue:0];
1434 passengers = [[dict oo_arrayForKey:@"passengers"] mutableCopy];
1435 passenger_record = [[dict oo_dictionaryForKey:@"passenger_record"] mutableCopy];
1436 /* Note: contracts from older savegames will have ints in the commodity.
1437 * Need to fix this up */
1438 contracts = [[dict oo_arrayForKey:@"contracts"] mutableCopy];
1439 NSMutableDictionary *contractInfo = nil;
1440
1441 // iterate downwards; lets us remove invalid ones as we go
1442 for (NSInteger i = (NSInteger)[contracts count] - 1; i >= 0; i--)
1443 {
1444 contractInfo = [[[contracts oo_dictionaryAtIndex:i] mutableCopy] autorelease];
1445 // if the trade good ID is an int
1446 if ([[contractInfo objectForKey:CARGO_KEY_TYPE] isKindOfClass:[NSNumber class]])
1447 {
1448 // look it up, and replace with a string
1449 NSUInteger legacy_type = [contractInfo oo_unsignedIntegerForKey:CARGO_KEY_TYPE];
1450 [contractInfo setObject:[OOCommodities legacyCommodityType:legacy_type] forKey:CARGO_KEY_TYPE];
1451 [contracts replaceObjectAtIndex:i withObject:[[contractInfo copy] autorelease]];
1452 }
1453 else
1454 {
1455 OOCommodityType new_type = [contractInfo oo_stringForKey:CARGO_KEY_TYPE];
1456 // check that that the type still exists
1457 if (![[UNIVERSE commodities] goodDefined:new_type])
1458 {
1459 OOLog(@"setCommanderDataFromDictionary.warning.contract",@"Cargo contract to deliver %@ could not be loaded from the saved game, as the commodity is no longer defined",new_type);
1460 [contracts removeObjectAtIndex:i];
1461 }
1462 }
1463 }
1464
1465 contract_record = [[dict oo_dictionaryForKey:@"contract_record"] mutableCopy];
1466 parcels = [[dict oo_arrayForKey:@"parcels"] mutableCopy];
1467 parcel_record = [[dict oo_dictionaryForKey:@"parcel_record"] mutableCopy];
1468
1469
1470
1471 if (passengers == nil) passengers = [[NSMutableArray alloc] init];
1472 if (passenger_record == nil) passenger_record = [[NSMutableDictionary alloc] init];
1473 if (contracts == nil) contracts = [[NSMutableArray alloc] init];
1474 if (contract_record == nil) contract_record = [[NSMutableDictionary alloc] init];
1475 if (parcels == nil) parcels = [[NSMutableArray alloc] init];
1476 if (parcel_record == nil) parcel_record = [[NSMutableDictionary alloc] init];
1477
1478 //specialCargo
1479 [specialCargo release];
1480 specialCargo = [[dict oo_stringForKey:@"special_cargo"] copy];
1481
1482 // mission destinations
1483 NSArray *legacyDestinations = [dict oo_arrayForKey:@"missionDestinations"];
1484
1485 NSDictionary *newDestinations = [dict oo_dictionaryForKey:@"mission_destinations"];
1486 [self initialiseMissionDestinations:newDestinations andLegacy:legacyDestinations];
1487
1488 // shipyard
1489 DESTROY(shipyard_record);
1490 shipyard_record = [[dict oo_dictionaryForKey:@"shipyard_record"] mutableCopy];
1491 if (shipyard_record == nil) shipyard_record = [[NSMutableDictionary alloc] init];
1492
1493 // Normalize cargo capacity
1494 unsigned original_hold_size = [UNIVERSE maxCargoForShip:[self shipDataKey]];
1495 // Not Suitable For Oolite
1496 //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace];
1497 //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE;
1498
1499 max_cargo = [dict oo_unsignedIntForKey:@"max_cargo" defaultValue:max_cargo];
1500 if (max_cargo > original_hold_size) [self addEquipmentItem:@"EQ_CARGO_BAY" inContext:@"loading"];
1501 max_cargo = original_hold_size + ([self hasExpandedCargoBay] ? extra_cargo : 0);
1502 if (max_cargo < max_passengers * PASSENGER_BERTH_SPACE)
1503 {
1504 // Something went wrong. Possibly the save file was hacked to contain more passenger cabins than the available cargo space would allow - Nikos 20110731
1505 unsigned originalMaxPassengers = max_passengers;
1506 max_passengers = (unsigned)(max_cargo / PASSENGER_BERTH_SPACE);
1507 OOLogWARN(@"setCommanderDataFromDictionary.inconsistency.max_passengers", @"player ship %@ had max_passengers set to a value requiring more cargo space than currently available (%u). Setting max_passengers to maximum possible value (%u).", [self name], originalMaxPassengers, max_passengers);
1508 }
1509 max_cargo -= max_passengers * PASSENGER_BERTH_SPACE;
1510
1511 // Do we have extra passengers?
1512 if (passengers && ([passengers count] > max_passengers))
1513 {
1514 OOLogWARN(@"setCommanderDataFromDictionary.inconsistency.passengers", @"player ship %@ had more passengers (%llu) than passenger berths (%u). Removing extra passengers.", [self name], [passengers count], max_passengers);
1515 for (NSInteger i = (NSInteger)[passengers count] - 1; i >= max_passengers; i--)
1516 {
1517 [passenger_record removeObjectForKey:[[passengers oo_dictionaryAtIndex:i] oo_stringForKey:PASSENGER_KEY_NAME]];
1518 [passengers removeObjectAtIndex:i];
1519 }
1520 }
1521
1522 // too much cargo?
1523 NSInteger excessCargo = (NSInteger)[self cargoQuantityOnBoard] - (NSInteger)[self maxAvailableCargoSpace];
1524 if (excessCargo > 0)
1525 {
1526 OOLogWARN(@"setCommanderDataFromDictionary.inconsistency.cargo", @"player ship %@ had more cargo (%i) than it can hold (%u). Removing extra cargo.", [self name], [self cargoQuantityOnBoard], [self maxAvailableCargoSpace]);
1527
1528 OOCommodityType type;
1529 OOMassUnit units;
1530 OOCargoQuantity oldAmount, toRemove;
1531
1532 OOCargoQuantity remainingExcess = (OOCargoQuantity)excessCargo;
1533
1534 // manifest always contains entries for all 17 commodities, even if their quantity is 0.
1535 foreach (type, [shipCommodityData goods])
1536 {
1537 units = [shipCommodityData massUnitForGood:type];
1538
1539 oldAmount = [shipCommodityData quantityForGood:type];
1540 BOOL roundedTon = (units != UNITS_TONS) && ((units == UNITS_KILOGRAMS && oldAmount > MAX_KILOGRAMS_IN_SAFE) || (units == UNITS_GRAMS && oldAmount > MAX_GRAMS_IN_SAFE));
1541 if (roundedTon || (units == UNITS_TONS && oldAmount > 0))
1542 {
1543 // let's remove stuff
1544 OOCargoQuantity partAmount = oldAmount;
1545 toRemove = 0;
1546 while (remainingExcess > 0 && partAmount > 0)
1547 {
1548 if (EXPECT_NOT(roundedTon && ((units == UNITS_KILOGRAMS && partAmount > MAX_KILOGRAMS_IN_SAFE) || (units == UNITS_GRAMS && partAmount > MAX_GRAMS_IN_SAFE))))
1549 {
1550 toRemove += (units == UNITS_KILOGRAMS) ? (partAmount > (KILOGRAMS_PER_POD + MAX_KILOGRAMS_IN_SAFE) ? KILOGRAMS_PER_POD : partAmount - MAX_KILOGRAMS_IN_SAFE)
1551 : (partAmount > (GRAMS_PER_POD + MAX_GRAMS_IN_SAFE) ? GRAMS_PER_POD : partAmount - MAX_GRAMS_IN_SAFE);
1552 partAmount = oldAmount - toRemove;
1553 remainingExcess--;
1554 }
1555 else if (!roundedTon)
1556 {
1557 toRemove++;
1558 partAmount--;
1559 remainingExcess--;
1560 }
1561 else
1562 {
1563 partAmount = 0;
1564 }
1565 }
1566 [shipCommodityData removeQuantity:toRemove forGood:type];
1567 }
1568 }
1569 }
1570
1571 credits = OODeciCreditsFromObject([dict objectForKey:@"credits"]);
1572
1573 fuel = [dict oo_unsignedIntForKey:@"fuel" defaultValue:fuel];
1574 galaxy_number = [dict oo_intForKey:@"galaxy_number"];
1575//
1576 NSDictionary *shipyard_info = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:[self shipDataKey]];
1577 OOWeaponFacingSet available_facings = [shipyard_info oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]];
1578
1579 if (available_facings & WEAPON_FACING_FORWARD)
1580 forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"forward_weapon"]);
1581 else
1582 forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1583
1584 if (available_facings & WEAPON_FACING_AFT)
1585 aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"aft_weapon"]);
1586 else
1587 aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1588
1589 if (available_facings & WEAPON_FACING_PORT)
1590 port_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"port_weapon"]);
1591 else
1592 port_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1593
1594 if (available_facings & WEAPON_FACING_STARBOARD)
1595 starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierLegacy([dict oo_stringForKey:@"starboard_weapon"]);
1596 else
1597 starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1598
1599 [self setWeaponDataFromType:forward_weapon_type];
1600
1601 if (hud != nil && [hud nonlinearScanner])
1602 {
1603 [hud setScannerZoom: [dict oo_floatForKey:@"ship_scanner_zoom" defaultValue: 1.0]];
1604 }
1605
1606 weapons_online = [dict oo_boolForKey:@"weapons_online" defaultValue:YES];
1607
1608 legalStatus = [dict oo_intForKey:@"legal_status"];
1609 market_rnd = [dict oo_intForKey:@"market_rnd"];
1610 ship_kills = [dict oo_intForKey:@"ship_kills"];
1611
1612 ship_clock = [dict oo_doubleForKey:@"ship_clock" defaultValue:PLAYER_SHIP_CLOCK_START];
1613 fps_check_time = ship_clock;
1614
1615 escape_pod_rescue_time = [dict oo_doubleForKey:@"escape_pod_rescue_time" defaultValue:0.0];
1616
1617 // role weights
1618 [roleWeights release];
1619 roleWeights = [[dict oo_arrayForKey:@"role_weights"] mutableCopy];
1620 NSUInteger rc = [self maxPlayerRoles];
1621 if (roleWeights == nil)
1622 {
1623 roleWeights = [[NSMutableArray alloc] initWithCapacity:rc];
1624 while (rc-- > 0)
1625 {
1626 [roleWeights addObject:@"player-unknown"];
1627 }
1628 }
1629 else
1630 {
1631 if ([roleWeights count] > rc)
1632 {
1633 [roleWeights removeObjectsInRange:(NSRange) {rc,[roleWeights count]-rc}];
1634 }
1635 }
1636
1637 roleWeightFlags = [[dict oo_dictionaryForKey:@"role_weight_flags"] mutableCopy];
1638 if (roleWeightFlags == nil)
1639 {
1640 roleWeightFlags = [[NSMutableDictionary alloc] init];
1641 }
1642
1643 roleSystemList = [[dict oo_arrayForKey:@"role_system_memory"] mutableCopy];
1644 if (roleSystemList == nil)
1645 {
1646 roleSystemList = [[NSMutableArray alloc] initWithCapacity:32];
1647 }
1648
1649
1650 // mission_variables
1651 [mission_variables release];
1652 mission_variables = [[dict oo_dictionaryForKey:@"mission_variables"] mutableCopy];
1653 if (mission_variables == nil) mission_variables = [[NSMutableDictionary alloc] init];
1654
1655 // persistant UNIVERSE info
1656 NSDictionary *planetInfoOverrides = [dict oo_dictionaryForKey:@"scripted_planetinfo_overrides"];
1657 if (planetInfoOverrides != nil)
1658 {
1659 [[UNIVERSE systemManager] importScriptedChanges:planetInfoOverrides];
1660 }
1661 else
1662 {
1663 // no scripted overrides? What about 1.80-style local overrides?
1664 planetInfoOverrides = [dict oo_dictionaryForKey:@"local_planetinfo_overrides"];
1665 if (planetInfoOverrides != nil)
1666 {
1667 [[UNIVERSE systemManager] importLegacyScriptedChanges:planetInfoOverrides];
1668 }
1669 }
1670
1671 // communications log
1672 [commLog release];
1673 commLog = [[NSMutableArray alloc] initWithCapacity:kCommLogTrimThreshold];
1674
1675 NSArray *savedCommLog = [dict oo_arrayForKey:@"comm_log"];
1676 NSUInteger commCount = [savedCommLog count];
1677 for (NSUInteger i = 0; i < commCount; i++)
1678 {
1679 [UNIVERSE addCommsMessage:[savedCommLog objectAtIndex:i] forCount:0 andShowComms:NO logOnly:YES];
1680 }
1681
1682 /* entity_personality for scripts and shaders. If undefined, we fall back
1683 to old behaviour of using a random value each time game is loaded (set
1684 up in -setUp). Saving of entity_personality was added in 1.74.
1685 -- Ahruman 2009-09-13
1686 */
1687 entity_personality = [dict oo_unsignedShortForKey:@"entity_personality" defaultValue:entity_personality];
1688
1689 // set up missiles
1690 [self setActiveMissile:0];
1691 for (NSUInteger i = 0; i < PLAYER_MAX_MISSILES; i++)
1692 {
1693 [missile_entity[i] release];
1694 missile_entity[i] = nil;
1695 }
1696 NSArray *missileRoles = [dict oo_arrayForKey:@"missile_roles"];
1697 if (missileRoles != nil)
1698 {
1699 unsigned missileCount = 0;
1700 for (NSUInteger roleIndex = 0; roleIndex < [missileRoles count] && missileCount < max_missiles; roleIndex++)
1701 {
1702 NSString *missile_desc = [missileRoles oo_stringAtIndex:roleIndex];
1703 if (missile_desc != nil && ![missile_desc isEqualToString:@"NONE"])
1704 {
1705 ShipEntity *amiss = [UNIVERSE newShipWithRole:missile_desc];
1706 if (amiss)
1707 {
1708 missile_list[missileCount] = [OOEquipmentType equipmentTypeWithIdentifier:missile_desc];
1709 missile_entity[missileCount] = amiss; // retain count = 1
1710 missileCount++;
1711 }
1712 else
1713 {
1714 OOLogWARN(@"load.failed.missileNotFound", @"couldn't find missile with role '%@' in [PlayerEntity setCommanderDataFromDictionary:], missile entry discarded.", missile_desc);
1715 }
1716 }
1717 missiles = missileCount;
1718 }
1719 }
1720 else // no missile_roles
1721 {
1722 for (NSUInteger i = 0; i < missiles; i++)
1723 {
1724 missile_list[i] = [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_MISSILE"];
1725 missile_entity[i] = [UNIVERSE newShipWithRole:@"EQ_MISSILE"]; // retain count = 1 - should be okay as long as we keep a missile with this role
1726 // in the base package.
1727 }
1728 }
1729
1730 if (energyBombCompensation)
1731 {
1732 /*
1733 Compensate energy bomb with either a QC mine or the cost of an
1734 energy bomb (900 credits). This must be done after missiles are
1735 set up.
1736 */
1737 if ([self mountMissileWithRole:@"EQ_QC_MINE"])
1738 {
1739 OOLog(@"load.upgrade.replacedEnergyBomb", @"%@", @"Replaced legacy energy bomb with Quirium cascade mine.");
1740 }
1741 else
1742 {
1743 credits += 9000;
1744 OOLog(@"load.upgrade.replacedEnergyBomb", @"%@", @"Compensated legacy energy bomb with 900 credits.");
1745 }
1746 }
1747
1748 [self setActiveMissile:0];
1749
1750 [self setHeatInsulation:1.0];
1751
1752 max_forward_shield = BASELINE_SHIELD_LEVEL;
1753 max_aft_shield = BASELINE_SHIELD_LEVEL;
1754
1755 forward_shield_recharge_rate = 2.0;
1756 aft_shield_recharge_rate = 2.0;
1757
1758 forward_shield = [self maxForwardShieldLevel];
1759 aft_shield = [self maxAftShieldLevel];
1760
1761 // used to get current_system and target_system here,
1762 // but stores the ID in the save file instead
1763
1764 // restore subentities status
1765 [self deserializeShipSubEntitiesFrom:[dict oo_stringForKey:@"subentities_status"]];
1766
1767 // wormholes
1768 NSArray * whArray;
1769 whArray = [dict objectForKey:@"wormholes"];
1770 NSDictionary * whCurrDict;
1771 [scannedWormholes release];
1772 scannedWormholes = [[NSMutableArray alloc] initWithCapacity:[whArray count]];
1773 foreach (whCurrDict, whArray)
1774 {
1775 WormholeEntity * wh = [[WormholeEntity alloc] initWithDict:whCurrDict];
1776 [scannedWormholes addObject:wh];
1777 /* TODO - add to Universe if the wormhole hasn't expired yet; but in this case
1778 * we need to save/load position and mass as well, which we currently
1779 * don't
1780 if (equal_seeds([wh origin], system_seed))
1781 {
1782 [UNIVERSE addEntity:wh];
1783 }
1784 */
1785 }
1786
1787 // custom view no.
1788 if (_customViews != nil)
1789 _customViewIndex = [dict oo_unsignedIntForKey:@"custom_view_index"] % [_customViews count];
1790
1791
1792 // docking clearance protocol
1793 [UNIVERSE setDockingClearanceProtocolActive:[dict oo_boolForKey:@"docking_clearance_protocol" defaultValue:NO]];
1794
1795 // trumble information
1796 [self setUpTrumbles];
1797 [self setTrumbleValueFrom:[dict objectForKey:@"trumbles"]]; // if it doesn't exist we'll check user-defaults
1798
1799 return YES;
1800}
1801
1802
1803
1805
1806
1807/* Nasty initialization mechanism:
1808 PlayerEntity is alloced and inited on demand by +sharedPlayer. This
1809 initialization doesn't actually set anything up -- apart from the
1810 assertion, it's like doing a bare alloc. -deferredInit does the work
1811 that -init "should" be doing. It assumes that -[ShipEntity initWithKey:
1812 definition:] will not return an object other than self.
1813 This is necessary because we need a pointer to the PlayerEntity early in
1814 startup, when ship data hasn't been loaded yet. In particular, we need
1815 a pointer to the player to set up the JavaScript environment, we need the
1816 JavaScript environment to set up OpenGL, and we need OpenGL set up to load
1817 ships.
1818*/
1819- (id) init
1820{
1821 NSAssert(gOOPlayer == nil, @"Expected only one PlayerEntity to exist at a time.");
1822 return [super initBypassForPlayer];
1823}
1824
1825
1826- (void) deferredInit
1827{
1828 NSAssert(gOOPlayer == self, @"Expected only one PlayerEntity to exist at a time.");
1829 NSAssert([super initWithKey:PLAYER_SHIP_DESC definition:[NSDictionary dictionary]] == self, @"PlayerEntity requires -[ShipEntity initWithKey:definition:] to return unmodified self.");
1830
1831 maxFieldOfView = MAX_FOV;
1832#if OO_FOV_INFLIGHT_CONTROL_ENABLED
1833 fov_delta = 2.0; // multiply by 2 each second
1834#endif
1835
1836 compassMode = COMPASS_MODE_BASIC;
1837
1838 afterburnerSoundLooping = NO;
1839
1840 isPlayer = YES;
1841
1842 [self setStatus:STATUS_START_GAME];
1843
1844 int i;
1845 for (i = 0; i < PLAYER_MAX_MISSILES; i++)
1846 {
1847 missile_entity[i] = nil;
1848 }
1849 [self setUpAndConfirmOK:NO];
1850
1851 save_path = nil;
1852
1853 scoopsActive = NO;
1854
1855 target_memory_index = 0;
1856
1857 DESTROY(dockingReport);
1858 dockingReport = [[NSMutableString alloc] init];
1859 [hud resetGuis:[NSDictionary dictionaryWithObjectsAndKeys:[NSDictionary dictionary], @"message_gui",
1860 [NSDictionary dictionary], @"comm_log_gui", nil]];
1861
1862 [self initControls];
1863}
1864
1865
1866- (BOOL) setUpAndConfirmOK:(BOOL)stopOnError
1867{
1868 return [self setUpAndConfirmOK:stopOnError saveGame:NO];
1869}
1870
1871
1872- (BOOL) setUpAndConfirmOK:(BOOL)stopOnError saveGame:(BOOL)saveGame
1873{
1874 fieldOfView = [[UNIVERSE gameView] fov:YES];
1875 unsigned i;
1876
1877 showDemoShips = NO;
1878 show_info_flag = NO;
1879 DESTROY(marketSelectedCommodity);
1880
1881 // Reset JavaScript.
1884
1885 GameController *gc = [[UNIVERSE gameView] gameController];
1886
1887 if (![gc inFullScreenMode] && stopOnError) [gc stopAnimationTimer]; // start of critical section
1888
1889 if (EXPECT_NOT(![[OOJavaScriptEngine sharedEngine] reset] && stopOnError)) // always (try to) reset the engine, then find out if we need to stop.
1890 {
1891 /*
1892 Occasionally there's a racing condition between timers being deleted
1893 and the js engine needing to be reset: the engine reset stops the timers
1894 from being deleted, and undeleted timers don't allow the engine to reset
1895 itself properly.
1896
1897 If the engine can't reset, let's give ourselves an extra 20ms to allow the
1898 timers to delete themselves.
1899
1900 We'll piggyback performDeadUpdates: when STATUS_DEAD, the engine waits until
1901 kDeadResetTime then restarts Oolite via [UNIVERSE updateGameOver]
1902 The variable shot_time is used to keep track of how long ago we were
1903 shot.
1904
1905 If we're loading a savegame the code will try a new JS reset immediately
1906 after failing this reset...
1907 */
1908
1909 // set up STATUS_DEAD
1910 [self setDockedStation:nil]; // needed for STATUS_DEAD
1911 [self setStatus:STATUS_DEAD];
1912 OOLog(@"script.javascript.init.error", @"%@", @"Scheduling new JavaScript reset.");
1913 shot_time = kDeadResetTime - 0.02f; // schedule reinit 20 milliseconds from now.
1914
1915 if (![gc inFullScreenMode]) [gc startAnimationTimer]; // keep the game ticking over.
1916 return NO;
1917 }
1918
1919 // end of critical section
1920 if (![gc inFullScreenMode] && stopOnError) [gc startAnimationTimer];
1921
1922 // Load locale script before any regular scripts.
1923 [OOJSScript jsScriptFromFileNamed:@"oolite-locale-functions.js"
1924 properties:nil];
1925
1926 [[GameController sharedController] logProgress:DESC(@"loading-scripts")];
1927
1928 [UNIVERSE setBlockJSPlayerShipProps:NO]; // full access to player.ship properties!
1929 DESTROY(worldScripts);
1930 DESTROY(worldScriptsRequiringTickle);
1931 DESTROY(commodityScripts);
1932
1933#if OOLITE_WINDOWS
1934 if (saveGame)
1935 {
1936 [UNIVERSE preloadSounds];
1937 [self setUpSound];
1938 worldScripts = [[ResourceManager loadScripts] retain];
1939 [UNIVERSE loadConditionScripts];
1940 commodityScripts = [[NSMutableDictionary alloc] init];
1941 }
1942#else
1943 /* on OSes that allow safe deletion of open files, can use sounds
1944 * on the OXZ screen and other start screens */
1945 [UNIVERSE preloadSounds];
1946 [self setUpSound];
1947 if (saveGame)
1948 {
1949 worldScripts = [[ResourceManager loadScripts] retain];
1950 [UNIVERSE loadConditionScripts];
1951 commodityScripts = [[NSMutableDictionary alloc] init];
1952 }
1953#endif
1954
1955 // make sure extraGuiScreenKeys is clear
1956 DESTROY(extraGuiScreenKeys);
1957
1958 [[GameController sharedController] logProgress:OOExpandKeyRandomized(@"loading-miscellany")];
1959
1960 // if there is cargo remaining from previously (e.g. a game restart), remove it
1961 if ([self cargoList] != nil)
1962 {
1963 [self removeAllCargo:YES]; // force removal of cargo
1964 }
1965
1966 [self setShipDataKey:PLAYER_SHIP_DESC];
1967 ship_trade_in_factor = 95;
1968
1969 // reset HUD & default commlog behaviour
1970 [UNIVERSE setAutoCommLog:YES];
1971 [UNIVERSE setPermanentCommLog:NO];
1972
1973 [multiFunctionDisplayText release];
1974 multiFunctionDisplayText = [[NSMutableDictionary alloc] init];
1975
1976 [multiFunctionDisplaySettings release];
1977 multiFunctionDisplaySettings = [[NSMutableArray alloc] init];
1978
1979 [customDialSettings release];
1980 customDialSettings = [[NSMutableDictionary alloc] init];
1981
1982 [self switchHudTo:@"hud.plist"];
1983 scanner_zoom_rate = 0.0f;
1984 longRangeChartMode = OOLRC_MODE_SUNCOLOR;
1985
1986 [mission_variables release];
1987 mission_variables = [[NSMutableDictionary alloc] init];
1988
1989 [localVariables release];
1990 localVariables = [[NSMutableDictionary alloc] init];
1991
1992 [self setScriptTarget:nil];
1993 [self resetMissionChoice];
1994 [[UNIVERSE gameView] resetTypedString];
1995 found_system_id = -1;
1996
1997 [reputation release];
1998 reputation = [[NSMutableDictionary alloc] initWithCapacity:6];
1999 [reputation oo_setInteger:0 forKey:CONTRACTS_GOOD_KEY];
2000 [reputation oo_setInteger:0 forKey:CONTRACTS_BAD_KEY];
2001 [reputation oo_setInteger:MAX_CONTRACT_REP forKey:CONTRACTS_UNKNOWN_KEY];
2002 [reputation oo_setInteger:0 forKey:PASSAGE_GOOD_KEY];
2003 [reputation oo_setInteger:0 forKey:PASSAGE_BAD_KEY];
2004 [reputation oo_setInteger:MAX_CONTRACT_REP forKey:PASSAGE_UNKNOWN_KEY];
2005 [reputation oo_setInteger:0 forKey:PARCEL_GOOD_KEY];
2006 [reputation oo_setInteger:0 forKey:PARCEL_BAD_KEY];
2007 [reputation oo_setInteger:MAX_CONTRACT_REP forKey:PARCEL_UNKNOWN_KEY];
2008
2009 DESTROY(roleWeights);
2010 roleWeights = [[NSMutableArray alloc] initWithCapacity:8];
2011 for (i = 0 ; i < 8 ; i++)
2012 {
2013 [roleWeights addObject:@"player-unknown"];
2014 }
2015 DESTROY(roleWeightFlags);
2016 roleWeightFlags = [[NSMutableDictionary alloc] init];
2017
2018 DESTROY(roleSystemList);
2019 roleSystemList = [[NSMutableArray alloc] initWithCapacity:32];
2020
2021 energy = 256;
2022 weapon_temp = 0.0f;
2023 forward_weapon_temp = 0.0f;
2024 aft_weapon_temp = 0.0f;
2025 port_weapon_temp = 0.0f;
2026 starboard_weapon_temp = 0.0f;
2027 lastShot = nil;
2028 forward_shot_time = INITIAL_SHOT_TIME;
2029 aft_shot_time = INITIAL_SHOT_TIME;
2030 port_shot_time = INITIAL_SHOT_TIME;
2031 starboard_shot_time = INITIAL_SHOT_TIME;
2032 ship_temperature = 60.0f;
2033 alertFlags = 0;
2034 hyperspeed_engaged = NO;
2035 autopilot_engaged = NO;
2036 velocity = kZeroVector;
2037
2038 flightRoll = 0.0f;
2039 flightPitch = 0.0f;
2040 flightYaw = 0.0f;
2041
2042 max_passengers = 0;
2043 [passengers release];
2044 passengers = [[NSMutableArray alloc] init];
2045 [passenger_record release];
2046 passenger_record = [[NSMutableDictionary alloc] init];
2047
2048 [contracts release];
2049 contracts = [[NSMutableArray alloc] init];
2050 [contract_record release];
2051 contract_record = [[NSMutableDictionary alloc] init];
2052
2053 [parcels release];
2054 parcels = [[NSMutableArray alloc] init];
2055 [parcel_record release];
2056 parcel_record = [[NSMutableDictionary alloc] init];
2057
2058 [missionDestinations release];
2059 missionDestinations = [[NSMutableDictionary alloc] init];
2060
2061 [shipyard_record release];
2062 shipyard_record = [[NSMutableDictionary alloc] init];
2063
2064 [target_memory release];
2065 target_memory = [[NSMutableArray alloc] initWithCapacity:PLAYER_TARGET_MEMORY_SIZE];
2066 [self clearTargetMemory]; // also does first-time initialisation
2067
2068 [self setMissionOverlayDescriptor:nil];
2069 [self setMissionBackgroundDescriptor:nil];
2070 [self setMissionBackgroundSpecial:nil];
2071 [self setEquipScreenBackgroundDescriptor:nil];
2072 marketOffset = 0;
2073 DESTROY(marketSelectedCommodity);
2074
2075 script_time = 0.0;
2076 script_time_check = SCRIPT_TIMER_INTERVAL;
2077 script_time_interval = SCRIPT_TIMER_INTERVAL;
2078
2079 NSCalendarDate *nowDate = [NSCalendarDate calendarDate];
2080 ship_clock = PLAYER_SHIP_CLOCK_START;
2081 ship_clock += [nowDate hourOfDay] * 3600.0;
2082 ship_clock += [nowDate minuteOfHour] * 60.0;
2083 ship_clock += [nowDate secondOfMinute];
2084 fps_check_time = ship_clock;
2085 ship_clock_adjust = 0.0;
2086 escape_pod_rescue_time = 0.0;
2087
2088 isSpeechOn = OOSPEECHSETTINGS_OFF;
2089#if OOLITE_ESPEAK
2090 voice_gender_m = YES;
2091 voice_no = [UNIVERSE setVoice:-1 withGenderM:voice_gender_m];
2092#endif
2093
2094 [_customViews release];
2095 _customViews = nil;
2096 _customViewIndex = 0;
2097
2098 mouse_control_on = NO;
2099
2100 // player commander data
2101 // Most of this is probably also set more than once
2102
2103 [self setCommanderName:PLAYER_DEFAULT_NAME];
2104 [self setLastsaveName:PLAYER_DEFAULT_NAME];
2105
2106 galaxy_coordinates = NSMakePoint(0x14,0xAD); // 20,173
2107
2108 credits = 1000;
2109 fuel = PLAYER_MAX_FUEL;
2110 fuel_accumulator = 0.0f;
2111 fuel_leak_rate = 0.0f;
2112
2113 galaxy_number = 0;
2114 // will load real weapon data later
2115 forward_weapon_type = nil;
2116 aft_weapon_type = nil;
2117 port_weapon_type = nil;
2118 starboard_weapon_type = nil;
2119 scannerRange = (float)SCANNER_MAX_RANGE;
2120
2121 weapons_online = YES;
2122
2123 ecm_in_operation = NO;
2124 last_ecm_time = [UNIVERSE getTime];
2125 compassMode = COMPASS_MODE_BASIC;
2126 ident_engaged = NO;
2127
2128 max_cargo = 20; // will be reset later
2129 marketFilterMode = MARKET_FILTER_MODE_OFF;
2130
2131 DESTROY(shipCommodityData);
2132 shipCommodityData = [[[UNIVERSE commodities] generateManifestForPlayer] retain];
2133
2134 // set up missiles
2135 missiles = PLAYER_STARTING_MISSILES;
2136 max_missiles = PLAYER_STARTING_MAX_MISSILES;
2137
2138 [eqScripts release];
2139 eqScripts = [[NSMutableArray alloc] init];
2140 primedEquipment = 0;
2141 [self setFastEquipmentA:@"EQ_CLOAKING_DEVICE"];
2142 [self setFastEquipmentB:@"EQ_ENERGY_BOMB"]; // for compatibility purposes
2143
2144 [self setActiveMissile:0];
2145 for (i = 0; i < missiles; i++)
2146 {
2147 [missile_entity[i] release];
2148 missile_entity[i] = nil;
2149 }
2150 [self safeAllMissiles];
2151
2152 [self clearSubEntities];
2153
2154 legalStatus = 0;
2155
2156 market_rnd = 0;
2157 ship_kills = 0;
2158 chart_centre_coordinates = galaxy_coordinates;
2159 target_chart_centre = chart_centre_coordinates;
2160 cursor_coordinates = galaxy_coordinates;
2161 chart_focus_coordinates = cursor_coordinates;
2162 target_chart_focus = chart_focus_coordinates;
2163 chart_zoom = 1.0;
2164 target_chart_zoom = 1.0;
2165 saved_chart_zoom = 1.0;
2166 ANA_mode = OPTIMIZED_BY_NONE;
2167
2168
2169 scripted_misjump = NO;
2170 _scriptedMisjumpRange = 0.5;
2171 scoopOverride = NO;
2172
2173 max_forward_shield = BASELINE_SHIELD_LEVEL;
2174 max_aft_shield = BASELINE_SHIELD_LEVEL;
2175
2176 forward_shield_recharge_rate = 2.0;
2177 aft_shield_recharge_rate = 2.0;
2178
2179 forward_shield = [self maxForwardShieldLevel];
2180 aft_shield = [self maxAftShieldLevel];
2181
2182 scanClass = CLASS_PLAYER;
2183
2184 [UNIVERSE clearGUIs];
2185
2186 dockingClearanceStatus = DOCKING_CLEARANCE_STATUS_GRANTED;
2187 targetDockStation = nil;
2188
2189 [self setDockedStation:[UNIVERSE station]];
2190
2191 [commLog release];
2192 commLog = nil;
2193
2194 [specialCargo release];
2195 specialCargo = nil;
2196
2197 // views
2198 forwardViewOffset = kZeroVector;
2199 aftViewOffset = kZeroVector;
2200 portViewOffset = kZeroVector;
2201 starboardViewOffset = kZeroVector;
2202 customViewOffset = kZeroVector;
2203
2204 currentWeaponFacing = WEAPON_FACING_FORWARD;
2205 [self currentWeaponStats];
2206
2207 [save_path autorelease];
2208 save_path = nil;
2209
2210 [scannedWormholes release];
2211 scannedWormholes = [[NSMutableArray alloc] init];
2212
2213 [self setUpTrumbles];
2214
2215 suppressTargetLost = NO;
2216
2217 scoopsActive = NO;
2218
2219 [dockingReport release];
2220 dockingReport = [[NSMutableString alloc] init];
2221
2222 [shipAI release];
2223 shipAI = [[AI alloc] initWithStateMachine:PLAYER_DOCKING_AI_NAME andState:@"GLOBAL"];
2224 [self resetAutopilotAI];
2225
2226 lastScriptAlertCondition = [self alertCondition];
2227
2228 entity_personality = ranrot_rand() & 0x7FFF;
2229
2230 [self setSystemID:[UNIVERSE findSystemNumberAtCoords:[self galaxy_coordinates] withGalaxy:galaxy_number includingHidden:YES]];
2231 [UNIVERSE setGalaxyTo:galaxy_number];
2232 [UNIVERSE setSystemTo:system_id];
2233
2234 [self setUpWeaponSounds];
2235
2236 [self setGalacticHyperspaceBehaviourTo:[[UNIVERSE globalSettings] oo_stringForKey:@"galactic_hyperspace_behaviour" defaultValue:@"BEHAVIOUR_STANDARD"]];
2237 [self setGalacticHyperspaceFixedCoordsTo:[[UNIVERSE globalSettings] oo_stringForKey:@"galactic_hyperspace_fixed_coords" defaultValue:@"96 96"]];
2238
2239 cloaking_device_active = NO;
2240
2241 demoShip = nil;
2242
2244 [stickProfileScreen release];
2245 stickProfileScreen = [[StickProfileScreen alloc] init];
2246 return YES;
2247}
2248
2249
2250- (void) completeSetUp
2251{
2252 [self completeSetUpAndSetTarget:YES];
2253}
2254
2255
2256- (void) completeSetUpAndSetTarget:(BOOL)setTarget
2257{
2259
2260 [self setDockedStation:[UNIVERSE station]];
2261 [self setLastAegisLock:[UNIVERSE planet]];
2262 // only do this if we're not in strict mode, otherwise all previously saved OXP key/joystick defs will be wiped.
2263 if (![[UNIVERSE useAddOns] isEqualToString:SCENARIO_OXP_DEFINITION_NONE])
2264 {
2265 [self validateCustomEquipActivationArray];
2266 }
2267
2268 JSContext *context = OOJSAcquireContext();
2269 [self doWorldScriptEvent:OOJSID("startUp") inContext:context withArguments:NULL count:0 timeLimit:MAX(0.0, [[NSUserDefaults standardUserDefaults] oo_floatForKey:@"start-script-limit-value" defaultValue:kOOJSLongTimeLimit])];
2270 OOJSRelinquishContext(context);
2271}
2272
2273
2274- (void) startUpComplete
2275{
2276 JSContext *context = OOJSAcquireContext();
2277 [self doWorldScriptEvent:OOJSID("startUpComplete") inContext:context withArguments:NULL count:0 timeLimit:kOOJSLongTimeLimit];
2278 OOJSRelinquishContext(context);
2279}
2280
2281
2282- (BOOL) setUpShipFromDictionary:(NSDictionary *)shipDict
2283{
2284 DESTROY(compassTarget);
2285 [UNIVERSE setBlockJSPlayerShipProps:NO]; // full access to player.ship properties!
2286
2287 if (![super setUpFromDictionary:shipDict]) return NO;
2288
2289 DESTROY(cargo);
2290 cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo];
2291
2292 // Player-only settings.
2293 //
2294 // set control factors..
2295 roll_delta = 2.0f * max_flight_roll;
2296 pitch_delta = 2.0f * max_flight_pitch;
2297 yaw_delta = 2.0f * max_flight_yaw;
2298
2299 energy = maxEnergy;
2300 //if (forward_weapon_type == WEAPON_NONE) [self setWeaponDataFromType:forward_weapon_type];
2301 scannerRange = (float)SCANNER_MAX_RANGE;
2302
2303 [roleSet release];
2304 roleSet = nil;
2305 [self setPrimaryRole:@"player"];
2306
2307 [self removeAllEquipment];
2308 [self addEquipmentFromCollection:[shipDict objectForKey:@"extra_equipment"]];
2309
2310 [self resetHud];
2311 [hud setHidden:NO];
2312
2313 // set up missiles
2314 // sanity check the number of missiles...
2315 if (max_missiles > PLAYER_MAX_MISSILES) max_missiles = PLAYER_MAX_MISSILES;
2316 if (missiles > max_missiles) missiles = max_missiles;
2317 // end sanity check
2318
2319 unsigned i;
2320 for (i = 0; i < PLAYER_MAX_MISSILES; i++)
2321 {
2322 [missile_entity[i] release];
2323 missile_entity[i] = nil;
2324 }
2325 for (i = 0; i < missiles; i++)
2326 {
2327 missile_list[i] = [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_MISSILE"];
2328 missile_entity[i] = [UNIVERSE newShipWithRole:@"EQ_MISSILE"]; // retain count = 1
2329 }
2330
2331 DESTROY(_primaryTarget);
2332 [self safeAllMissiles];
2333 [self setActiveMissile:0];
2334
2335 // set view offsets
2336 [self setDefaultViewOffsets];
2337
2338 if (EXPECT(_scaleFactor == 1.0f))
2339 {
2340 forwardViewOffset = [shipDict oo_vectorForKey:@"view_position_forward" defaultValue:forwardViewOffset];
2341 aftViewOffset = [shipDict oo_vectorForKey:@"view_position_aft" defaultValue:aftViewOffset];
2342 portViewOffset = [shipDict oo_vectorForKey:@"view_position_port" defaultValue:portViewOffset];
2343 starboardViewOffset = [shipDict oo_vectorForKey:@"view_position_starboard" defaultValue:starboardViewOffset];
2344 }
2345 else
2346 {
2347 forwardViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_forward" defaultValue:forwardViewOffset],_scaleFactor);
2348 aftViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_aft" defaultValue:aftViewOffset],_scaleFactor);
2349 portViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_port" defaultValue:portViewOffset],_scaleFactor);
2350 starboardViewOffset = vector_multiply_scalar([shipDict oo_vectorForKey:@"view_position_starboard" defaultValue:starboardViewOffset],_scaleFactor);
2351 }
2352
2353 [self setDefaultCustomViews];
2354
2355 NSArray *customViews = [shipDict oo_arrayForKey:@"custom_views"];
2356 if (customViews != nil)
2357 {
2358 [_customViews release];
2359 _customViews = [customViews retain];
2360 _customViewIndex = 0;
2361 }
2362
2363 massLockable = [shipDict oo_boolForKey:@"mass_lockable" defaultValue:YES];
2364
2365 // Load js script
2366 [script autorelease];
2367 NSDictionary *scriptProperties = [NSDictionary dictionaryWithObject:self forKey:@"ship"];
2368 script = [OOScript jsScriptFromFileNamed:[shipDict oo_stringForKey:@"script"]
2369 properties:scriptProperties];
2370 if (script == nil)
2371 {
2372 // Do not switch to using a default value above; we want to use the default script if loading fails.
2373 script = [OOScript jsScriptFromFileNamed:@"oolite-default-player-script.js"
2374 properties:scriptProperties];
2375 }
2376 [script retain];
2377
2378 return YES;
2379}
2380
2381- (void) dealloc
2382{
2383 DESTROY(compassTarget);
2384 DESTROY(hud);
2385 DESTROY(multiFunctionDisplayText);
2386 DESTROY(multiFunctionDisplaySettings);
2387 DESTROY(customDialSettings);
2388
2389 DESTROY(commLog);
2390 DESTROY(keyconfig2_settings);
2391 DESTROY(target_memory);
2392
2393 DESTROY(_fastEquipmentA);
2394 DESTROY(_fastEquipmentB);
2395
2396 DESTROY(eqScripts);
2397 DESTROY(worldScripts);
2398 DESTROY(worldScriptsRequiringTickle);
2399 DESTROY(commodityScripts);
2400 DESTROY(mission_variables);
2401
2402 DESTROY(localVariables);
2403
2404 DESTROY(lastTextKey);
2405
2406 DESTROY(marketSelectedCommodity);
2407 DESTROY(reputation);
2408 DESTROY(roleWeights);
2409 DESTROY(roleWeightFlags);
2410 DESTROY(roleSystemList);
2411 DESTROY(passengers);
2412 DESTROY(passenger_record);
2413 DESTROY(contracts);
2414 DESTROY(contract_record);
2415 DESTROY(parcels);
2416 DESTROY(parcel_record);
2417 DESTROY(missionDestinations);
2418 DESTROY(shipyard_record);
2419
2420 DESTROY(_missionOverlayDescriptor);
2421 DESTROY(_missionBackgroundDescriptor);
2422 DESTROY(_equipScreenBackgroundDescriptor);
2423
2424 DESTROY(_commanderName);
2425 DESTROY(_lastsaveName);
2426 DESTROY(shipCommodityData);
2427
2428 DESTROY(specialCargo);
2429
2430 DESTROY(save_path);
2431 DESTROY(scenarioKey);
2432
2433 DESTROY(_customViews);
2434 DESTROY(lastShot);
2435
2436 DESTROY(dockingReport);
2437
2438 DESTROY(_jumpCause);
2439
2440 [self destroySound];
2441
2442 DESTROY(scannedWormholes);
2443 DESTROY(wormhole);
2444
2445 int i;
2446 for (i = 0; i < PLAYER_MAX_MISSILES; i++) DESTROY(missile_entity[i]);
2447 for (i = 0; i < PLAYER_MAX_TRUMBLES; i++) DESTROY(trumble[i]);
2448
2449 DESTROY(keyShiftText);
2450 DESTROY(keyMod1Text);
2451 DESTROY(keyMod2Text);
2452 DESTROY(stickFunctions);
2453 DESTROY(keyFunctions);
2454 DESTROY(kbdLayouts);
2455
2456 DESTROY(customEquipActivation);
2457 DESTROY(customActivatePressed);
2458 DESTROY(customModePressed);
2459
2460 DESTROY(extraGuiScreenKeys);
2461
2462 [super dealloc];
2463}
2464
2465
2466- (NSUInteger) sessionID
2467{
2468 // The player ship always belongs to the current session.
2469 return [UNIVERSE sessionID];
2470}
2471
2472
2473- (void) warnAboutHostiles
2474{
2475 [self playHostileWarning];
2476}
2477
2478
2479- (BOOL) canCollide
2480{
2481 switch ([self status])
2482 {
2483 case STATUS_START_GAME:
2484 case STATUS_DOCKING:
2485 case STATUS_DOCKED:
2486 case STATUS_DEAD:
2487 case STATUS_ESCAPE_SEQUENCE:
2488 return NO;
2489
2490 default:
2491 return YES;
2492 }
2493}
2494
2495
2496- (NSComparisonResult) compareZeroDistance:(Entity *)otherEntity
2497{
2498 return NSOrderedDescending; // always the most near
2499}
2500
2501
2502- (BOOL) validForAddToUniverse
2503{
2504 return YES;
2505}
2506
2507
2508- (GLfloat) lookingAtSunWithThresholdAngleCos:(GLfloat) thresholdAngleCos
2509{
2510 OOSunEntity *sun = [UNIVERSE sun];
2511 GLfloat measuredCos = 999.0f, measuredCosAbs;
2512 GLfloat sunBrightness = 0.0f;
2513 Vector relativePosition, unitRelativePosition;
2514
2515 if (EXPECT_NOT(!sun)) return 0.0f;
2516
2517 // check if camera position is shadowed
2518 OOViewID vdir = [UNIVERSE viewDirection];
2519 unsigned i;
2520 unsigned ent_count = UNIVERSE->n_entities;
2521 Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list
2522 for (i = 0; i < ent_count; i++)
2523 {
2524 if (uni_entities[i]->isSunlit)
2525 {
2526 if ([uni_entities[i] isPlanet] ||
2527 ([uni_entities[i] isShip] &&
2528 [uni_entities[i] isVisible]))
2529 {
2530 // the player ship can't shadow internal views
2531 if (EXPECT(vdir > VIEW_STARBOARD || ![uni_entities[i] isPlayer]))
2532 {
2533 float shadow = 1.5f;
2534 shadowAtPointOcclusionToValue([self viewpointPosition],1.0f,uni_entities[i],sun,&shadow);
2535 /* BUG: if the shadowing entity is not spherical, this gives over-shadowing. True elsewhere as well, but not so obvious there. */
2536 if (shadow < 1) {
2537 return 0.0f;
2538 }
2539 }
2540 }
2541 }
2542 }
2543
2544
2545 relativePosition = HPVectorToVector(HPvector_subtract([self viewpointPosition], [sun position]));
2546 unitRelativePosition = vector_normal_or_zbasis(relativePosition);
2547 switch (vdir)
2548 {
2549 case VIEW_FORWARD:
2550 measuredCos = -dot_product(unitRelativePosition, v_forward);
2551 break;
2552 case VIEW_AFT:
2553 measuredCos = +dot_product(unitRelativePosition, v_forward);
2554 break;
2555 case VIEW_PORT:
2556 measuredCos = +dot_product(unitRelativePosition, v_right);
2557 break;
2558 case VIEW_STARBOARD:
2559 measuredCos = -dot_product(unitRelativePosition, v_right);
2560 break;
2561 case VIEW_CUSTOM:
2562 {
2563 Vector relativeView = [self customViewForwardVector];
2564 Vector absoluteView = quaternion_rotate_vector(quaternion_conjugate([self orientation]),relativeView);
2565 measuredCos = -dot_product(unitRelativePosition, absoluteView);
2566 }
2567 break;
2568
2569 default:
2570 break;
2571 }
2572 measuredCosAbs = fabs(measuredCos);
2573 /*
2574 Bugfix: 1.1f - floating point errors can mean the dot product of two
2575 normalised vectors can be very slightly more than 1, which can
2576 cause extreme flickering of the glare at certain ranges to the
2577 sun. The real test is just that it's not still 999 - CIM
2578 */
2579 if (thresholdAngleCos <= measuredCosAbs && measuredCosAbs <= 1.1f) // angle from viewpoint to sun <= desired threshold
2580 {
2581 sunBrightness = (measuredCos - thresholdAngleCos) / (1.0f - thresholdAngleCos);
2582// OOLog(@"glare.debug",@"raw brightness = %f",sunBrightness);
2583 if (sunBrightness < 0.0f) sunBrightness = 0.0f;
2584 else if (sunBrightness > 1.0f) sunBrightness = 1.0f;
2585 }
2586// OOLog(@"glare.debug",@"cos=%f, threshold = %f, brightness = %f",measuredCosAbs,thresholdAngleCos,sunBrightness);
2587 return sunBrightness * sunBrightness * sunBrightness;
2588}
2589
2590
2591- (GLfloat) insideAtmosphereFraction
2592{
2593 GLfloat insideAtmoFrac = 0.0f;
2594
2595 if ([UNIVERSE airResistanceFactor] > 0.01) // player is inside planetary atmosphere
2596 {
2597 insideAtmoFrac = 1.0f - ([self dialAltitude] * (GLfloat)PLAYER_DIAL_MAX_ALTITUDE / (10.0f * (GLfloat)ATMOSPHERE_DEPTH));
2598 }
2599
2600 return insideAtmoFrac;
2601}
2602
2603
2604#ifndef NDEBUG
2605#define STAGE_TRACKING_BEGIN { \
2606 NSString * volatile updateStage = @"initialisation"; \
2607 @try {
2608#define STAGE_TRACKING_END } \
2609 @catch (NSException *exception) \
2610 { \
2611 OOLog(kOOLogException, @"***** Exception during [%@] in %s : %@ : %@ *****", updateStage, __PRETTY_FUNCTION__, [exception name], [exception reason]); \
2612 @throw exception; \
2613 } \
2614 }
2615#define UPDATE_STAGE(x) do { updateStage = (x); } while (0)
2616#else
2617#define STAGE_TRACKING_BEGIN {
2618#define STAGE_TRACKING_END }
2619#define UPDATE_STAGE(x) do { (void) (x); } while (0);
2620#endif
2621
2622
2623- (void) update:(OOTimeDelta)delta_t
2624{
2626
2627 UPDATE_STAGE(@"updateMovementFlags");
2628 [self updateMovementFlags];
2629 UPDATE_STAGE(@"updateAlertCondition");
2630 [self updateAlertCondition];
2631 UPDATE_STAGE(@"updateFuelScoops:");
2632 [self updateFuelScoops:delta_t];
2633
2634 UPDATE_STAGE(@"updateClocks:");
2635 [self updateClocks:delta_t];
2636
2637 // scripting
2638 UPDATE_STAGE(@"updateTimers");
2640 UPDATE_STAGE(@"checkScriptsIfAppropriate");
2641 [self checkScriptsIfAppropriate];
2642
2643 // deal with collisions
2644 UPDATE_STAGE(@"manageCollisions");
2645 [self manageCollisions];
2646
2647 UPDATE_STAGE(@"pollControls:");
2648 [self pollControls:delta_t];
2649
2650 UPDATE_STAGE(@"updateTrumbles:");
2651 [self updateTrumbles:delta_t];
2652
2653 OOEntityStatus status = [self status];
2654 /* Validate that if the status is STATUS_START_GAME we're on one
2655 * of the few GUI screens which that makes sense for */
2656 if (EXPECT_NOT(status == STATUS_START_GAME &&
2657 gui_screen != GUI_SCREEN_INTRO1 &&
2658 gui_screen != GUI_SCREEN_SHIPLIBRARY &&
2659 gui_screen != GUI_SCREEN_GAMEOPTIONS &&
2660 gui_screen != GUI_SCREEN_STICKMAPPER &&
2661 gui_screen != GUI_SCREEN_STICKPROFILE &&
2662 gui_screen != GUI_SCREEN_NEWGAME &&
2663 gui_screen != GUI_SCREEN_OXZMANAGER &&
2664 gui_screen != GUI_SCREEN_LOAD &&
2665 gui_screen != GUI_SCREEN_KEYBOARD &&
2666 gui_screen != GUI_SCREEN_KEYBOARD_CONFIRMCLEAR &&
2667 gui_screen != GUI_SCREEN_KEYBOARD_CONFIG &&
2668 gui_screen != GUI_SCREEN_KEYBOARD_ENTRY &&
2669 gui_screen != GUI_SCREEN_KEYBOARD_LAYOUT))
2670 {
2671 // and if not, do a restart of the GUI
2672 UPDATE_STAGE(@"setGuiToIntroFirstGo:");
2673 [self setGuiToIntroFirstGo:YES]; //set up demo mode
2674 }
2675
2676 if (status == STATUS_AUTOPILOT_ENGAGED || status == STATUS_ESCAPE_SEQUENCE)
2677 {
2678 UPDATE_STAGE(@"performAutopilotUpdates:");
2679 [self performAutopilotUpdates:delta_t];
2680 }
2681 else if (![self isDocked])
2682 {
2683 UPDATE_STAGE(@"performInFlightUpdates:");
2684 [self performInFlightUpdates:delta_t];
2685 }
2686
2687 /* NOTE: status-contingent updates are not a switch since they can
2688 cascade when status changes.
2689 */
2690 if (status == STATUS_IN_FLIGHT)
2691 {
2692 UPDATE_STAGE(@"doBookkeeping:");
2693 [self doBookkeeping:delta_t];
2694 }
2695 if (status == STATUS_WITCHSPACE_COUNTDOWN)
2696 {
2697 UPDATE_STAGE(@"performWitchspaceCountdownUpdates:");
2698 [self performWitchspaceCountdownUpdates:delta_t];
2699 }
2700 if (status == STATUS_EXITING_WITCHSPACE)
2701 {
2702 UPDATE_STAGE(@"performWitchspaceExitUpdates:");
2703 [self performWitchspaceExitUpdates:delta_t];
2704 }
2705 if (status == STATUS_LAUNCHING)
2706 {
2707 UPDATE_STAGE(@"performLaunchingUpdates:");
2708 [self performLaunchingUpdates:delta_t];
2709 }
2710 if (status == STATUS_DOCKING)
2711 {
2712 UPDATE_STAGE(@"performDockingUpdates:");
2713 [self performDockingUpdates:delta_t];
2714 }
2715 if (status == STATUS_DEAD)
2716 {
2717 UPDATE_STAGE(@"performDeadUpdates:");
2718 [self performDeadUpdates:delta_t];
2719 }
2720
2721 UPDATE_STAGE(@"updateWormholes");
2722 [self updateWormholes];
2723
2725}
2726
2727
2728- (void) doBookkeeping:(double) delta_t
2729{
2731
2732 double speed_delta = SHIP_THRUST_FACTOR * thrust;
2733
2734 static BOOL gettingInterference = NO;
2735
2736 OOSunEntity *sun = [UNIVERSE sun];
2737 double external_temp = 0;
2738 GLfloat air_friction = 0.0f;
2739 air_friction = 0.5f * [UNIVERSE airResistanceFactor];
2740 if (air_friction < 0.005f) // aRF < 0.01
2741 {
2742 // stops mysteriously overheating and exploding in the middle of empty space
2743 air_friction = 0;
2744 }
2745
2746 UPDATE_STAGE(@"updating weapon temperatures and shot times");
2747 // cool all weapons.
2748 float coolAmount = WEAPON_COOLING_FACTOR * delta_t;
2749 forward_weapon_temp = fdim(forward_weapon_temp, coolAmount);
2750 aft_weapon_temp = fdim(aft_weapon_temp, coolAmount);
2751 port_weapon_temp = fdim(port_weapon_temp, coolAmount);
2752 starboard_weapon_temp = fdim(starboard_weapon_temp, coolAmount);
2753
2754 // update shot times.
2755 forward_shot_time += delta_t;
2756 aft_shot_time += delta_t;
2757 port_shot_time += delta_t;
2758 starboard_shot_time += delta_t;
2759
2760 // copy new temp & shot time to main temp & shot time
2761 switch (currentWeaponFacing)
2762 {
2764 weapon_temp = forward_weapon_temp;
2765 shot_time = forward_shot_time;
2766 break;
2767 case WEAPON_FACING_AFT:
2768 weapon_temp = aft_weapon_temp;
2769 shot_time = aft_shot_time;
2770 break;
2771 case WEAPON_FACING_PORT:
2772 weapon_temp = port_weapon_temp;
2773 shot_time = port_shot_time;
2774 break;
2776 weapon_temp = starboard_weapon_temp;
2777 shot_time = starboard_shot_time;
2778 break;
2779
2780 case WEAPON_FACING_NONE:
2781 break;
2782 }
2783
2784 // cloaking device
2785 if ([self hasCloakingDevice] && cloaking_device_active)
2786 {
2787 UPDATE_STAGE(@"updating cloaking device");
2788
2789 energy -= (float)delta_t * CLOAKING_DEVICE_ENERGY_RATE;
2790 if (energy < CLOAKING_DEVICE_MIN_ENERGY)
2791 [self deactivateCloakingDevice];
2792 }
2793
2794 // military_jammer
2795 if ([self hasMilitaryJammer])
2796 {
2797 UPDATE_STAGE(@"updating military jammer");
2798
2799 if (military_jammer_active)
2800 {
2801 energy -= (float)delta_t * MILITARY_JAMMER_ENERGY_RATE;
2802 if (energy < MILITARY_JAMMER_MIN_ENERGY)
2803 military_jammer_active = NO;
2804 }
2805 else
2806 {
2807 if (energy > 1.5 * MILITARY_JAMMER_MIN_ENERGY)
2808 military_jammer_active = YES;
2809 }
2810 }
2811
2812 // ecm
2813 if (ecm_in_operation)
2814 {
2815 UPDATE_STAGE(@"updating ECM");
2816
2817 if (energy > 0.0)
2818 energy -= (float)(ECM_ENERGY_DRAIN_FACTOR * delta_t); // drain energy because of the ECM
2819 else
2820 {
2821 ecm_in_operation = NO;
2822 [UNIVERSE addMessage:DESC(@"ecm-out-of-juice") forCount:3.0];
2823 }
2824 if ([UNIVERSE getTime] > ecm_start_time + ECM_DURATION)
2825 {
2826 ecm_in_operation = NO;
2827 }
2828 }
2829
2830 // ecm interference visual effect
2831 if ([UNIVERSE useShaders] && [UNIVERSE ECMVisualFXEnabled])
2832 {
2833 // we want to start and stop the effect exactly once, not start it
2834 // or stop it on every frame
2835 if ([self scannerFuzziness] > 0.0)
2836 {
2837 if (!gettingInterference)
2838 {
2839 [UNIVERSE setCurrentPostFX:OO_POSTFX_CRTBADSIGNAL];
2840 gettingInterference = YES;
2841 }
2842 }
2843 else
2844 {
2845 if (gettingInterference)
2846 {
2847 [UNIVERSE terminatePostFX:OO_POSTFX_CRTBADSIGNAL];
2848 gettingInterference = NO;
2849 }
2850 }
2851 }
2852
2853 // Energy Banks and Shields
2854
2855 /* Shield-charging behaviour, as per Eric's proposal:
2856 1. If shields are less than a threshold, recharge with all available energy
2857 2. If energy banks are below threshold, recharge with generated energy
2858 3. Charge shields with any surplus energy
2859 */
2860 UPDATE_STAGE(@"updating energy and shield charges");
2861
2862 // 1. (Over)charge energy banks (will get normalised later)
2863 energy += [self energyRechargeRate] * delta_t;
2864
2865 // 2. Calculate shield recharge rates
2866 float fwdMax = [self maxForwardShieldLevel];
2867 float aftMax = [self maxAftShieldLevel];
2868 float shieldRechargeFwd = [self forwardShieldRechargeRate] * delta_t;
2869 float shieldRechargeAft = [self aftShieldRechargeRate] * delta_t;
2870 /* there is potential for negative rechargeFwd and rechargeAFt values here
2871 (e.g. getting shield boosters damaged while shields are full). This may
2872 lead to energy being gained rather than consumed when recharging. Leaving
2873 as-is for now, as there might be OXPs that rely in such behaviour.
2874 Boosters case example mentioned above is the only known core equipment
2875 occurrence at this time and it has been fixed inside the
2876 oolite-equipment-control.js script. - Nikos 20160104.
2877 */
2878 float rechargeFwd = MIN(shieldRechargeFwd, fwdMax - forward_shield);
2879 float rechargeAft = MIN(shieldRechargeAft, aftMax - aft_shield);
2880
2881 // Note: we've simplified this a little, so if either shield is below
2882 // the critical threshold, we allocate all energy. Ideally we
2883 // would only allocate the full recharge to the critical shield,
2884 // but doing so would add another few levels of if-then below.
2885 float energyForShields = energy;
2886 if( (forward_shield > fwdMax * 0.25) && (aft_shield > aftMax * 0.25) )
2887 {
2888 // TODO: Can this be cached anywhere sensibly (without adding another member variable)?
2889 float minEnergyBankLevel = [[UNIVERSE globalSettings] oo_floatForKey:@"shield_charge_energybank_threshold" defaultValue:0.25];
2890 energyForShields = MAX(0.0, energy -0.1 - (maxEnergy * minEnergyBankLevel)); // NB: The - 0.1 ensures the energy value does not 'bounce' across the critical energy message and causes spurious energy-low warnings
2891 }
2892
2893 if( forward_shield < aft_shield )
2894 {
2895 rechargeFwd = MIN(rechargeFwd, energyForShields);
2896 rechargeAft = MIN(rechargeAft, energyForShields - rechargeFwd);
2897 }
2898 else
2899 {
2900 rechargeAft = MIN(rechargeAft, energyForShields);
2901 rechargeFwd = MIN(rechargeFwd, energyForShields - rechargeAft);
2902 }
2903
2904 // 3. Recharge shields, drain banks, and clamp values
2905 forward_shield += rechargeFwd;
2906 aft_shield += rechargeAft;
2907 energy -= rechargeFwd + rechargeAft;
2908
2909 forward_shield = OOClamp_0_max_f(forward_shield, fwdMax);
2910 aft_shield = OOClamp_0_max_f(aft_shield, aftMax);
2911 energy = OOClamp_0_max_f(energy, maxEnergy);
2912
2913 if (sun)
2914 {
2915 UPDATE_STAGE(@"updating sun effects");
2916
2917 // set the ambient temperature here
2918 double sun_zd = sun->zero_distance; // square of distance
2919 double sun_cr = sun->collision_radius;
2920 double alt1 = sun_cr * sun_cr / sun_zd;
2921 external_temp = SUN_TEMPERATURE * alt1;
2922
2923 if ([sun goneNova])
2924 external_temp *= 100;
2925 // fuel scooping during the nova mission very unlikely
2926 if ([sun willGoNova])
2927 external_temp *= 3;
2928
2929 // do Revised sun-skimming check here...
2930 if ([self hasFuelScoop] && alt1 > 0.75 && [self fuel] < [self fuelCapacity])
2931 {
2932 fuel_accumulator += (float)(delta_t * flightSpeed * 0.010 / [self fuelChargeRate]);
2933 // are we fast enough to collect any fuel?
2934 scoopsActive = YES && flightSpeed > 0.1f;
2935 while (fuel_accumulator > 1.0f)
2936 {
2937 [self setFuel:[self fuel] + 1];
2938 fuel_accumulator -= 1.0f;
2939 [self doScriptEvent:OOJSID("shipScoopedFuel")];
2940 }
2941 [UNIVERSE displayCountdownMessage:DESC(@"fuel-scoop-active") forCount:1.0];
2942 }
2943 }
2944
2945 //Bug #11692 CmdrJames added Status entering witchspace
2946 OOEntityStatus status = [self status];
2947 if ((status != STATUS_ESCAPE_SEQUENCE) && (status != STATUS_ENTERING_WITCHSPACE))
2948 {
2949 UPDATE_STAGE(@"updating cabin temperature");
2950
2951 // work on the cabin temperature
2952 float heatInsulation = [self heatInsulation]; // Optimisation, suggested by EricW
2953 float deltaInsulation = delta_t/heatInsulation;
2954 float heatThreshold = heatInsulation * 100.0f;
2955 ship_temperature += (float)( flightSpeed * air_friction * deltaInsulation); // wind_speed
2956
2957 if (external_temp > heatThreshold && external_temp > ship_temperature)
2958 ship_temperature += (float)((external_temp - ship_temperature) * SHIP_INSULATION_FACTOR * deltaInsulation);
2959 else
2960 {
2961 if (ship_temperature > SHIP_MIN_CABIN_TEMP)
2962 ship_temperature += (float)((external_temp - heatThreshold - ship_temperature) * SHIP_COOLING_FACTOR * deltaInsulation);
2963 }
2964
2965 if (ship_temperature > SHIP_MAX_CABIN_TEMP)
2966 [self takeHeatDamage: delta_t * ship_temperature];
2967 }
2968
2969 if ((status == STATUS_ESCAPE_SEQUENCE)&&(shot_time > ESCAPE_SEQUENCE_TIME))
2970 {
2971 UPDATE_STAGE(@"resetting after escape");
2972 ShipEntity *doppelganger = (ShipEntity*)[self foundTarget];
2973 // reset legal status again! Could have changed if a previously launched missile hit a clean NPC while in the escape pod.
2974 [self setBounty:0 withReason:kOOLegalStatusReasonEscapePod];
2975 bounty = 0;
2976 thrust = max_thrust; // re-enable inertialess drives
2977 // no access to all player.ship properties while inside the escape pod,
2978 // we're not supposed to be inside our ship anymore!
2979 [self doScriptEvent:OOJSID("escapePodSequenceOver")]; // allow oxps to override the escape pod target
2984 if (EXPECT_NOT(target_system_id != system_id)) // overridden: we're going to a nearby system!
2985 {
2986 system_id = target_system_id;
2987 info_system_id = target_system_id;
2988 [UNIVERSE setSystemTo:system_id];
2989 galaxy_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:system_id inGalaxy:galaxy_number]);
2990
2991 [UNIVERSE setUpSpace];
2992 // run initial system population
2993 [UNIVERSE populateNormalSpace];
2994
2995 [self setDockTarget:[UNIVERSE station]];
2996 // send world script events to let oxps know we're in a new system.
2997 // all player.ship properties are still disabled at this stage.
2998 [UNIVERSE setWitchspaceBreakPattern:YES];
2999 [self doScriptEvent:OOJSID("shipWillExitWitchspace")];
3000 [self doScriptEvent:OOJSID("shipExitedWitchspace")];
3001
3002 [[UNIVERSE planet] update: 2.34375 * market_rnd]; // from 0..10 minutes
3003 [[UNIVERSE station] update: 2.34375 * market_rnd]; // from 0..10 minutes
3004 }
3005
3006 Entity *dockTargetEntity = [UNIVERSE entityForUniversalID:_dockTarget]; // main station in the original system, unless overridden.
3007 if ([dockTargetEntity isStation]) // fails if _dockTarget is NO_TARGET
3008 {
3009 [doppelganger becomeExplosion]; // blow up the doppelganger
3010 // restore player ship
3011 ShipEntity *player_ship = [UNIVERSE newShipWithName:[self shipDataKey]]; // retained
3012 if (player_ship)
3013 {
3014 // FIXME: this should use OOShipType, which should exist. -- Ahruman
3015 [self setMesh:[player_ship mesh]];
3016 [player_ship release]; // we only wanted it for its polygons!
3017 }
3018 [UNIVERSE setViewDirection:VIEW_FORWARD];
3019 [UNIVERSE setBlockJSPlayerShipProps:NO]; // re-enable player.ship!
3020 [self enterDock:(StationEntity *)dockTargetEntity];
3021 }
3022 else // no dock target? dock target is not a station? game over!
3023 {
3024 [self setStatus:STATUS_DEAD];
3025 //[self playGameOver]; // no death explosion sounds for player pods
3026 // no shipDied events for player pods, either
3027 [UNIVERSE displayMessage:DESC(@"gameoverscreen-escape-pod") forCount:kDeadResetTime];
3028 [UNIVERSE displayMessage:@"" forCount:kDeadResetTime];
3029 [self showGameOver];
3030 }
3031 }
3032
3033
3034 // MOVED THE FOLLOWING FROM PLAYERENTITY POLLFLIGHTCONTROLS:
3035 travelling_at_hyperspeed = (flightSpeed > maxFlightSpeed);
3036 if (hyperspeed_engaged)
3037 {
3038 UPDATE_STAGE(@"updating hyperspeed");
3039
3040 // increase speed up to maximum hyperspeed
3041 if (flightSpeed < maxFlightSpeed * HYPERSPEED_FACTOR)
3042 flightSpeed += (float)(speed_delta * delta_t * HYPERSPEED_FACTOR);
3043 if (flightSpeed > maxFlightSpeed * HYPERSPEED_FACTOR)
3044 flightSpeed = (float)(maxFlightSpeed * HYPERSPEED_FACTOR);
3045
3046 // check for mass lock
3047 hyperspeed_locked = [self massLocked];
3048 // check for mass lock & external temperature?
3049 //hyperspeed_locked = flightSpeed * air_friction > 40.0f+(ship_temperature - external_temp ) * SHIP_COOLING_FACTOR || [self massLocked];
3050
3051 if (hyperspeed_locked)
3052 {
3053 [self playJumpMassLocked];
3054 [UNIVERSE addMessage:DESC(@"jump-mass-locked") forCount:4.5];
3055 hyperspeed_engaged = NO;
3056 }
3057 }
3058 else
3059 {
3060 if (afterburner_engaged)
3061 {
3062 UPDATE_STAGE(@"updating afterburner");
3063
3064 float abFactor = [self afterburnerFactor];
3065 float maxInjectionSpeed = maxFlightSpeed * abFactor;
3066 if (flightSpeed > maxInjectionSpeed)
3067 {
3068 // decellerate to maxInjectionSpeed but slower than without afterburner.
3069 flightSpeed -= (float)(speed_delta * delta_t * abFactor);
3070 }
3071 else
3072 {
3073 if (flightSpeed < maxInjectionSpeed)
3074 flightSpeed += (float)(speed_delta * delta_t * abFactor);
3075 if (flightSpeed > maxInjectionSpeed)
3076 flightSpeed = maxInjectionSpeed;
3077 }
3078 fuel_accumulator -= (float)(delta_t * afterburner_rate);
3079 while ((fuel_accumulator < 0)&&(fuel > 0))
3080 {
3081 fuel_accumulator += 1.0f;
3082 if (--fuel <= MIN_FUEL)
3083 afterburner_engaged = NO;
3084 }
3085 }
3086 else
3087 {
3088 UPDATE_STAGE(@"slowing from hyperspeed");
3089
3090 // slow back down...
3091 if (travelling_at_hyperspeed)
3092 {
3093 // decrease speed to maximum normal speed
3094 float deceleration = (speed_delta * delta_t * HYPERSPEED_FACTOR);
3095 if (alertFlags & ALERT_FLAG_MASS_LOCK)
3096 {
3097 // decelerate much quicker in masslocks
3098 // this does also apply to injector deceleration
3099 // but it's not very noticeable
3100 deceleration *= 3;
3101 }
3102 flightSpeed -= deceleration;
3103 if (flightSpeed < maxFlightSpeed)
3104 flightSpeed = maxFlightSpeed;
3105 }
3106 }
3107 }
3108
3109
3110
3111 // fuel leakage
3112 if ((fuel_leak_rate > 0.0)&&(fuel > 0))
3113 {
3114 UPDATE_STAGE(@"updating fuel leakage");
3115
3116 fuel_accumulator -= (float)(fuel_leak_rate * delta_t);
3117 while ((fuel_accumulator < 0)&&(fuel > 0))
3118 {
3119 fuel_accumulator += 1.0f;
3120 fuel--;
3121 }
3122 if (fuel == 0)
3123 fuel_leak_rate = 0;
3124 }
3125
3126 // smart_zoom
3127 UPDATE_STAGE(@"updating scanner zoom");
3128 if (scanner_zoom_rate)
3129 {
3130 double z = [hud scannerZoom];
3131 double z1 = z + scanner_zoom_rate * delta_t;
3132 if (scanner_zoom_rate > 0.0)
3133 {
3134 if (floor(z1) > floor(z))
3135 {
3136 z1 = floor(z1);
3137 scanner_zoom_rate = 0.0f;
3138 }
3139 }
3140 else
3141 {
3142 if (z1 < 1.0)
3143 {
3144 z1 = 1.0;
3145 scanner_zoom_rate = 0.0f;
3146 }
3147 }
3148 [hud setScannerZoom:z1];
3149 }
3150
3151 [[UNIVERSE gameView] setFov:fieldOfView fromFraction:YES];
3152
3153 // scanner sanity check - lose any targets further than maximum scanner range
3154 ShipEntity *primeTarget = [self primaryTarget];
3155 if (primeTarget && HPdistance2([primeTarget position], [self position]) > SCANNER_MAX_RANGE2 && !autopilot_engaged)
3156 {
3157 [UNIVERSE addMessage:DESC(@"target-lost") forCount:3.0];
3158 [self removeTarget:primeTarget];
3159 }
3160 // compass sanity check and update target for changed mode
3161 [self validateCompassTarget];
3162
3163 // update subentities
3164 UPDATE_STAGE(@"updating subentities");
3165 totalBoundingBox = boundingBox; // reset totalBoundingBox
3166 ShipEntity *se = nil;
3167 foreach (se, [self subEntities])
3168 {
3169 [se update:delta_t];
3170 if ([se isShip])
3171 {
3172 BoundingBox sebb = [se findSubentityBoundingBox];
3173 bounding_box_add_vector(&totalBoundingBox, sebb.max);
3174 bounding_box_add_vector(&totalBoundingBox, sebb.min);
3175 }
3176 }
3177 // and one thing which isn't a subentity. Fixes bug with
3178 // mispositioned laser beams particularly noticeable on side view.
3179 if (lastShot != nil)
3180 {
3181 OOLaserShotEntity *lse = nil;
3182 foreach (lse, lastShot)
3183 {
3184 [lse update:0.0];
3185 }
3186 DESTROY(lastShot);
3187 }
3188
3189 // update mousewheel status
3190 UPDATE_STAGE(@"updating mousewheel delta");
3191 MyOpenGLView *gView = [UNIVERSE gameView];
3192 float mouseWheelDelta = [gView mouseWheelDelta];
3193 if (mouseWheelDelta > 0.0f)
3194 {
3195 if (mouseWheelDelta < delta_t) [gView setMouseWheelDelta:0.0f];
3196 else [gView setMouseWheelDelta:mouseWheelDelta - delta_t];
3197 }
3198 else if (mouseWheelDelta < 0.0f)
3199 {
3200 if (mouseWheelDelta > -delta_t) [gView setMouseWheelDelta:0.0f];
3201 else [gView setMouseWheelDelta:mouseWheelDelta + delta_t];
3202 }
3203
3205}
3206
3207
3208- (void) updateMovementFlags
3209{
3210 hasMoved = !HPvector_equal(position, lastPosition);
3211 hasRotated = !quaternion_equal(orientation, lastOrientation);
3212 lastPosition = position;
3213 lastOrientation = orientation;
3214}
3215
3216
3217- (void) updateAlertConditionForNearbyEntities
3218{
3219 if (![self isInSpace] || [self status] == STATUS_DOCKING)
3220 {
3221 [self clearAlertFlags];
3222 // not needed while docked
3223 return;
3224 }
3225
3226 int i, ent_count = UNIVERSE->n_entities;
3227 Entity **uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list
3228 Entity *my_entities[ent_count];
3229 Entity *scannedEntity = nil;
3230 for (i = 0; i < ent_count; i++)
3231 {
3232 my_entities[i] = [uni_entities[i] retain]; // retained
3233 }
3234 BOOL massLocked = NO;
3235 BOOL foundHostiles = NO;
3236#if OO_VARIABLE_TORUS_SPEED
3237 BOOL needHyperspeedNearest = YES;
3238 double hsnDistance = 0;
3239#endif
3240 for (i = 0; i < ent_count; i++) // scanner lollypops
3241 {
3242 scannedEntity = my_entities[i];
3243
3244#if OO_VARIABLE_TORUS_SPEED
3245 if (EXPECT_NOT(needHyperspeedNearest))
3246 {
3247 // not visual effects, waypoints, ships, etc.
3248 if (scannedEntity != self && [scannedEntity canCollide] && (![scannedEntity isShip] || ![self collisionExceptedFor:(ShipEntity *) scannedEntity]))
3249 {
3250 hsnDistance = sqrt(scannedEntity->zero_distance)-[scannedEntity collisionRadius];
3251 needHyperspeedNearest = NO;
3252 }
3253 }
3254 else if ([scannedEntity isStellarObject])
3255 {
3256 // planets, stars might be closest surface even if not
3257 // closest centre. That could be true of others, but the
3258 // error is negligible there.
3259 double thisHSN = sqrt(scannedEntity->zero_distance)-[scannedEntity collisionRadius];
3260 if (thisHSN < hsnDistance)
3261 {
3262 hsnDistance = thisHSN;
3263 }
3264 }
3265#endif
3266
3267 if (scannedEntity->zero_distance < SCANNER_MAX_RANGE2 || !scannedEntity->isShip)
3268 {
3269 int theirClass = [scannedEntity scanClass];
3270 // here we could also force masslock for higher than yellow alert, but
3271 // if we are going to hand over masslock control to scripting, might as well
3272 // hand it over fully
3273 if ([self massLockable] /*|| alertFlags > ALERT_FLAG_YELLOW_LIMIT*/)
3274 {
3275 massLocked |= [self checkEntityForMassLock:scannedEntity withScanClass:theirClass]; // we just need one masslocker..
3276 }
3277 if (theirClass != CLASS_NO_DRAW)
3278 {
3279 if (theirClass == CLASS_THARGOID || [scannedEntity isCascadeWeapon])
3280 {
3281 foundHostiles = YES;
3282 }
3283 else if ([scannedEntity isShip])
3284 {
3285 ShipEntity *ship = (ShipEntity *)scannedEntity;
3286 foundHostiles |= (([ship hasHostileTarget])&&([ship primaryTarget] == self));
3287 }
3288 }
3289 }
3290 }
3291#if OO_VARIABLE_TORUS_SPEED
3292 if (EXPECT_NOT(needHyperspeedNearest))
3293 {
3294 // this case should only occur in an otherwise empty
3295 // interstellar space - unlikely but possible
3296 hyperspeedFactor = MIN_HYPERSPEED_FACTOR;
3297 }
3298 else
3299 {
3300 // once nearest object is >4x scanner range
3301 // start increasing torus speed
3302 double factor = hsnDistance/(4*SCANNER_MAX_RANGE);
3303 if (factor < 1.0)
3304 {
3305 hyperspeedFactor = MIN_HYPERSPEED_FACTOR;
3306 }
3307 else
3308 {
3309 hyperspeedFactor = MIN_HYPERSPEED_FACTOR * sqrt(factor);
3310 if (hyperspeedFactor > MAX_HYPERSPEED_FACTOR)
3311 {
3312 // caps out at ~10^8m from nearest object
3313 // which takes ~10 minutes of flying
3314 hyperspeedFactor = MAX_HYPERSPEED_FACTOR;
3315 }
3316 }
3317 }
3318#endif
3319
3320 [self setAlertFlag:ALERT_FLAG_MASS_LOCK to:massLocked];
3321
3322 [self setAlertFlag:ALERT_FLAG_HOSTILES to:foundHostiles];
3323
3324 for (i = 0; i < ent_count; i++)
3325 {
3326 [my_entities[i] release]; // released
3327 }
3328
3329 BOOL energyCritical = NO;
3330 if (energy < 64 && energy < maxEnergy * 0.8)
3331 {
3332 energyCritical = YES;
3333 }
3334 [self setAlertFlag:ALERT_FLAG_ENERGY to:energyCritical];
3335
3336 [self setAlertFlag:ALERT_FLAG_TEMP to:([self hullHeatLevel] > .90)];
3337
3338 [self setAlertFlag:ALERT_FLAG_ALT to:([self dialAltitude] < .10)];
3339
3340}
3341
3342
3343- (void) setMaxFlightPitch:(GLfloat)new
3344{
3345 max_flight_pitch = new;
3346 pitch_delta = 2.0 * new;
3347}
3348
3349
3350- (void) setMaxFlightRoll:(GLfloat)new
3351{
3352 max_flight_roll = new;
3353 roll_delta = 2.0 * new;
3354}
3355
3356
3357- (void) setMaxFlightYaw:(GLfloat)new
3358{
3359 max_flight_yaw = new;
3360 yaw_delta = 2.0 * new;
3361}
3362
3363
3364- (BOOL) checkEntityForMassLock:(Entity *)ent withScanClass:(int)theirClass
3365{
3366 BOOL massLocked = NO;
3367 BOOL entIsCloakedShip = [ent isShip] && [(ShipEntity *)ent isCloaked];
3368
3369 if (EXPECT_NOT([ent isStellarObject]))
3370 {
3372 if (EXPECT([stellar planetType] != STELLAR_TYPE_MINIATURE))
3373 {
3374 double dist = stellar->zero_distance;
3375 double rad = stellar->collision_radius;
3376 double factor = ([stellar isSun]) ? 2.0 : 4.0;
3377 // plus ensure mass lock when 25 km or less from the surface of small stellar bodies
3378 // dist is a square distance so it needs to be compared to (rad+25000) * (rad+25000)!
3379 if (dist < rad*rad*factor || dist < rad*rad + 50000*rad + 625000000 )
3380 {
3381 massLocked = YES;
3382 }
3383 }
3384 }
3385 else if (theirClass != CLASS_NO_DRAW)
3386 {
3387 if (EXPECT_NOT (entIsCloakedShip))
3388 {
3389 theirClass = CLASS_NO_DRAW;
3390 }
3391 }
3392
3393 if (!massLocked && ent->zero_distance <= SCANNER_MAX_RANGE2)
3394 {
3395 switch (theirClass)
3396 {
3397 case CLASS_NO_DRAW:
3398 // cloaked ships do mass lock! - Nikos 20200718
3399 if (entIsCloakedShip && ![ent isPlayer])
3400 {
3401 massLocked = YES;
3402 }
3403 break;
3404 case CLASS_PLAYER:
3405 case CLASS_BUOY:
3406 case CLASS_ROCK:
3407 case CLASS_CARGO:
3408 case CLASS_MINE:
3409 case CLASS_VISUAL_EFFECT:
3410 break;
3411
3412 case CLASS_THARGOID:
3413 case CLASS_MISSILE:
3414 case CLASS_STATION:
3415 case CLASS_POLICE:
3416 case CLASS_MILITARY:
3417 case CLASS_WORMHOLE:
3418 default:
3419 massLocked = YES;
3420 break;
3421 }
3422 }
3423
3424 return massLocked;
3425}
3426
3427
3428- (void) updateAlertCondition
3429{
3430 [self updateAlertConditionForNearbyEntities];
3431 /* TODO: update alert condition once per frame. Tried this before, but
3432 there turned out to be complications. See mailing list archive.
3433 -- Ahruman 20070802
3434 */
3435 OOAlertCondition cond = [self alertCondition];
3436 OOTimeAbsolute t = [UNIVERSE getTime];
3437 if (cond != lastScriptAlertCondition)
3438 {
3439 ShipScriptEventNoCx(self, "alertConditionChanged", INT_TO_JSVAL(cond), INT_TO_JSVAL(lastScriptAlertCondition));
3440 lastScriptAlertCondition = cond;
3441 }
3442 /* Update heuristic assessment of whether player is fleeing */
3443 if (cond == ALERT_CONDITION_DOCKED || cond == ALERT_CONDITION_GREEN || (cond == ALERT_CONDITION_YELLOW && energy == maxEnergy))
3444 {
3445 fleeing_status = PLAYER_FLEEING_NONE;
3446 }
3447 else if (fleeing_status == PLAYER_FLEEING_UNLIKELY && (energy > maxEnergy*0.6 || cond != ALERT_CONDITION_RED))
3448 {
3449 fleeing_status = PLAYER_FLEEING_NONE;
3450 }
3451 else if ((fleeing_status == PLAYER_FLEEING_MAYBE || fleeing_status == PLAYER_FLEEING_UNLIKELY) && cargo_dump_time > last_shot_time)
3452 {
3453 fleeing_status = PLAYER_FLEEING_CARGO;
3454 }
3455 else if (fleeing_status == PLAYER_FLEEING_MAYBE && last_shot_time + 10 > t)
3456 {
3457 fleeing_status = PLAYER_FLEEING_NONE;
3458 }
3459 else if (fleeing_status == PLAYER_FLEEING_LIKELY && last_shot_time + 10 > t)
3460 {
3461 fleeing_status = PLAYER_FLEEING_UNLIKELY;
3462 }
3463 else if (fleeing_status == PLAYER_FLEEING_NONE && cond == ALERT_CONDITION_RED && last_shot_time + 10 < t && flightSpeed > 0.75*maxFlightSpeed)
3464 {
3465 fleeing_status = PLAYER_FLEEING_MAYBE;
3466 }
3467 else if ((fleeing_status == PLAYER_FLEEING_MAYBE || fleeing_status == PLAYER_FLEEING_CARGO) && cond == ALERT_CONDITION_RED && last_shot_time + 10 < t && flightSpeed > 0.75*maxFlightSpeed && energy < maxEnergy * 0.5 && (forward_shield < [self maxForwardShieldLevel]*0.25 || aft_shield < [self maxAftShieldLevel]*0.25))
3468 {
3469 fleeing_status = PLAYER_FLEEING_LIKELY;
3470 }
3471}
3472
3473
3474- (void) updateFuelScoops:(OOTimeDelta)delta_t
3475{
3476 if (scoopsActive)
3477 {
3478 [self updateFuelScoopSoundWithInterval:delta_t];
3479 if (![self scoopOverride])
3480 {
3481 scoopsActive = NO;
3482 [self updateFuelScoopSoundWithInterval:delta_t];
3483 }
3484 }
3485}
3486
3487
3488- (void) updateClocks:(OOTimeDelta)delta_t
3489{
3490 // shot time updates are still needed here for STATUS_DEAD!
3491 shot_time += delta_t;
3492 script_time += delta_t;
3493 unsigned prev_day = floor(ship_clock / 86400);
3494 ship_clock += delta_t;
3495 if (ship_clock_adjust > 0.0) // adjust for coming out of warp (add LY * LY hrs)
3496 {
3497 double fine_adjust = delta_t * 7200.0;
3498 if (ship_clock_adjust > 86400) // more than a day
3499 fine_adjust = delta_t * 115200.0; // 16 times faster
3500 if (ship_clock_adjust > 0)
3501 {
3502 if (fine_adjust > ship_clock_adjust)
3503 fine_adjust = ship_clock_adjust;
3504 ship_clock += fine_adjust;
3505 ship_clock_adjust -= fine_adjust;
3506 }
3507 else
3508 {
3509 if (fine_adjust < ship_clock_adjust)
3510 fine_adjust = ship_clock_adjust;
3511 ship_clock -= fine_adjust;
3512 ship_clock_adjust += fine_adjust;
3513 }
3514 }
3515 else
3516 ship_clock_adjust = 0.0;
3517
3518 unsigned now_day = floor(ship_clock / 86400.0);
3519 while (prev_day < now_day)
3520 {
3521 prev_day++;
3522 [self doScriptEvent:OOJSID("dayChanged") withArgument:[NSNumber numberWithUnsignedInt:prev_day]];
3523 // not impossible that at ultra-low frame rates two of these will
3524 // happen in a single update.
3525 }
3526
3527 //fps
3528 if (ship_clock > fps_check_time)
3529 {
3530 if (![self clockAdjusting])
3531 {
3532 fps_counter = (int)([UNIVERSE timeAccelerationFactor] * floor([UNIVERSE framesDoneThisUpdate] / (fps_check_time - last_fps_check_time)));
3533 last_fps_check_time = fps_check_time;
3534 fps_check_time = ship_clock + MINIMUM_GAME_TICK;
3535 }
3536 else
3537 {
3538 // Good approximation for when the clock is adjusting and proper fps calculation
3539 // cannot be performed.
3540 fps_counter = (int)([UNIVERSE timeAccelerationFactor] * floor(1.0 / delta_t));
3541 fps_check_time = ship_clock + MINIMUM_GAME_TICK;
3542 }
3543 [UNIVERSE resetFramesDoneThisUpdate]; // Reset frame counter
3544 }
3545}
3546
3547
3548- (void) checkScriptsIfAppropriate
3549{
3550 if (script_time <= script_time_check) return;
3551
3552 if ([self status] != STATUS_IN_FLIGHT)
3553 {
3554 switch (gui_screen)
3555 {
3556 // Screens where no world script tickles are performed
3557 case GUI_SCREEN_MAIN:
3558 case GUI_SCREEN_INTRO1:
3559 case GUI_SCREEN_SHIPLIBRARY:
3560 case GUI_SCREEN_KEYBOARD:
3561 case GUI_SCREEN_NEWGAME:
3562 case GUI_SCREEN_OXZMANAGER:
3563 case GUI_SCREEN_MARKET:
3564 case GUI_SCREEN_MARKETINFO:
3565 case GUI_SCREEN_OPTIONS:
3566 case GUI_SCREEN_GAMEOPTIONS:
3567 case GUI_SCREEN_LOAD:
3568 case GUI_SCREEN_SAVE:
3569 case GUI_SCREEN_SAVE_OVERWRITE:
3570 case GUI_SCREEN_STICKMAPPER:
3571 case GUI_SCREEN_STICKPROFILE:
3572 case GUI_SCREEN_MISSION:
3573 case GUI_SCREEN_REPORT:
3574 case GUI_SCREEN_KEYBOARD_CONFIRMCLEAR:
3575 case GUI_SCREEN_KEYBOARD_CONFIG:
3576 case GUI_SCREEN_KEYBOARD_ENTRY:
3577 case GUI_SCREEN_KEYBOARD_LAYOUT:
3578 return;
3579
3580 // Screens from which it's safe to jump to the mission screen
3581// case GUI_SCREEN_CONTRACTS:
3582 case GUI_SCREEN_EQUIP_SHIP:
3583 case GUI_SCREEN_INTERFACES:
3584 case GUI_SCREEN_MANIFEST:
3585 case GUI_SCREEN_SHIPYARD:
3586 case GUI_SCREEN_LONG_RANGE_CHART:
3587 case GUI_SCREEN_SHORT_RANGE_CHART:
3588 case GUI_SCREEN_STATUS:
3589 case GUI_SCREEN_SYSTEM_DATA:
3590 // Test passed, we can run scripts. Nothing to do here.
3591 break;
3592 }
3593 }
3594
3595 // Test either passed or never ran, run scripts.
3596 [self checkScript];
3597 script_time_check += script_time_interval;
3598}
3599
3600
3601- (void) updateTrumbles:(OOTimeDelta)delta_t
3602{
3603 OOTrumble **trumbles = [self trumbleArray];
3604 NSUInteger i;
3605
3606 for (i = [self trumbleCount] ; i > 0; i--)
3607 {
3608 OOTrumble* trum = trumbles[i - 1];
3609 [trum updateTrumble:delta_t];
3610 }
3611}
3612
3613
3614- (void) performAutopilotUpdates:(OOTimeDelta)delta_t
3615{
3616 [self processBehaviour:delta_t];
3617 [self applyVelocity:delta_t];
3618 [self doBookkeeping:delta_t];
3619}
3620
3621- (void) performDockingRequest:(StationEntity *)stationForDocking
3622{
3623 if (stationForDocking == nil) return;
3624 if (![stationForDocking isStation] || ![stationForDocking isKindOfClass:[StationEntity class]]) return;
3625 if ([self isDocked]) return;
3626 if (autopilot_engaged && [self targetStation] == stationForDocking) return;
3627 if (autopilot_engaged && [self targetStation] != stationForDocking)
3628 {
3629 [self disengageAutopilot];
3630 }
3631 NSString *stationDockingClearanceStatus = [stationForDocking acceptDockingClearanceRequestFrom:self];
3632 if (stationDockingClearanceStatus != nil)
3633 {
3634 [self doScriptEvent:OOJSID("playerRequestedDockingClearance") withArgument:stationDockingClearanceStatus];
3635 if ([stationDockingClearanceStatus isEqualToString:@"DOCKING_CLEARANCE_GRANTED"])
3636 {
3637 [self doScriptEvent:OOJSID("playerDockingClearanceGranted")];
3638 }
3639 }
3640}
3641
3642- (void) requestDockingClearance:(StationEntity *)stationForDocking
3643{
3644 if (dockingClearanceStatus != DOCKING_CLEARANCE_STATUS_REQUESTED && dockingClearanceStatus != DOCKING_CLEARANCE_STATUS_GRANTED)
3645 {
3646 [self performDockingRequest:stationForDocking];
3647 }
3648}
3649
3650- (void) cancelDockingRequest:(StationEntity *)stationForDocking
3651{
3652 if (stationForDocking == nil) return;
3653 if (![stationForDocking isStation] || ![stationForDocking isKindOfClass:[StationEntity class]]) return;
3654 if ([self isDocked]) return;
3655 if (autopilot_engaged && [self targetStation] == stationForDocking) return;
3656 if (autopilot_engaged && [self targetStation] != stationForDocking)
3657 {
3658 [self disengageAutopilot];
3659 }
3660 if (dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_GRANTED || dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_REQUESTED)
3661 {
3662 NSString *stationDockingClearanceStatus = [stationForDocking acceptDockingClearanceRequestFrom:self];
3663 if (stationDockingClearanceStatus != nil && [stationDockingClearanceStatus isEqualToString:@"DOCKING_CLEARANCE_CANCELLED"])
3664 {
3665 [self doScriptEvent:OOJSID("playerDockingClearanceCancelled")];
3666 }
3667 }
3668}
3669
3670- (BOOL) engageAutopilotToStation:(StationEntity *)stationForDocking
3671{
3672 if (stationForDocking == nil) return NO;
3673 if ([self isDocked]) return NO;
3674
3675 if (autopilot_engaged && [self targetStation] == stationForDocking)
3676 {
3677 return YES;
3678 }
3679
3680 [self setTargetStation:stationForDocking];
3681 DESTROY(_primaryTarget);
3682 autopilot_engaged = YES;
3683 ident_engaged = NO;
3684 [self safeAllMissiles];
3685 velocity = kZeroVector;
3686 if ([self status] == STATUS_WITCHSPACE_COUNTDOWN) [self cancelWitchspaceCountdown]; // cancel witchspace countdown properly
3687 [self setStatus:STATUS_AUTOPILOT_ENGAGED];
3688 [self resetAutopilotAI];
3689 [shipAI setState:@"BEGIN_DOCKING"]; // reboot the AI
3690 [self playAutopilotOn];
3692 [self doScriptEvent:OOJSID("playerStartedAutoPilot") withArgument:stationForDocking];
3693 [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_GRANTED];
3694
3695 if (afterburner_engaged)
3696 {
3697 afterburner_engaged = NO;
3698 if (afterburnerSoundLooping) [self stopAfterburnerSound];
3699 }
3700 return YES;
3701}
3702
3703
3704
3705- (void) disengageAutopilot
3706{
3707 if (autopilot_engaged)
3708 {
3709 [self abortDocking]; // let the station know that you are no longer on approach
3710 behaviour = BEHAVIOUR_IDLE;
3711 frustration = 0.0;
3712 autopilot_engaged = NO;
3713 DESTROY(_primaryTarget);
3714 [self setTargetStation:nil];
3715 [self setStatus:STATUS_IN_FLIGHT];
3716 [self playAutopilotOff];
3717 [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
3719 [self doScriptEvent:OOJSID("playerCancelledAutoPilot")];
3720
3721 [self resetAutopilotAI];
3722 }
3723}
3724
3725
3726- (void) resetAutopilotAI
3727{
3728 AI *myAI = [self getAI];
3729 // JSAI: will need changing if oolite-dockingAI.js written
3730 if (![[myAI name] isEqualToString:PLAYER_DOCKING_AI_NAME])
3731 {
3732 [self setAITo:PLAYER_DOCKING_AI_NAME ];
3733 }
3734 [myAI clearAllData];
3735 [myAI setState:@"GLOBAL"];
3736 [myAI setNextThinkTime:[UNIVERSE getTime] + 2];
3737 [myAI setOwner:self];
3738}
3739
3740
3741#define VELOCITY_CLEANUP_MIN 2000.0f // Minimum speed for "power braking".
3742#define VELOCITY_CLEANUP_FULL 5000.0f // Speed at which full "power braking" factor is used.
3743#define VELOCITY_CLEANUP_RATE 0.001f // Factor for full "power braking".
3744
3745
3746#if OO_VARIABLE_TORUS_SPEED
3747- (GLfloat) hyperspeedFactor
3748{
3749 return hyperspeedFactor;
3750}
3751#endif
3752
3753
3754- (BOOL) injectorsEngaged
3755{
3756 return afterburner_engaged;
3757}
3758
3759
3760- (BOOL) hyperspeedEngaged
3761{
3762 return hyperspeed_engaged;
3763}
3764
3765
3766- (void) performInFlightUpdates:(OOTimeDelta)delta_t
3767{
3769
3770 // do flight routines
3772 UPDATE_STAGE(@"applying newtonian drift");
3774
3775 [self applyVelocity:delta_t];
3776
3777 GLfloat thrust_factor = 1.0;
3778 if (flightSpeed > maxFlightSpeed)
3779 {
3780 if (afterburner_engaged)
3781 {
3782 thrust_factor = [self afterburnerFactor];
3783 }
3784 else
3785 {
3786 thrust_factor = HYPERSPEED_FACTOR;
3787 }
3788 }
3789
3790
3791 GLfloat velmag = magnitude(velocity);
3792 GLfloat velmag2 = velmag - (float)delta_t * thrust * thrust_factor;
3793 if (velmag > 0)
3794 {
3795 UPDATE_STAGE(@"applying power braking");
3796
3797 if (velmag > VELOCITY_CLEANUP_MIN)
3798 {
3799 GLfloat rate;
3800 // Fix up extremely ridiculous speeds that can happen in collisions or explosions
3801 if (velmag > VELOCITY_CLEANUP_FULL) rate = VELOCITY_CLEANUP_RATE;
3803 velmag2 -= velmag * rate;
3804 }
3805 if (velmag2 < 0.0f) velocity = kZeroVector;
3806 else velocity = vector_multiply_scalar(velocity, velmag2 / velmag);
3807
3808 }
3809
3810 UPDATE_STAGE(@"updating joystick");
3811 [self applyRoll:(float)delta_t*flightRoll andClimb:(float)delta_t*flightPitch];
3812 if (flightYaw != 0.0)
3813 {
3814 [self applyYaw:(float)delta_t*flightYaw];
3815 }
3816
3817 UPDATE_STAGE(@"applying para-newtonian thrust");
3818 [self moveForward:delta_t*flightSpeed];
3819
3820 UPDATE_STAGE(@"updating targeting");
3821 [self updateTargeting];
3822
3824}
3825
3826
3827- (void) performWitchspaceCountdownUpdates:(OOTimeDelta)delta_t
3828{
3830
3831 UPDATE_STAGE(@"doing bookkeeping");
3832 [self doBookkeeping:delta_t];
3833
3834 UPDATE_STAGE(@"updating countdown timer");
3835 witchspaceCountdown = fdim(witchspaceCountdown, delta_t);
3836
3837 // damaged gal drive? abort!
3838 /* TODO: this check should possibly be hasEquipmentItemProviding:,
3839 * but if it was we'd need to know which item was actually doing
3840 * the providing so it could be removed. */
3841 if (EXPECT_NOT(galactic_witchjump && ![self hasEquipmentItem:@"EQ_GAL_DRIVE"]))
3842 {
3843 galactic_witchjump = NO;
3844 [self setStatus:STATUS_IN_FLIGHT];
3845 [self playHyperspaceAborted];
3846 ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("malfunction"));
3847 return;
3848 }
3849
3850 int seconds = round(witchspaceCountdown);
3851 if (galactic_witchjump)
3852 {
3853 [UNIVERSE displayCountdownMessage:OOExpandKey(@"witch-galactic-in-x-seconds", seconds) forCount:1.0];
3854 }
3855 else
3856 {
3857 NSString *destination = [UNIVERSE getSystemName:[self nextHopTargetSystemID]];
3858 [UNIVERSE displayCountdownMessage:OOExpandKey(@"witch-to-x-in-y-seconds", seconds, destination) forCount:1.0];
3859 }
3860
3861 if (witchspaceCountdown == 0.0)
3862 {
3863 UPDATE_STAGE(@"preloading planet textures");
3864 if (!galactic_witchjump)
3865 {
3866 /* Note: planet texture preloading is done twice for hyperspace jumps:
3867 once when starting the countdown and once at the beginning of the
3868 jump. The reason is that the preloading may have been skipped the
3869 first time because of rate limiting (see notes at
3870 -preloadPlanetTexturesForSystem:). There is no significant overhead
3871 from doing it twice thanks to the texture cache.
3872 -- Ahruman 2009-12-19
3873 */
3874 [UNIVERSE preloadPlanetTexturesForSystem:target_system_id];
3875 }
3876 else
3877 {
3878 // FIXME: preload target system for galactic jump?
3879 }
3880
3881 UPDATE_STAGE(@"JUMP!");
3882 if (galactic_witchjump) [self enterGalacticWitchspace];
3883 else [self enterWitchspace];
3884 galactic_witchjump = NO;
3885 }
3886
3888}
3889
3890
3891- (void) performWitchspaceExitUpdates:(OOTimeDelta)delta_t
3892{
3893 if ([UNIVERSE breakPatternOver])
3894 {
3895 [self resetExhaustPlumes];
3896 // time to check the script!
3897 [self checkScript];
3898 // next check in 10s
3899 [self resetScriptTimer]; // reset the in-system timer
3900
3901 // announce arrival
3902 if ([UNIVERSE planet])
3903 {
3904 [UNIVERSE addMessage:[NSString stringWithFormat:@" %@. ",[UNIVERSE getSystemName:system_id]] forCount:3.0];
3905 // and reset the compass
3906 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"])
3907 compassMode = COMPASS_MODE_PLANET;
3908 else
3909 compassMode = COMPASS_MODE_BASIC;
3910 }
3911 else
3912 {
3913 if ([UNIVERSE inInterstellarSpace]) [UNIVERSE addMessage:DESC(@"witch-engine-malfunction") forCount:3.0]; // if sun gone nova, print nothing
3914 }
3915
3916 [self setStatus:STATUS_IN_FLIGHT];
3917
3918 // If we are exiting witchspace after a scripted misjump. then make sure it gets reset now.
3919 // Scripted misjump situations should have a lifespan of one jump only, to keep things
3920 // simple - Nikos 20090728
3921 if ([self scriptedMisjump]) [self setScriptedMisjump:NO];
3922 // similarly reset the misjump range to the traditional 0.5
3923 [self setScriptedMisjumpRange:0.5];
3924
3925 [self doScriptEvent:OOJSID("shipExitedWitchspace") withArgument:[self jumpCause]];
3926
3927 [self doBookkeeping:delta_t]; // arrival frame updates
3928
3929 suppressAegisMessages=NO;
3930 }
3931}
3932
3933
3934- (void) performLaunchingUpdates:(OOTimeDelta)delta_t
3935{
3936 if (![UNIVERSE breakPatternHide])
3937 {
3938 flightRoll = launchRoll; // synchronise player's & launching station's spins.
3939 [self doBookkeeping:delta_t]; // don't show ghost exhaust plumes from previous docking!
3940 }
3941
3942 if ([UNIVERSE breakPatternOver])
3943 {
3944 // time to check the legacy scripts!
3945 [self checkScript];
3946 // next check in 10s
3947
3948 [self setStatus:STATUS_IN_FLIGHT];
3949
3950 [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
3951 StationEntity *stationLaunchedFrom = [UNIVERSE nearestEntityMatchingPredicate:IsStationPredicate parameter:NULL relativeToEntity:self];
3952 [self doScriptEvent:OOJSID("shipLaunchedFromStation") withArgument:stationLaunchedFrom];
3953 }
3954}
3955
3956
3957- (void) performDockingUpdates:(OOTimeDelta)delta_t
3958{
3959 if ([UNIVERSE breakPatternOver])
3960 {
3961 [self docked]; // bookkeeping for docking
3962 }
3963
3964 // if cloak or ecm visual effects are playing while docking, terminate them
3965 [UNIVERSE terminatePostFX:OO_POSTFX_CLOAK];
3966 if ([UNIVERSE ECMVisualFXEnabled]) [UNIVERSE terminatePostFX:OO_POSTFX_CRTBADSIGNAL];
3967}
3968
3969
3970- (void) performDeadUpdates:(OOTimeDelta)delta_t
3971{
3972 [UNIVERSE terminatePostFX:OO_POSTFX_CLOAK];
3973 if ([UNIVERSE ECMVisualFXEnabled]) [UNIVERSE terminatePostFX:OO_POSTFX_CRTBADSIGNAL];
3974
3975 [self gameOverFadeToBW];
3976
3977 if ([self shotTime] > kDeadResetTime)
3978 {
3979 BOOL was_mouse_control_on = mouse_control_on;
3980 [UNIVERSE handleGameOver]; // we restart the UNIVERSE
3981 mouse_control_on = was_mouse_control_on;
3982 }
3983}
3984
3985
3986- (void) gameOverFadeToBW
3987{
3988 float secondsToBWFadeOut = [[NSUserDefaults standardUserDefaults] oo_floatForKey:@"gameover-seconds-to-bw-fadeout" defaultValue:5.0f];
3989 if ([UNIVERSE detailLevel] >= DETAIL_LEVEL_SHADERS && secondsToBWFadeOut > 0.0f)
3990 {
3991 MyOpenGLView *gameView = [UNIVERSE gameView];
3992 static float originalColorSaturation = -1.0f;
3993 if (originalColorSaturation == -1.0f) originalColorSaturation = [gameView colorSaturation];
3994 if ([self shotTime] < secondsToBWFadeOut)
3995 {
3996 // fade to black & white within secondsToBWFadeOut, independently of
3997 // frame rate and original color saturation
3998 if (fps_counter != 0)
3999 {
4000 [gameView adjustColorSaturation:-(originalColorSaturation * (1.0f / secondsToBWFadeOut) * [UNIVERSE timeAccelerationFactor] / fps_counter)];
4001 }
4002 }
4003
4004 if ([self shotTime] > kDeadResetTime)
4005 {
4006 // make sure to subtract the current saturation because if the user presses space to skip
4007 // the game over screen before the transition to b/w has been completed, whatever is left
4008 // will be added to the original saturation, resulting in an oversaturated image
4009 [gameView adjustColorSaturation:originalColorSaturation - [gameView colorSaturation]];
4010 originalColorSaturation = -1.0f;
4011 }
4012 }
4013}
4014
4015
4016// Target is valid if it's within Scanner range, AND
4017// Target is a ship AND is not cloaked or jamming, OR
4018// Target is a wormhole AND player has the Wormhole Scanner
4019- (BOOL)isValidTarget:(Entity*)target
4020{
4021 // Just in case we got called with a bad target.
4022 if (!target)
4023 return NO;
4024
4025 // If target is beyond scanner range, it's lost
4026 if(target->zero_distance > SCANNER_MAX_RANGE2)
4027 return NO;
4028
4029 // If target is a ship, check whether it's cloaked or is actively jamming our scanner
4030 if ([target isShip])
4031 {
4032 ShipEntity *targetShip = (ShipEntity*)target;
4033 if ([targetShip isCloaked] || // checks for cloaked ships
4034 ([targetShip isJammingScanning] && ![self hasMilitaryScannerFilter])) // checks for activated jammer
4035 {
4036 return NO;
4037 }
4038 OOEntityStatus tstatus = [targetShip status];
4039 if (tstatus == STATUS_ENTERING_WITCHSPACE || tstatus == STATUS_IN_HOLD || tstatus == STATUS_DOCKED)
4040 { // checks for ships entering wormholes, docking, or been scooped
4041 return NO;
4042 }
4043 return YES;
4044 }
4045
4046 // If target is an unexpired wormhole and the player has bought the Wormhole Scanner and we're in ID mode
4047 if ([target isWormhole] && [target scanClass] != CLASS_NO_DRAW &&
4048 [self hasEquipmentItemProviding:@"EQ_WORMHOLE_SCANNER"] && ident_engaged)
4049 {
4050 return YES;
4051 }
4052
4053 // Target is neither a wormhole nor a ship
4054 return NO;
4055}
4056
4057
4058- (void) showGameOver
4059{
4060 [hud resetGuis:[NSDictionary dictionaryWithObject:[NSDictionary dictionary] forKey:@"message_gui"]];
4061 NSString *scoreMS = [NSString stringWithFormat:OOExpandKey(@"gameoverscreen-score-@"),
4062 KillCountToRatingAndKillString(ship_kills)];
4063
4064 [UNIVERSE displayMessage:OOExpandKey(@"gameoverscreen-game-over") forCount:kDeadResetTime];
4065 [UNIVERSE displayMessage:@"" forCount:kDeadResetTime];
4066 [UNIVERSE displayMessage:scoreMS forCount:kDeadResetTime];
4067 [UNIVERSE displayMessage:@"" forCount:kDeadResetTime];
4068 [UNIVERSE displayMessage:OOExpandKey(@"gameoverscreen-press-space") forCount:kDeadResetTime];
4069 [UNIVERSE displayMessage:@" " forCount:kDeadResetTime];
4070 [UNIVERSE displayMessage:@"" forCount:kDeadResetTime];
4071 [self resetShotTime];
4072}
4073
4074
4075- (void) showShipModelWithKey:(NSString *)shipKey shipData:(NSDictionary *)shipData personality:(uint16_t)personality factorX:(GLfloat)factorX factorY:(GLfloat)factorY factorZ:(GLfloat)factorZ inContext:(NSString *)context
4076{
4077 if (shipKey == nil) return;
4078 if (shipData == nil) shipData = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey];
4079 if (shipData == nil) return;
4080
4081 Quaternion q2 = { (GLfloat)M_SQRT1_2, (GLfloat)M_SQRT1_2, (GLfloat)0.0f, (GLfloat)0.0f };
4082 // MKW - retrieve last demo ships' orientation and release it
4083 if( demoShip != nil )
4084 {
4085 q2 = [demoShip orientation];
4086 [demoShip release];
4087 }
4088
4089 ShipEntity *ship = [[ProxyPlayerEntity alloc] initWithKey:shipKey definition:shipData];
4090 if (personality != ENTITY_PERSONALITY_INVALID) [ship setEntityPersonalityInt:personality];
4091
4092 [ship wasAddedToUniverse];
4093
4094 if (context) OOLog(@"script.debug.note.showShipModel", @"::::: showShipModel:'%@' in context: %@.", [ship name], context);
4095
4096 GLfloat cr = [ship collisionRadius];
4097 [ship setOrientation: q2];
4098 [ship setPositionX:factorX * cr y:factorY * cr z:factorZ * cr];
4099 [ship setScanClass: CLASS_NO_DRAW];
4100 [ship setDemoShip: 0.6];
4101 [ship setDemoStartTime: [UNIVERSE getTime]];
4102 if([ship pendingEscortCount] > 0) [ship setPendingEscortCount:0];
4103 [ship setAITo: @"nullAI.plist"];
4104 id subEntStatus = [shipData objectForKey:@"subentities_status"];
4105 // show missing subentities if there's a subentities_status key
4106 if (subEntStatus != nil) [ship deserializeShipSubEntitiesFrom:(NSString *)subEntStatus];
4107 [UNIVERSE addEntity: ship];
4108 // MKW - save demo ship for its rotation
4109 demoShip = [ship retain];
4110
4111 [ship setStatus: STATUS_COCKPIT_DISPLAY];
4112
4113 [ship release];
4114}
4115
4116
4117// Game options and status screens (for now) may require an immediate window redraw after
4118// said window has been resized. This method must be called after such resize events, including
4119// toggle to/from full screen - Nikos 20140129
4120- (void) doGuiScreenResizeUpdates
4121{
4122 switch ([self guiScreen])
4123 {
4124 case GUI_SCREEN_GAMEOPTIONS:
4125 //refresh play windowed / full screen
4126 [self setGuiToGameOptionsScreen];
4127 break;
4128 case GUI_SCREEN_STATUS:
4129 // status screen must be redone in order to possibly
4130 // refresh displayed model's draw position
4131 [self setGuiToStatusScreen];
4132 break;
4133 default:
4134 break;
4135 }
4136
4137
4138 [hud resetGuiPositions];
4139}
4140
4141
4142// Check for lost targeting - both on the ships' main target as well as each
4143// missile.
4144// If we're actively scanning and we don't have a current target, then check
4145// to see if we've locked onto a new target.
4146// Finally, if we have a target and it's a wormhole, check whether we have more
4147// information
4148- (void) updateTargeting
4149{
4151
4152 // check for lost ident target and ensure the ident system is actually scanning
4153 UPDATE_STAGE(@"checking ident target");
4154 if (ident_engaged && [self primaryTarget] != nil)
4155 {
4156 if (![self isValidTarget:[self primaryTarget]])
4157 {
4158 if (!suppressTargetLost)
4159 {
4160 [UNIVERSE addMessage:DESC(@"target-lost") forCount:3.0];
4161 [self playTargetLost];
4162 [self noteLostTarget];
4163 }
4164 else
4165 {
4166 suppressTargetLost = NO;
4167 }
4168
4169 DESTROY(_primaryTarget);
4170 }
4171 }
4172
4173 // check each unlaunched missile's target still exists and is in-range
4174 UPDATE_STAGE(@"checking missile targets");
4175 if (missile_status != MISSILE_STATUS_SAFE)
4176 {
4177 unsigned i;
4178 for (i = 0; i < max_missiles; i++)
4179 {
4180 if ([missile_entity[i] primaryTarget] != nil &&
4181 ![self isValidTarget:[missile_entity[i] primaryTarget]])
4182 {
4183 [UNIVERSE addMessage:DESC(@"target-lost") forCount:3.0];
4184 [self playTargetLost];
4185 [missile_entity[i] removeTarget:nil];
4186 if (i == activeMissile)
4187 {
4188 [self noteLostTarget];
4189 DESTROY(_primaryTarget);
4190 missile_status = MISSILE_STATUS_ARMED;
4191 }
4192 } else if (i == activeMissile && [missile_entity[i] primaryTarget] == nil) {
4193 missile_status = MISSILE_STATUS_ARMED;
4194 }
4195 }
4196 }
4197
4198 // if we don't have a primary target, and we're scanning, then check for a new
4199 // target to lock on to
4200 UPDATE_STAGE(@"looking for new target");
4201 if ([self primaryTarget] == nil &&
4202 (ident_engaged || missile_status != MISSILE_STATUS_SAFE) &&
4203 ([self status] == STATUS_IN_FLIGHT || [self status] == STATUS_WITCHSPACE_COUNTDOWN))
4204 {
4205 Entity *target = [UNIVERSE firstEntityTargetedByPlayer];
4206 if ([self isValidTarget:target])
4207 {
4208 [self addTarget:target];
4209 }
4210 }
4211
4212 // If our primary target is a wormhole, check to see if we have additional
4213 // information
4214 UPDATE_STAGE(@"checking for additional wormhole information");
4215 if ([[self primaryTarget] isWormhole])
4216 {
4217 WormholeEntity *wh = [self primaryTarget];
4218 switch ([wh scanInfo])
4219 {
4220 case WH_SCANINFO_NONE:
4221 OOLog(kOOLogInconsistentState, @"%@", @"Internal Error - WH_SCANINFO_NONE reached in [PlayerEntity updateTargeting:]");
4222 [self dumpState];
4223 [wh dumpState];
4224 // Workaround a reported hit of the assert here. We really
4225 // should work out how/why this could happen though and fix
4226 // the underlying cause.
4227 // - MKW 2011.03.11
4228 //assert([wh scanInfo] != WH_SCANINFO_NONE);
4229 [wh setScannedAt:[self clockTimeAdjusted]];
4230 break;
4232 if ([self clockTimeAdjusted] > [wh scanTime] + 2)
4233 {
4234 [wh setScanInfo:WH_SCANINFO_COLLAPSE_TIME];
4235 //[UNIVERSE addCommsMessage:[NSString stringWithFormat:DESC(@"wormhole-collapse-time-computed"),
4236 // [UNIVERSE getSystemName:[wh destination]]] forCount:5.0];
4237 }
4238 break;
4240 if([self clockTimeAdjusted] > [wh scanTime] + 4)
4241 {
4242 [wh setScanInfo:WH_SCANINFO_ARRIVAL_TIME];
4243 [UNIVERSE addCommsMessage:[NSString stringWithFormat:DESC(@"wormhole-arrival-time-computed-@"),
4244 ClockToString([wh estimatedArrivalTime], NO)] forCount:5.0];
4245 }
4246 break;
4248 if ([self clockTimeAdjusted] > [wh scanTime] + 7)
4249 {
4250 [wh setScanInfo:WH_SCANINFO_DESTINATION];
4251 [UNIVERSE addCommsMessage:[NSString stringWithFormat:DESC(@"wormhole-destination-computed-@"),
4252 [UNIVERSE getSystemName:[wh destination]]] forCount:5.0];
4253 }
4254 break;
4256 if ([self clockTimeAdjusted] > [wh scanTime] + 10)
4257 {
4258 [wh setScanInfo:WH_SCANINFO_SHIP];
4259 // TODO: Extract last ship from wormhole and display its name
4260 }
4261 break;
4262 case WH_SCANINFO_SHIP:
4263 break;
4264 }
4265 }
4266
4268}
4269
4270
4271- (void) orientationChanged
4272{
4273 quaternion_normalize(&orientation);
4274 rotMatrix = OOMatrixForQuaternionRotation(orientation);
4275 OOMatrixGetBasisVectors(rotMatrix, &v_right, &v_up, &v_forward);
4276
4277 orientation.w = -orientation.w;
4278 playerRotMatrix = OOMatrixForQuaternionRotation(orientation); // this is the rotation similar to ordinary ships
4279 orientation.w = -orientation.w;
4280}
4281
4282
4283- (void) applyAttitudeChanges:(double) delta_t
4284{
4285 [self applyRoll:flightRoll*delta_t andClimb:flightPitch*delta_t];
4286 [self applyYaw:flightYaw*delta_t];
4287}
4288
4289
4290- (void) applyRoll:(GLfloat) roll1 andClimb:(GLfloat) climb1
4291{
4292 if (roll1 == 0.0 && climb1 == 0.0 && hasRotated == NO)
4293 return;
4294
4295 if (roll1)
4296 quaternion_rotate_about_z(&orientation, -roll1);
4297 if (climb1)
4298 quaternion_rotate_about_x(&orientation, -climb1);
4299
4300 /* Bugginess may put us in a state where the orientation quat is all
4301 zeros, at which point it’s impossible to move.
4302 */
4303 if (EXPECT_NOT(quaternion_equal(orientation, kZeroQuaternion)))
4304 {
4305 if (!quaternion_equal(lastOrientation, kZeroQuaternion))
4306 {
4307 orientation = lastOrientation;
4308 }
4309 else
4310 {
4311 orientation = kIdentityQuaternion;
4312 }
4313 }
4314
4315 [self orientationChanged];
4316}
4317
4318/*
4319 * This method should not be necessary, but when I replaced the above with applyRoll:andClimb:andYaw, the
4320 * ship went crazy. Perhaps applyRoll:andClimb is called from one of the subclasses and that was messing
4321 * things up.
4322 */
4323- (void) applyYaw:(GLfloat) yaw
4324{
4325 quaternion_rotate_about_y(&orientation, -yaw);
4326
4327 [self orientationChanged];
4328}
4329
4330
4331- (OOMatrix) drawRotationMatrix // override to provide the 'correct' drawing matrix
4332{
4333 return playerRotMatrix;
4334}
4335
4336
4337- (OOMatrix) drawTransformationMatrix
4338{
4339 OOMatrix result = playerRotMatrix;
4340 // HPVect: modify to use camera-relative positioning
4341 return OOMatrixTranslate(result, HPVectorToVector(position));
4342}
4343
4344
4345- (Quaternion) normalOrientation
4346{
4347 return make_quaternion(-orientation.w, orientation.x, orientation.y, orientation.z);
4348}
4349
4350
4351- (void) setNormalOrientation:(Quaternion) quat
4352{
4353 [self setOrientation:make_quaternion(-quat.w, quat.x, quat.y, quat.z)];
4354}
4355
4356
4357- (void) moveForward:(double) amount
4358{
4359 distanceTravelled += (float)amount;
4360 [self setPosition:HPvector_add(position, vectorToHPVector(vector_multiply_scalar(v_forward, (float)amount)))];
4361}
4362
4363
4364- (HPVector) breakPatternPosition
4365{
4366 return HPvector_add(position,vectorToHPVector(quaternion_rotate_vector(quaternion_conjugate(orientation),forwardViewOffset)));
4367}
4368
4369
4370- (Vector) viewpointOffset
4371{
4372// if ([UNIVERSE breakPatternHide])
4373// return kZeroVector; // center view for break pattern
4374 // now done by positioning break pattern correctly
4375
4376 switch ([UNIVERSE viewDirection])
4377 {
4378 case VIEW_FORWARD:
4379 return forwardViewOffset;
4380 case VIEW_AFT:
4381 return aftViewOffset;
4382 case VIEW_PORT:
4383 return portViewOffset;
4384 case VIEW_STARBOARD:
4385 return starboardViewOffset;
4386 /* GILES custom viewpoints */
4387 case VIEW_CUSTOM:
4388 return customViewOffset;
4389 /* -- */
4390
4391 default:
4392 break;
4393 }
4394
4395 return kZeroVector;
4396}
4397
4398
4399- (Vector) viewpointOffsetAft
4400{
4401 return aftViewOffset;
4402}
4403
4404- (Vector) viewpointOffsetForward
4405{
4406 return forwardViewOffset;
4407}
4408
4409- (Vector) viewpointOffsetPort
4410{
4411 return portViewOffset;
4412}
4413
4414- (Vector) viewpointOffsetStarboard
4415{
4416 return starboardViewOffset;
4417}
4418
4419
4420/* TODO post 1.78: profiling suggests this gets called often enough
4421 * that it's worth caching the result per-frame - CIM */
4422- (HPVector) viewpointPosition
4423{
4424 HPVector viewpoint = position;
4425 if (showDemoShips)
4426 {
4427 viewpoint = kZeroHPVector;
4428 }
4429 Vector offset = [self viewpointOffset];
4430
4431 // FIXME: this ought to be done with matrix or quaternion functions.
4432 OOMatrix r = rotMatrix;
4433
4434 viewpoint.x += offset.x * r.m[0][0]; viewpoint.y += offset.x * r.m[1][0]; viewpoint.z += offset.x * r.m[2][0];
4435 viewpoint.x += offset.y * r.m[0][1]; viewpoint.y += offset.y * r.m[1][1]; viewpoint.z += offset.y * r.m[2][1];
4436 viewpoint.x += offset.z * r.m[0][2]; viewpoint.y += offset.z * r.m[1][2]; viewpoint.z += offset.z * r.m[2][2];
4437
4438 return viewpoint;
4439}
4440
4441
4442- (void) drawImmediate:(bool)immediate translucent:(bool)translucent
4443{
4444 switch ([self status])
4445 {
4446 case STATUS_DEAD:
4447 case STATUS_COCKPIT_DISPLAY:
4448 case STATUS_DOCKED:
4449 case STATUS_START_GAME:
4450 return;
4451
4452 default:
4453 if ([UNIVERSE breakPatternHide]) return;
4454 }
4455
4456 [super drawImmediate:immediate translucent:translucent];
4457}
4458
4459
4460- (void) setMassLockable:(BOOL)newValue
4461{
4462 massLockable = !!newValue;
4463 [self updateAlertCondition];
4464}
4465
4466
4467- (BOOL) massLockable
4468{
4469 return massLockable;
4470}
4471
4472
4473- (BOOL) massLocked
4474{
4475 return ((alertFlags & ALERT_FLAG_MASS_LOCK) != 0);
4476}
4477
4478
4479- (BOOL) atHyperspeed
4480{
4481 return travelling_at_hyperspeed;
4482}
4483
4484
4485- (float) occlusionLevel
4486{
4487 return occlusion_dial;
4488}
4489
4490
4491- (void) setOcclusionLevel:(float)level
4492{
4493 occlusion_dial = level;
4494}
4495
4496
4497- (void) setDockedAtMainStation
4498{
4499 [self setDockedStation:[UNIVERSE station]];
4500 if (_dockedStation != nil) [self setStatus:STATUS_DOCKED];
4501}
4502
4503
4504- (StationEntity *) dockedStation
4505{
4506 return [_dockedStation weakRefUnderlyingObject];
4507}
4508
4509
4510- (void) setDockedStation:(StationEntity *)station
4511{
4512 [_dockedStation release];
4513 _dockedStation = [station weakRetain];
4514}
4515
4516
4517- (void) setTargetDockStationTo:(StationEntity *) value
4518{
4519 targetDockStation = value;
4520}
4521
4522
4523- (StationEntity *) getTargetDockStation
4524{
4525 return targetDockStation;
4526}
4527
4528
4529- (HeadUpDisplay *) hud
4530{
4531 return hud;
4532}
4533
4534
4535- (void) resetHud
4536{
4537 // set up defauld HUD for the ship
4538 NSDictionary *shipDict = [[OOShipRegistry sharedRegistry] shipInfoForKey:[self shipDataKey]];
4539 NSString *hud_desc = [shipDict oo_stringForKey:@"hud" defaultValue:@"hud.plist"];
4540 if (![self switchHudTo:hud_desc]) [self switchHudTo:@"hud.plist"]; // ensure we have a HUD to fall back to
4541}
4542
4543
4544- (BOOL) switchHudTo:(NSString *)hudFileName
4545{
4546 NSDictionary *hudDict = nil;
4547 BOOL wasHidden = NO;
4548 BOOL wasCompassActive = YES;
4549 double scannerZoom = 1.0;
4550 NSUInteger lastMFD = 0;
4551 NSUInteger i;
4552
4553 if (!hudFileName) return NO;
4554
4555 // is the HUD in the process of being rendered? If yes, set it to defer state and abort the switching now
4556 if (hud != nil && [hud isUpdating])
4557 {
4558 [hud setDeferredHudName:hudFileName];
4559 return NO;
4560 }
4561
4562 hudDict = [ResourceManager dictionaryFromFilesNamed:hudFileName inFolder:@"Config" andMerge:YES];
4563 // hud defined, but buggy?
4564 if (hudDict == nil)
4565 {
4566 OOLog(@"PlayerEntity.switchHudTo.failed", @"HUD dictionary file %@ to switch to not found or invalid.", hudFileName);
4567 return NO;
4568 }
4569
4570 if (hud != nil)
4571 {
4572 // remember these values
4573 wasHidden = [hud isHidden];
4574 wasCompassActive = [hud isCompassActive];
4575 scannerZoom = [hud scannerZoom];
4576 lastMFD = activeMFD;
4577 }
4578
4579 // buggy oxp could override hud.plist with a non-dictionary.
4580 if (hudDict != nil)
4581 {
4582 [hud setHidden:YES]; // hide the hud while rebuilding it.
4583 DESTROY(hud);
4584 hud = [[HeadUpDisplay alloc] initWithDictionary:hudDict inFile:hudFileName];
4585 [hud resetGuis:hudDict];
4586 // reset zoom & hidden to what they were before the swich
4587 [hud setScannerZoom:scannerZoom];
4588 [hud setCompassActive:wasCompassActive];
4589 [hud setHidden:wasHidden];
4590 activeMFD = 0;
4591 NSArray *savedMFDs = [NSArray arrayWithArray:multiFunctionDisplaySettings];
4592 [multiFunctionDisplaySettings removeAllObjects];
4593 for (i = 0; i < [hud mfdCount] ; i++)
4594 {
4595 if ([savedMFDs count] > i)
4596 {
4597 [multiFunctionDisplaySettings addObject:[savedMFDs objectAtIndex:i]];
4598 }
4599 else
4600 {
4601 [multiFunctionDisplaySettings addObject:[NSNull null]];
4602 }
4603 }
4604 if (lastMFD < [hud mfdCount]) activeMFD = lastMFD;
4605 }
4606
4607 return YES;
4608}
4609
4610
4611- (float) dialCustomFloat:(NSString *)dialKey
4612{
4613 return [customDialSettings oo_floatForKey:dialKey defaultValue:0.0];
4614}
4615
4616
4617- (NSString *) dialCustomString:(NSString *)dialKey
4618{
4619 return [customDialSettings oo_stringForKey:dialKey defaultValue:@""];
4620}
4621
4622
4623- (OOColor *) dialCustomColor:(NSString *)dialKey
4624{
4625 return [OOColor colorWithDescription:[customDialSettings objectForKey:dialKey]];
4626}
4627
4628
4629- (void) setDialCustom:(id)value forKey:(NSString *)dialKey
4630{
4631 [customDialSettings setObject:value forKey:dialKey];
4632}
4633
4634
4635- (void) setShowDemoShips:(BOOL)value
4636{
4637 showDemoShips = value;
4638}
4639
4640
4641- (BOOL) showDemoShips
4642{
4643 return showDemoShips;
4644}
4645
4646
4647- (float) maxForwardShieldLevel
4648{
4649 return max_forward_shield;
4650}
4651
4652
4653- (float) maxAftShieldLevel
4654{
4655 return max_aft_shield;
4656}
4657
4658
4659- (float) forwardShieldRechargeRate
4660{
4661 return forward_shield_recharge_rate;
4662}
4663
4664
4665- (float) aftShieldRechargeRate
4666{
4667 return aft_shield_recharge_rate;
4668}
4669
4670
4671- (void) setMaxForwardShieldLevel:(float)new
4672{
4673 max_forward_shield = new;
4674}
4675
4676
4677- (void) setMaxAftShieldLevel:(float)new
4678{
4679 max_aft_shield = new;
4680}
4681
4682
4683- (void) setForwardShieldRechargeRate:(float)new
4684{
4685 forward_shield_recharge_rate = new;
4686}
4687
4688
4689- (void) setAftShieldRechargeRate:(float)new
4690{
4691 aft_shield_recharge_rate = new;
4692}
4693
4694
4695- (GLfloat) forwardShieldLevel
4696{
4697 return forward_shield;
4698}
4699
4700
4701- (GLfloat) aftShieldLevel
4702{
4703 return aft_shield;
4704}
4705
4706
4707- (void) setForwardShieldLevel:(GLfloat)level
4708{
4709 forward_shield = OOClamp_0_max_f(level, [self maxForwardShieldLevel]);
4710}
4711
4712
4713- (void) setAftShieldLevel:(GLfloat)level
4714{
4715 aft_shield = OOClamp_0_max_f(level, [self maxAftShieldLevel]);
4716}
4717
4718
4719- (NSDictionary *) keyConfig
4720{
4721 //return keyconfig_settings;
4722 return keyconfig2_settings;
4723}
4724
4725
4726- (BOOL) isMouseControlOn
4727{
4728 return mouse_control_on;
4729}
4730
4731
4732- (GLfloat) dialRoll
4733{
4734 GLfloat result = flightRoll / max_flight_roll;
4735 if ((result < 1.0f)&&(result > -1.0f))
4736 return result;
4737 if (result > 0.0f)
4738 return 1.0f;
4739 return -1.0f;
4740}
4741
4742
4743- (GLfloat) dialPitch
4744{
4745 GLfloat result = flightPitch / max_flight_pitch;
4746 if ((result < 1.0f)&&(result > -1.0f))
4747 return result;
4748 if (result > 0.0f)
4749 return 1.0f;
4750 return -1.0f;
4751}
4752
4753
4754- (GLfloat) dialYaw
4755{
4756 GLfloat result = -flightYaw / max_flight_yaw;
4757 if ((result < 1.0f)&&(result > -1.0f))
4758 return result;
4759 if (result > 0.0f)
4760 return 1.0f;
4761 return -1.0f;
4762}
4763
4764
4765- (GLfloat) dialSpeed
4766{
4767 GLfloat result = flightSpeed / maxFlightSpeed;
4768 return OOClamp_0_1_f(result);
4769}
4770
4771
4772- (GLfloat) dialHyperSpeed
4773{
4774 return flightSpeed / maxFlightSpeed;
4775}
4776
4777
4778- (GLfloat) dialForwardShield
4779{
4780 if (EXPECT_NOT([self maxForwardShieldLevel] <= 0))
4781 {
4782 return 0.0;
4783 }
4784 GLfloat result = forward_shield / [self maxForwardShieldLevel];
4785 return OOClamp_0_1_f(result);
4786}
4787
4788
4789- (GLfloat) dialAftShield
4790{
4791 if (EXPECT_NOT([self maxAftShieldLevel] <= 0))
4792 {
4793 return 0.0;
4794 }
4795 GLfloat result = aft_shield / [self maxAftShieldLevel];
4796 return OOClamp_0_1_f(result);
4797}
4798
4799
4800- (GLfloat) dialEnergy
4801{
4802 GLfloat result = energy / maxEnergy;
4803 return OOClamp_0_1_f(result);
4804}
4805
4806
4807- (GLfloat) dialMaxEnergy
4808{
4809 return maxEnergy;
4810}
4811
4812
4813- (GLfloat) dialFuel
4814{
4815 if (fuel <= 0.0f)
4816 return 0.0f;
4817 if (fuel > [self fuelCapacity])
4818 return 1.0f;
4819 return (GLfloat)fuel / (GLfloat)[self fuelCapacity];
4820}
4821
4822
4823- (GLfloat) dialHyperRange
4824{
4825 if (target_system_id == system_id && ![UNIVERSE inInterstellarSpace]) return 0.0f;
4826 return [self fuelRequiredForJump] / (GLfloat)PLAYER_MAX_FUEL;
4827}
4828
4829
4830- (GLfloat) laserHeatLevel
4831{
4832 GLfloat result = (GLfloat)weapon_temp / (GLfloat)PLAYER_MAX_WEAPON_TEMP;
4833 return OOClamp_0_1_f(result);
4834}
4835
4836
4837- (GLfloat)laserHeatLevelAft
4838{
4839 GLfloat result = aft_weapon_temp / (GLfloat)PLAYER_MAX_WEAPON_TEMP;
4840 return OOClamp_0_1_f(result);
4841}
4842
4843
4844- (GLfloat)laserHeatLevelForward
4845{
4846 GLfloat result = forward_weapon_temp / (GLfloat)PLAYER_MAX_WEAPON_TEMP;
4847// no need to check subents here
4848 return OOClamp_0_1_f(result);
4849}
4850
4851
4852- (GLfloat)laserHeatLevelPort
4853{
4854 GLfloat result = port_weapon_temp / PLAYER_MAX_WEAPON_TEMP;
4855 return OOClamp_0_1_f(result);
4856}
4857
4858
4859- (GLfloat)laserHeatLevelStarboard
4860{
4861 GLfloat result = starboard_weapon_temp / PLAYER_MAX_WEAPON_TEMP;
4862 return OOClamp_0_1_f(result);
4863}
4864
4865
4866
4867
4868- (GLfloat) dialAltitude
4869{
4870 if ([self isDocked]) return 0.0f;
4871
4872 // find nearest planet type entity...
4873 assert(UNIVERSE != nil);
4874
4875 Entity *nearestPlanet = [self findNearestStellarBody];
4876 if (nearestPlanet == nil) return 1.0f;
4877
4878 GLfloat zd = nearestPlanet->zero_distance;
4879 GLfloat cr = nearestPlanet->collision_radius;
4880 GLfloat alt = sqrt(zd) - cr;
4881
4882 return OOClamp_0_1_f(alt / (GLfloat)PLAYER_DIAL_MAX_ALTITUDE);
4883}
4884
4885
4886- (double) clockTime
4887{
4888 return ship_clock;
4889}
4890
4891
4892- (double) clockTimeAdjusted
4893{
4894 return ship_clock + ship_clock_adjust;
4895}
4896
4897
4898- (BOOL) clockAdjusting
4899{
4900 return ship_clock_adjust > 0;
4901}
4902
4903
4904- (void) addToAdjustTime:(double)seconds
4905{
4906 ship_clock_adjust += seconds;
4907}
4908
4909
4910- (double) escapePodRescueTime
4911{
4912 return escape_pod_rescue_time;
4913}
4914
4915
4916- (void) setEscapePodRescueTime:(double)seconds
4917{
4918 escape_pod_rescue_time = seconds;
4919}
4920
4921- (NSString *) dial_clock
4922{
4923 return ClockToString(ship_clock, ship_clock_adjust > 0);
4924}
4925
4926
4927- (NSString *) dial_clock_adjusted
4928{
4929 return ClockToString(ship_clock + ship_clock_adjust, NO);
4930}
4931
4932
4933- (NSString *) dial_fpsinfo
4934{
4935 unsigned fpsVal = fps_counter;
4936 return [NSString stringWithFormat:@"FPS: %3d", fpsVal];
4937}
4938
4939
4940- (NSString *) dial_objinfo
4941{
4942 NSString *result = [NSString stringWithFormat:@"Entities: %3llu", [UNIVERSE entityCount]];
4943#ifndef NDEBUG
4944 result = [NSString stringWithFormat:@"%@ (%d, %zu KiB, avg %zu bytes)", result, gLiveEntityCount, gTotalEntityMemory >> 10, gTotalEntityMemory / gLiveEntityCount];
4945#endif
4946
4947 return result;
4948}
4949
4950
4951- (unsigned) countMissiles
4952{
4953 unsigned n_missiles = 0;
4954 unsigned i;
4955 for (i = 0; i < max_missiles; i++)
4956 {
4957 if (missile_entity[i])
4958 n_missiles++;
4959 }
4960 return n_missiles;
4961}
4962
4963
4964- (OOMissileStatus) dialMissileStatus
4965{
4966 if ([self weaponsOnline])
4967 {
4968 return missile_status;
4969 }
4970 else
4971 {
4972 // Invariant/safety interlock: weapons offline implies missiles safe. -- Ahruman 2012-07-21
4973 if (missile_status != MISSILE_STATUS_SAFE)
4974 {
4975 OOLogERR(@"player.missilesUnsafe", @"%@", @"Missile state is not SAFE when weapons are offline. This is a bug, please report it.");
4976 [self safeAllMissiles];
4977 }
4978 return MISSILE_STATUS_SAFE;
4979 }
4980}
4981
4982
4983- (BOOL) canScoop:(ShipEntity *)other
4984{
4985 if (specialCargo) return NO;
4986 return [super canScoop:other];
4987}
4988
4989
4990- (OOFuelScoopStatus) dialFuelScoopStatus
4991{
4992 // need to account for the different ways of calculating cargo on board when docked/in-flight
4993 OOCargoQuantity cargoOnBoard = [self status] == STATUS_DOCKED ? current_cargo : (OOCargoQuantity)[cargo count];
4994 if ([self hasScoop])
4995 {
4996 if (scoopsActive)
4997 return SCOOP_STATUS_ACTIVE;
4998 if (cargoOnBoard >= [self maxAvailableCargoSpace] || specialCargo)
5000 return SCOOP_STATUS_OKAY;
5001 }
5002 else
5003 {
5005 }
5006}
5007
5008
5009- (float) fuelLeakRate
5010{
5011 return fuel_leak_rate;
5012}
5013
5014
5015- (void) setFuelLeakRate:(float)value
5016{
5017 fuel_leak_rate = fmax(value, 0.0f);
5018}
5019
5020
5021- (NSMutableArray *) commLog
5022{
5024
5025 if (commLog != nil)
5026 {
5027 NSUInteger count = [commLog count];
5029 {
5030 [commLog removeObjectsInRange:NSMakeRange(0, count - kCommLogTrimSize)];
5031 }
5032 }
5033 else
5034 {
5035 commLog = [[NSMutableArray alloc] init];
5036 }
5037
5038 return commLog;
5039}
5040
5041
5042- (NSMutableArray *) roleWeights
5043{
5044 return roleWeights;
5045}
5046
5047
5048- (void) addRoleForAggression:(ShipEntity *)victim
5049{
5050 if ([victim isExplicitlyUnpiloted] || [victim isHulk] || [victim hasHostileTarget] || [[victim primaryAggressor] isPlayer])
5051 {
5052 return;
5053 }
5054 NSString *role = nil;
5055 if ([[victim primaryRole] isEqualToString:@"escape-capsule"])
5056 {
5057 role = @"assassin-player";
5058 }
5059 else if ([victim bounty] > 0)
5060 {
5061 role = @"hunter";
5062 }
5063 else if ([victim isPirateVictim])
5064 {
5065 role = @"pirate";
5066 }
5067 else if ([UNIVERSE role:[self primaryRole] isInCategory:@"oolite-hunter"] || [victim scanClass] == CLASS_POLICE)
5068 {
5069 role = @"pirate-interceptor";
5070 }
5071 if (role == nil)
5072 {
5073 return;
5074 }
5075 NSUInteger times = [roleWeightFlags oo_intForKey:role defaultValue:0];
5076 times++;
5077 [roleWeightFlags setObject:[NSNumber numberWithUnsignedInteger:times] forKey:role];
5078 if ((times & (times-1)) == 0) // is power of 2
5079 {
5080 [self addRoleToPlayer:role];
5081 }
5082}
5083
5084
5085- (void) addRoleForMining
5086{
5087 NSString *role = @"miner";
5088 NSUInteger times = [roleWeightFlags oo_intForKey:role defaultValue:0];
5089 times++;
5090 [roleWeightFlags setObject:[NSNumber numberWithUnsignedInteger:times] forKey:role];
5091 if ((times & (times-1)) == 0) // is power of 2
5092 {
5093 [self addRoleToPlayer:role];
5094 }
5095}
5096
5097
5098- (void) addRoleToPlayer:(NSString *)role
5099{
5100 NSUInteger slot = Ranrot() & ([self maxPlayerRoles]-1);
5101 [self addRoleToPlayer:role inSlot:slot];
5102}
5103
5104
5105- (void) addRoleToPlayer:(NSString *)role inSlot:(NSUInteger)slot
5106{
5107 if (slot >= [self maxPlayerRoles])
5108 {
5109 slot = [self maxPlayerRoles]-1;
5110 }
5111 if (slot >= [roleWeights count])
5112 {
5113 [roleWeights addObject:role];
5114 }
5115 else
5116 {
5117 [roleWeights replaceObjectAtIndex:slot withObject:role];
5118 }
5119}
5120
5121
5122- (void) clearRoleFromPlayer:(BOOL)includingLongRange
5123{
5124 NSUInteger slot = Ranrot() % [roleWeights count];
5125 if (!includingLongRange)
5126 {
5127 NSString *role = [roleWeights objectAtIndex:slot];
5128 // long range roles cleared at 1/2 normal rate
5129 if ([role hasSuffix:@"+"] && randf() > 0.5)
5130 {
5131 return;
5132 }
5133 }
5134 [roleWeights replaceObjectAtIndex:slot withObject:@"player-unknown"];
5135}
5136
5137
5138- (void) clearRolesFromPlayer:(float)chance
5139{
5140 NSUInteger i, count=[roleWeights count];
5141 for (i = 0; i < count; i++)
5142 {
5143 if (randf() < chance)
5144 {
5145 [roleWeights replaceObjectAtIndex:i withObject:@"player-unknown"];
5146 }
5147 }
5148}
5149
5150
5151- (NSUInteger) maxPlayerRoles
5152{
5153 if (ship_kills >= 6400)
5154 {
5155 return 32;
5156 }
5157 else if (ship_kills >= 128)
5158 {
5159 return 16;
5160 }
5161 else
5162 {
5163 return 8;
5164 }
5165}
5166
5167
5168- (void) updateSystemMemory
5169{
5170 OOSystemID sys = [self currentSystemID];
5171 if (sys < 0)
5172 {
5173 return;
5174 }
5175 NSUInteger memory = 4;
5176 if (ship_kills >= 6400)
5177 {
5178 memory = 32;
5179 }
5180 else if (ship_kills >= 256)
5181 {
5182 memory = 16;
5183 }
5184 else if (ship_kills >= 64)
5185 {
5186 memory = 8;
5187 }
5188 if ([roleSystemList count] >= memory)
5189 {
5190 [roleSystemList removeObjectAtIndex:0];
5191 }
5192 [roleSystemList addObject:[NSNumber numberWithInt:sys]];
5193}
5194
5195
5196- (Entity *) compassTarget
5197{
5198 Entity *result = [compassTarget weakRefUnderlyingObject];
5199 if (result == nil)
5200 {
5201 DESTROY(compassTarget);
5202 return nil;
5203 }
5204 return result;
5205}
5206
5207
5208- (void) setCompassTarget:(Entity *)value
5209{
5210 [compassTarget release];
5211 compassTarget = [value weakRetain];
5212}
5213
5214
5215- (void) validateCompassTarget
5216{
5217 OOSunEntity *the_sun = [UNIVERSE sun];
5218 OOPlanetEntity *the_planet = [UNIVERSE planet];
5219 StationEntity *the_station = [UNIVERSE station];
5220 Entity *the_target = [self primaryTarget];
5221 Entity <OOBeaconEntity> *beacon = [self nextBeacon];
5222 if ([self isInSpace] && the_sun && the_planet // be in a system
5223 && ![the_sun goneNova]) // and the system has not been novabombed
5224 {
5225 Entity *new_target = nil;
5226 OOAegisStatus aegis = [self checkForAegis];
5227
5228 switch ([self compassMode])
5229 {
5230 case COMPASS_MODE_INACTIVE:
5231 break;
5232
5233 case COMPASS_MODE_BASIC:
5234 if ((aegis == AEGIS_CLOSE_TO_MAIN_PLANET || aegis == AEGIS_IN_DOCKING_RANGE) && the_station)
5235 {
5236 new_target = the_station;
5237 }
5238 else
5239 {
5240 new_target = the_planet;
5241 }
5242 break;
5243
5244 case COMPASS_MODE_PLANET:
5245 new_target = the_planet;
5246 break;
5247
5248 case COMPASS_MODE_STATION:
5249 new_target = the_station;
5250 break;
5251
5252 case COMPASS_MODE_SUN:
5253 new_target = the_sun;
5254 break;
5255
5256 case COMPASS_MODE_TARGET:
5257 new_target = the_target;
5258 break;
5259
5260 case COMPASS_MODE_BEACONS:
5261 new_target = beacon;
5262 break;
5263 }
5264
5265 if (new_target == nil || [new_target status] < STATUS_ACTIVE || [new_target status] == STATUS_IN_HOLD)
5266 {
5267 [self setCompassMode:COMPASS_MODE_PLANET];
5268 new_target = the_planet;
5269 }
5270
5271 if (EXPECT_NOT(new_target != [self compassTarget]))
5272 {
5273 [self setCompassTarget:new_target];
5274 [self doScriptEvent:OOJSID("compassTargetChanged") withArguments:[NSArray arrayWithObjects:new_target, OOStringFromCompassMode([self compassMode]), nil]];
5275 }
5276 }
5277}
5278
5279
5280- (NSString *) compassTargetLabel
5281{
5282 switch (compassMode)
5283 {
5284 case COMPASS_MODE_INACTIVE:
5285 return @"";
5286 case COMPASS_MODE_BASIC:
5287 return @"";
5288 case COMPASS_MODE_BEACONS:
5289 {
5290 Entity *target = [self compassTarget];
5291 if (target)
5292 {
5293 return [(Entity <OOBeaconEntity> *)target beaconLabel];
5294 }
5295 return @"";
5296 }
5297 case COMPASS_MODE_PLANET:
5298 return [[UNIVERSE planet] name];
5299 case COMPASS_MODE_SUN:
5300 return [[UNIVERSE sun] name];
5301 case COMPASS_MODE_STATION:
5302 return [[UNIVERSE station] displayName];
5303 case COMPASS_MODE_TARGET:
5304 return DESC(@"oolite-beacon-label-target");
5305 }
5306 return @"";
5307}
5308
5309
5310- (OOCompassMode) compassMode
5311{
5312 return compassMode;
5313}
5314
5315
5316- (void) setCompassMode:(OOCompassMode) value
5317{
5318 compassMode = value;
5319}
5320
5321
5322- (void) setPrevCompassMode
5323{
5324 OOAegisStatus aegis = AEGIS_NONE;
5325 Entity <OOBeaconEntity> *beacon = nil;
5326
5327 switch (compassMode)
5328 {
5329 case COMPASS_MODE_INACTIVE:
5330 case COMPASS_MODE_BASIC:
5331 case COMPASS_MODE_PLANET:
5332 beacon = [UNIVERSE lastBeacon];
5333 while (beacon != nil && [beacon isJammingScanning])
5334 {
5335 beacon = [beacon prevBeacon];
5336 }
5337 [self setNextBeacon:beacon];
5338
5339 if (beacon != nil)
5340 {
5341 [self setCompassMode:COMPASS_MODE_BEACONS];
5342 break;
5343 }
5344 // else fall through to switch to target mode.
5345
5346 case COMPASS_MODE_BEACONS:
5347 beacon = [self nextBeacon];
5348 do
5349 {
5350 beacon = [beacon prevBeacon];
5351 } while (beacon != nil && [beacon isJammingScanning]);
5352 [self setNextBeacon:beacon];
5353
5354 if (beacon == nil)
5355 {
5356 if ([self primaryTarget])
5357 {
5358 [self setCompassMode:COMPASS_MODE_TARGET];
5359 }
5360 else
5361 {
5362 [self setCompassMode:COMPASS_MODE_SUN];
5363 }
5364 break;
5365 }
5366 break;
5367
5368 case COMPASS_MODE_TARGET:
5369 [self setCompassMode:COMPASS_MODE_SUN];
5370 break;
5371
5372 case COMPASS_MODE_SUN:
5373 aegis = [self checkForAegis];
5374 if (aegis == AEGIS_CLOSE_TO_MAIN_PLANET || aegis == AEGIS_IN_DOCKING_RANGE)
5375 {
5376 [self setCompassMode:COMPASS_MODE_STATION];
5377 }
5378 else
5379 {
5380 [self setCompassMode:COMPASS_MODE_PLANET];
5381 }
5382 break;
5383
5384 case COMPASS_MODE_STATION:
5385 [self setCompassMode:COMPASS_MODE_PLANET];
5386 break;
5387 }
5388}
5389
5390
5391- (void) setNextCompassMode
5392{
5393 OOAegisStatus aegis = AEGIS_NONE;
5394 Entity <OOBeaconEntity> *beacon = nil;
5395
5396 switch (compassMode)
5397 {
5398 case COMPASS_MODE_INACTIVE:
5399 case COMPASS_MODE_BASIC:
5400 case COMPASS_MODE_PLANET:
5401 aegis = [self checkForAegis];
5402 if ([UNIVERSE station] && (aegis == AEGIS_CLOSE_TO_MAIN_PLANET || aegis == AEGIS_IN_DOCKING_RANGE))
5403 {
5404 [self setCompassMode:COMPASS_MODE_STATION];
5405 }
5406 else
5407 {
5408 [self setCompassMode:COMPASS_MODE_SUN];
5409 }
5410 break;
5411
5412 case COMPASS_MODE_STATION:
5413 [self setCompassMode:COMPASS_MODE_SUN];
5414 break;
5415
5416 case COMPASS_MODE_SUN:
5417 if ([self primaryTarget])
5418 {
5419 [self setCompassMode:COMPASS_MODE_TARGET];
5420 break;
5421 }
5422 // else fall through to switch to beacon mode.
5423
5424 case COMPASS_MODE_TARGET:
5425 beacon = [UNIVERSE firstBeacon];
5426 while (beacon != nil && [beacon isJammingScanning])
5427 {
5428 beacon = [beacon nextBeacon];
5429 }
5430 [self setNextBeacon:beacon];
5431
5432 if (beacon != nil) [self setCompassMode:COMPASS_MODE_BEACONS];
5433 else [self setCompassMode:COMPASS_MODE_PLANET];
5434 break;
5435
5436 case COMPASS_MODE_BEACONS:
5437 beacon = [self nextBeacon];
5438 do
5439 {
5440 beacon = [beacon nextBeacon];
5441 } while (beacon != nil && [beacon isJammingScanning]);
5442 [self setNextBeacon:beacon];
5443
5444 if (beacon == nil)
5445 {
5446 [self setCompassMode:COMPASS_MODE_PLANET];
5447 }
5448 break;
5449 }
5450}
5451
5452
5453- (NSUInteger) activeMissile
5454{
5455 return activeMissile;
5456}
5457
5458
5459- (void) setActiveMissile:(NSUInteger)value
5460{
5461 activeMissile = value;
5462}
5463
5464
5465- (NSUInteger) dialMaxMissiles
5466{
5467 return max_missiles;
5468}
5469
5470
5471- (BOOL) dialIdentEngaged
5472{
5473 return ident_engaged;
5474}
5475
5476
5477- (void) setDialIdentEngaged:(BOOL)newValue
5478{
5479 ident_engaged = !!newValue;
5480}
5481
5482
5483- (NSString *) specialCargo
5484{
5485 return specialCargo;
5486}
5487
5488
5489- (NSString *) dialTargetName
5490{
5491 Entity *target_entity = [self primaryTarget];
5492 NSString *result = nil;
5493
5494 if (target_entity == nil)
5495 {
5496 result = DESC(@"no-target-string");
5497 }
5498
5499 if ([target_entity respondsToSelector:@selector(identFromShip:)])
5500 {
5501 result = [(ShipEntity*)target_entity identFromShip:self];
5502 }
5503
5504 if (result == nil) result = DESC(@"unknown-target");
5505
5506 return result;
5507}
5508
5509
5510- (NSArray *) multiFunctionDisplayList
5511{
5512 return multiFunctionDisplaySettings;
5513}
5514
5515
5516- (NSString *) multiFunctionText:(NSUInteger)i
5517{
5518 NSString *key = [multiFunctionDisplaySettings oo_stringAtIndex:i defaultValue:nil];
5519 if (key == nil)
5520 {
5521 return nil;
5522 }
5523 NSString *text = [multiFunctionDisplayText oo_stringForKey:key defaultValue:nil];
5524 return text;
5525}
5526
5527
5528- (void) setMultiFunctionText:(NSString *)text forKey:(NSString *)key
5529{
5530 if (text != nil)
5531 {
5532 [multiFunctionDisplayText setObject:text forKey:key];
5533 }
5534 else if (key != nil)
5535 {
5536 [multiFunctionDisplayText removeObjectForKey:key];
5537 // and blank any MFDs currently using it
5538 NSUInteger index;
5539 while ((index = [multiFunctionDisplaySettings indexOfObject:key]) != NSNotFound)
5540 {
5541 [multiFunctionDisplaySettings replaceObjectAtIndex:index withObject:[NSNull null]];
5542 }
5543 }
5544}
5545
5546
5547- (BOOL) setMultiFunctionDisplay:(NSUInteger)index toKey:(NSString *)key
5548{
5549 if (index >= [hud mfdCount])
5550 {
5551 // is first inactive display
5552 index = [multiFunctionDisplaySettings indexOfObject:[NSNull null]];
5553 if (index == NSNotFound)
5554 {
5555 return NO;
5556 }
5557 }
5558
5559 if (index < [hud mfdCount])
5560 {
5561 if (key == nil)
5562 {
5563 [multiFunctionDisplaySettings replaceObjectAtIndex:index withObject:[NSNull null]];
5564 }
5565 else
5566 {
5567 [multiFunctionDisplaySettings replaceObjectAtIndex:index withObject:key];
5568 }
5569 return YES;
5570 }
5571 else
5572 {
5573 return NO;
5574 }
5575}
5576
5577
5578- (void) cycleNextMultiFunctionDisplay:(NSUInteger) index
5579{
5580 if ([[self hud] mfdCount] == 0) return;
5581 NSArray *keys = [multiFunctionDisplayText allKeys];
5582 NSString *key = nil;
5583 if ([keys count] == 0)
5584 {
5585 [self setMultiFunctionDisplay:index toKey:nil];
5586 return;
5587 }
5588 id current = [multiFunctionDisplaySettings objectAtIndex:index];
5589 if (current == [NSNull null])
5590 {
5591 key = [keys objectAtIndex:0];
5592 [self setMultiFunctionDisplay:index toKey:key];
5593 }
5594 else
5595 {
5596 NSUInteger cIndex = [keys indexOfObject:current];
5597 if (cIndex == NSNotFound || cIndex + 1 >= [keys count])
5598 {
5599 key = nil;
5600 [self setMultiFunctionDisplay:index toKey:nil];
5601 }
5602 else
5603 {
5604 key = [keys objectAtIndex:(cIndex+1)];
5605 [self setMultiFunctionDisplay:index toKey:key];
5606 }
5607 }
5608 JSContext *context = OOJSAcquireContext();
5609 jsval keyVal = OOJSValueFromNativeObject(context,key);
5610 ShipScriptEvent(context, self, "mfdKeyChanged", INT_TO_JSVAL(activeMFD), keyVal);
5611 OOJSRelinquishContext(context);
5612}
5613
5614
5615- (void) cyclePreviousMultiFunctionDisplay:(NSUInteger) index
5616{
5617 if ([[self hud] mfdCount] == 0) return;
5618 NSArray *keys = [multiFunctionDisplayText allKeys];
5619 NSString *key = nil;
5620 if ([keys count] == 0)
5621 {
5622 [self setMultiFunctionDisplay:index toKey:nil];
5623 return;
5624 }
5625 id current = [multiFunctionDisplaySettings objectAtIndex:index];
5626 if (current == [NSNull null])
5627 {
5628 key = [keys objectAtIndex:([keys count]-1)];
5629 [self setMultiFunctionDisplay:index toKey:key];
5630 }
5631 else
5632 {
5633 NSUInteger cIndex = [keys indexOfObject:current];
5634 if (cIndex == NSNotFound || cIndex == 0)
5635 {
5636 key = nil;
5637 [self setMultiFunctionDisplay:index toKey:nil];
5638 }
5639 else
5640 {
5641 key = [keys objectAtIndex:(cIndex-1)];
5642 [self setMultiFunctionDisplay:index toKey:key];
5643 }
5644 }
5645 JSContext *context = OOJSAcquireContext();
5646 jsval keyVal = OOJSValueFromNativeObject(context,key);
5647 ShipScriptEvent(context, self, "mfdKeyChanged", INT_TO_JSVAL(activeMFD), keyVal);
5648 OOJSRelinquishContext(context);
5649}
5650
5651
5652- (void) selectNextMultiFunctionDisplay
5653{
5654 if ([[self hud] mfdCount] == 0) return;
5655 activeMFD = (activeMFD + 1) % [[self hud] mfdCount];
5656 NSUInteger mfdID = activeMFD + 1;
5657 [UNIVERSE addMessage:OOExpandKey(@"mfd-N-selected", mfdID) forCount:3.0 ];
5658 JSContext *context = OOJSAcquireContext();
5659 ShipScriptEvent(context, self, "selectedMFDChanged", INT_TO_JSVAL(activeMFD));
5660 OOJSRelinquishContext(context);
5661}
5662
5663
5664- (void) selectPreviousMultiFunctionDisplay
5665{
5666 if ([[self hud] mfdCount] == 0) return;
5667 if (activeMFD == 0)
5668 {
5669 activeMFD = ([[self hud] mfdCount] - 1);
5670 }
5671 else
5672 {
5673 activeMFD = (activeMFD - 1);
5674 }
5675 NSUInteger mfdID = activeMFD + 1;
5676 [UNIVERSE addMessage:OOExpandKey(@"mfd-N-selected", mfdID) forCount:3.0 ];
5677 JSContext *context = OOJSAcquireContext();
5678 ShipScriptEvent(context, self, "selectedMFDChanged", INT_TO_JSVAL(activeMFD));
5679 OOJSRelinquishContext(context);
5680}
5681
5682
5683- (NSUInteger) activeMFD
5684{
5685 return activeMFD;
5686}
5687
5688
5689- (ShipEntity *) missileForPylon:(NSUInteger)value
5690{
5691 if (value < max_missiles) return missile_entity[value];
5692 return nil;
5693}
5694
5695
5696
5697- (void) safeAllMissiles
5698{
5699 // sets all missile targets to NO_TARGET
5700
5701 unsigned i;
5702 for (i = 0; i < max_missiles; i++)
5703 {
5704 if (missile_entity[i] && [missile_entity[i] primaryTarget] != nil)
5705 [missile_entity[i] removeTarget:nil];
5706 }
5707 missile_status = MISSILE_STATUS_SAFE;
5708}
5709
5710
5711- (void) tidyMissilePylons
5712{
5713 // Make sure there's no gaps between missiles, synchronise missile_entity & missile_list.
5714 int i, pylon = 0;
5715 OOLog(@"missile.tidying.debug",@"Tidying fitted %d of possible %d missiles",missiles,PLAYER_MAX_MISSILES);
5716 for(i = 0; i < PLAYER_MAX_MISSILES; i++)
5717 {
5718 OOLog(@"missile.tidying.debug",@"%d %@ %@",i,missile_entity[i],missile_list[i]);
5719 if(missile_entity[i] != nil)
5720 {
5721 missile_entity[pylon] = missile_entity[i];
5722 missile_list[pylon] = [OOEquipmentType equipmentTypeWithIdentifier:[missile_entity[i] primaryRole]];
5723 pylon++;
5724 }
5725 }
5726
5727 // Now clean up the remainder of the pylons.
5728 for(i = pylon; i < PLAYER_MAX_MISSILES; i++)
5729 {
5730 missile_entity[i] = nil;
5731 // not strictly needed, but helps clear things up
5732 missile_list[i] = nil;
5733 }
5734}
5735
5736
5737- (void) selectNextMissile
5738{
5739 if (![self weaponsOnline]) return;
5740
5741 unsigned i;
5742 for (i = 1; i < max_missiles; i++)
5743 {
5744 int next_missile = (activeMissile + i) % max_missiles;
5745 if (missile_entity[next_missile])
5746 {
5747 // If we don't have the multi-targeting module installed, clear the active missiles' target
5748 if( ![self hasEquipmentItemProviding:@"EQ_MULTI_TARGET"] && [missile_entity[activeMissile] isMissile] )
5749 {
5750 [missile_entity[activeMissile] removeTarget:nil];
5751 }
5752
5753 // Set next missile to active
5754 [self setActiveMissile:next_missile];
5755
5756 if (missile_status != MISSILE_STATUS_SAFE)
5757 {
5758 missile_status = MISSILE_STATUS_ARMED;
5759
5760 // If the newly active pylon contains a missile then work out its target, if any
5761 if( [missile_entity[activeMissile] isMissile] )
5762 {
5763 if( [self hasEquipmentItemProviding:@"EQ_MULTI_TARGET"] &&
5764 ([missile_entity[next_missile] primaryTarget] != nil))
5765 {
5766 // copy the missile's target
5767 [self addTarget:[missile_entity[next_missile] primaryTarget]];
5768 missile_status = MISSILE_STATUS_TARGET_LOCKED;
5769 }
5770 else if ([self primaryTarget] != nil)
5771 {
5772 // never inherit target if we have EQ_MULTI_TARGET installed! [ Bug #16221 : Targeting enhancement regression ]
5773 /* CIM: seems okay to do this when launching a
5774 * missile to stop multi-target being a bit
5775 * irritating in a fight - 20/8/2014 */
5776 if([self hasEquipmentItemProviding:@"EQ_MULTI_TARGET"] && !launchingMissile)
5777 {
5778 [self noteLostTarget];
5779 DESTROY(_primaryTarget);
5780 }
5781 else
5782 {
5783 [missile_entity[activeMissile] addTarget:[self primaryTarget]];
5784 missile_status = MISSILE_STATUS_TARGET_LOCKED;
5785 }
5786 }
5787 }
5788 }
5789 return;
5790 }
5791 }
5792}
5793
5794
5795- (void) clearAlertFlags
5796{
5797 alertFlags = 0;
5798}
5799
5800
5801- (int) alertFlags
5802{
5803 return alertFlags;
5804}
5805
5806
5807- (void) setAlertFlag:(int)flag to:(BOOL)value
5808{
5809 if (value)
5810 {
5811 alertFlags |= flag;
5812 }
5813 else
5814 {
5815 int comp = ~flag;
5816 alertFlags &= comp;
5817 }
5818}
5819
5820
5821// used by Javascript and the distinction is important for NPCs
5822- (OOAlertCondition) realAlertCondition
5823{
5824 return [self alertCondition];
5825}
5826
5827
5828- (OOAlertCondition) alertCondition
5829{
5830 OOAlertCondition old_alert_condition = alertCondition;
5831 alertCondition = ALERT_CONDITION_GREEN;
5832
5833 [self setAlertFlag:ALERT_FLAG_DOCKED to:[self status] == STATUS_DOCKED];
5834
5835 if (alertFlags & ALERT_FLAG_DOCKED)
5836 {
5837 alertCondition = ALERT_CONDITION_DOCKED;
5838 }
5839 else
5840 {
5841 if (alertFlags != 0)
5842 {
5843 alertCondition = ALERT_CONDITION_YELLOW;
5844 }
5845 if (alertFlags > ALERT_FLAG_YELLOW_LIMIT)
5846 {
5847 alertCondition = ALERT_CONDITION_RED;
5848 }
5849 }
5850 if ((alertCondition == ALERT_CONDITION_RED)&&(old_alert_condition < ALERT_CONDITION_RED))
5851 {
5852 [self playAlertConditionRed];
5853 }
5854
5855 return alertCondition;
5856}
5857
5858
5859- (OOPlayerFleeingStatus) fleeingStatus
5860{
5861 return fleeing_status;
5862}
5863
5865
5866
5867- (void) interpretAIMessage:(NSString *)ms
5868{
5869 if ([ms isEqual:@"HOLD_FULL"])
5870 {
5871 [self playHoldFull];
5872 [UNIVERSE addMessage:DESC(@"hold-full") forCount:4.5];
5873 }
5874
5875 if ([ms isEqual:@"INCOMING_MISSILE"])
5876 {
5877 if ([self primaryAggressor] != nil)
5878 {
5879 [self playIncomingMissile:HPVectorToVector([[self primaryAggressor] position])];
5880 }
5881 else
5882 {
5883 [self playIncomingMissile:kZeroVector];
5884 }
5885 [UNIVERSE addMessage:DESC(@"incoming-missile") forCount:4.5];
5886 }
5887
5888 if ([ms isEqual:@"ENERGY_LOW"])
5889 {
5890 [UNIVERSE addMessage:DESC(@"energy-low") forCount:6.0];
5891 }
5892
5893 if ([ms isEqual:@"ECM"] && ![self isDocked]) [self playHitByECMSound];
5894
5895 if ([ms isEqual:@"DOCKING_REFUSED"] && [self status] == STATUS_AUTOPILOT_ENGAGED)
5896 {
5897 [self playDockingDenied];
5898 [UNIVERSE addMessage:DESC(@"autopilot-denied") forCount:4.5];
5899 autopilot_engaged = NO;
5900 [self resetAutopilotAI];
5901 DESTROY(_primaryTarget);
5902 [self setStatus:STATUS_IN_FLIGHT];
5904 [self doScriptEvent:OOJSID("playerDockingRefused")];
5905 }
5906
5907 // aegis messages to advanced compass so in planet mode it behaves like the old compass
5908 if (compassMode != COMPASS_MODE_BASIC)
5909 {
5910 if ([ms isEqual:@"AEGIS_CLOSE_TO_MAIN_PLANET"]&&(compassMode == COMPASS_MODE_PLANET))
5911 {
5912 [self playAegisCloseToPlanet];
5913 [self setCompassMode:COMPASS_MODE_STATION];
5914 }
5915 if ([ms isEqual:@"AEGIS_IN_DOCKING_RANGE"]&&(compassMode == COMPASS_MODE_PLANET))
5916 {
5917 [self playAegisCloseToStation];
5918 [self setCompassMode:COMPASS_MODE_STATION];
5919 }
5920 if ([ms isEqual:@"AEGIS_NONE"]&&(compassMode == COMPASS_MODE_STATION))
5921 {
5922 [self setCompassMode:COMPASS_MODE_PLANET];
5923 }
5924 }
5925}
5926
5927
5928- (BOOL) mountMissile:(ShipEntity *)missile
5929{
5930 if (missile == nil) return NO;
5931
5932 unsigned i;
5933 for (i = 0; i < max_missiles; i++)
5934 {
5935 if (missile_entity[i] == nil)
5936 {
5937 missile_entity[i] = [missile retain];
5938 missile_list[missiles] = [OOEquipmentType equipmentTypeWithIdentifier:[missile primaryRole]];
5939 missiles++;
5940 if (missiles == 1) [self setActiveMissile:0]; // auto select the first purchased missile
5941 return YES;
5942 }
5943 }
5944
5945 return NO;
5946}
5947
5948
5949- (BOOL) mountMissileWithRole:(NSString *)role
5950{
5951 if ([self missileCount] >= [self missileCapacity]) return NO;
5952 return [self mountMissile:[[UNIVERSE newShipWithRole:role] autorelease]];
5953}
5954
5955
5956- (ShipEntity *) fireMissile
5957{
5958 ShipEntity *missile = missile_entity[activeMissile]; // retain count is 1
5959 NSString *identifier = [missile primaryRole];
5960 ShipEntity *firedMissile = nil;
5961
5962 if (missile == nil) return nil;
5963
5964 if (![self weaponsOnline]) return nil;
5965
5966 // check if we were cloaked before firing the missile - can't use
5967 // cloaking_device_active directly because fireMissilewithIdentifier: andTarget:
5968 // will reset it in case passive cloak is set - Nikos 20130313
5969 BOOL cloakedPriorToFiring = cloaking_device_active;
5970
5971 launchingMissile = YES;
5972 replacingMissile = NO;
5973
5974 if ([missile isMine] && (missile_status != MISSILE_STATUS_SAFE))
5975 {
5976 firedMissile = [self launchMine:missile];
5977 if (!replacingMissile) [self removeFromPylon:activeMissile];
5978 if (firedMissile != nil) [self playMineLaunched:[self missileLaunchPosition] weaponIdentifier:identifier];
5979 }
5980 else
5981 {
5982 if (missile_status != MISSILE_STATUS_TARGET_LOCKED) return nil;
5983 // release this before creating it anew in fireMissileWithIdentifier
5984 firedMissile = [self fireMissileWithIdentifier:identifier andTarget:[missile primaryTarget]];
5985
5986 if (firedMissile != nil)
5987 {
5988 if (!replacingMissile) [self removeFromPylon:activeMissile];
5989 [self playMissileLaunched:[self missileLaunchPosition] weaponIdentifier:identifier];
5990 }
5991 }
5992
5993 if (cloakedPriorToFiring && cloakPassive)
5994 {
5995 // fireMissilewithIdentifier: andTarget: has already taken care of deactivating
5996 // the cloak in the case of missiles by the time we get here, but explicitly
5997 // calling deactivateCloakingDevice is needed in order to be covered fully with mines too
5998 [self deactivateCloakingDevice];
5999 }
6000
6001 replacingMissile = NO;
6002 launchingMissile = NO;
6003
6004 return firedMissile;
6005}
6006
6007
6008- (ShipEntity *) launchMine:(ShipEntity*) mine
6009{
6010 if (!mine)
6011 return nil;
6012
6013 if (![self weaponsOnline])
6014 return nil;
6015
6016 [mine setOwner: self];
6017 [mine setBehaviour: BEHAVIOUR_IDLE];
6018 [self dumpItem: mine]; // includes UNIVERSE addEntity: CLASS_CARGO, STATUS_IN_FLIGHT, AI state GLOBAL ( the last one starts the timer !)
6019 [mine setScanClass: CLASS_MINE];
6020
6021 float mine_speed = 500.0f;
6022 Vector mvel = vector_subtract([mine velocity], vector_multiply_scalar(v_forward, mine_speed));
6023 [mine setVelocity: mvel];
6024 [self doScriptEvent:OOJSID("shipReleasedEquipment") withArgument:mine];
6025 return mine;
6026}
6027
6028
6029- (BOOL) assignToActivePylon:(NSString *)equipmentKey
6030{
6031 if (!launchingMissile) return NO;
6032
6033 OOEquipmentType *eqType = nil;
6034
6035 if ([equipmentKey hasSuffix:@"_DAMAGED"])
6036 {
6037 return NO;
6038 }
6039 else
6040 {
6041 eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey];
6042 }
6043
6044 // missiles with techlevel above 99 (kOOVariableTechLevel) are never available to the player
6045 if (![eqType isMissileOrMine] || [eqType effectiveTechLevel] > kOOVariableTechLevel)
6046 {
6047 return NO;
6048 }
6049
6050 ShipEntity *amiss = [UNIVERSE newShipWithRole:equipmentKey];
6051
6052 if (!amiss) return NO;
6053
6054 // replace the missile now.
6055 [missile_entity[activeMissile] release];
6056 missile_entity[activeMissile] = amiss;
6057 missile_list[activeMissile] = eqType;
6058
6059 // make sure the new missile is properly activated.
6060 if (activeMissile > 0) activeMissile--;
6061 else activeMissile = max_missiles - 1;
6062 [self selectNextMissile];
6063
6064 replacingMissile = YES;
6065
6066 return YES;
6067}
6068
6069
6070- (BOOL) activateCloakingDevice
6071{
6072 if (![self hasCloakingDevice]) return NO;
6073
6074 if ([super activateCloakingDevice])
6075 {
6076 [UNIVERSE setCurrentPostFX:OO_POSTFX_CLOAK];
6077 [UNIVERSE addMessage:DESC(@"cloak-on") forCount:2];
6078 [self playCloakingDeviceOn];
6079 return YES;
6080 }
6081 else
6082 {
6083 [UNIVERSE addMessage:DESC(@"cloak-low-juice") forCount:3];
6084 [self playCloakingDeviceInsufficientEnergy];
6085 return NO;
6086 }
6087}
6088
6089
6090- (void) deactivateCloakingDevice
6091{
6092 if (![self hasCloakingDevice]) return;
6093
6094 [super deactivateCloakingDevice];
6095 [UNIVERSE terminatePostFX:OO_POSTFX_CLOAK];
6096 [UNIVERSE addMessage:DESC(@"cloak-off") forCount:2];
6097 [self playCloakingDeviceOff];
6098}
6099
6100
6101/* Scanner fuzziness is entirely cosmetic - it doesn't affect the
6102 * player's actual target locks */
6103- (double) scannerFuzziness
6104{
6105 double fuzz = 0.0;
6106
6107 /* Fuzziness from ECM bursts */
6108 if (last_ecm_time > 0.0)
6109 {
6110 double since = [UNIVERSE getTime] - last_ecm_time;
6111 if (since < SCANNER_ECM_FUZZINESS)
6112 {
6113 fuzz += (SCANNER_ECM_FUZZINESS - since) * (SCANNER_ECM_FUZZINESS - since) * 500.0;
6114 }
6115 }
6116 /* Other causes could go here */
6117
6118 return fuzz;
6119}
6120
6121
6122- (void) noticeECM
6123{
6124 last_ecm_time = [UNIVERSE getTime];
6125}
6126
6127
6128- (BOOL) fireECM
6129{
6130 if ([super fireECM])
6131 {
6132 ecm_in_operation = YES;
6133 ecm_start_time = [UNIVERSE getTime];
6134 return YES;
6135 }
6136 else
6137 {
6138 return NO;
6139 }
6140}
6141
6142
6143- (OOEnergyUnitType) installedEnergyUnitType
6144{
6145 if ([self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT"]) return ENERGY_UNIT_NAVAL;
6146 if ([self hasEquipmentItem:@"EQ_ENERGY_UNIT"]) return ENERGY_UNIT_NORMAL;
6147 return ENERGY_UNIT_NONE;
6148}
6149
6150
6151- (OOEnergyUnitType) energyUnitType
6152{
6153 if ([self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT"]) return ENERGY_UNIT_NAVAL;
6154 if ([self hasEquipmentItem:@"EQ_ENERGY_UNIT"]) return ENERGY_UNIT_NORMAL;
6155 if ([self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT_DAMAGED"]) return ENERGY_UNIT_NAVAL_DAMAGED;
6156 if ([self hasEquipmentItem:@"EQ_ENERGY_UNIT_DAMAGED"]) return ENERGY_UNIT_NORMAL_DAMAGED;
6157 return ENERGY_UNIT_NONE;
6158}
6159
6160
6161- (void) currentWeaponStats
6162{
6163 OOWeaponType currentWeapon = [self currentWeapon];
6164 // Did find & correct a minor mismatch between player and NPC weapon stats. This is the resulting code - Kaks 20101027
6165
6166 // Basic stats: weapon_damage & weaponRange (weapon_recharge_rate is not used by the player)
6167 [self setWeaponDataFromType:currentWeapon];
6168}
6169
6170
6171- (BOOL) weaponsOnline
6172{
6173 return weapons_online;
6174}
6175
6176
6177- (void) setWeaponsOnline:(BOOL)newValue
6178{
6179 weapons_online = !!newValue;
6180 if (!weapons_online) [self safeAllMissiles];
6181}
6182
6183
6184- (NSArray *) currentLaserOffset
6185{
6186 return [self laserPortOffset:currentWeaponFacing];
6187}
6188
6189
6190- (BOOL) fireMainWeapon
6191{
6192 OOWeaponType weapon_to_be_fired = [self currentWeapon];
6193
6194 if (![self weaponsOnline])
6195 {
6196 return NO;
6197 }
6198
6199 if (weapon_temp / PLAYER_MAX_WEAPON_TEMP >= WEAPON_COOLING_CUTOUT)
6200 {
6201 [self playWeaponOverheated:[[self currentLaserOffset] oo_vectorAtIndex:0]];
6202 [UNIVERSE addMessage:DESC(@"weapon-overheat") forCount:3.0];
6203 return NO;
6204 }
6205
6206 if (isWeaponNone(weapon_to_be_fired))
6207 {
6208 return NO;
6209 }
6210
6211 [self currentWeaponStats];
6212
6213 NSUInteger multiplier = 1;
6214 if (_multiplyWeapons)
6215 {
6216 // multiple fitted
6217 multiplier = [[self laserPortOffset:currentWeaponFacing] count];
6218 }
6219
6220 if (energy <= weapon_energy_use * multiplier)
6221 {
6222 [UNIVERSE addMessage:DESC(@"weapon-out-of-juice") forCount:3.0];
6223 return NO;
6224 }
6225
6226 using_mining_laser = [weapon_to_be_fired isMiningLaser];
6227
6228 energy -= weapon_energy_use * multiplier;
6229
6230 switch (currentWeaponFacing)
6231 {
6233 forward_weapon_temp += weapon_shot_temperature * multiplier;
6234 forward_shot_time = 0.0;
6235 break;
6236
6237 case WEAPON_FACING_AFT:
6238 aft_weapon_temp += weapon_shot_temperature * multiplier;
6239 aft_shot_time = 0.0;
6240 break;
6241
6242 case WEAPON_FACING_PORT:
6243 port_weapon_temp += weapon_shot_temperature * multiplier;
6244 port_shot_time = 0.0;
6245 break;
6246
6248 starboard_weapon_temp += weapon_shot_temperature * multiplier;
6249 starboard_shot_time = 0.0;
6250 break;
6251
6252 case WEAPON_FACING_NONE:
6253 break;
6254 }
6255
6256 BOOL weaponFired = NO;
6257 if (!isWeaponNone(weapon_to_be_fired))
6258 {
6259 if (![weapon_to_be_fired isTurretLaser])
6260 {
6261 [self fireLaserShotInDirection:currentWeaponFacing weaponIdentifier:[[self currentWeapon] identifier]];
6262 weaponFired = YES;
6263 }
6264 else
6265 {
6266 // nothing: compatible with previous versions
6267 }
6268 }
6269
6270 if (weaponFired && cloaking_device_active && cloakPassive)
6271 {
6272 [self deactivateCloakingDevice];
6273 }
6274
6275 return weaponFired;
6276}
6277
6278
6279- (OOWeaponType) weaponForFacing:(OOWeaponFacing)facing
6280{
6281 switch (facing)
6282 {
6284 return forward_weapon_type;
6285
6286 case WEAPON_FACING_AFT:
6287 return aft_weapon_type;
6288
6289 case WEAPON_FACING_PORT:
6290 return port_weapon_type;
6291
6293 return starboard_weapon_type;
6294
6295 case WEAPON_FACING_NONE:
6296 break;
6297 }
6298 return nil;
6299}
6300
6301
6302- (OOWeaponType) currentWeapon
6303{
6304 return [self weaponForFacing:currentWeaponFacing];
6305}
6306
6307
6308// override ShipEntity definition to ensure that
6309// if shields are still up, always hit the main entity and take the damage
6310// on the shields
6311- (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1 :(ShipEntity **)hitEntity
6312{
6313 if (hitEntity)
6314 hitEntity[0] = (ShipEntity*)nil;
6315 Vector u0 = HPVectorToVector(HPvector_between(position, v0)); // relative to origin of model / octree
6316 Vector u1 = HPVectorToVector(HPvector_between(position, v1));
6317 Vector w0 = make_vector(dot_product(u0, v_right), dot_product(u0, v_up), dot_product(u0, v_forward)); // in ijk vectors
6318 Vector w1 = make_vector(dot_product(u1, v_right), dot_product(u1, v_up), dot_product(u1, v_forward));
6319 GLfloat hit_distance = [octree isHitByLine:w0 :w1];
6320 if (hit_distance)
6321 {
6322 if (hitEntity)
6323 hitEntity[0] = self;
6324 }
6325
6326 bool shields = false;
6327 if ((w0.z >= 0 && forward_shield > 1) || (w0.z <= 0 && aft_shield > 1))
6328 {
6329 shields = true;
6330 }
6331
6332 NSEnumerator *subEnum = nil;
6333 ShipEntity *se = nil;
6334 for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
6335 {
6336 HPVector p0 = [se absolutePositionForSubentity];
6337 Triangle ijk = [se absoluteIJKForSubentity];
6338 u0 = HPVectorToVector(HPvector_between(p0, v0));
6339 u1 = HPVectorToVector(HPvector_between(p0, v1));
6340 w0 = resolveVectorInIJK(u0, ijk);
6341 w1 = resolveVectorInIJK(u1, ijk);
6342
6343 GLfloat hitSub = [se->octree isHitByLine:w0 :w1];
6344 if (hitSub && (hit_distance == 0 || hit_distance > hitSub))
6345 {
6346 hit_distance = hitSub;
6347 if (hitEntity && !shields)
6348 {
6349 *hitEntity = se;
6350 }
6351 }
6352 }
6353
6354 return hit_distance;
6355}
6356
6357
6358
6359- (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other weaponIdentifier:(NSString *)weaponIdentifier
6360{
6361 HPVector rel_pos;
6362 OOScalar d_forward, d_right, d_up;
6363 BOOL internal_damage = NO; // base chance
6364
6365 OOLog(@"player.ship.damage", @"Player took damage from %@ becauseOf %@", ent, other);
6366
6367 if ([self status] == STATUS_DEAD) return;
6368 if ([self status] == STATUS_ESCAPE_SEQUENCE) return; // if the player has ejected, don't deal more damage
6369 if (amount == 0.0) return;
6370
6371 BOOL cascadeWeapon = [ent isCascadeWeapon];
6372 BOOL cascading = NO;
6373 if (cascadeWeapon)
6374 {
6375 cascading = [self cascadeIfAppropriateWithDamageAmount:amount cascadeOwner:[ent owner]];
6376 }
6377
6378 // make sure ent (& its position) is the attacking _ship_/missile !
6379 if (ent && [ent isSubEntity]) ent = [ent owner];
6380
6381 [[ent retain] autorelease];
6382 [[other retain] autorelease];
6383
6384 rel_pos = (ent != nil) ? [ent position] : kZeroHPVector;
6385 rel_pos = HPvector_subtract(rel_pos, position);
6386
6387 [self doScriptEvent:OOJSID("shipBeingAttacked") withArgument:ent];
6388 if ([ent isShip]) [(ShipEntity *)ent doScriptEvent:OOJSID("shipAttackedOther") withArgument:self];
6389
6390 d_forward = dot_product(HPVectorToVector(rel_pos), v_forward);
6391 d_right = dot_product(HPVectorToVector(rel_pos), v_right);
6392 d_up = dot_product(HPVectorToVector(rel_pos), v_up);
6393 Vector relative = make_vector(d_right,d_up,d_forward);
6394
6395 [self playShieldHit:relative weaponIdentifier:weaponIdentifier];
6396
6397 // firing on an innocent ship is an offence
6398 if ([other isShip])
6399 {
6400 [self broadcastHitByLaserFrom:(ShipEntity*) other];
6401 }
6402
6403 if (d_forward >= 0)
6404 {
6405 forward_shield -= amount;
6406 if (forward_shield < 0.0)
6407 {
6408 amount = -forward_shield;
6409 forward_shield = 0.0f;
6410 }
6411 else
6412 {
6413 amount = 0.0;
6414 }
6415 }
6416 else
6417 {
6418 aft_shield -= amount;
6419 if (aft_shield < 0.0)
6420 {
6421 amount = -aft_shield;
6422 aft_shield = 0.0f;
6423 }
6424 else
6425 {
6426 amount = 0.0;
6427 }
6428 }
6429
6430 OOShipDamageType damageType = cascadeWeapon ? kOODamageTypeCascadeWeapon : kOODamageTypeEnergy;
6431
6432 if (amount > 0.0)
6433 {
6434 energy -= amount;
6435 [self playDirectHit:relative weaponIdentifier:weaponIdentifier];
6436 if (ship_temperature < SHIP_MAX_CABIN_TEMP)
6437 {
6438 /* Heat increase from energy impacts will never directly cause
6439 * overheating - too easy for missile hits to cause an uncredited
6440 * death by overheating against NPCs, so same rules for player */
6441 ship_temperature += amount * SHIP_ENERGY_DAMAGE_TO_HEAT_FACTOR / [self heatInsulation];
6442 if (ship_temperature > SHIP_MAX_CABIN_TEMP)
6443 {
6444 ship_temperature = SHIP_MAX_CABIN_TEMP;
6445 }
6446 }
6447 }
6448 [self noteTakingDamage:amount from:other type:damageType];
6449 if (cascading) energy = 0.0; // explicitly set energy to zero when cascading, in case an oxp raised the energy in noteTakingDamage.
6450
6451 if (energy <= 0.0) //use normal ship temperature calculations for heat damage
6452 {
6453 if ([other isShip])
6454 {
6455 [(ShipEntity *)other noteTargetDestroyed:self];
6456 }
6457
6458 [self getDestroyedBy:other damageType:damageType];
6459 }
6460 else
6461 {
6462 while (amount > 0.0)
6463 {
6464 internal_damage = ((ranrot_rand() & PLAYER_INTERNAL_DAMAGE_FACTOR) < amount); // base chance of damage to systems
6465 if (internal_damage)
6466 {
6467 [self takeInternalDamage];
6468 }
6469 amount -= (PLAYER_INTERNAL_DAMAGE_FACTOR + 1);
6470 }
6471 }
6472}
6473
6474
6475- (void) takeScrapeDamage:(double) amount from:(Entity *) ent
6476{
6477 HPVector rel_pos;
6478 OOScalar d_forward, d_right, d_up;
6479 BOOL internal_damage = NO; // base chance
6480
6481 if ([self status] == STATUS_DEAD) return;
6482
6483 if (amount < 0)
6484 {
6485 OOLog(@"player.ship.damage", @"Player took negative scrape damage %.3f so we made it positive", amount);
6486 amount = -amount;
6487 }
6488 OOLog(@"player.ship.damage", @"Player took %.3f scrape damage from %@", amount, ent);
6489
6490 [[ent retain] autorelease];
6491 rel_pos = ent ? [ent position] : kZeroHPVector;
6492 rel_pos = HPvector_subtract(rel_pos, position);
6493 // rel_pos is now small
6494 d_forward = dot_product(HPVectorToVector(rel_pos), v_forward);
6495 d_right = dot_product(HPVectorToVector(rel_pos), v_right);
6496 d_up = dot_product(HPVectorToVector(rel_pos), v_up);
6497 Vector relative = make_vector(d_right,d_up,d_forward);
6498
6499 [self playScrapeDamage:relative];
6500 if (d_forward >= 0)
6501 {
6502 forward_shield -= amount;
6503 if (forward_shield < 0.0)
6504 {
6505 amount = -forward_shield;
6506 forward_shield = 0.0f;
6507 }
6508 else
6509 {
6510 amount = 0.0;
6511 }
6512 }
6513 else
6514 {
6515 aft_shield -= amount;
6516 if (aft_shield < 0.0)
6517 {
6518 amount = -aft_shield;
6519 aft_shield = 0.0f;
6520 }
6521 else
6522 {
6523 amount = 0.0;
6524 }
6525 }
6526
6527 [super takeScrapeDamage:amount from:ent];
6528
6529 while (amount > 0.0)
6530 {
6531 internal_damage = ((ranrot_rand() & PLAYER_INTERNAL_DAMAGE_FACTOR) < amount); // base chance of damage to systems
6532 if (internal_damage)
6533 {
6534 [self takeInternalDamage];
6535 }
6536 amount -= (PLAYER_INTERNAL_DAMAGE_FACTOR + 1);
6537 }
6538}
6539
6540
6541- (void) takeHeatDamage:(double) amount
6542{
6543 if ([self status] == STATUS_DEAD || amount < 0) return;
6544
6545 // hit the shields first!
6546 float fwd_amount = (float)(0.5 * amount);
6547 float aft_amount = (float)(0.5 * amount);
6548
6549 forward_shield -= fwd_amount;
6550 if (forward_shield < 0.0)
6551 {
6552 fwd_amount = -forward_shield;
6553 forward_shield = 0.0f;
6554 }
6555 else
6556 {
6557 fwd_amount = 0.0f;
6558 }
6559
6560 aft_shield -= aft_amount;
6561 if (aft_shield < 0.0)
6562 {
6563 aft_amount = -aft_shield;
6564 aft_shield = 0.0f;
6565 }
6566 else
6567 {
6568 aft_amount = 0.0f;
6569 }
6570
6571 double residual_amount = fwd_amount + aft_amount;
6572
6573 [super takeHeatDamage:residual_amount];
6574}
6575
6576
6577- (ProxyPlayerEntity *) createDoppelganger
6578{
6579 ProxyPlayerEntity *result = (ProxyPlayerEntity *)[[UNIVERSE newShipWithName:[self shipDataKey] usePlayerProxy:YES] autorelease];
6580
6581 if (result != nil)
6582 {
6583 [result setPosition:[self position]];
6584 [result setScanClass:CLASS_NEUTRAL];
6585 [result setOrientation:[self normalOrientation]];
6586 [result setVelocity:[self velocity]];
6587 [result setSpeed:[self flightSpeed]];
6588 [result setDesiredSpeed:[self flightSpeed]];
6589 [result setRoll:flightRoll];
6590 [result setBehaviour:BEHAVIOUR_IDLE];
6591 [result switchAITo:@"nullAI.plist"]; // fly straight on
6592 [result setTemperature:[self temperature]];
6593 [result copyValuesFromPlayer:self];
6594 }
6595
6596 return result;
6597}
6598
6599
6600- (ShipEntity *) launchEscapeCapsule
6601{
6602 ShipEntity *doppelganger = nil;
6603 ShipEntity *escapePod = nil;
6604
6605 if ([UNIVERSE displayGUI]) [self switchToMainView]; // Clear the F7 screen!
6606 [UNIVERSE setViewDirection:VIEW_FORWARD];
6607
6608 if ([self status] == STATUS_DEAD) return nil;
6609
6610 /*
6611 While inside the escape pod, we need to block access to all player.ship properties,
6612 since we're not supposed to be inside our ship anymore! -- Kaks 20101114
6613 */
6614
6615 [UNIVERSE setBlockJSPlayerShipProps:YES]; // no player.ship properties while inside the pod!
6616 // if a specific amount of time has been provided for the rescue, use it now
6617 if (escape_pod_rescue_time > 0)
6618 {
6619 ship_clock_adjust += escape_pod_rescue_time;
6620 escape_pod_rescue_time = 0; // reset value
6621 }
6622 else
6623 {
6624 // otherwise, use the default time calc
6625 ship_clock_adjust += 43200 + 5400 * (ranrot_rand() & 127); // add up to 8 days until rescue!
6626 }
6627 dockingClearanceStatus = DOCKING_CLEARANCE_STATUS_NOT_REQUIRED;
6628 flightSpeed = fmin(flightSpeed, maxFlightSpeed);
6629
6630 doppelganger = [self createDoppelganger];
6631 if (doppelganger)
6632 {
6633 [doppelganger setVelocity:vector_multiply_scalar(v_forward, flightSpeed)];
6634 [doppelganger setSpeed:0.0];
6635 [doppelganger setDesiredSpeed:0.0];
6636 [doppelganger setRoll:0.2 * (randf() - 0.5)];
6637 [doppelganger setOwner:self];
6638 [doppelganger setThrust:0]; // drifts
6639 [UNIVERSE addEntity:doppelganger];
6640 }
6641
6642 [self setFoundTarget:doppelganger]; // must do this before setting status
6643 [self setStatus:STATUS_ESCAPE_SEQUENCE]; // now set up the escape sequence.
6644
6645
6646 // must do this before next step or uses BBox of pod, not old ship!
6647 float sheight = (float)(boundingBox.max.y - boundingBox.min.y);
6648 position = HPvector_subtract(position, vectorToHPVector(vector_multiply_scalar(v_up, sheight)));
6649 float sdepth = (float)(boundingBox.max.z - boundingBox.min.z);
6650 position = HPvector_subtract(position, vectorToHPVector(vector_multiply_scalar(v_forward, sdepth/2.0)));
6651
6652 // set up you
6653 escapePod = [UNIVERSE newShipWithName:@"escape-capsule"]; // retained
6654 if (escapePod != nil)
6655 {
6656 // FIXME: this should use OOShipType, which should exist. -- Ahruman
6657 [self setMesh:[escapePod mesh]];
6658 }
6659
6660 /* These used to be non-zero, but BEHAVIOUR_IDLE levels off flight
6661 * anyway, and inertial velocity is used instead of inertialess
6662 * thrust - CIM */
6663 flightSpeed = 0.0f;
6664 flightPitch = 0.0f;
6665 flightRoll = 0.0f;
6666 flightYaw = 0.0f;
6667 // and turn off inertialess drive
6668 thrust = 0.0f;
6669
6670
6671 /* Add an impulse upwards and backwards to the escape pod. This avoids
6672 flying straight through the doppelganger in interstellar space or when
6673 facing the main station/escape target, and generally looks cool.
6674 -- Ahruman 2011-04-02
6675 */
6676 Vector launchVector = vector_add([doppelganger velocity],
6677 vector_add(vector_multiply_scalar(v_up, 15.0f),
6678 vector_multiply_scalar(v_forward, -90.0f)));
6679 [self setVelocity:launchVector];
6680
6681
6682
6683 // if multiple items providing escape pod, remove the first one
6684 [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_ESCAPE_POD"]];
6685
6686
6687 // set up the standard location where the escape pod will dock.
6688 target_system_id = system_id; // we're staying in this system
6689 info_system_id = system_id;
6690 [self setDockTarget:[UNIVERSE station]]; // we're docking at the main station, if there is one
6691
6692 [self doScriptEvent:OOJSID("shipLaunchedEscapePod") withArgument:escapePod]; // no player.ship properties should be available to script
6693
6694 // reset legal status
6695 [self setBounty:0 withReason:kOOLegalStatusReasonEscapePod];
6696 bounty = 0;
6697
6698 // new ship, so lose some memory of player actions
6699 if (ship_kills >= 6400)
6700 {
6701 [self clearRolesFromPlayer:0.1];
6702 }
6703 else if (ship_kills >= 2560)
6704 {
6705 [self clearRolesFromPlayer:0.25];
6706 }
6707 else
6708 {
6709 [self clearRolesFromPlayer:0.5];
6710 }
6711
6712 // reset trumbles
6713 if (trumbleCount != 0) trumbleCount = 1;
6714
6715 // remove cargo
6716 [cargo removeAllObjects];
6717
6718 energy = 25;
6719 [UNIVERSE addMessage:DESC(@"escape-sequence") forCount:4.5];
6720 [self resetShotTime];
6721
6722 // need to zero out all facings shot_times too, otherwise we may end up
6723 // with a broken escape pod sequence - Nikos 20100909
6724 forward_shot_time = 0.0;
6725 aft_shot_time = 0.0;
6726 port_shot_time = 0.0;
6727 starboard_shot_time = 0.0;
6728
6729 [escapePod release];
6730
6731 return doppelganger;
6732}
6733
6734
6735- (OOCommodityType) dumpCargo
6736{
6737 if (flightSpeed > 4.0 * maxFlightSpeed)
6738 {
6739 [UNIVERSE addMessage:OOExpandKey(@"hold-locked") forCount:3.0];
6740 return nil;
6741 }
6742
6743 OOCommodityType result = [super dumpCargo];
6744 if (result != nil)
6745 {
6746 NSString *commodity = [UNIVERSE displayNameForCommodity:result];
6747 [UNIVERSE addMessage:OOExpandKey(@"commodity-ejected", commodity) forCount:3.0 forceDisplay:YES];
6748 [self playCargoJettisioned];
6749 }
6750 return result;
6751}
6752
6753
6754- (void) rotateCargo
6755{
6756 NSInteger i, n_cargo = [cargo count];
6757 if (n_cargo == 0) return;
6758
6759 ShipEntity *pod = (ShipEntity *)[[cargo objectAtIndex:0] retain];
6760 OOCommodityType current_contents = [pod commodityType];
6761 OOCommodityType contents;
6762 NSInteger rotates = 0;
6763
6764 do
6765 {
6766 [cargo removeObjectAtIndex:0]; // take it from the eject position
6767 [cargo addObject:pod]; // move it to the last position
6768 [pod release];
6769 pod = (ShipEntity*)[[cargo objectAtIndex:0] retain];
6770 contents = [pod commodityType];
6771 rotates++;
6772 } while ([contents isEqualToString:current_contents]&&(rotates < n_cargo));
6773 [pod release];
6774
6775 NSString *commodity = [UNIVERSE displayNameForCommodity:contents];
6776 [UNIVERSE addMessage:OOExpandKey(@"ready-to-eject-commodity", commodity) forCount:3.0];
6777
6778 // now scan through the remaining 1..(n_cargo - rotates) places moving similar cargo to the last place
6779 // this means the cargo gets to be sorted as it is rotated through
6780 for (i = 1; i < (n_cargo - rotates); i++)
6781 {
6782 pod = [cargo objectAtIndex:i];
6783 if ([[pod commodityType] isEqualToString:current_contents])
6784 {
6785 [pod retain];
6786 [cargo removeObjectAtIndex:i--];
6787 [cargo addObject:pod];
6788 [pod release];
6789 rotates++;
6790 }
6791 }
6792}
6793
6794
6795- (void) setBounty:(OOCreditsQuantity) amount
6796{
6797 [self setBounty:amount withReason:kOOLegalStatusReasonUnknown];
6798}
6799
6800
6801- (void) setBounty:(OOCreditsQuantity)amount withReason:(OOLegalStatusReason)reason
6802{
6803 NSString *nReason = OOStringFromLegalStatusReason(reason);
6804 [self setBounty:amount withReasonAsString:nReason];
6805}
6806
6807
6808- (void) setBounty:(OOCreditsQuantity)amount withReasonAsString:(NSString *)reason
6809{
6810 JSContext *context = OOJSAcquireContext();
6811
6812 jsval amountVal = JSVAL_VOID;
6813 int amountVal2 = (int)amount-(int)legalStatus;
6814 JS_NewNumberValue(context, amountVal2, &amountVal);
6815
6816 legalStatus = (int)amount; // can't set the new bounty until the size of the change is known
6817
6818 jsval reasonVal = OOJSValueFromNativeObject(context,reason);
6819
6820 ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal);
6821
6822 OOJSRelinquishContext(context);
6823}
6824
6825
6826- (OOCreditsQuantity) bounty // overrides returning 'bounty'
6827{
6828 return legalStatus;
6829}
6830
6831
6832- (int) legalStatus
6833{
6834 return legalStatus;
6835}
6836
6837
6838- (void) markAsOffender:(int)offence_value
6839{
6840 [self markAsOffender:offence_value withReason:kOOLegalStatusReasonUnknown];
6841}
6842
6843
6844- (void) markAsOffender:(int)offence_value withReason:(OOLegalStatusReason)reason
6845{
6846 if (![self isCloaked])
6847 {
6848 JSContext *context = OOJSAcquireContext();
6849
6850 jsval amountVal = JSVAL_VOID;
6851 int amountVal2 = (legalStatus | offence_value) - legalStatus;
6852 JS_NewNumberValue(context, amountVal2, &amountVal);
6853
6854 legalStatus |= offence_value; // can't set the new bounty until the size of the change is known
6855
6856 jsval reasonVal = OOJSValueFromLegalStatusReason(context, reason);
6857
6858 ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal);
6859
6860 OOJSRelinquishContext(context);
6861
6862 }
6863}
6864
6865
6866- (void) collectBountyFor:(ShipEntity *)other
6867{
6868 if ([self status] == STATUS_DEAD) return; // no bounty if we died while trying
6869
6870 if (other == nil || [other isSubEntity]) return;
6871
6872 if (other == [UNIVERSE station])
6873 {
6874 // there is no way the player can destroy the main station
6875 // and so the explosion will be cancelled, so there shouldn't
6876 // be a kill award
6877 return;
6878 }
6879
6880 if ([self isCloaked])
6881 {
6882 // no-one knows about it; no award
6883 return;
6884 }
6885
6886 OOCreditsQuantity score = 10 * [other bounty];
6887 OOScanClass killClass = [other scanClass]; // **tgape** change (+line)
6888 BOOL killAward = [other countsAsKill];
6889
6890 if ([other isPolice]) // oops, we shot a copper!
6891 {
6892 [self markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice];
6893 }
6894
6895 BOOL killIsCargo = ((killClass == CLASS_CARGO) && ([other commodityAmount] > 0) && ![other isHulk]);
6896 if ((killIsCargo) || (killClass == CLASS_BUOY) || (killClass == CLASS_ROCK))
6897 {
6898 // EMMSTRAN: no killaward (but full bounty) for tharglets?
6899 if (![other hasRole:@"tharglet"]) // okay, we'll count tharglets as proper kills
6900 {
6901 score /= 10; // reduce bounty awarded
6902 killAward = NO; // don't award a kill
6903 }
6904 }
6905
6906 credits += score;
6907
6908 if (score > 9)
6909 {
6910 NSString *bonusMessage = OOExpandKey(@"bounty-awarded", score, credits);
6911 [UNIVERSE addDelayedMessage:bonusMessage forCount:6 afterDelay:0.15];
6912 }
6913
6914 if (killAward)
6915 {
6916 ship_kills++;
6917 if ((ship_kills % 256) == 0)
6918 {
6919 // congratulations method needs to be delayed a fraction of a second
6920 [UNIVERSE addDelayedMessage:DESC(@"right-on-commander") forCount:4 afterDelay:0.2];
6921 }
6922 }
6923}
6924
6925
6926- (BOOL) takeInternalDamage
6927{
6928 unsigned n_cargo = [self maxAvailableCargoSpace];
6929 unsigned n_mass = [self mass] / 10000;
6930 unsigned n_considered = (n_cargo + n_mass) * ship_trade_in_factor / 100; // a lower value of n_considered means more vulnerable to damage.
6931 unsigned damage_to = n_considered ? (ranrot_rand() % n_considered) : 0; // n_considered can be 0 for small ships.
6932 BOOL result = NO;
6933 // cargo damage
6934 if (damage_to < [cargo count])
6935 {
6936 ShipEntity* pod = (ShipEntity*)[cargo objectAtIndex:damage_to];
6937 NSString* cargo_desc = [UNIVERSE displayNameForCommodity:[pod commodityType]];
6938 if (!cargo_desc)
6939 return NO;
6940 [UNIVERSE clearPreviousMessage];
6941 [UNIVERSE addMessage:[NSString stringWithFormat:DESC(@"@-destroyed"), cargo_desc] forCount:4.5];
6942 [cargo removeObject:pod];
6943 return YES;
6944 }
6945 else
6946 {
6947 damage_to = n_considered - (damage_to + 1); // reverse the die-roll
6948 }
6949 // equipment damage
6950 NSEnumerator *eqEnum = [self equipmentEnumerator];
6951 OOEquipmentType *eqType = nil;
6952 NSString *system_key;
6953 unsigned damageableCounter = 0;
6954 GLfloat damageableOdds = 0.0;
6955 while ((system_key = [eqEnum nextObject]) != nil)
6956 {
6957 eqType = [OOEquipmentType equipmentTypeWithIdentifier:system_key];
6958 if ([eqType canBeDamaged])
6959 {
6960 damageableCounter++;
6961 damageableOdds += [eqType damageProbability];
6962 }
6963 }
6964
6965 if (damage_to < damageableCounter)
6966 {
6967 GLfloat target = randf() * damageableOdds;
6968 GLfloat accumulator = 0.0;
6969 eqEnum = [self equipmentEnumerator];
6970 while ((system_key = [eqEnum nextObject]) != nil)
6971 {
6972 eqType = [OOEquipmentType equipmentTypeWithIdentifier:system_key];
6973 accumulator += [eqType damageProbability];
6974 if (accumulator > target)
6975 {
6976 [system_key retain];
6977 break;
6978 }
6979 }
6980 if (system_key == nil)
6981 {
6982 [system_key release];
6983 return NO;
6984 }
6985
6986 NSString *system_name = [eqType name];
6987 if (![eqType canBeDamaged] || system_name == nil)
6988 {
6989 [system_key release];
6990 return NO;
6991 }
6992
6993 // set the following so removeEquipment works on the right entity
6994 [self setScriptTarget:self];
6995 [UNIVERSE clearPreviousMessage];
6996 [self removeEquipmentItem:system_key];
6997
6998 NSString *damagedKey = [NSString stringWithFormat:@"%@_DAMAGED", system_key];
6999 [self addEquipmentItem:damagedKey withValidation: NO inContext:@"damage"]; // for possible future repair.
7000 [self doScriptEvent:OOJSID("equipmentDamaged") withArgument:system_key];
7001
7002 if (![self hasEquipmentItem:system_name] && [self hasEquipmentItem:damagedKey])
7003 {
7004 /*
7005 Display "foo damaged" message only if no script has
7006 repaired or removed the equipment item. (If a script does
7007 either of those and wants a message, it can write it
7008 itself.)
7009 */
7010 [UNIVERSE addMessage:[NSString stringWithFormat:DESC(@"@-damaged"), system_name] forCount:4.5];
7011 }
7012
7013 /* There used to be a check for docking computers here, but
7014 * that didn't cover other ways they might fail in flight, so
7015 * it has been moved to the removeEquipment method. */
7016 [system_key release];
7017 return YES;
7018 }
7019 //cosmetic damage
7020 if (((damage_to & 7) == 7)&&(ship_trade_in_factor > 75))
7021 {
7022 ship_trade_in_factor--;
7023 result = YES;
7024 }
7025 return result;
7026}
7027
7028
7029- (void) getDestroyedBy:(Entity *)whom damageType:(OOShipDamageType)type
7030{
7031 if ([self isDocked]) return; // Can't die while docked. (Doing so would cause breakage elsewhere.)
7032
7033 OOLog(@"player.ship.damage", @"Player destroyed by %@ due to %@", whom, OOStringFromShipDamageType(type));
7034
7035 if (![[UNIVERSE gameController] playerFileToLoad])
7036 {
7037 [[UNIVERSE gameController] setPlayerFileToLoad: save_path]; // make sure we load the correct game
7038 }
7039
7040 energy = 0.0f;
7041 afterburner_engaged = NO;
7042 [self disengageAutopilot];
7043
7044 [UNIVERSE setDisplayText:NO];
7045 [UNIVERSE setViewDirection:VIEW_AFT];
7046
7047 // Let scripts know the player died.
7048 [self noteKilledBy:whom damageType:type]; // called before exploding, consistant with npc ships.
7049
7050 [self becomeLargeExplosion:4.0]; // also sets STATUS_DEAD
7051 [self moveForward:100.0];
7052
7053 flightSpeed = 160.0f;
7054 velocity = kZeroVector;
7055 flightRoll = 0.0;
7056 flightPitch = 0.0;
7057 flightYaw = 0.0;
7058 [[UNIVERSE messageGUI] clear]; // No messages for the dead.
7059 [self suppressTargetLost]; // No target lost messages when dead.
7060 [self playGameOver];
7061 [UNIVERSE setBlockJSPlayerShipProps:YES]; // Treat JS player as stale entity.
7062 [self removeAllEquipment]; // No scooping / equipment damage when dead.
7063 [self loseTargetStatus];
7064 [self showGameOver];
7065}
7066
7067
7068- (void) loseTargetStatus
7069{
7070 if (!UNIVERSE)
7071 return;
7072 int ent_count = UNIVERSE->n_entities;
7073 Entity** uni_entities = UNIVERSE->sortedEntities; // grab the public sorted list
7074 Entity* my_entities[ent_count];
7075 int i;
7076 for (i = 0; i < ent_count; i++)
7077 my_entities[i] = [uni_entities[i] retain]; // retained
7078 for (i = 0; i < ent_count ; i++)
7079 {
7080 Entity* thing = my_entities[i];
7081 if (thing->isShip)
7082 {
7083 ShipEntity* ship = (ShipEntity *)thing;
7084 if (self == [ship primaryTarget])
7085 {
7086 [ship noteLostTarget];
7087 }
7088 }
7089 }
7090 for (i = 0; i < ent_count; i++)
7091 {
7092 [my_entities[i] release]; // released
7093 }
7094}
7095
7096
7097- (BOOL) endScenario:(NSString *)key
7098{
7099 if (scenarioKey != nil && [key isEqualToString:scenarioKey])
7100 {
7101 [self setStatus:STATUS_RESTART_GAME];
7102 return YES;
7103 }
7104 return NO;
7105}
7106
7107
7108- (void) enterDock:(StationEntity *)station
7109{
7110 NSParameterAssert(station != nil);
7111 if ([self status] == STATUS_DEAD) return;
7112
7113 [self setStatus:STATUS_DOCKING];
7114 [self setDockedStation:station];
7115 [self doScriptEvent:OOJSID("shipWillDockWithStation") withArgument:station];
7116
7117 if (![hud nonlinearScanner])
7118 {
7119 [hud setScannerZoom: 1.0];
7120 }
7121 ident_engaged = NO;
7122 afterburner_engaged = NO;
7123 autopilot_engaged = NO;
7124 [self resetAutopilotAI];
7125
7126 cloaking_device_active = NO;
7127 hyperspeed_engaged = NO;
7128 hyperspeed_locked = NO;
7129 [self safeAllMissiles];
7130 DESTROY(_primaryTarget); // must happen before showing break_pattern to suppress active reticule.
7131 [self clearTargetMemory];
7132
7133 scanner_zoom_rate = 0.0f;
7134 [UNIVERSE setDisplayText:NO];
7135 [[UNIVERSE gameController] setMouseInteractionModeForFlight];
7136 if ([self status] == STATUS_LAUNCHING) return; // a JS script has aborted the docking.
7137
7138 [self setOrientation: kIdentityQuaternion]; // reset orientation to dock
7139 [UNIVERSE setUpBreakPattern:[self breakPatternPosition] orientation:orientation forDocking:YES];
7140 [self playDockWithStation];
7141 [station noteDockedShip:self];
7142
7143 [[UNIVERSE gameView] clearKeys]; // try to stop key bounces
7144}
7145
7146
7147- (void) docked
7148{
7149 StationEntity *dockedStation = [self dockedStation];
7150 if (dockedStation == nil)
7151 {
7152 [self setStatus:STATUS_IN_FLIGHT];
7153 return;
7154 }
7155
7156 [self setStatus:STATUS_DOCKED];
7157 [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
7158
7159 [self loseTargetStatus];
7160
7161 [self setPosition:[dockedStation position]];
7162 [self setOrientation:kIdentityQuaternion]; // reset orientation to dock
7163
7164 flightRoll = 0.0f;
7165 flightPitch = 0.0f;
7166 flightYaw = 0.0f;
7167 flightSpeed = 0.0f;
7168
7169 hyperspeed_engaged = NO;
7170 hyperspeed_locked = NO;
7171
7172 forward_shield = [self maxForwardShieldLevel];
7173 aft_shield = [self maxAftShieldLevel];
7174 energy = maxEnergy;
7175 weapon_temp = 0.0f;
7176 ship_temperature = 60.0f;
7177
7178 [self setAlertFlag:ALERT_FLAG_DOCKED to:YES];
7179
7180 if ([dockedStation localMarket] == nil)
7181 {
7182 [dockedStation initialiseLocalMarket];
7183 }
7184
7185 NSString *escapepodReport = [self processEscapePods];
7186 [self addMessageToReport:escapepodReport];
7187
7188 [self unloadCargoPods]; // fill up the on-ship commodities before...
7189
7190 // check import status of station
7191 // escape pods must be cleared before this happens
7192 if ([dockedStation marketMonitored])
7193 {
7194 OOCreditsQuantity oldbounty = [self bounty];
7195 [self markAsOffender:[dockedStation legalStatusOfManifest:shipCommodityData export:NO] withReason:kOOLegalStatusReasonIllegalImports];
7196 if ([self bounty] > oldbounty)
7197 {
7198 [self addRoleToPlayer:@"trader-smuggler"];
7199 }
7200 }
7201
7202 // check contracts
7203 NSString *passengerAndCargoReport = [self checkPassengerContracts]; // Is also processing cargo and parcel contracts.
7204 [self addMessageToReport:passengerAndCargoReport];
7205
7206 [UNIVERSE setDisplayText:YES];
7207
7210
7211 // Did we fail to observe traffic control regulations? However, due to the state of emergency,
7212 // apply no unauthorized docking penalties if a nova is ongoing.
7213 if ([dockedStation requiresDockingClearance] &&
7214 ![self clearedToDock] && ![[UNIVERSE sun] willGoNova])
7215 {
7216 [self penaltyForUnauthorizedDocking];
7217 }
7218
7219 // apply any pending fines. (No need to check gui_screen as fines is no longer an on-screen message).
7220 if (dockedStation == [UNIVERSE station])
7221 {
7222 // TODO: A proper system to allow some OXP stations to have a
7223 // galcop presence for fines. - CIM 18/11/2012
7224 if (being_fined && ![[UNIVERSE sun] willGoNova] && ![dockedStation suppressArrivalReports]) [self getFined];
7225 }
7226
7227 // it's time to check the script - can trigger legacy missions
7228 if (gui_screen != GUI_SCREEN_MISSION) [self checkScript]; // a scripted pilot could have created a mission screen.
7229
7231 [self doScriptEvent:OOJSID("shipDockedWithStation") withArgument:dockedStation];
7233 if ([self status] == STATUS_LAUNCHING) return;
7234
7235 // if we've not switched to the mission screen yet then proceed normally..
7236 if (gui_screen != GUI_SCREEN_MISSION)
7237 {
7238 [self setGuiToStatusScreen];
7239 }
7242
7243 // When a mission screen is started, any on-screen message is removed immediately.
7244 [self doWorldEventUntilMissionScreen:OOJSID("missionScreenOpportunity")]; // also displays docking reports first.
7245}
7246
7247
7248- (void) leaveDock:(StationEntity *)station
7249{
7250 if (station == nil) return;
7251 NSParameterAssert(station == [self dockedStation]);
7252
7253 // ensure we've not left keyboard entry on
7254 [[UNIVERSE gameView] allowStringInput: NO];
7255
7256 if (gui_screen == GUI_SCREEN_MISSION)
7257 {
7258 [[UNIVERSE gui] clearBackground];
7259 if (_missionWithCallback)
7260 {
7261 [self doMissionCallback];
7262 }
7263 // notify older scripts, but do not trigger missionScreenOpportunity.
7264 [self doWorldEventUntilMissionScreen:OOJSID("missionScreenEnded")];
7265 }
7266
7267 if ([station marketMonitored])
7268 {
7269 // 'leaving with those guns were you sir?'
7270 OOCreditsQuantity oldbounty = [self bounty];
7271 [self markAsOffender:[station legalStatusOfManifest:shipCommodityData export:YES] withReason:kOOLegalStatusReasonIllegalExports];
7272 if ([self bounty] > oldbounty)
7273 {
7274 [self addRoleToPlayer:@"trader-smuggler"];
7275 }
7276 }
7277 OOGUIScreenID oldScreen = gui_screen;
7278 gui_screen = GUI_SCREEN_MAIN;
7279 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
7280
7281 if (![hud nonlinearScanner])
7282 {
7283 [hud setScannerZoom: 1.0];
7284 }
7285 [self loadCargoPods];
7286 // do not do anything that calls JS handlers between now and calling
7287 // [station launchShip] below, or the cargo returned by JS may be off
7288 // CIM - 3.2.2012
7289
7290 // clear the way
7291 [station autoDockShipsOnApproach];
7292 [station clearDockingCorridor];
7293
7294// [self setAlertFlag:ALERT_FLAG_DOCKED to:NO];
7295 [self clearAlertFlags];
7296 [self setDockingClearanceStatus:DOCKING_CLEARANCE_STATUS_NONE];
7297
7298 scanner_zoom_rate = 0.0f;
7299 currentWeaponFacing = WEAPON_FACING_FORWARD;
7300 [self currentWeaponStats];
7301
7302 forward_weapon_temp = 0.0f;
7303 aft_weapon_temp = 0.0f;
7304 port_weapon_temp = 0.0f;
7305 starboard_weapon_temp = 0.0f;
7306
7307 forward_shield = [self maxForwardShieldLevel];
7308 aft_shield = [self maxAftShieldLevel];
7309
7310 [self clearTargetMemory];
7311 [self setShowDemoShips:NO];
7312 [UNIVERSE setDisplayText:NO];
7313 [[UNIVERSE gameController] setMouseInteractionModeForFlight];
7314
7315 [[UNIVERSE gameView] clearKeys]; // try to stop keybounces
7316
7317 if ([self isMouseControlOn])
7318 {
7319 [[UNIVERSE gameView] resetMouse];
7320 }
7321
7323
7324 [UNIVERSE forceWitchspaceEntries];
7325 ship_clock_adjust += 600.0; // 10 minutes to leave dock
7326 velocity = kZeroVector; // just in case
7327
7328 [station launchShip:self];
7329
7330 launchRoll = -flightRoll; // save the station's spin. (inverted for player)
7331 flightRoll = 0; // don't spin when showing the break pattern.
7332 [UNIVERSE setUpBreakPattern:[self breakPatternPosition] orientation:orientation forDocking:YES];
7333
7334 [self setDockedStation:nil];
7335
7336 suppressAegisMessages = YES;
7337 [self checkForAegis];
7338 suppressAegisMessages = NO;
7339 ident_engaged = NO;
7340
7341 [UNIVERSE removeDemoShips];
7342 // MKW - ensure GUI Screen ship is removed
7343 [demoShip release];
7344 demoShip = nil;
7345
7346 [self playLaunchFromStation];
7347}
7348
7349
7350- (void) witchStart
7351{
7352 // chances of entering witchspace with autopilot on are very low, but as Berlios bug #18307 has shown us, entirely possible
7353 // so in such cases we need to ensure that at least the docking music stops playing
7354 if (autopilot_engaged) [self disengageAutopilot];
7355
7356 if (![hud nonlinearScanner])
7357 {
7358 [hud setScannerZoom: 1.0];
7359 }
7360 [self safeAllMissiles];
7361
7362 OOViewID previousViewDirection = [UNIVERSE viewDirection];
7363 [UNIVERSE setViewDirection:VIEW_FORWARD];
7364 [self noteSwitchToView:VIEW_FORWARD fromView:previousViewDirection]; // notifies scripts of the switch
7365
7366 currentWeaponFacing = WEAPON_FACING_FORWARD;
7367 [self currentWeaponStats];
7368
7369 [self transitionToAegisNone];
7370 suppressAegisMessages=YES;
7371 hyperspeed_engaged = NO;
7372
7373 if ([self primaryTarget] != nil)
7374 {
7375 [self noteLostTarget]; // losing target? Fire lost target event!
7376 DESTROY(_primaryTarget);
7377 }
7378
7379 scanner_zoom_rate = 0.0f;
7380 [UNIVERSE setDisplayText:NO];
7381
7382 if ( ![self wormhole] && !galactic_witchjump) // galactic hyperspace does not generate a wormhole
7383 {
7384 OOLog(kOOLogInconsistentState, @"%@", @"Internal Error : Player entering witchspace with no wormhole.");
7385 }
7386 [UNIVERSE allShipsDoScriptEvent:OOJSID("playerWillEnterWitchspace") andReactToAIMessage:@"PLAYER WITCHSPACE"];
7387
7388 // set the new market seed now!
7389 // reseeding the RNG should be completely unnecessary here
7390// ranrot_srand((uint32_t)[[NSDate date] timeIntervalSince1970]); // seed randomiser by time
7391 market_rnd = ranrot_rand() & 255; // random factor for market values is reset
7392}
7393
7394
7395- (void) witchEnd
7396{
7397 [UNIVERSE setSystemTo:system_id];
7398 galaxy_coordinates = [[UNIVERSE systemManager] getCoordinatesForSystem:system_id inGalaxy:galaxy_number];
7399
7400 [UNIVERSE setUpUniverseFromWitchspace];
7401 [[UNIVERSE planet] update: 2.34375 * market_rnd]; // from 0..10 minutes
7402 [[UNIVERSE station] update: 2.34375 * market_rnd]; // from 0..10 minutes
7403
7404 chart_centre_coordinates = galaxy_coordinates;
7405 target_chart_centre = chart_centre_coordinates;
7406}
7407
7408
7409- (BOOL) witchJumpChecklist:(BOOL)isGalacticJump
7410{
7411 // Perform this check only when doing the actual jump
7412 if ([self status] == STATUS_WITCHSPACE_COUNTDOWN)
7413 {
7414 // check nearby masses
7415 //UPDATE_STAGE(@"checking for mass blockage");
7416 ShipEntity* blocker = [UNIVERSE entityForUniversalID:[self checkShipsInVicinityForWitchJumpExit]];
7417 if (blocker)
7418 {
7419 [UNIVERSE clearPreviousMessage];
7420 NSString *blockerName = [blocker name];
7421 [UNIVERSE addMessage:OOExpandKey(@"witch-blocked", blockerName) forCount:4.5];
7422 [self playWitchjumpBlocked];
7423 [self setStatus:STATUS_IN_FLIGHT];
7424 ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("blocked"));
7425 return NO;
7426 }
7427 }
7428
7429 // For galactic hyperspace jumps we skip the remaining checks
7430 if (isGalacticJump)
7431 {
7432 return YES;
7433 }
7434
7435 // Check we're not jumping into the current system
7436 if (![UNIVERSE inInterstellarSpace] && system_id == target_system_id)
7437 {
7438 //dont allow player to hyperspace to current location.
7439 //Note interstellar space will have a system_seed place we came from
7440 [UNIVERSE clearPreviousMessage];
7441 [UNIVERSE addMessage:OOExpandKey(@"witch-no-target") forCount: 4.5];
7442 if ([self status] == STATUS_WITCHSPACE_COUNTDOWN)
7443 {
7444 [self playWitchjumpInsufficientFuel];
7445 [self setStatus:STATUS_IN_FLIGHT];
7446 ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("no target"));
7447 }
7448 else [self playHyperspaceNoTarget];
7449
7450 return NO;
7451 }
7452
7453 // check max distance permitted
7454 if ([self hyperspaceJumpDistance] > [self maxHyperspaceDistance])
7455 {
7456 [UNIVERSE clearPreviousMessage];
7457 [UNIVERSE addMessage:DESC(@"witch-too-far") forCount: 4.5];
7458 if ([self status] == STATUS_WITCHSPACE_COUNTDOWN)
7459 {
7460 [self playWitchjumpDistanceTooGreat];
7461 [self setStatus:STATUS_IN_FLIGHT];
7462 ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("too far"));
7463 }
7464 else [self playHyperspaceDistanceTooGreat];
7465
7466 return NO;
7467 }
7468
7469 // check fuel level
7470 if (![self hasSufficientFuelForJump])
7471 {
7472 [UNIVERSE clearPreviousMessage];
7473 [UNIVERSE addMessage:DESC(@"witch-no-fuel") forCount: 4.5];
7474 if ([self status] == STATUS_WITCHSPACE_COUNTDOWN)
7475 {
7476 [self playWitchjumpInsufficientFuel];
7477 [self setStatus:STATUS_IN_FLIGHT];
7478 ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("insufficient fuel"));
7479 }
7480 else [self playHyperspaceNoFuel];
7481
7482 return NO;
7483 }
7484
7485 // All checks passed
7486 return YES;
7487}
7488
7489- (void) setJumpType:(BOOL)isGalacticJump
7490{
7491 if (isGalacticJump)
7492 {
7493 galactic_witchjump = YES;
7494 }
7495 else
7496 {
7497 galactic_witchjump = NO;
7498 }
7499}
7500
7501
7502
7503- (double) hyperspaceJumpDistance
7504{
7505 NSPoint targetCoordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:[self nextHopTargetSystemID] inGalaxy:galaxy_number]);
7506 return distanceBetweenPlanetPositions(targetCoordinates.x,targetCoordinates.y,galaxy_coordinates.x,galaxy_coordinates.y);
7507}
7508
7509
7511{
7512 return 10.0 * MAX(0.1, [self hyperspaceJumpDistance]);
7513}
7514
7515
7516- (BOOL) hasSufficientFuelForJump
7517{
7518 return fuel >= [self fuelRequiredForJump];
7519}
7520
7521
7522- (void) noteCompassLostTarget
7523{
7524 if ([[self hud] isCompassActive])
7525 {
7526 // "the compass, it says we're lost!" :)
7527 JSContext *context = OOJSAcquireContext();
7528 jsval jsmode = OOJSValueFromCompassMode(context, [self compassMode]);
7529 ShipScriptEvent(context, self, "compassTargetChanged", JSVAL_VOID, jsmode);
7530 OOJSRelinquishContext(context);
7531
7532 [[self hud] setCompassActive:NO]; // ensure a target change when returning to normal space.
7533 }
7534}
7535
7536
7537- (void) enterGalacticWitchspace
7538{
7539 if (![self witchJumpChecklist:true])
7540 return;
7541
7542
7543 OOGalaxyID destGalaxy = galaxy_number + 1;
7544 if (EXPECT_NOT(destGalaxy >= OO_GALAXIES_AVAILABLE))
7545 {
7546 destGalaxy = 0;
7547 }
7548
7549
7550 [self setStatus:STATUS_ENTERING_WITCHSPACE];
7551 JSContext *context = OOJSAcquireContext();
7552 [self setJumpCause:@"galactic jump"];
7553 [self setPreviousSystemID:[self currentSystemID]];
7554 ShipScriptEvent(context, self, "shipWillEnterWitchspace", STRING_TO_JSVAL(JS_InternString(context, [[self jumpCause] UTF8String])), INT_TO_JSVAL(destGalaxy));
7555 OOJSRelinquishContext(context);
7556
7557 [self noteCompassLostTarget];
7558
7559 [self witchStart];
7560
7561 [UNIVERSE removeAllEntitiesExceptPlayer];
7562
7563 // remove any contracts and parcels for the old galaxy
7564 if (contracts)
7565 [contracts removeAllObjects];
7566
7567 if (parcels)
7568 [parcels removeAllObjects];
7569
7570 // remove any mission destinations for the old galaxy
7571 if (missionDestinations)
7572 [missionDestinations removeAllObjects];
7573
7574 // expire passenger contracts for the old galaxy
7575 if (passengers)
7576 {
7577 unsigned i;
7578 for (i = 0; i < [passengers count]; i++)
7579 {
7580 // set the expected arrival time to now, so they storm off the ship at the first port
7581 NSMutableDictionary* passenger_info = [NSMutableDictionary dictionaryWithDictionary:[passengers oo_dictionaryAtIndex:i]];
7582 [passenger_info setObject:[NSNumber numberWithDouble:ship_clock] forKey:CONTRACT_KEY_ARRIVAL_TIME];
7583 [passengers replaceObjectAtIndex:i withObject:passenger_info];
7584 }
7585 }
7586
7587 // clear a lot of memory of player actions
7588 if (ship_kills >= 6400)
7589 {
7590 [self clearRolesFromPlayer:0.25];
7591 }
7592 else if (ship_kills >= 2560)
7593 {
7594 [self clearRolesFromPlayer:0.5];
7595 }
7596 else
7597 {
7598 [self clearRolesFromPlayer:0.9];
7599 }
7600 [roleWeightFlags removeAllObjects];
7601 [roleSystemList removeAllObjects];
7602
7603 // may be more than one item providing this
7604 [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_GAL_DRIVE"]];
7605
7606 galaxy_number = destGalaxy;
7607
7608 [UNIVERSE setGalaxyTo:galaxy_number];
7609
7610 // Choose the galactic hyperspace behaviour. Refers to where we may actually end up after an intergalactic jump.
7611 // The default behaviour is that the player cannot arrive on unreachable or isolated systems. The options
7612 // in planetinfo.plist, galactic_hyperspace_behaviour key can be used to allow arrival even at unreachable systems,
7613 // or at fixed coordinates on the galactic chart. The key galactic_hyperspace_fixed_coords in planetinfo.plist is
7614 // used in the fixed coordinates case and specifies the exact coordinates for the intergalactic jump.
7615 switch (galacticHyperspaceBehaviour)
7616 {
7617 case GALACTIC_HYPERSPACE_BEHAVIOUR_FIXED_COORDINATES:
7618 system_id = [UNIVERSE findSystemNumberAtCoords:galacticHyperspaceFixedCoords withGalaxy:galaxy_number includingHidden:YES];
7619 break;
7620 case GALACTIC_HYPERSPACE_BEHAVIOUR_ALL_SYSTEMS_REACHABLE:
7621 system_id = [UNIVERSE findSystemNumberAtCoords:galaxy_coordinates withGalaxy:galaxy_number includingHidden:YES];
7622 break;
7623 case GALACTIC_HYPERSPACE_BEHAVIOUR_STANDARD:
7624 default:
7625 // instead find a system connected to system 0 near the current coordinates...
7626 system_id = [UNIVERSE findConnectedSystemAtCoords:galaxy_coordinates withGalaxy:galaxy_number];
7627 break;
7628 }
7629 target_system_id = system_id;
7630 info_system_id = system_id;
7631
7632 [self setBounty:0 withReason:kOOLegalStatusReasonNewGalaxy]; // let's make a fresh start!
7633 cursor_coordinates = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:system_id inGalaxy:galaxy_number]);
7634
7635 [self witchEnd]; // sets coordinates, calls exiting witchspace JS events
7636}
7637
7638
7639// now with added misjump goodness!
7640// If the wormhole generator misjumped, the player's ship misjumps too. Kaks 20110211
7641- (void) enterWormhole:(WormholeEntity *) w_hole
7642{
7643 if ([self status] == STATUS_ENTERING_WITCHSPACE
7644 || [self status] == STATUS_EXITING_WITCHSPACE)
7645 {
7646 return; // has already entered a different wormhole
7647 }
7648 BOOL misjump = [self scriptedMisjump] || [w_hole withMisjump] || flightPitch == max_flight_pitch || randf() > 0.995;
7649 wormhole = [w_hole retain];
7650 [self addScannedWormhole:wormhole];
7651 [self setStatus:STATUS_ENTERING_WITCHSPACE];
7652 JSContext *context = OOJSAcquireContext();
7653 [self setJumpCause:@"wormhole"];
7654 [self setPreviousSystemID:[self currentSystemID]];
7655 ShipScriptEvent(context, self, "shipWillEnterWitchspace", STRING_TO_JSVAL(JS_InternString(context, [[self jumpCause] UTF8String])), INT_TO_JSVAL([w_hole destination]));
7656 OOJSRelinquishContext(context);
7657 if ([self scriptedMisjump])
7658 {
7659 misjump = YES; // a script could just have changed this to true;
7660 }
7661#ifdef OO_DUMP_PLANETINFO
7662 misjump = NO;
7663#endif
7664 if (misjump && [self scriptedMisjumpRange] != 0.5)
7665 {
7666 [w_hole setMisjumpWithRange:[self scriptedMisjumpRange]]; // overrides wormholes, if player also had non-default scriptedMisjumpRange
7667 }
7668 [self witchJumpTo:[w_hole destination] misjump:misjump];
7669}
7670
7671
7672- (void) enterWitchspace
7673{
7674 if (![self witchJumpChecklist:false]) return;
7675
7676 OOSystemID jumpTarget = [self nextHopTargetSystemID];
7677
7678 // perform any check here for forced witchspace encounters
7679 unsigned malfunc_chance = 253;
7680 if (ship_trade_in_factor < 80)
7681 {
7682 malfunc_chance -= (1 + ranrot_rand() % (81-ship_trade_in_factor)) / 2; // increase chance of misjump in worn-out craft
7683 }
7684 else if (ship_trade_in_factor >= 100)
7685 {
7686 malfunc_chance = 256; // force no misjumps on first jump
7687 }
7688
7689#ifdef OO_DUMP_PLANETINFO
7690 BOOL misjump = NO; // debugging
7691#else
7692 BOOL malfunc = ((ranrot_rand() & 0xff) > malfunc_chance);
7693 // 75% of the time a malfunction means a misjump
7694 BOOL misjump = [self scriptedMisjump] || (flightPitch == max_flight_pitch) || (malfunc && (randf() > 0.75));
7695
7696 if (malfunc && !misjump)
7697 {
7698 // some malfunctions will start fuel leaks, some will result in no witchjump at all.
7699 if ([self takeInternalDamage]) // Depending on ship type and loaded cargo, this will be true for 20 - 50% of the time.
7700 {
7701 [self playWitchjumpFailure];
7702 [self setStatus:STATUS_IN_FLIGHT];
7703 ShipScriptEventNoCx(self, "playerJumpFailed", OOJSSTR("malfunction"));
7704 return;
7705 }
7706 else
7707 {
7708 [self setFuelLeak:[NSString stringWithFormat:@"%f", (randf() + randf()) * 5.0]];
7709 }
7710 }
7711#endif
7712
7713 // From this point forward we are -definitely- witchjumping
7714
7715 // burn the full fuel amount to create the wormhole
7716 fuel -= [self fuelRequiredForJump];
7717
7718 // Create the players' wormhole
7719 wormhole = [[WormholeEntity alloc] initWormholeTo:jumpTarget fromShip:self];
7720 [UNIVERSE addEntity:wormhole]; // Add new wormhole to Universe to let other ships target it. Required for ships following the player.
7721 [self addScannedWormhole:wormhole];
7722
7723 [self setStatus:STATUS_ENTERING_WITCHSPACE];
7724 JSContext *context = OOJSAcquireContext();
7725 [self setJumpCause:@"standard jump"];
7726 [self setPreviousSystemID:[self currentSystemID]];
7727 ShipScriptEvent(context, self, "shipWillEnterWitchspace", STRING_TO_JSVAL(JS_InternString(context, [[self jumpCause] UTF8String])), INT_TO_JSVAL(jumpTarget));
7728 OOJSRelinquishContext(context);
7729
7730 [self updateSystemMemory];
7731 NSUInteger legality = [self legalStatusOfCargoList];
7732 OOCargoQuantity maxSpace = [self maxAvailableCargoSpace];
7733 OOCargoQuantity availSpace = [self availableCargoSpace];
7734 if ([roleWeightFlags objectForKey:@"bought-legal"])
7735 {
7736 if (maxSpace != availSpace)
7737 {
7738 [self addRoleToPlayer:@"trader"];
7739 if (maxSpace - availSpace > 20 || availSpace == 0)
7740 {
7741 if (legality == 0)
7742 {
7743 [self addRoleToPlayer:@"trader"];
7744 }
7745 }
7746 }
7747 }
7748 if ([roleWeightFlags objectForKey:@"bought-illegal"])
7749 {
7750 if (maxSpace != availSpace && legality > 0)
7751 {
7752 [self addRoleToPlayer:@"trader-smuggler"];
7753 if (maxSpace - availSpace > 20 || availSpace == 0)
7754 {
7755 if (legality >= 20 || legality >= maxSpace)
7756 {
7757 [self addRoleToPlayer:@"trader-smuggler"];
7758 }
7759 }
7760 }
7761 }
7762 [roleWeightFlags removeAllObjects];
7763
7764 [self noteCompassLostTarget];
7765 if ([self scriptedMisjump])
7766 {
7767 misjump = YES; // a script could just have changed this to true;
7768 }
7769 if (misjump)
7770 {
7771 [wormhole setMisjumpWithRange:[self scriptedMisjumpRange]];
7772 }
7773 [self witchJumpTo:jumpTarget misjump:misjump];
7774}
7775
7776
7777- (void) witchJumpTo:(OOSystemID)sTo misjump:(BOOL)misjump
7778{
7779 [self witchStart];
7780 if (info_system_id == system_id)
7781 {
7782 [self setInfoSystemID: sTo moveChart: YES];
7783 }
7784 //wear and tear on all jumps (inc misjumps, failures, and wormholes)
7785 if (2 * market_rnd < ship_trade_in_factor)
7786 {
7787 // every eight jumps or so drop the price down towards 75%
7788 [self adjustTradeInFactorBy:-(1 + (market_rnd & 3))];
7789 }
7790
7791 // set clock after "playerWillEnterWitchspace" and before removeAllEntitiesExceptPlayer, to allow escorts time to follow their mother.
7792 NSPoint destCoords = PointFromString([[UNIVERSE systemManager] getProperty:@"coordinates" forSystem:sTo inGalaxy:galaxy_number]);
7793 double distance = distanceBetweenPlanetPositions(destCoords.x,destCoords.y,galaxy_coordinates.x,galaxy_coordinates.y);
7794
7795 // if we just escaped a system gone nova, make sure all nova parameters are reset
7796 OOSunEntity *theSun = [UNIVERSE sun];
7797 if (theSun && [theSun goneNova])
7798 {
7799 [theSun resetNova];
7800 }
7801
7802 [UNIVERSE removeAllEntitiesExceptPlayer];
7803 if (!misjump)
7804 {
7805 ship_clock_adjust += distance * distance * 3600.0;
7806 [self setSystemID:sTo];
7807 [self setBounty:(legalStatus/2) withReason:kOOLegalStatusReasonNewSystem]; // 'another day, another system'
7808 [self witchEnd];
7809 if (market_rnd < 8) [self erodeReputation]; // every 32 systems or so, drop back towards 'unknown'
7810 }
7811 else
7812 {
7813 // Misjump: move halfway there!
7814 // misjumps do not change legal status.
7815 if (randf() < 0.1) [self erodeReputation]; // once every 10 misjumps - should be much rarer than successful jumps!
7816
7817 [wormhole setMisjump];
7818 // just in case, but this has usually been set already
7819
7820 // and now the wormhole has travel time and coordinates calculated
7821 // so rather than duplicate the calculation we'll just ask it...
7822 NSPoint dest = [wormhole destinationCoordinates];
7823 galaxy_coordinates.x = dest.x;
7824 galaxy_coordinates.y = dest.y;
7825
7826 ship_clock_adjust += [wormhole travelTime];
7827
7828 [self playWitchjumpMisjump];
7829 [UNIVERSE setUpUniverseFromMisjump];
7830 }
7831}
7832
7833
7834- (void) leaveWitchspace
7835{
7836 double d1 = SCANNER_MAX_RANGE * ((Ranrot() & 255)/256.0 - 0.5);
7837 HPVector pos = [UNIVERSE getWitchspaceExitPosition]; // no need to reset the PRNG
7838 Quaternion q1;
7839 HPVector whpos, exitpos;
7840
7841 double min_d1 = [UNIVERSE safeWitchspaceExitDistance];
7843 if (abs((int)d1) < min_d1)
7844 {
7845 d1 += ((d1 > 0.0)? min_d1: -min_d1); // not too close to the buoy.
7846 }
7847 HPVector v1 = HPvector_forward_from_quaternion(q1);
7848 exitpos = HPvector_add(pos, HPvector_multiply_scalar(v1, d1)); // randomise exit position
7849 position = exitpos;
7850 [self setOrientation:[UNIVERSE getWitchspaceExitRotation]];
7851
7852 // While setting the wormhole position to the player position looks very nice for ships following the player,
7853 // the more common case of the player following other ships, the player tends to
7854 // ram the back of the ships, or even jump on top of is when the ship jumped without initial speed, which is messy.
7855 // To avoid this problem, a small wormhole displacement is added.
7856 if (wormhole) // will be nil for galactic jump
7857 {
7858 if ([[wormhole shipsInTransit] count] > 0)
7859 {
7860 // player is not allone in his wormhole, synchronise player and wormhole position.
7861 double wh_arrival_time = ([PLAYER clockTimeAdjusted] - [wormhole arrivalTime]);
7862 if (wh_arrival_time > 0)
7863 {
7864 // Player is following other ship
7865 whpos = HPvector_add(exitpos, vectorToHPVector(vector_multiply_scalar([self forwardVector], 1000.0f)));
7866 [wormhole setContainsPlayer:YES];
7867 }
7868 else
7869 {
7870 // Player is the leadship
7871 whpos = HPvector_add(exitpos, vectorToHPVector(vector_multiply_scalar([self forwardVector], -500.0f)));
7872 // so it won't contain the player by the time they exit
7873 [wormhole setExitSpeed:maxFlightSpeed*WORMHOLE_LEADER_SPEED_FACTOR];
7874 }
7875
7876 HPVector distance = HPvector_subtract(whpos, pos);
7877 if (HPmagnitude2(distance) < min_d1*min_d1 ) // within safety distance from the buoy?
7878 {
7879 // the wormhole is to close to the buoy. Move both player and wormhole away from it in the x-y plane.
7880 distance.z = 0;
7881 distance = HPvector_multiply_scalar(HPvector_normal(distance), min_d1);
7882 whpos = HPvector_add(whpos, distance);
7883 position = HPvector_add(position, distance);
7884 }
7885 [wormhole setExitPosition: whpos];
7886 }
7887 else
7888 {
7889 // no-one else in the wormhole
7890 [wormhole setExitSpeed:maxFlightSpeed*WORMHOLE_LEADER_SPEED_FACTOR];
7891 }
7892 }
7893 /* there's going to be a slight pause at this stage anyway;
7894 * there's also going to be a lot of stale ship scripts. Force a
7895 * garbage collection while we have chance. - CIM */
7897 flightSpeed = wormhole ? [wormhole exitSpeed] : fmin(maxFlightSpeed,50.0f);
7898 [wormhole release]; // OK even if nil
7899 wormhole = nil;
7900
7901 flightRoll = 0.0f;
7902 flightPitch = 0.0f;
7903 flightYaw = 0.0f;
7904
7905 velocity = kZeroVector;
7906 [self setStatus:STATUS_EXITING_WITCHSPACE];
7907 gui_screen = GUI_SCREEN_MAIN;
7908 being_fined = NO; // until you're scanned by a copper!
7909 [self clearTargetMemory];
7910 [self setShowDemoShips:NO];
7911 [[UNIVERSE gameController] setMouseInteractionModeForFlight];
7912 [UNIVERSE setDisplayText:NO];
7913 [UNIVERSE setWitchspaceBreakPattern:YES];
7914 [self playExitWitchspace];
7915 if ([self currentSystemID] >= 0)
7916 {
7917 if (![roleSystemList containsObject:[NSNumber numberWithInt:[self currentSystemID]]])
7918 {
7919 // going somewhere new?
7920 [self clearRoleFromPlayer:NO];
7921 }
7922 }
7923
7924 if (galactic_witchjump)
7925 {
7926 [self doScriptEvent:OOJSID("playerEnteredNewGalaxy") withArgument:[NSNumber numberWithUnsignedInt:galaxy_number]];
7927 }
7928
7929 [self doScriptEvent:OOJSID("shipWillExitWitchspace") withArgument:[self jumpCause]];
7930 [UNIVERSE setUpBreakPattern:[self breakPatternPosition] orientation:orientation forDocking:NO];
7931}
7932
7933
7935
7936- (void) setGuiToStatusScreen
7937{
7938 NSString *systemName = nil;
7939 NSString *targetSystemName = nil;
7940 NSString *text = nil;
7941
7942 GuiDisplayGen *gui = [UNIVERSE gui];
7943 OOGUIScreenID oldScreen = gui_screen;
7944 if (oldScreen != GUI_SCREEN_STATUS)
7945 {
7946 [self noteGUIWillChangeTo:GUI_SCREEN_STATUS];
7947 }
7948
7949 gui_screen = GUI_SCREEN_STATUS;
7950 BOOL guiChanged = (oldScreen != gui_screen);
7951
7952 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO];
7953
7954 // Both system_seed & target_system_seed are != nil at all times when this function is called.
7955
7956 systemName = [UNIVERSE inInterstellarSpace] ? DESC(@"interstellar-space") : [UNIVERSE getSystemName:system_id];
7957 if ([self isDocked] && [self dockedStation] != [UNIVERSE station])
7958 {
7959 systemName = [NSString stringWithFormat:@"%@ : %@", systemName, [[self dockedStation] displayName]];
7960 }
7961
7962 targetSystemName = [UNIVERSE getSystemName:target_system_id];
7963 NSDictionary *systemInfo = [[UNIVERSE systemManager] getPropertiesForSystem:target_system_id inGalaxy:galaxy_number];
7964 NSInteger concealment = [systemInfo oo_intForKey:@"concealment" defaultValue:OO_SYSTEMCONCEALMENT_NONE];
7965 if (concealment >= OO_SYSTEMCONCEALMENT_NONAME) targetSystemName = DESC(@"status-unknown-system");
7966
7967 OOSystemID nextHop = [self nextHopTargetSystemID];
7968 if (nextHop != target_system_id) {
7969 NSString *nextHopSystemName = [UNIVERSE getSystemName:nextHop];
7970 systemInfo = [[UNIVERSE systemManager] getPropertiesForSystem:nextHop inGalaxy:galaxy_number];
7971 concealment = [systemInfo oo_intForKey:@"concealment" defaultValue:OO_SYSTEMCONCEALMENT_NONE];
7972 if (concealment >= OO_SYSTEMCONCEALMENT_NONAME) nextHopSystemName = DESC(@"status-unknown-system");
7973 targetSystemName = OOExpandKey(@"status-hyperspace-system-multi", targetSystemName, nextHopSystemName);
7974 }
7975
7976 // GUI stuff
7977 {
7978 NSString *shipName = [self displayName];
7979 NSString *legal_desc = nil, *rating_desc = nil,
7980 *alert_desc = nil, *fuel_desc = nil,
7981 *credits_desc = nil;
7982
7983 OOGUIRow i;
7984 OOGUITabSettings tab_stops;
7985 tab_stops[0] = 20;
7986 tab_stops[1] = 160;
7987 tab_stops[2] = 290;
7988 [gui overrideTabs:tab_stops from:kGuiStatusTabs length:3];
7989 [gui setTabStops:tab_stops];
7990
7991 NSString *lightYearsDesc = DESC(@"status-light-years-desc");
7992
7993 legal_desc = OODisplayStringFromLegalStatus(legalStatus);
7994 rating_desc = KillCountToRatingAndKillString(ship_kills);
7995 alert_desc = OODisplayStringFromAlertCondition([self alertCondition]);
7996 fuel_desc = [NSString stringWithFormat:@"%.1f %@", fuel/10.0, lightYearsDesc];
7997 credits_desc = OOCredits(credits);
7998
7999 [gui clearAndKeepBackground:!guiChanged];
8000 text = DESC(@"status-commander-@");
8001 [gui setTitle:[NSString stringWithFormat:text, [self commanderName]]];
8002
8003 [gui setText:shipName forRow:0 align:GUI_ALIGN_CENTER];
8004
8005 [gui setArray:[NSArray arrayWithObjects:DESC(@"status-present-system"), systemName, nil] forRow:1];
8006 if ([self hasHyperspaceMotor]) [gui setArray:[NSArray arrayWithObjects:DESC(@"status-hyperspace-system"), targetSystemName, nil] forRow:2];
8007 [gui setArray:[NSArray arrayWithObjects:DESC(@"status-condition"), alert_desc, nil] forRow:3];
8008 [gui setArray:[NSArray arrayWithObjects:DESC(@"status-fuel"), fuel_desc, nil] forRow:4];
8009 [gui setArray:[NSArray arrayWithObjects:DESC(@"status-cash"), credits_desc, nil] forRow:5];
8010 [gui setArray:[NSArray arrayWithObjects:DESC(@"status-legal-status"), legal_desc, nil] forRow:6];
8011 [gui setArray:[NSArray arrayWithObjects:DESC(@"status-rating"), rating_desc, nil] forRow:7];
8012
8013
8014 [gui setColor:[gui colorFromSetting:kGuiStatusShipnameColor defaultValue:nil] forRow:0];
8015 for (i = 1 ; i <= 7 ; ++i)
8016 {
8017 // nil default = fall back to global default colour
8018 [gui setColor:[gui colorFromSetting:kGuiStatusDataColor defaultValue:nil] forRow:i];
8019 }
8020
8021 [gui setText:DESC(@"status-equipment") forRow:9];
8022
8023 [gui setColor:[gui colorFromSetting:kGuiStatusEquipmentHeadingColor defaultValue:nil] forRow:9];
8024
8025 [gui setShowTextCursor:NO];
8026 }
8027 /* ends */
8028
8029 if (lastTextKey)
8030 {
8031 [lastTextKey release];
8032 lastTextKey = nil;
8033 }
8034
8035 [[UNIVERSE gameView] clearMouse];
8036
8037 // Contributed by Pleb - show ship model if the appropriate user default key has been set - Nikos 20140127
8038 if (EXPECT_NOT([[NSUserDefaults standardUserDefaults] boolForKey:@"show-ship-model-in-status-screen"]))
8039 {
8040 [UNIVERSE removeDemoShips];
8041 [self showShipModelWithKey:[self shipDataKey] shipData:nil personality:[self entityPersonalityInt]
8042 factorX:2.5 factorY:1.7 factorZ:8.0 inContext:@"GUI_SCREEN_STATUS"];
8043 [self setShowDemoShips:YES];
8044 }
8045 else
8046 {
8047 [self setShowDemoShips:NO];
8048 }
8049
8050 [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
8051
8052 if (guiChanged)
8053 {
8054 NSDictionary *fgDescriptor = nil, *bgDescriptor = nil;
8055 if ([self status] == STATUS_DOCKED)
8056 {
8057 fgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"docked_overlay"];
8058 bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_docked"];
8059 }
8060 else
8061 {
8062 fgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"overlay"];
8063 if (alertCondition == ALERT_CONDITION_RED) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_red_alert"];
8064 else bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_in_flight"];
8065 }
8066
8067 [gui setForegroundTextureDescriptor:fgDescriptor];
8068
8069 if (bgDescriptor == nil) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status"];
8070 [gui setBackgroundTextureDescriptor:bgDescriptor];
8071
8072 [gui setStatusPage:0];
8073 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
8074 }
8075}
8076
8077
8078- (NSArray *) equipmentList
8079{
8080 GuiDisplayGen *gui = [UNIVERSE gui];
8081 NSMutableArray *quip1 = [NSMutableArray array]; // damaged
8082 NSMutableArray *quip2 = [NSMutableArray array]; // working
8083 NSEnumerator *eqTypeEnum = nil;
8084 OOEquipmentType *eqType = nil;
8085 NSString *desc = nil;
8086 NSString *alldesc = nil;
8087
8088 BOOL prioritiseDamaged = [[gui userSettings] oo_boolForKey:kGuiStatusPrioritiseDamaged defaultValue:YES];
8089
8090 for (eqTypeEnum = [OOEquipmentType reverseEquipmentEnumerator]; (eqType = [eqTypeEnum nextObject]); )
8091 {
8092 if ([eqType isVisible])
8093 {
8094 if ([eqType canCarryMultiple] && ![eqType isMissileOrMine])
8095 {
8096 NSString *damagedIdentifier = [[eqType identifier] stringByAppendingString:@"_DAMAGED"];
8097 NSUInteger count = 0, okcount = 0;
8098 okcount = [self countEquipmentItem:[eqType identifier]];
8099 count = okcount + [self countEquipmentItem:damagedIdentifier];
8100 if (count == 0)
8101 {
8102 // do nothing
8103 }
8104 // all items okay
8105 else if (count == okcount)
8106 {
8107 // only one installed display normally
8108 if (count == 1)
8109 {
8110 [quip2 addObject:[NSArray arrayWithObjects:[eqType name], [NSNumber numberWithBool:YES], [eqType displayColor], nil]];
8111 }
8112 // display plural form
8113 else
8114 {
8115 NSString *equipmentName = [eqType name];
8116 alldesc = OOExpandKey(@"equipment-plural", count, equipmentName);
8117 [quip2 addObject:[NSArray arrayWithObjects:alldesc, [NSNumber numberWithBool:YES], [eqType displayColor], nil]];
8118 }
8119 }
8120 // all broken, only one installed
8121 else if (count == 1 && okcount == 0)
8122 {
8123 desc = [NSString stringWithFormat:DESC(@"equipment-@-not-available"), [eqType name]];
8124 if (prioritiseDamaged)
8125 {
8126 [quip1 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], [eqType displayColor], nil]];
8127 }
8128 else
8129 {
8130 [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], [eqType displayColor], nil]];
8131 }
8132 }
8133 // some broken, multiple installed
8134 else
8135 {
8136 NSString *equipmentName = [eqType name];
8137 alldesc = OOExpandKey(@"equipment-plural-some-na", okcount, count, equipmentName);
8138 if (prioritiseDamaged)
8139 {
8140 [quip1 addObject:[NSArray arrayWithObjects:alldesc, [NSNumber numberWithBool:NO], [eqType displayColor], nil]];
8141 }
8142 else
8143 {
8144 [quip2 addObject:[NSArray arrayWithObjects:alldesc, [NSNumber numberWithBool:NO], [eqType displayColor], nil]];
8145 }
8146 }
8147 }
8148 else if ([self hasEquipmentItem:[eqType identifier]])
8149 {
8150 [quip2 addObject:[NSArray arrayWithObjects:[eqType name], [NSNumber numberWithBool:YES], [eqType displayColor], nil]];
8151 }
8152 else
8153 {
8154 // Check for damaged version
8155 if ([self hasEquipmentItem:[[eqType identifier] stringByAppendingString:@"_DAMAGED"]])
8156 {
8157 desc = [NSString stringWithFormat:DESC(@"equipment-@-not-available"), [eqType name]];
8158
8159 if (prioritiseDamaged)
8160 {
8161 [quip1 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], [eqType displayColor], nil]];
8162 }
8163 else
8164 {
8165 // just add in to the normal array
8166 [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:NO], [eqType displayColor], nil]];
8167 }
8168 }
8169 }
8170 }
8171 }
8172
8173 if (max_passengers > 0)
8174 {
8175 desc = [NSString stringWithFormat:DESC_PLURAL(@"equipment-pass-berth-@", max_passengers), max_passengers];
8176 [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] displayColor], nil]];
8177 }
8178
8179 if (!isWeaponNone(forward_weapon_type))
8180 {
8181 desc = [NSString stringWithFormat:DESC(@"equipment-fwd-weapon-@"),[forward_weapon_type name]];
8182 [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], [forward_weapon_type displayColor], nil]];
8183 }
8184 if (!isWeaponNone(aft_weapon_type))
8185 {
8186 desc = [NSString stringWithFormat:DESC(@"equipment-aft-weapon-@"),[aft_weapon_type name]];
8187 [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], [aft_weapon_type displayColor], nil]];
8188 }
8189 if (!isWeaponNone(port_weapon_type))
8190 {
8191 desc = [NSString stringWithFormat:DESC(@"equipment-port-weapon-@"),[port_weapon_type name]];
8192 [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], [port_weapon_type displayColor], nil]];
8193 }
8194 if (!isWeaponNone(starboard_weapon_type))
8195 {
8196 desc = [NSString stringWithFormat:DESC(@"equipment-stb-weapon-@"),[starboard_weapon_type name]];
8197 [quip2 addObject:[NSArray arrayWithObjects:desc, [NSNumber numberWithBool:YES], [starboard_weapon_type displayColor], nil]];
8198 }
8199
8200 // list damaged first, then working
8201 [quip1 addObjectsFromArray:quip2];
8202 return quip1;
8203}
8204
8205
8206- (NSUInteger) primedEquipmentCount
8207{
8208 return [eqScripts count];
8209}
8210
8211
8212- (NSString *) primedEquipmentName:(NSInteger)offset
8213{
8214 NSUInteger c = [self primedEquipmentCount];
8215 NSUInteger idx = (primedEquipment+(c+1)+offset)%(c+1);
8216 if (idx == c)
8217 {
8218 return DESC(@"equipment-primed-none-hud-label");
8219 }
8220 else
8221 {
8222 return [[OOEquipmentType equipmentTypeWithIdentifier:[[eqScripts oo_arrayAtIndex:idx] oo_stringAtIndex:0]] name];
8223 }
8224}
8225
8226
8227- (NSString *) currentPrimedEquipment
8228{
8229 NSString *result = @"";
8230 NSUInteger c = [eqScripts count];
8231 if (primedEquipment != c)
8232 {
8233 result = [[eqScripts oo_arrayAtIndex:primedEquipment] oo_stringAtIndex:0];
8234 }
8235 return result;
8236}
8237
8238
8239- (BOOL) setPrimedEquipment:(NSString *)eqKey showMessage:(BOOL)showMsg
8240{
8241 NSUInteger c = [eqScripts count];
8242 NSUInteger current = primedEquipment;
8243 primedEquipment = [self eqScriptIndexForKey:eqKey]; // if key not found primedEquipment is set to primed-none
8244 BOOL unprimeEq = [eqKey isEqualToString:@""];
8245 BOOL result = YES;
8246
8247 if (primedEquipment == c && !unprimeEq)
8248 {
8249 primedEquipment = current;
8250 result = NO;
8251 }
8252 else
8253 {
8254 if (primedEquipment != current && showMsg == YES)
8255 {
8256 NSString *equipmentName = [[OOEquipmentType equipmentTypeWithIdentifier:[[eqScripts oo_arrayAtIndex:primedEquipment] oo_stringAtIndex:0]] name];
8257 [UNIVERSE addMessage:unprimeEq ? OOExpandKey(@"equipment-primed-none") : OOExpandKey(@"equipment-primed", equipmentName) forCount:2.0];
8258 }
8259 }
8260 return result;
8261}
8262
8263
8264- (void) activatePrimableEquipment:(NSUInteger)index withMode:(OOPrimedEquipmentMode)mode
8265{
8266 // index == [eqScripts count] means we don't want to activate any equipment.
8267 if(index < [eqScripts count])
8268 {
8269 OOJSScript *eqScript = [[eqScripts oo_arrayAtIndex:index] objectAtIndex:1];
8270 JSContext *context = OOJSAcquireContext();
8271 NSAssert1(mode <= OOPRIMEDEQUIP_MODE, @"Primable equipment mode %i out of range", (int)mode);
8272
8273 switch (mode)
8274 {
8275 case OOPRIMEDEQUIP_MODE:
8276 [eqScript callMethod:OOJSID("mode") inContext:context withArguments:NULL count:0 result:NULL];
8277 break;
8279 [eqScript callMethod:OOJSID("activated") inContext:context withArguments:NULL count:0 result:NULL];
8280 break;
8281 }
8282 OOJSRelinquishContext(context);
8283 }
8284
8285}
8286
8287
8288- (NSString *) fastEquipmentA
8289{
8290 return _fastEquipmentA;
8291}
8292
8293
8294- (NSString *) fastEquipmentB
8295{
8296 return _fastEquipmentB;
8297}
8298
8299
8300- (void) setFastEquipmentA:(NSString *)eqKey
8301{
8302 [_fastEquipmentA release];
8303 _fastEquipmentA = [eqKey copy];
8304}
8305
8306
8307- (void) setFastEquipmentB:(NSString *)eqKey
8308{
8309 [_fastEquipmentB release];
8310 _fastEquipmentB = [eqKey copy];
8311}
8312
8313
8314- (OOEquipmentType *) weaponTypeForFacing:(OOWeaponFacing)facing strict:(BOOL)strict
8315{
8316 OOWeaponType weaponType = nil;
8317
8318 switch (facing)
8319 {
8321 weaponType = forward_weapon_type;
8322 break;
8323
8324 case WEAPON_FACING_AFT:
8325 weaponType = aft_weapon_type;
8326 break;
8327
8328 case WEAPON_FACING_PORT:
8329 weaponType = port_weapon_type;
8330 break;
8331
8333 weaponType = starboard_weapon_type;
8334 break;
8335
8336 case WEAPON_FACING_NONE:
8337 break;
8338 }
8339
8340 return weaponType;
8341}
8342
8343
8344- (NSArray *) missilesList
8345{
8346 [self tidyMissilePylons]; // just in case.
8347 return [super missilesList];
8348}
8349
8350
8351- (NSArray *) cargoList
8352{
8353 NSMutableArray *manifest = [NSMutableArray array];
8354 NSArray *list = [self cargoListForScripting];
8355 NSDictionary *commodity;
8356
8357 if (specialCargo) [manifest addObject:specialCargo];
8358
8359 foreach (commodity, list)
8360 {
8361 NSInteger quantity = [commodity oo_integerForKey:@"quantity"];
8362 NSString *units = [commodity oo_stringForKey:@"unit"];
8363 NSString *commodityName = [commodity oo_stringForKey:@"displayName"];
8364 NSInteger containers = [commodity oo_intForKey:@"containers"];
8365 BOOL extended = ![units isEqualToString:DESC(@"cargo-tons-symbol")] && containers > 0;
8366
8367 if (extended) {
8368 [manifest addObject:OOExpandKey(@"manifest-cargo-quantity-extended", quantity, units, commodityName, containers)];
8369 } else {
8370 [manifest addObject:OOExpandKey(@"manifest-cargo-quantity", quantity, units, commodityName)];
8371 }
8372 }
8373
8374 return manifest;
8375}
8376
8377
8378- (NSArray *) cargoListForScripting
8379{
8380 NSMutableArray *list = [NSMutableArray array];
8381
8382 NSUInteger i, j, commodityCount = [shipCommodityData count];
8383 OOCargoQuantity quantityInHold[commodityCount];
8384 OOCargoQuantity containersInHold[commodityCount];
8385 NSArray *goods = [shipCommodityData goods];
8386
8387 // following changed to work whether docked or not
8388 for (i = 0; i < commodityCount; i++)
8389 {
8390 quantityInHold[i] = [shipCommodityData quantityForGood:[goods oo_stringAtIndex:i]];
8391 containersInHold[i] = 0;
8392 }
8393 for (i = 0; i < [cargo count]; i++)
8394 {
8395 ShipEntity *container = [cargo objectAtIndex:i];
8396 j = [goods indexOfObject:[container commodityType]];
8397 quantityInHold[j] += [container commodityAmount];
8398 ++containersInHold[j];
8399 }
8400
8401 for (i = 0; i < commodityCount; i++)
8402 {
8403 if (quantityInHold[i] > 0)
8404 {
8405 NSMutableDictionary *commodity = [NSMutableDictionary dictionaryWithCapacity:4];
8406 NSString *symName = [goods oo_stringAtIndex:i];
8407 // commodity, quantity - keep consistency between .manifest and .contracts
8408 [commodity setObject:symName forKey:@"commodity"];
8409 [commodity setObject:[NSNumber numberWithUnsignedInt:quantityInHold[i]] forKey:@"quantity"];
8410 [commodity setObject:[NSNumber numberWithUnsignedInt:containersInHold[i]] forKey:@"containers"];
8411 [commodity setObject:[shipCommodityData nameForGood:symName] forKey:@"displayName"];
8412 [commodity setObject:DisplayStringForMassUnitForCommodity(symName) forKey:@"unit"];
8413 [list addObject:commodity];
8414 }
8415 }
8416
8417 return [[list copy] autorelease]; // return an immutable copy
8418}
8419
8420
8421// determines general export legality, not tied to a station
8422- (unsigned) legalStatusOfCargoList
8423{
8424 NSString *good = nil;
8425 OOCargoQuantity amount;
8426 unsigned penalty = 0;
8427
8428 foreach (good, [shipCommodityData goods])
8429 {
8430 amount = [shipCommodityData quantityForGood:good];
8431 penalty += [shipCommodityData exportLegalityForGood:good] * amount;
8432 }
8433 return penalty;
8434}
8435
8436
8437- (NSArray*) contractsListForScriptingFromArray:(NSArray *) contracts_array forCargo:(BOOL)forCargo
8438{
8439 NSMutableArray *result = [NSMutableArray array];
8440 NSUInteger i;
8441
8442 for (i = 0; i < [contracts_array count]; i++)
8443 {
8444 NSMutableDictionary *contract = [NSMutableDictionary dictionaryWithCapacity:10];
8445 NSDictionary *dict = [contracts_array oo_dictionaryAtIndex:i];
8446 if (forCargo)
8447 {
8448 // commodity, quantity - keep consistency between .manifest and .contracts
8449 [contract setObject:[dict oo_stringForKey:CARGO_KEY_TYPE] forKey:@"commodity"];
8450 [contract setObject:[NSNumber numberWithUnsignedInt:[dict oo_intForKey:CARGO_KEY_AMOUNT]] forKey:@"quantity"];
8451 [contract setObject:[dict oo_stringForKey:CARGO_KEY_DESCRIPTION] forKey:@"description"];
8452 }
8453 else
8454 {
8455 [contract setObject:[dict oo_stringForKey:PASSENGER_KEY_NAME] forKey:PASSENGER_KEY_NAME];
8456 [contract setObject:[NSNumber numberWithUnsignedInt:[dict oo_unsignedIntForKey:CONTRACT_KEY_RISK]] forKey:CONTRACT_KEY_RISK];
8457 }
8458
8459 OOSystemID planet = [dict oo_intForKey:CONTRACT_KEY_DESTINATION];
8460 NSString *planetName = [UNIVERSE getSystemName:planet];
8461 [contract setObject:[NSNumber numberWithUnsignedInt:planet] forKey:CONTRACT_KEY_DESTINATION];
8462 [contract setObject:planetName forKey:@"destinationName"];
8463 planet = [dict oo_intForKey:CONTRACT_KEY_START];
8464 planetName = [UNIVERSE getSystemName: planet];
8465 [contract setObject:[NSNumber numberWithUnsignedInt:planet] forKey:CONTRACT_KEY_START];
8466 [contract setObject:planetName forKey:@"startName"];
8467
8468 int dest_eta = [dict oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock;
8469 [contract setObject:[NSNumber numberWithInt:dest_eta] forKey:@"eta"];
8470 [contract setObject:[UNIVERSE shortTimeDescription:dest_eta] forKey:@"etaDescription"];
8471 [contract setObject:[NSNumber numberWithInt:[dict oo_intForKey:CONTRACT_KEY_PREMIUM]] forKey:CONTRACT_KEY_PREMIUM];
8472 [contract setObject:[NSNumber numberWithInt:[dict oo_intForKey:CONTRACT_KEY_FEE]] forKey:CONTRACT_KEY_FEE];
8473 [result addObject:contract];
8474 }
8475
8476 return [[result copy] autorelease]; // return an immutable copy
8477}
8478
8479
8480- (NSArray *) passengerListForScripting
8481{
8482 return [self contractsListForScriptingFromArray:passengers forCargo:NO];
8483}
8484
8485
8486- (NSArray *) parcelListForScripting
8487{
8488 return [self contractsListForScriptingFromArray:parcels forCargo:NO];
8489}
8490
8491
8492- (NSArray *) contractListForScripting
8493{
8494 return [self contractsListForScriptingFromArray:contracts forCargo:YES];
8495}
8496
8497- (void) setGuiToSystemDataScreen
8498{
8499 [self setGuiToSystemDataScreenRefreshBackground: NO];
8500}
8501
8502- (void) setGuiToSystemDataScreenRefreshBackground: (BOOL) refreshBackground
8503{
8504 NSDictionary *infoSystemData;
8505 NSString *infoSystemName;
8506
8507 infoSystemData = [[UNIVERSE generateSystemData:info_system_id] retain]; // retained
8508 NSInteger concealment = [infoSystemData oo_intForKey:@"concealment" defaultValue:OO_SYSTEMCONCEALMENT_NONE];
8509 infoSystemName = [infoSystemData oo_stringForKey:KEY_NAME];
8510
8511 BOOL sunGoneNova = ([infoSystemData oo_boolForKey:@"sun_gone_nova"]);
8512 OOGUIScreenID oldScreen = gui_screen;
8513
8514 GuiDisplayGen *gui = [UNIVERSE gui];
8515 gui_screen = GUI_SCREEN_SYSTEM_DATA;
8516 BOOL guiChanged = (oldScreen != gui_screen);
8517
8518 Random_Seed infoSystemRandomSeed = [[UNIVERSE systemManager] getRandomSeedForSystem:info_system_id
8519 inGalaxy:[self galaxyNumber]];
8520
8521 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO];
8522
8523 // GUI stuff
8524 {
8525 OOGUITabSettings tab_stops;
8526 tab_stops[0] = 0;
8527 tab_stops[1] = 96;
8528 tab_stops[2] = 144;
8529 [gui overrideTabs:tab_stops from:kGuiSystemdataTabs length:3];
8530 [gui setTabStops:tab_stops];
8531
8532 NSUInteger techLevel = [infoSystemData oo_intForKey:KEY_TECHLEVEL] + 1;
8533 int population = [infoSystemData oo_intForKey:KEY_POPULATION];
8534 int productivity = [infoSystemData oo_intForKey:KEY_PRODUCTIVITY];
8535 int radius = [infoSystemData oo_intForKey:KEY_RADIUS];
8536
8537 NSString *government_desc = [infoSystemData oo_stringForKey:KEY_GOVERNMENT_DESC
8538 defaultValue:OODisplayStringFromGovernmentID([infoSystemData oo_intForKey:KEY_GOVERNMENT])];
8539 NSString *economy_desc = [infoSystemData oo_stringForKey:KEY_ECONOMY_DESC
8540 defaultValue:OODisplayStringFromEconomyID([infoSystemData oo_intForKey:KEY_ECONOMY])];
8541 NSString *inhabitants = [infoSystemData oo_stringForKey:KEY_INHABITANTS];
8542 NSString *system_desc = [infoSystemData oo_stringForKey:KEY_DESCRIPTION];
8543
8544 NSString *populationDesc = [infoSystemData oo_stringForKey:KEY_POPULATION_DESC
8545 defaultValue:OOExpandKeyWithSeed(kNilRandomSeed, @"sysdata-pop-value", population)];
8546
8547 if (sunGoneNova)
8548 {
8549 population = 0;
8550 productivity = 0;
8551 radius = 0;
8552 techLevel = 0;
8553
8554 government_desc = OOExpandKeyWithSeed(infoSystemRandomSeed, @"nova-system-government");
8555 economy_desc = OOExpandKeyWithSeed(infoSystemRandomSeed, @"nova-system-economy");
8556 inhabitants = OOExpandKeyWithSeed(infoSystemRandomSeed, @"nova-system-inhabitants");
8557 {
8558 NSString *system = infoSystemName;
8559 system_desc = OOExpandKeyWithSeed(infoSystemRandomSeed, @"nova-system-description", system);
8560 }
8561 populationDesc = OOExpandKeyWithSeed(infoSystemRandomSeed, @"sysdata-pop-value", population);
8562 }
8563
8564
8565 [gui clearAndKeepBackground:!refreshBackground && !guiChanged];
8566 [UNIVERSE removeDemoShips];
8567
8568 if (concealment < OO_SYSTEMCONCEALMENT_NONAME)
8569 {
8570 NSString *system = infoSystemName;
8571 [gui setTitle:OOExpandKeyWithSeed(infoSystemRandomSeed, @"sysdata-data-on-system", system)];
8572 }
8573 else
8574 {
8575 [gui setTitle:OOExpandKey(@"sysdata-data-on-system-no-name")];
8576 }
8577
8578 if (concealment >= OO_SYSTEMCONCEALMENT_NODATA)
8579 {
8580 OOGUIRow i = [gui addLongText:OOExpandKey(@"sysdata-data-on-system-no-data") startingAtRow:15 align:GUI_ALIGN_LEFT];
8581 missionTextRow = i;
8582 for (i-- ; i > 14 ; --i)
8583 {
8584 [gui setColor:[gui colorFromSetting:kGuiSystemdataDescriptionColor defaultValue:[OOColor greenColor]] forRow:i];
8585 }
8586 }
8587 else
8588 {
8589 NSPoint infoSystemCoordinates = [[UNIVERSE systemManager] getCoordinatesForSystem: info_system_id inGalaxy: galaxy_number];
8590 double distance = distanceBetweenPlanetPositions(infoSystemCoordinates.x, infoSystemCoordinates.y, galaxy_coordinates.x, galaxy_coordinates.y);
8591 if(distance == 0.0 && info_system_id != system_id)
8592 {
8593 distance = 0.1;
8594 }
8595 NSString *distanceInfo = [NSString stringWithFormat: @"%.1f ly", distance];
8596 if (ANA_mode != OPTIMIZED_BY_NONE)
8597 {
8598 NSDictionary *routeInfo = nil;
8599 routeInfo = [UNIVERSE routeFromSystem: system_id toSystem: info_system_id optimizedBy: ANA_mode];
8600 if (routeInfo != nil)
8601 {
8602 double routeDistance = [[routeInfo objectForKey: @"distance"] doubleValue];
8603 double routeTime = [[routeInfo objectForKey: @"time"] doubleValue];
8604 int routeJumps = [[routeInfo objectForKey: @"jumps"] intValue];
8605 if(routeDistance == 0.0 && info_system_id != system_id) {
8606 routeDistance = 0.1;
8607 routeTime = 0.01;
8608 routeJumps = 0;
8609 }
8610 distanceInfo = [NSString stringWithFormat: @"%.1f ly / %.1f %@ / %d %@",
8611 routeDistance,
8612 routeTime,
8613 // don't rely on DESC_PLURAL for routeTime since it is of type double
8614 routeTime > 1.05 || routeTime < 0.95 ? DESC(@"sysdata-route-hours%1") : DESC(@"sysdata-route-hours%0"),
8615 routeJumps,
8616 DESC_PLURAL(@"sysdata-route-jumps", routeJumps)];
8617 }
8618 }
8619
8620 OOGUIRow i;
8621
8622 for (i = 1; i <= 16; i++) {
8623 NSString *ln = [NSString stringWithFormat:@"sysdata-line-%ld", (long)i];
8624 NSString *line = OOExpandKeyWithSeed(infoSystemRandomSeed, ln, economy_desc, government_desc, techLevel, populationDesc, inhabitants, productivity, radius, distanceInfo);
8625 if (![line isEqualToString:@""])
8626 {
8627 NSArray *lines = [line componentsSeparatedByString:@"\t"];
8628 if ([lines count] == 1)
8629 {
8630 [gui setArray:[NSArray arrayWithObjects:[lines objectAtIndex:0],
8631 nil]
8632 forRow:i];
8633 }
8634 if ([lines count] == 2)
8635 {
8636 [gui setArray:[NSArray arrayWithObjects:[lines objectAtIndex:0],
8637 [lines objectAtIndex:1],
8638 nil]
8639 forRow:i];
8640 }
8641 if ([lines count] == 3)
8642 {
8643 if ([[lines objectAtIndex:2] isEqualToString:@""])
8644 {
8645 [gui setArray:[NSArray arrayWithObjects:[lines objectAtIndex:0],
8646 [lines objectAtIndex:1],
8647 nil]
8648 forRow:i];
8649 }
8650 else
8651 {
8652 [gui setArray:[NSArray arrayWithObjects:[lines objectAtIndex:0],
8653 [lines objectAtIndex:1],
8654 [lines objectAtIndex:2],
8655 nil]
8656 forRow:i];
8657 }
8658 }
8659 }
8660 else
8661 {
8662 [gui setArray:[NSArray arrayWithObjects:@"",
8663 nil]
8664 forRow:i];
8665 }
8666 }
8667
8668
8669 i = [gui addLongText:system_desc startingAtRow:17 align:GUI_ALIGN_LEFT];
8670 missionTextRow = i;
8671 for (i-- ; i > 16 ; --i)
8672 {
8673 [gui setColor:[gui colorFromSetting:kGuiSystemdataDescriptionColor defaultValue:[OOColor greenColor]] forRow:i];
8674 }
8675 for (i = 1 ; i <= 14 ; ++i)
8676 {
8677 // nil default = fall back to global default colour
8678 [gui setColor:[gui colorFromSetting:kGuiSystemdataFactsColor defaultValue:nil] forRow:i];
8679 }
8680 }
8681
8682 [gui setShowTextCursor:NO];
8683 }
8684 /* ends */
8685
8686 [lastTextKey release];
8687 lastTextKey = nil;
8688
8689 [[UNIVERSE gameView] clearMouse];
8690
8691 [infoSystemData release];
8692
8693 [self setShowDemoShips:NO];
8694 [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
8695
8696 // if the system has gone nova, there's no planet to display
8697 if (!sunGoneNova && concealment < OO_SYSTEMCONCEALMENT_NODATA)
8698 {
8699 // The next code is generating the miniature planets.
8700 // When normal planets are displayed, the PRNG is reset. This happens not with procedural planet display.
8701 RANROTSeed ranrotSavedSeed = RANROTGetFullSeed();
8702 RNG_Seed saved_seed = currentRandomSeed();
8703
8704 if (info_system_id == system_id)
8705 {
8706 [self setBackgroundFromDescriptionsKey:@"gui-scene-show-local-planet"];
8707 }
8708 else
8709 {
8710 [self setBackgroundFromDescriptionsKey:@"gui-scene-show-planet"];
8711 }
8712
8713 setRandomSeed(saved_seed);
8714 RANROTSetFullSeed(ranrotSavedSeed);
8715 }
8716
8717 if (refreshBackground || guiChanged)
8718 {
8719 [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"];
8720 [gui setBackgroundTextureKey:sunGoneNova ? @"system_data_nova" : @"system_data"];
8721
8722 [self noteGUIDidChangeFrom:oldScreen to:gui_screen refresh: refreshBackground];
8723 [self checkScript]; // Still needed by some OXPs?
8724 }
8725}
8726
8727
8728- (void) prepareMarkedDestination:(NSMutableDictionary *)markers :(NSDictionary *)marker
8729{
8730 NSNumber *key = [NSNumber numberWithInt:[marker oo_intForKey:@"system"]];
8731 NSMutableArray *list = [markers objectForKey:key];
8732 if (list == nil)
8733 {
8734 list = [NSMutableArray arrayWithObject:marker];
8735 }
8736 else
8737 {
8738 [list addObject:marker];
8739 }
8740 [markers setObject:list forKey:key];
8741}
8742
8743
8744- (NSDictionary *) markedDestinations
8745{
8746 // get a list of systems marked as contract destinations
8747 NSMutableDictionary *destinations = [NSMutableDictionary dictionaryWithCapacity:256];
8748 unsigned i;
8749 OOSystemID sysid;
8750 NSDictionary *marker;
8751
8752 for (i = 0; i < [passengers count]; i++)
8753 {
8754 sysid = [[passengers oo_dictionaryAtIndex:i] oo_unsignedCharForKey:CONTRACT_KEY_DESTINATION];
8755 marker = [self passengerContractMarker:sysid];
8756 [self prepareMarkedDestination:destinations:marker];
8757 }
8758 for (i = 0; i < [parcels count]; i++)
8759 {
8760 sysid = [[parcels oo_dictionaryAtIndex:i] oo_unsignedCharForKey:CONTRACT_KEY_DESTINATION];
8761 marker = [self parcelContractMarker:sysid];
8762 [self prepareMarkedDestination:destinations:marker];
8763 }
8764 for (i = 0; i < [contracts count]; i++)
8765 {
8766 sysid = [[contracts oo_dictionaryAtIndex:i] oo_unsignedCharForKey:CONTRACT_KEY_DESTINATION];
8767 marker = [self cargoContractMarker:sysid];
8768 [self prepareMarkedDestination:destinations:marker];
8769 }
8770
8771 NSString *key = nil;
8772
8773 foreachkey (key, missionDestinations)
8774 {
8775 marker = [missionDestinations objectForKey:key];
8776 [self prepareMarkedDestination:destinations:marker];
8777 }
8778
8779 return destinations;
8780}
8781
8782- (void) setGuiToLongRangeChartScreen
8783{
8784 OOGUIScreenID oldScreen = gui_screen;
8785 GuiDisplayGen *gui = [UNIVERSE gui];
8786 [gui clearAndKeepBackground:NO];
8787 [gui setBackgroundTextureKey:@"short_range_chart"];
8788 [self setMissionBackgroundSpecial: nil];
8789 gui_screen = GUI_SCREEN_LONG_RANGE_CHART;
8790 target_chart_zoom = CHART_MAX_ZOOM;
8791 [self setGuiToChartScreenFrom: oldScreen];
8792}
8793
8794- (void) setGuiToShortRangeChartScreen
8795{
8796 OOGUIScreenID oldScreen = gui_screen;
8797 GuiDisplayGen *gui = [UNIVERSE gui];
8798 [gui clearAndKeepBackground:NO];
8799 [gui setBackgroundTextureKey:@"short_range_chart"];
8800 [self setMissionBackgroundSpecial: nil];
8801 gui_screen = GUI_SCREEN_SHORT_RANGE_CHART;
8802 [self setGuiToChartScreenFrom: oldScreen];
8803}
8804
8805- (void) setGuiToChartScreenFrom: (OOGUIScreenID) oldScreen
8806{
8807 GuiDisplayGen *gui = [UNIVERSE gui];
8808
8809 BOOL guiChanged = (oldScreen != gui_screen);
8810
8811 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
8812
8813 target_system_id = [UNIVERSE findSystemNumberAtCoords:cursor_coordinates withGalaxy:galaxy_number includingHidden:NO];
8814
8815 [UNIVERSE preloadPlanetTexturesForSystem:target_system_id];
8816
8817 // GUI stuff
8818 {
8819 //[gui clearAndKeepBackground:!guiChanged];
8820 [gui setStarChartTitle];
8821 // refresh the short range chart cache, in case we've just loaded a save game with different local overrides, etc.
8822 [gui refreshStarChart];
8823 //[gui setText:targetSystemName forRow:19];
8824 // distance-f & est-travel-time-f are identical between short & long range charts in standard Oolite, however can be alterered separately via OXPs
8825 //[gui setText:OOExpandKey(@"short-range-chart-distance", distance) forRow:20];
8826 //NSString *travelTimeRow = @"";
8827 //if ([self hasHyperspaceMotor] && distance > 0.0 && distance * 10.0 <= fuel)
8828 //{
8829 // double time = estimatedTravelTime;
8830 // travelTimeRow = OOExpandKey(@"short-range-chart-est-travel-time", time);
8831 //}
8832 //[gui setText:travelTimeRow forRow:21];
8833 if (gui_screen == GUI_SCREEN_LONG_RANGE_CHART)
8834 {
8835 NSString *displaySearchString = planetSearchString ? [planetSearchString capitalizedString] : (NSString *)@"";
8836 [gui setText:[NSString stringWithFormat:DESC(@"long-range-chart-find-planet-@"), displaySearchString] forRow:GUI_ROW_PLANET_FINDER];
8837 [gui setColor:[OOColor cyanColor] forRow:GUI_ROW_PLANET_FINDER];
8838 [gui setShowTextCursor:YES];
8839 [gui setCurrentRow:GUI_ROW_PLANET_FINDER];
8840 }
8841 else
8842 {
8843 [gui setShowTextCursor:NO];
8844 }
8845 }
8846 /* ends */
8847
8848 [self setShowDemoShips:NO];
8849 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
8850
8851 if (guiChanged)
8852 {
8853 [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"];
8854
8855 [gui setBackgroundTextureKey:@"short_range_chart"];
8856 if (found_system_id >= 0)
8857 {
8858 [UNIVERSE findSystemCoordinatesWithPrefix:[[UNIVERSE getSystemName:found_system_id] lowercaseString] exactMatch:YES];
8859 }
8860 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
8861 }
8862}
8863
8864
8865static NSString *SliderString(NSInteger amountIn20ths)
8866{
8867 NSString *filledSlider = [@"|||||||||||||||||||||||||" substringToIndex:amountIn20ths];
8868 NSString *emptySlider = [@"........................." substringToIndex:20 - amountIn20ths];
8869 return [NSString stringWithFormat:@"%@%@", filledSlider, emptySlider];
8870}
8871
8872
8873- (void) setGuiToGameOptionsScreen
8874{
8875 MyOpenGLView *gameView = [UNIVERSE gameView];
8876
8877 [[UNIVERSE gameView] clearMouse];
8878 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
8879
8880 gui_screen = GUI_SCREEN_GAMEOPTIONS;
8881
8882 // GUI stuff
8883 {
8884 #define OO_SETACCESSCONDITIONFORROW(condition, row) \
8885 do { \
8886 if ((condition)) \
8887 { \
8888 [gui setKey:GUI_KEY_OK forRow:(row)]; \
8889 } \
8890 else \
8891 { \
8892 [gui setColor:[OOColor grayColor] forRow:(row)]; \
8893 } \
8894 } while(0)
8895 BOOL startingGame = [self status] == STATUS_START_GAME;
8896 GuiDisplayGen* gui = [UNIVERSE gui];
8897 GUI_ROW_INIT(gui);
8898
8899 int first_sel_row = GUI_FIRST_ROW(GAME)-4; // repositioned menu
8900
8901 [gui clear];
8902 [gui setTitle:[NSString stringWithFormat:DESC(@"status-commander-@"), [self commanderName]]]; // Same title as status screen.
8903
8904#if OO_RESOLUTION_OPTION
8905 GameController *controller = [UNIVERSE gameController];
8906
8907 NSUInteger displayModeIndex = [controller indexOfCurrentDisplayMode];
8908 if (displayModeIndex == NSNotFound)
8909 {
8910 OOLogWARN(@"display.currentMode.notFound", @"%@", @"couldn't find current fullscreen setting, switching to default.");
8911 displayModeIndex = 0;
8912 }
8913
8914 NSArray *modeList = [controller displayModes];
8915 NSDictionary *mode = nil;
8916 if ([modeList count])
8917 {
8918 mode = [modeList objectAtIndex:displayModeIndex];
8919 }
8920 if (mode == nil) return; // Got a better idea?
8921
8922 unsigned modeWidth = [mode oo_unsignedIntForKey:kOODisplayWidth];
8923 unsigned modeHeight = [mode oo_unsignedIntForKey:kOODisplayHeight];
8924 float modeRefresh = [mode oo_floatForKey:kOODisplayRefreshRate];
8925
8926 BOOL runningOnPrimaryDisplayDevice = [gameView isRunningOnPrimaryDisplayDevice];
8927#if OOLITE_WINDOWS
8928 if (!runningOnPrimaryDisplayDevice)
8929 {
8930 MONITORINFOEX mInfo = [gameView currentMonitorInfo];
8931 modeWidth = mInfo.rcMonitor.right - mInfo.rcMonitor.left;
8932 modeHeight = mInfo.rcMonitor.bottom - mInfo.rcMonitor.top;
8933 }
8934#endif
8935
8936 NSString *displayModeString = [self screenModeStringForWidth:modeWidth height:modeHeight refreshRate:modeRefresh];
8937
8938 [gui setText:displayModeString forRow:GUI_ROW(GAME,DISPLAY) align:GUI_ALIGN_CENTER];
8939 if (runningOnPrimaryDisplayDevice)
8940 {
8941 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,DISPLAY)];
8942 }
8943 else
8944 {
8945 [gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,DISPLAY)];
8946 }
8947#endif // OO_RESOLUTIOM_OPTION
8948
8949
8950#if OOLITE_WINDOWS
8951 if ([gameView hdrOutput])
8952 {
8953 NSArray *brightnesses = [[UNIVERSE descriptions] oo_arrayForKey: @"hdr_maxBrightness_array"];
8954 int brightnessIdx = [brightnesses indexOfObject:[NSString stringWithFormat:@"%d", (int)[gameView hdrMaxBrightness]]];
8955
8956 if (brightnessIdx == NSNotFound)
8957 {
8958 OOLogWARN(@"hdr.maxBrightness.notFound", @"%@", @"couldn't find current max brightness setting, switching to 400 nits.");
8959 brightnessIdx = 0;
8960 }
8961
8962 int brightnessValue = [brightnesses oo_intAtIndex:brightnessIdx];
8963 NSString *maxBrightnessString = OOExpandKey(@"gameoptions-hdr-maxbrightness", brightnessValue);
8964
8965 [gui setText:maxBrightnessString forRow:GUI_ROW(GAME,HDRMAXBRIGHTNESS) align:GUI_ALIGN_CENTER];
8966 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,HDRMAXBRIGHTNESS)];
8967 }
8968#endif
8969
8970
8971 if ([UNIVERSE autoSave])
8972 [gui setText:DESC(@"gameoptions-autosave-yes") forRow:GUI_ROW(GAME,AUTOSAVE) align:GUI_ALIGN_CENTER];
8973 else
8974 [gui setText:DESC(@"gameoptions-autosave-no") forRow:GUI_ROW(GAME,AUTOSAVE) align:GUI_ALIGN_CENTER];
8975 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,AUTOSAVE)];
8976
8977 // volume control
8978 if ([OOSound respondsToSelector:@selector(masterVolume)] && [OOSound isSoundOK])
8979 {
8980 double volume = 100.0 * [OOSound masterVolume];
8981 int vol = (volume / 5.0 + 0.5); // avoid rounding errors
8982 NSString* soundVolumeWordDesc = DESC(@"gameoptions-sound-volume");
8983 if (vol > 0)
8984 [gui setText:[NSString stringWithFormat:@"%@%@ ", soundVolumeWordDesc, SliderString(vol)] forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER];
8985 else
8986 [gui setText:DESC(@"gameoptions-sound-volume-mute") forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER];
8987 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,VOLUME)];
8988 }
8989 else
8990 {
8991 [gui setText:DESC(@"gameoptions-volume-external-only") forRow:GUI_ROW(GAME,VOLUME) align:GUI_ALIGN_CENTER];
8992 [gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,VOLUME)];
8993 }
8994
8995
8996 // field of view control
8997 float fov = [gameView fov:NO];
8998 int fovTicks = (int)((fov - MIN_FOV_DEG) * 20 / (MAX_FOV_DEG - MIN_FOV_DEG));
8999 NSString* fovWordDesc = DESC(@"gameoptions-fov-value");
9000 [gui setText:[NSString stringWithFormat:@"%@%@ (%d%c) ", fovWordDesc, SliderString(fovTicks), (int)fov, 176 /*176 is the degrees symbol Unicode code point*/] forRow:GUI_ROW(GAME,FOV) align:GUI_ALIGN_CENTER];
9001 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,FOV)];
9002
9003 // color blind mode
9004 int colorblindMode = [UNIVERSE colorblindMode];
9005 NSString *colorblindModeDesc = [[[UNIVERSE descriptions] oo_arrayForKey: @"colorblind_mode"] oo_stringAtIndex:[UNIVERSE useShaders] ? colorblindMode : 0];
9006 NSString *colorblindModeMsg = OOExpandKey(@"gameoptions-colorblind-mode", colorblindModeDesc);
9007 [gui setText:colorblindModeMsg forRow:GUI_ROW(GAME,COLORBLINDMODE) align:GUI_ALIGN_CENTER];
9008 if ([UNIVERSE useShaders])
9009 {
9010 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,COLORBLINDMODE)];
9011 }
9012 else
9013 {
9014 [gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,COLORBLINDMODE)];
9015 }
9016
9017#if OOLITE_SPEECH_SYNTH
9018 // Speech control
9019 switch (isSpeechOn)
9020 {
9022 [gui setText:DESC(@"gameoptions-spoken-messages-no") forRow:GUI_ROW(GAME,SPEECH) align:GUI_ALIGN_CENTER];
9023 break;
9025 [gui setText:DESC(@"gameoptions-spoken-messages-comms") forRow:GUI_ROW(GAME,SPEECH) align:GUI_ALIGN_CENTER];
9026 break;
9028 [gui setText:DESC(@"gameoptions-spoken-messages-yes") forRow:GUI_ROW(GAME,SPEECH) align:GUI_ALIGN_CENTER];
9029 break;
9030 }
9031 OO_SETACCESSCONDITIONFORROW(!startingGame, GUI_ROW(GAME,SPEECH));
9032
9033#if OOLITE_ESPEAK
9034 {
9035 NSString *voiceName = [UNIVERSE voiceName:voice_no];
9036 NSString *message = OOExpandKey(@"gameoptions-voice-name", voiceName);
9037 [gui setText:message forRow:GUI_ROW(GAME,SPEECH_LANGUAGE) align:GUI_ALIGN_CENTER];
9038 OO_SETACCESSCONDITIONFORROW(!startingGame, GUI_ROW(GAME,SPEECH_LANGUAGE));
9039
9040 message = [NSString stringWithFormat:@"%@", DESC(voice_gender_m ? @"gameoptions-voice-M" : @"gameoptions-voice-F")];
9041 [gui setText:message forRow:GUI_ROW(GAME,SPEECH_GENDER) align:GUI_ALIGN_CENTER];
9042 OO_SETACCESSCONDITIONFORROW(!startingGame, GUI_ROW(GAME,SPEECH_GENDER));
9043 }
9044#endif
9045#endif
9046#if !OOLITE_MAC_OS_X
9047 // window/fullscreen
9048 if([gameView inFullScreenMode])
9049 {
9050 [gui setText:DESC(@"gameoptions-play-in-window") forRow:GUI_ROW(GAME,DISPLAYSTYLE) align:GUI_ALIGN_CENTER];
9051 }
9052 else
9053 {
9054 [gui setText:DESC(@"gameoptions-play-in-fullscreen") forRow:GUI_ROW(GAME,DISPLAYSTYLE) align:GUI_ALIGN_CENTER];
9055 }
9056 [gui setKey: GUI_KEY_OK forRow: GUI_ROW(GAME,DISPLAYSTYLE)];
9057#endif
9058
9059 [gui setText:DESC(@"gameoptions-joystick-configuration") forRow: GUI_ROW(GAME,STICKMAPPER) align: GUI_ALIGN_CENTER];
9060 OO_SETACCESSCONDITIONFORROW([[OOJoystickManager sharedStickHandler] joystickCount], GUI_ROW(GAME,STICKMAPPER));
9061
9062 [gui setText:DESC(@"gameoptions-keyboard-configuration") forRow: GUI_ROW(GAME,KEYMAPPER) align: GUI_ALIGN_CENTER];
9063 [gui setKey: GUI_KEY_OK forRow: GUI_ROW(GAME,KEYMAPPER)];
9064
9065
9066 NSString *musicMode = [UNIVERSE descriptionForArrayKey:@"music-mode" index:[[OOMusicController sharedController] mode]];
9067 NSString *message = OOExpandKey(@"gameoptions-music-mode", musicMode);
9068 [gui setText:message forRow:GUI_ROW(GAME,MUSIC) align:GUI_ALIGN_CENTER];
9069 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,MUSIC)];
9070
9071 if (![gameView hdrOutput])
9072 {
9073 if ([UNIVERSE wireframeGraphics])
9074 [gui setText:DESC(@"gameoptions-wireframe-graphics-yes") forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS) align:GUI_ALIGN_CENTER];
9075 else
9076 [gui setText:DESC(@"gameoptions-wireframe-graphics-no") forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS) align:GUI_ALIGN_CENTER];
9077 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,WIREFRAMEGRAPHICS)];
9078 }
9079#if OOLITE_WINDOWS
9080 else
9081 {
9082 float paperWhite = [gameView hdrPaperWhiteBrightness];
9083 int paperWhiteTicks = (int)((paperWhite - MIN_HDR_PAPERWHITE) * 20 / (MAX_HDR_PAPERWHITE - MIN_HDR_PAPERWHITE));
9084 NSString* paperWhiteWordDesc = DESC(@"gameoptions-hdr-paperwhite");
9085 [gui setText:[NSString stringWithFormat:@"%@%@ (%d) ", paperWhiteWordDesc, SliderString(paperWhiteTicks), (int)paperWhite] forRow:GUI_ROW(GAME,HDRPAPERWHITE) align:GUI_ALIGN_CENTER];
9086 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,HDRPAPERWHITE)];
9087 }
9088#endif
9089
9090#if !NEW_PLANETS
9091 if ([UNIVERSE doProcedurallyTexturedPlanets])
9092 [gui setText:DESC(@"gameoptions-procedurally-textured-planets-yes") forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS) align:GUI_ALIGN_CENTER];
9093 else
9094 [gui setText:DESC(@"gameoptions-procedurally-textured-planets-no") forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS) align:GUI_ALIGN_CENTER];
9095 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,PROCEDURALLYTEXTUREDPLANETS)];
9096#endif
9097
9098 OOGraphicsDetail detailLevel = [UNIVERSE detailLevel];
9099 NSString *shaderEffectsOptionsString = OOExpand(@"gameoptions-detaillevel-[detailLevel]", detailLevel);
9100 [gui setText:OOExpandKey(shaderEffectsOptionsString) forRow:GUI_ROW(GAME,SHADEREFFECTS) align:GUI_ALIGN_CENTER];
9101 if (![[OOOpenGLExtensionManager sharedManager] shadersForceDisabled])
9102 {
9103 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,SHADEREFFECTS)];
9104 }
9105 else
9106 {
9107 // deactivate this option if shaders have been disabled from the commend line
9108 [gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,SHADEREFFECTS)];
9109 }
9110
9111
9112 if ([UNIVERSE dockingClearanceProtocolActive])
9113 {
9114 [gui setText:DESC(@"gameoptions-docking-clearance-yes") forRow:GUI_ROW(GAME,DOCKINGCLEARANCE) align:GUI_ALIGN_CENTER];
9115 }
9116 else
9117 {
9118 [gui setText:DESC(@"gameoptions-docking-clearance-no") forRow:GUI_ROW(GAME,DOCKINGCLEARANCE) align:GUI_ALIGN_CENTER];
9119 }
9120 OO_SETACCESSCONDITIONFORROW(!startingGame, GUI_ROW(GAME,DOCKINGCLEARANCE));
9121
9122 // Back menu option
9123 [gui setText:DESC(@"gui-back") forRow:GUI_ROW(GAME,BACK) align:GUI_ALIGN_CENTER];
9124 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,BACK)];
9125
9126 [gui setSelectableRange:NSMakeRange(first_sel_row, GUI_ROW_GAMEOPTIONS_END_OF_LIST)];
9127 [gui setSelectedRow: first_sel_row];
9128
9129 [gui setShowTextCursor:NO];
9130 [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"paused_overlay"];
9131 [gui setBackgroundTextureKey:@"settings"];
9132 }
9133 /* ends */
9134
9135 [self setShowDemoShips:NO];
9136 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
9137}
9138
9139
9140- (void) setGuiToLoadSaveScreen
9141{
9142 BOOL gamePaused = [[UNIVERSE gameController] isGamePaused];
9143 BOOL canLoadOrSave = NO;
9144 MyOpenGLView *gameView = [UNIVERSE gameView];
9145 OOGUIScreenID oldScreen = gui_screen;
9146
9147 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
9148
9149 gui_screen = GUI_SCREEN_OPTIONS;
9150
9151 if ([self status] == STATUS_DOCKED)
9152 {
9153 if ([self dockedStation] == nil) [self setDockedAtMainStation];
9154 canLoadOrSave = (([self dockedStation] == [UNIVERSE station] || [[self dockedStation] allowsSaving]) && !([[UNIVERSE sun] goneNova] || [[UNIVERSE sun] willGoNova]));
9155 }
9156
9157 BOOL canQuickSave = (canLoadOrSave && ([[gameView gameController] playerFileToLoad] != nil));
9158
9159 // GUI stuff
9160 {
9161 GuiDisplayGen* gui = [UNIVERSE gui];
9162 GUI_ROW_INIT(gui);
9163
9164 int first_sel_row = (canLoadOrSave)? GUI_ROW(,SAVE) : GUI_ROW(,GAMEOPTIONS);
9165 if (canQuickSave)
9166 first_sel_row = GUI_ROW(,QUICKSAVE);
9167
9168 [gui clear];
9169 [gui setTitle:[NSString stringWithFormat:DESC(@"status-commander-@"), [self commanderName]]]; //Same title as status screen.
9170
9171 [gui setText:DESC(@"options-quick-save") forRow:GUI_ROW(,QUICKSAVE) align:GUI_ALIGN_CENTER];
9172 if (canQuickSave)
9173 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,QUICKSAVE)];
9174 else
9175 [gui setColor:[OOColor grayColor] forRow:GUI_ROW(,QUICKSAVE)];
9176
9177 [gui setText:DESC(@"options-save-commander") forRow:GUI_ROW(,SAVE) align:GUI_ALIGN_CENTER];
9178 [gui setText:DESC(@"options-load-commander") forRow:GUI_ROW(,LOAD) align:GUI_ALIGN_CENTER];
9179 if (canLoadOrSave)
9180 {
9181 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,SAVE)];
9182 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,LOAD)];
9183 }
9184 else
9185 {
9186 [gui setColor:[OOColor grayColor] forRow:GUI_ROW(,SAVE)];
9187 [gui setColor:[OOColor grayColor] forRow:GUI_ROW(,LOAD)];
9188 }
9189
9190 [gui setText:DESC(@"options-return-to-menu") forRow:GUI_ROW(,BEGIN_NEW) align:GUI_ALIGN_CENTER];
9191 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,BEGIN_NEW)];
9192
9193 [gui setText:DESC(@"options-game-options") forRow:GUI_ROW(,GAMEOPTIONS) align:GUI_ALIGN_CENTER];
9194 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,GAMEOPTIONS)];
9195
9196#if OOLITE_SDL
9197 // GNUstep needs a quit option at present (no Cmd-Q) but
9198 // doesn't need speech.
9199
9200 // quit menu option
9201 [gui setText:DESC(@"options-exit-game") forRow:GUI_ROW(,QUIT) align:GUI_ALIGN_CENTER];
9202 [gui setKey:GUI_KEY_OK forRow:GUI_ROW(,QUIT)];
9203#endif
9204
9205 [gui setSelectableRange:NSMakeRange(first_sel_row, GUI_ROW_OPTIONS_END_OF_LIST)];
9206
9207 if (gamePaused || (!canLoadOrSave && [self status] == STATUS_DOCKED))
9208 {
9209 [gui setSelectedRow: GUI_ROW(,GAMEOPTIONS)];
9210 }
9211 else
9212 {
9213 [gui setSelectedRow: first_sel_row];
9214 }
9215
9216 [gui setShowTextCursor:NO];
9217
9218 if ([gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"paused_overlay"] && [UNIVERSE pauseMessageVisible])
9219 [[UNIVERSE messageGUI] clear];
9220 // Graphically, this screen is analogous to the various settings screens
9221 [gui setBackgroundTextureKey:@"settings"];
9222 }
9223 /* ends */
9224
9225 [[UNIVERSE gameView] clearMouse];
9226
9227 [self setShowDemoShips:NO];
9228
9229 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
9230
9231 if (gamePaused)
9232 {
9233 [[UNIVERSE messageGUI] clear];
9234 NSString *pauseKey = [PLAYER keyBindingDescription2:@"key_pausebutton"];
9235 [UNIVERSE addMessage:OOExpandKey(@"game-paused-docked", pauseKey) forCount:1.0 forceDisplay:YES];
9236 }
9237
9238 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
9239}
9240
9241
9242static NSString *last_outfitting_key=nil;
9243
9244
9245- (void) highlightEquipShipScreenKey:(NSString *)key
9246{
9247 int i=0;
9248 OOGUIRow row;
9249 NSString *otherKey = @"";
9250 GuiDisplayGen *gui = [UNIVERSE gui];
9251 [last_outfitting_key release];
9252 last_outfitting_key = [key copy];
9253 [self setGuiToEquipShipScreen:-1];
9254 key = last_outfitting_key;
9255 // TODO: redo the equipShipScreen in a way that isn't broken. this whole method 'works'
9256 // based on the way setGuiToEquipShipScreen 'worked' on 20090913 - Kaks
9257
9258 // setGuiToEquipShipScreen doesn't take a page number, it takes an offset from the beginning
9259 // of the dictionary, the first line will show the key at that offset...
9260
9261 // try the last page first - 10 pages max.
9262 while (otherKey)
9263 {
9264 [self setGuiToEquipShipScreen:i];
9265 for (row = GUI_ROW_EQUIPMENT_START;row<=GUI_MAX_ROWS_EQUIPMENT+2;row++)
9266 {
9267 otherKey = [gui keyForRow:row];
9268 if (!otherKey)
9269 {
9270 [self setGuiToEquipShipScreen:0];
9271 return;
9272 }
9273 if ([otherKey isEqualToString:key])
9274 {
9275 [gui setSelectedRow:row];
9277 return;
9278 }
9279 }
9280 if ([otherKey hasPrefix:@"More:"])
9281 {
9282 i = [[otherKey componentsSeparatedByString:@":"] oo_intAtIndex:1];
9283 }
9284 else
9285 {
9286 [self setGuiToEquipShipScreen:0];
9287 return;
9288 }
9289 }
9290}
9291
9292
9293- (OOWeaponFacingSet) availableFacings
9294{
9296 NSDictionary *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]];
9297 unsigned available_facings = [shipyardInfo oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]]; // use defaults explicitly
9298
9299 return available_facings & VALID_WEAPON_FACINGS;
9300}
9301
9302
9303- (void) setGuiToEquipShipScreen:(int)skipParam selectingFacingFor:(NSString *)eqKeyForSelectFacing
9304{
9305 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
9306
9307 missiles = [self countMissiles];
9308 OOEntityStatus searchStatus; // use STATUS_TEST, STATUS_DEAD & STATUS_ACTIVE
9309 NSString *showKey = nil;
9310 unsigned skip;
9311
9312 if (skipParam < 0)
9313 {
9314 skip = 0;
9315 searchStatus = STATUS_TEST;
9316 }
9317 else
9318 {
9319 skip = skipParam;
9320 searchStatus = STATUS_ACTIVE;
9321 }
9322
9323 // don't show a "Back" item if we're only skipping one item - just show the item
9324 if (skip == 1)
9325 skip = 0;
9326
9327 double priceFactor = 1.0;
9328 OOTechLevelID techlevel = [[UNIVERSE currentSystemData] oo_intForKey:KEY_TECHLEVEL];
9329
9330 StationEntity *dockedStation = [self dockedStation];
9331 if (dockedStation)
9332 {
9333 priceFactor = [dockedStation equipmentPriceFactor];
9334 if ([dockedStation equivalentTechLevel] != NSNotFound)
9335 techlevel = [dockedStation equivalentTechLevel];
9336 }
9337
9338 // build an array of all equipment - and take away that which has been bought (or is not permitted)
9339 NSMutableArray *equipmentAllowed = [NSMutableArray array];
9340
9341 // find options that agree with this ship
9343 NSDictionary *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]];
9344 NSMutableSet *options = [NSMutableSet setWithArray:[shipyardInfo oo_arrayForKey:KEY_OPTIONAL_EQUIPMENT]];
9345
9346 // add standard items too!
9347 [options addObjectsFromArray:[[shipyardInfo oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]];
9348
9349 unsigned i = 0;
9350 NSEnumerator *eqEnum = nil;
9351 OOEquipmentType *eqType = nil;
9352 unsigned available_facings = [shipyardInfo oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]]; // use defaults explicitly
9353
9354
9355 if (eqKeyForSelectFacing != nil) // Weapons purchase subscreen.
9356 {
9357 skip = 1; // show the back button
9358 // The 3 lines below are needed by the present GUI. TODO:create a sane GUI. Kaks - 20090915 & 201005
9359 [equipmentAllowed addObject:eqKeyForSelectFacing];
9360 [equipmentAllowed addObject:eqKeyForSelectFacing];
9361 [equipmentAllowed addObject:eqKeyForSelectFacing];
9362 }
9363 else for (eqEnum = [OOEquipmentType equipmentEnumeratorOutfitting]; (eqType = [eqEnum nextObject]); i++)
9364 {
9365 NSString *eqKey = [eqType identifier];
9366 OOTechLevelID minTechLevel = [eqType effectiveTechLevel];
9367
9368 // set initial availability to NO
9369 BOOL isOK = NO;
9370
9371 // check special availability
9372 if ([eqType isAvailableToAll]) [options addObject:eqKey];
9373
9374 // if you have a damaged system you can get it repaired at a tech level one less than that required to buy it
9375 if (minTechLevel != 0 && [self hasEquipmentItem:[eqType damagedIdentifier]]) minTechLevel--;
9376
9377 // reduce the minimum techlevel occasionally as a bonus..
9378 if (techlevel < minTechLevel && techlevel + 3 > minTechLevel)
9379 {
9380 unsigned day = i * 13 + (unsigned)floor([UNIVERSE getTime] / 86400.0);
9381 unsigned char dayRnd = (day & 0xff) ^ (unsigned char)system_id;
9382 OOTechLevelID originalMinTechLevel = minTechLevel;
9383
9384 while (minTechLevel > 0 && minTechLevel > originalMinTechLevel - 3 && !(dayRnd & 7)) // bargain tech days every 1/8 days
9385 {
9386 dayRnd = dayRnd >> 2;
9387 minTechLevel--; // occasional bonus items according to TL
9388 }
9389 }
9390
9391 // check initial availability against options AND standard extras
9392 if ([options containsObject:eqKey])
9393 {
9394 isOK = YES;
9395 [options removeObject:eqKey];
9396 }
9397
9398 if (isOK)
9399 {
9400 if (techlevel < minTechLevel) isOK = NO;
9401 if (![self canAddEquipment:eqKey inContext:@"purchase"]) isOK = NO;
9402 if (available_facings == 0 && [eqType isPrimaryWeapon]) isOK = NO;
9403 if (isOK) [equipmentAllowed addObject:eqKey];
9404 }
9405
9406 if (searchStatus == STATUS_DEAD && isOK)
9407 {
9408 showKey = eqKey;
9409 searchStatus = STATUS_ACTIVE;
9410 }
9411 if (searchStatus == STATUS_TEST)
9412 {
9413 if (isOK) showKey = eqKey;
9414 if ([eqKey isEqualToString:last_outfitting_key])
9415 searchStatus = isOK ? STATUS_ACTIVE : STATUS_DEAD;
9416 }
9417 }
9418 if (searchStatus != STATUS_TEST && showKey != nil)
9419 {
9420 [last_outfitting_key release];
9421 last_outfitting_key = [showKey copy];
9422 }
9423
9424 // GUI stuff
9425 {
9426 GuiDisplayGen *gui = [UNIVERSE gui];
9428 OOGUIRow row = start_row;
9429 unsigned facing_count = 0;
9430 BOOL displayRow = YES;
9431 BOOL weaponMounted = NO;
9432 BOOL guiChanged = (gui_screen != GUI_SCREEN_EQUIP_SHIP);
9433
9434 gui_screen = GUI_SCREEN_EQUIP_SHIP;
9435
9436 [gui clearAndKeepBackground:!guiChanged];
9437 [gui setTitle:DESC(@"equip-title")];
9438
9439 [gui setColor:[gui colorFromSetting:kGuiEquipmentCashColor defaultValue:nil] forRow: GUI_ROW_EQUIPMENT_CASH];
9440 [gui setText:OOExpandKey(@"equip-cash-value", credits) forRow:GUI_ROW_EQUIPMENT_CASH];
9441
9442 OOGUITabSettings tab_stops;
9443 tab_stops[0] = 0;
9444 tab_stops[1] = -360;
9445 tab_stops[2] = -480;
9446 [gui overrideTabs:tab_stops from:kGuiEquipmentTabs length:3];
9447 [gui setTabStops:tab_stops];
9448
9449 unsigned n_rows = GUI_MAX_ROWS_EQUIPMENT;
9450 NSUInteger count = [equipmentAllowed count];
9451
9452 if (count > 0)
9453 {
9454 if (skip > 0) // lose the first row to Back <--
9455 {
9456 unsigned previous;
9457
9458 if (count <= n_rows || skip < n_rows)
9459 previous = 0; // single page
9460 else
9461 {
9462 previous = skip - (n_rows - 2); // multi-page.
9463 if (previous < 2)
9464 previous = 0; // if only one previous item, just show it
9465 }
9466
9467 if (eqKeyForSelectFacing != nil)
9468 {
9469 previous = 0;
9470 // keep weapon selected if we go back.
9471 [gui setKey:[NSString stringWithFormat:@"More:%d:%@", previous, eqKeyForSelectFacing] forRow:row];
9472 }
9473 else
9474 {
9475 [gui setKey:[NSString stringWithFormat:@"More:%d", previous] forRow:row];
9476 }
9477 [gui setColor:[gui colorFromSetting:kGuiEquipmentScrollColor defaultValue:[OOColor greenColor]] forRow:row];
9478 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @"", @" <-- ", nil] forRow:row];
9479 row++;
9480 }
9481
9482 for (i = skip; i < count && (row - start_row < (OOGUIRow)n_rows); i++)
9483 {
9484 NSString *eqKey = [equipmentAllowed oo_stringAtIndex:i];
9486 OOCreditsQuantity pricePerUnit = [eqInfo price];
9487 NSString *desc = [NSString stringWithFormat:@" %@ ", [eqInfo name]];
9488 NSString *eq_key_damaged = [eqInfo damagedIdentifier];
9489 double price;
9490
9491 OOColor *dispCol = [eqInfo displayColor];
9492 if (dispCol == nil) dispCol = [gui colorFromSetting:kGuiEquipmentOptionColor defaultValue:nil];
9493 [gui setColor:dispCol forRow:row];
9494
9495 if ([eqKey isEqual:@"EQ_FUEL"])
9496 {
9497 price = (PLAYER_MAX_FUEL - fuel) * pricePerUnit * [self fuelChargeRate];
9498 }
9499 else if ([eqKey isEqualToString:@"EQ_RENOVATION"])
9500 {
9501 price = [self renovationCosts];
9502 [gui setColor:[gui colorFromSetting:kGuiEquipmentRepairColor defaultValue:[OOColor orangeColor]] forRow:row];
9503 }
9504 else
9505 {
9506 price = pricePerUnit;
9507 }
9508
9509 price = [self adjustPriceByScriptForEqKey:eqKey withCurrent:price];
9510
9511 price *= priceFactor; // increased prices at some stations
9512
9513 NSUInteger installTime = [eqInfo installTime];
9514 if (installTime == 0)
9515 {
9516 installTime = 600 + price;
9517 }
9518 // is this item damaged?
9519 if ([self hasEquipmentItem:eq_key_damaged])
9520 {
9521 desc = [NSString stringWithFormat:DESC(@"equip-repair-@"), desc];
9522 price /= 2.0;
9523 installTime = [eqInfo repairTime];
9524 if (installTime == 0)
9525 {
9526 installTime = 600 + price;
9527 }
9528 [gui setColor:[gui colorFromSetting:kGuiEquipmentRepairColor defaultValue:[OOColor orangeColor]] forRow:row];
9529
9530 }
9531
9532 NSString *timeString = [UNIVERSE shortTimeDescription:installTime];
9533 NSString *priceString = [NSString stringWithFormat:@" %@ ", OOCredits(price)];
9534
9535 if ([eqKeyForSelectFacing isEqualToString:eqKey])
9536 {
9537 // Weapons purchase subscreen.
9538 while (facing_count < 5)
9539 {
9540 NSUInteger multiplier = 1;
9541 switch (facing_count)
9542 {
9543 case 0:
9544 break;
9545
9546 case 1:
9547 displayRow = available_facings & WEAPON_FACING_FORWARD;
9548 desc = FORWARD_FACING_STRING;
9549 weaponMounted = !isWeaponNone(forward_weapon_type);
9550 if (_multiplyWeapons)
9551 {
9552 multiplier = [forwardWeaponOffset count];
9553 }
9554 break;
9555
9556 case 2:
9557 displayRow = available_facings & WEAPON_FACING_AFT;
9558 desc = AFT_FACING_STRING;
9559 weaponMounted = !isWeaponNone(aft_weapon_type);
9560 if (_multiplyWeapons)
9561 {
9562 multiplier = [aftWeaponOffset count];
9563 }
9564 break;
9565
9566 case 3:
9567 displayRow = available_facings & WEAPON_FACING_PORT;
9568 desc = PORT_FACING_STRING;
9569 weaponMounted = !isWeaponNone(port_weapon_type);
9570 if (_multiplyWeapons)
9571 {
9572 multiplier = [portWeaponOffset count];
9573 }
9574 break;
9575
9576 case 4:
9577 displayRow = available_facings & WEAPON_FACING_STARBOARD;
9579 weaponMounted = !isWeaponNone(starboard_weapon_type);
9580 if (_multiplyWeapons)
9581 {
9582 multiplier = [starboardWeaponOffset count];
9583 }
9584 break;
9585 }
9586
9587 if(weaponMounted)
9588 {
9589 [gui setColor:[gui colorFromSetting:kGuiEquipmentLaserFittedColor defaultValue:[OOColor colorWithRed:0.0f green:0.6f blue:0.0f alpha:1.0f]] forRow:row];
9590 }
9591 else
9592 {
9593 [gui setColor:[gui colorFromSetting:kGuiEquipmentLaserColor defaultValue:[OOColor greenColor]] forRow:row];
9594 }
9595 if (displayRow) // Always true for the first pass. The first pass is used to display the name of the weapon being purchased.
9596 {
9597
9598 priceString = [NSString stringWithFormat:@" %@ ", OOCredits(price*multiplier)];
9599
9600 [gui setKey:eqKey forRow:row];
9601 [gui setArray:[NSArray arrayWithObjects:desc, (facing_count > 0 ? priceString : (NSString *)@""), timeString, nil] forRow:row];
9602 row++;
9603 }
9604 facing_count++;
9605 }
9606 }
9607 else
9608 {
9609 // Normal equipment list.
9610 [gui setKey:eqKey forRow:row];
9611 // check if the hidevalues property has been set
9612 if (![eqInfo hideValues])
9613 {
9614 [gui setArray:[NSArray arrayWithObjects:desc, priceString, timeString, nil] forRow:row];
9615 }
9616 else
9617 {
9618 // if so, only output the description
9619 [gui setArray:[NSArray arrayWithObjects:desc, nil] forRow:row];
9620 }
9621 row++;
9622 }
9623 }
9624
9625 if (i < count)
9626 {
9627 // just overwrite the last item :-)
9628 [gui setColor:[gui colorFromSetting:kGuiEquipmentScrollColor defaultValue:[OOColor greenColor]] forRow:row-1];
9629 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @"", @" --> ", nil] forRow:row - 1];
9630 [gui setKey:[NSString stringWithFormat:@"More:%d", i - 1] forRow:row - 1];
9631 }
9632
9633 [gui setSelectableRange:NSMakeRange(start_row,row - start_row)];
9634
9635 if ([gui selectedRow] != start_row)
9636 [gui setSelectedRow:start_row];
9637
9638 if (eqKeyForSelectFacing != nil)
9639 {
9640 [gui setSelectedRow:start_row + 1];
9641 [self showInformationForSelectedUpgradeWithFormatString:DESC(@"@-select-where-to-install")];
9642 }
9643 else
9644 {
9645 [self showInformationForSelectedUpgrade];
9646 }
9647 }
9648 else
9649 {
9650 [gui setText:DESC(@"equip-no-equipment-available-for-purchase") forRow:GUI_ROW_NO_SHIPS align:GUI_ALIGN_CENTER];
9651 [gui setColor:[gui colorFromSetting:kGuiEquipmentUnavailableColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_NO_SHIPS];
9652
9653 [gui setSelectableRange:NSMakeRange(0,0)];
9654 [gui setNoSelectedRow];
9655 [self showInformationForSelectedUpgrade];
9656 }
9657
9658 [gui setShowTextCursor:NO];
9659
9660 // TODO: split the mount_weapon sub-screen into a separate screen, and use it for pylon mounted wepons as well?
9661 if (guiChanged)
9662 {
9663 [gui setForegroundTextureKey:@"docked_overlay"];
9664 NSDictionary *background = [UNIVERSE screenTextureDescriptorForKey:@"equip_ship"];
9665 [self setEquipScreenBackgroundDescriptor:background];
9666 [gui setBackgroundTextureDescriptor:background];
9667 }
9668 else if (eqKeyForSelectFacing != nil) // weapon purchase
9669 {
9670 NSDictionary *bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"mount_weapon"];
9671 if (bgDescriptor != nil) [gui setBackgroundTextureDescriptor:bgDescriptor];
9672 }
9673 else // Returning from a weapon purchase. (Also called, redundantly, when paging)
9674 {
9675 [gui setBackgroundTextureDescriptor:[self equipScreenBackgroundDescriptor]];
9676 }
9677 }
9678 /* ends */
9679
9680 chosen_weapon_facing = WEAPON_FACING_NONE;
9681
9682 [self setShowDemoShips:NO];
9683 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
9684}
9685
9686
9687- (void) setGuiToEquipShipScreen:(int)skip
9688{
9689 [self setGuiToEquipShipScreen:skip selectingFacingFor:nil];
9690}
9691
9692
9693- (void) showInformationForSelectedUpgrade
9694{
9695 [self showInformationForSelectedUpgradeWithFormatString:nil];
9696}
9697
9698
9699- (void) showInformationForSelectedUpgradeWithFormatString:(NSString *)formatString
9700{
9701 GuiDisplayGen* gui = [UNIVERSE gui];
9702 NSString* eqKey = [gui selectedRowKey];
9703 int i;
9704
9705 OOColor *descColor = [gui colorFromSetting:kGuiEquipmentDescriptionColor defaultValue:[OOColor greenColor]];
9706 for (i = GUI_ROW_EQUIPMENT_DETAIL; i < GUI_MAX_ROWS; i++)
9707 {
9708 [gui setText:@"" forRow:i];
9709 [gui setColor:descColor forRow:i];
9710 }
9711 if (eqKey)
9712 {
9713 if (![eqKey hasPrefix:@"More:"])
9714 {
9716 NSString* eq_key_damaged = [NSString stringWithFormat:@"%@_DAMAGED", eqKey];
9718 if ([self hasEquipmentItem:eq_key_damaged])
9719 {
9720 desc = [NSString stringWithFormat:DESC(@"upgradeinfo-@-price-is-for-repairing"), desc];
9721 }
9722 else
9723 {
9724 if([eqKey hasSuffix:@"ENERGY_UNIT"] && ([self hasEquipmentItem:@"EQ_ENERGY_UNIT_DAMAGED"] || [self hasEquipmentItem:@"EQ_ENERGY_UNIT"] || [self hasEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT_DAMAGED"]))
9725 desc = [NSString stringWithFormat:DESC(@"@-will-replace-other-energy"), desc];
9726 if (weight > 0) desc = [NSString stringWithFormat:DESC(@"upgradeinfo-@-weight-d-of-equipment"), desc, weight];
9727 }
9728 if (formatString) desc = [NSString stringWithFormat:formatString, desc];
9729 [gui addLongText:desc startingAtRow:GUI_ROW_EQUIPMENT_DETAIL align:GUI_ALIGN_LEFT];
9730 }
9731 }
9732}
9733
9734
9735- (void) setGuiToInterfacesScreen:(int)skip
9736{
9737 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
9738 if (gui_screen != GUI_SCREEN_INTERFACES)
9739 {
9740 [self noteGUIWillChangeTo:GUI_SCREEN_INTERFACES];
9741 }
9742
9743 // build an array of available interfaces
9744 NSDictionary *interfaces = [[self dockedStation] localInterfaces];
9745 NSArray *interfaceKeys = [interfaces keysSortedByValueUsingSelector:@selector(interfaceCompare:)]; // sorts by category, then title
9746 int i;
9747
9748 OOGUIScreenID oldScreen = gui_screen;
9749
9750 // GUI stuff
9751 {
9752 GuiDisplayGen *gui = [UNIVERSE gui];
9754 OOGUIRow row = start_row;
9755 BOOL guiChanged = (gui_screen != GUI_SCREEN_INTERFACES);
9756
9757 [gui clearAndKeepBackground:!guiChanged];
9758 [gui setTitle:DESC(@"interfaces-title")];
9759
9760 gui_screen = GUI_SCREEN_INTERFACES;
9761
9762 OOGUITabSettings tab_stops;
9763 tab_stops[0] = 0;
9764 tab_stops[1] = -480;
9765 [gui overrideTabs:tab_stops from:kGuiInterfaceTabs length:2];
9766 [gui setTabStops:tab_stops];
9767
9768 unsigned n_rows = GUI_MAX_ROWS_INTERFACES;
9769 NSUInteger count = [interfaceKeys count];
9770
9771 if (count > 0)
9772 {
9773 if (skip > 0) // lose the first row to Back <--
9774 {
9775 unsigned previous;
9776
9777 if (count <= n_rows || skip < (NSInteger)n_rows)
9778 {
9779 previous = 0; // single page
9780 }
9781 else
9782 {
9783 previous = skip - (n_rows - 2); // multi-page.
9784 if (previous < 2)
9785 {
9786 previous = 0; // if only one previous item, just show it
9787 }
9788 }
9789
9790 [gui setKey:[NSString stringWithFormat:@"More:%d", previous] forRow:row];
9791 [gui setColor:[gui colorFromSetting:kGuiInterfaceScrollColor defaultValue:[OOColor greenColor]] forRow:row];
9792 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:row];
9793 row++;
9794 }
9795
9796 for (i = skip; i < (NSInteger)count && (row - start_row < (OOGUIRow)n_rows); i++)
9797 {
9798 NSString *interfaceKey = [interfaceKeys objectAtIndex:i];
9799 OOJSInterfaceDefinition *definition = [interfaces objectForKey:interfaceKey];
9800
9801 [gui setColor:[gui colorFromSetting:kGuiInterfaceEntryColor defaultValue:nil] forRow:row];
9802 [gui setKey:interfaceKey forRow:row];
9803 [gui setArray:[NSArray arrayWithObjects:[definition title],[definition category], nil] forRow:row];
9804
9805 row++;
9806 }
9807
9808 if (i < (NSInteger)count)
9809 {
9810 // just overwrite the last item :-)
9811 [gui setColor:[gui colorFromSetting:kGuiInterfaceScrollColor defaultValue:[OOColor greenColor]] forRow:row - 1];
9812 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:row - 1];
9813 [gui setKey:[NSString stringWithFormat:@"More:%d", i - 1] forRow:row - 1];
9814 }
9815
9816 [gui setSelectableRange:NSMakeRange(start_row,row - start_row)];
9817
9818 if ([gui selectedRow] != start_row)
9819 {
9820 [gui setSelectedRow:start_row];
9821 }
9822
9823 [self showInformationForSelectedInterface];
9824 }
9825 else
9826 {
9827 [gui setText:DESC(@"interfaces-no-interfaces-available-for-use") forRow:GUI_ROW_NO_INTERFACES align:GUI_ALIGN_LEFT];
9828 [gui setColor:[gui colorFromSetting:kGuiInterfaceNoneColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_NO_INTERFACES];
9829
9830 [gui setSelectableRange:NSMakeRange(0,0)];
9831 [gui setNoSelectedRow];
9832
9833 }
9834
9835 [gui setShowTextCursor:NO];
9836
9837 NSString *desc = [NSString stringWithFormat:DESC(@"interfaces-for-ship-@-and-station-@"), [self displayName], [[self dockedStation] displayName]];
9838 [gui setColor:[gui colorFromSetting:kGuiInterfaceHeadingColor defaultValue:nil] forRow:GUI_ROW_INTERFACES_HEADING];
9839 [gui setText:desc forRow:GUI_ROW_INTERFACES_HEADING];
9840
9841
9842 if (guiChanged)
9843 {
9844 [gui setForegroundTextureKey:@"docked_overlay"];
9845 NSDictionary *background = [UNIVERSE screenTextureDescriptorForKey:@"interfaces"];
9846 [gui setBackgroundTextureDescriptor:background];
9847 }
9848 }
9849 /* ends */
9850
9851 [self setShowDemoShips:NO];
9852
9853 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
9854
9855 [self setShowDemoShips:NO];
9856 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
9857
9858}
9859
9860
9861- (void) showInformationForSelectedInterface
9862{
9863 GuiDisplayGen* gui = [UNIVERSE gui];
9864 NSString* interfaceKey = [gui selectedRowKey];
9865
9866 int i;
9867
9868 for (i = GUI_ROW_EQUIPMENT_DETAIL; i < GUI_MAX_ROWS; i++)
9869 {
9870 [gui setText:@"" forRow:i];
9871 [gui setColor:[gui colorFromSetting:kGuiInterfaceDescriptionColor defaultValue:[OOColor greenColor]] forRow:i];
9872 }
9873
9874 if (interfaceKey && ![interfaceKey hasPrefix:@"More:"])
9875 {
9876 NSDictionary *interfaces = [[self dockedStation] localInterfaces];
9877 OOJSInterfaceDefinition *definition = [interfaces objectForKey:interfaceKey];
9878 if (definition)
9879 {
9880 [gui addLongText:[definition summary] startingAtRow:GUI_ROW_INTERFACES_DETAIL align:GUI_ALIGN_LEFT];
9881 }
9882 }
9883
9884}
9885
9886
9887- (void) activateSelectedInterface
9888{
9889 GuiDisplayGen* gui = [UNIVERSE gui];
9890 NSString* key = [gui selectedRowKey];
9891
9892 if ([key hasPrefix:@"More:"])
9893 {
9894 int from_item = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1];
9895 [self setGuiToInterfacesScreen:from_item];
9896
9897 if ([gui selectedRow] < 0)
9898 [gui setSelectedRow:GUI_ROW_INTERFACES_START];
9899 if (from_item == 0)
9900 [gui setSelectedRow:GUI_ROW_INTERFACES_START + GUI_MAX_ROWS_INTERFACES - 1];
9901 [self showInformationForSelectedInterface];
9902
9903
9904 return;
9905 }
9906
9907 NSDictionary *interfaces = [[self dockedStation] localInterfaces];
9908 OOJSInterfaceDefinition *definition = [interfaces objectForKey:key];
9909 if (definition)
9910 {
9911 [[UNIVERSE gameView] clearKeys];
9912 [definition runCallback:key];
9913 }
9914 else
9915 {
9916 OOLog(@"interface.missingCallback", @"Unable to find callback definition for key %@", key);
9917 }
9918}
9919
9920
9921- (void) setupStartScreenGui
9922{
9923 GuiDisplayGen *gui = [UNIVERSE gui];
9924 NSString *text = nil;
9925
9926 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
9927
9928 [gui clear];
9929
9930 [gui setTitle:@"Oolite"];
9931
9932 text = DESC(@"game-copyright");
9933 [gui setText:text forRow:15 align:GUI_ALIGN_CENTER];
9934 [gui setColor:[OOColor whiteColor] forRow:15];
9935
9936 text = DESC(@"theme-music-credit");
9937 [gui setText:text forRow:17 align:GUI_ALIGN_CENTER];
9938 [gui setColor:[OOColor grayColor] forRow:17];
9939
9940 int initialRow = 22;
9941 int row = initialRow;
9942
9943 text = DESC(@"oolite-start-option-1");
9944 [gui setText:text forRow:row align:GUI_ALIGN_CENTER];
9945 [gui setColor:[OOColor yellowColor] forRow:row];
9946 [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row];
9947
9948 ++row;
9949
9950 text = DESC(@"oolite-start-option-2");
9951 [gui setText:text forRow:row align:GUI_ALIGN_CENTER];
9952 [gui setColor:[OOColor yellowColor] forRow:row];
9953 [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row];
9954
9955 ++row;
9956
9957 text = DESC(@"oolite-start-option-3");
9958 [gui setText:text forRow:row align:GUI_ALIGN_CENTER];
9959 [gui setColor:[OOColor yellowColor] forRow:row];
9960 [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row];
9961
9962 ++row;
9963
9964 text = DESC(@"oolite-start-option-4");
9965 [gui setText:text forRow:row align:GUI_ALIGN_CENTER];
9966 [gui setColor:[OOColor yellowColor] forRow:row];
9967 [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row];
9968
9969 ++row;
9970
9971 text = DESC(@"oolite-start-option-5");
9972 [gui setText:text forRow:row align:GUI_ALIGN_CENTER];
9973 [gui setColor:[OOColor yellowColor] forRow:row];
9974 [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row];
9975
9976 ++row;
9977
9978 text = DESC(@"oolite-start-option-6");
9979 [gui setText:text forRow:row align:GUI_ALIGN_CENTER];
9980 [gui setColor:[OOColor yellowColor] forRow:row];
9981 [gui setKey:[NSString stringWithFormat:@"Start:%d", row] forRow:row];
9982
9983
9984 [gui setSelectableRange:NSMakeRange(initialRow, row - initialRow + 1)];
9985 [gui setSelectedRow:initialRow];
9986
9987 [gui setBackgroundTextureKey:@"intro"];
9988
9989}
9990
9995- (void) setGuiToIntroFirstGo:(BOOL)justCobra
9996{
9997 NSString *text = nil;
9998 GuiDisplayGen *gui = [UNIVERSE gui];
9999 OOGUIRow msgLine = 2;
10000
10001 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO];
10002 [[UNIVERSE gameView] clearMouse];
10003 [[UNIVERSE gameView] clearKeys];
10004
10005
10006 if (justCobra)
10007 {
10008 [UNIVERSE removeDemoShips];
10009 [[OOCacheManager sharedCache] flush]; // At first startup, a lot of stuff is cached
10010 }
10011
10012 if (justCobra)
10013 {
10014 [self setupStartScreenGui];
10015
10016 // check for error messages from Resource Manager
10017 //[ResourceManager paths]; done in Universe already
10018 NSString *errors = [ResourceManager errors];
10019 if (errors != nil)
10020 {
10021 OOGUIRow ms_start = msgLine;
10022 OOGUIRow i = msgLine = [gui addLongText:errors startingAtRow:ms_start align:GUI_ALIGN_LEFT];
10023 for (i-- ; i >= ms_start ; i--) [gui setColor:[OOColor redColor] forRow:i];
10024 msgLine++;
10025 }
10026
10027 // check for messages from OXPs
10028 NSArray *OXPsWithMessages = [ResourceManager OXPsWithMessagesFound];
10029 if ([OXPsWithMessages count] > 0)
10030 {
10031 NSString *messageToDisplay = @"";
10032
10033 // Show which OXPs were found with messages, but don't spam the screen if more than
10034 // a certain number of them exist
10035 if ([OXPsWithMessages count] < 5)
10036 {
10037 NSString *messageSourceList = [OXPsWithMessages componentsJoinedByString:@", "];
10038 messageToDisplay = OOExpandKey(@"oxp-containing-messages-list", messageSourceList);
10039 } else {
10040 messageToDisplay = OOExpandKey(@"oxp-containing-messages-found");
10041 }
10042
10043 OOGUIRow ms_start = msgLine;
10044 OOGUIRow i = msgLine = [gui addLongText:messageToDisplay startingAtRow:ms_start align:GUI_ALIGN_LEFT];
10045 for (i--; i >= ms_start; i--)
10046 {
10047 [gui setColor:[OOColor orangeColor] forRow:i];
10048 }
10049 msgLine++;
10050 }
10051
10052 // check for messages from the command line
10053 NSArray* arguments = [[NSProcessInfo processInfo] arguments];
10054 unsigned i;
10055 for (i = 0; i < [arguments count]; i++)
10056 {
10057 if (([[arguments objectAtIndex:i] isEqual:@"-message"])&&(i < [arguments count] - 1))
10058 {
10059 OOGUIRow ms_start = msgLine;
10060 NSString* message = [arguments oo_stringAtIndex:i + 1];
10061 OOGUIRow i = msgLine = [gui addLongText:message startingAtRow:ms_start align:GUI_ALIGN_CENTER];
10062 for (i-- ; i >= ms_start; i--)
10063 {
10064 [gui setColor:[OOColor magentaColor] forRow:i];
10065 }
10066 }
10067 if ([[arguments objectAtIndex:i] isEqual:@"-showversion"])
10068 {
10069 OOGUIRow ms_start = msgLine;
10070 NSString *version = @"Version " @OO_VERSION_FULL;
10071 OOGUIRow i = msgLine = [gui addLongText:version startingAtRow:ms_start align:GUI_ALIGN_CENTER];
10072 for (i-- ; i >= ms_start; i--)
10073 {
10074 [gui setColor:[OOColor magentaColor] forRow:i];
10075 }
10076 }
10077 }
10078 }
10079 else
10080 {
10081 [gui clear];
10082
10083 text = DESC(@"oolite-ship-library-title");
10084 [gui setTitle:text];
10085
10086 text = DESC(@"oolite-ship-library-exit");
10087 [gui setText:text forRow:27 align:GUI_ALIGN_CENTER];
10088 [gui setColor:[OOColor yellowColor] forRow:27];
10089 }
10090
10091 [gui setShowTextCursor:NO];
10092
10093 [UNIVERSE setupIntroFirstGo: justCobra];
10094
10095 if (gui != nil)
10096 {
10097 gui_screen = justCobra ? GUI_SCREEN_INTRO1 : GUI_SCREEN_SHIPLIBRARY;
10098 }
10099 if ([self status] == STATUS_START_GAME)
10100 {
10102 }
10103
10104 [self setShowDemoShips:YES];
10105 if (justCobra)
10106 {
10107 [gui setBackgroundTextureKey:@"intro"];
10108 }
10109 else
10110 {
10111 [gui setBackgroundTextureKey:@"shiplibrary"];
10112 }
10113 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
10114}
10115
10116
10117
10118- (void) setGuiToOXZManager
10119{
10120
10121 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:NO];
10122 [[UNIVERSE gameView] clearMouse];
10123 [UNIVERSE removeDemoShips];
10124
10125 gui_screen = GUI_SCREEN_OXZMANAGER;
10126
10127 [[UNIVERSE gui] clearAndKeepBackground:NO];
10128
10130
10132 [[UNIVERSE gui] setBackgroundTextureKey:@"oxz-manager"];
10133 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
10134}
10135
10136
10137
10138
10139
10140- (void) noteGUIWillChangeTo:(OOGUIScreenID)toScreen
10141{
10142 JSContext *context = OOJSAcquireContext();
10143 ShipScriptEvent(context, self, "guiScreenWillChange", OOJSValueFromGUIScreenID(context, toScreen), OOJSValueFromGUIScreenID(context, gui_screen));
10144 OOJSRelinquishContext(context);
10145}
10146
10147
10148- (void) noteGUIDidChangeFrom:(OOGUIScreenID)fromScreen to:(OOGUIScreenID)toScreen
10149{
10150 [self noteGUIDidChangeFrom: fromScreen to: toScreen refresh: NO];
10151}
10152
10153
10154- (void) noteGUIDidChangeFrom:(OOGUIScreenID)fromScreen to:(OOGUIScreenID)toScreen refresh: (BOOL) refresh
10155{
10156 // No events triggered if we're changing screens while paused, or if screen never actually changed.
10157 if (fromScreen != toScreen || refresh)
10158 {
10159 // MKW - release GUI Screen ship, if we have one
10160 switch (fromScreen)
10161 {
10162 case GUI_SCREEN_SHIPYARD:
10163 case GUI_SCREEN_LOAD:
10164 case GUI_SCREEN_SAVE:
10165 [demoShip release];
10166 demoShip = nil;
10167 break;
10168 default:
10169 // Nothing
10170 break;
10171
10172 }
10173
10174 if (toScreen == GUI_SCREEN_SYSTEM_DATA)
10175 {
10176 // system data screen: ensure correct sun light color is used on miniature planet
10177 [[UNIVERSE sun] setSunColor:[OOColor colorWithDescription:[[UNIVERSE systemManager] getProperty:@"sun_color" forSystem:info_system_id inGalaxy:[self galaxyNumber]]]];
10178 }
10179 else
10180 {
10181 // any other screen: reset local sun light color
10182 [[UNIVERSE sun] setSunColor:[OOColor colorWithDescription:[[UNIVERSE systemManager] getProperty:@"sun_color" forSystem:system_id inGalaxy:[self galaxyNumber]]]];
10183 }
10184
10185 if (![[UNIVERSE gameController] isGamePaused])
10186 {
10187 JSContext *context = OOJSAcquireContext();
10188 ShipScriptEvent(context, self, "guiScreenChanged", OOJSValueFromGUIScreenID(context, toScreen), OOJSValueFromGUIScreenID(context, fromScreen));
10189 OOJSRelinquishContext(context);
10190 }
10191 }
10192}
10193
10194
10195- (void) noteViewDidChangeFrom:(OOViewID)fromView toView:(OOViewID)toView
10196{
10197 [self noteSwitchToView:toView fromView:fromView];
10198}
10199
10200
10201- (void) buySelectedItem
10202{
10203 GuiDisplayGen* gui = [UNIVERSE gui];
10204 NSString* key = [gui selectedRowKey];
10205
10206 if ([key hasPrefix:@"More:"])
10207 {
10208 int from_item = [[key componentsSeparatedByString:@":"] oo_intAtIndex:1];
10209 NSString *weaponKey = [[key componentsSeparatedByString:@":"] oo_stringAtIndex:2];
10210
10211 [self setGuiToEquipShipScreen:from_item];
10212 if (weaponKey != nil)
10213 {
10214 [self highlightEquipShipScreenKey:weaponKey];
10215 }
10216 else
10217 {
10218 if ([gui selectedRow] < 0)
10219 [gui setSelectedRow:GUI_ROW_EQUIPMENT_START];
10220 if (from_item == 0)
10221 [gui setSelectedRow:GUI_ROW_EQUIPMENT_START + GUI_MAX_ROWS_EQUIPMENT - 1];
10222 [self showInformationForSelectedUpgrade];
10223 }
10224
10225 return;
10226 }
10227
10228 NSString *itemText = [gui selectedRowText];
10229
10230 // FIXME: this is nuts, should be associating lines with keys in some sensible way. --Ahruman 20080311
10231 if ([itemText isEqual:FORWARD_FACING_STRING])
10232 chosen_weapon_facing = WEAPON_FACING_FORWARD;
10233 if ([itemText isEqual:AFT_FACING_STRING])
10234 chosen_weapon_facing = WEAPON_FACING_AFT;
10235 if ([itemText isEqual:PORT_FACING_STRING])
10236 chosen_weapon_facing = WEAPON_FACING_PORT;
10237 if ([itemText isEqual:STARBOARD_FACING_STRING])
10238 chosen_weapon_facing = WEAPON_FACING_STARBOARD;
10239
10240 OOCreditsQuantity old_credits = credits;
10242 BOOL isRepair = [self hasEquipmentItem:[eqInfo damagedIdentifier]];
10243 if ([self tryBuyingItem:key])
10244 {
10245 if (credits == old_credits)
10246 {
10247 // laser pre-purchase, or free equipment
10248 [self playMenuNavigationDown];
10249 }
10250 else
10251 {
10252 [self playBuyCommodity];
10253 }
10254
10255 if(credits != old_credits || ![key hasPrefix:@"EQ_WEAPON_"])
10256 {
10257 // adjust time before playerBoughtEquipment gets to change credits dynamically
10258 // wind the clock forward by 10 minutes plus 10 minutes for every 60 credits spent
10259 NSUInteger adjust = 0;
10260 if (isRepair)
10261 {
10262 adjust = [eqInfo repairTime];
10263 }
10264 else
10265 {
10266 adjust = [eqInfo installTime];
10267 }
10268 double time_adjust = (old_credits > credits) ? (old_credits - credits) : 0.0;
10269 [UNIVERSE forceWitchspaceEntries];
10270 if (adjust == 0)
10271 {
10272 ship_clock_adjust += time_adjust + 600.0;
10273 }
10274 else
10275 {
10276 ship_clock_adjust += (double)adjust;
10277 }
10278
10279 [self doScriptEvent:OOJSID("playerBoughtEquipment") withArguments:[NSArray arrayWithObjects:key, [NSNumber numberWithLongLong:(old_credits - credits)], nil]];
10280 if (gui_screen == GUI_SCREEN_EQUIP_SHIP) //if we haven't changed gui screen inside playerBoughtEquipment
10281 {
10282 // show any change due to playerBoughtEquipment
10283 [self setGuiToEquipShipScreen:0];
10284 // then try to go back where we were
10285 [self highlightEquipShipScreenKey:key];
10286 }
10287
10288 if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES];
10289 }
10290 }
10291 else
10292 {
10293 [self playCantBuyCommodity];
10294 }
10295}
10296
10297
10298- (OOCreditsQuantity) adjustPriceByScriptForEqKey:(NSString *)eqKey withCurrent:(OOCreditsQuantity)price
10299{
10300 NSString *condition_script = [[OOEquipmentType equipmentTypeWithIdentifier:eqKey] conditionScript];
10301 if (condition_script != nil)
10302 {
10303 OOJSScript *condScript = [UNIVERSE getConditionScript:condition_script];
10304 if (condScript != nil) // should always be non-nil, but just in case
10305 {
10306 JSContext *JScontext = OOJSAcquireContext();
10307 BOOL OK;
10308 jsval result;
10309 int32 newPrice;
10310 jsval args[] = { OOJSValueFromNativeObject(JScontext, eqKey) , JSVAL_NULL };
10311 OK = JS_NewNumberValue(JScontext, price, &args[1]);
10312
10313 if (OK)
10314 {
10315 OK = [condScript callMethod:OOJSID("updateEquipmentPrice")
10316 inContext:JScontext
10317 withArguments:args count:sizeof args / sizeof *args
10318 result:&result];
10319 }
10320
10321 if (OK)
10322 {
10323 OK = JS_ValueToInt32(JScontext, result, &newPrice);
10324 if (OK && newPrice >= 0)
10325 {
10326 price = (OOCreditsQuantity)newPrice;
10327 }
10328 }
10329 OOJSRelinquishContext(JScontext);
10330 }
10331 }
10332 return price;
10333}
10334
10335
10336- (BOOL) tryBuyingItem:(NSString *)eqKey
10337{
10338 // note this doesn't check the availability by tech-level
10340 OOCreditsQuantity pricePerUnit = [eqType price];
10341 NSString *eqKeyDamaged = [eqType damagedIdentifier];
10342 double price = pricePerUnit;
10343 double priceFactor = 1.0;
10344 OOCreditsQuantity tradeIn = 0;
10345 BOOL isRepair = NO;
10346
10347 // repairs cost 50%
10348 if ([self hasEquipmentItem:eqKeyDamaged])
10349 {
10350 price /= 2.0;
10351 isRepair = YES;
10352 }
10353
10354 if ([eqKey isEqualToString:@"EQ_RENOVATION"])
10355 {
10356 price = [self renovationCosts];
10357 }
10358
10359 price = [self adjustPriceByScriptForEqKey:eqKey withCurrent:price];
10360
10361 StationEntity *dockedStation = [self dockedStation];
10362 if (dockedStation)
10363 {
10364 priceFactor = [dockedStation equipmentPriceFactor];
10365 }
10366
10367 price *= priceFactor; // increased prices at some stations
10368
10369 if (price > credits)
10370 {
10371 return NO;
10372 }
10373
10374 if ([eqType isPrimaryWeapon])
10375 {
10376 if (chosen_weapon_facing == WEAPON_FACING_NONE)
10377 {
10378 [self setGuiToEquipShipScreen:0 selectingFacingFor:eqKey]; // reset
10379 return YES;
10380 }
10381
10383 OOWeaponType current_weapon = nil;
10384
10385 NSUInteger multiplier = 1;
10386
10387 switch (chosen_weapon_facing)
10388 {
10390 current_weapon = forward_weapon_type;
10391 forward_weapon_type = chosen_weapon;
10392 if (_multiplyWeapons)
10393 {
10394 multiplier = [forwardWeaponOffset count];
10395 }
10396 break;
10397
10398 case WEAPON_FACING_AFT:
10399 current_weapon = aft_weapon_type;
10400 aft_weapon_type = chosen_weapon;
10401 if (_multiplyWeapons)
10402 {
10403 multiplier = [aftWeaponOffset count];
10404 }
10405 break;
10406
10407 case WEAPON_FACING_PORT:
10408 current_weapon = port_weapon_type;
10409 port_weapon_type = chosen_weapon;
10410 if (_multiplyWeapons)
10411 {
10412 multiplier = [portWeaponOffset count];
10413 }
10414 break;
10415
10417 current_weapon = starboard_weapon_type;
10418 starboard_weapon_type = chosen_weapon;
10419 if (_multiplyWeapons)
10420 {
10421 multiplier = [starboardWeaponOffset count];
10422 }
10423 break;
10424
10425 case WEAPON_FACING_NONE:
10426 break;
10427 }
10428
10429 price *= multiplier;
10430
10431 if (price > credits)
10432 {
10433 // not enough money - ensure that weapon
10434 // type is reset to what it was before
10435 // the attempt to buy took place
10436 switch (chosen_weapon_facing)
10437 {
10439 forward_weapon_type = current_weapon;
10440 break;
10441 case WEAPON_FACING_AFT:
10442 aft_weapon_type = current_weapon;
10443 break;
10444 case WEAPON_FACING_PORT:
10445 port_weapon_type = current_weapon;
10446 break;
10448 starboard_weapon_type = current_weapon;
10449 break;
10450 case WEAPON_FACING_NONE:
10451 break;
10452 }
10453 return NO;
10454 }
10455 credits -= price;
10456
10457 // Refund current_weapon
10458 if (current_weapon != nil)
10459 {
10460 tradeIn = [UNIVERSE getEquipmentPriceForKey:OOEquipmentIdentifierFromWeaponType(current_weapon)] * multiplier;
10461 }
10462
10463 [self doTradeIn:tradeIn forPriceFactor:priceFactor];
10464 // If equipped, remove damaged weapon after repairs. -- But there's no way we should get a damaged weapon. Ever.
10465 [self removeEquipmentItem:eqKeyDamaged];
10466 return YES;
10467 }
10468
10469 if ([eqType isMissileOrMine] && missiles >= max_missiles)
10470 {
10471 OOLog(@"equip.buy.mounted.failed.full", @"%@", @"rejecting missile because already full");
10472 return NO;
10473 }
10474
10475 // NSFO!
10476 //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace];
10477 //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE;
10478
10479 if ([eqKey isEqualToString:@"EQ_PASSENGER_BERTH"] && [self availableCargoSpace] < PASSENGER_BERTH_SPACE)
10480 {
10481 return NO;
10482 }
10483
10484 if ([eqKey isEqualToString:@"EQ_FUEL"])
10485 {
10486#if MASS_DEPENDENT_FUEL_PRICES
10487 OOCreditsQuantity creditsForRefuel = ([self fuelCapacity] - [self fuel]) * pricePerUnit * [self fuelChargeRate];
10488#else
10489 OOCreditsQuantity creditsForRefuel = ([self fuelCapacity] - [self fuel]) * pricePerUnit;
10490#endif
10491 if (credits >= creditsForRefuel) // Ensure we don't overflow
10492 {
10493 credits -= creditsForRefuel;
10494 fuel = [self fuelCapacity];
10495 return YES;
10496 }
10497 else
10498 {
10499 return NO;
10500 }
10501 }
10502
10503 // check energy unit replacement
10504 if ([eqKey hasSuffix:@"ENERGY_UNIT"] && [self energyUnitType] != ENERGY_UNIT_NONE)
10505 {
10506 switch ([self energyUnitType])
10507 {
10508 case ENERGY_UNIT_NAVAL :
10509 [self removeEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT"];
10510 tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_NAVAL_ENERGY_UNIT"] / 2; // 50 % refund
10511 break;
10513 [self removeEquipmentItem:@"EQ_NAVAL_ENERGY_UNIT_DAMAGED"];
10514 tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_NAVAL_ENERGY_UNIT"] / 4; // half of the working one
10515 break;
10516 case ENERGY_UNIT_NORMAL :
10517 [self removeEquipmentItem:@"EQ_ENERGY_UNIT"];
10518 tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_ENERGY_UNIT"] * 3 / 4; // 75 % refund
10519 break;
10521 [self removeEquipmentItem:@"EQ_ENERGY_UNIT_DAMAGED"];
10522 tradeIn = [UNIVERSE getEquipmentPriceForKey:@"EQ_ENERGY_UNIT"] * 3 / 8; // half of the working one
10523 break;
10524
10525 default:
10526 break;
10527 }
10528 [self doTradeIn:tradeIn forPriceFactor:priceFactor];
10529 }
10530
10531 // maintain ship
10532 if ([eqKey isEqualToString:@"EQ_RENOVATION"])
10533 {
10534 OOTechLevelID techLevel = NSNotFound;
10535 if (dockedStation != nil) techLevel = [dockedStation equivalentTechLevel];
10536 if (techLevel == NSNotFound) techLevel = [[UNIVERSE currentSystemData] oo_unsignedIntForKey:KEY_TECHLEVEL];
10537
10538 credits -= price;
10539 ship_trade_in_factor += 5 + techLevel; // you get better value at high-tech repair bases
10540 if (ship_trade_in_factor > 100) ship_trade_in_factor = 100;
10541
10542 [self clearSubEntities];
10543 [self setUpSubEntities];
10544
10545 return YES;
10546 }
10547
10548 if ([eqKey hasSuffix:@"MISSILE"] || [eqKey hasSuffix:@"MINE"])
10549 {
10550 ShipEntity* weapon = [[UNIVERSE newShipWithRole:eqKey] autorelease];
10551 if (weapon) OOLog(kOOLogBuyMountedOK, @"Got ship for mounted weapon role %@", eqKey);
10552 else OOLog(kOOLogBuyMountedFailed, @"Could not find ship for mounted weapon role %@", eqKey);
10553
10554 BOOL mounted_okay = [self mountMissile:weapon];
10555 if (mounted_okay)
10556 {
10557 credits -= price;
10558 [self safeAllMissiles];
10559 [self tidyMissilePylons];
10560 [self setActiveMissile:0];
10561 }
10562 return mounted_okay;
10563 }
10564
10565 if ([eqKey isEqualToString:@"EQ_PASSENGER_BERTH"])
10566 {
10567 [self changePassengerBerths:+1];
10568 credits -= price;
10569 return YES;
10570 }
10571
10572 if ([eqKey isEqualToString:@"EQ_PASSENGER_BERTH_REMOVAL"])
10573 {
10574 [self changePassengerBerths:-1];
10575 credits -= price;
10576 return YES;
10577 }
10578
10579 if ([eqKey isEqualToString:@"EQ_MISSILE_REMOVAL"])
10580 {
10581 credits -= price;
10582 tradeIn += [self removeMissiles];
10583 [self doTradeIn:tradeIn forPriceFactor:priceFactor];
10584 return YES;
10585 }
10586
10587 if ([self canAddEquipment:eqKey inContext:@"purchase"])
10588 {
10589 credits -= price;
10590 [self addEquipmentItem:eqKey withValidation:NO inContext:@"purchase"]; // no need to validate twice.
10591 if (isRepair)
10592 {
10593 [self doScriptEvent:OOJSID("equipmentRepaired") withArgument:eqKey];
10594 }
10595 return YES;
10596 }
10597
10598 return NO;
10599}
10600
10601
10602- (BOOL) setWeaponMount:(OOWeaponFacing)facing toWeapon:(NSString *)eqKey
10603{
10604 return [self setWeaponMount:facing toWeapon:eqKey inContext:@"purchase"];
10605}
10606
10607
10608- (BOOL) setWeaponMount:(OOWeaponFacing)facing toWeapon:(NSString *)eqKey inContext:(NSString *) context
10609{
10610
10611 NSDictionary *shipyardInfo = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:[self shipDataKey]];
10612 unsigned available_facings = [shipyardInfo oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:[self weaponFacings]]; // use defaults explicitly
10613
10614 // facing exists?
10615 if (!(available_facings & facing))
10616 {
10617 return NO;
10618 }
10619
10620 // weapon allowed (or NONE)?
10621 if (![eqKey isEqualToString:@"EQ_WEAPON_NONE"])
10622 {
10623 if (![self canAddEquipment:eqKey inContext:context])
10624 {
10625 return NO;
10626 }
10627 }
10628
10629 // sets WEAPON_NONE if not recognised
10631
10632 switch (facing)
10633 {
10635 forward_weapon_type = chosen_weapon;
10636 break;
10637
10638 case WEAPON_FACING_AFT:
10639 aft_weapon_type = chosen_weapon;
10640 break;
10641
10642 case WEAPON_FACING_PORT:
10643 port_weapon_type = chosen_weapon;
10644 break;
10645
10647 starboard_weapon_type = chosen_weapon;
10648 break;
10649
10650 case WEAPON_FACING_NONE:
10651 break;
10652 }
10653
10654 return YES;
10655}
10656
10657
10658- (BOOL) changePassengerBerths:(int) addRemove
10659{
10660 if (addRemove == 0) return NO;
10661 addRemove = (addRemove > 0) ? 1 : -1; // change only by one berth at a time!
10662 // NSFO!
10663 //unsigned passenger_space = [[OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"] requiredCargoSpace];
10664 //if (passenger_space == 0) passenger_space = PASSENGER_BERTH_SPACE;
10665 if ((max_passengers < 1 && addRemove == -1) || ([self maxAvailableCargoSpace] - current_cargo < PASSENGER_BERTH_SPACE && addRemove == 1)) return NO;
10666 max_passengers += addRemove;
10667 max_cargo -= PASSENGER_BERTH_SPACE * addRemove;
10668 return YES;
10669}
10670
10671
10672- (OOCreditsQuantity) removeMissiles
10673{
10674 [self safeAllMissiles];
10675 OOCreditsQuantity tradeIn = 0;
10676 unsigned i;
10677 for (i = 0; i < missiles; i++)
10678 {
10679 NSString *weapon_key = [missile_list[i] identifier];
10680
10681 if (weapon_key != nil)
10682 tradeIn += (int)[UNIVERSE getEquipmentPriceForKey:weapon_key];
10683 }
10684
10685 for (i = 0; i < max_missiles; i++)
10686 {
10687 [missile_entity[i] release];
10688 missile_entity[i] = nil;
10689 }
10690
10691 missiles = 0;
10692 return tradeIn;
10693}
10694
10695
10696- (void) doTradeIn:(OOCreditsQuantity)tradeInValue forPriceFactor:(double)priceFactor
10697{
10698 if (tradeInValue != 0)
10699 {
10700 if (priceFactor < 1.0) tradeInValue *= priceFactor;
10701 credits += tradeInValue;
10702 }
10703}
10704
10705
10706- (OOCargoQuantity) cargoQuantityForType:(OOCommodityType)type
10707{
10708 OOCargoQuantity amount = [shipCommodityData quantityForGood:type];
10709
10710 if ([self status] != STATUS_DOCKED)
10711 {
10712 NSInteger i;
10713 OOCommodityType co_type;
10714 ShipEntity *cargoItem = nil;
10715
10716 for (i = [cargo count] - 1; i >= 0 ; i--)
10717 {
10718 cargoItem = [cargo objectAtIndex:i];
10719 co_type = [cargoItem commodityType];
10720 if ([co_type isEqualToString:type])
10721 {
10722 amount += [cargoItem commodityAmount];
10723 }
10724 }
10725 }
10726
10727 return amount;
10728}
10729
10730
10731- (OOCargoQuantity) setCargoQuantityForType:(OOCommodityType)type amount:(OOCargoQuantity)amount
10732{
10733 OOMassUnit unit = [shipCommodityData massUnitForGood:type];
10734 if([self specialCargo] && unit == UNITS_TONS) return 0; // don't do anything if we've got a special cargo...
10735
10736 OOCargoQuantity oldAmount = [self cargoQuantityForType:type];
10737 OOCargoQuantity available = [self availableCargoSpace];
10738 BOOL inPods = ([self status] != STATUS_DOCKED);
10739
10740 // check it against the max amount.
10741 if (unit == UNITS_TONS && (available + oldAmount) < amount)
10742 {
10743 amount = available + oldAmount;
10744 }
10745 // if we have 1499 kg the ship registers only 1 ton, so it's possible to exceed the max cargo:
10746 // eg: with maxAvailableCargoSpace 2 & gold 1499kg, you can still add 1 ton alloy.
10747 else if (unit == UNITS_KILOGRAMS && amount > oldAmount)
10748 {
10749 // Allow up to 0.5 ton of kg (& g) goods above the cargo capacity but respect existing quantities.
10750 OOCargoQuantity safeAmount = available * KILOGRAMS_PER_POD + MAX_KILOGRAMS_IN_SAFE;
10751 if (safeAmount < amount) amount = (safeAmount < oldAmount) ? oldAmount : safeAmount;
10752 }
10753 else if (unit == UNITS_GRAMS && amount > oldAmount)
10754 {
10755 OOCargoQuantity safeAmount = available * GRAMS_PER_POD + MAX_GRAMS_IN_SAFE;
10756 if (safeAmount < amount) amount = (safeAmount < oldAmount) ? oldAmount : safeAmount;
10757 }
10758
10759 if (inPods)
10760 {
10761 if (amount > oldAmount) // increase
10762 {
10763 [self loadCargoPodsForType:type amount:(amount - oldAmount)];
10764 }
10765 else
10766 {
10767 [self unloadCargoPodsForType:type amount:(oldAmount - amount)];
10768 }
10769 }
10770 else
10771 {
10772 [shipCommodityData setQuantity:amount forGood:type];
10773 }
10774
10775 [self calculateCurrentCargo];
10776 return [shipCommodityData quantityForGood:type];
10777}
10778
10779
10780- (void) calculateCurrentCargo
10781{
10782 current_cargo = [self cargoQuantityOnBoard];
10783}
10784
10785
10786- (OOCargoQuantity) cargoQuantityOnBoard
10787{
10788 if ([self specialCargo] != nil)
10789 {
10790 return [self maxAvailableCargoSpace];
10791 }
10792
10793 /*
10794 The cargo array is nil when the player ship is docked, due to action in unloadCargopods. For
10795 this reason, we must use a slightly more complex method to determine the quantity of cargo
10796 carried in this case - Nikos 20090830
10797
10798 Optimised this method, to compensate for increased usage - Kaks 20091002
10799 */
10800 OOCargoQuantity cargoQtyOnBoard = 0;
10801 NSString *good = nil;
10802
10803 foreach (good, [shipCommodityData goods])
10804 {
10805 OOCargoQuantity quantity = [shipCommodityData quantityForGood:good];
10806
10807 OOMassUnit commodityUnits = [shipCommodityData massUnitForGood:good];
10808
10809 if (commodityUnits != UNITS_TONS)
10810 {
10811 // calculate the number of pods that would be used
10812 // we're using integer math, so 99/100 = 0 , 100/100 = 1, etc...
10813
10814 assert(KILOGRAMS_PER_POD > MAX_KILOGRAMS_IN_SAFE && GRAMS_PER_POD > MAX_GRAMS_IN_SAFE); // otherwise we're in trouble!
10815
10816 if (commodityUnits == UNITS_KILOGRAMS) quantity = ((KILOGRAMS_PER_POD - MAX_KILOGRAMS_IN_SAFE - 1) + quantity) / KILOGRAMS_PER_POD;
10817 else quantity = ((GRAMS_PER_POD - MAX_GRAMS_IN_SAFE - 1) + quantity) / GRAMS_PER_POD;
10818 }
10819 cargoQtyOnBoard += quantity;
10820 }
10821 cargoQtyOnBoard += [[self cargo] count];
10822
10823 return cargoQtyOnBoard;
10824}
10825
10826
10827- (OOCommodityMarket *) localMarket
10828{
10829 StationEntity *station = [self dockedStation];
10830 if (station == nil)
10831 {
10832 if ([[self primaryTarget] isStation] && [(StationEntity *)[self primaryTarget] marketBroadcast])
10833 {
10834 station = [self primaryTarget];
10835 }
10836 else
10837 {
10838 station = [UNIVERSE station];
10839 }
10840 if (station == nil)
10841 {
10842 // interstellar space or similar
10843 return nil;
10844 }
10845 }
10846 OOCommodityMarket *localMarket = [station localMarket];
10847 if (localMarket == nil)
10848 {
10849 localMarket = [station initialiseLocalMarket];
10850 }
10851
10852 return localMarket;
10853}
10854
10855
10856- (NSArray *) applyMarketFilter:(NSArray *)goods onMarket:(OOCommodityMarket *)market
10857{
10858 if (marketFilterMode == MARKET_FILTER_MODE_OFF)
10859 {
10860 return goods;
10861 }
10862 NSMutableArray *filteredGoods = [NSMutableArray arrayWithCapacity:[goods count]];
10863 OOCommodityType good = nil;
10864 foreach (good, goods)
10865 {
10866 switch (marketFilterMode)
10867 {
10869 // never reached, but keeps compiler happy
10870 [filteredGoods addObject:good];
10871 break;
10873 if ([market quantityForGood:good] > 0 || [self cargoQuantityForType:good] > 0)
10874 {
10875 [filteredGoods addObject:good];
10876 }
10877 break;
10879 if ([self cargoQuantityForType:good] > 0)
10880 {
10881 [filteredGoods addObject:good];
10882 }
10883 break;
10885 if ([market quantityForGood:good] > 0)
10886 {
10887 [filteredGoods addObject:good];
10888 }
10889 break;
10891 if ([market exportLegalityForGood:good] == 0 && [market importLegalityForGood:good] == 0)
10892 {
10893 [filteredGoods addObject:good];
10894 }
10895 break;
10897 if ([market exportLegalityForGood:good] > 0 || [market importLegalityForGood:good] > 0)
10898 {
10899 [filteredGoods addObject:good];
10900 }
10901 break;
10902 }
10903 }
10904 return [[filteredGoods copy] autorelease];
10905}
10906
10907
10908- (NSArray *) applyMarketSorter:(NSArray *)goods onMarket:(OOCommodityMarket *)market
10909{
10910 switch (marketSorterMode)
10911 {
10913 return [goods sortedArrayUsingFunction:marketSorterByName context:market];
10915 return [goods sortedArrayUsingFunction:marketSorterByPrice context:market];
10917 return [goods sortedArrayUsingFunction:marketSorterByQuantity context:market];
10919 return [goods sortedArrayUsingFunction:marketSorterByQuantity context:shipCommodityData];
10921 return [goods sortedArrayUsingFunction:marketSorterByMassUnit context:market];
10923 // keep default sort order
10924 break;
10925 }
10926 return goods;
10927}
10928
10929
10931{
10932 GuiDisplayGen *gui = [UNIVERSE gui];
10933 OOGUITabSettings tab_stops;
10934 tab_stops[0] = 0;
10935 tab_stops[1] = 137;
10936 tab_stops[2] = 187;
10937 tab_stops[3] = 267;
10938 tab_stops[4] = 321;
10939 tab_stops[5] = 431;
10940 [gui overrideTabs:tab_stops from:kGuiMarketTabs length:6];
10941 [gui setTabStops:tab_stops];
10942
10943 [gui setColor:[gui colorFromSetting:kGuiMarketHeadingColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_KEY];
10944 [gui setArray:[NSArray arrayWithObjects: DESC(@"commodity-column-title"), OOPadStringToEms(DESC(@"price-column-title"),3.5),
10945 OOPadStringToEms(DESC(@"for-sale-column-title"),3.75), OOPadStringToEms(DESC(@"in-hold-column-title"),5.75), DESC(@"oolite-legality-column-title"), DESC(@"oolite-extras-column-title"), nil] forRow:GUI_ROW_MARKET_KEY];
10946 [gui setArray:[NSArray arrayWithObjects: DESC(@"commodity-column-title"), DESC(@"oolite-extras-column-title"), OOPadStringToEms(DESC(@"price-column-title"),3.5),
10947 OOPadStringToEms(DESC(@"for-sale-column-title"),3.75), OOPadStringToEms(DESC(@"in-hold-column-title"),5.75), DESC(@"oolite-legality-column-title"), nil] forRow:GUI_ROW_MARKET_KEY];
10948
10949}
10950
10951
10952- (void) showMarketScreenDataLine:(OOGUIRow)row forGood:(OOCommodityType)good inMarket:(OOCommodityMarket *)localMarket holdQuantity:(OOCargoQuantity)quantity
10953{
10954 GuiDisplayGen *gui = [UNIVERSE gui];
10955 NSString* desc = [NSString stringWithFormat:@" %@ ", [shipCommodityData nameForGood:good]];
10956 OOCargoQuantity available_units = [localMarket quantityForGood:good];
10957 OOCargoQuantity units_in_hold = quantity;
10958 OOCreditsQuantity pricePerUnit = [localMarket priceForGood:good];
10959 OOMassUnit unit = [shipCommodityData massUnitForGood:good];
10960
10961 NSString *available = OOPadStringToEms(((available_units > 0) ? (NSString *)[NSString stringWithFormat:@"%d",available_units] : DESC(@"commodity-quantity-none")), 2.5);
10962
10963 NSUInteger priceDecimal = pricePerUnit % 10;
10964 NSString *price = [NSString stringWithFormat:@" %@.%llu ",OOPadStringToEms([NSString stringWithFormat:@"%lu",(unsigned long)(pricePerUnit/10)],2.5),priceDecimal];
10965
10966 // this works with up to 9999 tons of gemstones. Any more than that, they deserve the formatting they get! :)
10967
10968 NSString *owned = OOPadStringToEms((units_in_hold > 0) ? (NSString *)[NSString stringWithFormat:@"%d",units_in_hold] : DESC(@"commodity-quantity-none"), 4.5);
10969 NSString *units = DisplayStringForMassUnit(unit);
10970 NSString *units_available = [NSString stringWithFormat:@" %@ %@ ",available, units];
10971 NSString *units_owned = [NSString stringWithFormat:@" %@ %@ ",owned, units];
10972
10973 NSUInteger import_legality = [localMarket importLegalityForGood:good];
10974 NSUInteger export_legality = [localMarket exportLegalityForGood:good];
10975 NSString *legaldesc = nil;
10976 if (import_legality == 0)
10977 {
10978 if (export_legality == 0)
10979 {
10980 legaldesc = DESC(@"oolite-legality-clear");
10981 }
10982 else
10983 {
10984 legaldesc = DESC(@"oolite-legality-import");
10985 }
10986 }
10987 else
10988 {
10989 if (export_legality == 0)
10990 {
10991 legaldesc = DESC(@"oolite-legality-export");
10992 }
10993 else
10994 {
10995 legaldesc = DESC(@"oolite-legality-neither");
10996 }
10997 }
10998 legaldesc = [NSString stringWithFormat:@" %@ ",legaldesc];
10999
11000 NSString *extradesc = [shipCommodityData shortCommentForGood:good];
11001
11002 [gui setKey:good forRow:row];
11003 [gui setColor:[gui colorFromSetting:kGuiMarketCommodityColor defaultValue:nil] forRow:row];
11004 [gui setArray:[NSArray arrayWithObjects: desc, extradesc, price, units_available, units_owned, legaldesc, nil] forRow:row++];
11005
11006}
11007
11008
11009- (NSString *)marketScreenTitle
11010{
11011 StationEntity *dockedStation = [self dockedStation];
11012
11013 /* Override normal behaviour if station broadcasts market */
11014 if (dockedStation == nil)
11015 {
11016 if ([[self primaryTarget] isStation] && [(StationEntity *)[self primaryTarget] marketBroadcast])
11017 {
11018 dockedStation = [self primaryTarget];
11019 }
11020 }
11021
11022 NSString *system = nil;
11023 if ([UNIVERSE sun] != nil) system = [UNIVERSE getSystemName:system_id];
11024
11025 if (dockedStation == nil || dockedStation == [UNIVERSE station])
11026 {
11027 if ([UNIVERSE sun] != nil)
11028 {
11029 return OOExpandKey(@"system-commodity-market", system);
11030 }
11031 else
11032 {
11033 // Witchspace
11034 return OOExpandKey(@"commodity-market");
11035 }
11036 }
11037 else
11038 {
11039 NSString *station = [dockedStation displayName];
11040 return OOExpandKey(@"station-commodity-market", station);
11041 }
11042}
11043
11044
11045- (void) setGuiToMarketScreen
11046{
11047 OOCommodityMarket *localMarket = [self localMarket];
11048 GuiDisplayGen *gui = [UNIVERSE gui];
11049 OOGUIScreenID oldScreen = gui_screen;
11050
11051 gui_screen = GUI_SCREEN_MARKET;
11052 BOOL guiChanged = (oldScreen != gui_screen);
11053
11054
11055 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
11056
11057 // fix problems with economies in witchspace
11058 if (localMarket == nil)
11059 {
11060 localMarket = [[UNIVERSE commodities] generateBlankMarket];
11061 }
11062
11063 // following changed to work whether docked or not
11064 NSArray *goods = [self applyMarketSorter:[self applyMarketFilter:[localMarket goods] onMarket:localMarket] onMarket:localMarket];
11065 NSInteger maxOffset = 0;
11067 {
11068 maxOffset = [goods count]-(GUI_ROW_MARKET_END-GUI_ROW_MARKET_START);
11069 }
11070
11071 NSUInteger commodityCount = [shipCommodityData count];
11072 OOCargoQuantity quantityInHold[commodityCount];
11073
11074 for (NSUInteger i = 0; i < commodityCount; i++)
11075 {
11076 quantityInHold[i] = [shipCommodityData quantityForGood:[goods oo_stringAtIndex:i]];
11077 }
11078 for (NSUInteger i = 0; i < [cargo count]; i++)
11079 {
11080 ShipEntity *container = [cargo objectAtIndex:i];
11081 NSUInteger goodsIndex = [goods indexOfObject:[container commodityType]];
11082 // can happen with filters
11083 if (goodsIndex != NSNotFound)
11084 {
11085 quantityInHold[goodsIndex] += [container commodityAmount];
11086 }
11087 }
11088
11089 if (marketSelectedCommodity != nil && ([marketSelectedCommodity isEqualToString:@"<<<"] || [marketSelectedCommodity isEqualToString:@">>>"]))
11090 {
11091 // nothing?
11092 }
11093 else
11094 {
11095 if (marketSelectedCommodity == nil || [goods indexOfObject:marketSelectedCommodity] == NSNotFound)
11096 {
11097 DESTROY(marketSelectedCommodity);
11098 if ([goods count] > 0)
11099 {
11100 marketSelectedCommodity = [[goods oo_stringAtIndex:0] retain];
11101 }
11102 }
11103 if (maxOffset > 0)
11104 {
11105 NSInteger goodsIndex = [goods indexOfObject:marketSelectedCommodity];
11106 // validate marketOffset when returning from infoscreen
11107 if (goodsIndex <= marketOffset)
11108 {
11109 // is off top of list, move list upwards
11110 if (goodsIndex == 0) {
11111 marketOffset = 0;
11112 } else {
11113 marketOffset = goodsIndex-1;
11114 }
11115 }
11116 else if (goodsIndex > marketOffset+(GUI_ROW_MARKET_END-GUI_ROW_MARKET_START)-2)
11117 {
11118 // is off bottom of list, move list downwards
11119 marketOffset = 2+goodsIndex-(GUI_ROW_MARKET_END-GUI_ROW_MARKET_START);
11120 if (marketOffset > maxOffset)
11121 {
11122 marketOffset = maxOffset;
11123 }
11124 }
11125 }
11126 }
11127
11128 // GUI stuff
11129 {
11130 OOGUIRow start_row = GUI_ROW_MARKET_START;
11131 OOGUIRow row = start_row;
11132 OOGUIRow active_row = [gui selectedRow];
11133
11134 [gui clearAndKeepBackground:!guiChanged];
11135
11136
11137 StationEntity *dockedStation = [self dockedStation];
11138 if (dockedStation == nil && [[self primaryTarget] isStation] && [(StationEntity *)[self primaryTarget] marketBroadcast])
11139 {
11140 dockedStation = [self primaryTarget];
11141 }
11142
11143 [gui setTitle:[self marketScreenTitle]];
11144
11145 [self showMarketScreenHeaders];
11146
11147 if (marketOffset > maxOffset)
11148 {
11149 marketOffset = 0;
11150 }
11151 else if (marketOffset < 0)
11152 {
11153 marketOffset = maxOffset;
11154 }
11155
11156 if ([goods count] > 0)
11157 {
11158 OOCommodityType good = nil;
11159 NSInteger i = 0;
11160 foreach (good, goods)
11161 {
11162 if (i < marketOffset)
11163 {
11164 ++i;
11165 continue;
11166 }
11167 [self showMarketScreenDataLine:row forGood:good inMarket:localMarket holdQuantity:quantityInHold[i++]];
11168 if ([good isEqualToString:marketSelectedCommodity])
11169 {
11170 active_row = row;
11171 }
11172
11173 ++row;
11174 if (row >= GUI_ROW_MARKET_END)
11175 {
11176 break;
11177 }
11178 }
11179
11180 if (marketOffset < maxOffset)
11181 {
11182 if ([marketSelectedCommodity isEqualToString:@">>>"])
11183 {
11184 active_row = GUI_ROW_MARKET_LAST;
11185 }
11186 [gui setKey:@">>>" forRow:GUI_ROW_MARKET_LAST];
11187 [gui setColor:[gui colorFromSetting:kGuiMarketScrollColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_LAST];
11188 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @"", @"", @"", @" --> ", nil] forRow:GUI_ROW_MARKET_LAST];
11189 }
11190 if (marketOffset > 0)
11191 {
11192 if ([marketSelectedCommodity isEqualToString:@"<<<"])
11193 {
11194 active_row = GUI_ROW_MARKET_START;
11195 }
11196 [gui setKey:@"<<<" forRow:GUI_ROW_MARKET_START];
11197 [gui setColor:[gui colorFromSetting:kGuiMarketScrollColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_START];
11198 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @"", @"", @"", @" <-- ", nil] forRow:GUI_ROW_MARKET_START];
11199 }
11200 }
11201 else
11202 {
11203 // filter is excluding everything
11204 [gui setColor:[gui colorFromSetting:kGuiMarketFilteredAllColor defaultValue:[OOColor yellowColor]] forRow:GUI_ROW_MARKET_START];
11205 [gui setText:DESC(@"oolite-market-filtered-all") forRow:GUI_ROW_MARKET_START];
11206 active_row = -1;
11207 }
11208
11209 // actually count the containers and valuables (may be > max_cargo)
11210 current_cargo = [self cargoQuantityOnBoard];
11211 if (current_cargo > [self maxAvailableCargoSpace]) current_cargo = [self maxAvailableCargoSpace];
11212
11213 // filter sort info
11214 {
11215 NSString *filterMode = OOExpandKey(OOExpand(@"oolite-market-filter-[marketFilterMode]", marketFilterMode));
11216 NSString *filterText = OOExpandKey(@"oolite-market-filter-line", filterMode);
11217 NSString *sortMode = OOExpandKey(OOExpand(@"oolite-market-sorter-[marketSorterMode]", marketSorterMode));
11218 NSString *sorterText = OOExpandKey(@"oolite-market-sorter-line", sortMode);
11219 [gui setArray:[NSArray arrayWithObjects:filterText, @"", sorterText, nil] forRow:GUI_ROW_MARKET_END];
11220 }
11221 [gui setColor:[gui colorFromSetting:kGuiMarketFilterInfoColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_END];
11222
11223 [self showMarketCashAndLoadLine];
11224
11225 [gui setSelectableRange:NSMakeRange(start_row,row - start_row)];
11226 [gui setSelectedRow:active_row];
11227
11228 [gui setShowTextCursor:NO];
11229 }
11230
11231
11232 [[UNIVERSE gameView] clearMouse];
11233
11234 [self setShowDemoShips:NO];
11235 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
11236
11237 if (guiChanged)
11238 {
11239 [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"];
11240 [gui setBackgroundTextureKey:@"market"];
11241 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
11242 }
11243}
11244
11245
11246- (void) setGuiToMarketInfoScreen
11247{
11248 OOCommodityMarket *localMarket = [self localMarket];
11249 GuiDisplayGen *gui = [UNIVERSE gui];
11250 OOGUIScreenID oldScreen = gui_screen;
11251
11252 gui_screen = GUI_SCREEN_MARKETINFO;
11253 BOOL guiChanged = (oldScreen != gui_screen);
11254
11255
11256 [[UNIVERSE gameController] setMouseInteractionModeForUIWithMouseInteraction:YES];
11257
11258 // fix problems with economies in witchspace
11259 if (localMarket == nil)
11260 {
11261 localMarket = [[UNIVERSE commodities] generateBlankMarket];
11262 }
11263
11264 // following changed to work whether docked or not
11265 NSArray *goods = [self applyMarketSorter:[self applyMarketFilter:[localMarket goods] onMarket:localMarket] onMarket:localMarket];
11266
11267 NSUInteger i, j, commodityCount = [shipCommodityData count];
11268 OOCargoQuantity quantityInHold[commodityCount];
11269
11270 for (i = 0; i < commodityCount; i++)
11271 {
11272 quantityInHold[i] = [shipCommodityData quantityForGood:[goods oo_stringAtIndex:i]];
11273 }
11274 for (i = 0; i < [cargo count]; i++)
11275 {
11276 ShipEntity *container = [cargo objectAtIndex:i];
11277 j = [goods indexOfObject:[container commodityType]];
11278 quantityInHold[j] += [container commodityAmount];
11279 }
11280
11281
11282 // GUI stuff
11283 {
11284 if (EXPECT_NOT(marketSelectedCommodity == nil))
11285 {
11286 j = NSNotFound;
11287 }
11288 else
11289 {
11290 j = [goods indexOfObject:marketSelectedCommodity];
11291 }
11292 if (j == NSNotFound)
11293 {
11294 DESTROY(marketSelectedCommodity);
11295 [self setGuiToMarketScreen];
11296 return;
11297 }
11298
11299 [gui clearAndKeepBackground:!guiChanged];
11300
11301 [gui setTitle:[NSString stringWithFormat:DESC(@"oolite-commodity-information-@"), [shipCommodityData nameForGood:marketSelectedCommodity]]];
11302
11303 [self showMarketScreenHeaders];
11304 [self showMarketScreenDataLine:GUI_ROW_MARKET_START forGood:marketSelectedCommodity inMarket:localMarket holdQuantity:quantityInHold[j]];
11305
11306 OOCargoQuantity contracted = [self contractedVolumeForGood:marketSelectedCommodity];
11307 if (contracted > 0)
11308 {
11309 OOMassUnit unit = [shipCommodityData massUnitForGood:marketSelectedCommodity];
11310 [gui setColor:[gui colorFromSetting:kGuiMarketContractedColor defaultValue:nil] forRow:GUI_ROW_MARKET_START+1];
11311 [gui setText:[NSString stringWithFormat:DESC(@"oolite-commodity-contracted-d-@"), contracted, DisplayStringForMassUnit(unit)] forRow:GUI_ROW_MARKET_START+1];
11312 }
11313
11314 NSString *info = [shipCommodityData commentForGood:marketSelectedCommodity];
11315 OOGUIRow i = 0;
11316 if (info == nil || [info length] == 0)
11317 {
11318 i = [gui addLongText:DESC(@"oolite-commodity-no-comment") startingAtRow:GUI_ROW_MARKET_START+2 align:GUI_ALIGN_LEFT];
11319 }
11320 else
11321 {
11322 i = [gui addLongText:info startingAtRow:GUI_ROW_MARKET_START+2 align:GUI_ALIGN_LEFT];
11323 }
11324 for (i-- ; i > GUI_ROW_MARKET_START+2 ; --i)
11325 {
11326 [gui setColor:[gui colorFromSetting:kGuiMarketDescriptionColor defaultValue:nil] forRow:i];
11327 }
11328
11329 [self showMarketCashAndLoadLine];
11330
11331 }
11332
11333 [[UNIVERSE gameView] clearMouse];
11334
11335 [self setShowDemoShips:NO];
11336 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
11337
11338 if (guiChanged)
11339 {
11340 [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"];
11341 [gui setBackgroundTextureKey:@"marketinfo"];
11342 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
11343 }
11344}
11345
11347{
11348 GuiDisplayGen *gui = [UNIVERSE gui];
11349 OOCargoQuantity currentCargo = current_cargo;
11350 OOCargoQuantity cargoCapacity = [self maxAvailableCargoSpace];
11351 [gui setText:OOExpandKey(@"market-cash-and-load", credits, currentCargo, cargoCapacity) forRow:GUI_ROW_MARKET_CASH];
11352 [gui setColor:[gui colorFromSetting:kGuiMarketCashColor defaultValue:[OOColor yellowColor]] forRow:GUI_ROW_MARKET_CASH];
11353}
11354
11355- (OOGUIScreenID) guiScreen
11356{
11357 return gui_screen;
11358}
11359
11360
11361- (BOOL) tryBuyingCommodity:(OOCommodityType)index all:(BOOL)all
11362{
11363 if ([index isEqualToString:@"<<<"] || [index isEqualToString:@">>>"])
11364 {
11365 ++marketOffset;
11366 return NO;
11367 }
11368
11369 if (![self isDocked]) return NO; // can't buy if not docked.
11370
11371 OOCommodityMarket *localMarket = [self localMarket];
11372 OOCreditsQuantity pricePerUnit = [localMarket priceForGood:index];
11373 OOMassUnit unit = [localMarket massUnitForGood:index];
11374
11375 if (specialCargo != nil && unit == UNITS_TONS)
11376 {
11377 return NO; // can't buy tons of stuff when carrying a specialCargo
11378 }
11379 int manifest_quantity = [shipCommodityData quantityForGood:index];
11380 int market_quantity = [localMarket quantityForGood:index];
11381
11382 int purchase = 1;
11383 if (all)
11384 {
11385 // if cargo contracts, put a break point on the contract volume
11386 int contracted = [self contractedVolumeForGood:index];
11387 if (manifest_quantity >= contracted)
11388 {
11389 purchase = [localMarket capacityForGood:index];
11390 }
11391 else
11392 {
11393 purchase = contracted-manifest_quantity;
11394 }
11395 }
11396 if (purchase > market_quantity)
11397 {
11398 purchase = market_quantity; // limit to what's available
11399 }
11400 if (purchase * pricePerUnit > credits)
11401 {
11402 purchase = floor (credits / pricePerUnit); // limit to what's affordable
11403 }
11404 // TODO - fix brokenness here...
11405 if (unit == UNITS_TONS && purchase + current_cargo > [self maxAvailableCargoSpace])
11406 {
11407 purchase = [self availableCargoSpace]; // limit to available cargo space
11408 }
11409 else
11410 {
11411 if (current_cargo == [self maxAvailableCargoSpace])
11412 {
11413 // other cases are fine so long as buying is limited to <1000kg / <1000000g
11414 // but if this case is true, we need to see if there is more space in
11415 // the manifest (safe) or an already-accounted-for pod
11416 if (unit == UNITS_KILOGRAMS)
11417 {
11418 if (manifest_quantity % KILOGRAMS_PER_POD <= MAX_KILOGRAMS_IN_SAFE && (manifest_quantity + purchase) % KILOGRAMS_PER_POD > MAX_KILOGRAMS_IN_SAFE)
11419 {
11420 // going from < n500 to >= n500 would increase pods needed by 1
11421 purchase = MAX_KILOGRAMS_IN_SAFE - manifest_quantity; // max possible
11422 }
11423 }
11424 else // UNITS_GRAMS
11425 {
11426 if (manifest_quantity % GRAMS_PER_POD <= MAX_GRAMS_IN_SAFE && (manifest_quantity + purchase) % GRAMS_PER_POD > MAX_GRAMS_IN_SAFE)
11427 {
11428 // going from < n500000 to >= n500000 would increase pods needed by 1
11429 purchase = MAX_GRAMS_IN_SAFE - manifest_quantity; // max possible
11430 }
11431 }
11432 }
11433 }
11434 if (purchase <= 0)
11435 {
11436 return NO; // stop if that results in nothing to be bought
11437 }
11438
11439 [localMarket removeQuantity:purchase forGood:index];
11440 [shipCommodityData addQuantity:purchase forGood:index];
11441 credits -= pricePerUnit * purchase;
11442
11443 [self calculateCurrentCargo];
11444
11445 if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES];
11446
11447 [self doScriptEvent:OOJSID("playerBoughtCargo") withArguments:[NSArray arrayWithObjects:index, [NSNumber numberWithInt:purchase], [NSNumber numberWithUnsignedLongLong:pricePerUnit], nil]];
11448 if ([localMarket exportLegalityForGood:index] > 0)
11449 {
11450 [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-illegal"];
11451 }
11452 else
11453 {
11454 [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-legal"];
11455 }
11456
11457 return YES;
11458}
11459
11460
11461- (BOOL) trySellingCommodity:(OOCommodityType)index all:(BOOL)all
11462{
11463 if ([index isEqualToString:@"<<<"] || [index isEqualToString:@">>>"])
11464 {
11465 --marketOffset;
11466 return NO;
11467 }
11468
11469 if (![self isDocked]) return NO; // can't sell if not docked.
11470
11471 OOCommodityMarket *localMarket = [self localMarket];
11472 int available_units = [shipCommodityData quantityForGood:index];
11473 OOCreditsQuantity pricePerUnit = [localMarket priceForGood:index];
11474
11475 if (available_units == 0) return NO;
11476
11477 int market_quantity = [localMarket quantityForGood:index];
11478
11479 int capacity = [localMarket capacityForGood:index];
11480 int sell = 1;
11481 if (all)
11482 {
11483 // if cargo contracts, put a break point on the contract volume
11484 int contracted = [self contractedVolumeForGood:index];
11485 if (available_units <= contracted)
11486 {
11487 sell = capacity;
11488 }
11489 else
11490 {
11491 sell = available_units-contracted;
11492 }
11493 }
11494
11495 if (sell > available_units)
11496 sell = available_units; // limit to what's in the hold
11497 if (sell + market_quantity > capacity)
11498 sell = capacity - market_quantity; // avoid flooding the market
11499 if (sell <= 0)
11500 return NO; // stop if that results in nothing to be sold
11501
11502 [localMarket addQuantity:sell forGood:index];
11503 [shipCommodityData removeQuantity:sell forGood:index];
11504 credits += pricePerUnit * sell;
11505
11506 [self calculateCurrentCargo];
11507
11508 if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES];
11509
11510 [self doScriptEvent:OOJSID("playerSoldCargo") withArguments:[NSArray arrayWithObjects:index, [NSNumber numberWithInt:sell], [NSNumber numberWithUnsignedLongLong: pricePerUnit], nil]];
11511
11512 return YES;
11513}
11514
11515
11516- (BOOL) isMining
11517{
11518 return using_mining_laser;
11519}
11520
11521
11522- (OOSpeechSettings) isSpeechOn
11523{
11524 return isSpeechOn;
11525}
11526
11527
11528- (BOOL) canAddEquipment:(NSString *)equipmentKey inContext:(NSString *)context
11529{
11530 if ([equipmentKey isEqualToString:@"EQ_RENOVATION"] && !(ship_trade_in_factor < 85 || [[[self shipSubEntityEnumerator] allObjects] count] < [self maxShipSubEntities])) return NO;
11531 if (![super canAddEquipment:equipmentKey inContext:context]) return NO;
11532
11533 NSArray *conditions = [[OOEquipmentType equipmentTypeWithIdentifier:equipmentKey] conditions];
11534 if (conditions != nil && ![self scriptTestConditions:conditions]) return NO;
11535
11536 return YES;
11537}
11538
11539
11540- (BOOL) addEquipmentItem:(NSString *)equipmentKey inContext:(NSString *)context
11541{
11542 return [self addEquipmentItem:equipmentKey withValidation:YES inContext:context];
11543}
11544
11545
11546- (BOOL) addEquipmentItem:(NSString *)equipmentKey withValidation:(BOOL)validateAddition inContext:(NSString *)context
11547{
11548 // deal with trumbles..
11549 if ([equipmentKey isEqualToString:@"EQ_TRUMBLE"])
11550 {
11551 /* Bug fix: must return here if eqKey == @"EQ_TRUMBLE", even if
11552 trumbleCount >= 1. Otherwise, the player becomes immune to
11553 trumbles. See comment in -setCommanderDataFromDictionary: for more
11554 details.
11555 -- Ahruman 2008-12-04
11556 */
11557 // the old trumbles will kill the new one if there are enough of them.
11558 if ((trumbleCount < PLAYER_MAX_TRUMBLES / 6) || (trumbleCount < PLAYER_MAX_TRUMBLES / 3 && ranrot_rand() % 2 > 0))
11559 {
11560 [self addTrumble:trumble[ranrot_rand() % PLAYER_MAX_TRUMBLES]]; // randomise its looks.
11561 return YES;
11562 }
11563 return NO;
11564 }
11565
11566 BOOL OK = [super addEquipmentItem:equipmentKey withValidation:validateAddition inContext:context];
11567
11568 if (OK)
11569 {
11570 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"] && [self compassMode] == COMPASS_MODE_BASIC)
11571 {
11572 [self setCompassMode:COMPASS_MODE_PLANET];
11573 }
11574
11575 [self addEqScriptForKey:equipmentKey];
11576 [self addEquipmentWithScriptToCustomKeyArray:equipmentKey];
11577 }
11578 return OK;
11579}
11580
11581
11582- (NSMutableArray *) customEquipmentActivation
11583{
11584 return customEquipActivation;
11585}
11586
11587
11588- (void) addEquipmentWithScriptToCustomKeyArray:(NSString *)equipmentKey
11589{
11590 NSDictionary *item;
11591 NSUInteger i, j;
11592 NSArray *object;
11593
11594 for (i = 0; i < [eqScripts count]; i++)
11595 {
11596 if ([[[eqScripts oo_arrayAtIndex:i] oo_stringAtIndex:0] isEqualToString:equipmentKey])
11597 {
11598 //check if this equipment item is already in the array
11599 for (j = 0; j < [customEquipActivation count]; j++) {
11600 item = [customEquipActivation objectAtIndex:j];
11601 if ([[item oo_stringForKey:CUSTOMEQUIP_EQUIPKEY] isEqualToString:equipmentKey]) return;
11602 }
11603 // if we get here, this item is new
11604 // add the basic info at this point (equipkey and name only)
11606 NSMutableDictionary *customKey = [[NSMutableDictionary alloc] initWithObjectsAndKeys:equipmentKey, CUSTOMEQUIP_EQUIPKEY, [eq name], CUSTOMEQUIP_EQUIPNAME, nil];
11607
11608 // grab any default keys from the equipment item
11609 // default activate
11610 object = [eq defaultActivateKey];
11611 if ((object != nil && [object count] > 0))
11612 [customKey setObject:object forKey:CUSTOMEQUIP_KEYACTIVATE];
11613 // default mode
11614 object = [eq defaultModeKey];
11615 if ((object != nil && [object count] > 0))
11616 [customKey setObject:object forKey:CUSTOMEQUIP_KEYMODE];
11617
11618 [customEquipActivation addObject:customKey];
11619 [customKey release];
11620 // keep the keypress arrays in sync
11621 [customActivatePressed addObject:[NSNumber numberWithBool:NO]];
11622 [customModePressed addObject:[NSNumber numberWithBool:NO]];
11623
11624 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
11625 [defaults setObject:customEquipActivation forKey:KEYCONFIG_CUSTOMEQUIP];
11626 return;
11627 }
11628 }
11629}
11630
11631
11632- (void) validateCustomEquipActivationArray
11633{
11634 int i;
11635 bool update = NO;
11636 NSString *equipmentKey;
11637 if ([customEquipActivation count] == 0) return;
11638 for (i = [customEquipActivation count] - 1; i >= 0; i--) {
11639 equipmentKey = [[customEquipActivation objectAtIndex:i] oo_stringForKey:CUSTOMEQUIP_EQUIPKEY];
11641 if (!eq) {
11642 [customEquipActivation removeObjectAtIndex:i];
11643 [customActivatePressed removeObjectAtIndex:i];
11644 [customModePressed removeObjectAtIndex:i];
11645 update = YES;
11646 }
11647 }
11648 if (update) {
11649 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
11650 [defaults setObject:customEquipActivation forKey:KEYCONFIG_CUSTOMEQUIP];
11651 }
11652}
11653
11654
11655- (void) removeEquipmentItem:(NSString *)equipmentKey
11656{
11657 if(![self hasEquipmentItemProviding:@"EQ_ADVANCED_COMPASS"] && [self compassMode] != COMPASS_MODE_BASIC)
11658 {
11659 [self setCompassMode:COMPASS_MODE_BASIC];
11660 }
11661 [super removeEquipmentItem:equipmentKey];
11662 if(![self hasEquipmentItem:equipmentKey]) {
11663 // removed the last one
11664 [self removeEqScriptForKey:equipmentKey];
11665 }
11666}
11667
11668
11669- (void) addEquipmentFromCollection:(id)equipment
11670{
11671 NSDictionary *dict = nil;
11672 NSEnumerator *eqEnum = nil;
11673 NSString *eqDesc = nil;
11674 NSUInteger i, count;
11675
11676 // Pass 1: Load the entire collection.
11677 if ([equipment isKindOfClass:[NSDictionary class]])
11678 {
11679 dict = equipment;
11680 eqEnum = [equipment keyEnumerator];
11681 }
11682 else if ([equipment isKindOfClass:[NSArray class]] || [equipment isKindOfClass:[NSSet class]])
11683 {
11684 eqEnum = [equipment objectEnumerator];
11685 }
11686 else if ([equipment isKindOfClass:[NSString class]])
11687 {
11688 eqEnum = [[NSArray arrayWithObject:equipment] objectEnumerator];
11689 }
11690 else
11691 {
11692 return;
11693 }
11694
11695 while ((eqDesc = [eqEnum nextObject]))
11696 {
11697 /* Bug workaround: extra_equipment should never contain EQ_TRUMBLE,
11698 which is basically a magic flag passed to awardEquipment: to infect
11699 the player. However, prior to Oolite 1.70.1, if the player had a
11700 trumble infection and awardEquipment:EQ_TRUMBLE was called, an
11701 EQ_TRUMBLE would be added to the equipment list. Subsequent calls
11702 to awardEquipment:EQ_TRUMBLE would exit early because there was an
11703 EQ_TRUMBLE in the equipment list. as a result, it would no longer
11704 be possible to infect the player after the current infection ended.
11705
11706 The bug is fixed in 1.70.1. The following line is to fix old saved
11707 games which had been "corrupted" by the bug.
11708 -- Ahruman 2007-12-04
11709 */
11710 if ([eqDesc isEqualToString:@"EQ_TRUMBLE"]) continue;
11711
11712 // Traditional form is a dictionary of booleans; we only accept those where the value is true.
11713 if (dict != nil && ![dict oo_boolForKey:eqDesc]) continue;
11714
11715 // We need to add the entire collection without validation first and then remove the items that are
11716 // not compliant (like items that do not satisfy the requiresEquipment criterion). This is to avoid
11717 // unintentionally excluding valid equipment, just because the required equipment existed but had
11718 // not been yet added to the equipment list at the time of the canAddEquipment validation check.
11719 // Nikos, 20080817.
11720 count = [dict oo_unsignedIntegerForKey:eqDesc];
11721 for (i=0;i<count;i++)
11722 {
11723 [self addEquipmentItem:eqDesc withValidation:NO inContext:@"loading"];
11724 }
11725 }
11726
11727 // Pass 2: Remove items that do not satisfy validation criteria (like requires_equipment etc.).
11728 if ([equipment isKindOfClass:[NSDictionary class]])
11729 {
11730 eqEnum = [equipment keyEnumerator];
11731 }
11732 else if ([equipment isKindOfClass:[NSArray class]] || [equipment isKindOfClass:[NSSet class]])
11733 {
11734 eqEnum = [equipment objectEnumerator];
11735 }
11736 else if ([equipment isKindOfClass:[NSString class]])
11737 {
11738 eqEnum = [[NSArray arrayWithObject:equipment] objectEnumerator];
11739 }
11740 // Now remove items that should not be in the equipment list.
11741 while ((eqDesc = [eqEnum nextObject]))
11742 {
11743 if (![self equipmentValidToAdd:eqDesc whileLoading:YES inContext:@"loading"])
11744 {
11745 [self removeEquipmentItem:eqDesc];
11746 }
11747 }
11748}
11749
11750
11751- (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeMissiles:(BOOL)includeMissiles
11752{
11753 // Check basic equipment the normal way.
11754 if ([super hasOneEquipmentItem:itemKey includeMissiles:NO whileLoading:NO]) return YES;
11755
11756 // Custom handling for player missiles.
11757 if (includeMissiles)
11758 {
11759 unsigned i;
11760 for (i = 0; i < max_missiles; i++)
11761 {
11762 if ([[self missileForPylon:i] hasPrimaryRole:itemKey]) return YES;
11763 }
11764 }
11765
11766 if ([itemKey isEqualToString:@"EQ_TRUMBLE"])
11767 {
11768 return [self trumbleCount] > 0;
11769 }
11770
11771 return NO;
11772}
11773
11774
11775- (BOOL) hasPrimaryWeapon:(OOWeaponType)weaponType
11776{
11777 if ([[forward_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
11778 [[aft_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
11779 [[port_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
11780 [[starboard_weapon_type identifier] isEqualToString:[weaponType identifier]])
11781 {
11782 return YES;
11783 }
11784
11785 return [super hasPrimaryWeapon:weaponType];
11786}
11787
11788
11789- (BOOL) removeExternalStore:(OOEquipmentType *)eqType
11790{
11791 NSString *identifier = [eqType identifier];
11792
11793 // Look for matching missile.
11794 unsigned i;
11795 for (i = 0; i < max_missiles; i++)
11796 {
11797 if ([[self missileForPylon:i] hasPrimaryRole:identifier])
11798 {
11799 [self removeFromPylon:i];
11800
11801 // Just remove one at a time.
11802 return YES;
11803 }
11804 }
11805 return NO;
11806}
11807
11808
11809- (BOOL) removeFromPylon:(NSUInteger)pylon
11810{
11811 if (pylon >= max_missiles) return NO;
11812
11813 if (missile_entity[pylon] != nil)
11814 {
11815 NSString *identifier = [missile_entity[pylon] primaryRole];
11816 [super removeExternalStore:[OOEquipmentType equipmentTypeWithIdentifier:identifier]];
11817
11818 // Remove the missile (must wait until we've finished with its identifier string!)
11819 [missile_entity[pylon] release];
11820 missile_entity[pylon] = nil;
11821
11822 [self tidyMissilePylons];
11823
11824 // This should be the currently selected missile, deselect it.
11825 if (pylon <= activeMissile)
11826 {
11827 if (activeMissile == missiles && missiles > 0) activeMissile--;
11828 if (activeMissile > 0) activeMissile--;
11829 else activeMissile = max_missiles - 1;
11830
11831 [self selectNextMissile];
11832 }
11833
11834 return YES;
11835 }
11836
11837 return NO;
11838}
11839
11840
11841- (NSUInteger) parcelCount
11842{
11843 return [parcels count];
11844}
11845
11846
11847- (NSUInteger) passengerCount
11848{
11849 return [passengers count];
11850}
11851
11852
11853- (NSUInteger) passengerCapacity
11854{
11855 return max_passengers;
11856}
11857
11858
11859- (BOOL) hasHostileTarget
11860{
11861 ShipEntity *playersTarget = [self primaryTarget];
11862 return ([playersTarget isShip] && [playersTarget hasHostileTarget] && [playersTarget primaryTarget] == self);
11863}
11864
11865
11866- (void) receiveCommsMessage:(NSString *) message_text from:(ShipEntity *) other
11867{
11868 if ([self status] == STATUS_DEAD || [self status] == STATUS_DOCKED)
11869 {
11870 // only when in flight
11871 return;
11872 }
11873 [UNIVERSE addCommsMessage:[NSString stringWithFormat:@"%@:\n %@", [other displayName], message_text] forCount:4.5];
11874 [super receiveCommsMessage:message_text from:other];
11875}
11876
11877
11878- (void) getFined
11879{
11880 if (legalStatus == 0) return; // nothing to pay for
11881
11882 OOGovernmentID local_gov = [[UNIVERSE currentSystemData] oo_intForKey:KEY_GOVERNMENT];
11883 if ([UNIVERSE inInterstellarSpace]) local_gov = 1; // equivalent to Feudal. I'm assuming any station in interstellar space is military. -- Ahruman 2008-05-29
11884 OOCreditsQuantity fine = 500 + ((local_gov < 2 || local_gov > 5) ? 500 : 0);
11885 fine *= legalStatus;
11886 if (fine > credits)
11887 {
11888 int payback = (int)(legalStatus * credits / fine);
11889 [self setBounty:(legalStatus-payback) withReason:kOOLegalStatusReasonPaidFine];
11890 credits = 0;
11891 }
11892 else
11893 {
11894 [self setBounty:0 withReason:kOOLegalStatusReasonPaidFine];
11895 credits -= fine;
11896 }
11897
11898 // one of the fined-@-credits strings includes expansion tokens
11899 NSString *fined_message = [NSString stringWithFormat:OOExpandKey(@"fined-@-credits"), OOCredits(fine)];
11900 [self addMessageToReport:fined_message];
11901 [UNIVERSE forceWitchspaceEntries];
11902 ship_clock_adjust += 24 * 3600; // take up a day
11903}
11904
11905
11906- (void) adjustTradeInFactorBy:(int)value
11907{
11908 ship_trade_in_factor += value;
11909 if (ship_trade_in_factor < 75) ship_trade_in_factor = 75;
11910 if (ship_trade_in_factor > 100) ship_trade_in_factor = 100;
11911}
11912
11913
11914- (int) tradeInFactor
11915{
11916 return ship_trade_in_factor;
11917}
11918
11919
11920- (double) renovationCosts
11921{
11922 // 5% of value of ships wear + correction for missing subentities.
11923 OOCreditsQuantity shipValue = [UNIVERSE tradeInValueForCommanderDictionary:[self commanderDataDictionary]];
11924
11925 double costs = 0.005 * (100 - ship_trade_in_factor) * shipValue;
11926 costs += 0.01 * shipValue * [self missingSubEntitiesAdjustment];
11927 costs *= [self renovationFactor];
11928 return cunningFee(costs, 0.05);
11929}
11930
11931
11932- (double) renovationFactor
11933{
11935 NSDictionary *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]];
11936 return [shipyardInfo oo_doubleForKey:KEY_RENOVATION_MULTIPLIER defaultValue:1.0];
11937}
11938
11939
11940- (void) setDefaultViewOffsets
11941{
11942 float halfLength = 0.5f * (boundingBox.max.z - boundingBox.min.z);
11943 float halfWidth = 0.5f * (boundingBox.max.x - boundingBox.min.x);
11944
11945 forwardViewOffset = make_vector(0.0f, 0.0f, boundingBox.max.z - halfLength);
11946 aftViewOffset = make_vector(0.0f, 0.0f, boundingBox.min.z + halfLength);
11947 portViewOffset = make_vector(boundingBox.min.x + halfWidth, 0.0f, 0.0f);
11948 starboardViewOffset = make_vector(boundingBox.max.x - halfWidth, 0.0f, 0.0f);
11949 customViewOffset = kZeroVector;
11950}
11951
11952
11953- (void) setDefaultCustomViews
11954{
11955 NSArray *customViews = [[[OOShipRegistry sharedRegistry] shipInfoForKey:PLAYER_SHIP_DESC] oo_arrayForKey:@"custom_views"];
11956
11957 [_customViews release];
11958 _customViews = nil;
11959 _customViewIndex = 0;
11960 if (customViews != nil)
11961 {
11962 _customViews = [customViews retain];
11963 }
11964}
11965
11966
11967- (Vector) weaponViewOffset
11968{
11969 switch (currentWeaponFacing)
11970 {
11972 return forwardViewOffset;
11973 case WEAPON_FACING_AFT:
11974 return aftViewOffset;
11975 case WEAPON_FACING_PORT:
11976 return portViewOffset;
11978 return starboardViewOffset;
11979
11980 case WEAPON_FACING_NONE:
11981 // N.b.: this case should never happen.
11982 return customViewOffset;
11983 }
11984 return kZeroVector;
11985}
11986
11987
11988- (void) setUpTrumbles
11989{
11990 NSMutableString *trumbleDigrams = [NSMutableString stringWithCapacity:256];
11991 unichar xchar = (unichar)0;
11992 unichar digramchars[2];
11993
11994 while ([trumbleDigrams length] < PLAYER_MAX_TRUMBLES + 2)
11995 {
11996 NSString *commanderName = [self commanderName];
11997 if ([commanderName length] > 0)
11998 {
11999 [trumbleDigrams appendFormat:@"%@%@", commanderName, [[self mesh] modelName]];
12000 }
12001 else
12002 {
12003 [trumbleDigrams appendString:@"Some Random Text!"];
12004 }
12005 }
12006 int i;
12007 for (i = 0; i < PLAYER_MAX_TRUMBLES; i++)
12008 {
12009 digramchars[0] = ([trumbleDigrams characterAtIndex:i] & 0x007f) | 0x0020;
12010 digramchars[1] = (([trumbleDigrams characterAtIndex:i + 1] ^ xchar) & 0x007f) | 0x0020;
12011 xchar = digramchars[0];
12012 NSString *digramstring = [NSString stringWithCharacters:digramchars length:2];
12013 [trumble[i] release];
12014 trumble[i] = [[OOTrumble alloc] initForPlayer:self digram:digramstring];
12015 }
12016
12017 trumbleCount = 0;
12018
12019 [self setTrumbleAppetiteAccumulator:0.0f];
12020}
12021
12022
12023- (void) addTrumble:(OOTrumble *)papaTrumble
12024{
12025 if (trumbleCount >= PLAYER_MAX_TRUMBLES)
12026 {
12027 return;
12028 }
12029 OOTrumble *trumblePup = trumble[trumbleCount];
12030 [trumblePup spawnFrom:papaTrumble];
12031 trumbleCount++;
12032}
12033
12034
12035- (void) removeTrumble:(OOTrumble *)deadTrumble
12036{
12037 if (trumbleCount <= 0)
12038 {
12039 return;
12040 }
12041 NSUInteger trumble_index = NSNotFound;
12042 NSUInteger i;
12043
12044 for (i = 0; (trumble_index == NSNotFound)&&(i < trumbleCount); i++)
12045 {
12046 if (trumble[i] == deadTrumble)
12047 trumble_index = i;
12048 }
12049 if (trumble_index == NSNotFound)
12050 {
12051 OOLog(@"trumble.zombie", @"DEBUG can't get rid of inactive trumble %@", deadTrumble);
12052 return;
12053 }
12054 trumbleCount--; // reduce number of trumbles
12055 trumble[trumble_index] = trumble[trumbleCount]; // swap with the current last trumble
12056 trumble[trumbleCount] = deadTrumble; // swap with the current last trumble
12057}
12058
12059
12060- (OOTrumble**) trumbleArray
12061{
12062 return trumble;
12063}
12064
12065
12066- (NSUInteger) trumbleCount
12067{
12068 return trumbleCount;
12069}
12070
12071
12072- (id)trumbleValue
12073{
12074 NSString *namekey = [NSString stringWithFormat:@"%@-humbletrash", [self commanderName]];
12075 int trumbleHash;
12076
12078 [self mungChecksumWithNSString:[self commanderName]];
12079 munge_checksum(credits);
12080 munge_checksum(ship_kills);
12081 trumbleHash = munge_checksum(trumbleCount);
12082
12083 [[NSUserDefaults standardUserDefaults] setInteger:trumbleHash forKey:namekey];
12084
12085 int i;
12086 NSMutableArray *trumbleArray = [NSMutableArray arrayWithCapacity:PLAYER_MAX_TRUMBLES];
12087 for (i = 0; i < PLAYER_MAX_TRUMBLES; i++)
12088 {
12089 [trumbleArray addObject:[trumble[i] dictionary]];
12090 }
12091
12092 return [NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:trumbleCount], [NSNumber numberWithInt:trumbleHash], trumbleArray, nil];
12093}
12094
12095
12096- (void) setTrumbleValueFrom:(NSObject*) trumbleValue
12097{
12098 BOOL info_failed = NO;
12099 int trumbleHash;
12100 int putativeHash = 0;
12101 int putativeNTrumbles = 0;
12102 NSArray *putativeTrumbleArray = nil;
12103 int i;
12104 NSString *namekey = [NSString stringWithFormat:@"%@-humbletrash", [self commanderName]];
12105
12106 [self setUpTrumbles];
12107
12108 if (trumbleValue)
12109 {
12110 BOOL possible_cheat = NO;
12111 if (![trumbleValue isKindOfClass:[NSArray class]])
12112 info_failed = YES;
12113 else
12114 {
12115 NSArray* values = (NSArray*) trumbleValue;
12116 if ([values count] >= 1)
12117 putativeNTrumbles = [values oo_intAtIndex:0];
12118 if ([values count] >= 2)
12119 putativeHash = [values oo_intAtIndex:1];
12120 if ([values count] >= 3)
12121 putativeTrumbleArray = [values oo_arrayAtIndex:2];
12122 }
12123 // calculate a hash for the putative values
12125 [self mungChecksumWithNSString:[self commanderName]];
12126 munge_checksum(credits);
12127 munge_checksum(ship_kills);
12128 trumbleHash = munge_checksum(putativeNTrumbles);
12129
12130 if (putativeHash != trumbleHash)
12131 info_failed = YES;
12132
12133 if (info_failed)
12134 {
12135 OOLog(@"cheat.tentative", @"%@", @"POSSIBLE CHEAT DETECTED");
12136 possible_cheat = YES;
12137 }
12138
12139 for (i = 1; (info_failed)&&(i < PLAYER_MAX_TRUMBLES); i++)
12140 {
12141 // try to determine trumbleCount from the key in the saved game
12143 [self mungChecksumWithNSString:[self commanderName]];
12144 munge_checksum(credits);
12145 munge_checksum(ship_kills);
12146 trumbleHash = munge_checksum(i);
12147 if (putativeHash == trumbleHash)
12148 {
12149 info_failed = NO;
12150 putativeNTrumbles = i;
12151 }
12152 }
12153
12154 if (possible_cheat && !info_failed)
12155 OOLog(@"cheat.verified", @"%@", @"CHEAT DEFEATED - that's not the way to get rid of trumbles!");
12156 }
12157 else
12158 // if trumbleValue comes in as nil, then probably someone has toyed with the save file
12159 // by removing the entire trumbles array
12160 {
12161 OOLog(@"cheat.tentative", @"%@", @"POSSIBLE CHEAT DETECTED");
12162 info_failed = YES;
12163 }
12164
12165 if (info_failed && [[NSUserDefaults standardUserDefaults] objectForKey:namekey])
12166 {
12167 // try to determine trumbleCount from the key in user defaults
12168 putativeHash = (int)[[NSUserDefaults standardUserDefaults] integerForKey:namekey];
12169 for (i = 1; (info_failed)&&(i < PLAYER_MAX_TRUMBLES); i++)
12170 {
12172 [self mungChecksumWithNSString:[self commanderName]];
12173 munge_checksum(credits);
12174 munge_checksum(ship_kills);
12175 trumbleHash = munge_checksum(i);
12176 if (putativeHash == trumbleHash)
12177 {
12178 info_failed = NO;
12179 putativeNTrumbles = i;
12180 }
12181 }
12182
12183 if (!info_failed)
12184 OOLog(@"cheat.verified", @"%@", @"CHEAT DEFEATED - that's not the way to get rid of trumbles!");
12185 }
12186 // at this stage we've done the best we can to stop cheaters
12187 trumbleCount = putativeNTrumbles;
12188
12189 if ((putativeTrumbleArray != nil) && ([putativeTrumbleArray count] == PLAYER_MAX_TRUMBLES))
12190 {
12191 for (i = 0; i < PLAYER_MAX_TRUMBLES; i++)
12192 [trumble[i] setFromDictionary:[putativeTrumbleArray oo_dictionaryAtIndex:i]];
12193 }
12194
12196 [self mungChecksumWithNSString:[self commanderName]];
12197 munge_checksum(credits);
12198 munge_checksum(ship_kills);
12199 trumbleHash = munge_checksum(trumbleCount);
12200
12201 [[NSUserDefaults standardUserDefaults] setInteger:trumbleHash forKey:namekey];
12202}
12203
12204
12205- (float) trumbleAppetiteAccumulator
12206{
12207 return _trumbleAppetiteAccumulator;
12208}
12209
12210
12211- (void) setTrumbleAppetiteAccumulator:(float)value
12212{
12213 _trumbleAppetiteAccumulator = value;
12214}
12215
12216
12217- (void) mungChecksumWithNSString:(NSString *)str
12218{
12219 if (str == nil) return;
12220
12221 NSUInteger i, length = [str length];
12222 for (i = 0; i < length; i++)
12223 {
12224 munge_checksum([str characterAtIndex:i]);
12225 }
12226}
12227
12228
12229- (NSString *) screenModeStringForWidth:(unsigned)width height:(unsigned)height refreshRate:(float)refreshRate
12230{
12231 if (0.0f != refreshRate)
12232 {
12233 return OOExpandKey(@"gameoptions-fullscreen-with-refresh-rate", width, height, refreshRate);
12234 }
12235 else
12236 {
12237 return OOExpandKey(@"gameoptions-fullscreen", width, height);
12238 }
12239}
12240
12241
12242- (void) suppressTargetLost
12243{
12244 suppressTargetLost = YES;
12245}
12246
12247
12248- (void) setScoopsActive
12249{
12250 scoopsActive = YES;
12251}
12252
12253
12254// override shipentity to stop foundTarget being changed during escape sequence
12255- (void) setFoundTarget:(Entity *) targetEntity
12256{
12257 /* Rare, but can happen, e.g. if a Q-mine goes off nearby during
12258 * the sequence */
12259 if ([self status] == STATUS_ESCAPE_SEQUENCE)
12260 {
12261 return;
12262 }
12263 [_foundTarget release];
12264 _foundTarget = [targetEntity weakRetain];
12265}
12266
12267
12268// override shipentity addTarget to implement target_memory
12269- (void) addTarget:(Entity *) targetEntity
12270{
12271 if ([self status] != STATUS_IN_FLIGHT && [self status] != STATUS_WITCHSPACE_COUNTDOWN) return;
12272 if (targetEntity == self) return;
12273
12274 [super addTarget:targetEntity];
12275
12276 if ([targetEntity isWormhole])
12277 {
12278 assert ([self hasEquipmentItemProviding:@"EQ_WORMHOLE_SCANNER"]);
12279 [self addScannedWormhole:(WormholeEntity*)targetEntity];
12280 }
12281 // wormholes don't go in target memory
12282 else if ([self hasEquipmentItemProviding:@"EQ_TARGET_MEMORY"] && targetEntity != nil)
12283 {
12284 OOWeakReference *targetRef = [targetEntity weakSelf];
12285 NSUInteger i = [target_memory indexOfObject:targetRef];
12286 // if already in target memory, preserve that and just change the index
12287 if (i != NSNotFound)
12288 {
12289 target_memory_index = i;
12290 }
12291 else
12292 {
12293 i = [target_memory indexOfObject:[NSNull null]];
12294 // find and use a blank space in memory
12295 if (i != NSNotFound)
12296 {
12297 [target_memory replaceObjectAtIndex:i withObject:targetRef];
12298 target_memory_index = i;
12299 }
12300 else
12301 {
12302 // use the next memory space
12303 target_memory_index = (target_memory_index + 1) % PLAYER_TARGET_MEMORY_SIZE;
12304 [target_memory replaceObjectAtIndex:target_memory_index withObject:targetRef];
12305 }
12306 }
12307 }
12308
12309 if (ident_engaged)
12310 {
12311 [self playIdentLockedOn];
12312 [self printIdentLockedOnForMissile:NO];
12313 }
12314 else if ([targetEntity isShip] && [self weaponsOnline]) // Only let missiles target-lock onto ships
12315 {
12316 if ([missile_entity[activeMissile] isMissile])
12317 {
12318 missile_status = MISSILE_STATUS_TARGET_LOCKED;
12319 [missile_entity[activeMissile] addTarget:targetEntity];
12320 [self playMissileLockedOn];
12321 [self printIdentLockedOnForMissile:YES];
12322 }
12323 else // It's a mine or something
12324 {
12325 missile_status = MISSILE_STATUS_ARMED;
12326 [self playIdentLockedOn];
12327 [self printIdentLockedOnForMissile:NO];
12328 }
12329 }
12330}
12331
12332
12333- (void) clearTargetMemory
12334{
12335 NSUInteger memoryCount = [target_memory count];
12336 for (NSUInteger i = 0; i < PLAYER_TARGET_MEMORY_SIZE; i++)
12337 {
12338 if (i < memoryCount)
12339 {
12340 [target_memory replaceObjectAtIndex:i withObject:[NSNull null]];
12341 }
12342 else
12343 {
12344 [target_memory addObject:[NSNull null]];
12345 }
12346 }
12347 target_memory_index = 0;
12348}
12349
12350
12351- (NSMutableArray *) targetMemory
12352{
12353 return target_memory;
12354}
12355
12356- (BOOL) moveTargetMemoryBy:(NSInteger)delta
12357{
12358 unsigned i = 0;
12359 while (i++ < PLAYER_TARGET_MEMORY_SIZE) // limit loops
12360 {
12361 NSInteger idx = (NSInteger)target_memory_index + delta;
12362 while (idx < 0) idx += PLAYER_TARGET_MEMORY_SIZE;
12364 target_memory_index = idx;
12365
12366 id targ_id = [target_memory objectAtIndex:target_memory_index];
12367 if ([targ_id isProxy])
12368 {
12369 ShipEntity *potential_target = [(OOWeakReference *)targ_id weakRefUnderlyingObject];
12370
12371 if ((potential_target)&&(potential_target->isShip)&&([potential_target isInSpace]))
12372 {
12373 if (potential_target->zero_distance < SCANNER_MAX_RANGE2 && (![potential_target isCloaked]))
12374 {
12375 [super addTarget:potential_target];
12376 if (missile_status != MISSILE_STATUS_SAFE)
12377 {
12378 if( [missile_entity[activeMissile] isMissile])
12379 {
12380 [missile_entity[activeMissile] addTarget:potential_target];
12381 missile_status = MISSILE_STATUS_TARGET_LOCKED;
12382 [self printIdentLockedOnForMissile:YES];
12383 }
12384 else
12385 {
12386 missile_status = MISSILE_STATUS_ARMED;
12387 [self playIdentLockedOn];
12388 [self printIdentLockedOnForMissile:NO];
12389 }
12390 }
12391 else
12392 {
12393 ident_engaged = YES;
12394 [self printIdentLockedOnForMissile:NO];
12395 }
12396 [self playTargetSwitched];
12397 return YES;
12398 }
12399 }
12400 else
12401 {
12402 [target_memory replaceObjectAtIndex:target_memory_index withObject:[NSNull null]];
12403 }
12404 }
12405 }
12406
12407 [self playNoTargetInMemory];
12408 return NO;
12409}
12410
12411
12412- (void) printIdentLockedOnForMissile:(BOOL)missile
12413{
12414 if ([self primaryTarget] == nil) return;
12415
12416 NSString *fmt = missile ? @"missile-locked-onto-target" : @"ident-locked-onto-target";
12417 NSString *target = [[self primaryTarget] identFromShip:self];
12418 [UNIVERSE addMessage:OOExpandKey(fmt, target) forCount:4.5];
12419}
12420
12421
12422- (Quaternion) customViewQuaternion
12423{
12424 return customViewQuaternion;
12425}
12426
12427
12428- (void) setCustomViewQuaternion:(Quaternion)q
12429{
12430 customViewQuaternion = q;
12431 [self setCustomViewData];
12432}
12433
12434
12435- (OOMatrix) customViewMatrix
12436{
12437 return customViewMatrix;
12438}
12439
12440
12441- (Vector) customViewOffset
12442{
12443 return customViewOffset;
12444}
12445
12446
12447- (void) setCustomViewOffset:(Vector) offset
12448{
12449 customViewOffset = offset;
12450}
12451
12452
12453- (Vector) customViewRotationCenter
12454{
12455 return customViewRotationCenter;
12456}
12457
12458
12459- (void) setCustomViewRotationCenter:(Vector) center
12460{
12461 customViewRotationCenter = center;
12462}
12463
12464
12465- (void) customViewZoomIn:(OOScalar) rate
12466{
12467 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12468 customViewOffset = vector_multiply_scalar(customViewOffset, 1.0/rate);
12469 OOScalar m = magnitude(customViewOffset);
12470 if (m < CUSTOM_VIEW_MAX_ZOOM_IN * collision_radius)
12471 {
12472 scale_vector(&customViewOffset, CUSTOM_VIEW_MAX_ZOOM_IN * collision_radius / m);
12473 }
12474 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12475}
12476
12477
12478- (void) customViewZoomOut:(OOScalar) rate
12479{
12480 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12481 customViewOffset = vector_multiply_scalar(customViewOffset, rate);
12482 OOScalar m = magnitude(customViewOffset);
12483 if (m > CUSTOM_VIEW_MAX_ZOOM_OUT * collision_radius)
12484 {
12485 scale_vector(&customViewOffset, CUSTOM_VIEW_MAX_ZOOM_OUT * collision_radius / m);
12486 }
12487 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12488}
12489
12490
12491- (void) customViewRotateLeft:(OOScalar) angle
12492{
12493 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12494 OOScalar m = magnitude(customViewOffset);
12495 quaternion_rotate_about_axis(&customViewQuaternion, customViewUpVector, -angle);
12496 [self setCustomViewData];
12497 customViewOffset = vector_flip(customViewForwardVector);
12498 scale_vector(&customViewOffset, m / magnitude(customViewOffset));
12499 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12500}
12501
12502
12503- (void) customViewRotateRight:(OOScalar) angle
12504{
12505 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12506 OOScalar m = magnitude(customViewOffset);
12507 quaternion_rotate_about_axis(&customViewQuaternion, customViewUpVector, angle);
12508 [self setCustomViewData];
12509 customViewOffset = vector_flip(customViewForwardVector);
12510 scale_vector(&customViewOffset, m / magnitude(customViewOffset));
12511 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12512}
12513
12514
12515- (void) customViewRotateUp:(OOScalar) angle
12516{
12517 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12518 OOScalar m = magnitude(customViewOffset);
12519 quaternion_rotate_about_axis(&customViewQuaternion, customViewRightVector, -angle);
12520 [self setCustomViewData];
12521 customViewOffset = vector_flip(customViewForwardVector);
12522 scale_vector(&customViewOffset, m / magnitude(customViewOffset));
12523 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12524}
12525
12526
12527- (void) customViewRotateDown:(OOScalar) angle
12528{
12529 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12530 OOScalar m = magnitude(customViewOffset);
12531 quaternion_rotate_about_axis(&customViewQuaternion, customViewRightVector, angle);
12532 [self setCustomViewData];
12533 customViewOffset = vector_flip(customViewForwardVector);
12534 scale_vector(&customViewOffset, m / magnitude(customViewOffset));
12535 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12536}
12537
12538
12539- (void) customViewRollRight:(OOScalar) angle
12540{
12541 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12542 OOScalar m = magnitude(customViewOffset);
12543 quaternion_rotate_about_axis(&customViewQuaternion, customViewForwardVector, -angle);
12544 [self setCustomViewData];
12545 customViewOffset = vector_flip(customViewForwardVector);
12546 scale_vector(&customViewOffset, m / magnitude(customViewOffset));
12547 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12548}
12549
12550
12551- (void) customViewRollLeft:(OOScalar) angle
12552{
12553 customViewOffset = vector_subtract(customViewOffset, customViewRotationCenter);
12554 OOScalar m = magnitude(customViewOffset);
12555 quaternion_rotate_about_axis(&customViewQuaternion, customViewForwardVector, angle);
12556 [self setCustomViewData];
12557 customViewOffset = vector_flip(customViewForwardVector);
12558 scale_vector(&customViewOffset, m / magnitude(customViewOffset));
12559 customViewOffset = vector_add(customViewOffset, customViewRotationCenter);
12560}
12561
12562
12563- (void) customViewPanUp:(OOScalar) angle
12564{
12565 quaternion_rotate_about_axis(&customViewQuaternion, customViewRightVector, angle);
12566 [self setCustomViewData];
12567 customViewRotationCenter = vector_subtract(customViewOffset, vector_multiply_scalar(customViewForwardVector, dot_product(customViewOffset, customViewForwardVector)));
12568}
12569
12570
12571- (void) customViewPanDown:(OOScalar) angle
12572{
12573 quaternion_rotate_about_axis(&customViewQuaternion, customViewRightVector, -angle);
12574 [self setCustomViewData];
12575 customViewRotationCenter = vector_subtract(customViewOffset, vector_multiply_scalar(customViewForwardVector, dot_product(customViewOffset, customViewForwardVector)));
12576}
12577
12578
12579- (void) customViewPanLeft:(OOScalar) angle
12580{
12581 quaternion_rotate_about_axis(&customViewQuaternion, customViewUpVector, angle);
12582 [self setCustomViewData];
12583 customViewRotationCenter = vector_subtract(customViewOffset, vector_multiply_scalar(customViewForwardVector, dot_product(customViewOffset, customViewForwardVector)));
12584}
12585
12586
12587- (void) customViewPanRight:(OOScalar) angle
12588{
12589 quaternion_rotate_about_axis(&customViewQuaternion, customViewUpVector, -angle);
12590 [self setCustomViewData];
12591 customViewRotationCenter = vector_subtract(customViewOffset, vector_multiply_scalar(customViewForwardVector, dot_product(customViewOffset, customViewForwardVector)));
12592}
12593
12594
12595- (Vector) customViewForwardVector
12596{
12597 return customViewForwardVector;
12598}
12599
12600
12601- (Vector) customViewUpVector
12602{
12603 return customViewUpVector;
12604}
12605
12606
12607- (Vector) customViewRightVector
12608{
12609 return customViewRightVector;
12610}
12611
12612
12613- (NSString *) customViewDescription
12614{
12615 return customViewDescription;
12616}
12617
12618
12619- (void) resetCustomView
12620{
12621 [self setCustomViewDataFromDictionary:[_customViews oo_dictionaryAtIndex:_customViewIndex] withScaling:NO];
12622}
12623
12624
12625- (void) setCustomViewData
12626{
12627 customViewRightVector = vector_right_from_quaternion(customViewQuaternion);
12628 customViewUpVector = vector_up_from_quaternion(customViewQuaternion);
12629 customViewForwardVector = vector_forward_from_quaternion(customViewQuaternion);
12630
12631 Quaternion q1 = customViewQuaternion;
12632 q1.w = -q1.w;
12633 customViewMatrix = OOMatrixForQuaternionRotation(q1);
12634}
12635
12636- (void) setCustomViewDataFromDictionary:(NSDictionary *)viewDict withScaling:(BOOL)withScaling
12637{
12638 customViewMatrix = kIdentityMatrix;
12639 customViewOffset = kZeroVector;
12640 if (viewDict == nil) return;
12641
12642 customViewQuaternion = [viewDict oo_quaternionForKey:@"view_orientation"];
12643 [self setCustomViewData];
12644
12645 // easier to do the multiplication at this point than at load time
12646 if (withScaling)
12647 {
12648 customViewOffset = vector_multiply_scalar([viewDict oo_vectorForKey:@"view_position"],_scaleFactor);
12649 }
12650 else
12651 {
12652 // but don't do this when the custom view is set through JS
12653 customViewOffset = [viewDict oo_vectorForKey:@"view_position"];
12654 }
12655 customViewRotationCenter = vector_subtract(customViewOffset, vector_multiply_scalar(customViewForwardVector, dot_product(customViewOffset, customViewForwardVector)));
12656 customViewDescription = [viewDict oo_stringForKey:@"view_description"];
12657
12658 NSString *facing = [[viewDict oo_stringForKey:@"weapon_facing"] lowercaseString];
12659 if ([facing isEqual:@"aft"])
12660 {
12661 currentWeaponFacing = WEAPON_FACING_AFT;
12662 }
12663 else if ([facing isEqual:@"port"])
12664 {
12665 currentWeaponFacing = WEAPON_FACING_PORT;
12666 }
12667 else if ([facing isEqual:@"starboard"])
12668 {
12669 currentWeaponFacing = WEAPON_FACING_STARBOARD;
12670 }
12671 else if ([facing isEqual:@"forward"])
12672 {
12673 currentWeaponFacing = WEAPON_FACING_FORWARD;
12674 }
12675 // if the weapon facing is unset / unknown,
12676 // don't change current weapon facing!
12677}
12678
12679
12680- (BOOL) showInfoFlag
12681{
12682 return show_info_flag;
12683}
12684
12685
12686- (NSDictionary *) missionOverlayDescriptor
12687{
12688 return _missionOverlayDescriptor;
12689}
12690
12691
12692- (NSDictionary *) missionOverlayDescriptorOrDefault
12693{
12694 NSDictionary *result = [self missionOverlayDescriptor];
12695 if (result == nil)
12696 {
12697 if ([[self missionTitle] length] == 0)
12698 {
12699 result = [UNIVERSE screenTextureDescriptorForKey:@"mission_overlay_no_title"];
12700 }
12701 else
12702 {
12703 result = [UNIVERSE screenTextureDescriptorForKey:@"mission_overlay_with_title"];
12704 }
12705 }
12706
12707 return result;
12708}
12709
12710
12711- (void) setMissionOverlayDescriptor:(NSDictionary *)descriptor
12712{
12713 if (descriptor != _missionOverlayDescriptor)
12714 {
12715 [_missionOverlayDescriptor autorelease];
12716 _missionOverlayDescriptor = [descriptor copy];
12717 }
12718}
12719
12720
12721- (NSDictionary *) missionBackgroundDescriptor
12722{
12723 return _missionBackgroundDescriptor;
12724}
12725
12726
12727- (NSDictionary *) missionBackgroundDescriptorOrDefault
12728{
12729 NSDictionary *result = [self missionBackgroundDescriptor];
12730 if (result == nil)
12731 {
12732 result = [UNIVERSE screenTextureDescriptorForKey:@"mission"];
12733 }
12734
12735 return result;
12736}
12737
12738
12739- (void) setMissionBackgroundDescriptor:(NSDictionary *)descriptor
12740{
12741 if (descriptor != _missionBackgroundDescriptor)
12742 {
12743 [_missionBackgroundDescriptor autorelease];
12744 _missionBackgroundDescriptor = [descriptor copy];
12745 }
12746}
12747
12748
12749- (OOGUIBackgroundSpecial) missionBackgroundSpecial
12750{
12751 return _missionBackgroundSpecial;
12752}
12753
12754
12755- (void) setMissionBackgroundSpecial:(NSString *)special
12756{
12757 if (special == nil) {
12758 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_NONE;
12759 }
12760 else if ([special isEqualToString:@"SHORT_RANGE_CHART"])
12761 {
12762 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_SHORT;
12763 }
12764 else if ([special isEqualToString:@"SHORT_RANGE_CHART_SHORTEST"])
12765 {
12766 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"])
12767 {
12768 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_SHORT_ANA_SHORTEST;
12769 }
12770 else
12771 {
12772 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_SHORT;
12773 }
12774 }
12775 else if ([special isEqualToString:@"SHORT_RANGE_CHART_QUICKEST"])
12776 {
12777 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"])
12778 {
12779 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_SHORT_ANA_QUICKEST;
12780 }
12781 else
12782 {
12783 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_SHORT;
12784 }
12785 }
12786 else if ([special isEqualToString:@"CUSTOM_CHART"])
12787 {
12788 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_CUSTOM;
12789 }
12790 else if ([special isEqualToString:@"CUSTOM_CHART_SHORTEST"])
12791 {
12792 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"])
12793 {
12794 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_SHORTEST;
12795 }
12796 else
12797 {
12798 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_CUSTOM;
12799 }
12800 }
12801 else if ([special isEqualToString:@"CUSTOM_CHART_QUICKEST"])
12802 {
12803 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"])
12804 {
12805 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_QUICKEST;
12806 }
12807 else
12808 {
12809 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_CUSTOM;
12810 }
12811 }
12812 else if ([special isEqualToString:@"LONG_RANGE_CHART"])
12813 {
12814 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG;
12815 }
12816 else if ([special isEqualToString:@"LONG_RANGE_CHART_SHORTEST"])
12817 {
12818 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"])
12819 {
12820 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST;
12821 }
12822 else
12823 {
12824 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG;
12825 }
12826 }
12827 else if ([special isEqualToString:@"LONG_RANGE_CHART_QUICKEST"])
12828 {
12829 if ([self hasEquipmentItemProviding:@"EQ_ADVANCED_NAVIGATIONAL_ARRAY"])
12830 {
12831 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST;
12832 }
12833 else
12834 {
12835 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_LONG;
12836 }
12837 }
12838 else
12839 {
12840 _missionBackgroundSpecial = GUI_BACKGROUND_SPECIAL_NONE;
12841 }
12842}
12843
12844
12845- (void) setMissionExitScreen:(OOGUIScreenID)screen
12846{
12847 _missionExitScreen = screen;
12848}
12849
12850
12851- (OOGUIScreenID) missionExitScreen
12852{
12853 return _missionExitScreen;
12854}
12855
12856
12857- (NSDictionary *) equipScreenBackgroundDescriptor
12858{
12859 return _equipScreenBackgroundDescriptor;
12860}
12861
12862
12863- (void) setEquipScreenBackgroundDescriptor:(NSDictionary *)descriptor
12864{
12865 if (descriptor != _equipScreenBackgroundDescriptor)
12866 {
12867 [_equipScreenBackgroundDescriptor autorelease];
12868 _equipScreenBackgroundDescriptor = [descriptor copy];
12869 }
12870}
12871
12872
12873- (BOOL) scriptsLoaded
12874{
12875 return worldScripts != nil && [worldScripts count] > 0;
12876}
12877
12878
12879- (NSArray *) worldScriptNames
12880{
12881 return [worldScripts allKeys];
12882}
12883
12884
12885- (NSDictionary *) worldScriptsByName
12886{
12887 return [[worldScripts copy] autorelease];
12888}
12889
12890
12891- (OOScript *) commodityScriptNamed:(NSString *)scriptName
12892{
12893 if (scriptName == nil)
12894 {
12895 return nil;
12896 }
12897 OOScript *cscript = nil;
12898 if ((cscript = [commodityScripts objectForKey:scriptName]))
12899 {
12900 return cscript;
12901 }
12902 cscript = [OOScript jsScriptFromFileNamed:scriptName properties:nil];
12903 if (cscript != nil)
12904 {
12905 // storing it in here retains it
12906 [commodityScripts setObject:cscript forKey:scriptName];
12907 }
12908 else
12909 {
12910 OOLog(@"script.commodityScript.load",@"Could not load script %@",scriptName);
12911 }
12912 return cscript;
12913}
12914
12915
12916- (void) doScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc
12917{
12918 [super doScriptEvent:message inContext:context withArguments:argv count:argc];
12919 [self doWorldScriptEvent:message inContext:context withArguments:argv count:argc timeLimit:0.0];
12920}
12921
12922
12923- (BOOL) doWorldEventUntilMissionScreen:(jsid)message
12924{
12925 NSEnumerator *scriptEnum = [worldScripts objectEnumerator];
12926 OOScript *theScript;
12927
12928 // Check for the presence of report messages first.
12929 if (gui_screen != GUI_SCREEN_MISSION && [dockingReport length] > 0 && [self isDocked] && ![[self dockedStation] suppressArrivalReports])
12930 {
12931 [self setGuiToDockingReportScreen]; // go here instead!
12932 [[UNIVERSE messageGUI] clear];
12933 return YES;
12934 }
12935
12936 JSContext *context = OOJSAcquireContext();
12937 while ((theScript = [scriptEnum nextObject]) && gui_screen != GUI_SCREEN_MISSION && [self isDocked])
12938 {
12939 [theScript callMethod:message inContext:context withArguments:NULL count:0 result:NULL];
12940 }
12941 OOJSRelinquishContext(context);
12942
12943 if (gui_screen == GUI_SCREEN_MISSION)
12944 {
12945 // remove any comms/console messages from the screen!
12946 [[UNIVERSE messageGUI] clear];
12947 return YES;
12948 }
12949
12950 return NO;
12951}
12952
12953
12954- (void) doWorldScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc timeLimit:(OOTimeDelta)limit
12955{
12956 NSParameterAssert(context != NULL && JS_IsInRequest(context));
12957
12958 OOScript *theScript = nil;
12959
12960 foreach (theScript, [worldScripts allValues])
12961 {
12963 [theScript callMethod:message inContext:context withArguments:argv count:argc result:NULL];
12965 }
12966}
12967
12968
12969- (void) setGalacticHyperspaceBehaviour:(OOGalacticHyperspaceBehaviour)inBehaviour
12970{
12971 if (GALACTIC_HYPERSPACE_BEHAVIOUR_UNKNOWN < inBehaviour && inBehaviour <= GALACTIC_HYPERSPACE_MAX)
12972 {
12973 galacticHyperspaceBehaviour = inBehaviour;
12974 }
12975}
12976
12977
12978- (OOGalacticHyperspaceBehaviour) galacticHyperspaceBehaviour
12979{
12980 return galacticHyperspaceBehaviour;
12981}
12982
12983
12984- (void) setGalacticHyperspaceFixedCoords:(NSPoint)point
12985{
12986 return [self setGalacticHyperspaceFixedCoordsX:OOClamp_0_max_f(round(point.x), 255.0f) y:OOClamp_0_max_f(round(point.y), 255.0f)];
12987}
12988
12989
12990- (void) setGalacticHyperspaceFixedCoordsX:(unsigned char)x y:(unsigned char)y
12991{
12992 galacticHyperspaceFixedCoords.x = x;
12993 galacticHyperspaceFixedCoords.y = y;
12994}
12995
12996
12997- (NSPoint) galacticHyperspaceFixedCoords
12998{
12999 return galacticHyperspaceFixedCoords;
13000}
13001
13002
13003- (void) setWitchspaceCountdown:(int)spin_time
13004{
13005 witchspaceCountdown = spin_time;
13006}
13007
13008- (OOLongRangeChartMode) longRangeChartMode
13009{
13010 return longRangeChartMode;
13011}
13012
13013
13014- (void) setLongRangeChartMode:(OOLongRangeChartMode) mode
13015{
13016 longRangeChartMode = mode;
13017}
13018
13019
13020- (BOOL) scoopOverride
13021{
13022 return scoopOverride;
13023}
13024
13025
13026- (void) setScoopOverride:(BOOL)newValue
13027{
13028 scoopOverride = !!newValue;
13029 if (scoopOverride) [self setScoopsActive];
13030}
13031
13032
13033#if MASS_DEPENDENT_FUEL_PRICES
13034- (GLfloat) fuelChargeRate
13035{
13036 GLfloat rate = 1.0; // Standard charge rate.
13037
13038 rate = [super fuelChargeRate];
13039
13040 // Experimental: the state of repair affects the fuel charge rate - more fuel needed for jumps, etc...
13041 if (EXPECT(ship_trade_in_factor <= 90 && ship_trade_in_factor >= 75))
13042 {
13043 rate *= 2.0 - (ship_trade_in_factor / 100); // between 1.1x and 1.25x
13044 //OOLog(@"fuelPrices", @"\"%@\" - repair status: %d%%, adjusted rate to:%.2f)", [self shipDataKey], ship_trade_in_factor, rate);
13045 }
13046
13047 return rate;
13048}
13049#endif
13050
13051
13052- (void) setDockTarget:(ShipEntity *)entity
13053{
13054if ([entity isStation]) _dockTarget = [entity universalID];
13055else _dockTarget = NO_TARGET;
13056 //_dockTarget = [entity isStation] ? [entity universalID]: NO_TARGET;
13057}
13058
13059
13060- (NSString *) jumpCause
13061{
13062 return _jumpCause;
13063}
13064
13065
13066- (void) setJumpCause:(NSString *)value
13067{
13068 NSParameterAssert(value != nil);
13069 [_jumpCause autorelease];
13070 _jumpCause = [value copy];
13071}
13072
13073
13074- (NSString *) commanderName
13075{
13076 return _commanderName;
13077}
13078
13079
13080- (NSString *) lastsaveName
13081{
13082 return _lastsaveName;
13083}
13084
13085
13086- (void) setCommanderName:(NSString *)value
13087{
13088 NSParameterAssert(value != nil);
13089 [_commanderName autorelease];
13090 _commanderName = [value copy];
13091}
13092
13093
13094- (void) setLastsaveName:(NSString *)value
13095{
13096 NSParameterAssert(value != nil);
13097 [_lastsaveName autorelease];
13098 _lastsaveName = [value copy];
13099}
13100
13101
13102- (BOOL) isDocked
13103{
13104 BOOL isDockedStatus = NO;
13105
13106 switch ([self status])
13107 {
13108 case STATUS_DOCKED:
13109 case STATUS_DOCKING:
13110 case STATUS_START_GAME:
13111 isDockedStatus = YES;
13112 break;
13113 // special case - can be either docked or not, so avoid safety check below
13114 case STATUS_RESTART_GAME:
13115 return NO;
13116 case STATUS_EFFECT:
13117 case STATUS_ACTIVE:
13118 case STATUS_COCKPIT_DISPLAY:
13119 case STATUS_TEST:
13120 case STATUS_INACTIVE:
13121 case STATUS_DEAD:
13122 case STATUS_IN_FLIGHT:
13123 case STATUS_AUTOPILOT_ENGAGED:
13124 case STATUS_LAUNCHING:
13125 case STATUS_WITCHSPACE_COUNTDOWN:
13126 case STATUS_ENTERING_WITCHSPACE:
13127 case STATUS_EXITING_WITCHSPACE:
13128 case STATUS_ESCAPE_SEQUENCE:
13129 case STATUS_IN_HOLD:
13130 case STATUS_BEING_SCOOPED:
13131 case STATUS_HANDLING_ERROR:
13132 break;
13133 //no default, so that we get notified by the compiler if something is missing
13134 }
13135
13136#ifndef NDEBUG
13137 // Sanity check
13138 if (isDockedStatus)
13139 {
13140 if ([self dockedStation] == nil)
13141 {
13142 //there are a number of possible current statuses, not just STATUS_DOCKED
13143 OOLogERR(kOOLogInconsistentState, @"status is %@, but dockedStation is nil; treating as not docked. %@", OOStringFromEntityStatus([self status]), @"This is an internal error, please report it.");
13144 [self setStatus:STATUS_IN_FLIGHT];
13145 isDockedStatus = NO;
13146 }
13147 }
13148 else
13149 {
13150 if ([self dockedStation] != nil && [self status] != STATUS_LAUNCHING)
13151 {
13152 OOLogERR(kOOLogInconsistentState, @"status is %@, but dockedStation is not nil; treating as docked. %@", OOStringFromEntityStatus([self status]), @"This is an internal error, please report it.");
13153 [self setStatus:STATUS_DOCKED];
13154 isDockedStatus = YES;
13155 }
13156 }
13157#endif
13158
13159 return isDockedStatus;
13160}
13161
13162
13163- (BOOL)clearedToDock
13164{
13165 return dockingClearanceStatus > DOCKING_CLEARANCE_STATUS_REQUESTED || dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_NOT_REQUIRED;
13166}
13167
13168
13169- (void)setDockingClearanceStatus:(OODockingClearanceStatus)newValue
13170{
13171 dockingClearanceStatus = newValue;
13172 if (dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_NONE)
13173 {
13174 targetDockStation = nil;
13175 }
13176 else if (dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_REQUESTED || dockingClearanceStatus == DOCKING_CLEARANCE_STATUS_NOT_REQUIRED)
13177 {
13178 if ([[self primaryTarget] isStation])
13179 {
13180 targetDockStation = [self primaryTarget];
13181 }
13182 else
13183 {
13184 OOLog(@"player.badDockingTarget", @"Attempt to dock at %@.", [self primaryTarget]);
13185 targetDockStation = nil;
13186 dockingClearanceStatus = DOCKING_CLEARANCE_STATUS_NONE;
13187 }
13188 }
13189}
13190
13191- (OODockingClearanceStatus)getDockingClearanceStatus
13192{
13193 return dockingClearanceStatus;
13194}
13195
13196
13197- (void)penaltyForUnauthorizedDocking
13198{
13199 OOCreditsQuantity amountToPay = 0;
13200 OOCreditsQuantity calculatedFine = credits * 0.05;
13201 OOCreditsQuantity maximumFine = 50000ULL;
13202
13203 if ([self clearedToDock])
13204 return;
13205
13206 amountToPay = MIN(maximumFine, calculatedFine);
13207 credits -= amountToPay;
13208 [self addMessageToReport:[NSString stringWithFormat:DESC(@"station-docking-clearance-fined-@-cr"), OOCredits(amountToPay)]];
13209}
13210
13211
13212//
13213// Wormhole Scanner support functions
13214//
13215- (void)addScannedWormhole:(WormholeEntity*)whole
13216{
13217 assert(scannedWormholes != nil);
13218 assert(whole != nil);
13219
13220 // Only add if we don't have it already!
13221 WormholeEntity *wh = nil;
13222 foreach (wh, scannedWormholes)
13223 {
13224 if (wh == whole) return;
13225 }
13226 [whole setScannedAt:[self clockTimeAdjusted]];
13227 [scannedWormholes addObject:whole];
13228}
13229
13230// Checks through our array of wormholes for any which have expired
13231// If it is in the current system, spawn ships
13232// Else remove it
13233- (void)updateWormholes
13234{
13235 assert(scannedWormholes != nil);
13236
13237 if ([scannedWormholes count] == 0)
13238 return;
13239
13240 double now = [self clockTimeAdjusted];
13241
13242 NSMutableArray * savedWormholes = [[NSMutableArray alloc] initWithCapacity:[scannedWormholes count]];
13243 WormholeEntity *wh;
13244
13245 foreach (wh, scannedWormholes)
13246 {
13247 // TODO: Start drawing wormhole exit a few seconds before the first
13248 // ship is disgorged.
13249 if ([wh arrivalTime] > now)
13250 {
13251 [savedWormholes addObject:wh];
13252 }
13253 else if (NSEqualPoints(galaxy_coordinates, [wh destinationCoordinates]))
13254 {
13255 [wh disgorgeShips];
13256 if ([[wh shipsInTransit] count] > 0)
13257 {
13258 [savedWormholes addObject:wh];
13259 }
13260 }
13261 // Else wormhole has expired in another system, let it expire
13262 }
13263
13264 [scannedWormholes release];
13265 scannedWormholes = savedWormholes;
13266}
13267
13268
13269- (NSArray *) scannedWormholes
13270{
13271 return [NSArray arrayWithArray:scannedWormholes];
13272}
13273
13274
13275- (void) initialiseMissionDestinations:(NSDictionary *)destinations andLegacy:(NSArray *)legacy
13276{
13277 NSString *key = nil;
13278 id value = nil;
13279
13280 /* same need to make inner objects mutable as in localPlanetInfoOverrides */
13281
13282 [missionDestinations release];
13283 missionDestinations = [[NSMutableDictionary alloc] init];
13284
13285 foreachkey (key, destinations)
13286 {
13287 value = [destinations objectForKey:key];
13288 if (value != nil)
13289 {
13290 if ([value isKindOfClass:[NSDictionary class]])
13291 {
13292 value = [value mutableCopy];
13293 [missionDestinations setObject:value forKey:key];
13294 [value release];
13295 }
13296 }
13297 }
13298
13299 if (legacy != nil)
13300 {
13301 OOSystemID dest;
13302 NSNumber *legacyMarker;
13303 foreach (legacyMarker, legacy)
13304 {
13305 dest = [legacyMarker intValue];
13306 [self addMissionDestinationMarker:[self defaultMarker:dest]];
13307 }
13308 }
13309
13310}
13311
13312
13313- (NSString *)markerKey:(NSDictionary *)marker
13314{
13315 return [NSString stringWithFormat:@"%d-%@",[marker oo_intForKey:@"system"], [marker oo_stringForKey:@"name"]];
13316}
13317
13318
13319- (void) addMissionDestinationMarker:(NSDictionary *)marker
13320{
13321 NSDictionary *validated = [self validatedMarker:marker];
13322 if (validated == nil)
13323 {
13324 return;
13325 }
13326
13327 [missionDestinations setObject:validated forKey:[self markerKey:validated]];
13328}
13329
13330
13331- (BOOL) removeMissionDestinationMarker:(NSDictionary *)marker
13332{
13333 NSDictionary *validated = [self validatedMarker:marker];
13334 if (validated == nil)
13335 {
13336 return NO;
13337 }
13338 BOOL result = NO;
13339 if ([missionDestinations objectForKey:[self markerKey:validated]] != nil) {
13340 result = YES;
13341 }
13342 [missionDestinations removeObjectForKey:[self markerKey:validated]];
13343 return result;
13344}
13345
13346
13347- (NSMutableDictionary*) getMissionDestinations
13348{
13349 return missionDestinations;
13350}
13351
13352
13353- (NSMutableDictionary*) shipyardRecord
13354{
13355 return shipyard_record;
13356}
13357
13358
13359- (void) setLastShot:(NSArray *)shot
13360{
13361 lastShot = [shot retain];
13362}
13363
13364
13365- (void) clearExtraMissionKeys
13366{
13367 [extraMissionKeys release];
13368 extraMissionKeys = nil;
13369}
13370
13371
13372- (void) setExtraMissionKeys:(NSDictionary *)keys
13373{
13374 NSString *key = nil;
13375 NSMutableDictionary *final = [[NSMutableDictionary alloc] init];
13376 foreach (key, [keys allKeys])
13377 {
13378 [final setObject:[self processKeyCode:[keys oo_arrayForKey:key]] forKey:key];
13379 }
13380 extraMissionKeys = [final copy];
13381 [final release];
13382}
13383
13384
13385- (void) clearExtraGuiScreenKeys:(OOGUIScreenID)gui key:(NSString *)key
13386{
13387 NSMutableArray *keydefs = [extraGuiScreenKeys objectForKey:[NSString stringWithFormat:@"%d",gui]];
13388 NSInteger i = [keydefs count];
13389 NSDictionary *def = nil;
13390 while (i--)
13391 {
13392 def = [keydefs objectAtIndex:i];
13393 if (def && [[def oo_stringForKey:@"name"] isEqualToString:key])
13394 {
13395 [keydefs removeObjectAtIndex:i];
13396 break;
13397 }
13398 }
13399 // do we have to put the array back, or does the reference update the source?
13400}
13401
13402
13403- (BOOL) setExtraGuiScreenKeys:(OOGUIScreenID)gui definition:(OOJSGuiScreenKeyDefinition *)definition
13404{
13405 // process all the keys in the definition
13406 BOOL result = YES;
13407 NSMutableArray *newarray = nil;
13408 NSString *key = nil;
13409 NSMutableDictionary *final = [[NSMutableDictionary alloc] init];
13410 NSDictionary *keys = [definition registerKeys];
13411 NSMutableArray *checklist = [[NSMutableArray alloc] init];
13412
13413 foreach (key, [keys allKeys])
13414 {
13415 NSArray *item = [self processKeyCode:[keys oo_arrayForKey:key]];
13416 [checklist addObject:item];
13417 [final setObject:item forKey:key];
13418 }
13419 [definition setRegisterKeys:[final copy]];
13420 [final release];
13421
13423 if (!extraGuiScreenKeys)
13424 {
13425 extraGuiScreenKeys = [[NSMutableDictionary alloc] init];
13426 }
13427
13428 if (![extraGuiScreenKeys objectForKey:[NSString stringWithFormat:@"%d",gui]])
13429 {
13430 // brand new - just add
13431 newarray = [[NSMutableArray alloc] init];
13432 }
13433 else
13434 {
13435 newarray = [[extraGuiScreenKeys objectForKey:[NSString stringWithFormat:@"%d",gui]] mutableCopy];
13436 NSInteger i = [newarray count];
13437 NSInteger j = 0;
13438 OOJSGuiScreenKeyDefinition *def_existing = nil;
13439 while (i--)
13440 {
13441 def_existing = [newarray objectAtIndex:i];
13442 // if we find this name already in the array, remove it
13443 if (def_existing && [[def_existing name] isEqualToString:[definition name]])
13444 {
13445 [newarray removeObjectAtIndex:i];
13446 }
13447 else
13448 {
13449 // check whether any of those keycodes is already in use on this screen
13450 NSDictionary *keydefs = [def_existing registerKeys];
13451
13452 foreach (key, [keydefs allKeys])
13453 {
13454 j = [checklist count];
13455 while (j--)
13456 {
13457 if ([[NSString stringWithFormat:@"%@",[keydefs objectForKey:key]] isEqualToString:[NSString stringWithFormat:@"%@",[checklist objectAtIndex:j]]])
13458 {
13459 result = NO;
13460 OOLog(kOOLogException, @"***** Exception in setExtraGuiScreenKeys: %@ : %@ (%@)", @"invalid key settings", @"key already in use", key);
13461 }
13462 }
13463 }
13464 }
13465 }
13466 }
13467 [newarray addObject:definition];
13468 // only add the item if there were no errors
13469 if (result) [extraGuiScreenKeys setObject:[newarray mutableCopy] forKey:[NSString stringWithFormat:@"%d",gui]];
13470 [newarray release];
13471 return result;
13472}
13473
13474
13475#ifndef NDEBUG
13476- (void)dumpSelfState
13477{
13478 NSMutableArray *flags = nil;
13479 NSString *flagsString = nil;
13480
13481 [super dumpSelfState];
13482
13483 OOLog(@"dumpState.playerEntity", @"Script time: %g", script_time);
13484 OOLog(@"dumpState.playerEntity", @"Script time check: %g", script_time_check);
13485 OOLog(@"dumpState.playerEntity", @"Script time interval: %g", script_time_interval);
13486 OOLog(@"dumpState.playerEntity", @"Roll/pitch/yaw delta: %g, %g, %g", roll_delta, pitch_delta, yaw_delta);
13487 OOLog(@"dumpState.playerEntity", @"Shield: %g fore, %g aft", forward_shield, aft_shield);
13488 OOLog(@"dumpState.playerEntity", @"Alert level: %u, flags: %#x", alertFlags, alertCondition);
13489 OOLog(@"dumpState.playerEntity", @"Missile status: %i", missile_status);
13490 OOLog(@"dumpState.playerEntity", @"Energy unit: %@", EnergyUnitTypeToString([self installedEnergyUnitType]));
13491 OOLog(@"dumpState.playerEntity", @"Fuel leak rate: %g", fuel_leak_rate);
13492 OOLog(@"dumpState.playerEntity", @"Trumble count: %llu", trumbleCount);
13493
13494 flags = [NSMutableArray array];
13495 #define ADD_FLAG_IF_SET(x) if (x) { [flags addObject:@#x]; }
13496 ADD_FLAG_IF_SET(found_equipment);
13497 ADD_FLAG_IF_SET(pollControls);
13498 ADD_FLAG_IF_SET(suppressTargetLost);
13499 ADD_FLAG_IF_SET(scoopsActive);
13500 ADD_FLAG_IF_SET(game_over);
13501 ADD_FLAG_IF_SET(finished);
13502 ADD_FLAG_IF_SET(bomb_detonated);
13503 ADD_FLAG_IF_SET(autopilot_engaged);
13504 ADD_FLAG_IF_SET(afterburner_engaged);
13505 ADD_FLAG_IF_SET(afterburnerSoundLooping);
13506 ADD_FLAG_IF_SET(hyperspeed_engaged);
13507 ADD_FLAG_IF_SET(travelling_at_hyperspeed);
13508 ADD_FLAG_IF_SET(hyperspeed_locked);
13509 ADD_FLAG_IF_SET(ident_engaged);
13510 ADD_FLAG_IF_SET(galactic_witchjump);
13511 ADD_FLAG_IF_SET(ecm_in_operation);
13512 ADD_FLAG_IF_SET(show_info_flag);
13513 ADD_FLAG_IF_SET(showDemoShips);
13514 ADD_FLAG_IF_SET(rolling);
13515 ADD_FLAG_IF_SET(pitching);
13516 ADD_FLAG_IF_SET(yawing);
13517 ADD_FLAG_IF_SET(using_mining_laser);
13518 ADD_FLAG_IF_SET(mouse_control_on);
13519// ADD_FLAG_IF_SET(isSpeechOn);
13520 ADD_FLAG_IF_SET(keyboardRollOverride); // Handle keyboard roll...
13521 ADD_FLAG_IF_SET(keyboardPitchOverride); // ...and pitch override separately - (fix for BUG #17490)
13522 ADD_FLAG_IF_SET(keyboardYawOverride);
13523 ADD_FLAG_IF_SET(waitingForStickCallback);
13524 flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)@"none";
13525 OOLog(@"dumpState.playerEntity", @"Flags: %@", flagsString);
13526}
13527
13528
13529/* This method exists purely to suppress Clang static analyzer warnings that
13530 these ivars are unused (but may be used by categories, which they are).
13531 FIXME: there must be a feature macro we can use to avoid actually building
13532 this into the app, but I can't find it in docs.
13533
13534 Mind you, we could suppress some of this by using civilized accessors.
13535*/
13536- (BOOL) suppressClangStuff
13537{
13538 return missionChoice &&
13539 commanderNameString &&
13540 cdrDetailArray &&
13541 currentPage &&
13542 n_key_roll_left &&
13543 n_key_roll_right &&
13544 n_key_pitch_forward &&
13545 n_key_pitch_back &&
13546 n_key_yaw_left &&
13547 n_key_yaw_right &&
13548 n_key_view_forward &&
13549 n_key_view_aft &&
13550 n_key_view_port &&
13551 n_key_view_starboard &&
13552 n_key_launch_ship &&
13553 n_key_gui_screen_options &&
13554 n_key_gui_screen_equipship &&
13555 n_key_gui_screen_interfaces &&
13556 n_key_gui_screen_status &&
13557 n_key_gui_chart_screens &&
13558 n_key_gui_system_data &&
13559 n_key_gui_market &&
13560 n_key_gui_arrow_left &&
13561 n_key_gui_arrow_right &&
13562 n_key_gui_arrow_up &&
13563 n_key_gui_arrow_down &&
13564 n_key_gui_page_up &&
13565 n_key_gui_page_down &&
13566 n_key_gui_select &&
13567 n_key_increase_speed &&
13568 n_key_decrease_speed &&
13569 n_key_inject_fuel &&
13570 n_key_fire_lasers &&
13571 n_key_launch_missile &&
13572 n_key_next_missile &&
13573 n_key_ecm &&
13574 n_key_prime_next_equipment &&
13575 n_key_prime_previous_equipment &&
13576 n_key_activate_equipment &&
13577 n_key_mode_equipment &&
13578 n_key_fastactivate_equipment_a &&
13579 n_key_fastactivate_equipment_b &&
13580 n_key_target_missile &&
13581 n_key_untarget_missile &&
13582 n_key_target_incoming_missile &&
13583 n_key_ident_system &&
13584 n_key_scanner_zoom &&
13585 n_key_scanner_unzoom &&
13586 n_key_launch_escapepod &&
13587 n_key_galactic_hyperspace &&
13588 n_key_hyperspace &&
13589 n_key_jumpdrive &&
13590 n_key_dump_cargo &&
13591 n_key_rotate_cargo &&
13592 n_key_autopilot &&
13593 n_key_autodock &&
13594 n_key_snapshot &&
13595 n_key_docking_music &&
13596 n_key_advanced_nav_array_next &&
13597 n_key_advanced_nav_array_previous &&
13598 n_key_info_next_system &&
13599 n_key_info_previous_system &&
13600 n_key_map_home &&
13601 n_key_map_end &&
13602 n_key_map_next_system &&
13603 n_key_map_previous_system &&
13604 n_key_map_info &&
13605 n_key_map_zoom_in &&
13606 n_key_map_zoom_out &&
13607 n_key_system_home &&
13608 n_key_system_end &&
13609 n_key_system_next_system &&
13610 n_key_system_previous_system &&
13611 n_key_pausebutton &&
13612 n_key_show_fps &&
13613 n_key_bloom_toggle &&
13614 n_key_mouse_control_roll &&
13615 n_key_mouse_control_yaw &&
13616 n_key_hud_toggle &&
13617 n_key_comms_log &&
13618 n_key_prev_compass_mode &&
13619 n_key_next_compass_mode &&
13620 n_key_chart_highlight &&
13621 n_key_market_filter_cycle &&
13622 n_key_market_sorter_cycle &&
13623 n_key_market_buy_one &&
13624 n_key_market_sell_one &&
13625 n_key_market_buy_max &&
13626 n_key_market_sell_max &&
13627 n_key_next_target &&
13628 n_key_previous_target &&
13629 n_key_custom_view &&
13630 n_key_custom_view_zoom_out &&
13631 n_key_custom_view_zoom_in &&
13632 n_key_custom_view_roll_left &&
13633 n_key_custom_view_pan_left &&
13634 n_key_custom_view_roll_right &&
13635 n_key_custom_view_pan_right &&
13636 n_key_custom_view_rotate_up &&
13637 n_key_custom_view_pan_up &&
13638 n_key_custom_view_rotate_down &&
13639 n_key_custom_view_pan_down &&
13640 n_key_custom_view_rotate_left &&
13641 n_key_custom_view_rotate_right &&
13642 n_key_docking_clearance_request &&
13643 n_key_weapons_online_toggle &&
13644 n_key_cycle_next_mfd &&
13645 n_key_cycle_previous_mfd &&
13646 n_key_switch_next_mfd &&
13647 n_key_switch_previous_mfd &&
13648 n_key_oxzmanager_setfilter &&
13649 n_key_oxzmanager_showinfo &&
13650 n_key_oxzmanager_extract &&
13651#if OO_FOV_INFLIGHT_CONTROL_ENABLED
13652 n_key_inc_field_of_view &&
13653 n_key_dec_field_of_view &&
13654#endif
13655 n_key_dump_target_state &&
13656 n_key_dump_entity_list &&
13657 n_key_debug_full &&
13658 n_key_debug_collision &&
13659 n_key_debug_console_connect &&
13660 n_key_debug_bounding_boxes &&
13661 n_key_debug_shaders &&
13662 n_key_debug_off &&
13663 _sysInfoLight.x &&
13664 selFunctionIdx &&
13665 stickFunctions &&
13666 keyFunctions &&
13667 customEquipActivation &&
13668 customActivatePressed &&
13669 customModePressed &&
13670 kbdLayouts &&
13671 showingLongRangeChart &&
13672 _missionAllowInterrupt &&
13673 _missionScreenID &&
13674 _missionTitle &&
13675 _missionTextEntry;
13676}
13677#endif
13678
13679@end
13680
13681
13682NSComparisonResult marketSorterByName(id a, id b, void *context)
13683{
13684 OOCommodityMarket *market = (OOCommodityMarket *)context;
13685 return [[market nameForGood:(OOCommodityType)a] compare:[market nameForGood:(OOCommodityType)b]];
13686}
13687
13688
13689NSComparisonResult marketSorterByPrice(id a, id b, void *context)
13690{
13691 OOCommodityMarket *market = (OOCommodityMarket *)context;
13692 int result = (int)[market priceForGood:(OOCommodityType)a] - (int)[market priceForGood:(OOCommodityType)b];
13693 if (result < 0)
13694 {
13695 return NSOrderedAscending;
13696 }
13697 else if (result > 0)
13698 {
13699 return NSOrderedDescending;
13700 }
13701 else
13702 {
13703 return NSOrderedSame;
13704 }
13705}
13706
13707
13708NSComparisonResult marketSorterByQuantity(id a, id b, void *context)
13709{
13710 OOCommodityMarket *market = (OOCommodityMarket *)context;
13711 int result = (int)[market quantityForGood:(OOCommodityType)a] - (int)[market quantityForGood:(OOCommodityType)b];
13712 if (result < 0)
13713 {
13714 return NSOrderedAscending;
13715 }
13716 else if (result > 0)
13717 {
13718 return NSOrderedDescending;
13719 }
13720 else
13721 {
13722 return NSOrderedSame;
13723 }
13724}
13725
13726
13727NSComparisonResult marketSorterByMassUnit(id a, id b, void *context)
13728{
13729 OOCommodityMarket *market = (OOCommodityMarket *)context;
13730 int result = (int)[market massUnitForGood:(OOCommodityType)a] - (int)[market massUnitForGood:(OOCommodityType)b];
13731 if (result < 0)
13732 {
13733 return NSOrderedAscending;
13734 }
13735 else if (result > 0)
13736 {
13737 return NSOrderedDescending;
13738 }
13739 else
13740 {
13741 return NSOrderedSame;
13742 }
13743}
#define MAX_FOV
#define MIN_FOV_DEG
#define MAX_FOV_DEG
BOOL shadowAtPointOcclusionToValue(HPVector e1pos, GLfloat e1rad, Entity *e2, OOSunEntity *the_sun, float *outValue)
OOEntityStatus
Definition Entity.h:60
OOScanClass
Definition Entity.h:71
NSString * OOStringFromEntityStatus(OOEntityStatus status) CONST_FUNC
#define SCANNER_MAX_RANGE
Definition Entity.h:51
#define SCANNER_MAX_RANGE2
Definition Entity.h:52
#define ADD_FLAG_IF_SET(x)
#define MINIMUM_GAME_TICK
#define GUI_MAX_ROWS
OOGUIBackgroundSpecial
@ GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_QUICKEST
@ GUI_BACKGROUND_SPECIAL_SHORT_ANA_SHORTEST
@ GUI_BACKGROUND_SPECIAL_LONG
@ GUI_BACKGROUND_SPECIAL_LONG_ANA_SHORTEST
@ GUI_BACKGROUND_SPECIAL_SHORT
@ GUI_BACKGROUND_SPECIAL_NONE
@ GUI_BACKGROUND_SPECIAL_SHORT_ANA_QUICKEST
@ GUI_BACKGROUND_SPECIAL_LONG_ANA_QUICKEST
@ GUI_BACKGROUND_SPECIAL_CUSTOM
@ GUI_BACKGROUND_SPECIAL_CUSTOM_ANA_SHORTEST
OOGUITabStop OOGUITabSettings[GUI_MAX_COLUMNS]
NSInteger OOGUIRow
#define DESTROY(x)
Definition OOCocoa.h:75
#define foreachkey(VAR, DICT)
Definition OOCocoa.h:353
OOINLINE jsval OOJSValueFromLegalStatusReason(JSContext *context, OOLegalStatusReason value)
OOINLINE jsval OOJSValueFromCompassMode(JSContext *context, OOCompassMode value)
OOINLINE jsval OOJSValueFromGUIScreenID(JSContext *context, OOGUIScreenID value)
NSString * EnergyUnitTypeToString(OOEnergyUnitType unit) CONST_FUNC
NSString * DisplayStringForMassUnit(OOMassUnit unit)
NSString * OOStringFromLegalStatusReason(OOLegalStatusReason reason)
#define EXPECT_NOT(x)
#define EXPECT(x)
const HPVector kZeroHPVector
Definition OOHPVector.m:28
#define OOJSStopTimeLimiter()
#define kOOJSLongTimeLimit
#define OOJSStartTimeLimiterWithTimeLimit(limit)
#define OOJSSTR(str)
#define JS_IsInRequest(context)
OOINLINE jsval OOJSValueFromNativeObject(JSContext *context, id object)
OOINLINE JSContext * OOJSAcquireContext(void)
OOINLINE void OOJSRelinquishContext(JSContext *context)
#define OOLogWARN(class, format,...)
Definition OOLogging.h:113
#define OOLogERR(class, format,...)
Definition OOLogging.h:112
NSString *const kOOLogException
Definition OOLogging.m:651
NSString *const kOOLogInconsistentState
Definition OOLogging.m:650
#define OOLog(class, format,...)
Definition OOLogging.h:88
#define M_SQRT1_2
Definition OOMaths.h:94
#define MAX(A, B)
Definition OOMaths.h:114
#define MIN(A, B)
Definition OOMaths.h:111
GLfloat OOScalar
Definition OOMaths.h:64
const OOMatrix kIdentityMatrix
Definition OOMatrix.m:31
OOMatrix OOMatrixForQuaternionRotation(Quaternion orientation)
Definition OOMatrix.m:65
return self
unsigned count
return nil
Vector vector_up_from_quaternion(Quaternion quat)
void quaternion_rotate_about_x(Quaternion *quat, OOScalar angle)
HPVector HPvector_forward_from_quaternion(Quaternion quat)
Vector vector_right_from_quaternion(Quaternion quat)
Vector vector_forward_from_quaternion(Quaternion quat)
void quaternion_rotate_about_z(Quaternion *quat, OOScalar angle)
void quaternion_set_random(Quaternion *quat)
Vector quaternion_rotate_vector(Quaternion q, Vector v)
const Quaternion kIdentityQuaternion
void quaternion_rotate_about_y(Quaternion *quat, OOScalar angle)
const Quaternion kZeroQuaternion
void quaternion_rotate_about_axis(Quaternion *quat, Vector axis, OOScalar angle)
float y
float x
#define ATMOSPHERE_DEPTH
@ STELLAR_TYPE_MINIATURE
#define OOExpandKey(key,...)
#define OOExpandKeyWithSeed(seed, key,...)
#define OOExpand(string,...)
NSMutableArray * ScanTokensFromString(NSString *values)
NSString * ClockToString(double clock, BOOL adjusting)
OOINLINE NSString * OOCredits(OOCreditsQuantity tenthsOfCredits)
NSPoint PointFromString(NSString *xyString)
NSString * OOPadStringToEms(NSString *string, float numEms)
#define OO_GALAXIES_AVAILABLE
@ OO_SYSTEMCONCEALMENT_NODATA
@ OO_SYSTEMCONCEALMENT_NONAME
#define OO_SYSTEMS_PER_GALAXY
OOLongRangeChartMode
Definition OOTypes.h:50
uint16_t OOFuelQuantity
Definition OOTypes.h:179
uint8_t OOWeaponFacingSet
Definition OOTypes.h:237
NSString * OOCommodityType
Definition OOTypes.h:106
OOAegisStatus
Definition OOTypes.h:60
@ AEGIS_IN_DOCKING_RANGE
Definition OOTypes.h:64
@ AEGIS_CLOSE_TO_MAIN_PLANET
Definition OOTypes.h:63
@ AEGIS_NONE
Definition OOTypes.h:61
OORouteType
Definition OOTypes.h:33
@ OPTIMIZED_BY_NONE
Definition OOTypes.h:34
OOGraphicsDetail
Definition OOTypes.h:243
@ DETAIL_LEVEL_SHADERS
Definition OOTypes.h:246
OOLegalStatusReason
Definition OOTypes.h:157
OOViewID
Definition OOTypes.h:43
uint64_t OOCreditsQuantity
Definition OOTypes.h:182
@ kOOVariableTechLevel
Definition OOTypes.h:202
#define VALID_WEAPON_FACINGS
Definition OOTypes.h:239
NSUInteger OOTechLevelID
Definition OOTypes.h:204
int16_t OOSystemID
Definition OOTypes.h:211
OOCompassMode
Definition OOTypes.h:145
uint8_t OOGalaxyID
Definition OOTypes.h:210
uint32_t OOCargoQuantity
Definition OOTypes.h:176
OOMassUnit
Definition OOTypes.h:123
@ UNITS_TONS
Definition OOTypes.h:124
@ UNITS_GRAMS
Definition OOTypes.h:126
@ UNITS_KILOGRAMS
Definition OOTypes.h:125
double OOTimeDelta
Definition OOTypes.h:224
uint8_t OOGovernmentID
Definition OOTypes.h:206
OODockingClearanceStatus
Definition OOTypes.h:167
@ DOCKING_CLEARANCE_STATUS_NOT_REQUIRED
Definition OOTypes.h:169
@ DOCKING_CLEARANCE_STATUS_GRANTED
Definition OOTypes.h:171
@ DOCKING_CLEARANCE_STATUS_NONE
Definition OOTypes.h:168
@ DOCKING_CLEARANCE_STATUS_REQUESTED
Definition OOTypes.h:170
double OOTimeAbsolute
Definition OOTypes.h:223
@ NO_TARGET
Definition OOTypes.h:194
OOWeaponFacing
Definition OOTypes.h:228
@ WEAPON_FACING_FORWARD
Definition OOTypes.h:229
@ WEAPON_FACING_NONE
Definition OOTypes.h:234
@ WEAPON_FACING_AFT
Definition OOTypes.h:230
@ WEAPON_FACING_PORT
Definition OOTypes.h:231
@ WEAPON_FACING_STARBOARD
Definition OOTypes.h:232
OOEnergyUnitType
Definition OOTypes.h:131
@ ENERGY_UNIT_NORMAL
Definition OOTypes.h:137
@ ENERGY_UNIT_NAVAL_DAMAGED
Definition OOTypes.h:134
@ ENERGY_UNIT_NAVAL
Definition OOTypes.h:138
@ OLD_ENERGY_UNIT_NORMAL
Definition OOTypes.h:135
@ ENERGY_UNIT_NONE
Definition OOTypes.h:132
@ OLD_ENERGY_UNIT_NAVAL
Definition OOTypes.h:136
@ ENERGY_UNIT_NORMAL_DAMAGED
Definition OOTypes.h:133
uint8_t OOEconomyID
Definition OOTypes.h:207
const Vector kZeroVector
Definition OOVector.m:28
#define CARGO_KEY_TYPE
#define MAX_CONTRACT_REP
OOCreditsQuantity OODeciCreditsFromObject(id object)
OOGalacticHyperspaceBehaviour
@ GALACTIC_HYPERSPACE_MAX
#define MAX_KILOGRAMS_IN_SAFE
@ GUI_ROW_EQUIPMENT_DETAIL
@ GUI_MAX_ROWS_EQUIPMENT
@ GUI_ROW_EQUIPMENT_START
@ GUI_ROW_MARKET_START
@ GUI_ROW_MARKET_END
@ GUI_ROW_MARKET_LAST
@ GUI_ROW_INTERFACES_START
@ GUI_MAX_ROWS_INTERFACES
#define PLAYER_STARTING_MISSILES
OOPlayerFleeingStatus
@ PLAYER_FLEEING_MAYBE
@ PLAYER_FLEEING_LIKELY
@ PLAYER_FLEEING_NONE
@ PLAYER_FLEEING_CARGO
@ PLAYER_FLEEING_UNLIKELY
#define ESCAPE_SEQUENCE_TIME
#define CUSTOM_VIEW_MAX_ZOOM_IN
OOPrimedEquipmentMode
@ OOPRIMEDEQUIP_ACTIVATED
@ OOPRIMEDEQUIP_MODE
#define CHART_WIDTH_AT_MAX_ZOOM
#define PLAYER_MAX_WEAPON_TEMP
#define PORT_FACING_STRING
#define PLAYER_INTERNAL_DAMAGE_FACTOR
@ MARKET_FILTER_MODE_HOLD
@ MARKET_FILTER_MODE_RESTRICTED
@ MARKET_FILTER_MODE_STOCK
@ MARKET_FILTER_MODE_TRADE
@ MARKET_FILTER_MODE_OFF
@ MARKET_FILTER_MODE_LEGAL
#define MAX_GRAMS_IN_SAFE
OOSpeechSettings
@ OOSPEECHSETTINGS_ALL
@ OOSPEECHSETTINGS_OFF
@ OOSPEECHSETTINGS_COMMS
#define HYPERSPEED_FACTOR
#define KILOGRAMS_PER_POD
#define CHART_MAX_ZOOM
#define PLAYER_DIAL_MAX_ALTITUDE
#define MAX_HYPERSPEED_FACTOR
#define FORWARD_FACING_STRING
#define AFT_FACING_STRING
OOGUIScreenID
#define GUI_ROW_INIT(GUI)
OOFuelScoopStatus
@ SCOOP_STATUS_FULL_HOLD
@ SCOOP_STATUS_NOT_INSTALLED
@ SCOOP_STATUS_ACTIVE
@ SCOOP_STATUS_OKAY
#define CHART_HEIGHT_AT_MAX_ZOOM
#define CUSTOM_VIEW_MAX_ZOOM_OUT
#define MIN_HYPERSPEED_FACTOR
#define GUI_ROW(GROUP, ITEM)
#define STARBOARD_FACING_STRING
#define CUSTOMEQUIP_EQUIPKEY
#define CHART_SCROLL_AT_Y
#define SCANNER_ECM_FUZZINESS
@ ALERT_FLAG_DOCKED
@ ALERT_FLAG_MASS_LOCK
@ ALERT_FLAG_YELLOW_LIMIT
#define GRAMS_PER_POD
#define CHART_SCROLL_AT_X
#define PLAYER_MAX_FUEL
#define SCRIPT_TIMER_INTERVAL
#define PLAYER_DOCKING_AI_NAME
OOMissileStatus
@ MISSILE_STATUS_TARGET_LOCKED
@ MISSILE_STATUS_ARMED
@ MISSILE_STATUS_SAFE
#define PLAYER_SHIP_DESC
@ MARKET_SORTER_MODE_PRICE
@ MARKET_SORTER_MODE_OFF
@ MARKET_SORTER_MODE_STOCK
@ MARKET_SORTER_MODE_ALPHA
@ MARKET_SORTER_MODE_UNIT
@ MARKET_SORTER_MODE_HOLD
#define ECM_DURATION
#define ECM_ENERGY_DRAIN_FACTOR
NSString * OODisplayStringFromLegalStatus(int legalStatus)
#define GUI_FIRST_ROW(GROUP)
NSString * KillCountToRatingAndKillString(unsigned kills)
#define PLAYER_SHIP_CLOCK_START
#define PLAYER_TARGET_MEMORY_SIZE
#define PLAYER_STARTING_MAX_MISSILES
#define PLAYER_MAX_TRUMBLES
#define PLAYER_MAX_MISSILES
static NSString *const kOOLogBuyMountedOK
static NSString *const kOOLogBuyMountedFailed
#define VELOCITY_CLEANUP_MIN
#define STAGE_TRACKING_END
#define UPDATE_STAGE(x)
PlayerEntity * gOOPlayer
NSComparisonResult marketSorterByName(id a, id b, void *market)
NSComparisonResult marketSorterByPrice(id a, id b, void *market)
NSComparisonResult marketSorterByQuantity(id a, id b, void *market)
static NSString * last_outfitting_key
@ kCommLogTrimSize
@ kCommLogTrimThreshold
static float const kDeadResetTime
#define VELOCITY_CLEANUP_RATE
#define STAGE_TRACKING_BEGIN
NSComparisonResult marketSorterByMassUnit(id a, id b, void *market)
#define VELOCITY_CLEANUP_FULL
static GLfloat sBaseMass
#define OO_SETACCESSCONDITIONFORROW(condition, row)
#define SCENARIO_OXP_DEFINITION_NONE
#define SCENARIO_OXP_DEFINITION_ALL
#define MIN_HDR_PAPERWHITE
#define MAX_HDR_PAPERWHITE
#define OO_VERSION_FULL
Definition main.m:55
#define MIN_FUEL
Definition ShipEntity.h:102
#define INITIAL_SHOT_TIME
Definition ShipEntity.h:100
BOOL isWeaponNone(OOWeaponType weapon)
#define SHIP_COOLING_FACTOR
Definition ShipEntity.h:61
#define CLOAKING_DEVICE_MIN_ENERGY
Definition ShipEntity.h:48
OOAlertCondition
Definition ShipEntity.h:172
@ ALERT_CONDITION_GREEN
Definition ShipEntity.h:176
@ ALERT_CONDITION_RED
Definition ShipEntity.h:178
@ ALERT_CONDITION_YELLOW
Definition ShipEntity.h:177
@ ALERT_CONDITION_DOCKED
Definition ShipEntity.h:175
OOWeaponType OOWeaponTypeFromEquipmentIdentifierStrict(NSString *string) PURE_FUNC
#define ShipScriptEventNoCx(ship, event,...)
#define ENTITY_PERSONALITY_INVALID
Definition ShipEntity.h:111
OOWeaponType OOWeaponTypeFromEquipmentIdentifierLegacy(NSString *string)
#define SHIP_MIN_CABIN_TEMP
Definition ShipEntity.h:68
#define MILITARY_JAMMER_ENERGY_RATE
Definition ShipEntity.h:51
#define CLOAKING_DEVICE_ENERGY_RATE
Definition ShipEntity.h:47
#define WEAPON_COOLING_FACTOR
Definition ShipEntity.h:114
#define SHIP_MAX_CABIN_TEMP
Definition ShipEntity.h:67
#define MILITARY_JAMMER_MIN_ENERGY
Definition ShipEntity.h:52
NSString * OOStringFromShipDamageType(OOShipDamageType type) CONST_FUNC
#define SUN_TEMPERATURE
Definition ShipEntity.h:72
NSString * OODisplayStringFromAlertCondition(OOAlertCondition alertCondition)
OOWeaponType OOWeaponTypeFromEquipmentIdentifierSloppy(NSString *string) PURE_FUNC
#define BASELINE_SHIELD_LEVEL
Definition ShipEntity.h:99
OOShipDamageType
Definition ShipEntity.h:183
#define SHIP_THRUST_FACTOR
Definition ShipEntity.h:44
#define ShipScriptEvent(context, ship, event,...)
#define WEAPON_COOLING_CUTOUT
Definition ShipEntity.h:116
#define SHIP_INSULATION_FACTOR
Definition ShipEntity.h:66
#define SHIP_ENERGY_DAMAGE_TO_HEAT_FACTOR
Definition ShipEntity.h:65
#define UNIVERSE
Definition Universe.h:842
#define PASSENGER_BERTH_SPACE
Definition Universe.h:152
#define DESC(key)
Definition Universe.h:848
@ WH_SCANINFO_NONE
@ WH_SCANINFO_SCANNED
@ WH_SCANINFO_SHIP
@ WH_SCANINFO_ARRIVAL_TIME
@ WH_SCANINFO_DESTINATION
@ WH_SCANINFO_COLLAPSE_TIME
OOFuelQuantity fuelRequiredForJump()
Definition AI.h:38
void setNextThinkTime:(OOTimeAbsolute ntt)
Definition AI.m:658
void setOwner:(ShipEntity *ship)
Definition AI.m:197
void clearAllData()
Definition AI.m:691
void setState:(NSString *stateName)
Definition AI.m:334
GLfloat collision_radius
Definition Entity.h:111
OOUniversalID universalID
Definition Entity.h:89
HPVector absolutePositionForSubentity()
Definition Entity.m:670
void dumpState()
Definition Entity.m:997
void setVelocity:(Vector vel)
Definition Entity.m:758
BOOL isSun()
Definition Entity.m:168
void setOrientation:(Quaternion quat)
Definition Entity.m:726
GLfloat zero_distance
Definition Entity.h:108
unsigned isShip
Definition Entity.h:91
GLfloat collisionRadius()
Definition Entity.m:906
OOScanClass scanClass
Definition Entity.h:106
void setScanClass:(OOScanClass sClass)
Definition Entity.m:800
OOEntityStatus status()
Definition Entity.m:794
void setPositionX:y:z:(OOHPScalar x,[y] OOHPScalar y,[z] OOHPScalar z)
Definition Entity.m:655
HPVector position
Definition Entity.h:112
id owner()
Definition Entity.m:584
void setPosition:(HPVector posn)
Definition Entity.m:648
NSString * playerFileToLoad
GameController * sharedController()
void logProgress:(NSString *message)
BOOL setBackgroundTextureKey:(NSString *key)
OOColor * colorFromSetting:defaultValue:(NSString *setting,[defaultValue] OOColor *def)
BOOL setSelectedRow:(OOGUIRow row)
OOGUIRow addLongText:startingAtRow:align:(NSString *str,[startingAtRow] OOGUIRow row,[align] OOGUIAlignment alignment)
BOOL setForegroundTextureKey:(NSString *key)
void setStatusPage:(NSInteger pageNum)
NSString * selectedRowText()
void setText:forRow:(NSString *str,[forRow] OOGUIRow row)
void setText:forRow:align:(NSString *str,[forRow] OOGUIRow row,[align] OOGUIAlignment alignment)
void clearAndKeepBackground:(BOOL keepBackground)
BOOL setForegroundTextureDescriptor:(NSDictionary *descriptor)
OOGUIRow selectedRow
void overrideTabs:from:length:(OOGUITabSettings stops,[from] NSString *setting,[length] NSUInteger len)
NSDictionary * userSettings()
void setSelectableRange:(NSRange range)
void setColor:forRow:(OOColor *color,[forRow] OOGUIRow row)
NSString * selectedRowKey()
void setTitle:(NSString *str)
void setTabStops:(OOGUITabSettings stops)
NSString * keyForRow:(OOGUIRow row)
void setShowTextCursor:(BOOL yesno)
void setCurrentRow:(OOGUIRow value)
BOOL setBackgroundTextureDescriptor:(NSDictionary *descriptor)
void setArray:forRow:(NSArray *arr,[forRow] OOGUIRow row)
void setKey:forRow:(NSString *str,[forRow] OOGUIRow row)
float mouseWheelDelta()
BOOL isRunningOnPrimaryDisplayDevice()
float fov:(BOOL inFraction)
void adjustColorSaturation:(float colorSaturationAdjustment)
float colorSaturation()
void setMouseWheelDelta:(float newWheelDelta)
GameController * gameController
OOCacheManager * sharedCache()
OOColor * cyanColor()
Definition OOColor.m:286
OOColor * colorWithRed:green:blue:alpha:(float red,[green] float green,[blue] float blue,[alpha] float alpha)
Definition OOColor.m:95
OOColor * orangeColor()
Definition OOColor.m:304
OOColor * colorWithDescription:(id description)
Definition OOColor.m:127
OOColor * greenColor()
Definition OOColor.m:274
OOColor * grayColor()
Definition OOColor.m:262
OOColor * whiteColor()
Definition OOColor.m:256
OOColor * yellowColor()
Definition OOColor.m:292
OOColor * magentaColor()
Definition OOColor.m:298
OOCommodityType legacyCommodityType:(NSUInteger i)
NSUInteger exportLegalityForGood:(OOCommodityType good)
OOMassUnit massUnitForGood:(OOCommodityType good)
NSUInteger importLegalityForGood:(OOCommodityType good)
BOOL removeQuantity:forGood:(OOCargoQuantity quantity,[forGood] OOCommodityType good)
OOCargoQuantity capacityForGood:(OOCommodityType good)
BOOL setQuantity:forGood:(OOCargoQuantity quantity,[forGood] OOCommodityType good)
OOCargoQuantity quantityForGood:(OOCommodityType good)
BOOL addQuantity:forGood:(OOCargoQuantity quantity,[forGood] OOCommodityType good)
OOCreditsQuantity priceForGood:(OOCommodityType good)
NSString * nameForGood:(OOCommodityType good)
NSString * conditionScript()
NSUInteger repairTime()
NSString * damagedIdentifier()
OOColor * displayColor()
NSArray * defaultActivateKey()
NSArray * defaultModeKey()
OOTechLevelID effectiveTechLevel()
OOCargoQuantity requiredCargoSpace()
OOEquipmentType * equipmentTypeWithIdentifier:(NSString *identifier)
OOCreditsQuantity price()
NSUInteger installTime()
NSString * identifier()
NSString * descriptiveText()
void setRegisterKeys:(NSDictionary *registerKeys)
BOOL callMethod:inContext:withArguments:count:result:(jsid methodID,[inContext] JSContext *context,[withArguments] jsval *argv,[count] intN argc,[result] jsval *outResult)
Definition OOJSScript.m:394
OOJavaScriptEngine * sharedEngine()
void garbageCollectionOpportunity:(BOOL force)
void update:(OOTimeDelta delta_t)
OOMusicController * sharedController()
OOOXZManager * sharedManager()
id jsScriptFromFileNamed:properties:(NSString *fileName,[properties] NSDictionary *properties)
Definition OOScript.m:191
BOOL callMethod:inContext:withArguments:count:result:(jsid methodID,[inContext] JSContext *context,[withArguments] jsval *argv,[count] intN argc,[result] jsval *outResult)
Definition OOJSScript.m:651
NSDictionary * shipyardInfoForKey:(NSString *key)
OOShipRegistry * sharedRegistry()
NSDictionary * shipInfoForKey:(NSString *key)
float masterVolume()
Definition OOALSound.m:80
void spawnFrom:(OOTrumble *parentTrumble)
Definition OOTrumble.m:219
void updateTrumble:(double delta_t)
Definition OOTrumble.m:494
OOWeakReference * weakSelf
void showInformationForSelectedUpgrade()
void setGuiToEquipShipScreen:(int skip)
void copyValuesFromPlayer:(PlayerEntity *player)
NSDictionary * loadScripts()
NSArray * OXPsWithMessagesFound()
NSDictionary * dictionaryFromFilesNamed:inFolder:andMerge:(NSString *fileName,[inFolder] NSString *folderName,[andMerge] BOOL mergeFiles)
void setDemoStartTime:(OOTimeAbsolute time)
void deserializeShipSubEntitiesFrom:(NSString *string)
Definition ShipEntity.m:833
void setDesiredSpeed:(double amount)
void setStatus:(OOEntityStatus stat)
void setRoll:(double amount)
void setThrust:(double amount)
void setSpeed:(double amount)
NSString * primaryRole
Definition ShipEntity.h:333
NSString * name
Definition ShipEntity.h:327
void wasAddedToUniverse()
BOOL countsAsKill()
OOCreditsQuantity bounty
Definition ShipEntity.h:300
OOScanClass scanClass()
void setAITo:(NSString *aiString)
void setPendingEscortCount:(uint8_t count)
void setEntityPersonalityInt:(uint16_t value)
BOOL hasHostileTarget()
OOCargoQuantity commodityAmount()
void update:(OOTimeDelta delta_t)
void setTemperature:(GLfloat value)
void setDemoShip:(OOScalar demoRate)
void setBehaviour:(OOBehaviour cond)
void switchAITo:(NSString *aiString)
unsigned isHulk
Definition ShipEntity.h:261
OOMesh * mesh()
Triangle absoluteIJKForSubentity()
void becomeExplosion()
void setCommodity:andAmount:(OOCommodityType co_type,[andAmount] OOCargoQuantity co_amount)
OOCommodityType commodityType()
void setOwner:(Entity *who_owns_entity)
BoundingBox findSubentityBoundingBox()
NSString * displayName
Definition ShipEntity.h:330
OOCommodityMarket * localMarket
OOCommodityMarket * initialiseLocalMarket()
void autoDockShipsOnApproach()
OOCreditsQuantity legalStatusOfManifest:export:(OOCommodityMarket *manifest,[export] BOOL export)
void launchShip:(ShipEntity *ship)
NSString * acceptDockingClearanceRequestFrom:(ShipEntity *other)
OOTechLevelID equivalentTechLevel
void noteDockedShip:(ShipEntity *ship)
float equipmentPriceFactor
void clearDockingCorridor()
double estimatedArrivalTime()
OOSystemID destination
void setMisjumpWithRange:(GLfloat range)
NSDictionary * getDict()
void setScannedAt:(double time)
void setScanInfo:(WORMHOLE_SCANINFO scanInfo)
voidpf uLong offset
Definition ioapi.h:140
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
const char int mode
Definition ioapi.h:133
float randf(void)
void clear_checksum()
RANROTSeed RANROTGetFullSeed(void)
void setRandomSeed(RNG_Seed a_seed)
int16_t munge_checksum(long long value_)
RNG_Seed currentRandomSeed(void)
void RANROTSetFullSeed(RANROTSeed seed)
double cunningFee(double value, double precision)
unsigned Ranrot(void)
#define ranrot_rand()
OOINLINE double distanceBetweenPlanetPositions(int x1, int y1, int x2, int y2) INLINE_CONST_FUNC