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