Line data Source code
1 0 : /*
2 :
3 : PlayerEntityContracts.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 : #import "PlayerEntity.h"
26 : #import "PlayerEntityLegacyScriptEngine.h"
27 : #import "PlayerEntityContracts.h"
28 : #import "PlayerEntityControls.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"
41 : #import "OOCollectionExtractors.h"
42 : #import "OOConstToString.h"
43 : #import "MyOpenGLView.h"
44 : #import "NSStringOOExtensions.h"
45 : #import "OOShipRegistry.h"
46 : #import "OOEquipmentType.h"
47 : #import "OOTexture.h"
48 : #import "OOJavaScriptEngine.h"
49 :
50 :
51 : static unsigned RepForRisk(unsigned risk);
52 :
53 : @interface PlayerEntity (ContractsPrivate)
54 :
55 0 : - (OOCreditsQuantity) tradeInValue;
56 0 : - (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 : {
100 : [self runUnsanitizedScriptActions:[rescuee legacyScript]
101 : allowingAIMethods:YES
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 :
161 : [self calculateCurrentCargo];
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 :
582 : for (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 0 : - (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 0 : #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 : {
1297 : OOGUIRow r_start = MANIFEST_SCREEN_ROW_BACK;
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];
1319 : r_end = MANIFEST_SCREEN_ROW_BACK;
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 :
1443 0 : static 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 0 : - (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 : {
1958 : item = [OOEquipmentType equipmentTypeWithIdentifier:eq_desc];
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 :
1998 0 : static 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 : }
|