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