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