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