Oolite 1.91.0.7604-240417-a536cbe
Loading...
Searching...
No Matches
PlayerEntityContracts.m
Go to the documentation of this file.
1/*
2
3PlayerEntityContracts.m
4
5Oolite
6Copyright (C) 2004-2013 Giles C Williams and contributors
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program; if not, write to the Free Software
20Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21MA 02110-1301, USA.
22
23*/
24
25#import "PlayerEntity.h"
29#import "ProxyPlayerEntity.h"
30#import "HeadUpDisplay.h"
31
32#import "ShipEntityAI.h"
33#import "Universe.h"
34#import "AI.h"
35#import "OOColor.h"
36#import "OOCharacter.h"
37#import "StationEntity.h"
38#import "GuiDisplayGen.h"
39#import "OOStringExpander.h"
40#import "OOStringParsing.h"
42#import "OOConstToString.h"
43#import "MyOpenGLView.h"
45#import "OOShipRegistry.h"
46#import "OOEquipmentType.h"
47#import "OOTexture.h"
49
50
51static unsigned RepForRisk(unsigned risk);
52
53@interface PlayerEntity (ContractsPrivate)
54
56- (NSArray*) contractsListFromArray:(NSArray *) contracts_array forCargo:(BOOL) forCargo forParcels:(BOOL)forParcels;
57
58@end
59
60
61@implementation PlayerEntity (Contracts)
62
63- (NSString *) processEscapePods // removes pods from cargo bay and treats categories of characters carried
64{
65 unsigned i;
66 BOOL added_entry = NO; // to prevent empty lines for slaves and the rare empty report.
67 NSMutableString *result = [NSMutableString string];
68 NSMutableArray *rescuees = [NSMutableArray array];
69 OOGovernmentID government = [[[UNIVERSE currentSystemData] objectForKey:KEY_GOVERNMENT] intValue];
70 if ([UNIVERSE inInterstellarSpace]) government = 1; // equivalent to Feudal. I'm assuming any station in interstellar space is military. -- Ahruman 2008-05-29
71
72 // step through the cargo removing crew from any escape pods
73 // No enumerator because we're mutating the array -- Ahruman
74 for (i = 0; i < [cargo count]; i++)
75 {
76 ShipEntity *cargoItem = [cargo objectAtIndex:i];
77 NSArray *podCrew = [cargoItem crew];
78
79 if (podCrew != nil)
80 {
81 // Has crew -> is escape pod.
82 [rescuees addObjectsFromArray:podCrew];
83 [cargoItem setCrew:nil];
84 [cargo removeObjectAtIndex:i];
85 i--;
86 }
87 }
88
89 // step through the rescuees awarding insurance or bounty or adding to slaves
90 for (i = 0; i < [rescuees count]; i++)
91 {
92 OOCharacter *rescuee = [rescuees objectAtIndex:i];
93
94 if ([rescuee script])
95 {
96 [rescuee doScriptEvent:OOJSID("unloadCharacter")];
97 }
98 else if ([rescuee legacyScript])
99 {
102 withContextName:[NSString stringWithFormat:@"<character \"%@\" script>", [rescuee name]]
103 forTarget:nil];
104 }
105 else if ([rescuee insuranceCredits] && [rescuee legalStatus])
106 {
107 float reward = (5.0 + government) * [rescuee legalStatus];
108 float insurance = 10 * [rescuee insuranceCredits];
109 if (government > (Ranrot() & 7) || reward >= insurance)
110 {
111 // claim bounty for capture, ignore insurance
112 [result appendFormat:DESC(@"capture-reward-for-@@-@-credits-@-alt"),
113 [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits(reward, YES, NO),
114 OOStringFromDeciCredits(insurance, YES, NO)];
115 [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:reward],@"bounty",[rescuee infoForScripting],nil]];
116 }
117 else
118 {
119 // claim insurance reward with reduction of bounty
120 [result appendFormat:DESC(@"rescue-reward-for-@@-@-credits-@-alt"),
121 [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits(insurance - reward, YES, NO),
122 OOStringFromDeciCredits(reward, YES, NO)];
123 reward = insurance - reward;
124 [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:reward],@"insurance",[rescuee infoForScripting],nil]];
125 }
126 credits += reward;
127 added_entry = YES;
128 }
129 else if ([rescuee insuranceCredits])
130 {
131 // claim insurance reward
132 [result appendFormat:DESC(@"rescue-reward-for-@@-@-credits"),
133 [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits([rescuee insuranceCredits] * 10, YES, NO)];
134 credits += 10 * [rescuee insuranceCredits];
135 [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:(10 * [rescuee insuranceCredits])],@"insurance",[rescuee infoForScripting],nil]];
136
137 added_entry = YES;
138 }
139 else if ([rescuee legalStatus])
140 {
141 // claim bounty for capture
142 float reward = (5.0 + government) * [rescuee legalStatus];
143 [result appendFormat:DESC(@"capture-reward-for-@@-@-credits"),
144 [rescuee name], [rescuee shortDescription], OOStringFromDeciCredits(reward, YES, NO)];
145 credits += reward;
146 [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:reward],@"bounty",[rescuee infoForScripting],nil]];
147 added_entry = YES;
148 }
149 else
150 {
151 // sell as slave - increase no. of slaves in manifest
152 [shipCommodityData addQuantity:1 forGood:@"slaves"];
153 [self doScriptEvent:OOJSID("playerRescuedEscapePod") withArguments:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedInteger:0],@"slave",[rescuee infoForScripting],nil]];
154
155 }
156 if ((i < [rescuees count] - 1) && added_entry)
157 [result appendString:@"\n"];
158 added_entry = NO;
159 }
160
162
163 return result;
164}
165
166
167- (NSString *) checkPassengerContracts // returns messages from any passengers whose status have changed
168{
169 if ([self dockedStation] != [UNIVERSE station]) // only drop off passengers or fulfil contracts at main station
170 return nil;
171
172 // check escape pods...
173 // TODO
174
175 NSMutableString *result = [NSMutableString string];
176 unsigned i;
177
178 // check passenger contracts
179 for (i = 0; i < [passengers count]; i++)
180 {
181 NSDictionary* passenger_info = [[passengers oo_dictionaryAtIndex:i] retain];
182 NSString* passenger_name = [passenger_info oo_stringForKey:PASSENGER_KEY_NAME];
183 int dest = [passenger_info oo_intForKey:CONTRACT_KEY_DESTINATION];
184 // the system name can change via script
185 NSString* passenger_dest_name = [UNIVERSE getSystemName: dest];
186 int dest_eta = [passenger_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock;
187
188 if (system_id == dest)
189 {
190 // we've arrived in system!
191 if (dest_eta > 0)
192 {
193 // and in good time
194 long long fee = [passenger_info oo_longLongForKey:CONTRACT_KEY_FEE];
195 while ((randf() < 0.75)&&(dest_eta > 3600)) // delivered with more than an hour to spare and a decent customer?
196 {
197 fee *= 110; // tip + 10%
198 fee /= 100;
199 dest_eta *= 0.5;
200 }
201 credits += 10 * fee;
202
203 [result appendFormatLine:DESC(@"passenger-delivered-okay-@-@-@"), passenger_name, OOIntCredits(fee), passenger_dest_name];
204 if ([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0)
205 {
206 [self addRoleToPlayer:@"trader-courier+"];
207 }
208
209 [self increasePassengerReputation:RepForRisk([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])];
210 [passengers removeObjectAtIndex:i--];
211 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"passenger",@"success",[NSNumber numberWithUnsignedInteger:(10*fee)],passenger_info,nil]];
212 }
213 else
214 {
215 // but we're late!
216 long long fee = [passenger_info oo_longLongForKey:CONTRACT_KEY_FEE] / 2; // halve fare
217 while (randf() < 0.5) // maybe halve fare a few times!
218 fee /= 2;
219 credits += 10 * fee;
220
221 [result appendFormatLine:DESC(@"passenger-delivered-late-@-@-@"), passenger_name, OOIntCredits(fee), passenger_dest_name];
222 if ([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0)
223 {
224 [self addRoleToPlayer:@"trader-courier+"];
225 }
226
227 [passengers removeObjectAtIndex:i--];
228 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"passenger",@"late",[NSNumber numberWithUnsignedInteger:10*fee],passenger_info,nil]];
229
230 }
231 }
232 else
233 {
234 if (dest_eta < 0)
235 {
236 // we've run out of time!
237 [result appendFormatLine:DESC(@"passenger-failed-@"), passenger_name];
238
239 [self decreasePassengerReputation:RepForRisk([passenger_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])];
240 [passengers removeObjectAtIndex:i--];
241 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"passenger",@"failed",[NSNumber numberWithUnsignedInteger:0],passenger_info,nil]];
242 }
243 }
244 [passenger_info release];
245 }
246
247 // check parcel contracts
248 for (i = 0; i < [parcels count]; i++)
249 {
250 NSDictionary* parcel_info = [[parcels oo_dictionaryAtIndex:i] retain];
251 NSString* parcel_name = [parcel_info oo_stringForKey:PASSENGER_KEY_NAME];
252 int dest = [parcel_info oo_intForKey:CONTRACT_KEY_DESTINATION];
253 int dest_eta = [parcel_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock;
254
255 if (system_id == dest)
256 {
257 // we've arrived in system!
258 if (dest_eta > 0)
259 {
260 // and in good time
261 long long fee = [parcel_info oo_longLongForKey:CONTRACT_KEY_FEE];
262 while ((randf() < 0.75)&&(dest_eta > 86400)) // delivered with more than a day to spare and a decent customer?
263 {
264 // lower tips than passengers
265 fee *= 110; // tip + 10%
266 fee /= 100;
267 dest_eta *= 0.5;
268 }
269 credits += 10 * fee;
270
271 [result appendFormatLine:DESC(@"parcel-delivered-okay-@-@"), parcel_name, OOIntCredits(fee)];
272
273 [self increaseParcelReputation:RepForRisk([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])];
274
275 [parcels removeObjectAtIndex:i--];
276 if ([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0)
277 {
278 [self addRoleToPlayer:@"trader-courier+"];
279 }
280 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"parcel",@"success",[NSNumber numberWithUnsignedInteger:10*fee],parcel_info,nil]];
281
282 }
283 else
284 {
285 // but we're late!
286 long long fee = [parcel_info oo_longLongForKey:CONTRACT_KEY_FEE] / 2; // halve fare
287 while (randf() < 0.5) // maybe halve fare a few times!
288 fee /= 2;
289 credits += 10 * fee;
290
291 [result appendFormatLine:DESC(@"parcel-delivered-late-@-@"), parcel_name, OOIntCredits(fee)];
292 if ([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0] > 0)
293 {
294 [self addRoleToPlayer:@"trader-courier+"];
295 }
296 [parcels removeObjectAtIndex:i--];
297 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"parcel",@"late",[NSNumber numberWithUnsignedInteger:10*fee],parcel_info,nil]];
298 }
299 }
300 else
301 {
302 if (dest_eta < 0)
303 {
304 // we've run out of time!
305 [result appendFormatLine:DESC(@"parcel-failed-@"), parcel_name];
306
307 [self decreaseParcelReputation:RepForRisk([parcel_info oo_unsignedIntForKey:CONTRACT_KEY_RISK defaultValue:0])];
308 [parcels removeObjectAtIndex:i--];
309 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"parcel",@"failed",[NSNumber numberWithUnsignedInteger:0],parcel_info,nil]];
310 }
311 }
312 [parcel_info release];
313 }
314
315
316 // check cargo contracts
317 for (i = 0; i < [contracts count]; i++)
318 {
319 NSDictionary* contract_info = [[contracts oo_dictionaryAtIndex:i] retain];
320 NSString* contract_cargo_desc = [contract_info oo_stringForKey:CARGO_KEY_DESCRIPTION];
321 int dest = [contract_info oo_intForKey:CONTRACT_KEY_DESTINATION];
322 int dest_eta = [contract_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock;
323
324 if (system_id == dest)
325 {
326 // no longer needed
327 // int premium = 10 * [contract_info oo_floatForKey:CONTRACT_KEY_PREMIUM];
328 int fee = 10 * [contract_info oo_floatForKey:CONTRACT_KEY_FEE];
329
330 OOCommodityType contract_cargo_type = [contract_info oo_stringForKey:CARGO_KEY_TYPE];
331 int contract_amount = [contract_info oo_intForKey:CARGO_KEY_AMOUNT];
332
333 int quantity_on_hand = [shipCommodityData quantityForGood:contract_cargo_type];
334
335 // we've arrived in system!
336 if (dest_eta > 0)
337 {
338 // and in good time
339 if (quantity_on_hand >= contract_amount)
340 {
341 // with the goods too!
342
343 // remove the goods...
344 [shipCommodityData removeQuantity:contract_amount forGood:contract_cargo_type];
345
346 // pay the premium and fee
347 // credits += fee + premium;
348 // not any more: all contracts initially awarded by JS, so fee
349 // is now all that needs to be paid - CIM
350
351 if ([shipCommodityData exportLegalityForGood:contract_cargo_type] > 0)
352 {
353 [self addRoleToPlayer:@"trader-smuggler"];
354 }
355 else
356 {
357 [self addRoleToPlayer:@"trader"];
358 }
359
360 credits += fee;
361 [result appendFormatLine:DESC(@"cargo-delivered-okay-@-@"), contract_cargo_desc, OOCredits(fee)];
362
363 [contracts removeObjectAtIndex:i--];
364 // repute++
365 // +10 as cargo contracts don't have risk modifiers
366 [self increaseContractReputation:10];
367 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"success",[NSNumber numberWithUnsignedInteger:fee],contract_info,nil]];
368
369 }
370 else
371 {
372 // see if the amount of goods delivered is acceptable
373
374 float percent_delivered = 100.0 * (float)quantity_on_hand/(float)contract_amount;
375 float acceptable_ratio = 100.0 - 10.0 * system_id / 256.0; // down to 90%
376
377 if (percent_delivered >= acceptable_ratio)
378 {
379 // remove the goods...
380 [shipCommodityData setQuantity:0 forGood:contract_cargo_type];
381
382 // pay the fee
383 int shortfall = 100 - percent_delivered;
384 int payment = percent_delivered * (fee) / 100.0;
385 credits += payment;
386
387 if ([shipCommodityData exportLegalityForGood:contract_cargo_type] > 0)
388 {
389 [self addRoleToPlayer:@"trader-smuggler"];
390 }
391 else
392 {
393 [self addRoleToPlayer:@"trader"];
394 }
395
396 [result appendFormatLine:DESC(@"cargo-delivered-short-@-@-d"), contract_cargo_desc, OOCredits(payment), shortfall];
397
398 [contracts removeObjectAtIndex:i--];
399 // repute unchanged
400 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"short",[NSNumber numberWithUnsignedInteger:payment],contract_info,nil]];
401
402 }
403 else
404 {
405 [result appendFormatLine:DESC(@"cargo-refused-short-%@"), contract_cargo_desc];
406 // The player has still time to buy the missing goods elsewhere and fulfil the contract.
407 }
408 }
409 }
410 else
411 {
412 // but we're late!
413 [result appendFormatLine:DESC(@"cargo-delivered-late-@"), contract_cargo_desc];
414
415 [contracts removeObjectAtIndex:i--];
416 // repute--
417 [self decreaseContractReputation:10];
418 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"late",[NSNumber numberWithUnsignedInteger:0],contract_info,nil]];
419 }
420 }
421 else
422 {
423 if (dest_eta < 0)
424 {
425 // we've run out of time!
426 [result appendFormatLine:DESC(@"cargo-failed-@"), contract_cargo_desc];
427
428 [contracts removeObjectAtIndex:i--];
429 // repute--
430 [self decreaseContractReputation:10];
431 [self doScriptEvent:OOJSID("playerCompletedContract") withArguments:[NSArray arrayWithObjects:@"cargo",@"failed",[NSNumber numberWithUnsignedInteger:0],contract_info,nil]];
432 }
433 }
434 [contract_info release];
435 }
436
437 // check passenger_record for expired contracts
438 NSArray* names = [passenger_record allKeys];
439 for (i = 0; i < [names count]; i++)
440 {
441 double dest_eta = [passenger_record oo_doubleForKey:[names objectAtIndex:i]] - ship_clock;
442 if (dest_eta < 0)
443 {
444 // check they're not STILL on board
445 BOOL on_board = NO;
446 unsigned j;
447 for (j = 0; j < [passengers count]; j++)
448 {
449 NSDictionary* passenger_info = [passengers oo_dictionaryAtIndex:j];
450 if ([[passenger_info objectForKey:PASSENGER_KEY_NAME] isEqual:[names objectAtIndex:i]])
451 on_board = YES;
452 }
453 if (!on_board)
454 {
455 [passenger_record removeObjectForKey:[names objectAtIndex:i]];
456 }
457 }
458 }
459
460 // check contract_record for expired contracts
461 NSArray* ids = [contract_record allKeys];
462 for (i = 0; i < [ids count]; i++)
463 {
464 double dest_eta = [(NSNumber*)[contract_record objectForKey:[ids objectAtIndex:i]] doubleValue] - ship_clock;
465 if (dest_eta < 0)
466 {
467 [contract_record removeObjectForKey:[ids objectAtIndex:i]];
468 }
469 }
470
471 // check parcel_record for expired deliveries
472 ids = [parcel_record allKeys];
473 for (i = 0; i < [ids count]; i++)
474 {
475 double dest_eta = [(NSNumber*)[parcel_record objectForKey:[ids objectAtIndex:i]] doubleValue] - ship_clock;
476 if (dest_eta < 0)
477 {
478 [parcel_record removeObjectForKey:[ids objectAtIndex:i]];
479 }
480 }
481
482
483 if ([result length] == 0)
484 {
485 result = nil;
486 }
487 else
488 {
489 // Should have a trailing \n
490 [result deleteCharacterAtIndex:[result length] - 1];
491 }
492
493 return result;
494}
495
496
497- (OOCargoQuantity) contractedVolumeForGood:(OOCommodityType) good
498{
499 OOCargoQuantity total = 0;
500 for (unsigned i = 0; i < [contracts count]; i++)
501 {
502 NSDictionary* contract_info = [contracts oo_dictionaryAtIndex:i];
503 OOCommodityType contract_cargo_type = [contract_info oo_stringForKey:CARGO_KEY_TYPE];
504 if ([good isEqualToString:contract_cargo_type])
505 {
506 total += [contract_info oo_unsignedIntegerForKey:CARGO_KEY_AMOUNT];
507 }
508 }
509 return total;
510}
511
512
513- (void) addMessageToReport:(NSString*) report
514{
515 if ([report length] != 0)
516 {
517 if ([dockingReport length] == 0)
518 [dockingReport appendString:report];
519 else
520 [dockingReport appendFormat:@"\n\n%@", report];
521 }
522}
523
524
525- (NSDictionary*) reputation
526{
527 return reputation;
528}
529
530
531- (int) passengerReputation
532{
533 int good = [reputation oo_intForKey:PASSAGE_GOOD_KEY];
534 int bad = [reputation oo_intForKey:PASSAGE_BAD_KEY];
535 int unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY];
536
537 if (unknown > 0)
538 unknown = MAX_CONTRACT_REP - (((2*unknown)+(market_rnd % unknown))/3);
539 else
540 unknown = MAX_CONTRACT_REP;
541
542 return (good + unknown - 3 * bad) / 2; // return a number from -MAX_CONTRACT_REP to +MAX_CONTRACT_REP
543}
544
545
546- (void) increasePassengerReputation:(unsigned)amount
547{
548 int good = [reputation oo_intForKey:PASSAGE_GOOD_KEY];
549 int bad = [reputation oo_intForKey:PASSAGE_BAD_KEY];
550 int unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY];
551
552 for (unsigned i=0;i<amount;i++)
553 {
554 if (bad > 0)
555 {
556 // shift a bean from bad to unknown
557 bad--;
558 if (unknown < MAX_CONTRACT_REP)
559 unknown++;
560 }
561 else
562 {
563 // shift a bean from unknown to good
564 if (unknown > 0)
565 unknown--;
566 if (good < MAX_CONTRACT_REP)
567 good++;
568 }
569 }
570 [reputation oo_setInteger:good forKey:PASSAGE_GOOD_KEY];
571 [reputation oo_setInteger:bad forKey:PASSAGE_BAD_KEY];
572 [reputation oo_setInteger:unknown forKey:PASSAGE_UNKNOWN_KEY];
573}
574
575
576- (void) decreasePassengerReputation:(unsigned)amount
577{
578 int good = [reputation oo_intForKey:PASSAGE_GOOD_KEY];
579 int bad = [reputation oo_intForKey:PASSAGE_BAD_KEY];
580 int unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY];
581
582for (unsigned i=0;i<amount;i++)
583 {
584 if (good > 0)
585 {
586 // shift a bean from good to bad
587 good--;
588 if (bad < MAX_CONTRACT_REP)
589 bad++;
590 }
591 else
592 {
593 // shift a bean from unknown to bad
594 if (unknown > 0)
595 unknown--;
596 if (bad < MAX_CONTRACT_REP)
597 bad++;
598 }
599 }
600 [reputation oo_setInteger:good forKey:PASSAGE_GOOD_KEY];
601 [reputation oo_setInteger:bad forKey:PASSAGE_BAD_KEY];
602 [reputation oo_setInteger:unknown forKey:PASSAGE_UNKNOWN_KEY];
603}
604
605
606- (int) parcelReputation
607{
608 int good = [reputation oo_intForKey:PARCEL_GOOD_KEY];
609 int bad = [reputation oo_intForKey:PARCEL_BAD_KEY];
610 int unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY];
611
612 if (unknown > 0)
613 unknown = MAX_CONTRACT_REP - (((2*unknown)+(market_rnd % unknown))/3);
614 else
615 unknown = MAX_CONTRACT_REP;
616
617 return (good + unknown - 3 * bad) / 2; // return a number from -MAX_CONTRACT_REP to +MAX_CONTRACT_REP
618}
619
620
621- (void) increaseParcelReputation:(unsigned)amount
622{
623 int good = [reputation oo_intForKey:PARCEL_GOOD_KEY];
624 int bad = [reputation oo_intForKey:PARCEL_BAD_KEY];
625 int unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY];
626
627 for (unsigned i=0;i<amount;i++)
628 {
629 if (bad > 0)
630 {
631 // shift a bean from bad to unknown
632 bad--;
633 if (unknown < MAX_CONTRACT_REP)
634 unknown++;
635 }
636 else
637 {
638 // shift a bean from unknown to good
639 if (unknown > 0)
640 unknown--;
641 if (good < MAX_CONTRACT_REP)
642 good++;
643 }
644 }
645 [reputation oo_setInteger:good forKey:PARCEL_GOOD_KEY];
646 [reputation oo_setInteger:bad forKey:PARCEL_BAD_KEY];
647 [reputation oo_setInteger:unknown forKey:PARCEL_UNKNOWN_KEY];
648}
649
650
651- (void) decreaseParcelReputation:(unsigned)amount
652{
653 int good = [reputation oo_intForKey:PARCEL_GOOD_KEY];
654 int bad = [reputation oo_intForKey:PARCEL_BAD_KEY];
655 int unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY];
656
657 for (unsigned i=0;i<amount;i++)
658 {
659 if (good > 0)
660 {
661 // shift a bean from good to bad
662 good--;
663 if (bad < MAX_CONTRACT_REP)
664 bad++;
665 }
666 else
667 {
668 // shift a bean from unknown to bad
669 if (unknown > 0)
670 unknown--;
671 if (bad < MAX_CONTRACT_REP)
672 bad++;
673 }
674 }
675 [reputation oo_setInteger:good forKey:PARCEL_GOOD_KEY];
676 [reputation oo_setInteger:bad forKey:PARCEL_BAD_KEY];
677 [reputation oo_setInteger:unknown forKey:PARCEL_UNKNOWN_KEY];
678}
679
680
681- (int) contractReputation
682{
683 int good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY];
684 int bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY];
685 int unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY];
686
687 if (unknown > 0)
688 unknown = MAX_CONTRACT_REP - (((2*unknown)+(market_rnd % unknown))/3);
689 else
690 unknown = MAX_CONTRACT_REP;
691
692 return (good + unknown - 3 * bad) / 2; // return a number from -MAX_CONTRACT_REP to +MAX_CONTRACT_REP
693}
694
695
696- (void) increaseContractReputation:(unsigned)amount
697{
698 int good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY];
699 int bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY];
700 int unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY];
701
702 for (unsigned i=0;i<amount;i++)
703 {
704 if (bad > 0)
705 {
706 // shift a bean from bad to unknown
707 bad--;
708 if (unknown < MAX_CONTRACT_REP)
709 unknown++;
710 }
711 else
712 {
713 // shift a bean from unknown to good
714 if (unknown > 0)
715 unknown--;
716 if (good < MAX_CONTRACT_REP)
717 good++;
718 }
719 }
720 [reputation oo_setInteger:good forKey:CONTRACTS_GOOD_KEY];
721 [reputation oo_setInteger:bad forKey:CONTRACTS_BAD_KEY];
722 [reputation oo_setInteger:unknown forKey:CONTRACTS_UNKNOWN_KEY];
723}
724
725
726- (void) decreaseContractReputation:(unsigned)amount
727{
728 int good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY];
729 int bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY];
730 int unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY];
731
732 for (unsigned i=0;i<amount;i++)
733 {
734 if (good > 0)
735 {
736 // shift a bean from good to bad
737 good--;
738 if (bad < MAX_CONTRACT_REP)
739 bad++;
740 }
741 else
742 {
743 // shift a bean from unknown to bad
744 if (unknown > 0)
745 unknown--;
746 if (bad < MAX_CONTRACT_REP)
747 bad++;
748 }
749 }
750 [reputation oo_setInteger:good forKey:CONTRACTS_GOOD_KEY];
751 [reputation oo_setInteger:bad forKey:CONTRACTS_BAD_KEY];
752 [reputation oo_setInteger:unknown forKey:CONTRACTS_UNKNOWN_KEY];
753}
754
755
756- (void) erodeReputation
757{
758 int c_good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY];
759 int c_bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY];
760 int c_unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY];
761 int p_good = [reputation oo_intForKey:PASSAGE_GOOD_KEY];
762 int p_bad = [reputation oo_intForKey:PASSAGE_BAD_KEY];
763 int p_unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY];
764 int pl_good = [reputation oo_intForKey:PARCEL_GOOD_KEY];
765 int pl_bad = [reputation oo_intForKey:PARCEL_BAD_KEY];
766 int pl_unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY];
767
768 if (c_unknown < MAX_CONTRACT_REP)
769 {
770 if (c_bad > 0)
771 c_bad--;
772 else
773 {
774 if (c_good > 0)
775 c_good--;
776 }
777 c_unknown++;
778 }
779
780 if (p_unknown < MAX_CONTRACT_REP)
781 {
782 if (p_bad > 0)
783 p_bad--;
784 else
785 {
786 if (p_good > 0)
787 p_good--;
788 }
789 p_unknown++;
790 }
791
792 if (pl_unknown < MAX_CONTRACT_REP)
793 {
794 if (pl_bad > 0)
795 pl_bad--;
796 else
797 {
798 if (pl_good > 0)
799 pl_good--;
800 }
801 pl_unknown++;
802 }
803
804 [reputation setObject:[NSNumber numberWithInt:c_good] forKey:CONTRACTS_GOOD_KEY];
805 [reputation setObject:[NSNumber numberWithInt:c_bad] forKey:CONTRACTS_BAD_KEY];
806 [reputation setObject:[NSNumber numberWithInt:c_unknown] forKey:CONTRACTS_UNKNOWN_KEY];
807 [reputation setObject:[NSNumber numberWithInt:p_good] forKey:PASSAGE_GOOD_KEY];
808 [reputation setObject:[NSNumber numberWithInt:p_bad] forKey:PASSAGE_BAD_KEY];
809 [reputation setObject:[NSNumber numberWithInt:p_unknown] forKey:PASSAGE_UNKNOWN_KEY];
810 [reputation setObject:[NSNumber numberWithInt:pl_good] forKey:PARCEL_GOOD_KEY];
811 [reputation setObject:[NSNumber numberWithInt:pl_bad] forKey:PARCEL_BAD_KEY];
812 [reputation setObject:[NSNumber numberWithInt:pl_unknown] forKey:PARCEL_UNKNOWN_KEY];
813
814}
815
816
817/* Update reputation levels in case of change in MAX_CONTRACT_REP */
818- (void) normaliseReputation
819{
820 int c_good = [reputation oo_intForKey:CONTRACTS_GOOD_KEY];
821 int c_bad = [reputation oo_intForKey:CONTRACTS_BAD_KEY];
822 int c_unknown = [reputation oo_intForKey:CONTRACTS_UNKNOWN_KEY];
823 int p_good = [reputation oo_intForKey:PASSAGE_GOOD_KEY];
824 int p_bad = [reputation oo_intForKey:PASSAGE_BAD_KEY];
825 int p_unknown = [reputation oo_intForKey:PASSAGE_UNKNOWN_KEY];
826 int pl_good = [reputation oo_intForKey:PARCEL_GOOD_KEY];
827 int pl_bad = [reputation oo_intForKey:PARCEL_BAD_KEY];
828 int pl_unknown = [reputation oo_intForKey:PARCEL_UNKNOWN_KEY];
829
830 int c = c_good + c_bad + c_unknown;
831 if (c == 0)
832 {
833 c_unknown = MAX_CONTRACT_REP;
834 }
835 else if (c != MAX_CONTRACT_REP)
836 {
837 c_good = c_good * MAX_CONTRACT_REP / c;
838 c_bad = c_bad * MAX_CONTRACT_REP / c;
839 c_unknown = MAX_CONTRACT_REP - c_good - c_bad;
840 }
841
842 int p = p_good + p_bad + p_unknown;
843 if (p == 0)
844 {
845 p_unknown = MAX_CONTRACT_REP;
846 }
847 else if (p != MAX_CONTRACT_REP)
848 {
849 p_good = p_good * MAX_CONTRACT_REP / p;
850 p_bad = p_bad * MAX_CONTRACT_REP / p;
851 p_unknown = MAX_CONTRACT_REP - p_good - p_bad;
852 }
853
854 int pl = pl_good + pl_bad + pl_unknown;
855 if (pl == 0)
856 {
857 pl_unknown = MAX_CONTRACT_REP;
858 }
859 else if (pl != MAX_CONTRACT_REP)
860 {
861 pl_good = pl_good * MAX_CONTRACT_REP / pl;
862 pl_bad = pl_bad * MAX_CONTRACT_REP / pl;
863 pl_unknown = MAX_CONTRACT_REP - pl_good - pl_bad;
864 }
865
866 [reputation setObject:[NSNumber numberWithInt:c_good] forKey:CONTRACTS_GOOD_KEY];
867 [reputation setObject:[NSNumber numberWithInt:c_bad] forKey:CONTRACTS_BAD_KEY];
868 [reputation setObject:[NSNumber numberWithInt:c_unknown] forKey:CONTRACTS_UNKNOWN_KEY];
869 [reputation setObject:[NSNumber numberWithInt:p_good] forKey:PASSAGE_GOOD_KEY];
870 [reputation setObject:[NSNumber numberWithInt:p_bad] forKey:PASSAGE_BAD_KEY];
871 [reputation setObject:[NSNumber numberWithInt:p_unknown] forKey:PASSAGE_UNKNOWN_KEY];
872 [reputation setObject:[NSNumber numberWithInt:pl_good] forKey:PARCEL_GOOD_KEY];
873 [reputation setObject:[NSNumber numberWithInt:pl_bad] forKey:PARCEL_BAD_KEY];
874 [reputation setObject:[NSNumber numberWithInt:pl_unknown] forKey:PARCEL_UNKNOWN_KEY];
875
876}
877
878
879- (BOOL) addPassenger:(NSString*)Name start:(unsigned)start destination:(unsigned)Destination eta:(double)eta fee:(double)fee advance:(double)advance risk:(unsigned)risk
880{
881 NSDictionary* passenger_info = [NSDictionary dictionaryWithObjectsAndKeys:
882 Name, PASSENGER_KEY_NAME,
883 [NSNumber numberWithInt:start], CONTRACT_KEY_START,
884 [NSNumber numberWithInt:Destination], CONTRACT_KEY_DESTINATION,
885 [NSNumber numberWithDouble:[PLAYER clockTime]], CONTRACT_KEY_DEPARTURE_TIME,
886 [NSNumber numberWithDouble:eta], CONTRACT_KEY_ARRIVAL_TIME,
887 [NSNumber numberWithDouble:fee], CONTRACT_KEY_FEE,
888 [NSNumber numberWithDouble:advance], CONTRACT_KEY_PREMIUM,
889 [NSNumber numberWithUnsignedInt:risk], CONTRACT_KEY_RISK,
890
891 NULL];
892
893 // extra checks, just in case.
894 if ([passengers count] >= max_passengers || [passenger_record objectForKey:Name] != nil) return NO;
895
896 if (risk > 1)
897 {
898 [self addRoleToPlayer:@"trader-courier+"];
899 }
900
901 [passengers addObject:passenger_info];
902 [passenger_record setObject:[NSNumber numberWithDouble:eta] forKey:Name];
903
904 [self doScriptEvent:OOJSID("playerEnteredContract") withArguments:[NSArray arrayWithObjects:@"passenger",passenger_info,nil]];
905
906 return YES;
907}
908
909
910- (BOOL) removePassenger:(NSString*)Name // removes the first passenger that answers to Name, returns NO if none found
911{
912 // extra check, just in case.
913 if ([passengers count] == 0) return NO;
914
915 unsigned i;
916
917 for (i = 0; i < [passengers count]; i++)
918 {
919 NSString *this_name = [[passengers oo_dictionaryAtIndex:i] oo_stringForKey:PASSENGER_KEY_NAME];
920
921 if ([Name isEqualToString:this_name])
922 {
923 [passengers removeObjectAtIndex:i];
924 [passenger_record removeObjectForKey:Name];
925 return YES;
926 }
927 }
928
929 return NO;
930}
931
932
933- (BOOL) addParcel:(NSString*)Name start:(unsigned)start destination:(unsigned)Destination eta:(double)eta fee:(double)fee premium:(double)premium risk:(unsigned)risk
934{
935 NSDictionary* parcel_info = [NSDictionary dictionaryWithObjectsAndKeys:
936 Name, PASSENGER_KEY_NAME,
937 [NSNumber numberWithInt:start], CONTRACT_KEY_START,
938 [NSNumber numberWithInt:Destination], CONTRACT_KEY_DESTINATION,
939 [NSNumber numberWithDouble:[PLAYER clockTime]], CONTRACT_KEY_DEPARTURE_TIME,
940 [NSNumber numberWithDouble:eta], CONTRACT_KEY_ARRIVAL_TIME,
941 [NSNumber numberWithDouble:fee], CONTRACT_KEY_FEE,
942 [NSNumber numberWithDouble:premium], CONTRACT_KEY_PREMIUM,
943 [NSNumber numberWithUnsignedInt:risk], CONTRACT_KEY_RISK,
944 NULL];
945
946 // extra checks, just in case.
947 // FIXME: do we absolutely need this check? can we live
948 // with parcels of senders who happen to have the same
949 // name? - Nikos 20160527
950 //if ([parcel_record objectForKey:Name] != nil) return NO;
951
952 if (risk > 1)
953 {
954 [self addRoleToPlayer:@"trader-courier+"];
955 }
956
957 [parcels addObject:parcel_info];
958 [parcel_record setObject:[NSNumber numberWithDouble:eta] forKey:Name];
959
960 [self doScriptEvent:OOJSID("playerEnteredContract") withArguments:[NSArray arrayWithObjects:@"parcel",parcel_info,nil]];
961
962 return YES;
963}
964
965
966- (BOOL) removeParcel:(NSString*)Name // removes the first parcel that answers to Name, returns NO if none found
967{
968 // extra check, just in case.
969 if ([parcels count] == 0) return NO;
970
971 unsigned i;
972
973 for (i = 0; i < [parcels count]; i++)
974 {
975 NSString *this_name = [[parcels oo_dictionaryAtIndex:i] oo_stringForKey:PASSENGER_KEY_NAME];
976
977 if ([Name isEqualToString:this_name])
978 {
979 [parcels removeObjectAtIndex:i];
980 [parcel_record removeObjectForKey:Name];
981 return YES;
982 }
983 }
984
985 return NO;
986}
987
988
989- (BOOL) awardContract:(unsigned)qty commodity:(OOCommodityType)type start:(unsigned)start
990 destination:(unsigned)Destination eta:(double)eta fee:(double)fee premium:(double)premium
991{
992
993 unsigned sr1 = Ranrot()&0x111111;
994 int sr2 = Ranrot()&0x111111;
995
996 NSString *cargo_ID =[NSString stringWithFormat:@"%06x-%06x", sr1, sr2];
997
998 if (![[UNIVERSE commodities] goodDefined:type]) return NO;
999 if (qty < 1) return NO;
1000
1001 // avoid duplicate cargo_IDs
1002 while ([contract_record objectForKey:cargo_ID] != nil)
1003 {
1004 sr2++;
1005 cargo_ID =[NSString stringWithFormat:@"%06x-%06x", sr1, sr2];
1006 }
1007
1008 NSDictionary* cargo_info = [NSDictionary dictionaryWithObjectsAndKeys:
1009 cargo_ID, CARGO_KEY_ID,
1010 type, CARGO_KEY_TYPE,
1011 [NSNumber numberWithInt:qty], CARGO_KEY_AMOUNT,
1012 [UNIVERSE describeCommodity:type amount:qty], CARGO_KEY_DESCRIPTION,
1013 [NSNumber numberWithInt:start], CONTRACT_KEY_START,
1014 [NSNumber numberWithInt:Destination], CONTRACT_KEY_DESTINATION,
1015 [NSNumber numberWithDouble:[PLAYER clockTime]], CONTRACT_KEY_DEPARTURE_TIME,
1016 [NSNumber numberWithDouble:eta], CONTRACT_KEY_ARRIVAL_TIME,
1017 [NSNumber numberWithDouble:fee], CONTRACT_KEY_FEE,
1018 [NSNumber numberWithDouble:premium], CONTRACT_KEY_PREMIUM,
1019 NULL];
1020
1021 // check available space
1022
1023 OOCargoQuantity cargoSpaceRequired = qty;
1024 OOMassUnit contractCargoUnits = [shipCommodityData massUnitForGood:type];
1025
1026 if (contractCargoUnits == UNITS_KILOGRAMS) cargoSpaceRequired /= 1000;
1027 if (contractCargoUnits == UNITS_GRAMS) cargoSpaceRequired /= 1000000;
1028
1029 if (cargoSpaceRequired > [self availableCargoSpace]) return NO;
1030
1031 [shipCommodityData addQuantity:qty forGood:type];
1032
1033 current_cargo = [self cargoQuantityOnBoard];
1034
1035 if ([shipCommodityData exportLegalityForGood:type] > 0)
1036 {
1037 [self addRoleToPlayer:@"trader-smuggler"];
1038 [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-illegal"];
1039 }
1040 else
1041 {
1042 [self addRoleToPlayer:@"trader"];
1043 [roleWeightFlags setObject:[NSNumber numberWithInt:1] forKey:@"bought-legal"];
1044 }
1045
1046 [contracts addObject:cargo_info];
1047 [contract_record setObject:[NSNumber numberWithDouble:eta] forKey:cargo_ID];
1048
1049 [self doScriptEvent:OOJSID("playerEnteredContract") withArguments:[NSArray arrayWithObjects:@"cargo",cargo_info,nil]];
1050
1051 return YES;
1052}
1053
1054
1055- (BOOL) removeContract:(OOCommodityType)type destination:(unsigned)dest // removes the first match found, returns NO if none found
1056{
1057 if ([contracts count] == 0 || dest > 255) return NO;
1058
1059 if (![[UNIVERSE commodities] goodDefined:type]) return NO;
1060
1061 unsigned i;
1062
1063 for (i = 0; i < [contracts count]; i++)
1064 {
1065 NSDictionary *contractInfo = [contracts oo_dictionaryAtIndex:i];
1066 unsigned cargoDest = [contractInfo oo_intForKey:CONTRACT_KEY_DESTINATION];
1067 OOCommodityType cargoType = [contractInfo oo_stringForKey:CARGO_KEY_TYPE];
1068
1069 if ([cargoType isEqualToString:type] && cargoDest == dest)
1070 {
1071 [contract_record removeObjectForKey:[contractInfo oo_stringForKey:CARGO_KEY_ID]];
1072 [contracts removeObjectAtIndex:i];
1073 return YES;
1074 }
1075 }
1076
1077 return NO;
1078}
1079
1080
1081
1082
1083- (NSArray*) passengerList
1084{
1085 return [self contractsListFromArray:passengers forCargo:NO forParcels:NO];
1086}
1087
1088
1089- (NSArray*) parcelList
1090{
1091 return [self contractsListFromArray:parcels forCargo:NO forParcels:YES];
1092}
1093
1094
1095- (NSArray*) contractList
1096{
1097 return [self contractsListFromArray:contracts forCargo:YES forParcels:NO];
1098}
1099
1100
1101- (NSArray*) contractsListFromArray:(NSArray *) contracts_array forCargo:(BOOL) forCargo forParcels:(BOOL)forParcels
1102{
1103 // check contracts
1104 NSMutableArray *result = [NSMutableArray arrayWithCapacity:5];
1105 NSString *formatString = (forCargo||forParcels) ? @"oolite-manifest-item-delivery" : @"oolite-manifest-person-travelling";
1106 unsigned i;
1107 for (i = 0; i < [contracts_array count]; i++)
1108 {
1109 NSDictionary* contract_info = (NSDictionary *)[contracts_array objectAtIndex:i];
1110 NSString* label = [contract_info oo_stringForKey:forCargo ? CARGO_KEY_DESCRIPTION : PASSENGER_KEY_NAME];
1111 // the system name can change via script. The following PASSENGER_KEYs are identical to the corresponding CONTRACT_KEYs
1112 NSString* destination = [UNIVERSE getSystemName: [contract_info oo_intForKey:CONTRACT_KEY_DESTINATION]];
1113 int dest_eta = [contract_info oo_doubleForKey:CONTRACT_KEY_ARRIVAL_TIME] - ship_clock;
1114 NSString *deadline = [UNIVERSE shortTimeDescription:dest_eta];
1115
1116 OOCreditsQuantity fee = [contract_info oo_intForKey:CONTRACT_KEY_FEE];
1117 NSString *feeDesc = OOIntCredits(fee);
1118
1119 [result addObject:OOExpandKey(formatString, label, destination, deadline, feeDesc)];
1120
1121 }
1122
1123 return result;
1124}
1125
1126
1127// only use within setGuiToManifestScreen
1128#define SET_MANIFEST_ROW(obj,color,row) ([self setManifestScreenRow:obj inColor:color forRow:row ofRows:max_rows andOffset:page_offset inMultipage:multi_page])
1129
1130- (void) setGuiToManifestScreen
1131{
1132 OOGUIScreenID oldScreen = gui_screen;
1133
1134 GuiDisplayGen *gui = [UNIVERSE gui];
1135 gui_screen = GUI_SCREEN_MANIFEST;
1136 BOOL guiChanged = (oldScreen != gui_screen);
1137 if (guiChanged)
1138 {
1139 [gui setStatusPage:0]; // need to do this earlier than the rest
1140 }
1141
1142 // GUI stuff
1143 {
1144 NSInteger current, max;
1145 OOColor *subheadColor = [gui colorFromSetting:kGuiManifestSubheadColor defaultValue:[OOColor greenColor]];
1146 OOColor *entryColor = [gui colorFromSetting:kGuiManifestEntryColor defaultValue:nil];
1147 OOColor *scrollColor = [gui colorFromSetting:kGuiManifestScrollColor defaultValue:[OOColor greenColor]];
1148 OOColor *noScrollColor = [gui colorFromSetting:kGuiManifestNoScrollColor defaultValue:[OOColor darkGrayColor]];
1149
1150 NSArray* cargoManifest = [self cargoList];
1151 NSArray* missionsManifest = [self missionsList];
1152
1153 NSUInteger i = 0;
1154 NSUInteger max_rows = 20;
1155 NSUInteger manifestCount = [cargoManifest count];
1156 NSUInteger cargoRowCount = (manifestCount + 1)/2;
1157 OOGUIRow cargoRow = 2;
1158 OOGUIRow missionsRow = 2;
1159
1160 OOGUIRow nextPageRow = MANIFEST_SCREEN_ROW_NEXT;
1161 // show extra lines if no HUD is displayed.
1162 if ([[self hud] isHidden] || [[self hud] allowBigGui])
1163 {
1164 max_rows += 7;
1165 nextPageRow += 7;
1166 }
1167
1168 NSUInteger mmRows = 0;
1169 id mmEntry = nil;
1170 foreach (mmEntry, missionsManifest)
1171 {
1172 if ([mmEntry isKindOfClass:[NSString class]])
1173 {
1174 ++mmRows;
1175 }
1176 else if ([mmEntry isKindOfClass:[NSArray class]])
1177 {
1178 mmRows += [(NSArray *)mmEntry count];
1179 }
1180 }
1181
1182 NSInteger page_offset = 0;
1183 BOOL multi_page = NO;
1184// NSUInteger total_rows = cargoRowCount + MAX(1U,[passengerManifest count]) + MAX(1U,[contractManifest count]) + mmRows + MAX(1U,[parcelManifest count]) + 5;
1185 NSUInteger total_rows = cargoRowCount + mmRows + 5;
1186 if (total_rows > max_rows)
1187 {
1188 max_rows -= 2;
1189 page_offset = ([gui statusPage]-1) * max_rows;
1190 if (page_offset < 0 || (NSUInteger)page_offset >= total_rows)
1191 {
1192 [gui setStatusPage:0];
1193 page_offset = 0;
1194 }
1195 multi_page = YES;
1196 }
1197
1198
1199 OOGUITabSettings tab_stops;
1200 tab_stops[0] = 0;
1201 tab_stops[1] = 256;
1202 [gui overrideTabs:tab_stops from:kGuiManifestTabs length:3];
1203 [gui setTabStops:tab_stops];
1204
1205 // Cargo Manifest
1206 current_cargo = [self cargoQuantityOnBoard];
1207
1208 [gui clearAndKeepBackground:!guiChanged];
1209 [gui setTitle:DESC(@"manifest-title")];
1210
1211 current = current_cargo;
1212 max = [self maxAvailableCargoSpace];
1213 NSString *cargoString = OOExpandKey(@"oolite-manifest-cargo", current, max);
1214 current = [[self passengerList] count];
1215 max = max_passengers;
1216 NSString *cabinString = OOExpandKey(@"oolite-manifest-cabins", current, max);
1217 NSArray *manifestHeader = [NSArray arrayWithObjects:cargoString,cabinString,nil];
1218
1219 SET_MANIFEST_ROW( manifestHeader , entryColor, cargoRow - 1);
1220
1221 if (manifestCount > 0)
1222 {
1223 for (i = 0; i < cargoRowCount; i++)
1224 {
1225 NSMutableArray* row_info = [NSMutableArray arrayWithCapacity:3];
1226 // i is always smaller than manifest_count, no need to test.
1227 [row_info addObject:[cargoManifest objectAtIndex:i]];
1228 if (i + cargoRowCount < manifestCount)
1229 {
1230 [row_info addObject:[cargoManifest objectAtIndex:i + cargoRowCount]];
1231 }
1232 else
1233 {
1234 [row_info addObject:@""];
1235 }
1236 SET_MANIFEST_ROW( (NSArray *)row_info, subheadColor, cargoRow + i);
1237 }
1238 }
1239 else
1240 {
1241 SET_MANIFEST_ROW( (DESC(@"manifest-none")), subheadColor, cargoRow);
1242 cargoRowCount=1;
1243 }
1244
1245 missionsRow = cargoRow + cargoRowCount + 1;
1246
1247 // Missions Manifest
1248 manifestCount = [missionsManifest count];
1249
1250 if (manifestCount > 0)
1251 {
1252 if ([[missionsManifest objectAtIndex:0] isKindOfClass:[NSString class]])
1253 {
1254 // then there's at least one without its own heading
1255 // to go under the generic 'missions' heading
1256 SET_MANIFEST_ROW( (DESC(@"manifest-missions")) , entryColor, missionsRow - 1);
1257 }
1258 else
1259 {
1260 missionsRow--;
1261 }
1262
1263 NSUInteger mmRow = 0;
1264 for (i = 0; i < manifestCount; i++)
1265 {
1266 NSString *mmItem = nil;
1267 mmEntry = [missionsManifest objectAtIndex:i];
1268 if ([mmEntry isKindOfClass:[NSString class]])
1269 {
1270 mmItem = [NSString stringWithFormat:@"\t%@",(NSString *)mmEntry];
1271 SET_MANIFEST_ROW( (mmItem) , subheadColor, missionsRow + mmRow);
1272 ++mmRow;
1273 }
1274 else if ([mmEntry isKindOfClass:[NSArray class]])
1275 {
1276 BOOL isHeading = YES;
1277 foreach (mmItem, mmEntry)
1278 {
1279 if (isHeading)
1280 {
1281 SET_MANIFEST_ROW( ((NSString *)mmItem) , entryColor , missionsRow + mmRow);
1282 }
1283 else
1284 {
1285 mmItem = [NSString stringWithFormat:@"\t%@",(NSString *)mmItem];
1286 SET_MANIFEST_ROW( ((NSString *)mmItem) , subheadColor , missionsRow + mmRow);
1287 }
1288 isHeading = NO;
1289 ++mmRow;
1290 }
1291 }
1292 }
1293 }
1294
1295 if (multi_page)
1296 {
1298 OOGUIRow r_end = nextPageRow;
1299 if (page_offset > 0)
1300 {
1301 [gui setColor:scrollColor forRow:MANIFEST_SCREEN_ROW_BACK];
1302 [gui setKey:GUI_KEY_OK forRow:MANIFEST_SCREEN_ROW_BACK];
1303 }
1304 else
1305 {
1306 [gui setColor:noScrollColor forRow:MANIFEST_SCREEN_ROW_BACK];
1307 r_start = nextPageRow;
1308 }
1309 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ",nil] forRow:MANIFEST_SCREEN_ROW_BACK];
1310
1311 if (total_rows > max_rows + page_offset)
1312 {
1313 [gui setColor:scrollColor forRow:nextPageRow];
1314 [gui setKey:GUI_KEY_OK forRow:nextPageRow];
1315 }
1316 else
1317 {
1318 [gui setColor:noScrollColor forRow:nextPageRow];
1320 }
1321 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ",nil] forRow:nextPageRow];
1322
1323 [gui setSelectableRange:NSMakeRange(r_start,r_end+1-r_start)];
1324 [gui setSelectedRow:r_start];
1325
1326 }
1327
1328 [gui setShowTextCursor:NO];
1329 }
1330 /* ends */
1331
1332 if (lastTextKey)
1333 {
1334 [lastTextKey release];
1335 lastTextKey = nil;
1336 }
1337
1338 [self setShowDemoShips:NO];
1339 [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
1340
1341 if (guiChanged)
1342 {
1343 [gui setForegroundTextureKey:[self status] == STATUS_DOCKED ? @"docked_overlay" : @"overlay"];
1344 [gui setBackgroundTextureKey:@"manifest"];
1345 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
1346 }
1347}
1348
1349
1350- (void) setManifestScreenRow:(id)object inColor:(OOColor*)color forRow:(OOGUIRow)row ofRows:(OOGUIRow)max_rows andOffset:(OOGUIRow)offset inMultipage:(BOOL)multi
1351{
1352 OOGUIRow disp_row = row - offset;
1353 if (disp_row < 1 || disp_row > max_rows) return;
1354 if (multi) disp_row++;
1355 GuiDisplayGen *gui = [UNIVERSE gui];
1356 if ([object isKindOfClass:[NSString class]])
1357 {
1358 [gui setText:(NSString*)object forRow:disp_row];
1359 }
1360 else if ([object isKindOfClass:[NSArray class]])
1361 {
1362 [gui setArray:(NSArray*)object forRow:disp_row];
1363 }
1364 [gui setColor:color forRow:disp_row];
1365}
1366
1367
1368- (void) setGuiToDockingReportScreen
1369{
1370 GuiDisplayGen *gui = [UNIVERSE gui];
1371
1372 OOGUIScreenID oldScreen = gui_screen;
1373 gui_screen = GUI_SCREEN_REPORT;
1374 BOOL guiChanged = (oldScreen != gui_screen);
1375
1376 OOGUIRow i, text_row = 1;
1377
1378 [dockingReport setString:[dockingReport stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
1379
1380 // GUI stuff
1381 {
1382 [gui clearAndKeepBackground:!guiChanged];
1383 [gui setTitle:OOExpandKey(@"arrival-report-title")];
1384
1385 for (i=1;i<=18;i++) {
1386 [gui setColor:[gui colorFromSetting:kGuiDockingReportColor defaultValue:nil] forRow:21];
1387 }
1388
1389 // dockingReport might be a multi-line message
1390
1391 while (([dockingReport length] > 0)&&(text_row < 18))
1392 {
1393 if ([dockingReport rangeOfString:@"\n"].location != NSNotFound)
1394 {
1395 while (([dockingReport rangeOfString:@"\n"].location != NSNotFound)&&(text_row < 18))
1396 {
1397 NSUInteger line_break = [dockingReport rangeOfString:@"\n"].location;
1398 NSString* line = [dockingReport substringToIndex:line_break];
1399 [dockingReport deleteCharactersInRange: NSMakeRange( 0, line_break + 1)];
1400 text_row = [gui addLongText:line startingAtRow:text_row align:GUI_ALIGN_LEFT];
1401 }
1402 [dockingReport setString:[dockingReport stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
1403 }
1404 else
1405 {
1406 text_row = [gui addLongText:[NSString stringWithString:dockingReport] startingAtRow:text_row align:GUI_ALIGN_LEFT];
1407 [dockingReport setString:@""];
1408 }
1409 }
1410
1411 [gui setText:[NSString stringWithFormat:DESC_PLURAL(@"contracts-cash-@-load-d-of-d-passengers-d-of-d-berths", max_passengers), OOCredits(credits), current_cargo, [self maxAvailableCargoSpace], [passengers count], max_passengers] forRow: GUI_ROW_MARKET_CASH];
1412 [gui setColor:[gui colorFromSetting:kGuiDockingSummaryColor defaultValue:nil] forRow:GUI_ROW_MARKET_CASH];
1413
1414 [gui setText:DESC(@"press-space-commander") forRow:21 align:GUI_ALIGN_CENTER];
1415 [gui setColor:[gui colorFromSetting:kGuiDockingContinueColor defaultValue:nil] forRow:21];
1416 [gui setShowTextCursor:NO];
1417 }
1418 /* ends */
1419
1420 if (lastTextKey)
1421 {
1422 [lastTextKey release];
1423 lastTextKey = nil;
1424 }
1425
1426 [self setShowDemoShips:NO];
1427 [UNIVERSE enterGUIViewModeWithMouseInteraction:NO];
1428
1429 if (guiChanged)
1430 {
1431 [gui setForegroundTextureKey:@"docked_overlay"]; // has to be docked!
1432
1433 NSDictionary *bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"report"];
1434 if (bgDescriptor == nil) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status_docked"];
1435 if (bgDescriptor == nil) bgDescriptor = [UNIVERSE screenTextureDescriptorForKey:@"status"];
1436 [gui setBackgroundTextureDescriptor:bgDescriptor];
1437 [self noteGUIDidChangeFrom:oldScreen to:gui_screen];
1438 }
1439}
1440
1441// ----------------------------------------------------------------------
1442
1443static NSMutableDictionary *currentShipyard = nil;
1444
1445
1446- (OOCreditsQuantity) priceForShipKey:(NSString *)key
1447{
1448 NSDictionary *shipInfo = [currentShipyard oo_dictionaryForKey:key];
1449 return [shipInfo oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE];
1450}
1451
1452
1453- (void) setGuiToShipyardScreen:(NSUInteger)skip
1454{
1455 OOGUIScreenID oldScreen = gui_screen;
1456
1457 GuiDisplayGen *gui = [UNIVERSE gui];
1458 gui_screen = GUI_SCREEN_SHIPYARD;
1459 BOOL guiChanged = (oldScreen != gui_screen);
1460
1461 unsigned i;
1462
1463 // set up initial market if there is none
1464 OOTechLevelID stationTechLevel;
1465 StationEntity *station = [self dockedStation];
1466
1467 if (station != nil)
1468 {
1469 stationTechLevel = [station equivalentTechLevel];
1470 }
1471 else
1472 {
1473 station = [UNIVERSE station];
1474 stationTechLevel = NSNotFound;
1475 }
1476 if ([station localShipyard] == nil)
1477 {
1478 [station generateShipyard:stationTechLevel];
1479 }
1480
1481 NSMutableArray *shipyard = [station localShipyard];
1482
1483 [currentShipyard release];
1484 currentShipyard = [[NSMutableDictionary alloc] initWithCapacity:[shipyard count]];
1485
1486 for (i = 0; i < [shipyard count]; i++)
1487 {
1488 [currentShipyard setObject:[shipyard objectAtIndex:i]
1489 forKey:[[shipyard oo_dictionaryAtIndex:i] oo_stringForKey:SHIPYARD_KEY_ID]];
1490 }
1491
1492 NSUInteger shipCount = [shipyard count];
1493
1494 //error check
1495 if (skip >= shipCount) skip = shipCount - 1;
1496 if (skip < 2) skip = 0;
1497
1498 // GUI stuff
1499 {
1500 [gui clearAndKeepBackground:!guiChanged];
1501 NSString *system = [UNIVERSE getSystemName:system_id];
1502 [gui setTitle:OOExpandKey(@"shipyard-title", system)];
1503
1504 OOGUITabSettings tab_stops;
1505 tab_stops[0] = 0;
1506 tab_stops[1] = -258;
1507 tab_stops[2] = 270;
1508 tab_stops[3] = 370;
1509 tab_stops[4] = 450;
1510 [gui overrideTabs:tab_stops from:kGuiShipyardTabs length:5];
1511 [gui setTabStops:tab_stops];
1512
1513 int rowCount = MAX_ROWS_SHIPS_FOR_SALE;
1514 int startRow = GUI_ROW_SHIPYARD_START;
1515 NSInteger previous = 0;
1516
1517 if (shipCount <= MAX_ROWS_SHIPS_FOR_SALE)
1518 skip = 0;
1519 else
1520 {
1521 if (skip > 0)
1522 {
1523 rowCount -= 1;
1524 startRow += 1;
1525 previous = skip - MAX_ROWS_SHIPS_FOR_SALE + 2;
1526 if (previous < 2)
1527 previous = 0;
1528 }
1529 if (skip + rowCount < shipCount)
1530 rowCount -= 1;
1531 }
1532
1533 if (shipCount > 0)
1534 {
1535 [gui setColor:[gui colorFromSetting:kGuiShipyardHeadingColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_SHIPYARD_LABELS];
1536 [gui setArray:[NSArray arrayWithObjects:DESC(@"shipyard-shiptype"), DESC(@"shipyard-price-label"),
1537 DESC(@"shipyard-cargo-label"), DESC(@"shipyard-speed-label"), nil] forRow:GUI_ROW_SHIPYARD_LABELS];
1538
1539 if (skip > 0)
1540 {
1541 [gui setColor:[gui colorFromSetting:kGuiShipyardScrollColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_SHIPYARD_START];
1542 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-back"), @" <-- ", nil] forRow:GUI_ROW_SHIPYARD_START];
1543 [gui setKey:[NSString stringWithFormat:@"More:%ld", previous] forRow:GUI_ROW_SHIPYARD_START];
1544 }
1545 for (i = 0; i < (shipCount - skip) && (int)i < rowCount; i++)
1546 {
1547 NSDictionary* ship_info = [shipyard oo_dictionaryAtIndex:i + skip];
1548 OOCreditsQuantity ship_price = [ship_info oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE];
1549 [gui setColor:[gui colorFromSetting:kGuiShipyardEntryColor defaultValue:nil] forRow:startRow + i];
1550 [gui setArray:[NSArray arrayWithObjects:
1551 [NSString stringWithFormat:@" %@ ",[[ship_info oo_dictionaryForKey:SHIPYARD_KEY_SHIP] oo_stringForKey:@"display_name" defaultValue:[[ship_info oo_dictionaryForKey:SHIPYARD_KEY_SHIP] oo_stringForKey:KEY_NAME]]],
1552 OOIntCredits(ship_price),
1553 nil]
1554 forRow:startRow + i];
1555 [gui setKey:(NSString*)[ship_info objectForKey:SHIPYARD_KEY_ID] forRow:startRow + i];
1556 }
1557 if (i < shipCount - skip)
1558 {
1559 [gui setColor:[gui colorFromSetting:kGuiShipyardScrollColor defaultValue:[OOColor greenColor]] forRow:startRow + i];
1560 [gui setArray:[NSArray arrayWithObjects:DESC(@"gui-more"), @" --> ", nil] forRow:startRow + i];
1561 [gui setKey:[NSString stringWithFormat:@"More:%ld", rowCount + skip] forRow:startRow + i];
1562 i++;
1563 }
1564
1565 [gui setSelectableRange:NSMakeRange( GUI_ROW_SHIPYARD_START, i + startRow - GUI_ROW_SHIPYARD_START)];
1566 // ensure that at least one row is selected at all times
1567 if(shipCount == 1) [gui setFirstSelectableRow];
1568 [self showShipyardInfoForSelection];
1569 }
1570 else
1571 {
1572 [gui setText:DESC(@"shipyard-no-ships-available-for-purchase") forRow:GUI_ROW_NO_SHIPS align:GUI_ALIGN_CENTER];
1573 [gui setColor:[gui colorFromSetting:kGuiShipyardNoshipColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_NO_SHIPS];
1574
1575 [gui setNoSelectedRow];
1576 }
1577
1578 [self showTradeInInformationFooter];
1579
1580 [gui setShowTextCursor:NO];
1581 }
1582
1583 // the following are necessary...
1584
1585 [self setShowDemoShips:(shipCount > 0)];
1586 [UNIVERSE enterGUIViewModeWithMouseInteraction:YES];
1587
1588 if (guiChanged)
1589 {
1590 [gui setForegroundTextureKey:@"docked_overlay"];
1591 [gui setBackgroundTextureKey:@"shipyard"];
1592 }
1593}
1594
1595
1596- (void) showShipyardInfoForSelection
1597{
1598 NSUInteger i;
1599 GuiDisplayGen *gui = [UNIVERSE gui];
1600 OOGUIRow sel_row = [gui selectedRow];
1601
1602 if (sel_row <= 0) return;
1603
1604 NSMutableArray *row_info = [NSMutableArray arrayWithArray:(NSArray*)[gui objectForRow:GUI_ROW_SHIPYARD_LABELS]];
1605 while ([row_info count] < 4)
1606 {
1607 [row_info addObject:@""];
1608 }
1609
1610 NSString *key = [gui keyForRow:sel_row];
1611
1612 NSDictionary *info = [currentShipyard oo_dictionaryForKey:key];
1613
1614 // clean up the display ready for the newly-selected ship (if there is one)
1615 [row_info replaceObjectAtIndex:2 withObject:@""];
1616 [row_info replaceObjectAtIndex:3 withObject:@""];
1617 for (i = GUI_ROW_SHIPYARD_INFO_START; i < GUI_ROW_MARKET_CASH - 1; i++)
1618 {
1619 [gui setText:@"" forRow:i];
1620 [gui setColor:[gui colorFromSetting:kGuiShipyardDescriptionColor defaultValue:[OOColor greenColor]] forRow:i];
1621 }
1622 [UNIVERSE removeDemoShips];
1623
1624 if (info)
1625 {
1626 // the key is a particular ship - show the details
1627 NSString *salesPitch = [info oo_stringForKey:KEY_SHORT_DESCRIPTION];
1628 NSDictionary *shipDict = [info oo_dictionaryForKey:SHIPYARD_KEY_SHIP];
1629
1630 int cargoRating = [shipDict oo_intForKey:@"max_cargo"];
1631 int cargo_extra;
1632 cargo_extra = [shipDict oo_intForKey:@"extra_cargo" defaultValue:15];
1633 float speedRating = 0.001 * [shipDict oo_intForKey:@"max_flight_speed"];
1634
1635 NSArray *shipExtras = [info oo_arrayForKey:KEY_EQUIPMENT_EXTRAS];
1636 for (i = 0; i < [shipExtras count]; i++)
1637 {
1638 if ([[shipExtras oo_stringAtIndex:i] isEqualToString:@"EQ_CARGO_BAY"])
1639 {
1640 cargoRating += cargo_extra;
1641 }
1642 else if ([[shipExtras oo_stringAtIndex:i] isEqualToString:@"EQ_PASSENGER_BERTH"])
1643 {
1644 cargoRating -= PASSENGER_BERTH_SPACE;
1645 }
1646 }
1647
1648 [row_info replaceObjectAtIndex:2 withObject:OOExpandKey(@"shipyard-cargo-value", cargoRating)];
1649 [row_info replaceObjectAtIndex:3 withObject:OOExpandKey(@"shipyard-speed-value", speedRating)];
1650
1651 // Show footer first. It'll be overwritten by the sales_pitch if that text is longer than usual.
1652 [self showTradeInInformationFooter];
1653 i = [gui addLongText:salesPitch startingAtRow:GUI_ROW_SHIPYARD_INFO_START align:GUI_ALIGN_LEFT];
1654 if (i - 1 >= GUI_ROW_MARKET_CASH - 1)
1655 {
1656 [gui setColor:[gui colorFromSetting:kGuiShipyardDescriptionColor defaultValue:[OOColor greenColor]] forRow:i - 1];
1657 [gui setColor:[gui colorFromSetting:kGuiShipyardDescriptionColor defaultValue:[OOColor greenColor]] forRow:GUI_ROW_MARKET_CASH - 1];
1658 }
1659
1660 // now display the ship
1661 [self showShipyardModel:[info oo_stringForKey:SHIPYARD_KEY_SHIPDATA_KEY]
1662 shipData:shipDict
1663 personality:[info oo_unsignedShortForKey:SHIPYARD_KEY_PERSONALITY]];
1664 }
1665 else
1666 {
1667 // the key is a particular model of ship which we must expand...
1668 // build an array from the entries for that model in the currentShipyard TODO
1669 //
1670 }
1671
1672 [gui setArray:[NSArray arrayWithArray:row_info] forRow:GUI_ROW_SHIPYARD_LABELS];
1673}
1674
1675
1676- (void) showTradeInInformationFooter
1677{
1678 GuiDisplayGen *gui = [UNIVERSE gui];
1679 OOCreditsQuantity tradeIn = [self tradeInValue];
1680 OOCreditsQuantity total = tradeIn + credits;
1681 NSString *shipType = [self displayName];
1682
1683 [gui setColor:[gui colorFromSetting:kGuiShipyardTradeinColor defaultValue:nil] forRow:GUI_ROW_MARKET_CASH - 1];
1684 [gui setColor:[gui colorFromSetting:kGuiShipyardTradeinColor defaultValue:nil] forRow:GUI_ROW_MARKET_CASH];
1685 [gui setText:OOExpandKey(@"shipyard-trade-in-value", shipType, tradeIn) forRow: GUI_ROW_MARKET_CASH - 1];
1686 [gui setText:OOExpandKey(@"shipyard-total-available-with-trade-in", shipType, total, credits, tradeIn) forRow: GUI_ROW_MARKET_CASH];
1687}
1688
1689
1690- (void) showShipyardModel:(NSString *)shipKey shipData:(NSDictionary *)shipData personality:(uint16_t)personality
1691{
1692 if (shipKey == nil || [self dockedStation] == nil) return;
1693 [self showShipModelWithKey:shipKey shipData:shipData personality:personality factorX:1.2 factorY:0.8 factorZ:6.4 inContext:@"shipyard"];
1694}
1695
1696
1697- (NSInteger) missingSubEntitiesAdjustment
1698{
1699 // each missing subentity depreciates the ship by 5%, up to a maximum of 35% depreciation.
1700 NSUInteger percent = 5 * ([self maxShipSubEntities] - [[[self shipSubEntityEnumerator] allObjects] count]);
1701 return (percent > 35 ? 35 : percent);
1702}
1703
1704
1705- (OOCreditsQuantity) tradeInValue
1706{
1707 // returns down to ship_trade_in_factor% of the full credit value of your ship
1708
1709 /* FIXME: the trade-in value can be more than the sale value, and
1710 ship_trade_in_factor starts at 100%, so it can be profitable to sit
1711 and buy the same ship over and over again. This bug predates Oolite
1712 1.65.
1713 Partial fix: make effective trade-in value 75% * ship_trade_in_factor%
1714 of the "raw" trade-in value. This still allows profitability! A better
1715 solution would be to unify the price calculation for trade-in and
1716 for-sale ships.
1717 -- Ahruman 20070707, fix applied 20070708
1718 */
1719 unsigned long long value = [UNIVERSE tradeInValueForCommanderDictionary:[self commanderDataDictionary]];
1720 value -= value * 0.006 * [self missingSubEntitiesAdjustment]; // TODO: 0.006 might need rethinking.
1721 value = cunningFee(((value * 75 * ship_trade_in_factor) + 5000) / 10000, 0.005); // Multiply by two percentages, divide by 100*100. The +5000 is to get normal rounding.
1722 return value * 10;
1723}
1724
1725
1726- (BOOL) buySelectedShip
1727{
1728 GuiDisplayGen *gui = [UNIVERSE gui];
1729 OOGUIRow selectedRow = [gui selectedRow];
1730
1731 if (selectedRow <= 0) return NO;
1732
1733 NSString *key = [gui keyForRow:selectedRow];
1734
1735 if ([key hasPrefix:@"More:"])
1736 {
1737 NSInteger fromShip = [[key componentsSeparatedByString:@":"] oo_integerAtIndex:1];
1738 if (fromShip < 0) fromShip = 0;
1739
1740 [self setGuiToShipyardScreen:fromShip];
1741 if ([[UNIVERSE gui] selectedRow] < 0)
1742 {
1743 [[UNIVERSE gui] setSelectedRow:GUI_ROW_SHIPYARD_START];
1744 }
1745 if (fromShip == 0)
1746 {
1747 [[UNIVERSE gui] setSelectedRow:GUI_ROW_SHIPYARD_START + MAX_ROWS_SHIPS_FOR_SALE - 1];
1748 }
1749 // next bit or the first ship on the list gets wrongly previewed
1750 // clean up the display
1751 NSMutableArray *row_info = [NSMutableArray arrayWithArray:(NSArray*)[gui objectForRow:GUI_ROW_SHIPYARD_LABELS]];
1752 while ([row_info count] < 4)
1753 {
1754 [row_info addObject:@""];
1755 }
1756 [row_info replaceObjectAtIndex:2 withObject:@""];
1757 [row_info replaceObjectAtIndex:3 withObject:@""];
1758 NSUInteger i;
1759 for (i = GUI_ROW_SHIPYARD_INFO_START; i < GUI_ROW_MARKET_CASH - 1; i++)
1760 {
1761 [gui setText:@"" forRow:i];
1762 [gui setColor:[gui colorFromSetting:kGuiShipyardDescriptionColor defaultValue:[OOColor greenColor]] forRow:i];
1763 }
1764 [gui setArray:[NSArray arrayWithArray:row_info] forRow:GUI_ROW_SHIPYARD_LABELS];
1765 [UNIVERSE removeDemoShips];
1766 return YES;
1767 }
1768
1769 // first check you can afford it!
1770 NSDictionary *shipInfo = [currentShipyard oo_dictionaryForKey:key];
1771 OOCreditsQuantity price = [shipInfo oo_unsignedLongLongForKey:SHIPYARD_KEY_PRICE];
1772 OOCreditsQuantity tradeIn = [self tradeInValue];
1773
1774 if (credits + tradeIn < price * 10)
1775 return NO; // you can't afford it!
1776
1777 // from this point, the player is committed to buying - raise a pre-buy script event
1778 [self doScriptEvent:OOJSID("playerWillBuyNewShip")
1779 withArguments:[NSArray arrayWithObjects:[shipInfo oo_stringForKey:SHIPYARD_KEY_SHIPDATA_KEY],
1780 [[[self dockedStation] localShipyard] objectAtIndex:selectedRow - GUI_ROW_SHIPYARD_START],
1781 [NSNumber numberWithUnsignedLongLong:price],
1782 [NSNumber numberWithUnsignedLongLong:(tradeIn / 10)], nil]];
1783
1784 // sell all the commodities carried
1785 NSString *good = nil;
1786 foreach (good, [shipCommodityData goods])
1787 {
1788 [self trySellingCommodity:good all:YES];
1789 }
1790 // We tried to sell everything. If there are still items present in our inventory, it
1791 // means that the market got saturated (quantity in station > 127 t) before we could sell
1792 // it all. Everything that could not be sold will be lost. -- Nikos 20083012
1793
1794 // pay over the mazoolah
1795 credits -= 10 * price - tradeIn;
1796
1797 NSDictionary *shipDict = [shipInfo oo_dictionaryForKey:SHIPYARD_KEY_SHIP];
1798 [self newShipCommonSetup:[shipInfo oo_stringForKey:SHIPYARD_KEY_SHIPDATA_KEY] yardInfo:shipInfo baseInfo:shipDict];
1799
1800 // this ship has a clean record
1801 legalStatus = 0;
1802
1803 NSArray *extras = [shipInfo oo_arrayForKey:KEY_EQUIPMENT_EXTRAS];
1804 for (NSUInteger i = 0; i < [extras count]; i++)
1805 {
1806 NSString *eq_key = [extras oo_stringAtIndex:i];
1807 if ([eq_key isEqualToString:@"EQ_PASSENGER_BERTH"])
1808 {
1809 max_passengers++;
1810 max_cargo -= PASSENGER_BERTH_SPACE;
1811 }
1812 else
1813 {
1814 [self addEquipmentItem:eq_key withValidation:YES inContext:@"newShip"];
1815 }
1816 }
1817
1818 // add bought ship to shipyard_record
1819 [shipyard_record setObject:[self shipDataKey] forKey:[shipInfo objectForKey:SHIPYARD_KEY_ID]];
1820
1821 // remove the ship from the localShipyard
1822 [[[self dockedStation] localShipyard] removeObjectAtIndex:selectedRow - GUI_ROW_SHIPYARD_START];
1823
1824 // perform the transformation
1825 NSDictionary* cmdr_dict = [self commanderDataDictionary]; // gather up all the info
1826 if (![self setCommanderDataFromDictionary:cmdr_dict]) return NO;
1827
1828 [self setStatus:STATUS_DOCKED];
1829 [self setEntityPersonalityInt:[shipInfo oo_unsignedShortForKey:SHIPYARD_KEY_PERSONALITY]];
1830
1831 // adjust the clock forward by an hour
1832 ship_clock_adjust += 3600.0;
1833
1834 // finally we can get full hock if we sell it back
1835 ship_trade_in_factor = 100;
1836
1837 if ([UNIVERSE autoSave]) [UNIVERSE setAutoSaveNow:YES];
1838
1839 return YES;
1840}
1841
1842- (BOOL) replaceShipWithNamedShip:(NSString *)shipKey
1843{
1844
1845 NSDictionary *ship_info = [[OOShipRegistry sharedRegistry] shipyardInfoForKey:shipKey];
1846
1847 NSDictionary *ship_base_dict = [[OOShipRegistry sharedRegistry] shipInfoForKey:shipKey];
1848
1849 if (ship_info == nil || ship_base_dict == nil) {
1850 return NO;
1851 }
1852
1853 // from this point, the player is committed to replacing - raise a pre-replace script event
1854 [self doScriptEvent:OOJSID("playerWillReplaceShip") withArgument:shipKey];
1855
1856 [self newShipCommonSetup:shipKey yardInfo:ship_info baseInfo:ship_base_dict];
1857
1858 // perform the transformation
1859 NSDictionary* cmdr_dict = [self commanderDataDictionary]; // gather up all the info
1860 if (![self setCommanderDataFromDictionary:cmdr_dict]) return NO;
1861
1862 // refill from ship_info
1863 NSArray* extras = [NSMutableArray arrayWithArray:[[ship_info oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]];
1864 for (unsigned i = 0; i < [extras count]; i++)
1865 {
1866 NSString* eq_key = [extras oo_stringAtIndex:i];
1867 if ([eq_key isEqualToString:@"EQ_PASSENGER_BERTH"])
1868 {
1869 max_passengers++;
1870 max_cargo -= PASSENGER_BERTH_SPACE;
1871 }
1872 else
1873 {
1874 [self addEquipmentItem:eq_key withValidation:YES inContext:@"newShip"];
1875 }
1876 }
1877
1878 [self setEntityPersonalityInt:[ship_info oo_unsignedShortForKey:SHIPYARD_KEY_PERSONALITY]];
1879
1880 return YES;
1881}
1882
1883- (void) newShipCommonSetup:(NSString *)shipKey yardInfo:(NSDictionary *)ship_info baseInfo:(NSDictionary *)ship_base_dict
1884{
1885 // Zero out our manifest.
1886 [shipCommodityData removeAllGoods];
1887 current_cargo = 0;
1888
1889 // drop all passengers
1890 [passengers removeAllObjects];
1891 [passenger_record removeAllObjects];
1892
1893 // parcels stay the same; easy to transfer between ships
1894 // contracts stay the same, so if you default - tough!
1895 // okay we need to switch the model used, lots of the stats, and add all the extras
1896
1897 [self clearSubEntities];
1898
1899 [self setShipDataKey:shipKey];
1900
1901 NSDictionary *shipDict = ship_base_dict;
1902
1903
1904 // get a full tank for free
1905 [self setFuel:[self fuelCapacity]];
1906
1907 // get forward_weapon aft_weapon port_weapon starboard_weapon from ship_info
1908 int base_facings = [shipDict oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:15];
1909 int available_facings = [ship_info oo_unsignedIntForKey:KEY_WEAPON_FACINGS defaultValue:base_facings];
1910
1911 // not retained - weapon types are references to the objects in OOEquipmentType's cache
1912 if (available_facings & WEAPON_FACING_AFT)
1913 aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"aft_weapon_type"]);
1914 else
1915 aft_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1916
1917 if (available_facings & WEAPON_FACING_PORT)
1918 port_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"port_weapon_type"]);
1919 else
1920 port_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1921
1922 if (available_facings & WEAPON_FACING_STARBOARD)
1923 starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"starboard_weapon_type"]);
1924 else
1925 starboard_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1926
1927 if (available_facings & WEAPON_FACING_FORWARD)
1928 forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy([shipDict oo_stringForKey:@"forward_weapon_type"]);
1929 else
1930 forward_weapon_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_NONE");
1931
1932 // new ships start with weapons online
1933 weapons_online = 1;
1934
1935 // get basic max_cargo
1936 max_cargo = [UNIVERSE maxCargoForShip:[self shipDataKey]];
1937
1938 // ensure all missiles are tidied up and start at pylon 0
1939 [self tidyMissilePylons];
1940
1941 // get missiles from ship_info
1942 missiles = [shipDict oo_unsignedIntForKey:@"missiles"];
1943
1944 // reset max_passengers
1945 max_passengers = 0;
1946
1947 // reset and refill extra_equipment then set flags from it
1948
1949 // keep track of portable equipment..
1950
1951 NSMutableSet *portable_equipment = [NSMutableSet set];
1952 NSEnumerator *eqEnum = nil;
1953 NSString *eq_desc = nil;
1954 OOEquipmentType *item = nil;
1955
1956 for (eqEnum = [self equipmentEnumerator]; (eq_desc = [eqEnum nextObject]);)
1957 {
1959 if ([item isPortableBetweenShips]) [portable_equipment addObject:eq_desc];
1960 }
1961
1962 // remove ALL
1963 [self removeAllEquipment];
1964
1965 // restore portable equipment
1966 for (eqEnum = [portable_equipment objectEnumerator]; (eq_desc = [eqEnum nextObject]); )
1967 {
1968 [self addEquipmentItem:eq_desc withValidation:NO inContext:@"portable"];
1969 }
1970
1971
1972 // set up subentities from scratch; new ship could carry more or fewer than the old one
1973 [self setUpSubEntities];
1974
1975 // clear old ship names
1976 [self setShipClassName:[shipDict oo_stringForKey:@"name"]];
1977 [self setShipUniqueName:@""];
1978
1979 // new ship, so lose some memory of actions
1980 // new ship, so lose some memory of player actions
1981 if (ship_kills >= 6400)
1982 {
1983 [self clearRolesFromPlayer:0.1];
1984 }
1985 else if (ship_kills >= 2560)
1986 {
1987 [self clearRolesFromPlayer:0.25];
1988 }
1989 else
1990 {
1991 [self clearRolesFromPlayer:0.5];
1992 }
1993
1994}
1995
1996@end
1997
1998static unsigned RepForRisk(unsigned risk)
1999{
2000 switch (risk)
2001 {
2002 case 0:
2003 return 1;
2004 case 1:
2005 return 2;
2006 case 2:
2007 default:
2008 return 4;
2009 }
2010}
OOGUITabStop OOGUITabSettings[GUI_MAX_COLUMNS]
NSInteger OOGUIRow
return self
unsigned count
return nil
#define OOExpandKey(key,...)
OOINLINE NSString * OOIntCredits(OOCreditsQuantity integerCredits)
NSString * OOCommodityType
Definition OOTypes.h:106
uint64_t OOCreditsQuantity
Definition OOTypes.h:182
NSUInteger OOTechLevelID
Definition OOTypes.h:204
uint32_t OOCargoQuantity
Definition OOTypes.h:176
OOMassUnit
Definition OOTypes.h:123
@ UNITS_GRAMS
Definition OOTypes.h:126
@ UNITS_KILOGRAMS
Definition OOTypes.h:125
uint8_t OOGovernmentID
Definition OOTypes.h:206
@ WEAPON_FACING_FORWARD
Definition OOTypes.h:229
@ WEAPON_FACING_AFT
Definition OOTypes.h:230
@ WEAPON_FACING_PORT
Definition OOTypes.h:231
@ WEAPON_FACING_STARBOARD
Definition OOTypes.h:232
#define MAX_CONTRACT_REP
#define MAX_ROWS_SHIPS_FOR_SALE
#define GUI_ROW_SHIPYARD_START
#define CONTRACT_KEY_RISK
#define PASSENGER_KEY_NAME
#define GUI_ROW_SHIPYARD_INFO_START
#define SET_MANIFEST_ROW(obj, color, row)
static unsigned RepForRisk(unsigned risk)
static NSMutableDictionary * currentShipyard
@ GUI_ROW_MARKET_CASH
#define MANIFEST_SCREEN_ROW_BACK
OOGUIScreenID
#define MANIFEST_SCREEN_ROW_NEXT
OOWeaponType OOWeaponTypeFromEquipmentIdentifierSloppy(NSString *string) PURE_FUNC
#define UNIVERSE
Definition Universe.h:833
#define PASSENGER_BERTH_SPACE
Definition Universe.h:151
#define DESC(key)
Definition Universe.h:839
BOOL setBackgroundTextureKey:(NSString *key)
OOColor * colorFromSetting:defaultValue:(NSString *setting,[defaultValue] OOColor *def)
BOOL setSelectedRow:(OOGUIRow row)
OOGUIRow addLongText:startingAtRow:align:(NSString *str,[startingAtRow] OOGUIRow row,[align] OOGUIAlignment alignment)
BOOL setForegroundTextureKey:(NSString *key)
void setStatusPage:(NSInteger pageNum)
void setText:forRow:(NSString *str,[forRow] OOGUIRow row)
void setText:forRow:align:(NSString *str,[forRow] OOGUIRow row,[align] OOGUIAlignment alignment)
void clearAndKeepBackground:(BOOL keepBackground)
OOGUIRow selectedRow
void overrideTabs:from:length:(OOGUITabSettings stops,[from] NSString *setting,[length] NSUInteger len)
void setSelectableRange:(NSRange range)
void setColor:forRow:(OOColor *color,[forRow] OOGUIRow row)
id objectForRow:(OOGUIRow row)
void setTitle:(NSString *str)
void setTabStops:(OOGUITabSettings stops)
NSString * keyForRow:(OOGUIRow row)
NSUInteger statusPage
void setShowTextCursor:(BOOL yesno)
BOOL setBackgroundTextureDescriptor:(NSDictionary *descriptor)
void setArray:forRow:(NSArray *arr,[forRow] OOGUIRow row)
void setKey:forRow:(NSString *str,[forRow] OOGUIRow row)
OOCreditsQuantity insuranceCredits()
NSString * name()
void doScriptEvent:(jsid message)
NSDictionary * infoForScripting()
NSString * shortDescription()
NSArray * legacyScript()
OOColor * darkGrayColor()
Definition OOColor.m:244
OOColor * greenColor()
Definition OOColor.m:274
OOEquipmentType * equipmentTypeWithIdentifier:(NSString *identifier)
NSDictionary * shipyardInfoForKey:(NSString *key)
OOShipRegistry * sharedRegistry()
NSDictionary * shipInfoForKey:(NSString *key)
void calculateCurrentCargo()
void runUnsanitizedScriptActions:allowingAIMethods:withContextName:forTarget:(NSArray *unsanitizedActions,[allowingAIMethods] BOOL allowAIMethods,[withContextName] NSString *contextName,[forTarget] ShipEntity *target)
void doScriptEvent:withArguments:(jsid message,[withArguments] NSArray *arguments)
void setCrew:(NSArray *crewArray)
NSArray * crew
Definition ShipEntity.h:399
NSMutableArray * localShipyard
void generateShipyard:(OOTechLevelID stationTechLevel)
OOTechLevelID equivalentTechLevel
voidpf uLong offset
Definition ioapi.h:140
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
float randf(void)
double cunningFee(double value, double precision)
unsigned Ranrot(void)