LCOV - code coverage report
Current view: top level - Core/Entities - PlayerEntityContracts.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 8 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.14