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

          Line data    Source code
       1           0 : /*
       2             : 
       3             : ShipEntity.m
       4             : 
       5             : 
       6             : Oolite
       7             : Copyright (C) 2004-2013 Giles C Williams and contributors
       8             : 
       9             : This program is free software; you can redistribute it and/or
      10             : modify it under the terms of the GNU General Public License
      11             : as published by the Free Software Foundation; either version 2
      12             : of the License, or (at your option) any later version.
      13             : 
      14             : This program is distributed in the hope that it will be useful,
      15             : but WITHOUT ANY WARRANTY; without even the impllied warranty of
      16             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             : GNU General Public License for more details.
      18             : 
      19             : You should have received a copy of the GNU General Public License
      20             : along with this program; if not, write to the Free Software
      21             : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
      22             : MA 02110-1301, USA.
      23             : 
      24             : */
      25             : 
      26             : #import "ShipEntity.h"
      27             : #import "ShipEntityAI.h"
      28             : #import "ShipEntityScriptMethods.h"
      29             : 
      30             : #import "OOMaths.h"
      31             : #import "Universe.h"
      32             : #import "OOShaderMaterial.h"
      33             : #import "OOOpenGLExtensionManager.h"
      34             : 
      35             : #import "ResourceManager.h"
      36             : #import "OOStringExpander.h"
      37             : #import "OOStringParsing.h"
      38             : #import "OOCollectionExtractors.h"
      39             : #import "OOConstToString.h"
      40             : #import "OOConstToJSString.h"
      41             : #import "NSScannerOOExtensions.h"
      42             : #import "OOFilteringEnumerator.h"
      43             : #import "OORoleSet.h"
      44             : #import "OOShipGroup.h"
      45             : #import "OOExcludeObjectEnumerator.h"
      46             : #import "OOWeakSet.h"
      47             : #import "GameController.h"
      48             : #import "MyOpenGLView.h"
      49             : #import "OOSystemDescriptionManager.h"
      50             : #import "OOCharacter.h"
      51             : #import "AI.h"
      52             : 
      53             : #import "OOMesh.h"
      54             : #import "OOPlanetDrawable.h"
      55             : 
      56             : #import "Octree.h"
      57             : #import "OOColor.h"
      58             : #import "OOPolygonSprite.h"
      59             : 
      60             : #import "OOParticleSystem.h"
      61             : #import "StationEntity.h"
      62             : #import "DockEntity.h"
      63             : #import "OOSunEntity.h"
      64             : #import "OOPlanetEntity.h"
      65             : #import "PlanetEntity.h"
      66             : #import "PlayerEntity.h"
      67             : #import "WormholeEntity.h"
      68             : #import "OOFlasherEntity.h"
      69             : #import "OOExhaustPlumeEntity.h"
      70             : #import "OOSparkEntity.h"
      71             : #import "OOECMBlastEntity.h"
      72             : #import "OOPlasmaShotEntity.h"
      73             : #import "OOFlashEffectEntity.h"
      74             : #import "OOExplosionCloudEntity.h"
      75             : #import "ProxyPlayerEntity.h"
      76             : #import "OOLaserShotEntity.h"
      77             : #import "OOQuiriumCascadeEntity.h"
      78             : #import "OORingEffectEntity.h"
      79             : 
      80             : #import "PlayerEntityLegacyScriptEngine.h"
      81             : #import "PlayerEntitySound.h"
      82             : #import "GuiDisplayGen.h"
      83             : #import "HeadUpDisplay.h"
      84             : #import "OOEntityFilterPredicate.h"
      85             : #import "OOShipRegistry.h"
      86             : #import "OOEquipmentType.h"
      87             : 
      88             : #import "OODebugGLDrawing.h"
      89             : #import "OODebugFlags.h"
      90             : #import "OODebugStandards.h"
      91             : 
      92             : #import "OOJSScript.h"
      93             : #import "OOJSVector.h"
      94             : #import "OOJSEngineTimeManagement.h"
      95             : 
      96           0 : #define USEMASC 1
      97             : 
      98             : 
      99           0 : static NSString * const kOOLogSyntaxAddShips                    = @"script.debug.syntax.addShips";
     100             : #ifndef NDEBUG
     101           0 : static NSString * const kOOLogEntityBehaviourChanged    = @"entity.behaviour.changed";
     102             : #endif
     103             : 
     104             : #if MASS_DEPENDENT_FUEL_PRICES
     105             : static GLfloat calcFuelChargeRate (GLfloat myMass)
     106             : {
     107             : #define kMassCharge 0.65                                // the closer to 1 this number is, the more the fuel price changes from ship to ship.
     108             : #define kBaseCharge (1.0 - kMassCharge) // proportion of price that doesn't change with ship's mass.
     109             : 
     110             :         GLfloat baseMass = [PLAYER baseMass];
     111             :         // if anything is wrong, use 1 (the default  charge rate).
     112             :         if (myMass <= 0.0 || baseMass <=0.0) return 1.0;
     113             :         
     114             :         GLfloat result = (kMassCharge * myMass / baseMass) + kBaseCharge;
     115             :         
     116             :         // round the result to the second decimal digit.
     117             :         result = roundf(result * 100.0f) / 100.0f;
     118             :         
     119             :         // Make sure that the rate is clamped to between three times and a third of the standard charge rate.
     120             :         if (result > 3.0f) result = 3.0f;
     121             :         else if (result < 0.33f) result = 0.33f;
     122             :         
     123             :         return result;
     124             :         
     125             : #undef kMassCharge
     126             : #undef kBaseCharge
     127             : }
     128             : #endif
     129             : 
     130             : 
     131             : @interface ShipEntity (Private)
     132             : 
     133           0 : - (void)subEntityDied:(ShipEntity *)sub;
     134           0 : - (void)subEntityReallyDied:(ShipEntity *)sub;
     135             : 
     136             : #ifndef NDEBUG
     137           0 : - (void) drawDebugStuff;
     138             : #endif
     139             : 
     140           0 : - (void) rescaleBy:(GLfloat)factor;
     141           0 : - (void) rescaleBy:(GLfloat)factor writeToCache:(BOOL)writeToCache;
     142             : 
     143           0 : - (BOOL) setUpOneSubentity:(NSDictionary *) subentDict;
     144           0 : - (BOOL) setUpOneFlasher:(NSDictionary *) subentDict;
     145             : 
     146           0 : - (Entity<OOStellarBody> *) lastAegisLock;
     147             : 
     148           0 : - (void) addSubEntity:(Entity<OOSubEntity> *) subent;
     149             : 
     150           0 : - (void) refreshEscortPositions;
     151           0 : - (HPVector) coordinatesForEscortPosition:(unsigned)idx;
     152           0 : - (void) setUpMixedEscorts;
     153           0 : - (void) setUpOneEscort:(ShipEntity *)escorter inGroup:(OOShipGroup *)escortGroup withRole:(NSString *)escortRole atPosition:(HPVector)ex_pos andCount:(uint8_t)currentEscortCount;
     154             : 
     155           0 : - (void) addSubentityToCollisionRadius:(Entity<OOSubEntity> *) subent;
     156           0 : - (ShipEntity *) launchPodWithCrew:(NSArray *)podCrew;
     157             : 
     158             : // equipment
     159           0 : - (OOEquipmentType *) generateMissileEquipmentTypeFrom:(NSString *)role;
     160             : 
     161           0 : - (void) setShipHitByLaser:(ShipEntity *)ship;
     162             : 
     163           0 : - (void) noteFrustration:(NSString *)context;
     164             : 
     165           0 : - (BOOL) cloakPassive;
     166             : 
     167             : @end
     168             : 
     169             : 
     170           0 : static ShipEntity *doOctreesCollide(ShipEntity *prime, ShipEntity *other);
     171             : 
     172             : 
     173             : @implementation ShipEntity
     174             : 
     175           0 : - (id) init
     176             : {
     177             :         /*      -init used to set up a bunch of defaults that were different from
     178             :                 those in -reinit and -setUpShipFromDictionary:. However, it seems that
     179             :                 no ships are ever used which are not -setUpShipFromDictionary: (which
     180             :                 is as it should be), so these different defaults were meaningless.
     181             :         */
     182             :         return [self initWithKey:@"" definition:nil];
     183             : }
     184             : 
     185             : 
     186           0 : - (id) initBypassForPlayer
     187             : {
     188             :         return [super init];
     189             : }
     190             : 
     191             : 
     192             : // Designated initializer
     193             : - (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict
     194             : {
     195             :         OOJS_PROFILE_ENTER
     196             :         
     197             :         NSParameterAssert(dict != nil);
     198             :         
     199             :         self = [super init];
     200             :         if (self == nil)  return nil;
     201             :         
     202             :         _shipKey = [key retain];
     203             :         
     204             :         isShip = YES;
     205             :         entity_personality = Ranrot() & ENTITY_PERSONALITY_MAX;
     206             :         [self setStatus:STATUS_IN_FLIGHT];
     207             :         
     208             :         zero_distance = SCANNER_MAX_RANGE2 * 2.0;
     209             :         weapon_recharge_rate = 6.0;
     210             :         shot_time = INITIAL_SHOT_TIME;
     211             :         ship_temperature = SHIP_MIN_CABIN_TEMP;
     212             :         weapon_temp                             = 0.0f;
     213             :         currentWeaponFacing             = WEAPON_FACING_FORWARD;
     214             :         forward_weapon_temp             = 0.0f;
     215             :         aft_weapon_temp                 = 0.0f;
     216             :         port_weapon_temp                = 0.0f;
     217             :         starboard_weapon_temp   = 0.0f;
     218             : 
     219             :         _nextAegisCheck = -0.1f;
     220             :         aiScriptWakeTime = 0;
     221             :         
     222             :         if (![self setUpShipFromDictionary:dict])
     223             :         {
     224             :                 [self release];
     225             :                 self = nil;
     226             :         }
     227             :         
     228             :         // Problem observed in testing -- Ahruman
     229             :         if (self != nil && !isfinite(maxFlightSpeed))
     230             :         {
     231             :                 OOLog(@"ship.sanityCheck.failed", @"Ship %@ %@ infinite top speed, clamped to 300.", self, @"generated with");
     232             :                 maxFlightSpeed = 300;
     233             :         }
     234             :         return self;
     235             :         
     236             :         OOJS_PROFILE_EXIT
     237             : }
     238             : 
     239             : 
     240             : - (BOOL) setUpFromDictionary:(NSDictionary *) shipDict
     241             : {
     242             :         OOJS_PROFILE_ENTER
     243             :         
     244             :         // Settings shared by players & NPCs.
     245             :         //
     246             :         // In order for default values to work and float values to not be junk,
     247             :         // replace nil with empty dictionary. -- Ahruman 2008-04-28
     248             :         shipinfoDictionary = [shipDict copy];
     249             :         if (shipinfoDictionary == nil)  shipinfoDictionary = [[NSDictionary alloc] init];
     250             :         shipDict = shipinfoDictionary;  // Ensure no mutation.
     251             :         
     252             :         // set these flags explicitly.
     253             :         haveExecutedSpawnAction = NO;
     254             :         haveStartedJSAI = NO;
     255             :         scripted_misjump                = NO;
     256             :         _scriptedMisjumpRange           = 0.5;
     257             :         being_fined = NO;
     258             :         isNearPlanetSurface = NO;
     259             :         suppressAegisMessages = NO;
     260             :         isMissile = NO;
     261             :         suppressExplosion = NO;
     262             :         _lightsActive = YES;
     263             :         
     264             : 
     265             :         // set things from dictionary from here out - default values might require adjustment -- Kaks 20091130
     266             :         _scaleFactor = [shipDict oo_floatForKey:@"model_scale_factor" defaultValue:1.0f];
     267             : 
     268             : 
     269             :         float defaultSpeed = isStation ? 0.0f : 160.0f;
     270             :         maxFlightSpeed = [shipDict oo_floatForKey:@"max_flight_speed" defaultValue:defaultSpeed];
     271             :         max_flight_roll = [shipDict oo_floatForKey:@"max_flight_roll" defaultValue:2.0f];
     272             :         max_flight_pitch = [shipDict oo_floatForKey:@"max_flight_pitch" defaultValue:1.0f];
     273             :         max_flight_yaw = [shipDict oo_floatForKey:@"max_flight_yaw" defaultValue:max_flight_pitch];   // Note by default yaw == pitch
     274             :         cruiseSpeed = maxFlightSpeed*0.8f;
     275             :         
     276             :         max_thrust = [shipDict oo_floatForKey:@"thrust" defaultValue:15.0f];
     277             :         thrust = max_thrust;
     278             : 
     279             :         afterburner_rate = [shipDict oo_floatForKey:@"injector_burn_rate" defaultValue:AFTERBURNER_BURNRATE];
     280             :         afterburner_speed_factor = [shipDict oo_floatForKey:@"injector_speed_factor" defaultValue:7.0f];
     281             :         if (afterburner_speed_factor < 1.0)
     282             :         {
     283             :                 OOLog(@"ship.setup.injectorSpeed",@"injector_speed_factor cannot be lower than 1.0 for %@",self);
     284             :                 afterburner_speed_factor = 1.0;
     285             :         }
     286             : #if OO_VARIABLE_TORUS_SPEED
     287             :         else if (afterburner_speed_factor > MIN_HYPERSPEED_FACTOR)
     288             :         {
     289             :                 OOLog(@"ship.setup.injectorSpeed",@"injector_speed_factor cannot be higher than minimum torus speed factor (%f) for %@.",MIN_HYPERSPEED_FACTOR,self);
     290             :                 afterburner_speed_factor = MIN_HYPERSPEED_FACTOR;
     291             :         }
     292             : #else
     293             :         else if (afterburner_speed_factor > HYPERSPEED_FACTOR)
     294             :         {
     295             :                 OOLog(@"ship.setup.injectorSpeed",@"injector_speed_factor cannot be higher than torus speed factor (%f) for %@.",HYPERSPEED_FACTOR,self);
     296             :                 afterburner_speed_factor = HYPERSPEED_FACTOR;
     297             :         }
     298             : #endif
     299             : 
     300             :         maxEnergy = [shipDict oo_floatForKey:@"max_energy" defaultValue:200.0f];
     301             :         energy_recharge_rate = [shipDict oo_floatForKey:@"energy_recharge_rate" defaultValue:1.0f];
     302             :         
     303             :         _showDamage = [shipDict oo_boolForKey:@"show_damage" defaultValue:(energy_recharge_rate > 0)];
     304             :         // Each new ship should start in seemingly good operating condition, unless specifically told not to - this does not affect the ship's energy levels
     305             :         [self setThrowSparks:[shipDict oo_boolForKey:@"throw_sparks" defaultValue:NO]];
     306             :         
     307             :         weapon_facings = [shipDict oo_intForKey:@"weapon_facings" defaultValue:VALID_WEAPON_FACINGS] & VALID_WEAPON_FACINGS;
     308             :         if (weapon_facings & WEAPON_FACING_FORWARD)
     309             :                 forward_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"forward_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]);
     310             :         if (weapon_facings & WEAPON_FACING_AFT)
     311             :                 aft_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"aft_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]);
     312             :         if (weapon_facings & WEAPON_FACING_PORT)
     313             :                 port_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"port_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]);
     314             :         if (weapon_facings & WEAPON_FACING_STARBOARD)
     315             :                 starboard_weapon_type = OOWeaponTypeFromString([shipDict oo_stringForKey:@"starboard_weapon_type" defaultValue:@"EQ_WEAPON_NONE"]);
     316             : 
     317             :         cloaking_device_active = NO;
     318             :         military_jammer_active = NO;
     319             :         cloakPassive = [shipDict oo_boolForKey:@"cloak_passive" defaultValue:YES]; // Nikos - switched passive cloak default to YES 20120523
     320             :         cloakAutomatic = [shipDict oo_boolForKey:@"cloak_automatic" defaultValue:YES];
     321             : 
     322             :         missiles = [shipDict oo_intForKey:@"missiles" defaultValue:0];
     323             :         /* TODO: The following initializes the missile list to be blank, which prevents a crash caused by hasOneEquipmentItem trying to access a missile list
     324             :                  previously initialized but then released.  See issue #204.  We need to investigate further the cause of the missile list being released.
     325             :                         - kanthoney 10/03/2017
     326             :                 Update 20170818: The issue seems to have been resolved properly using the fix below and the problem was apparently an access of the
     327             :                 missile_list array elements before their initialization and while we were checking whether equipment can be added or not. There is
     328             :                 probably not much more that can be done here, unless someone would like to have a go at refactoring the entire ship initialization
     329             :                 code. In any case, the crash is no more and the applied solution is both simple and logical - Nikos
     330             :         */
     331             :         unsigned i;
     332             :         for (i = 0; i < missiles; i++)
     333             :         {
     334             :                 missile_list[i] = nil;
     335             :         }
     336             :         max_missiles = [shipDict oo_intForKey:@"max_missiles" defaultValue:missiles];
     337             :         if (max_missiles > SHIPENTITY_MAX_MISSILES) max_missiles = SHIPENTITY_MAX_MISSILES;
     338             :         if (missiles > max_missiles) missiles = max_missiles;
     339             :         missile_load_time = fmax(0.0, [shipDict oo_doubleForKey:@"missile_load_time" defaultValue:0.0]); // no negative load times
     340             :         missile_launch_time = [UNIVERSE getTime] + missile_load_time;
     341             :         
     342             :         // upgrades:
     343             :         equipment_weight = 0; 
     344             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_ecm"])  [self addEquipmentItem:@"EQ_ECM" inContext:@"npc"];
     345             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_scoop"])  [self addEquipmentItem:@"EQ_FUEL_SCOOPS" inContext:@"npc"];
     346             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_escape_pod"])  [self addEquipmentItem:@"EQ_ESCAPE_POD" inContext:@"npc"];
     347             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_cloaking_device"])  [self addEquipmentItem:@"EQ_CLOAKING_DEVICE" inContext:@"npc"];
     348             :         if ([shipDict oo_floatForKey:@"has_energy_bomb"] > 0)
     349             :         {
     350             :                 /*      NOTE: has_energy_bomb actually refers to QC mines.
     351             :                         
     352             :                         max_missiles for NPCs is a newish addition, and ships have
     353             :                         traditionally not needed to reserve a slot for a Q-mine added this
     354             :                         way. If has_energy_bomb is possible, and max_missiles is not
     355             :                         explicit, we add an extra missile slot to compensate.
     356             :                         -- Ahruman 2011-03-25
     357             :                 */
     358             :                 if ([shipDict oo_fuzzyBooleanForKey:@"has_energy_bomb"])
     359             :                 {
     360             :                         if (max_missiles == missiles && max_missiles < SHIPENTITY_MAX_MISSILES && [shipDict objectForKey:@"max_missiles"] == nil)
     361             :                         {
     362             :                                 max_missiles++;
     363             :                         }
     364             :                         [self addEquipmentItem:@"EQ_QC_MINE" inContext:@"npc"];
     365             :                 }
     366             :         }
     367             : 
     368             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_fuel_injection"])  [self addEquipmentItem:@"EQ_FUEL_INJECTION" inContext:@"npc"];
     369             : 
     370             : #if USEMASC
     371             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_military_jammer"])  [self addEquipmentItem:@"EQ_MILITARY_JAMMER" inContext:@"npc"];
     372             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_military_scanner_filter"])  [self addEquipmentItem:@"EQ_MILITARY_SCANNER_FILTER" inContext:@"npc"];
     373             : #endif
     374             :         
     375             :         
     376             :         // can it be 'mined' for alloys?
     377             :         canFragment = [shipDict oo_fuzzyBooleanForKey:@"fragment_chance" defaultValue:0.9];
     378             :         isWreckage = NO;
     379             : 
     380             :         // can subentities be destroyed separately?
     381             :         isFrangible = [shipDict oo_boolForKey:@"frangible" defaultValue:YES];
     382             :         
     383             :         max_cargo = [shipDict oo_unsignedIntForKey:@"max_cargo"];
     384             :         extra_cargo = [shipDict oo_unsignedIntForKey:@"extra_cargo" defaultValue:15];
     385             :         
     386             :         hyperspaceMotorSpinTime = [shipDict oo_floatForKey:@"hyperspace_motor_spin_time" defaultValue:DEFAULT_HYPERSPACE_SPIN_TIME];
     387             :         if(![shipDict oo_boolForKey:@"hyperspace_motor" defaultValue:YES]) hyperspaceMotorSpinTime = -1;
     388             :         
     389             :         [name autorelease];
     390             :         name = [[shipDict oo_stringForKey:@"name" defaultValue:@"?"] copy];
     391             :         
     392             :         [shipUniqueName autorelease];
     393             :         shipUniqueName = [[shipDict oo_stringForKey:@"ship_name" defaultValue:@""] copy];
     394             : 
     395             :         [shipClassName autorelease];
     396             :         shipClassName = [[shipDict oo_stringForKey:@"ship_class_name" defaultValue:name] copy];
     397             : 
     398             :         [displayName autorelease];
     399             :         displayName = [[shipDict oo_stringForKey:@"display_name" defaultValue:nil] copy];
     400             :         
     401             :         // Load the model (must be before subentities)
     402             :         NSString *modelName = [shipDict oo_stringForKey:@"model"];
     403             :         if (modelName != nil)
     404             :         {
     405             :                 OOMesh *mesh = nil;
     406             : 
     407             :                 mesh = [OOMesh meshWithName:modelName
     408             :                                                    cacheKey:[NSString stringWithFormat:@"%@-%.3f",_shipKey,_scaleFactor]
     409             :                                  materialDictionary:[shipDict oo_dictionaryForKey:@"materials"]
     410             :                                   shadersDictionary:[shipDict oo_dictionaryForKey:@"shaders"]
     411             :                                                          smooth:[shipDict oo_boolForKey:@"smooth" defaultValue:NO]
     412             :                                            shaderMacros:OODefaultShipShaderMacros()
     413             :                                            shaderBindingTarget:self
     414             :                                                 scaleFactor:_scaleFactor
     415             :                                          cacheWriteable:YES];
     416             : 
     417             :                 if (mesh == nil)  return NO;
     418             :                 [self setMesh:mesh];
     419             :         }
     420             :         
     421             :         float density = [shipDict oo_floatForKey:@"density" defaultValue:1.0f];
     422             :         if (octree)  mass = (GLfloat)(density * 20.0f * [octree volume]);
     423             :         
     424             :         DESTROY(default_laser_color);
     425             :         default_laser_color = [[OOColor brightColorWithDescription:[shipDict objectForKey:@"laser_color"]] retain];
     426             :         
     427             :         if (default_laser_color == nil) 
     428             :         {
     429             :                 [self setLaserColor:[OOColor redColor]];
     430             :         }
     431             :         else
     432             :         {
     433             :                 [self setLaserColor:default_laser_color];
     434             :         }
     435             :         // exhaust emissive color
     436             :         OORGBAComponents defaultExhaustEmissiveColorComponents; // pale blue is exhaust default color
     437             :         defaultExhaustEmissiveColorComponents.r = 0.7f;
     438             :         defaultExhaustEmissiveColorComponents.g = 0.9f;
     439             :         defaultExhaustEmissiveColorComponents.b = 1.0f;
     440             :         defaultExhaustEmissiveColorComponents.a = 0.9f;
     441             :         OOColor *color = [OOColor brightColorWithDescription:[shipDict objectForKey:@"exhaust_emissive_color"]];
     442             :         if (color == nil)  color = [OOColor colorWithRGBAComponents:defaultExhaustEmissiveColorComponents];
     443             :         [self setExhaustEmissiveColor:color];
     444             :         
     445             :         [self clearSubEntities];
     446             :         [self setUpSubEntities];
     447             : 
     448             : // correctly initialise weaponRange, etc. (must be after subentity setup)
     449             :         if (isWeaponNone(forward_weapon_type))
     450             :         {
     451             :                 OOWeaponType                    weapon_type = nil;
     452             :                 BOOL hasTurrets = NO;
     453             :                 NSEnumerator    *subEnum = [self shipSubEntityEnumerator];
     454             :                 ShipEntity              *se = nil;
     455             :                 while (isWeaponNone(weapon_type) && (se = [subEnum nextObject]))
     456             :                 {
     457             :                         weapon_type = se->forward_weapon_type;
     458             :                         if (se->behaviour == BEHAVIOUR_TRACK_AS_TURRET)
     459             :                         {
     460             :                                 hasTurrets = YES;
     461             :                         }
     462             :                 }
     463             :                 if (isWeaponNone(weapon_type) && hasTurrets)
     464             :                 { /* safety for ships only equipped with turrets
     465             :                      note: this was hard-coded to 10000.0, although turrets have a notably 
     466             :                      shorter range. We are using a multiplier of 1.667 in order to not change
     467             :                      something that already works, but probably it would be best to use
     468             :                      TURRET_SHOT_RANGE * COMBAT_WEAPON_RANGE_FACTOR here
     469             :                   */
     470             :                         weaponRange = TURRET_SHOT_RANGE * 1.667;
     471             :                 }
     472             :                 else
     473             :                 {
     474             :                         [self setWeaponDataFromType:weapon_type];
     475             :                 }
     476             :         }
     477             :         else
     478             :         {
     479             :                 [self setWeaponDataFromType:forward_weapon_type];
     480             :         }
     481             :         
     482             :         // rotating subentities
     483             :         subentityRotationalVelocity = kIdentityQuaternion;
     484             :         if ([shipDict objectForKey:@"rotational_velocity"])
     485             :         {
     486             :                 subentityRotationalVelocity = [shipDict oo_quaternionForKey:@"rotational_velocity"];
     487             :         }
     488             : 
     489             :         // set weapon offsets
     490             :         NSString *weaponMountMode = [shipDict oo_stringForKey:@"weapon_mount_mode" defaultValue:@"single"];
     491             :         _multiplyWeapons = [weaponMountMode isEqualToString:@"multiply"];
     492             :         forwardWeaponOffset = [[self getWeaponOffsetFrom:shipDict withKey:@"weapon_position_forward" inMode:weaponMountMode] retain];
     493             :         aftWeaponOffset = [[self getWeaponOffsetFrom:shipDict withKey:@"weapon_position_aft" inMode:weaponMountMode] retain];
     494             :         portWeaponOffset = [[self getWeaponOffsetFrom:shipDict withKey:@"weapon_position_port" inMode:weaponMountMode] retain];
     495             :         starboardWeaponOffset = [[self getWeaponOffsetFrom:shipDict withKey:@"weapon_position_starboard" inMode:weaponMountMode] retain];
     496             : 
     497             :         
     498             :         tractor_position = vector_multiply_scalar([shipDict oo_vectorForKey:@"scoop_position"],_scaleFactor);
     499             :         
     500             : 
     501             :         
     502             :         // sun glare filter - default is high filter, both for HDR and SDR
     503             :         [self setSunGlareFilter:[shipDict oo_floatForKey:@"sun_glare_filter" defaultValue:0.97f]];
     504             :         
     505             :         // Get scriptInfo dictionary, containing arbitrary stuff scripts might be interested in.
     506             :         scriptInfo = [[shipDict oo_dictionaryForKey:@"script_info" defaultValue:nil] retain];
     507             : 
     508             :         explosionType = [[shipDict oo_arrayForKey:@"explosion_type" defaultValue:nil] retain];
     509             : 
     510             :         isDemoShip = NO;
     511             :         
     512             :         return YES;
     513             :         
     514             :         OOJS_PROFILE_EXIT
     515             : }
     516             : 
     517             : 
     518             : 
     519             : - (BOOL) setUpShipFromDictionary:(NSDictionary *) shipDict
     520             : {
     521             :         OOJS_PROFILE_ENTER
     522             :         
     523             :         if (![self setUpFromDictionary:shipDict]) return NO;
     524             :         
     525             :         // NPC-only settings.
     526             :         //
     527             :         orientation = kIdentityQuaternion;
     528             :         rotMatrix       = kIdentityMatrix;
     529             :         v_forward       = kBasisZVector;
     530             :         v_up            = kBasisYVector;
     531             :         v_right         = kBasisXVector;
     532             :         reference       = v_forward;  // reference vector for (* turrets *)
     533             :         
     534             :         isShip = YES;
     535             : 
     536             :         // scan class settings. 'scanClass' is in common usage, but we could also have a more standard 'scan_class' key with higher precedence. Kaks 20090810 
     537             :         // let's see if scan_class is set... 
     538             :         scanClass = OOScanClassFromString([shipDict oo_stringForKey:@"scan_class" defaultValue:@"CLASS_NOT_SET"]);
     539             :         
     540             :         // if not, try 'scanClass'. NOTE: non-standard capitalization is documented and entrenched.
     541             :         if (scanClass == CLASS_NOT_SET)
     542             :         {
     543             :                 scanClass = OOScanClassFromString([shipDict oo_stringForKey:@"scanClass" defaultValue:@"CLASS_NOT_SET"]);
     544             :         }
     545             : 
     546             :         [scan_description autorelease];
     547             :         scan_description = [[shipDict oo_stringForKey:@"scan_description" defaultValue:nil] copy];
     548             : 
     549             :         // FIXME: give NPCs shields instead.
     550             :         
     551             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_shield_booster"])  [self addEquipmentItem:@"EQ_SHIELD_BOOSTER" inContext:@"npc"];
     552             :         if ([shipDict oo_fuzzyBooleanForKey:@"has_shield_enhancer"])  [self addEquipmentItem:@"EQ_SHIELD_ENHANCER" inContext:@"npc"];
     553             :         
     554             :         // Start with full energy banks.
     555             :         energy = maxEnergy;
     556             :         weapon_temp                             = 0.0f;
     557             :         forward_weapon_temp             = 0.0f;
     558             :         aft_weapon_temp                 = 0.0f;
     559             :         port_weapon_temp                = 0.0f;
     560             :         starboard_weapon_temp   = 0.0f;
     561             :         
     562             :         // setWeaponDataFromType inside setUpFromDictionary should set weapon_damage from the front laser.
     563             :         // no weapon_damage? It's a missile: set weapon_damage from shipdata!
     564             :         if (weapon_damage == 0.0) 
     565             :         {
     566             :                 weapon_damage_override = weapon_damage = [shipDict oo_floatForKey:@"weapon_energy" defaultValue:0]; // any damage value for missiles/bombs
     567             :         }
     568             :         else
     569             :         {
     570             :                 weapon_damage_override = 0;
     571             :         }
     572             : 
     573             :         scannerRange = [shipDict oo_floatForKey:@"scanner_range" defaultValue:(float)SCANNER_MAX_RANGE];
     574             :         
     575             :         fuel = [shipDict oo_unsignedShortForKey:@"fuel"];     // Does it make sense that this defaults to 0? Should it not be 70? -- Ahruman
     576             :         
     577             :         fuel_accumulator = 1.0;
     578             :         
     579             :         [self setBounty:[shipDict oo_unsignedIntForKey:@"bounty" defaultValue:0] withReason:kOOLegalStatusReasonSetup];
     580             :         
     581             :         [shipAI autorelease];
     582             :         shipAI = [[AI alloc] init];
     583             :         [shipAI setOwner:self];
     584             :         [self setAITo:[shipDict oo_stringForKey:@"ai_type" defaultValue:@"nullAI.plist"]];
     585             :         
     586             :         likely_cargo = [shipDict oo_unsignedIntForKey:@"likely_cargo"];
     587             :         noRocks = [shipDict oo_fuzzyBooleanForKey:@"no_boulders"];
     588             :         
     589             :         commodity_amount = 0;
     590             :         commodity_type = nil;
     591             :         NSString *cargoString = [shipDict oo_stringForKey:@"cargo_carried"];
     592             :         if (cargoString != nil)
     593             :         {
     594             :                 if ([cargoString isEqualToString:@"SCARCE_GOODS"])
     595             :                 {
     596             :                         cargo_flag = CARGO_FLAG_FULL_SCARCE;
     597             :                 }
     598             :                 else if ([cargoString isEqualToString:@"PLENTIFUL_GOODS"])
     599             :                 {
     600             :                         cargo_flag = CARGO_FLAG_FULL_PLENTIFUL;
     601             :                 }
     602             :                 else
     603             :                 {
     604             :                         cargo_flag = CARGO_FLAG_FULL_UNIFORM;
     605             : 
     606             :                         OOCommodityType c_commodity = nil;
     607             :                         int                             c_amount = 1;
     608             :                         NSScanner               *scanner = [NSScanner scannerWithString:cargoString];
     609             :                         if ([scanner scanInt:&c_amount])
     610             :                         {
     611             :                                 [scanner ooliteScanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:NULL];  // skip whitespace
     612             :                                 c_commodity = [[scanner string] substringFromIndex:[scanner scanLocation]];
     613             :                                 if ([[UNIVERSE commodities] goodDefined:c_commodity])
     614             :                                 {
     615             :                                         [self setCommodityForPod:c_commodity andAmount:c_amount];
     616             :                                 } 
     617             :                                 else
     618             :                                 {
     619             :                                         c_commodity = [[UNIVERSE commodities] goodNamed:c_commodity];
     620             :                                         if ([[UNIVERSE commodities] goodDefined:c_commodity])
     621             :                                         {
     622             :                                                 [self setCommodityForPod:c_commodity andAmount:c_amount];
     623             :                                         }
     624             :                                 }                                       
     625             :                         }
     626             :                         else
     627             :                         {
     628             :                                 c_amount = 1;
     629             :                                 c_commodity = [shipDict oo_stringForKey:@"cargo_carried"];
     630             :                                 if ([[UNIVERSE commodities] goodDefined:c_commodity])
     631             :                                 {
     632             :                                         [self setCommodityForPod:c_commodity andAmount:c_amount];
     633             :                                 } 
     634             :                                 else
     635             :                                 {
     636             :                                         c_commodity = [[UNIVERSE commodities] goodNamed:c_commodity];
     637             :                                         if ([[UNIVERSE commodities] goodDefined:c_commodity])
     638             :                                         {
     639             :                                                 [self setCommodityForPod:c_commodity andAmount:c_amount];
     640             :                                         }
     641             :                                 }
     642             :                         }
     643             :                 }
     644             :         }
     645             :         
     646             :         cargoString = [shipDict oo_stringForKey:@"cargo_type"];
     647             :         if (cargoString)
     648             :         {
     649             :                 if (cargo != nil) [cargo autorelease];
     650             :                 cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // alloc retains;
     651             :                 
     652             :                 [self setUpCargoType:cargoString];
     653             :         }
     654             :         else if (scanClass != CLASS_CARGO)
     655             :         {
     656             :                 if (cargo != nil) [cargo autorelease];
     657             :                 cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // alloc retains;
     658             :                 // if not CLASS_CARGO, and no cargo type set, default to CARGO_NOT_CARGO
     659             :                 cargo_type = CARGO_NOT_CARGO;
     660             :         }
     661             :         
     662             :         hasScoopMessage = [shipDict oo_boolForKey:@"has_scoop_message" defaultValue:YES];
     663             : 
     664             :         
     665             :         [roleSet release];
     666             :         roleSet = [[[OORoleSet roleSetWithString:[shipDict oo_stringForKey:@"roles"]] roleSetWithRemovedRole:@"player"] retain];
     667             :         [primaryRole release];
     668             :         primaryRole = nil;
     669             :         
     670             :         [self setOwner:self];
     671             :         [self setHulk:[shipDict oo_boolForKey:@"is_hulk"]];
     672             :         
     673             :         // these are the colors used for the "lollipop" of the ship. Any of the two (or both, for flash effect) can be defined. nil means use default from shipData.
     674             :         [self setScannerDisplayColor1:nil];
     675             :         [self setScannerDisplayColor2:nil];
     676             :         // and the same for the "hostile" colours
     677             :         [self setScannerDisplayColorHostile1:nil];
     678             :         [self setScannerDisplayColorHostile2:nil];
     679             : 
     680             : 
     681             :         // Populate the missiles here. Must come after scanClass.
     682             :         _missileRole = [shipDict oo_stringForKey:@"missile_role"];
     683             :         unsigned        i, j;
     684             :         for (i = 0, j = 0; i < missiles; i++)
     685             :         {
     686             :                 missile_list[i] = [self selectMissile];
     687             :                 // could loop forever (if missile_role is badly defined, selectMissile might return nil in some cases) . Try 3 times, and if no luck, skip
     688             :                 if (missile_list[i] == nil && j < 3)
     689             :                 {
     690             :                         j++;
     691             :                         i--;
     692             :                 }
     693             :                 else
     694             :                 {
     695             :                         j = 0;
     696             :                         if (missile_list[i] == nil)
     697             :                         {
     698             :                                 missiles--;
     699             :                         }
     700             :                 }
     701             :         }
     702             : 
     703             :         // accuracy. Must come after scanClass, because we are using scanClass to determine if this is a missile.
     704             : 
     705             : // missiles: range 0 to +10
     706             : // ships: range -5 to +10, but randomly only -5 <= accuracy < +5
     707             : // enables "better" AIs at +5 and above
     708             : // police and military always have positive accuracy
     709             : 
     710             :         accuracy = [shipDict oo_floatForKey:@"accuracy" defaultValue:-100.0f];        // Out-of-range default
     711             :         if (accuracy < -5.0f || accuracy > 10.0f)
     712             :         {
     713             :                 accuracy = (randf() * 10.0)-5.0;
     714             : 
     715             :                 if (accuracy < 0.0f && (scanClass == CLASS_MILITARY || scanClass == CLASS_POLICE))
     716             :                 { // police and military pilots have a better average skill. 
     717             :                         accuracy = -accuracy;
     718             :                 }
     719             :         }
     720             :         if (scanClass == CLASS_MISSILE)
     721             :         { // missile accuracy range is 0 to 10
     722             :                 accuracy = OOClamp_0_max_f(accuracy, 10.0f);
     723             :         }
     724             :         [self setAccuracy:accuracy]; // set derived variables
     725             :         _missed_shots = 0;
     726             : 
     727             :         //  escorts
     728             :         _maxEscortCount = MIN([shipDict oo_unsignedCharForKey:@"escorts" defaultValue:0], (uint8_t)MAX_ESCORTS);
     729             :         _pendingEscortCount = _maxEscortCount;
     730             :         if (_pendingEscortCount == 0 && [shipDict oo_arrayForKey:@"escort_roles" defaultValue:nil] != nil)
     731             :         {
     732             :                 // mostly ignored by setUpMixedEscorts, but needs to be high
     733             :                 // enough that it doesn't end up at zero (e.g. by governmental
     734             :                 // reductions in [Universe addShipAt]
     735             :                 _pendingEscortCount = MAX_ESCORTS;
     736             :         }
     737             : 
     738             :         
     739             :         // beacons
     740             :         [self setBeaconCode:[shipDict oo_stringForKey:@"beacon"]];
     741             :         [self setBeaconLabel:[shipDict oo_stringForKey:@"beacon_label" defaultValue:[shipDict oo_stringForKey:@"beacon"]]];
     742             : 
     743             :         
     744             :         // contact tracking entities
     745             :         [self setTrackCloseContacts:[shipDict oo_boolForKey:@"track_contacts" defaultValue:NO]];
     746             :         
     747             :         // ship skin insulation factor (1.0 is normal)
     748             :         [self setHeatInsulation:[shipDict oo_floatForKey:@"heat_insulation" defaultValue:[self hasHeatShield] ? 2.0 : 1.0]];
     749             :         
     750             :         // unpiloted (like missiles asteroids etc.)
     751             :         _explicitlyUnpiloted = [shipDict oo_fuzzyBooleanForKey:@"unpiloted"];
     752             :         if (_explicitlyUnpiloted)
     753             :         {
     754             :                 [self setCrew:nil];
     755             :         }
     756             :         else 
     757             :         {
     758             :                 // crew and passengers
     759             :                 NSDictionary *cdict = [[UNIVERSE characters] objectForKey:[shipDict oo_stringForKey:@"pilot"]];
     760             :                 if (cdict != nil)
     761             :                 {
     762             :                         OOCharacter     *pilot = [OOCharacter characterWithDictionary:cdict];
     763             :                         [self setCrew:[NSArray arrayWithObject:pilot]];
     764             :                 }
     765             :         }
     766             :         
     767             :         [self setShipScript:[shipDict oo_stringForKey:@"script"]];
     768             : 
     769             :         home_system = [UNIVERSE currentSystemID];
     770             :         destination_system = [UNIVERSE currentSystemID];
     771             : 
     772             :         reactionTime = [shipDict oo_floatForKey: @"reaction_time" defaultValue: COMBAT_AI_STANDARD_REACTION_TIME];
     773             :         
     774             :         return YES;
     775             :         
     776             :         OOJS_PROFILE_EXIT
     777             : }
     778             : 
     779             : 
     780             : - (void) setSubIdx:(NSUInteger)value
     781             : {
     782             :         _subIdx = value;
     783             : }
     784             : 
     785             : 
     786             : - (NSUInteger) subIdx
     787             : {
     788             :         return _subIdx;
     789             : }
     790             : 
     791             : 
     792             : - (NSUInteger) maxShipSubEntities
     793             : {
     794             :         return _maxShipSubIdx;
     795             : }
     796             : 
     797             : 
     798           0 : - (NSString *) repeatString:(NSString *)str times:(NSUInteger)times
     799             : {
     800             :         if (times == 0)  return @"";
     801             :         
     802             :         NSMutableString         *result = [NSMutableString stringWithCapacity:[str length] * times];
     803             :         
     804             :         for (NSUInteger i = 0; i < times; i++)
     805             :         {
     806             :             [result appendString:str];
     807             :         }
     808             :         
     809             :         return result;
     810             : }
     811             : 
     812             :  
     813             : - (NSString *) serializeShipSubEntities
     814             : {       
     815             :         NSMutableString         *result = [NSMutableString stringWithCapacity:4];
     816             :         NSEnumerator            *subEnum = nil;
     817             :         ShipEntity                      *se = nil;
     818             :         NSUInteger                      diff, i = 0;
     819             :         
     820             :         for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
     821             :         {
     822             :                 diff = [se subIdx] - i;
     823             :                 i += diff + 1;
     824             :                 [result appendString:[self repeatString:@"0" times:diff]];
     825             :                 [result appendString:@"1"];
     826             :         }
     827             :         // add trailing zeroes
     828             :         [result appendString:[self repeatString:@"0" times:[self maxShipSubEntities] - i]];
     829             :         return result;
     830             : }
     831             : 
     832             : 
     833             : - (void) deserializeShipSubEntitiesFrom:(NSString *)string
     834             : {
     835             :         NSArray                         *subEnts = [[self shipSubEntityEnumerator] allObjects];
     836             :         NSInteger                       i,idx, start = [subEnts count] - 1;
     837             :         NSInteger                       strMaxIdx = [string length] - 1;
     838             :                 
     839             :         ShipEntity                      *se = nil;
     840             :         
     841             :         for (i = start; i >= 0; i--)
     842             :         {
     843             :                 se = (ShipEntity *)[subEnts objectAtIndex:i];
     844             :                 idx = [se subIdx]; // should be identical to i, but better safe than sorry...
     845             :                 if (idx <= strMaxIdx && [[string substringWithRange:NSMakeRange(idx, 1)] isEqualToString:@"0"])
     846             :                 {
     847             :                         [se setSuppressExplosion:NO];
     848             :                         [se setEnergy:1];
     849             :                         [se takeEnergyDamage:500000000.0 from:nil becauseOf:nil weaponIdentifier:@""];
     850             :                 }
     851             :         }
     852             : }
     853             : 
     854             : 
     855             : - (BOOL) setUpSubEntities
     856             : {
     857             :         OOJS_PROFILE_ENTER
     858             :         
     859             :         unsigned int    i;
     860             :         NSDictionary    *shipDict = [self shipInfoDictionary];
     861             :         NSArray                 *plumes = [shipDict oo_arrayForKey:@"exhaust"];
     862             :         
     863             :         _profileRadius = collision_radius;
     864             :         _maxShipSubIdx = 0;
     865             :         
     866             :         for (i = 0; i < [plumes count]; i++)
     867             :         {
     868             :                 NSArray *definition = ScanTokensFromString([plumes oo_stringAtIndex:i]);
     869             :                 OOExhaustPlumeEntity *exhaust = [OOExhaustPlumeEntity exhaustForShip:self withDefinition:definition andScale:_scaleFactor];
     870             :                 [self addSubEntity:exhaust];
     871             :         }
     872             :         
     873             :         NSArray *subs = [shipDict oo_arrayForKey:@"subentities"];
     874             :         
     875             :         totalBoundingBox = boundingBox;
     876             :         
     877             :         for (i = 0; i < [subs count]; i++)
     878             :         {
     879             :                 [self setUpOneSubentity:[subs oo_dictionaryAtIndex:i]];
     880             :         }
     881             :         
     882             :         no_draw_distance = _profileRadius * _profileRadius * NO_DRAW_DISTANCE_FACTOR * NO_DRAW_DISTANCE_FACTOR * 2.0;
     883             :         
     884             :         return YES;
     885             :         
     886             :         OOJS_PROFILE_EXIT
     887             : }
     888             : 
     889             : 
     890             : - (GLfloat) frustumRadius
     891             : {
     892             :         OOScalar exhaust_length = 0;
     893             :         NSEnumerator *exEnum = nil;
     894             :         OOExhaustPlumeEntity *exEnt = nil;
     895             :         for (exEnum = [self exhaustEnumerator]; (exEnt = [exEnum nextObject]); )
     896             :         {
     897             :                 if ([exEnt findCollisionRadius] > exhaust_length)
     898             :                 {
     899             :                         exhaust_length = [exEnt findCollisionRadius];
     900             :                 }
     901             :         }
     902             :         return _profileRadius + exhaust_length;
     903             : }
     904             : 
     905             : 
     906           0 : - (BOOL) setUpOneSubentity:(NSDictionary *) subentDict
     907             : {
     908             :         OOJS_PROFILE_ENTER
     909             :         
     910             :         NSString                        *type = nil;
     911             :         
     912             :         type = [subentDict oo_stringForKey:@"type"];
     913             :         if ([type isEqualToString:@"flasher"])
     914             :         {
     915             :                 return [self setUpOneFlasher:subentDict];
     916             :         }
     917             :         else
     918             :         {
     919             :                 return [self setUpOneStandardSubentity:subentDict asTurret:[type isEqualToString:@"ball_turret"]];
     920             :         }
     921             :         
     922             :         OOJS_PROFILE_EXIT
     923             : }
     924             : 
     925             : 
     926           0 : - (BOOL) setUpOneFlasher:(NSDictionary *) subentDict
     927             : {
     928             :         OOFlasherEntity *flasher = [OOFlasherEntity flasherWithDictionary:subentDict];
     929             :         [flasher setPosition:HPvector_multiply_scalar([subentDict oo_hpvectorForKey:@"position"],_scaleFactor)];
     930             :         [flasher rescaleBy:_scaleFactor];
     931             :         [self addSubEntity:flasher];
     932             :         return YES;
     933             : }
     934             : 
     935             : 
     936             : - (BOOL) setUpOneStandardSubentity:(NSDictionary *)subentDict asTurret:(BOOL)asTurret
     937             : {
     938             :         ShipEntity                      *subentity = nil;
     939             :         NSString                        *subentKey = nil;
     940             :         HPVector                                subPosition;
     941             :         Quaternion                      subOrientation;
     942             :         
     943             :         subentKey = [subentDict oo_stringForKey:@"subentity_key"];
     944             :         if (subentKey == nil) {
     945             :                 OOLog(@"setup.ship.badEntry.subentities",@"Failed to set up entity - no subentKey in %@",subentDict);
     946             :                 return NO;
     947             :         }
     948             :         
     949             :         if (!asTurret && [self isStation] && [subentDict oo_boolForKey:@"is_dock"])
     950             :         {
     951             :                 subentity = [UNIVERSE newDockWithName:subentKey andScaleFactor:_scaleFactor];
     952             :         }
     953             :         else 
     954             :         {
     955             :                 subentity = [UNIVERSE newSubentityWithName:subentKey andScaleFactor:_scaleFactor];
     956             :         }
     957             :         if (subentity == nil) {
     958             :                 OOLog(@"setup.ship.badEntry.subentities",@"Failed to set up entity %@",subentKey);
     959             :                 return NO;
     960             :         }
     961             :         
     962             :         subPosition = HPvector_multiply_scalar([subentDict oo_hpvectorForKey:@"position"],_scaleFactor);
     963             :         subOrientation = [subentDict oo_quaternionForKey:@"orientation"];
     964             :         
     965             :         [subentity setPosition:subPosition];
     966             :         [subentity setOrientation:subOrientation];
     967             :         [subentity setReference:vector_forward_from_quaternion(subOrientation)];
     968             :         // subentities inherit parent personality
     969             :         [subentity setEntityPersonalityInt:[self entityPersonalityInt]];
     970             : 
     971             :         if (asTurret)
     972             :         {
     973             :                 [subentity setBehaviour:BEHAVIOUR_TRACK_AS_TURRET];
     974             :                 [subentity setWeaponRechargeRate:[subentDict oo_floatForKey:@"fire_rate" defaultValue:TURRET_SHOT_FREQUENCY]];
     975             :                 [subentity setWeaponEnergy:[subentDict oo_floatForKey:@"weapon_energy" defaultValue:TURRET_TYPICAL_ENERGY]];
     976             :                 [subentity setWeaponRange:[subentDict oo_floatForKey:@"weapon_range" defaultValue:TURRET_SHOT_RANGE]];
     977             :                 [subentity setStatus: STATUS_ACTIVE];
     978             :         }
     979             :         else
     980             :         {
     981             :                 [subentity setStatus:STATUS_INACTIVE];
     982             :         }
     983             :         
     984             :         [subentity overrideScriptInfo:[subentDict oo_dictionaryForKey:@"script_info"]];
     985             :         
     986             :         [self addSubEntity:subentity];
     987             :         [subentity setSubIdx:_maxShipSubIdx];
     988             :         _maxShipSubIdx++;
     989             :         
     990             :         // update subentities
     991             :         BoundingBox sebb = [subentity findSubentityBoundingBox];
     992             :         bounding_box_add_vector(&totalBoundingBox, sebb.max);
     993             :         bounding_box_add_vector(&totalBoundingBox, sebb.min);
     994             : 
     995             :         if (!asTurret && [self isStation] && [subentDict oo_boolForKey:@"is_dock"])
     996             :         {
     997             :                 BOOL allow_docking = [subentDict oo_boolForKey:@"allow_docking" defaultValue:YES];
     998             :                 BOOL ddc = [subentDict oo_boolForKey:@"disallowed_docking_collides" defaultValue:NO];
     999             :                 BOOL allow_launching = [subentDict oo_boolForKey:@"allow_launching" defaultValue:YES];
    1000             :                 // do not include this key in OOShipRegistry; should never be set by shipdata
    1001             :                 BOOL virtual_dock = [subentDict oo_boolForKey:@"_is_virtual_dock" defaultValue:NO];
    1002             :                 if (virtual_dock)
    1003             :                 {
    1004             :                         [(DockEntity *)subentity setVirtual];
    1005             :                 }
    1006             :                 
    1007             :                 [(DockEntity *)subentity setDimensionsAndCorridor:allow_docking:ddc:allow_launching];
    1008             :                 [subentity setDisplayName:[subentDict oo_stringForKey:@"dock_label" defaultValue:@"the docking bay"]];
    1009             :         }
    1010             : 
    1011             :         [subentity release];
    1012             :         
    1013             :         return YES;
    1014             : }
    1015             : 
    1016             : 
    1017             : - (BOOL) isTemplateCargoPod
    1018             : {
    1019             :         return [[self primaryRole] isEqualToString:@"oolite-template-cargopod"];
    1020             : }
    1021             : 
    1022             : 
    1023             : - (void) setUpCargoType:(NSString *) cargoString
    1024             : {
    1025             :         cargo_type = StringToCargoType(cargoString);
    1026             :         
    1027             :         switch (cargo_type)
    1028             :         {
    1029             :                 case CARGO_SLAVES:
    1030             :                         commodity_amount = 1;
    1031             :                         DESTROY(commodity_type);
    1032             :                         commodity_type = @"slaves";
    1033             :                         cargo_type = CARGO_RANDOM; // not realy random, but it tells that cargo is selected.
    1034             :                         break;
    1035             :                         
    1036             :                 case CARGO_ALLOY:
    1037             :                         commodity_amount = 1;
    1038             :                         DESTROY(commodity_type);
    1039             :                         commodity_type = @"alloys";
    1040             :                         cargo_type = CARGO_RANDOM;
    1041             :                         break;
    1042             :                         
    1043             :                 case CARGO_MINERALS:
    1044             :                         commodity_amount = 1;
    1045             :                         DESTROY(commodity_type);
    1046             :                         commodity_type = @"minerals";
    1047             :                         cargo_type = CARGO_RANDOM;
    1048             :                         break;
    1049             :                         
    1050             :                 case CARGO_THARGOID:
    1051             :                         commodity_amount = 1;
    1052             :                         DESTROY(commodity_type);
    1053             :                         commodity_type = @"alien_items";
    1054             :                         cargo_type = CARGO_RANDOM;
    1055             :                         break;
    1056             :                         
    1057             :                 case CARGO_SCRIPTED_ITEM:
    1058             :                         commodity_amount = 1; // value > 0 is needed to be recognised as cargo by scripts;
    1059             :                         DESTROY(commodity_type); // will be defined elsewhere when needed.
    1060             :                         break;
    1061             :                         
    1062             :                 case CARGO_RANDOM:
    1063             :                         // Could already be set by the cargo_carried key. If not, ensure at least one.
    1064             :                         if (commodity_amount == 0) commodity_amount = 1;
    1065             :                         break;
    1066             : 
    1067             :                 default:
    1068             :                         break;
    1069             :         }
    1070             : }
    1071             : 
    1072             : 
    1073           0 : - (void) dealloc
    1074             : {
    1075             :         /*      NOTE: we guarantee that entityDestroyed is sent immediately after the
    1076             :                 JS ship becomes invalid (as a result of dropping the weakref), i.e.
    1077             :                 with no intervening script activity.
    1078             :                 It has to be after the invalidation so that scripts can't directly or
    1079             :                 indirectly cause the ship to become strong-referenced. (Actually, we
    1080             :                 could handle that situation by breaking out of dealloc, but that's a
    1081             :                 nasty abuse of framework semantics and would require special-casing in
    1082             :                 subclasses.)
    1083             :                 -- Ahruman 2011-02-27
    1084             :         */
    1085             :         [weakSelf weakRefDrop];
    1086             :         weakSelf = nil;
    1087             :         ShipScriptEventNoCx(self, "entityDestroyed");
    1088             :         
    1089             :         [self setTrackCloseContacts:NO];        // deallocs tracking dictionary
    1090             :         [[self parentEntity] subEntityReallyDied:self]; // Will do nothing if we're not really a subentity
    1091             :         [self clearSubEntities];
    1092             :         
    1093             :         DESTROY(_shipKey);
    1094             :         DESTROY(shipinfoDictionary);
    1095             :         DESTROY(shipAI);
    1096             :         DESTROY(cargo);
    1097             :         DESTROY(name);
    1098             :         DESTROY(shipUniqueName);
    1099             :         DESTROY(shipClassName);
    1100             :         DESTROY(displayName);
    1101             :         DESTROY(scan_description);
    1102             :         DESTROY(roleSet);
    1103             :         DESTROY(primaryRole);
    1104             :         DESTROY(laser_color);
    1105             :         DESTROY(default_laser_color);
    1106             :         DESTROY(exhaust_emissive_color);
    1107             :         DESTROY(scanner_display_color1);
    1108             :         DESTROY(scanner_display_color2);
    1109             :         DESTROY(scanner_display_color_hostile1);
    1110             :         DESTROY(scanner_display_color_hostile2);
    1111             :         DESTROY(script);
    1112             :         DESTROY(aiScript);
    1113             :         DESTROY(previousCondition);
    1114             :         DESTROY(dockingInstructions);
    1115             :         DESTROY(crew);
    1116             :         DESTROY(lastRadioMessage);
    1117             :         DESTROY(octree);
    1118             :         DESTROY(_defenseTargets);
    1119             :         DESTROY(_collisionExceptions);
    1120             : 
    1121             :         DESTROY(forwardWeaponOffset);
    1122             :         DESTROY(aftWeaponOffset);
    1123             :         DESTROY(portWeaponOffset);
    1124             :         DESTROY(starboardWeaponOffset);
    1125             :         
    1126             :         DESTROY(commodity_type);
    1127             : 
    1128             :         [self setSubEntityTakingDamage:nil];
    1129             :         [self removeAllEquipment];
    1130             :         
    1131             :         [_group removeShip:self];
    1132             :         DESTROY(_group);
    1133             :         [_escortGroup removeShip:self];
    1134             :         DESTROY(_escortGroup);
    1135             :         
    1136             :         DESTROY(_lastAegisLock);
    1137             :         
    1138             :         DESTROY(_beaconCode);
    1139             :         DESTROY(_beaconLabel);
    1140             :         DESTROY(_beaconDrawable);
    1141             :         
    1142             :         DESTROY(explosionType);
    1143             : 
    1144             :         [super dealloc];
    1145             : }
    1146             : 
    1147             : 
    1148             : - (void) removeScript
    1149             : {
    1150             :         [script autorelease];
    1151             :         script = nil;
    1152             : }
    1153             : 
    1154             : 
    1155             : - (void) clearSubEntities
    1156             : {
    1157             :         [subEntities makeObjectsPerformSelector:@selector(setOwner:) withObject:nil];   // Ensure backlinks are broken
    1158             :         [subEntities release];
    1159             :         subEntities = nil;
    1160             :         
    1161             :         // reset size & mass!
    1162             :         collision_radius = [self findCollisionRadius];
    1163             :         _profileRadius = collision_radius;
    1164             :         float density = [[self shipInfoDictionary] oo_floatForKey:@"density" defaultValue:1.0f];
    1165             :         if (octree)  mass = (GLfloat)(density * 20.0f * [octree volume]);
    1166             : }
    1167             : 
    1168             : 
    1169             : - (Quaternion) subEntityRotationalVelocity
    1170             : {
    1171             :         return subentityRotationalVelocity;
    1172             : }
    1173             : 
    1174             : 
    1175             : - (void) setSubEntityRotationalVelocity:(Quaternion)rv
    1176             : {
    1177             :         subentityRotationalVelocity = rv;
    1178             : }
    1179             : 
    1180             : 
    1181           0 : - (NSString *)descriptionComponents
    1182             : {
    1183             :         if (![self isSubEntity])
    1184             :         {
    1185             :                 return [NSString stringWithFormat:@"\"%@\" %@", [self name], [super descriptionComponents]];
    1186             :         }
    1187             :         else
    1188             :         {
    1189             :                 // ID, scanClass and status are of no interest for subentities.
    1190             :                 NSString *subtype = nil;
    1191             :                 if ([self behaviour] == BEHAVIOUR_TRACK_AS_TURRET)  subtype = @"(turret)";
    1192             :                 else  subtype = @"(subentity)";
    1193             :                 
    1194             :                 return [NSString stringWithFormat:@"\"%@\" position: %@ %@", [self name], HPVectorDescription([self position]), subtype];
    1195             :         }
    1196             : }
    1197             : 
    1198             : 
    1199           0 : - (NSString *) shortDescriptionComponents
    1200             : {
    1201             :         return [NSString stringWithFormat:@"\"%@\"", [self name]];
    1202             : }
    1203             : 
    1204             : 
    1205             : - (GLfloat) sunGlareFilter
    1206             : {
    1207             :         return sunGlareFilter;
    1208             : }
    1209             : 
    1210             : 
    1211             : - (void) setSunGlareFilter:(GLfloat)newValue
    1212             : {
    1213             :         sunGlareFilter = OOClamp_0_1_f(newValue);
    1214             : }
    1215             : 
    1216             : 
    1217             : - (GLfloat) accuracy
    1218             : {
    1219             :         return accuracy;
    1220             : }
    1221             : 
    1222             : 
    1223             : - (void) setAccuracy:(GLfloat) new_accuracy
    1224             : {
    1225             :         if (new_accuracy < 0.0f && scanClass == CLASS_MISSILE)
    1226             :         {
    1227             :                 new_accuracy = 0.0;
    1228             :         }
    1229             :         else if (new_accuracy < -5.0f)
    1230             :         {
    1231             :                 new_accuracy = -5.0;
    1232             :         }
    1233             :         else if (new_accuracy > 10.0f)
    1234             :         {
    1235             :                 new_accuracy = 10.0;
    1236             :         }
    1237             :         accuracy = new_accuracy;
    1238             :         pitch_tolerance = 0.01 * (85.0f + accuracy);
    1239             : // especially against small targets, less good pilots will waste some shots
    1240             :         aim_tolerance = 240.0 - (18.0f * accuracy);
    1241             : 
    1242             :         if (accuracy >= COMBAT_AI_ISNT_AWFUL && missile_load_time < 0.1)
    1243             :         {
    1244             :                 missile_load_time = 2.0; // smart enough not to waste all missiles on 1 ECM!
    1245             :         }
    1246             : }
    1247             : 
    1248             : - (OOMesh *)mesh
    1249             : {
    1250             :         return (OOMesh *)[self drawable];
    1251             : }
    1252             : 
    1253             : 
    1254             : - (void)setMesh:(OOMesh *)mesh
    1255             : {
    1256             :         if (mesh != [self mesh])
    1257             :         {
    1258             :                 [self setDrawable:mesh];
    1259             :                 [octree autorelease];
    1260             :                 octree = [[mesh octree] retain];
    1261             :         }
    1262             : }
    1263             : 
    1264             : 
    1265             : - (BoundingBox) totalBoundingBox
    1266             : {
    1267             :         return totalBoundingBox;
    1268             : }
    1269             : 
    1270             : 
    1271             : - (Vector) forwardVector
    1272             : {
    1273             :         return v_forward;
    1274             : }
    1275             : 
    1276             : 
    1277             : - (Vector) upVector
    1278             : {
    1279             :         return v_up;
    1280             : }
    1281             : 
    1282             : 
    1283             : - (Vector) rightVector
    1284             : {
    1285             :         return v_right;
    1286             : }
    1287             : 
    1288             : 
    1289             : - (BOOL) scriptedMisjump
    1290             : {
    1291             :         return scripted_misjump;
    1292             : }
    1293             : 
    1294             : 
    1295             : - (void) setScriptedMisjump:(BOOL)newValue
    1296             : {
    1297             :         scripted_misjump = !!newValue;
    1298             : }
    1299             : 
    1300             : 
    1301             : - (GLfloat) scriptedMisjumpRange
    1302             : {
    1303             :         return _scriptedMisjumpRange;
    1304             : }
    1305             : 
    1306             : 
    1307             : - (void) setScriptedMisjumpRange:(GLfloat)newValue
    1308             : {
    1309             :         _scriptedMisjumpRange = newValue;
    1310             : }
    1311             : 
    1312             : 
    1313             : - (NSArray *) subEntities
    1314             : {
    1315             :         return [[subEntities copy] autorelease];
    1316             : }
    1317             : 
    1318             : 
    1319             : - (NSUInteger) subEntityCount
    1320             : {
    1321             :         return [subEntities count];
    1322             : }
    1323             : 
    1324             : 
    1325             : - (BOOL) hasSubEntity:(Entity<OOSubEntity> *)sub
    1326             : {
    1327             :         return [subEntities containsObject:sub];
    1328             : }
    1329             : 
    1330             : 
    1331             : - (NSEnumerator *)subEntityEnumerator
    1332             : {
    1333             :         return [[self subEntities] objectEnumerator];
    1334             : }
    1335             : 
    1336             : 
    1337             : - (NSEnumerator *)shipSubEntityEnumerator
    1338             : {
    1339             :         return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isShip)];
    1340             : }
    1341             : 
    1342             : 
    1343             : - (NSEnumerator *)flasherEnumerator
    1344             : {
    1345             :         return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isFlasher)];
    1346             : }
    1347             : 
    1348             : 
    1349             : - (NSEnumerator *)exhaustEnumerator
    1350             : {
    1351             :         return [[self subEntities] objectEnumeratorFilteredWithSelector:@selector(isExhaust)];
    1352             : }
    1353             : 
    1354             : 
    1355             : - (ShipEntity *) subEntityTakingDamage
    1356             : {
    1357             :         ShipEntity *result = [_subEntityTakingDamage weakRefUnderlyingObject];
    1358             :         
    1359             : #ifndef NDEBUG
    1360             :         // Sanity check - there have been problems here, see fireLaserShotInDirection:
    1361             :         // -parentEntity will take care of reporting insanity.
    1362             :         if ([result parentEntity] != self)  result = nil;
    1363             : #endif
    1364             :         
    1365             :         // Clear the weakref if the subentity is dead.
    1366             :         if (result == nil)  [self setSubEntityTakingDamage:nil];
    1367             :         
    1368             :         return result;
    1369             : }
    1370             : 
    1371             : 
    1372             : - (void) setSubEntityTakingDamage:(ShipEntity *)sub
    1373             : {
    1374             : #ifndef NDEBUG
    1375             :         // Sanity checks: sub must be a ship subentity of self, or nil.
    1376             :         if (sub != nil)
    1377             :         {
    1378             :                 if (![self hasSubEntity:sub])
    1379             :                 {
    1380             :                         OOLog(@"ship.subentity.sanityCheck.failed.details", @"Attempt to set subentity taking damage of %@ to %@, which is not a subentity.", [self shortDescription], sub);
    1381             :                         sub = nil;
    1382             :                 }
    1383             :                 else if (![sub isShip])
    1384             :                 {
    1385             :                         OOLog(@"ship.subentity.sanityCheck.failed", @"Attempt to set subentity taking damage of %@ to %@, which is not a ship.", [self shortDescription], sub);
    1386             :                         sub = nil;
    1387             :                 }
    1388             :         }
    1389             : #endif
    1390             :         
    1391             :         [_subEntityTakingDamage release];
    1392             :         _subEntityTakingDamage = [sub weakRetain];
    1393             : }
    1394             : 
    1395             : 
    1396             : - (OOScript *)shipScript
    1397             : {
    1398             :         return script;
    1399             : }
    1400             : 
    1401             : 
    1402             : - (OOScript *)shipAIScript
    1403             : {
    1404             :         return aiScript;
    1405             : }
    1406             : 
    1407             : 
    1408             : - (OOTimeAbsolute) shipAIScriptWakeTime
    1409             : {
    1410             :         return aiScriptWakeTime;
    1411             : }
    1412             : 
    1413             : 
    1414             : - (void) setAIScriptWakeTime:(OOTimeAbsolute) t
    1415             : {
    1416             :         aiScriptWakeTime = t;
    1417             : }
    1418             : 
    1419             : 
    1420           0 : - (BoundingBox)findBoundingBoxRelativeToPosition:(HPVector)opv InVectors:(Vector) _i :(Vector) _j :(Vector) _k
    1421             : {
    1422             :         // HPVect: check that this conversion doesn't lose needed precision
    1423             :         return [[self mesh] findBoundingBoxRelativeToPosition:HPVectorToVector(opv)
    1424             :                                                                                                         basis:_i :_j :_k
    1425             :                                                                                  selfPosition:HPVectorToVector(position)
    1426             :                                                                                                 selfBasis:v_right :v_up :v_forward];
    1427             : }
    1428             : 
    1429             : 
    1430             : - (Octree *) octree
    1431             : {
    1432             :         return octree;
    1433             : }
    1434             : 
    1435             : 
    1436             : - (float) volume
    1437             : {
    1438             :         return [octree volume];
    1439             : }
    1440             : 
    1441             : 
    1442             : - (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1
    1443             : {
    1444             :         Vector u0 = HPVectorToVector(HPvector_between(position, v0));   // relative to origin of model / octree
    1445             :         Vector u1 = HPVectorToVector(HPvector_between(position, v1));
    1446             :         Vector w0 = make_vector(dot_product(u0, v_right), dot_product(u0, v_up), dot_product(u0, v_forward));   // in ijk vectors
    1447             :         Vector w1 = make_vector(dot_product(u1, v_right), dot_product(u1, v_up), dot_product(u1, v_forward));
    1448             :         return [octree isHitByLine:w0 :w1];
    1449             : }
    1450             : 
    1451             : 
    1452             : - (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1 :(ShipEntity **)hitEntity
    1453             : {
    1454             :         if (hitEntity)
    1455             :                 hitEntity[0] = (ShipEntity*)nil;
    1456             :         Vector u0 = HPVectorToVector(HPvector_between(position, v0));   // relative to origin of model / octree
    1457             :         Vector u1 = HPVectorToVector(HPvector_between(position, v1));
    1458             :         Vector w0 = make_vector(dot_product(u0, v_right), dot_product(u0, v_up), dot_product(u0, v_forward));   // in ijk vectors
    1459             :         Vector w1 = make_vector(dot_product(u1, v_right), dot_product(u1, v_up), dot_product(u1, v_forward));
    1460             :         GLfloat hit_distance = [octree isHitByLine:w0 :w1];
    1461             :         if (hit_distance)
    1462             :         {
    1463             :                 if (hitEntity)
    1464             :                         hitEntity[0] = self;
    1465             :         }
    1466             :         
    1467             :         NSEnumerator    *subEnum = nil;
    1468             :         ShipEntity              *se = nil;
    1469             :         for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
    1470             :         {
    1471             :                 HPVector p0 = [se absolutePositionForSubentity];
    1472             :                 Triangle ijk = [se absoluteIJKForSubentity];
    1473             :                 u0 = HPVectorToVector(HPvector_between(p0, v0));
    1474             :                 u1 = HPVectorToVector(HPvector_between(p0, v1));
    1475             :                 w0 = resolveVectorInIJK(u0, ijk);
    1476             :                 w1 = resolveVectorInIJK(u1, ijk);
    1477             :                 
    1478             :                 GLfloat hitSub = [se->octree isHitByLine:w0 :w1];
    1479             :                 if (hitSub && (hit_distance == 0 || hit_distance > hitSub))
    1480             :                 {       
    1481             :                         hit_distance = hitSub;
    1482             :                         if (hitEntity)
    1483             :                         {
    1484             :                                 *hitEntity = se;
    1485             :                         }
    1486             :                 }
    1487             :         }
    1488             :         
    1489             :         return hit_distance;
    1490             : }
    1491             : 
    1492             : 
    1493             : - (GLfloat)doesHitLine:(HPVector)v0 :(HPVector)v1 withPosition:(HPVector)o andIJK:(Vector)i :(Vector)j :(Vector)k
    1494             : {
    1495             :         Vector u0 = HPVectorToVector(HPvector_between(o, v0));  // relative to origin of model / octree
    1496             :         Vector u1 = HPVectorToVector(HPvector_between(o, v1));
    1497             :         Vector w0 = make_vector(dot_product(u0, i), dot_product(u0, j), dot_product(u0, k));    // in ijk vectors
    1498             :         Vector w1 = make_vector(dot_product(u1, j), dot_product(u1, j), dot_product(u1, k));
    1499             :         return [octree isHitByLine:w0 :w1];
    1500             : }
    1501             : 
    1502             : 
    1503           0 : - (void) wasAddedToUniverse
    1504             : {
    1505             :         [super wasAddedToUniverse];
    1506             :         
    1507             :         // if we have a universal id then we can proceed to set up any
    1508             :         // stuff that happens when we get added to the UNIVERSE
    1509             :         if (universalID != NO_TARGET)
    1510             :         {
    1511             :                 // set up escorts
    1512             :                 if (([self status] == STATUS_IN_FLIGHT || [self status] == STATUS_LAUNCHING) && _pendingEscortCount != 0)       // just popped into existence
    1513             :                 {
    1514             :                         [self setUpEscorts];
    1515             :                 }
    1516             :                 else
    1517             :                 {
    1518             :                         /*      Earlier there was a silly log message here because I thought
    1519             :                                 this would never happen, but wasn't entirely sure. Turns out
    1520             :                                 it did!
    1521             :                                 -- Ahruman 2009-09-13
    1522             :                         */
    1523             :                         _pendingEscortCount = 0;
    1524             :                 }
    1525             :         }
    1526             : 
    1527             :         //      Tell subentities, too
    1528             :         [subEntities makeObjectsPerformSelector:@selector(wasAddedToUniverse)];
    1529             :         
    1530             :         [self resetExhaustPlumes];
    1531             : }
    1532             : 
    1533             : 
    1534           0 : - (void)wasRemovedFromUniverse
    1535             : {
    1536             :         [subEntities makeObjectsPerformSelector:@selector(wasRemovedFromUniverse)];
    1537             : }
    1538             : 
    1539             : 
    1540             : - (HPVector)absoluteTractorPosition
    1541             : {
    1542             :         return HPvector_add(position, vectorToHPVector(quaternion_rotate_vector([self normalOrientation], tractor_position)));
    1543             : }
    1544             : 
    1545             : 
    1546           0 : - (NSString *) beaconCode
    1547             : {
    1548             :         return _beaconCode;
    1549             : }
    1550             : 
    1551             : 
    1552           0 : - (void) setBeaconCode:(NSString *)bcode
    1553             : {
    1554             :         if ([bcode length] == 0)  bcode = nil;
    1555             :         
    1556             :         if (_beaconCode != bcode)
    1557             :         {
    1558             :                 [_beaconCode release];
    1559             :                 _beaconCode = [bcode copy];
    1560             :                 
    1561             :                 DESTROY(_beaconDrawable);
    1562             :         }
    1563             :         // if not blanking code and label is currently blank, default label to code
    1564             :         if (bcode != nil && (_beaconLabel == nil || [_beaconLabel length] == 0))
    1565             :         {
    1566             :                 [self setBeaconLabel:bcode];
    1567             :         }
    1568             : }
    1569             : 
    1570             : 
    1571           0 : - (NSString *) beaconLabel
    1572             : {
    1573             :         return _beaconLabel;
    1574             : }
    1575             : 
    1576             : 
    1577           0 : - (void) setBeaconLabel:(NSString *)blabel
    1578             : {
    1579             :         if ([blabel length] == 0)  blabel = nil;
    1580             :         
    1581             :         if (_beaconLabel != blabel)
    1582             :         {
    1583             :                 [_beaconLabel release];
    1584             :                 _beaconLabel = [OOExpand(blabel) retain];
    1585             :         }
    1586             : }
    1587             : 
    1588             : 
    1589           0 : - (BOOL) isVisible
    1590             : {
    1591             :         return cam_zero_distance <= no_draw_distance;
    1592             : }
    1593             : 
    1594             : 
    1595           0 : - (BOOL) isBeacon
    1596             : {
    1597             :         return [self beaconCode] != nil;
    1598             : }
    1599             : 
    1600             : 
    1601           0 : - (id <OOHUDBeaconIcon>) beaconDrawable
    1602             : {
    1603             :         if (_beaconDrawable == nil)
    1604             :         {
    1605             :                 NSString        *beaconCode = [self beaconCode];
    1606             :                 NSUInteger      length = [beaconCode length];
    1607             :                 
    1608             :                 if (length > 1)
    1609             :                 {
    1610             :                         NSArray *iconData = [[UNIVERSE descriptions] oo_arrayForKey:beaconCode];
    1611             :                         if (iconData != nil)  _beaconDrawable = [[OOPolygonSprite alloc] initWithDataArray:iconData outlineWidth:0.5 name:beaconCode];
    1612             :                 }
    1613             :                 
    1614             :                 if (_beaconDrawable == nil)
    1615             :                 {
    1616             :                         if (length > 0)  _beaconDrawable = [[beaconCode substringToIndex:1] retain];
    1617             :                         else  _beaconDrawable = @"";
    1618             :                 }
    1619             :         }
    1620             :         
    1621             :         return _beaconDrawable;
    1622             : }
    1623             : 
    1624             : 
    1625           0 : - (Entity <OOBeaconEntity> *) prevBeacon
    1626             : {
    1627             :         return [_prevBeacon weakRefUnderlyingObject];
    1628             : }
    1629             : 
    1630             : 
    1631           0 : - (Entity <OOBeaconEntity> *) nextBeacon
    1632             : {
    1633             :         return [_nextBeacon weakRefUnderlyingObject];
    1634             : }
    1635             : 
    1636             : 
    1637           0 : - (void) setPrevBeacon:(Entity <OOBeaconEntity> *)beaconShip
    1638             : {
    1639             :         if (beaconShip != [self prevBeacon])
    1640             :         {
    1641             :                 [_prevBeacon release];
    1642             :                 _prevBeacon = [beaconShip weakRetain];
    1643             :         }
    1644             : }
    1645             : 
    1646             : 
    1647           0 : - (void) setNextBeacon:(Entity <OOBeaconEntity> *)beaconShip
    1648             : {
    1649             :         if (beaconShip != [self nextBeacon])
    1650             :         {
    1651             :                 [_nextBeacon release];
    1652             :                 _nextBeacon = [beaconShip weakRetain];
    1653             :         }
    1654             : }
    1655             : 
    1656             : 
    1657           0 : #define kBoulderRole (@"boulder")
    1658             : 
    1659             : - (void) setIsBoulder:(BOOL)flag
    1660             : {
    1661             :         if (flag)  [self addRole:kBoulderRole];
    1662             :         else  [self removeRole:kBoulderRole];
    1663             : }
    1664             : 
    1665             : 
    1666             : - (BOOL) isBoulder
    1667             : {
    1668             :         return [roleSet hasRole:kBoulderRole];
    1669             : }
    1670             : 
    1671             : 
    1672             : - (BOOL) isMinable
    1673             : {
    1674             :         if ([self hasRole:@"asteroid"] || [self isBoulder])
    1675             :         {
    1676             :                 if (!noRocks)
    1677             :                 {
    1678             :                         return YES;
    1679             :                 }
    1680             :         }
    1681             :         return NO;
    1682             : }
    1683             : 
    1684             : 
    1685             : - (BOOL) countsAsKill
    1686             : {
    1687             :         return [[self shipInfoDictionary] oo_boolForKey:@"counts_as_kill" defaultValue:YES];
    1688             : }
    1689             : 
    1690             : 
    1691             : - (void) setUpEscorts
    1692             : {
    1693             :         // Ensure that we do not try to create escorts if we are an escort ship ourselves.
    1694             :         // This could lead to circular reference memory overflows (e.g. "boa-mk2" trying to create 4 "boa-mk2"
    1695             :         // escorts or the case of two ships specifying eachother as escorts) - Nikos 20090510
    1696             :         if ([self isEscort])
    1697             :         {
    1698             :                 OOLogWARN(@"ship.setUp.escortShipCircularReference", 
    1699             :                                 @"Ship %@ requested escorts, when it is an escort ship itself. Avoiding possible circular reference overflow by ignoring escort setup.", self);
    1700             :                 return;
    1701             :         }
    1702             : 
    1703             :         if ([shipinfoDictionary objectForKey:@"escort_roles"] != nil)
    1704             :         {
    1705             :                 [self setUpMixedEscorts];
    1706             :                 return;
    1707             :         }
    1708             : 
    1709             :         NSString        *defaultRole = @"escort";
    1710             :         NSString                *escortRole = nil;
    1711             :         NSString                *escortShipKey = nil;
    1712             :         
    1713             :         if (_pendingEscortCount == 0)  return;
    1714             :         
    1715             :         if (_maxEscortCount < _pendingEscortCount)
    1716             :         {
    1717             :                 if ([self hasPrimaryRole:@"police"] || [self hasPrimaryRole:@"hunter"])
    1718             :                 {
    1719             :                         _maxEscortCount = MAX_ESCORTS; // police and hunters get up to MAX_ESCORTS, overriding the 'escorts' key.
    1720             :                         [self updateEscortFormation];
    1721             :                 }
    1722             :                 else
    1723             :                 {
    1724             :                         _pendingEscortCount = _maxEscortCount;  // other ships can only get what's defined inside their 'escorts' key.
    1725             :                 }
    1726             :         }
    1727             :         
    1728             :         if ([self isPolice])  defaultRole = @"wingman";
    1729             :         
    1730             :         escortRole = [shipinfoDictionary oo_stringForKey:@"escort_role" defaultValue:nil];
    1731             :         if (escortRole == nil)
    1732             :                 escortRole = [shipinfoDictionary oo_stringForKey:@"escort-role" defaultValue:defaultRole];
    1733             :         if (![escortRole isEqualToString: defaultRole])
    1734             :         {
    1735             :                 if (![[UNIVERSE newShipWithRole:escortRole] autorelease])
    1736             :                 {
    1737             :                         escortRole = defaultRole;
    1738             :                 }
    1739             :         }
    1740             :         
    1741             :         escortShipKey = [shipinfoDictionary oo_stringForKey:@"escort_ship" defaultValue:nil];
    1742             :         if (escortShipKey == nil)
    1743             :                 escortShipKey = [shipinfoDictionary oo_stringForKey:@"escort-ship"];
    1744             :         
    1745             :         if (escortShipKey != nil)
    1746             :         {
    1747             :                 if (![[UNIVERSE newShipWithName:escortShipKey] autorelease])
    1748             :                 {
    1749             :                         escortShipKey = nil;
    1750             :                 }
    1751             :                 else
    1752             :                 {
    1753             :                         escortRole = [NSString stringWithFormat:@"[%@]",escortShipKey];
    1754             :                 }
    1755             :         }
    1756             : 
    1757             :         OOShipGroup *escortGroup = [self escortGroup];
    1758             :         if ([self group] == nil)
    1759             :         {
    1760             :                 [self setGroup:escortGroup]; // should probably become a copy of the escortGroup post NMSR.
    1761             :         }
    1762             :         [escortGroup setLeader:self];
    1763             :         
    1764             :         [self refreshEscortPositions];
    1765             :         
    1766             :         uint8_t currentEscortCount = [escortGroup count] - 1;   // always at least 0.
    1767             :         
    1768             :         while (_pendingEscortCount > 0 && ([self isThargoid] || currentEscortCount < _maxEscortCount))
    1769             :         {
    1770             :                  // The following line adds escort 1 in position 1, etc... up to MAX_ESCORTS.
    1771             :                 HPVector ex_pos = [self coordinatesForEscortPosition:currentEscortCount];
    1772             :                 
    1773             :                 ShipEntity *escorter = nil;
    1774             :                 
    1775             :                 escorter = [UNIVERSE newShipWithRole:escortRole];       // retained
    1776             :                 
    1777             :                 if (escorter == nil)  break;
    1778             :                 [self setUpOneEscort:escorter inGroup:escortGroup withRole:escortRole atPosition:ex_pos andCount:currentEscortCount];
    1779             : 
    1780             :                 [escorter release];
    1781             : 
    1782             :                 _pendingEscortCount--;
    1783             :                 currentEscortCount = [escortGroup count] - 1;
    1784             :         }
    1785             :         // done assigning escorts
    1786             :         _pendingEscortCount = 0;
    1787             : }
    1788             : 
    1789             : 
    1790           0 : - (void) setUpMixedEscorts
    1791             : {
    1792             :         NSArray *escortRoles = [shipinfoDictionary oo_arrayForKey:@"escort_roles" defaultValue:nil];
    1793             :         if (escortRoles == nil)
    1794             :         {
    1795             :                 OOLogWARN(@"eship.setUp.escortShipRoles", 
    1796             :                                   @"Ship %@ has bad escort_roles definition.", self);
    1797             :                 return;
    1798             :         }
    1799             :         NSEnumerator                            *edefEnumerator = nil;
    1800             :         NSDictionary                            *escortDefinition = nil;
    1801             :         NSDictionary            *systeminfo = nil;
    1802             :         OOGovernmentID          government;
    1803             : 
    1804             :         systeminfo = [UNIVERSE currentSystemData];
    1805             :         government = [systeminfo oo_unsignedCharForKey:KEY_GOVERNMENT];
    1806             : 
    1807             :         OOShipGroup *escortGroup = [self escortGroup];
    1808             :         if ([self group] == nil)
    1809             :         {
    1810             :                 [self setGroup:escortGroup]; // should probably become a copy of the escortGroup post NMSR.
    1811             :         }
    1812             :         [escortGroup setLeader:self];
    1813             :         _maxEscortCount = MAX_ESCORTS;
    1814             :         [self refreshEscortPositions];
    1815             :         
    1816             :         uint8_t currentEscortCount = [escortGroup count] - 1;   // always at least 0
    1817             :         
    1818             :         _maxEscortCount = 0;
    1819             :         int8_t i = 0;
    1820             :         for (edefEnumerator = [escortRoles objectEnumerator]; (escortDefinition = [edefEnumerator nextObject]); )
    1821             :         {
    1822             :                 if (currentEscortCount >= MAX_ESCORTS)
    1823             :                 {
    1824             :                         break;
    1825             :                 }
    1826             :                 // int rather than uint because, at least for min, there is a
    1827             :                 // use to giving a negative value
    1828             :                 int8_t min = [escortDefinition oo_intForKey:@"min" defaultValue:0];
    1829             :                 int8_t max = [escortDefinition oo_intForKey:@"max" defaultValue:2];
    1830             :                 NSString *escortRole = [escortDefinition oo_stringForKey:@"role" defaultValue:@"escort"];
    1831             :                 int8_t desired = max;
    1832             :                 if (min < desired)
    1833             :                 {
    1834             :                         for (i = min ; i < max ; i++)
    1835             :                         {
    1836             :                                 if (Ranrot()%11 < government+2)
    1837             :                                 {
    1838             :                                         desired--;
    1839             :                                 }
    1840             :                         }
    1841             :                 }
    1842             :                 for (i = 0; i < desired; i++)
    1843             :                 {
    1844             :                         if (currentEscortCount >= MAX_ESCORTS)
    1845             :                         {
    1846             :                                 break;
    1847             :                         }
    1848             :                         if (![escortRole isEqualToString:@""])
    1849             :                         {
    1850             :                                 HPVector ex_pos = [self coordinatesForEscortPosition:currentEscortCount];
    1851             :                                 ShipEntity *escorter = [UNIVERSE newShipWithRole:escortRole];   // retained
    1852             :                                 if (escorter == nil)
    1853             :                                 {
    1854             :                                         break;
    1855             :                                 }
    1856             :                                 [self setUpOneEscort:escorter inGroup:escortGroup withRole:escortRole atPosition:ex_pos andCount:currentEscortCount];
    1857             :                                 [escorter release];
    1858             :                         }
    1859             :                         currentEscortCount++;
    1860             :                         _maxEscortCount++;
    1861             :                 }
    1862             :         }
    1863             :         // done assigning escorts
    1864             :         _pendingEscortCount = 0;
    1865             : }
    1866             : 
    1867             : 
    1868           0 : - (void) setUpOneEscort:(ShipEntity *)escorter inGroup:(OOShipGroup *)escortGroup withRole:(NSString *)escortRole atPosition:(HPVector)ex_pos andCount:(uint8_t)currentEscortCount
    1869             : {
    1870             :         NSString                *autoAI = nil;
    1871             :         NSString                *pilotRole = nil;
    1872             :         NSDictionary    *autoAIMap = nil;
    1873             :         NSDictionary    *escortShipDict = nil;
    1874             :         AI                              *escortAI = nil; 
    1875             :         NSString                *defaultRole = @"escort";
    1876             : 
    1877             :         if ([self isPolice])
    1878             :         {
    1879             :                 defaultRole = @"wingman";
    1880             :                 pilotRole = @"police"; // police are always insured.
    1881             :         }
    1882             :         else
    1883             :         {
    1884             :                 pilotRole = bounty ? @"pirate" : @"hunter"; // hunters have insurancies, pirates not.
    1885             :         }
    1886             :         
    1887             :         double dd = escorter->collision_radius;
    1888             :                 
    1889             :         if (EXPECT(currentEscortCount < (uint8_t)MAX_ESCORTS))
    1890             :         {
    1891             :                 // spread them around a little randomly
    1892             :                 ex_pos.x += dd * 6.0 * (randf() - 0.5);
    1893             :                 ex_pos.y += dd * 6.0 * (randf() - 0.5);
    1894             :                 ex_pos.z += dd * 6.0 * (randf() - 0.5);
    1895             :         }
    1896             :         else
    1897             :         {
    1898             :                 // Thargoid armada(!) Add more distance between the 'escorts'.
    1899             :                 ex_pos.x += dd * 12.0 * (randf() - 0.5);
    1900             :                 ex_pos.y += dd * 12.0 * (randf() - 0.5);
    1901             :                 ex_pos.z += dd * 12.0 * (randf() - 0.5);
    1902             :         }
    1903             :                 
    1904             :         [escorter setPosition:ex_pos];  // minimise lollipop flash
    1905             :                 
    1906             :         if ([escorter crew] == nil)
    1907             :         {
    1908             :                 [escorter setSingleCrewWithRole:pilotRole];
    1909             :         }
    1910             :                 
    1911             :         [escorter setPrimaryRole:defaultRole];  //for mothership
    1912             :         // in case this hasn't yet been set, make sure escorts get a real scan class
    1913             :         // shouldn't happen very often, but is possible
    1914             :         if (scanClass == CLASS_NOT_SET)
    1915             :         {
    1916             :                 scanClass = CLASS_NEUTRAL;
    1917             :         }
    1918             :         [escorter setScanClass:scanClass];              // you are the same as I
    1919             :                 
    1920             :         if ([self bounty] == 0)  [escorter setBounty:0 withReason:kOOLegalStatusReasonSetup];   // Avoid dirty escorts for clean mothers
    1921             :                 
    1922             :         // find the right autoAI.
    1923             :         escortShipDict = [escorter shipInfoDictionary];
    1924             :         autoAIMap = [ResourceManager dictionaryFromFilesNamed:@"autoAImap.plist" inFolder:@"Config" andMerge:YES];
    1925             :         autoAI = [autoAIMap oo_stringForKey:defaultRole];
    1926             :         if (autoAI==nil) // no 'wingman' defined in autoAImap?
    1927             :         {
    1928             :                 autoAI = [autoAIMap oo_stringForKey:@"escort" defaultValue:@"nullAI.plist"];
    1929             :         }
    1930             :                 
    1931             :         escortAI = [escorter getAI];
    1932             :                 
    1933             :         // Let the populator decide which AI to use, unless we have a working alternative AI & we specify auto_ai = NO !
    1934             :         if ( (escortRole && [escortShipDict oo_fuzzyBooleanForKey:@"auto_ai" defaultValue:YES])
    1935             :                  || ([[escortAI name] isEqualToString: @"nullAI.plist"] && ![autoAI isEqualToString:@"nullAI.plist"]) )
    1936             :         {
    1937             :                 [escorter switchAITo:autoAI];
    1938             :         }
    1939             : 
    1940             :         [escorter setGroup:escortGroup];
    1941             :         [escorter setOwner:self];       // mark self as group leader
    1942             : 
    1943             :         
    1944             :         if ([self status] == STATUS_DOCKED)
    1945             :         {
    1946             :                 [[self owner] addShipToLaunchQueue:escorter withPriority:NO];
    1947             :         }
    1948             :         else
    1949             :         {
    1950             :                 [UNIVERSE addEntity:escorter];  // STATUS_IN_FLIGHT, AI state GLOBAL
    1951             :                 [escortAI setState:@"FLYING_ESCORT"]; // Begin escort flight. (If the AI doesn't define FLYING_ESCORT, this has no effect.)
    1952             :                 [escorter doScriptEvent:OOJSID("spawnedAsEscort") withArgument:self];
    1953             :         }
    1954             :         
    1955             :         if([escorter heatInsulation] < [self heatInsulation]) [escorter setHeatInsulation:[self heatInsulation]]; // give escorts same protection as mother.
    1956             :         if(([escorter maxFlightSpeed] < cruiseSpeed) && ([escorter maxFlightSpeed] > cruiseSpeed * 0.3)) 
    1957             :                 cruiseSpeed = [escorter maxFlightSpeed] * 0.99;  // adapt patrolSpeed to the slowest escort but ignore the very slow ones.
    1958             :                 
    1959             :                 
    1960             :         if (bounty)
    1961             :         {
    1962             :                 int extra = 1 | (ranrot_rand() & 15);
    1963             :                 // if mothership is offender, make sure escorter is too.
    1964             :                 [escorter markAsOffender:extra withReason:kOOLegalStatusReasonSetup];
    1965             :         }
    1966             :         else
    1967             :         {
    1968             :                 // otherwise force the escort to be clean
    1969             :                 [escorter setBounty:0 withReason:kOOLegalStatusReasonSetup];
    1970             :         }
    1971             :         
    1972             : }
    1973             : 
    1974             : - (NSString *)shipDataKey
    1975             : {
    1976             :         return _shipKey;
    1977             : }
    1978             : 
    1979             : 
    1980             : - (NSString *)shipDataKeyAutoRole
    1981             : {
    1982             :         return [[[NSString alloc] initWithFormat:@"[%@]",[self shipDataKey]] autorelease];
    1983             : }
    1984             : 
    1985             : 
    1986             : - (void)setShipDataKey:(NSString *)key
    1987             : {
    1988             :         DESTROY(_shipKey);
    1989             :         _shipKey = [key copy];
    1990             : }
    1991             : 
    1992             : 
    1993             : - (NSDictionary *)shipInfoDictionary
    1994             : {
    1995             :         return shipinfoDictionary;
    1996             : }
    1997             : 
    1998             : 
    1999           0 : #define MAKE_VECTOR_ARRAY(v) [[[OONativeVector alloc] initWithVector:v] autorelease]
    2000             : 
    2001             : - (NSArray *) getWeaponOffsetFrom:(NSDictionary *)dict withKey:(NSString *)key inMode:(NSString *)mode
    2002             : {
    2003             :         Vector offset;
    2004             :         if ([mode isEqualToString:@"single"])
    2005             :         {
    2006             :                 offset = vector_multiply_scalar([dict oo_vectorForKey:key defaultValue:kZeroVector],_scaleFactor);
    2007             :                 return [NSArray arrayWithObject:MAKE_VECTOR_ARRAY(offset)];
    2008             :         }
    2009             :         else
    2010             :         {
    2011             :                 NSArray *offsets = [dict oo_arrayForKey:key defaultValue:nil];
    2012             :                 if (offsets == nil) {
    2013             :                         offset = kZeroVector;
    2014             :                         return [NSArray arrayWithObject:MAKE_VECTOR_ARRAY(offset)];
    2015             :                 }
    2016             :                 NSMutableArray *output = [NSMutableArray arrayWithCapacity:[offsets count]];
    2017             :                 NSUInteger i;
    2018             :                 for (i=0;i<[offsets count];i++) {
    2019             :                         offset = vector_multiply_scalar([offsets oo_vectorAtIndex:i defaultValue:kZeroVector],_scaleFactor);
    2020             :                         [output addObject:MAKE_VECTOR_ARRAY(offset)];
    2021             :                 }
    2022             :                 return [NSArray arrayWithArray:output];
    2023             :         }
    2024             : }
    2025             : 
    2026             : 
    2027             : - (NSArray *) aftWeaponOffset
    2028             : {
    2029             :         return aftWeaponOffset;
    2030             : }
    2031             : 
    2032             : 
    2033             : - (NSArray *) forwardWeaponOffset
    2034             : {
    2035             :         return forwardWeaponOffset;
    2036             : }
    2037             : 
    2038             : 
    2039             : - (NSArray *) portWeaponOffset
    2040             : {
    2041             :         return portWeaponOffset;
    2042             : }
    2043             : 
    2044             : 
    2045             : - (NSArray *) starboardWeaponOffset
    2046             : {
    2047             :         return starboardWeaponOffset;
    2048             : }
    2049             : 
    2050             : 
    2051             : - (BOOL)isFrangible
    2052             : {
    2053             :         return isFrangible;
    2054             : }
    2055             : 
    2056             : 
    2057             : - (BOOL) suppressFlightNotifications
    2058             : {
    2059             :         return suppressAegisMessages;
    2060             : }
    2061             : 
    2062             : 
    2063           0 : - (OOScanClass) scanClass
    2064             : {
    2065             :         if (cloaking_device_active)  return CLASS_NO_DRAW;
    2066             :         return scanClass;
    2067             : }
    2068             : 
    2069             : //////////////////////////////////////////////
    2070             : 
    2071           0 : - (BOOL) canCollide
    2072             : {
    2073             :         int status = [self status];
    2074             :         if (status == STATUS_COCKPIT_DISPLAY || status == STATUS_DEAD || status == STATUS_BEING_SCOOPED)
    2075             :         {       
    2076             :                 return NO;
    2077             :         }
    2078             : 
    2079             :         if (isWreckage)
    2080             :         {
    2081             :                 // wreckage won't collide
    2082             :                 return NO;
    2083             :         }
    2084             :         
    2085             :         if (isMissile && [self shotTime] < 0.25) // not yet fused
    2086             :         {
    2087             :                 return NO;
    2088             :         }
    2089             :         
    2090             :         return YES;
    2091             : }
    2092             : 
    2093           0 : ShipEntity* doOctreesCollide(ShipEntity* prime, ShipEntity* other)
    2094             : {
    2095             :         // octree check
    2096             :         Octree          *prime_octree = prime->octree;
    2097             :         Octree          *other_octree = other->octree;
    2098             :         
    2099             :         HPVector                prime_position = [prime absolutePositionForSubentity];
    2100             :         Triangle        prime_ijk = [prime absoluteIJKForSubentity];
    2101             :         HPVector                other_position = [other absolutePositionForSubentity];
    2102             :         Triangle        other_ijk = [other absoluteIJKForSubentity];
    2103             : 
    2104             :         Vector          relative_position_of_other = resolveVectorInIJK(HPVectorToVector(HPvector_between(prime_position, other_position)), prime_ijk);
    2105             :         Triangle        relative_ijk_of_other;
    2106             :         relative_ijk_of_other.v[0] = resolveVectorInIJK(other_ijk.v[0], prime_ijk);
    2107             :         relative_ijk_of_other.v[1] = resolveVectorInIJK(other_ijk.v[1], prime_ijk);
    2108             :         relative_ijk_of_other.v[2] = resolveVectorInIJK(other_ijk.v[2], prime_ijk);
    2109             :         
    2110             :         // check hull octree against other hull octree
    2111             :         if ([prime_octree isHitByOctree:other_octree
    2112             :                                                  withOrigin:relative_position_of_other
    2113             :                                                          andIJK:relative_ijk_of_other])
    2114             :         {
    2115             :                 return other;
    2116             :         }
    2117             :         
    2118             :         // check prime subentities against the other's hull
    2119             :         NSArray *prime_subs = prime->subEntities;
    2120             :         if (prime_subs)
    2121             :         {
    2122             :                 NSUInteger i, n_subs = [prime_subs count];
    2123             :                 for (i = 0; i < n_subs; i++)
    2124             :                 {
    2125             :                         Entity* se = [prime_subs objectAtIndex:i];
    2126             :                         if ([se isShip] && [se canCollide] && doOctreesCollide((ShipEntity*)se, other))
    2127             :                                 return other;
    2128             :                 }
    2129             :         }
    2130             : 
    2131             :         // check prime hull against the other's subentities
    2132             :         NSArray *other_subs = other->subEntities;
    2133             :         if (other_subs)
    2134             :         {
    2135             :                 NSUInteger i, n_subs = [other_subs count];
    2136             :                 for (i = 0; i < n_subs; i++)
    2137             :                 {
    2138             :                         Entity* se = [other_subs objectAtIndex:i];
    2139             :                         if ([se isShip] && [se canCollide] && doOctreesCollide(prime, (ShipEntity*)se))
    2140             :                                 return (ShipEntity*)se;
    2141             :                 }
    2142             :         }
    2143             :         
    2144             :         // check prime subenties against the other's subentities
    2145             :         if ((prime_subs)&&(other_subs))
    2146             :         {
    2147             :                 NSUInteger i, n_osubs = [other_subs count];
    2148             :                 for (i = 0; i < n_osubs; i++)
    2149             :                 {
    2150             :                         Entity* oe = [other_subs objectAtIndex:i];
    2151             :                         if ([oe isShip] && [oe canCollide])
    2152             :                         {
    2153             :                                 NSUInteger j, n_psubs = [prime_subs count];
    2154             :                                 for (j = 0; j <  n_psubs; j++)
    2155             :                                 {
    2156             :                                         Entity* pe = [prime_subs objectAtIndex:j];
    2157             :                                         if ([pe isShip] && [pe canCollide] && doOctreesCollide((ShipEntity*)pe, (ShipEntity*)oe))
    2158             :                                                 return (ShipEntity*)oe;
    2159             :                                 }
    2160             :                         }
    2161             :                 }
    2162             :         }
    2163             : 
    2164             :         // fall through => no collision
    2165             :         return nil;
    2166             : }
    2167             : 
    2168             : 
    2169           0 : - (BOOL) checkCloseCollisionWith:(Entity *)other
    2170             : {
    2171             :         if (other == nil)  return NO;
    2172             :         if ([collidingEntities containsObject:other])  return NO;       // we know about this already!
    2173             :         
    2174             :         ShipEntity *otherShip = nil;
    2175             :         if ([other isShip])  otherShip = (ShipEntity *)other;
    2176             :         
    2177             :         if ([self canScoop:otherShip])  return YES;     // quick test - could this improve scooping for small ships? I think so!
    2178             :         
    2179             :         if (otherShip != nil && trackCloseContacts)
    2180             :         {
    2181             :                 // in update we check if close contacts have gone out of touch range (origin within our collision_radius)
    2182             :                 // here we check if something has come within that range
    2183             :                 HPVector                        otherPos = [otherShip position];
    2184             :                 OOUniversalID   otherID = [otherShip universalID];
    2185             :                 NSString                *other_key = [NSString stringWithFormat:@"%d", otherID];
    2186             :                 
    2187             :                 if (![closeContactsInfo objectForKey:other_key] &&
    2188             :                         HPdistance2(position, otherPos) < collision_radius * collision_radius)
    2189             :                 {
    2190             :                         // calculate position with respect to our own position and orientation
    2191             :                         Vector  dpos = HPVectorToVector(HPvector_between(position, otherPos));
    2192             :                         Vector  rpos = make_vector(dot_product(dpos, v_right), dot_product(dpos, v_up), dot_product(dpos, v_forward));
    2193             :                         [closeContactsInfo setObject:[NSString stringWithFormat:@"%f %f %f", rpos.x, rpos.y, rpos.z] forKey: other_key];
    2194             :                         
    2195             :                         // send AI a message about the touch
    2196             :                         OOWeakReference *temp = _primaryTarget;
    2197             :                         _primaryTarget = [otherShip weakRetain];
    2198             :                         [self doScriptEvent:OOJSID("shipCloseContact") withArgument:otherShip andReactToAIMessage:@"CLOSE CONTACT"];
    2199             :                         _primaryTarget = temp;
    2200             :                 }
    2201             :         }
    2202             :         
    2203             :         /* This does not appear to save a significant amount of time in
    2204             :          * most situations. No significant change in frame rate with a
    2205             :          * 350-segment planetary ring at 1400 collision candidates, even
    2206             :          * on old hardware. There are perhaps situations in which it could
    2207             :          * be a significant optimisation, but those are likely to also be
    2208             :          * the situations where the effect of adding hundreds of extra
    2209             :          * false-positive collisions leaves the player returning to a
    2210             :          * mess... So, commented out: CIM 21 Jan 2014
    2211             :         if (zero_distance > CLOSE_COLLISION_CHECK_MAX_RANGE2)        // don't work too hard on entities that are far from the player
    2212             :         return YES; 
    2213             :         */
    2214             :         
    2215             :         if (otherShip != nil)
    2216             :         {
    2217             :                 // check hull octree versus other hull octree
    2218             :                 collider = doOctreesCollide(self, otherShip);
    2219             :                 return (collider != nil);
    2220             :         }
    2221             :         
    2222             :         // default at this stage is to say YES they've collided!
    2223             :         collider = other;
    2224             :         return YES;
    2225             : }
    2226             : 
    2227             : 
    2228             : - (BoundingBox)findSubentityBoundingBox
    2229             : {
    2230             :         return [[self mesh] findSubentityBoundingBoxWithPosition:HPVectorToVector(position) rotMatrix:rotMatrix];
    2231             : }
    2232             : 
    2233             : 
    2234             : - (Triangle) absoluteIJKForSubentity
    2235             : {
    2236             :         Triangle        result = {{ kBasisXVector, kBasisYVector, kBasisZVector }};
    2237             :         Entity          *last = nil;
    2238             :         Entity          *father = self;
    2239             :         OOMatrix        r_mat;
    2240             :         
    2241             :         while ((father)&&(father != last) && (father != NO_TARGET))
    2242             :         {
    2243             :                 r_mat = [father drawRotationMatrix];
    2244             :                 result.v[0] = OOVectorMultiplyMatrix(result.v[0], r_mat);
    2245             :                 result.v[1] = OOVectorMultiplyMatrix(result.v[1], r_mat);
    2246             :                 result.v[2] = OOVectorMultiplyMatrix(result.v[2], r_mat);
    2247             :                 
    2248             :                 last = father;
    2249             :                 if (![last isSubEntity]) break;
    2250             :                 father = [father owner];
    2251             :         }
    2252             :         return result;
    2253             : }
    2254             : 
    2255             : 
    2256           0 : - (void) addSubentityToCollisionRadius:(Entity<OOSubEntity> *)subent
    2257             : {
    2258             :         if (!subent)  return;
    2259             :         
    2260             :         double distance = HPmagnitude([subent position]) + [subent findCollisionRadius];
    2261             :         if ([subent isKindOfClass:[ShipEntity class]])  // Solid subentity
    2262             :         {
    2263             :                 if (distance > collision_radius)
    2264             :                 {
    2265             :                         collision_radius = distance;
    2266             :                 }
    2267             :                 
    2268             :                 mass += [subent mass];
    2269             :         }
    2270             :         if (distance > _profileRadius)
    2271             :         {
    2272             :                 _profileRadius = distance;
    2273             :         }
    2274             : }
    2275             : 
    2276             : 
    2277           0 : - (ShipEntity *) launchPodWithCrew:(NSArray *)podCrew
    2278             : {
    2279             :         ShipEntity *pod = nil;
    2280             :         
    2281             :         pod = [UNIVERSE newShipWithRole:[shipinfoDictionary oo_stringForKey:@"escape_pod_role"]];     // or nil
    2282             :         if (!pod)
    2283             :         {
    2284             :                 //      _role not defined? it might have _model defined;
    2285             :                 pod = [UNIVERSE newShipWithRole:[shipinfoDictionary oo_stringForKey:@"escape_pod_model" defaultValue:@"escape-capsule"]];
    2286             :                 if (!pod)
    2287             :                 {
    2288             :                         pod = [UNIVERSE newShipWithRole:@"escape-capsule"];
    2289             :                         OOLog(@"shipEntity.noEscapePod", @"Ship %@ has no correct escape_pod_role defined. Now using default capsule.", self);
    2290             :                 }
    2291             :         }
    2292             :         
    2293             :         if (pod)
    2294             :         {
    2295             :                 [pod setOwner:self];
    2296             :                 [pod setTemperature:[self randomEjectaTemperatureWithMaxFactor:0.9]];
    2297             :                 [pod setCommodity:@"slaves" andAmount:1];
    2298             :                 [pod setCrew:podCrew];
    2299             :                 [pod switchAITo:@"oolite-shuttleAI.js"];
    2300             :                 [self dumpItem:pod];    // CLASS_CARGO, STATUS_IN_FLIGHT, AI state GLOBAL
    2301             :                 [pod release]; //release
    2302             :         }
    2303             :         
    2304             :         return pod;
    2305             : }
    2306             : 
    2307             : 
    2308           0 : - (BOOL) validForAddToUniverse
    2309             : {
    2310             :         if (shipinfoDictionary == nil)
    2311             :         {
    2312             :                 OOLog(@"shipEntity.notDict", @"Ship %@ was not set up from dictionary.", self);
    2313             :                 return NO;
    2314             :         }
    2315             :         return [super validForAddToUniverse];
    2316             : }
    2317             : 
    2318             : 
    2319           0 : - (void) update:(OOTimeDelta)delta_t
    2320             : {
    2321             :         if (shipinfoDictionary == nil)
    2322             :         {
    2323             :                 OOLog(@"shipEntity.notDict", @"Ship %@ was not set up from dictionary.", self);
    2324             :                 [UNIVERSE removeEntity:self];
    2325             :                 return;
    2326             :         }
    2327             :         
    2328             :         if (!isfinite(maxFlightSpeed))
    2329             :         {
    2330             :                 OOLog(@"ship.sanityCheck.failed", @"Ship %@ %@ infinite top speed, clamped to 300.", self, @"had");
    2331             :                 maxFlightSpeed = 300;
    2332             :         }
    2333             : 
    2334             :         bool isSubEnt = [self isSubEntity];
    2335             : 
    2336             :         if (isDemoShip)
    2337             :         {
    2338             :                 if (demoRate > 0)
    2339             :                 {
    2340             :                         OOScalar cos1 = cos(M_PI * ([UNIVERSE getTime] - demoStartTime) * demoRate / 11);
    2341             :                         OOScalar sin1 = sin(M_PI * ([UNIVERSE getTime] - demoStartTime) * demoRate / 11);
    2342             :                         OOScalar cos2 = cos(-M_PI * ([UNIVERSE getTime] - demoStartTime) * demoRate / 15);
    2343             :                         OOScalar sin2 = sin(-M_PI * ([UNIVERSE getTime] - demoStartTime) * demoRate / 15);
    2344             :                         Quaternion q1 = make_quaternion(cos1, sin1*sqrt(3)/2, sin1/2, 0);
    2345             :                         Quaternion q2 = make_quaternion(cos2, -sin2*sqrt(4)/sqrt(5), 0, sin2*sqrt(1)/sqrt(5));
    2346             :                         [self setOrientation: quaternion_multiply(q2, quaternion_multiply(q1, demoStartOrientation))];
    2347             :                 }
    2348             : 
    2349             :                 [super update:delta_t];
    2350             :                 if ([self subEntityCount] > 0)
    2351             :                 {
    2352             :                         // only copy the subent array if there are subentities
    2353             :                         ShipEntity *se = nil;
    2354             :                         foreach (se, [self subEntities])
    2355             :                         {
    2356             :                                 [se update:delta_t];
    2357             :                                 if ([se isShip])
    2358             :                                 {
    2359             :                                         BoundingBox sebb = [se findSubentityBoundingBox];
    2360             :                                         bounding_box_add_vector(&totalBoundingBox, sebb.max);
    2361             :                                         bounding_box_add_vector(&totalBoundingBox, sebb.min);
    2362             :                                 }
    2363             :                         }
    2364             :                 }
    2365             :                 return;
    2366             :         }
    2367             : 
    2368             : 
    2369             :         if (!isSubEnt)
    2370             :         {
    2371             :                 if (scanClass == CLASS_NOT_SET)
    2372             :                 {
    2373             :                         scanClass = CLASS_NEUTRAL;
    2374             :                         OOLog(@"ship.sanityCheck.failed", @"Ship %@ %@ with scanClass CLASS_NOT_SET; forced to CLASS_NEUTRAL.", self, [self primaryRole]);
    2375             :                 }
    2376             : 
    2377             :                 [self updateTrackingCurve];
    2378             : 
    2379             :                 //
    2380             :                 // deal with collisions
    2381             :                 //
    2382             :                 [self manageCollisions];
    2383             : 
    2384             :     // subentity collisions managed via parent entity
    2385             :         
    2386             :                 //
    2387             :                 // reset any inadvertant legal mishaps
    2388             :                 //
    2389             :                 if (scanClass == CLASS_POLICE)
    2390             :                 {
    2391             :                         if (bounty > 0)
    2392             :                         {
    2393             :                                 [self setBounty:0 withReason:kOOLegalStatusReasonPoliceAreClean];
    2394             :                         }
    2395             :                         ShipEntity* target = [self primaryTarget];
    2396             :                         if ((target)&&([target scanClass] == CLASS_POLICE))
    2397             :                         {
    2398             :                                 [self noteLostTarget];
    2399             :                         }
    2400             :                 }
    2401             :                 
    2402             :                 if (trackCloseContacts)
    2403             :                 {
    2404             :                         // in checkCloseCollisionWith: we check if some thing has come within touch range (origin within our collision_radius)
    2405             :                         // here we check if it has gone outside that range
    2406             :                         NSString *other_key = nil;
    2407             : 
    2408             :                         // create a temp copy to iterate over, since we may want to
    2409             :                         // change the original
    2410             :                         NSDictionary *closeContactsTemp = [[NSDictionary alloc] initWithDictionary:closeContactsInfo];
    2411             :                         foreachkey (other_key, closeContactsTemp)
    2412             :                         {
    2413             :                                 ShipEntity* other = [UNIVERSE entityForUniversalID:[other_key intValue]];
    2414             :                                 if ((other != nil) && (other->isShip))
    2415             :                                 {
    2416             :                                         if (HPdistance2(position, other->position) > collision_radius * collision_radius) // moved beyond our sphere!
    2417             :                                         {
    2418             :                                                 // calculate position with respect to our own position and orientation
    2419             :                                                 Vector  dpos = HPVectorToVector(HPvector_between(position, other->position));
    2420             :                                                 Vector  pos1 = make_vector(dot_product(dpos, v_right), dot_product(dpos, v_up), dot_product(dpos, v_forward));
    2421             :                                                 Vector  pos0 = {0, 0, 0};
    2422             :                                                 ScanVectorFromString([closeContactsInfo objectForKey: other_key], &pos0);
    2423             :                                                 // send AI messages about the contact
    2424             :                                                 OOWeakReference *temp = _primaryTarget;
    2425             :                                                 _primaryTarget = [other weakRetain];
    2426             :                                                 if ((pos0.x < 0.0)&&(pos1.x > 0.0))
    2427             :                                                 {
    2428             :                                                         [self doScriptEvent:OOJSID("shipTraversePositiveX") withArgument:other andReactToAIMessage:@"POSITIVE X TRAVERSE"];
    2429             :                                                 }
    2430             :                                                 if ((pos0.x > 0.0)&&(pos1.x < 0.0))
    2431             :                                                 {
    2432             :                                                         [self doScriptEvent:OOJSID("shipTraverseNegativeX") withArgument:other andReactToAIMessage:@"NEGATIVE X TRAVERSE"];
    2433             :                                                 }
    2434             :                                                 if ((pos0.y < 0.0)&&(pos1.y > 0.0))
    2435             :                                                 {
    2436             :                                                         [self doScriptEvent:OOJSID("shipTraversePositiveY") withArgument:other andReactToAIMessage:@"POSITIVE Y TRAVERSE"];
    2437             :                                                 }
    2438             :                                                 if ((pos0.y > 0.0)&&(pos1.y < 0.0))
    2439             :                                                 {
    2440             :                                                         [self doScriptEvent:OOJSID("shipTraverseNegativeY") withArgument:other andReactToAIMessage:@"NEGATIVE Y TRAVERSE"];
    2441             :                                                 }
    2442             :                                                 if ((pos0.z < 0.0)&&(pos1.z > 0.0))
    2443             :                                                 {
    2444             :                                                         [self doScriptEvent:OOJSID("shipTraversePositiveZ") withArgument:other andReactToAIMessage:@"POSITIVE Z TRAVERSE"];
    2445             :                                                 }
    2446             :                                                 if ((pos0.z > 0.0)&&(pos1.z < 0.0))
    2447             :                                                 {
    2448             :                                                         [self doScriptEvent:OOJSID("shipTraverseNegativeZ") withArgument:other andReactToAIMessage:@"NEGATIVE Z TRAVERSE"];
    2449             :                                                 }
    2450             :                                                 _primaryTarget = temp;
    2451             :                                                 [closeContactsInfo removeObjectForKey: other_key];
    2452             :                                         }
    2453             :                                 }
    2454             :                                 else
    2455             :                                 {
    2456             :                                         [closeContactsInfo removeObjectForKey: other_key];
    2457             :                                 }
    2458             :                         }
    2459             :                         [closeContactsTemp release];
    2460             :                 } // end if trackCloseContacts
    2461             : 
    2462             :         } // end if !isSubEntity
    2463             : 
    2464             : 
    2465             : #ifndef NDEBUG
    2466             :         // DEBUGGING
    2467             :         if (reportAIMessages && (debugLastBehaviour != behaviour))
    2468             :         {
    2469             :                 OOLog(kOOLogEntityBehaviourChanged, @"%@ behaviour is now %@", self, OOStringFromBehaviour(behaviour));
    2470             :                 debugLastBehaviour = behaviour;
    2471             :         }
    2472             : #endif
    2473             :         
    2474             :         // cool all weapons.
    2475             :         weapon_temp = fmaxf(weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f);
    2476             :         forward_weapon_temp = fmaxf(forward_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f);
    2477             :         aft_weapon_temp = fmaxf(aft_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f);
    2478             :         port_weapon_temp = fmaxf(port_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f);
    2479             :         starboard_weapon_temp = fmaxf(starboard_weapon_temp - (float)(WEAPON_COOLING_FACTOR * delta_t), 0.0f);
    2480             :         
    2481             :         // update time between shots
    2482             :         shot_time += delta_t;
    2483             : 
    2484             :         // handle radio message effects
    2485             :         if (messageTime > 0.0)
    2486             :         {
    2487             :                 messageTime -= delta_t;
    2488             :                 if (messageTime < 0.0)  messageTime = 0.0;
    2489             :         }
    2490             :         
    2491             :         // temperature factors
    2492             :         if(!isSubEnt)
    2493             :         {
    2494             :                 double external_temp = 0.0;
    2495             :                 OOSunEntity *sun = [UNIVERSE sun];
    2496             :                 if (sun != nil)
    2497             :                 {
    2498             :                         // set the ambient temperature here
    2499             :                         double  sun_zd = HPdistance2(position, [sun position]); // square of distance
    2500             :                         double  sun_cr = sun->collision_radius;
    2501             :                         double  alt1 = sun_cr * sun_cr / sun_zd;
    2502             :                         external_temp = SUN_TEMPERATURE * alt1;
    2503             :                         if ([sun goneNova])  external_temp *= 100;
    2504             : 
    2505             :                         if ([self hasFuelScoop] && alt1 > 0.75 && [self fuel] < [self fuelCapacity])
    2506             :                         {
    2507             :                                 fuel_accumulator += (float)(delta_t * flightSpeed * 0.010 / [self fuelChargeRate]);
    2508             :                         // are we fast enough to collect any fuel?
    2509             :                                 while (fuel_accumulator > 1.0f)
    2510             :                                 {
    2511             :                                         [self setFuel:[self fuel] + 1];
    2512             :                                         fuel_accumulator -= 1.0f;
    2513             :                                         [self doScriptEvent:OOJSID("shipScoopedFuel")];
    2514             :                                 }
    2515             :                         }
    2516             :                 }
    2517             : 
    2518             :                 // work on the ship temperature
    2519             :                 //
    2520             :                 float heatThreshold = [self heatInsulation] * 100.0f;
    2521             :                 if (external_temp > heatThreshold &&  external_temp > ship_temperature)
    2522             :                         ship_temperature += (external_temp - ship_temperature) * delta_t * SHIP_INSULATION_FACTOR / [self heatInsulation];
    2523             :                 else
    2524             :                 {
    2525             :                         if (ship_temperature > SHIP_MIN_CABIN_TEMP)
    2526             :                         {
    2527             :                                 ship_temperature += (external_temp - heatThreshold - ship_temperature) * delta_t * SHIP_COOLING_FACTOR / [self heatInsulation];
    2528             :                                 if (ship_temperature < SHIP_MIN_CABIN_TEMP) ship_temperature = SHIP_MIN_CABIN_TEMP;
    2529             :                         }
    2530             :                 }
    2531             :         }
    2532             :         else //subents
    2533             :         {
    2534             :                 ship_temperature = [[self owner] temperature];
    2535             :         }
    2536             : 
    2537             :         if (ship_temperature > SHIP_MAX_CABIN_TEMP)
    2538             :                 [self takeHeatDamage: delta_t * ship_temperature];
    2539             : 
    2540             :         // are we burning due to low energy
    2541             :         if ((energy < maxEnergy * 0.20)&&_showDamage)        // prevents asteroid etc. from burning
    2542             :                 throw_sparks = YES;
    2543             :         
    2544             :         // burning effects
    2545             :         if (throw_sparks)
    2546             :         {
    2547             :                 next_spark_time -= delta_t;
    2548             :                 if (next_spark_time < 0.0)
    2549             :                 {
    2550             :                         [self throwSparks];
    2551             :                         throw_sparks = NO;      // until triggered again
    2552             :                 }
    2553             :         }
    2554             :         
    2555             :         if (!isSubEnt)
    2556             :         {
    2557             : 
    2558             :                 // cloaking device
    2559             :                 if ([self hasCloakingDevice])
    2560             :                 {
    2561             :                         if (cloaking_device_active)
    2562             :                         {
    2563             :                                 energy -= delta_t * CLOAKING_DEVICE_ENERGY_RATE;
    2564             :                                 if (energy < CLOAKING_DEVICE_MIN_ENERGY)
    2565             :                                 {  
    2566             :                                         [self deactivateCloakingDevice];
    2567             :                                         if (energy < 0) energy = 0;
    2568             :                                 }
    2569             :                         }
    2570             :                 }
    2571             : 
    2572             :                 // military_jammer
    2573             :                 if ([self hasMilitaryJammer])
    2574             :                 {
    2575             :                         if (military_jammer_active)
    2576             :                         {
    2577             :                                 energy -= delta_t * MILITARY_JAMMER_ENERGY_RATE;
    2578             :                                 if (energy < MILITARY_JAMMER_MIN_ENERGY)
    2579             :                                 {
    2580             :                                         military_jammer_active = NO;
    2581             :                                         if (energy < 0) energy = 0;
    2582             :                                 }
    2583             :                         }
    2584             :                         else
    2585             :                         {
    2586             :                                 if (energy > 1.5 * MILITARY_JAMMER_MIN_ENERGY)
    2587             :                                         military_jammer_active = YES;
    2588             :                         }
    2589             :                 }
    2590             : 
    2591             :         // check outside factors
    2592             :                 /* aegis checks are expensive, so only do them once every km or so of flight
    2593             :                  * unlikely to be important otherwise. (every 100m if already close to
    2594             :                  * planet, to watch for surface)
    2595             : 
    2596             :                  * if have non-zero inertial velocity, need to check every frame,
    2597             :                  * as distanceTravelled does not include this component - CIM */
    2598             :                 if (_nextAegisCheck < distanceTravelled || !vector_equal([super velocity],kZeroVector))
    2599             :                 {
    2600             :                         aegis_status = [self checkForAegis];   // is a station or something nearby??
    2601             :                         if (aegis_status == AEGIS_NONE)
    2602             :                         {
    2603             :                                 // in open space: check every km
    2604             :                                 _nextAegisCheck = distanceTravelled + 1000.0;
    2605             :                         }
    2606             :                         else
    2607             :                         {
    2608             :                                 // near planets: check every 100m
    2609             :                                 _nextAegisCheck = distanceTravelled + 100.0;
    2610             :                         }
    2611             :                 }
    2612             :         } // end if !isSubEntity
    2613             : 
    2614             :         // scripting
    2615             :         if (!haveExecutedSpawnAction)
    2616             :         {
    2617             :                 // When crashing into a boulder, STATUS_LAUNCHING is sometimes skipped on scooping the resulting splinters.
    2618             :                 OOEntityStatus status = [self status];
    2619             :                 if (script != nil && (status == STATUS_IN_FLIGHT ||
    2620             :                                                           status == STATUS_LAUNCHING ||
    2621             :                                                           status == STATUS_BEING_SCOOPED ||
    2622             :                                                           (status == STATUS_ACTIVE && self == [UNIVERSE station])
    2623             :                                                           ))
    2624             :                 {
    2625             :                         [PLAYER setScriptTarget:self];
    2626             :                         [self doScriptEvent:OOJSID("shipSpawned")];
    2627             :                         if ([self status] != STATUS_DEAD)  [PLAYER doScriptEvent:OOJSID("shipSpawned") withArgument:self];
    2628             :                 }
    2629             :                 haveExecutedSpawnAction = YES;
    2630             :         }
    2631             :         /* No point in starting the AI if still launching */
    2632             :         if (!haveStartedJSAI && [self status] != STATUS_LAUNCHING)
    2633             :         {
    2634             :                 haveStartedJSAI = YES;
    2635             :                 [self doScriptEvent:OOJSID("aiStarted")];
    2636             :         }
    2637             : 
    2638             :         // behaviours according to status and behaviour
    2639             :         //
    2640             :         if ([self status] == STATUS_LAUNCHING)
    2641             :         {
    2642             :                 if ([UNIVERSE getTime] > launch_time + launch_delay)         // move for while before thinking
    2643             :                 {
    2644             :                         StationEntity *stationLaunchedFrom = [UNIVERSE nearestEntityMatchingPredicate:IsStationPredicate parameter:NULL relativeToEntity:self];
    2645             :                         [self setStatus:STATUS_IN_FLIGHT];
    2646             :                         // awaken JS-based AIs
    2647             :                         haveStartedJSAI = YES;
    2648             :                         [self doScriptEvent:OOJSID("aiStarted")];
    2649             :                         [self doScriptEvent:OOJSID("shipLaunchedFromStation") withArgument:stationLaunchedFrom];
    2650             :                         [shipAI reactToMessage:@"LAUNCHED OKAY" context:@"launched"];
    2651             :                 }
    2652             :                 else
    2653             :                 {
    2654             :                         // ignore behaviour just keep moving...
    2655             :                         flightYaw = 0.0;
    2656             :                         [self applyAttitudeChanges:delta_t];
    2657             :                         [self applyThrust:delta_t];
    2658             :                         if (energy < maxEnergy)
    2659             :                         {
    2660             :                                 energy += energy_recharge_rate * delta_t;
    2661             :                                 if (energy > maxEnergy)
    2662             :                                 {
    2663             :                                         energy = maxEnergy;
    2664             :                                         [self doScriptEvent:OOJSID("shipEnergyBecameFull")];
    2665             :                                         [shipAI message:@"ENERGY_FULL"];
    2666             :                                 }
    2667             :                         }
    2668             :                         
    2669             :                         if ([self subEntityCount] > 0)
    2670             :                         {
    2671             :                                 // only copy the subent array if there are subentities
    2672             :                                 ShipEntity *se = nil;
    2673             :                                 foreach (se, [self subEntities])
    2674             :                                 {
    2675             :                                         [se update:delta_t];
    2676             :                                 }
    2677             :                         }
    2678             :                         // super update
    2679             :                         [super update:delta_t];
    2680             : 
    2681             :                         return;
    2682             :                 }
    2683             :         }
    2684             :         //
    2685             :         // double check scooped behaviour
    2686             :         //
    2687             :         if ([self status] == STATUS_BEING_SCOOPED)
    2688             :         {
    2689             :                 //if we are being tractored, but we have no owner, then we have a problem
    2690             :                 if (behaviour != BEHAVIOUR_TRACTORED  || [self owner] == nil || [self owner] == self || [self owner] == NO_TARGET)
    2691             :                 {
    2692             :                         // escaped tractor beam
    2693             :                         [self setStatus:STATUS_IN_FLIGHT];      // should correct 'uncollidable objects' bug
    2694             :                         behaviour = BEHAVIOUR_IDLE;
    2695             :                         frustration = 0.0;
    2696             :                         [self setOwner:self];
    2697             :                         [shipAI exitStateMachineWithMessage:nil];  // Escapepods and others should continue their old AI here.
    2698             :                 }
    2699             :         }
    2700             :         
    2701             :         if ([self status] == STATUS_COCKPIT_DISPLAY)
    2702             :         {
    2703             :                 flightYaw = 0.0;
    2704             :                 [self applyAttitudeChanges:delta_t];
    2705             :                 GLfloat range2 = 0.1 * HPdistance2(position, _destination) / (collision_radius * collision_radius);
    2706             :                 if ((range2 > 1.0)||(velocity.z > 0.0))   range2 = 1.0;
    2707             :                 position = HPvector_add(position, vectorToHPVector(vector_multiply_scalar(velocity, range2 * delta_t)));
    2708             :         }
    2709             :         else
    2710             :         {
    2711             :                 [self processBehaviour:delta_t];
    2712             : 
    2713             :                 // manage energy
    2714             :                 if (energy < maxEnergy)
    2715             :                 {
    2716             :                         energy += energy_recharge_rate * delta_t;
    2717             :                         if (energy > maxEnergy)
    2718             :                         {
    2719             :                                 energy = maxEnergy;
    2720             :                                 [self doScriptEvent:OOJSID("shipEnergyBecameFull")];
    2721             :                                 [shipAI message:@"ENERGY_FULL"];
    2722             :                         }
    2723             :                 }
    2724             :                 
    2725             :                 if (!isSubEnt)
    2726             :                 {
    2727             :                 // update destination position for escorts
    2728             :                         [self refreshEscortPositions];
    2729             :                         if ([self hasEscorts])
    2730             :                         {
    2731             :                                 ShipEntity      *escort = nil;
    2732             :                                 unsigned        i = 0;
    2733             :                                 // Note: works on escortArray rather than escortEnumerator because escorts may be mutated.
    2734             :                                 foreach(escort, [self escortArray])
    2735             :                                 {
    2736             :                                         [escort setEscortDestination:[self coordinatesForEscortPosition:i++]];
    2737             :                                 }
    2738             :                         
    2739             :                                 ShipEntity *leader = [[self escortGroup] leader];
    2740             :                                 if (leader != nil && ([leader scanClass] != [self scanClass])) {
    2741             :                                         OOLog(@"ship.sanityCheck.failed", @"Ship %@ escorting %@ with wrong scanclass!", self, leader);
    2742             :                                         [[self escortGroup] removeShip:self];
    2743             :                                         [self setEscortGroup:nil];
    2744             :                                 }
    2745             :                         }
    2746             :                 }
    2747             :         }
    2748             :         
    2749             :         // rotational velocity
    2750             :         if (!quaternion_equal(subentityRotationalVelocity, kIdentityQuaternion) &&
    2751             :                 !quaternion_equal(subentityRotationalVelocity, kZeroQuaternion))
    2752             :         {
    2753             :                 Quaternion qf = subentityRotationalVelocity;
    2754             :                 qf.w *= (1.0 - delta_t);
    2755             :                 qf.x *= delta_t;
    2756             :                 qf.y *= delta_t;
    2757             :                 qf.z *= delta_t;
    2758             :                 [self setOrientation:quaternion_multiply(qf, orientation)];
    2759             :         }
    2760             :         
    2761             :         //      reset totalBoundingBox
    2762             :         totalBoundingBox = boundingBox;
    2763             :         
    2764             :         // super update
    2765             :         [super update:delta_t];
    2766             : 
    2767             :         // update subentities
    2768             : 
    2769             :         if ([self subEntityCount] > 0)
    2770             :         {
    2771             :                 // only copy the subent array if there are subentities
    2772             :                 ShipEntity *se = nil;
    2773             :                 foreach (se, [self subEntities])
    2774             :                 {
    2775             :                         [se update:delta_t];
    2776             :                         if ([se isShip])
    2777             :                         {
    2778             :                                 BoundingBox sebb = [se findSubentityBoundingBox];
    2779             :                                 bounding_box_add_vector(&totalBoundingBox, sebb.max);
    2780             :                                 bounding_box_add_vector(&totalBoundingBox, sebb.min);
    2781             :                         }
    2782             :                 }
    2783             :         }
    2784             :         
    2785             :         if (aiScriptWakeTime > 0 && [PLAYER clockTimeAdjusted] > aiScriptWakeTime)
    2786             :         {
    2787             :                 aiScriptWakeTime = 0;
    2788             :                 [self doScriptEvent:OOJSID("aiAwoken")];
    2789             :         }
    2790             : }
    2791             : 
    2792             : 
    2793             : - (void) processBehaviour:(OOTimeDelta)delta_t
    2794             : {
    2795             :         BOOL applyThrust = YES;
    2796             :         switch (behaviour)
    2797             :         {
    2798             :         case BEHAVIOUR_TUMBLE :
    2799             :                 [self behaviour_tumble: delta_t];
    2800             :                 break;
    2801             : 
    2802             :         case BEHAVIOUR_STOP_STILL :
    2803             :         case BEHAVIOUR_STATION_KEEPING :
    2804             :                 [self behaviour_stop_still: delta_t];
    2805             :                 break;
    2806             : 
    2807             :         case BEHAVIOUR_IDLE :
    2808             :                 if ([self isSubEntity])
    2809             :                 {
    2810             :                         applyThrust = NO;
    2811             :                 }
    2812             :                 [self behaviour_idle: delta_t];
    2813             :                 break;
    2814             : 
    2815             :         case BEHAVIOUR_TRACTORED :
    2816             :                 [self behaviour_tractored: delta_t];
    2817             :                 break;
    2818             : 
    2819             :         case BEHAVIOUR_TRACK_TARGET :
    2820             :                 [self behaviour_track_target: delta_t];
    2821             :                 break;
    2822             : 
    2823             :         case BEHAVIOUR_INTERCEPT_TARGET :
    2824             :         case BEHAVIOUR_COLLECT_TARGET :
    2825             :                 [self behaviour_intercept_target: delta_t];
    2826             :                 break;
    2827             : 
    2828             :         case BEHAVIOUR_ATTACK_TARGET :
    2829             :                 [self behaviour_attack_target: delta_t];
    2830             :                 break;
    2831             : 
    2832             :         case BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX :
    2833             :         case BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE :
    2834             :                 [self behaviour_fly_to_target_six: delta_t];
    2835             :                 break;
    2836             : 
    2837             :         case BEHAVIOUR_ATTACK_MINING_TARGET :
    2838             :                 [self behaviour_attack_mining_target: delta_t];
    2839             :                 break;
    2840             : 
    2841             :         case BEHAVIOUR_ATTACK_FLY_TO_TARGET :
    2842             :                 [self behaviour_attack_fly_to_target: delta_t];
    2843             :                 break;
    2844             : 
    2845             :         case BEHAVIOUR_ATTACK_FLY_FROM_TARGET :
    2846             :                 [self behaviour_attack_fly_from_target: delta_t];
    2847             :                 break;
    2848             : 
    2849             :         case BEHAVIOUR_ATTACK_BREAK_OFF_TARGET :
    2850             :                 [self behaviour_attack_break_off_target: delta_t];
    2851             :                 break;
    2852             : 
    2853             :         case BEHAVIOUR_ATTACK_SLOW_DOGFIGHT :
    2854             :                 [self behaviour_attack_slow_dogfight: delta_t];
    2855             :                 break;
    2856             : 
    2857             :         case BEHAVIOUR_RUNNING_DEFENSE :
    2858             :                 [self behaviour_running_defense: delta_t];
    2859             :                 break;
    2860             : 
    2861             :         case BEHAVIOUR_ATTACK_BROADSIDE :
    2862             :                 [self behaviour_attack_broadside: delta_t];
    2863             :                 break;
    2864             : 
    2865             :         case BEHAVIOUR_ATTACK_BROADSIDE_LEFT :
    2866             :                 [self behaviour_attack_broadside_left: delta_t];
    2867             :                 break;
    2868             : 
    2869             :         case BEHAVIOUR_ATTACK_BROADSIDE_RIGHT :
    2870             :                 [self behaviour_attack_broadside_right: delta_t];
    2871             :                 break;
    2872             : 
    2873             :         case BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE :
    2874             :                 [self behaviour_close_to_broadside_range: delta_t];
    2875             :                 break;
    2876             : 
    2877             :         case BEHAVIOUR_CLOSE_WITH_TARGET :
    2878             :                 [self behaviour_close_with_target: delta_t];
    2879             :                 break;
    2880             : 
    2881             :         case BEHAVIOUR_ATTACK_SNIPER :
    2882             :                 [self behaviour_attack_sniper: delta_t];
    2883             :                 break;
    2884             : 
    2885             :         case BEHAVIOUR_EVASIVE_ACTION :
    2886             :         case BEHAVIOUR_FLEE_EVASIVE_ACTION :
    2887             :                 [self behaviour_evasive_action: delta_t];
    2888             :                 break;
    2889             : 
    2890             :         case BEHAVIOUR_FLEE_TARGET :
    2891             :                 [self behaviour_flee_target: delta_t];
    2892             :                 break;
    2893             : 
    2894             :         case BEHAVIOUR_FLY_RANGE_FROM_DESTINATION :
    2895             :                 [self behaviour_fly_range_from_destination: delta_t];
    2896             :                 break;
    2897             : 
    2898             :         case BEHAVIOUR_FACE_DESTINATION :
    2899             :                 [self behaviour_face_destination: delta_t];
    2900             :                 break;
    2901             : 
    2902             :         case BEHAVIOUR_LAND_ON_PLANET :
    2903             :                 [self behaviour_land_on_planet: delta_t];
    2904             :                 break;
    2905             :                                 
    2906             :         case BEHAVIOUR_FORMATION_FORM_UP :
    2907             :                 [self behaviour_formation_form_up: delta_t];
    2908             :                 break;
    2909             : 
    2910             :         case BEHAVIOUR_FLY_TO_DESTINATION :
    2911             :                 [self behaviour_fly_to_destination: delta_t];
    2912             :                 break;
    2913             : 
    2914             :         case BEHAVIOUR_FLY_FROM_DESTINATION :
    2915             :         case BEHAVIOUR_FORMATION_BREAK :
    2916             :                 [self behaviour_fly_from_destination: delta_t];
    2917             :                 break;
    2918             : 
    2919             :         case BEHAVIOUR_AVOID_COLLISION :
    2920             :                 [self behaviour_avoid_collision: delta_t];
    2921             :                 break;
    2922             : 
    2923             :         case BEHAVIOUR_TRACK_AS_TURRET :
    2924             :                 applyThrust = NO;
    2925             :                 [self behaviour_track_as_turret: delta_t];
    2926             :                 break;
    2927             : 
    2928             :         case BEHAVIOUR_FLY_THRU_NAVPOINTS :
    2929             :                 [self behaviour_fly_thru_navpoints: delta_t];
    2930             :                 break;
    2931             : 
    2932             :         case BEHAVIOUR_SCRIPTED_AI:
    2933             :         case BEHAVIOUR_SCRIPTED_ATTACK_AI:
    2934             :                 [self behaviour_scripted_ai: delta_t];
    2935             :                 break;
    2936             : 
    2937             :         case BEHAVIOUR_ENERGY_BOMB_COUNTDOWN:
    2938             :                 applyThrust = NO;
    2939             :                 // Do nothing
    2940             :                 break;
    2941             :         }
    2942             : 
    2943             :         // generally the checks above should be turning this *off* for subents
    2944             :         if (applyThrust)
    2945             :         {
    2946             :                 [self applyAttitudeChanges:delta_t];
    2947             :                 [self applyThrust:delta_t];
    2948             :         }
    2949             : }
    2950             : 
    2951             : 
    2952             : // called when behaviour is unable to improve position
    2953           0 : - (void)noteFrustration:(NSString *)context
    2954             : {
    2955             :         [shipAI reactToMessage:@"FRUSTRATED" context:context];
    2956             :         [self doScriptEvent:OOJSID("shipAIFrustrated") withArgument:context];
    2957             : }
    2958             : 
    2959             : 
    2960             : - (void)respondToAttackFrom:(Entity *)from becauseOf:(Entity *)other
    2961             : {
    2962             :         Entity                          *source = nil;
    2963             :         
    2964             :         if ([other isKindOfClass:[ShipEntity class]])
    2965             :         {
    2966             :                 source = other;
    2967             : 
    2968             :                 // JSAIs handle friendly fire themselves
    2969             :                 if (![self hasNewAI])
    2970             :                 {
    2971             :                 
    2972             :                         ShipEntity *hunter = (ShipEntity *)other;
    2973             :                         //if we are in the same group, then we have to be careful about how we handle things
    2974             :                         if ([self isPolice] && [hunter isPolice]) 
    2975             :                         {
    2976             :                                 //police never get into a fight with each other
    2977             :                                 return;
    2978             :                         }
    2979             :                 
    2980             :                         OOShipGroup *group = [self group];
    2981             :                 
    2982             :                         if (group != nil && group == [hunter group]) 
    2983             :                         {
    2984             :                                 //we are in the same group, do we forgive you?
    2985             :                                 //criminals are less likely to forgive
    2986             :                                 if (randf() < (0.8 - (bounty/100))) 
    2987             :                                 {
    2988             :                                         //it was an honest mistake, lets get on with it
    2989             :                                         return;
    2990             :                                 }
    2991             :                         
    2992             :                                 ShipEntity *groupLeader = [group leader];
    2993             :                                 if (hunter == groupLeader)
    2994             :                                 {
    2995             :                                         //oops we were attacked by our leader, desert him
    2996             :                                         [group removeShip:self];
    2997             :                                 }
    2998             :                                 else 
    2999             :                                 {
    3000             :                                         //evict them from our group
    3001             :                                         [group removeShip:hunter];
    3002             :                                 
    3003             :                                         [groupLeader setFoundTarget:other];
    3004             :                                         [groupLeader setPrimaryAggressor:hunter];
    3005             :                                         [groupLeader respondToAttackFrom:from becauseOf:other];
    3006             :                                 }
    3007             :                         }
    3008             :                 }
    3009             :         }
    3010             :         else
    3011             :         {
    3012             :                 source = from;
    3013             :         }       
    3014             :         
    3015             :         [self doScriptEvent:OOJSID("shipBeingAttacked") withArgument:source andReactToAIMessage:@"ATTACKED"];
    3016             :         if ([source isShip]) [(ShipEntity *)source doScriptEvent:OOJSID("shipAttackedOther") withArgument:self];
    3017             : }
    3018             : 
    3019             : 
    3020             : // Equipment
    3021             : 
    3022             : - (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading
    3023             : {
    3024             :         if ([self hasOneEquipmentItem:itemKey includeMissiles:includeWeapons whileLoading:loading])  return YES;
    3025             : 
    3026             :         if (loading) 
    3027             :         {
    3028             :                 NSString *damaged = [itemKey stringByAppendingString:@"_DAMAGED"];
    3029             :                 if ([_equipment containsObject:damaged])  return YES;
    3030             :         }
    3031             : 
    3032             :         if (includeWeapons)
    3033             :         {
    3034             :                 // Check for primary weapon
    3035             :                 OOWeaponType weaponType = OOWeaponTypeFromEquipmentIdentifierStrict(itemKey);
    3036             :                 if (!isWeaponNone(weaponType))
    3037             :                 {
    3038             :                         if ([self hasPrimaryWeapon:weaponType])  return YES;
    3039             :                 }
    3040             :         }
    3041             :         
    3042             :         return NO;
    3043             : }
    3044             : 
    3045             : 
    3046             : - (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeMissiles:(BOOL)includeMissiles whileLoading:(BOOL)loading
    3047             : {
    3048             :         if ([_equipment containsObject:itemKey])  return YES;
    3049             :         
    3050             :         if (loading) 
    3051             :         {
    3052             :                 NSString *damaged = [itemKey stringByAppendingString:@"_DAMAGED"];
    3053             :                 if ([_equipment containsObject:damaged])  return YES;
    3054             :         }
    3055             : 
    3056             :         if (includeMissiles && missiles > 0)
    3057             :         {
    3058             :                 unsigned i;
    3059             :                 if ([itemKey isEqualToString:@"thargon"]) itemKey = @"EQ_THARGON";
    3060             :                 for (i = 0; i < missiles; i++)
    3061             :                 {
    3062             :                         if (missile_list[i] != nil && [[missile_list[i] identifier] isEqualTo:itemKey])  return YES;
    3063             :                 }
    3064             :         }
    3065             :         
    3066             :         return NO;
    3067             : }
    3068             : 
    3069             : 
    3070             : - (BOOL) hasPrimaryWeapon:(OOWeaponType)weaponType
    3071             : {
    3072             :         NSEnumerator                            *subEntEnum = nil;
    3073             :         ShipEntity                                      *subEntity = nil;
    3074             :         
    3075             :         if ([[forward_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
    3076             :                 [[aft_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
    3077             :                 [[port_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
    3078             :                 [[starboard_weapon_type identifier] isEqualToString:[weaponType identifier]])
    3079             :         {
    3080             :                 return YES;
    3081             :         }
    3082             : 
    3083             :         for (subEntEnum = [self shipSubEntityEnumerator]; (subEntity = [subEntEnum nextObject]); )
    3084             :         {
    3085             :                 if ([subEntity hasPrimaryWeapon:weaponType])  return YES;
    3086             :         }
    3087             :         
    3088             :         return NO;
    3089             : }
    3090             : 
    3091             : 
    3092             : - (NSUInteger) countEquipmentItem:(NSString *)eqkey
    3093             : {
    3094             :         NSString *eq = nil;
    3095             :         NSUInteger count = 0;
    3096             :         foreach (eq, _equipment)
    3097             :         {
    3098             :                 if ([eqkey isEqualToString:eq])
    3099             :                 {
    3100             :                         ++count;
    3101             :                 }
    3102             :         }
    3103             :         return count;
    3104             : }
    3105             : 
    3106             : 
    3107             : - (BOOL) hasEquipmentItem:(id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading
    3108             : {
    3109             :         // this method is also used internally to find out if an equipped item is undamaged.
    3110             :         if ([equipmentKeys isKindOfClass:[NSString class]])
    3111             :         {
    3112             :                 return [self hasOneEquipmentItem:equipmentKeys includeWeapons:includeWeapons whileLoading:loading];
    3113             :         }
    3114             :         else
    3115             :         {
    3116             :                 NSParameterAssert([equipmentKeys isKindOfClass:[NSArray class]] || [equipmentKeys isKindOfClass:[NSSet class]]);
    3117             :                 
    3118             :                 id key = nil;
    3119             :                 foreach (key, equipmentKeys)
    3120             :                 {
    3121             :                         if ([self hasOneEquipmentItem:key includeWeapons:includeWeapons whileLoading:loading])  return YES;
    3122             :                 }
    3123             :         }
    3124             :         
    3125             :         return NO;
    3126             : }
    3127             : 
    3128             : 
    3129             : - (BOOL) hasEquipmentItem:(id)equipmentKeys
    3130             : {
    3131             :         return [self hasEquipmentItem:equipmentKeys includeWeapons:NO whileLoading:NO];
    3132             : }
    3133             : 
    3134             : 
    3135             : /* allows OXP equipment to provide core functions (or indeed OXP
    3136             :  * functions, potentially) */
    3137             : - (BOOL) hasEquipmentItemProviding:(NSString *)equipmentType
    3138             : {
    3139             :         NSString *key = nil;
    3140             :         foreach (key, _equipment) {
    3141             :                 if ([key isEqualToString:equipmentType])
    3142             :                 {
    3143             :                         // equipment always provides itself
    3144             :                         return YES;
    3145             :                 }
    3146             :                 else
    3147             :                 {
    3148             :                         OOEquipmentType *et = [OOEquipmentType equipmentTypeWithIdentifier:key];
    3149             :                         if (et != nil && [et provides:equipmentType])
    3150             :                         {
    3151             :                                 return YES;
    3152             :                         }
    3153             :                 }
    3154             :         }
    3155             :         return NO;
    3156             : }
    3157             : 
    3158             : 
    3159             : - (NSString *) equipmentItemProviding:(NSString *)equipmentType
    3160             : {
    3161             :         NSString *key = nil;
    3162             :         foreach (key, _equipment) {
    3163             :                 if ([key isEqualToString:equipmentType])
    3164             :                 {
    3165             :                         // equipment always provides itself
    3166             :                         return [[key copy] autorelease];
    3167             :                 }
    3168             :                 else
    3169             :                 {
    3170             :                         OOEquipmentType *et = [OOEquipmentType equipmentTypeWithIdentifier:key];
    3171             :                         if (et != nil && [et provides:equipmentType])
    3172             :                         {
    3173             :                                 return [[key copy] autorelease];
    3174             :                         }
    3175             :                 }
    3176             :         }
    3177             :         return nil;
    3178             : }
    3179             : 
    3180             : 
    3181             : - (BOOL) hasAllEquipment:(id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading
    3182             : {
    3183             :         NSEnumerator                            *keyEnum = nil;
    3184             :         id                                                      key = nil;
    3185             :         
    3186             :         if (_equipment == nil)  return NO;
    3187             :         
    3188             :         // Make sure it's an array or set, using a single-object set if it's a string.
    3189             :         if ([equipmentKeys isKindOfClass:[NSString class]])  equipmentKeys = [NSArray arrayWithObject:equipmentKeys];
    3190             :         else if (![equipmentKeys isKindOfClass:[NSArray class]] && ![equipmentKeys isKindOfClass:[NSSet class]])  return NO;
    3191             :         
    3192             :         for (keyEnum = [equipmentKeys objectEnumerator]; (key = [keyEnum nextObject]); )
    3193             :         {
    3194             :                 if (![self hasOneEquipmentItem:key includeWeapons:includeWeapons whileLoading:loading])  return NO;
    3195             :         }
    3196             :         
    3197             :         return YES;
    3198             : }
    3199             : 
    3200             : 
    3201             : - (BOOL) hasAllEquipment:(id)equipmentKeys
    3202             : {
    3203             :         return [self hasAllEquipment:equipmentKeys includeWeapons:NO whileLoading:NO];
    3204             : }
    3205             : 
    3206             : 
    3207             : - (BOOL) hasHyperspaceMotor
    3208             : {
    3209             :         return hyperspaceMotorSpinTime >= 0;
    3210             : }
    3211             : 
    3212             : 
    3213             : - (float) hyperspaceSpinTime
    3214             : {
    3215             :         return hyperspaceMotorSpinTime;
    3216             : }
    3217             : 
    3218             : 
    3219             : - (void) setHyperspaceSpinTime:(float)new
    3220             : {
    3221             :         hyperspaceMotorSpinTime = new;
    3222             : }
    3223             : 
    3224             : 
    3225             : - (BOOL) canAddEquipment:(NSString *)equipmentKey inContext:(NSString *)context
    3226             : {
    3227             :         if ([equipmentKey hasSuffix:@"_DAMAGED"])
    3228             :         {
    3229             :                 equipmentKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]];
    3230             :         }
    3231             :         
    3232             :         NSString * lcEquipmentKey = [equipmentKey lowercaseString];
    3233             :         if ([equipmentKey hasSuffix:@"MISSILE"]||[equipmentKey hasSuffix:@"MINE"]||([self isThargoid] && ([lcEquipmentKey hasPrefix:@"thargon"] || [lcEquipmentKey hasSuffix:@"thargon"])))
    3234             :         {
    3235             :                 if (missiles >= max_missiles) return NO;
    3236             :         }
    3237             :         
    3238             :         OOEquipmentType *eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey];
    3239             :         
    3240             :         if (![eqType canCarryMultiple] && [self hasEquipmentItem:equipmentKey])  return NO;
    3241             :         if (![self equipmentValidToAdd:equipmentKey inContext:context])  return NO;
    3242             :         
    3243             :         return YES;
    3244             : }
    3245             : 
    3246             : 
    3247             : - (OOWeaponFacingSet) weaponFacings
    3248             : {
    3249             :         return weapon_facings;
    3250             : }
    3251             : 
    3252             : 
    3253             : - (OOWeaponType) weaponTypeIDForFacing:(OOWeaponFacing)facing strict:(BOOL)strict
    3254             : {
    3255             :         OOWeaponType weaponType = nil;
    3256             : 
    3257             :         if (facing & weapon_facings)
    3258             :         {
    3259             :                 switch (facing)
    3260             :                 {
    3261             :                         case WEAPON_FACING_FORWARD:
    3262             :                                 weaponType = forward_weapon_type;
    3263             :                                 // if no forward weapon, and not carrying out a strict check, see if subentities have forward weapons, return the first one found.
    3264             :                                 if (isWeaponNone(weaponType) && !strict)
    3265             :                                 {
    3266             :                                         NSEnumerator    *subEntEnum = [self shipSubEntityEnumerator];
    3267             :                                         ShipEntity              *subEntity = nil;
    3268             :                                         while (isWeaponNone(weaponType) && (subEntity = [subEntEnum nextObject]))
    3269             :                                         {
    3270             :                                                 weaponType = subEntity->forward_weapon_type;
    3271             :                                         }
    3272             :                                 }
    3273             :                                 break;
    3274             :                                 
    3275             :                         case WEAPON_FACING_AFT:
    3276             :                                 weaponType = aft_weapon_type;
    3277             :                                 break;
    3278             :                                 
    3279             :                         case WEAPON_FACING_PORT:
    3280             :                                 weaponType = port_weapon_type;
    3281             :                                 break;
    3282             :                                 
    3283             :                         case WEAPON_FACING_STARBOARD:
    3284             :                                 weaponType = starboard_weapon_type;
    3285             :                                 break;
    3286             :                                 
    3287             :                         case WEAPON_FACING_NONE:
    3288             :                                 break;
    3289             :                 }
    3290             :         }
    3291             :         return weaponType;
    3292             : }
    3293             : 
    3294             : - (OOEquipmentType *) weaponTypeForFacing:(OOWeaponFacing)facing strict:(BOOL)strict
    3295             : {
    3296             : //      OOWeaponType weaponType = [self weaponTypeIDForFacing:facing strict:strict];
    3297             : //      return [OOEquipmentType equipmentTypeWithIdentifier:OOEquipmentIdentifierFromWeaponType(weaponType)];
    3298             :         return [self weaponTypeIDForFacing:facing strict:strict];
    3299             : }
    3300             : 
    3301             : 
    3302             : - (NSArray *) missilesList
    3303             : {
    3304             :         // if missile_list is empty, avoid exception and return empty NSArray instead
    3305             :         return missile_list[0] != nil ? [NSArray arrayWithObjects:missile_list count:missiles] :
    3306             :                                                                         [NSArray array];
    3307             : }
    3308             : 
    3309             : 
    3310             : - (NSArray *) passengerListForScripting
    3311             : {
    3312             :         return [NSArray array];
    3313             : }
    3314             : 
    3315             : 
    3316             : - (NSArray *) parcelListForScripting
    3317             : {
    3318             :         return [NSArray array];
    3319             : }
    3320             : 
    3321             : 
    3322             : - (NSArray *) contractListForScripting
    3323             : {
    3324             :         return [NSArray array];
    3325             : }
    3326             : 
    3327             : 
    3328           0 : - (OOEquipmentType *) generateMissileEquipmentTypeFrom:(NSString *)role
    3329             : {
    3330             :         /*      The generated missile equipment type provides for backward compatibility with pre-1.74 OXPs  missile_roles
    3331             :                 and follows this template:
    3332             :                 
    3333             :                 //NPC equipment, incompatible with player ship. Not buyable because of its TL.
    3334             :                 (
    3335             :                         100, 100000, "Missile",
    3336             :                         "EQ_X_MISSILE",
    3337             :                         "Unidentified missile type.",
    3338             :                         {
    3339             :                                 is_external_store = true;
    3340             :                         }
    3341             :                 )
    3342             :         */
    3343             :         NSArray  *itemInfo = [NSArray arrayWithObjects:@"100", @"100000", @"Missile", role, @"Unidentified missile type.",
    3344             :                                                         [NSDictionary dictionaryWithObjectsAndKeys: @"true", @"is_external_store", nil], nil];
    3345             :         
    3346             :         [OOEquipmentType addEquipmentWithInfo:itemInfo];
    3347             :         return [OOEquipmentType equipmentTypeWithIdentifier:role];
    3348             : }
    3349             : 
    3350             : 
    3351             : - (NSArray *) equipmentListForScripting
    3352             : {
    3353             :         NSArray                         *eqTypes = [OOEquipmentType allEquipmentTypes];
    3354             :         NSMutableArray          *quip = [NSMutableArray arrayWithCapacity:[eqTypes count]];
    3355             :         NSEnumerator            *eqTypeEnum = nil;
    3356             :         OOEquipmentType         *eqType = nil;
    3357             :         BOOL                            isDamaged;
    3358             :         
    3359             :         for (eqTypeEnum = [eqTypes objectEnumerator]; (eqType = [eqTypeEnum nextObject]); )
    3360             :         {
    3361             :                 // Equipment list,  consistent with the rest of the API - Kaks
    3362             :                 if ([eqType canCarryMultiple])
    3363             :                 {
    3364             :                         NSString *damagedIdentifier = [[eqType identifier] stringByAppendingString:@"_DAMAGED"];
    3365             :                         NSUInteger i, count = 0;
    3366             :                         count += [self countEquipmentItem:[eqType identifier]];
    3367             :                         count += [self countEquipmentItem:damagedIdentifier];
    3368             :                         for (i=0;i<count;i++)
    3369             :                         {
    3370             :                                 [quip addObject:eqType];        
    3371             :                         }
    3372             :                 }
    3373             :                 else
    3374             :                 {
    3375             :                         isDamaged = [self hasEquipmentItem:[[eqType identifier] stringByAppendingString:@"_DAMAGED"]];
    3376             :                         if ([self hasEquipmentItem:[eqType identifier]] || isDamaged)
    3377             :                         {
    3378             :                                 [quip addObject:eqType];
    3379             :                         }
    3380             :                 }
    3381             :         }
    3382             :         
    3383             :         // Passengers - not supported yet for NPCs, but it's here for genericity.
    3384             :         if ([self passengerCapacity] > 0)
    3385             :         {
    3386             :                 eqType = [OOEquipmentType equipmentTypeWithIdentifier:@"EQ_PASSENGER_BERTH"];
    3387             :                 //[quip addObject:[self eqDictionaryWithType:eqType isDamaged:NO]];
    3388             :                 [quip addObject:eqType];
    3389             :         }
    3390             :         
    3391             :         return [[quip copy] autorelease];
    3392             : }
    3393             : 
    3394             : 
    3395             : - (BOOL) equipmentValidToAdd:(NSString *)equipmentKey inContext:(NSString *)context
    3396             : {
    3397             :         return [self equipmentValidToAdd:equipmentKey whileLoading:NO inContext:context];
    3398             : }
    3399             : 
    3400             : 
    3401             : - (BOOL) equipmentValidToAdd:(NSString *)equipmentKey whileLoading:(BOOL)loading inContext:(NSString *)context
    3402             : {
    3403             :         OOEquipmentType                 *eqType = nil;
    3404             :         BOOL                                    validationForDamagedEquipment = NO;
    3405             :         
    3406             :         if ([equipmentKey hasSuffix:@"_DAMAGED"])
    3407             :         {
    3408             :                 equipmentKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]];
    3409             :         }
    3410             :         
    3411             :         eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey];
    3412             :         if (eqType == nil)  return NO;
    3413             :         
    3414             :         // need to know if we are trying to add a Repair version of the equipment. In some cases
    3415             :         // (e.g. available cargo space required), it makes sense to deny installation of equipment
    3416             :         // if the condition is not satisfied, but it doesn't make sense to deny repair when the
    3417             :         // equipment is already installed. For now, we are checking only the cargo space condition,
    3418             :         // but other conditions might need to be revised too. - Nikos, 20151115
    3419             :         if ([self hasEquipmentItem:[eqType damagedIdentifier]])
    3420             :         {
    3421             :                 validationForDamagedEquipment = YES;
    3422             :         }
    3423             :         
    3424             :         // not all conditions make sence checking while loading a game with already purchaged equipment.
    3425             :         // while loading, we mainly need to catch changes when the installed oxps set has changed since saving. 
    3426             :         if ([eqType requiresEmptyPylon] && [self missileCount] >= [self missileCapacity] && !loading)  return NO;
    3427             :         if ([eqType  requiresMountedPylon] && [self missileCount] == 0 && !loading)  return NO;
    3428             :         if ([self availableCargoSpace] < [eqType requiredCargoSpace] && !validationForDamagedEquipment && !loading)  return NO;
    3429             :         if ([eqType requiresEquipment] != nil && ![self hasAllEquipment:[eqType requiresEquipment] includeWeapons:YES whileLoading:loading])  return NO;
    3430             :         if ([eqType requiresAnyEquipment] != nil && ![self hasEquipmentItem:[eqType requiresAnyEquipment] includeWeapons:YES whileLoading:loading])  return NO;
    3431             :         if ([eqType incompatibleEquipment] != nil && [self hasEquipmentItem:[eqType incompatibleEquipment] includeWeapons:YES whileLoading:loading])  return NO;
    3432             :         if ([eqType requiresCleanLegalRecord] && [self legalStatus] != 0 && !loading)  return NO;
    3433             :         if ([eqType requiresNonCleanLegalRecord] && [self legalStatus] == 0 && !loading)  return NO;
    3434             :         if ([eqType requiresFreePassengerBerth] && [self passengerCount] >= [self passengerCapacity])  return NO;
    3435             :         if ([eqType requiresFullFuel] && [self fuel] < [self fuelCapacity] && !loading)  return NO;
    3436             :         if ([eqType requiresNonFullFuel] && [self fuel] >= [self fuelCapacity] && !loading)  return NO;
    3437             : 
    3438             :         if (!loading)
    3439             :         {
    3440             :                 NSString *condition_script = [eqType conditionScript];
    3441             :                 if (condition_script != nil)
    3442             :                 {
    3443             :                         OOJSScript *condScript = [UNIVERSE getConditionScript:condition_script];
    3444             :                         if (condScript != nil) // should always be non-nil, but just in case
    3445             :                         {
    3446             :                                 JSContext                       *JScontext = OOJSAcquireContext();
    3447             :                                 BOOL OK;
    3448             :                                 JSBool allow_addition = false;
    3449             :                                 jsval result;
    3450             :                                 jsval args[] = { OOJSValueFromNativeObject(JScontext, equipmentKey) , OOJSValueFromNativeObject(JScontext, self) , OOJSValueFromNativeObject(JScontext, context)};
    3451             :                                 
    3452             :                                 OK = [condScript callMethod:OOJSID("allowAwardEquipment")
    3453             :                                                                                         inContext:JScontext
    3454             :                                                                         withArguments:args count:sizeof args / sizeof *args
    3455             :                                                                                                  result:&result];
    3456             : 
    3457             :                                 if (OK) OK = JS_ValueToBoolean(JScontext, result, &allow_addition);
    3458             :                                 
    3459             :                                 OOJSRelinquishContext(JScontext);
    3460             : 
    3461             :                                 if (OK && !allow_addition)
    3462             :                                 {
    3463             :                                         /* if the script exists, the function exists, the function
    3464             :                                          * returns a bool, and that bool is false, block
    3465             :                                          * addition. Otherwise allow it as default */
    3466             :                                         return NO;
    3467             :                                 }
    3468             :                         }
    3469             :                 }
    3470             :         }
    3471             : 
    3472             :         if ([self isPlayer])
    3473             :         {
    3474             :                 if (![eqType isAvailableToPlayer])  return NO;
    3475             :                 if (![eqType isAvailableToAll])  
    3476             :                 {
    3477             :                         // find options that agree with this ship. Only player ships have these options.
    3478             :                         OOShipRegistry          *registry = [OOShipRegistry sharedRegistry];
    3479             :                         NSDictionary            *shipyardInfo = [registry shipyardInfoForKey:[self shipDataKey]];
    3480             :                         NSMutableSet            *options = [NSMutableSet setWithArray:[shipyardInfo oo_arrayForKey:KEY_OPTIONAL_EQUIPMENT]];
    3481             :                         [options addObjectsFromArray:[[shipyardInfo oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]];
    3482             :                         if (![options containsObject:equipmentKey])  return NO;
    3483             :                 }
    3484             :         }
    3485             :         else
    3486             :         {
    3487             :                 if (![eqType isAvailableToNPCs])  return NO;
    3488             :         }
    3489             :         
    3490             :         return YES;
    3491             : }
    3492             : 
    3493             : 
    3494             : - (BOOL) setWeaponMount:(OOWeaponFacing)facing toWeapon:(NSString *)eqKey
    3495             : {
    3496             :         // sets WEAPON_NONE if not recognised
    3497             :         if (weapon_facings & facing) 
    3498             :         {
    3499             :                 OOWeaponType chosen_weapon = OOWeaponTypeFromEquipmentIdentifierStrict(eqKey);
    3500             :                 switch (facing)
    3501             :                 {
    3502             :                         case WEAPON_FACING_FORWARD:
    3503             :                                 forward_weapon_type = chosen_weapon;
    3504             :                                 break;
    3505             :                                 
    3506             :                         case WEAPON_FACING_AFT:
    3507             :                                 aft_weapon_type = chosen_weapon;
    3508             :                                 break;
    3509             :                                 
    3510             :                         case WEAPON_FACING_PORT:
    3511             :                                 port_weapon_type = chosen_weapon;
    3512             :                                 break;
    3513             :                                 
    3514             :                         case WEAPON_FACING_STARBOARD:
    3515             :                                 starboard_weapon_type = chosen_weapon;
    3516             :                                 break;
    3517             :                                 
    3518             :                         case WEAPON_FACING_NONE:
    3519             :                                 break;
    3520             :                 }
    3521             : 
    3522             :                 return YES;
    3523             :         }
    3524             :         else
    3525             :         {
    3526             :                 return NO;
    3527             :         }
    3528             : }
    3529             : 
    3530             : 
    3531             : - (BOOL) addEquipmentItem:(NSString *)equipmentKey inContext:(NSString *)context
    3532             : {
    3533             :         return [self addEquipmentItem:equipmentKey withValidation:YES inContext:context];
    3534             : }
    3535             : 
    3536             : 
    3537             : - (BOOL) addEquipmentItem:(NSString *)equipmentKey withValidation:(BOOL)validateAddition inContext:(NSString *)context
    3538             : {
    3539             :         OOEquipmentType                 *eqType = nil;
    3540             :         NSString                                *lcEquipmentKey = [equipmentKey lowercaseString];
    3541             :         NSString                                *damagedKey;
    3542             :         BOOL                                    isEqThargon = [lcEquipmentKey hasSuffix:@"thargon"] || [lcEquipmentKey hasPrefix:@"thargon"];
    3543             :         BOOL                                    isRepairedEquipment = NO;
    3544             :         
    3545             :         if([lcEquipmentKey isEqualToString:@"thargon"]) equipmentKey = @"EQ_THARGON";
    3546             :         
    3547             :         // canAddEquipment always checks if the undamaged version is equipped.
    3548             :         if (validateAddition == YES && ![self canAddEquipment:equipmentKey inContext:context])  return NO;
    3549             :         
    3550             :         if ([equipmentKey hasSuffix:@"_DAMAGED"])
    3551             :         {
    3552             :                 eqType = [OOEquipmentType equipmentTypeWithIdentifier:[equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]]];
    3553             :         }
    3554             :         else 
    3555             :         {
    3556             :                 eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentKey];
    3557             :                 // in case we have the damaged version!
    3558             :                 if (![eqType canCarryMultiple])
    3559             :                 {
    3560             :                         damagedKey = [equipmentKey stringByAppendingString:@"_DAMAGED"];
    3561             :                         if ([_equipment containsObject:damagedKey])
    3562             :                         {
    3563             :                                 [_equipment removeObject:damagedKey];
    3564             :                                 isRepairedEquipment = YES;
    3565             :                         }
    3566             :                 }
    3567             :         }
    3568             :         
    3569             :         // does this equipment actually exist?
    3570             :         if (eqType == nil)  return NO;
    3571             :         
    3572             :         // special cases
    3573             :         if ([eqType isMissileOrMine] || ([self isThargoid] && isEqThargon))
    3574             :         {
    3575             :                 if (missiles >= max_missiles) return NO;
    3576             :                 
    3577             :                 missile_list[missiles] = eqType;
    3578             :                 missiles++;
    3579             :                 return YES;
    3580             :         }
    3581             :         
    3582             :         // don't add any thargons to non-thargoid ships.
    3583             :         if(isEqThargon) return NO;
    3584             :         
    3585             :         // we can theoretically add a damaged weapon, but not a working one.
    3586             :         if([equipmentKey hasPrefix:@"EQ_WEAPON"] && ![equipmentKey hasSuffix:@"_DAMAGED"])
    3587             :         {
    3588             :                 return NO;
    3589             :         }
    3590             :         // end special cases
    3591             :         
    3592             :         if (_equipment == nil)  _equipment = [[NSMutableArray alloc] init];
    3593             :         
    3594             :         if (![equipmentKey isEqualToString:@"EQ_PASSENGER_BERTH"] && !isRepairedEquipment) 
    3595             :         {
    3596             :                 // Add to equipment_weight with all other equipment.
    3597             :                 equipment_weight += [eqType requiredCargoSpace];
    3598             :                 if (equipment_weight > max_cargo)
    3599             :                 {
    3600             :                         // should not even happen with old save games. Reject equipment now.
    3601             :                         equipment_weight -= [eqType requiredCargoSpace];
    3602             :                         return NO;
    3603             :                 }
    3604             :         }
    3605             :         
    3606             :         
    3607             :         if (!isPlayer)
    3608             :         {
    3609             :                 if ([equipmentKey isEqual:@"EQ_CARGO_BAY"])
    3610             :                 {
    3611             :                         max_cargo += extra_cargo;
    3612             :                 }
    3613             :                 else if([equipmentKey isEqualToString:@"EQ_SHIELD_BOOSTER"]) 
    3614             :                 {
    3615             :                         maxEnergy += 256.0f;
    3616             :                 }
    3617             :                 if([equipmentKey isEqualToString:@"EQ_SHIELD_ENHANCER"]) 
    3618             :                 {
    3619             :                         maxEnergy += 256.0f;
    3620             :                         energy_recharge_rate *= 1.5;
    3621             :                 }
    3622             :         } 
    3623             :         // add the equipment
    3624             :         [_equipment addObject:equipmentKey];
    3625             :         [self doScriptEvent:OOJSID("equipmentAdded") withArgument:equipmentKey];
    3626             :         return YES;
    3627             : }
    3628             : 
    3629             : 
    3630             : - (NSEnumerator *) equipmentEnumerator
    3631             : {
    3632             :         return [_equipment objectEnumerator];
    3633             : }
    3634             : 
    3635             : 
    3636             : - (NSUInteger) equipmentCount
    3637             : {
    3638             :         return [_equipment count];
    3639             : }
    3640             : 
    3641             : 
    3642             : - (void) removeEquipmentItem:(NSString *)equipmentKey
    3643             : {
    3644             :         NSString                *equipmentTypeCheckKey = equipmentKey;
    3645             :         NSString                *lcEquipmentKey = [equipmentKey lowercaseString];
    3646             :         NSUInteger              equipmentIndex = NSNotFound;
    3647             :         // determine the equipment type and make sure it works also in the case of damaged equipment
    3648             :         if ([equipmentKey hasSuffix:@"_DAMAGED"])
    3649             :         {
    3650             :                 equipmentTypeCheckKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]];
    3651             :         }
    3652             :         OOEquipmentType *eqType = [OOEquipmentType equipmentTypeWithIdentifier:equipmentTypeCheckKey];
    3653             :         if (eqType == nil)  return;
    3654             :         
    3655             :         if ([eqType isMissileOrMine] || ([self isThargoid] && ([lcEquipmentKey hasSuffix:@"thargon"] || [lcEquipmentKey hasPrefix:@"thargon"])))
    3656             :         {
    3657             :                 [self removeExternalStore:eqType];
    3658             :         }
    3659             :         else
    3660             :         {
    3661             :                 if ([_equipment containsObject:equipmentKey])
    3662             :                 {
    3663             :                         if (![equipmentKey isEqualToString:@"EQ_PASSENGER_BERTH"])
    3664             :                         {
    3665             :                                 equipment_weight -= [eqType requiredCargoSpace]; // all other cases;
    3666             :                         }
    3667             :                                                 
    3668             :                         if ([equipmentKey isEqualToString:@"EQ_CLOAKING_DEVICE"])
    3669             :                         {
    3670             :                                 if ([self isCloaked])  [self setCloaked:NO];
    3671             :                         }
    3672             : 
    3673             :                         if (!isPlayer)
    3674             :                         {
    3675             :                                 if([equipmentKey isEqualToString:@"EQ_SHIELD_BOOSTER"])
    3676             :                                 {
    3677             :                                         maxEnergy -= 256.0f;
    3678             :                                         if (maxEnergy < energy) energy = maxEnergy;
    3679             :                                 }
    3680             :                                 else if([equipmentKey isEqualToString:@"EQ_SHIELD_ENHANCER"]) 
    3681             :                                 {
    3682             :                                         maxEnergy -= 256.0f;
    3683             :                                         energy_recharge_rate /= 1.5;
    3684             :                                         if (maxEnergy < energy) energy = maxEnergy;
    3685             :                                 }
    3686             :                                 else if ([equipmentKey isEqual:@"EQ_CARGO_BAY"])
    3687             :                                 {
    3688             :                                         max_cargo -= extra_cargo;
    3689             :                                 }
    3690             :                         }
    3691             :                 }
    3692             :                 
    3693             :                 if (![equipmentKey hasSuffix:@"_DAMAGED"] && ![eqType canCarryMultiple])
    3694             :                 {
    3695             :                         NSString *damagedKey = [equipmentKey stringByAppendingString:@"_DAMAGED"];
    3696             :                         if ([_equipment containsObject:damagedKey])
    3697             :                         {
    3698             :                                 equipmentIndex = [_equipment indexOfObject:damagedKey];
    3699             :                                 if (equipmentIndex != NSNotFound)
    3700             :                                 {
    3701             :                                         // remove damaged counterpart
    3702             :                                         [_equipment removeObjectAtIndex:equipmentIndex];
    3703             :                                 }
    3704             :                                 equipment_weight -= [eqType requiredCargoSpace];
    3705             :                         }
    3706             :                 }
    3707             :                 equipmentIndex = [_equipment indexOfObject:equipmentKey];
    3708             :                 if (equipmentIndex != NSNotFound)
    3709             :                 {
    3710             :                         [_equipment removeObjectAtIndex:equipmentIndex];
    3711             :                 }
    3712             :                 // this event must come after the item is actually removed
    3713             :                 [self doScriptEvent:OOJSID("equipmentRemoved") withArgument:equipmentKey];
    3714             :                 
    3715             :                 // if all docking computers are damaged while active
    3716             :                 if ([self isPlayer] && [self status] == STATUS_AUTOPILOT_ENGAGED && ![self hasDockingComputer])
    3717             :                 {
    3718             :                         [(PlayerEntity *)self disengageAutopilot];
    3719             :                 }
    3720             : 
    3721             : 
    3722             :                 if ([_equipment count] == 0)  [self removeAllEquipment];
    3723             :         }
    3724             : }
    3725             : 
    3726             : 
    3727             : - (BOOL) removeExternalStore:(OOEquipmentType *)eqType
    3728             : {
    3729             :         NSString        *identifier = [eqType identifier];
    3730             :         unsigned        i;
    3731             :         
    3732             :         for (i = 0; i < missiles; i++)
    3733             :         {
    3734             :                 if ([[missile_list[i] identifier] isEqualTo:identifier])
    3735             :                 {
    3736             :                         // now 'delete' [i] by compacting the array
    3737             :                         while ( ++i < missiles ) missile_list[i - 1] = missile_list[i];
    3738             :                         
    3739             :                         missiles--;
    3740             :                         return YES;
    3741             :                 }
    3742             :         }
    3743             :         return NO;
    3744             : }
    3745             : 
    3746             : 
    3747           0 : - (OOEquipmentType *) verifiedMissileTypeFromRole:(NSString *)role
    3748             : {
    3749             :         NSString                        *eqRole = nil;
    3750             :         NSString                        *shipKey = nil;
    3751             :         ShipEntity                      *missile = nil;
    3752             :         OOEquipmentType         *missileType = nil;
    3753             :         BOOL                            isRandomMissile = [role isEqualToString:@"missile"];
    3754             :         
    3755             :         if (isRandomMissile)
    3756             :         {
    3757             :                 while (!shipKey)
    3758             :                 {
    3759             :                         shipKey = [UNIVERSE randomShipKeyForRoleRespectingConditions:role];
    3760             :                         if (!shipKey) 
    3761             :                         {
    3762             :                                 OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"random missile", shipKey, [self name], @"shipdata",  @"Trying another missile.");
    3763             :                         }
    3764             :                 }
    3765             :         }
    3766             :         else
    3767             :         {
    3768             :                 shipKey = [UNIVERSE randomShipKeyForRoleRespectingConditions:role];
    3769             :                 if (!shipKey) 
    3770             :                 {
    3771             :                         OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"missile_role", role, [self name], @"shipdata", @" Using defaults instead.");
    3772             :                         return nil;
    3773             :                 }
    3774             :         }
    3775             :         
    3776             :         eqRole = [OOEquipmentType getMissileRegistryRoleForShip:shipKey];       // eqRole != role for generic missiles.
    3777             :         
    3778             :         if (eqRole == nil)
    3779             :         {
    3780             :                 missile = [UNIVERSE newShipWithName:shipKey];
    3781             :                 if (!missile)
    3782             :                 {
    3783             :                         if (isRandomMissile)
    3784             :                                 OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"random missile", shipKey, [self name], @"shipdata",  @"Trying another missile.");
    3785             :                         else
    3786             :                                 OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", @"missile_role", role, [self name], @"shipdata", @" Using defaults instead.");
    3787             :                                 
    3788             :                         [OOEquipmentType setMissileRegistryRole:@"" forShip:shipKey]; // no valid role for this shipKey
    3789             :                         if (isRandomMissile) return [self verifiedMissileTypeFromRole:role];
    3790             :                         else return nil;
    3791             :                 }
    3792             :                 
    3793             :                 if(isRandomMissile)
    3794             :                 {
    3795             :                         id                              value;
    3796             :                         NSEnumerator    *enumerator = [[[missile roleSet] roles] objectEnumerator];
    3797             :                         
    3798             :                         while ((value = [enumerator nextObject]))
    3799             :                         {
    3800             :                                 role = (NSString *)value;
    3801             :                                 missileType = [OOEquipmentType equipmentTypeWithIdentifier:role];
    3802             :                                 // ensure that we have a missile or mine
    3803             :                                 if ([missileType isMissileOrMine]) break;
    3804             :                         }
    3805             :                 
    3806             :                         if (![missileType isMissileOrMine])
    3807             :                         {
    3808             :                                 role = shipKey; // unique identifier to use in lieu of a valid equipment type if none are defined inside the generic missile roleset.
    3809             :                         }
    3810             :                 }
    3811             :                 
    3812             :                 missileType = [OOEquipmentType equipmentTypeWithIdentifier:role];
    3813             :                 
    3814             :                 if (!missileType)
    3815             :                 {
    3816             :                         OOLogWARN(@"ship.setUp.missiles", @"%@ \"%@\" used in ship \"%@\" needs a valid %@.plist entry.%@", (isRandomMissile ? @"random missile" : @"missile_role"), role, [self name], @"equipment", @" Enabling compatibility mode.");
    3817             :                         missileType = [self generateMissileEquipmentTypeFrom:role];
    3818             :                 }
    3819             :                 
    3820             :                 [OOEquipmentType setMissileRegistryRole:role forShip:shipKey];
    3821             :                 [missile release];
    3822             :         }
    3823             :         else
    3824             :         {
    3825             :                 if ([eqRole isEqualToString:@""])
    3826             :                 {
    3827             :                         // wrong ship definition, already written to the log in a previous call.
    3828             :                         if (isRandomMissile) return [self verifiedMissileTypeFromRole:role];    // try and find a valid missile with role 'missile'.
    3829             :                         return nil;
    3830             :                 }
    3831             :                 missileType = [OOEquipmentType equipmentTypeWithIdentifier:eqRole];
    3832             :         }
    3833             : 
    3834             :         return missileType;
    3835             : }
    3836             : 
    3837             : 
    3838             : - (OOEquipmentType *) selectMissile
    3839             : {
    3840             :         OOEquipmentType         *missileType = nil;
    3841             :         NSString                        *role = nil;
    3842             :         double                          chance = randf();
    3843             :         BOOL                            thargoidMissile = NO;
    3844             :         
    3845             :         if ([self isThargoid])
    3846             :         {
    3847             :                 if (_missileRole != nil) missileType = [self verifiedMissileTypeFromRole:_missileRole];
    3848             :                 if (missileType == nil) {
    3849             :                         _missileRole = @"EQ_THARGON"; // no valid missile_role defined, use thargoid fallback from now on.
    3850             :                         missileType = [self verifiedMissileTypeFromRole:_missileRole];
    3851             :                 }
    3852             :         }
    3853             :         else
    3854             :         {
    3855             :                 // All other ships: random role 10% of the cases when auto weapons is set, if a missile_role is defined.
    3856             :                 // Without auto weapons, never random.
    3857             :                 float randomSelectionChance = chance;
    3858             :                 if(![self hasAutoWeapons])  randomSelectionChance = 0.0f;
    3859             :                 if (randomSelectionChance < 0.9f && _missileRole != nil)
    3860             :                 {
    3861             :                         missileType = [self verifiedMissileTypeFromRole:_missileRole];
    3862             :                 }
    3863             :                 
    3864             :                 if (missileType == nil) // the random 10% , or no valid missile_role defined
    3865             :                 {
    3866             :                         if (chance < 0.9f && _missileRole != nil)    // no valid missile_role defined?
    3867             :                         {
    3868             :                                 _missileRole = nil;     // use generic ship fallback from now on.
    3869             :                         }
    3870             :                         
    3871             :                         // assign random missiles 20% of the time without missile_role (or 10% with valid missile_role)
    3872             :                         if (chance > 0.8f) role = @"missile";
    3873             :                         // otherwise use the standard role
    3874             :                         else role = @"EQ_MISSILE";
    3875             :                         
    3876             :                         missileType = [self verifiedMissileTypeFromRole:role];
    3877             :                 }
    3878             :         }
    3879             :         
    3880             :         if (missileType == nil) OOLogERR(@"ship.setUp.missiles", @"could not resolve missile / mine type for ship \"%@\". Original missile role:\"%@\".", [self name],_missileRole);
    3881             :         
    3882             :         role = [[missileType identifier] lowercaseString];
    3883             :         thargoidMissile = [self isThargoid] && ([role hasSuffix:@"thargon"] || [role hasPrefix:@"thargon"]);
    3884             : 
    3885             :         if (thargoidMissile || (!thargoidMissile && [missileType isMissileOrMine]))
    3886             :         {
    3887             :                 return missileType;
    3888             :         }
    3889             :         else
    3890             :         {
    3891             :                 OOLogWARN(@"ship.setUp.missiles", @"missile_role \"%@\" is not a valid missile / mine type for ship \"%@\".%@", [missileType identifier] , [self name],@" No missile selected.");
    3892             :                 return nil;
    3893             :         }
    3894             : }
    3895             : 
    3896             : 
    3897             : - (void) removeAllEquipment
    3898             : {
    3899             :         [_equipment release];
    3900             :         _equipment = nil;
    3901             : }
    3902             : 
    3903             : 
    3904             : - (OOCreditsQuantity) removeMissiles
    3905             : {
    3906             :         missiles = 0;
    3907             :         return 0;
    3908             : }
    3909             : 
    3910             : 
    3911             : - (NSUInteger) parcelCount
    3912             : {
    3913             :         return 0;
    3914             : }
    3915             : 
    3916             : 
    3917             : - (NSUInteger) passengerCount
    3918             : {
    3919             :         return 0;
    3920             : }
    3921             : 
    3922             : 
    3923             : - (NSUInteger) passengerCapacity
    3924             : {
    3925             :         return 0;
    3926             : }
    3927             : 
    3928             : 
    3929             : - (NSUInteger) missileCount
    3930             : {
    3931             :         return missiles;
    3932             : }
    3933             : 
    3934             : 
    3935             : - (NSUInteger) missileCapacity
    3936             : {
    3937             :         return max_missiles;
    3938             : }
    3939             : 
    3940             : 
    3941             : - (NSUInteger) extraCargo
    3942             : {
    3943             :         return extra_cargo;
    3944             : }
    3945             : 
    3946             : 
    3947             : /* This is used for e.g. displaying the HUD icon */
    3948             : - (BOOL) hasScoop
    3949             : {
    3950             :         return [self hasEquipmentItemProviding:@"EQ_FUEL_SCOOPS"] || [self hasEquipmentItemProviding:@"EQ_CARGO_SCOOPS"];
    3951             : }
    3952             : 
    3953             : 
    3954             : - (BOOL) hasFuelScoop
    3955             : {
    3956             :         return [self hasEquipmentItemProviding:@"EQ_FUEL_SCOOPS"];
    3957             : }
    3958             : 
    3959             : 
    3960             : /* No such core equipment item, but EQ_FUEL_SCOOPS provides it */
    3961             : - (BOOL) hasCargoScoop
    3962             : {
    3963             :         return [self hasEquipmentItemProviding:@"EQ_CARGO_SCOOPS"];
    3964             : }
    3965             : 
    3966             : 
    3967             : - (BOOL) hasECM
    3968             : {
    3969             :         return [self hasEquipmentItemProviding:@"EQ_ECM"];
    3970             : }
    3971             : 
    3972             : 
    3973             : - (BOOL) hasCloakingDevice
    3974             : {
    3975             :         /* TODO: Checks above stop this being 'providing'. */
    3976             :         return [self hasEquipmentItem:@"EQ_CLOAKING_DEVICE"];
    3977             : }
    3978             : 
    3979             : 
    3980             : - (BOOL) hasMilitaryScannerFilter
    3981             : {
    3982             : #if USEMASC
    3983             :         return [self hasEquipmentItemProviding:@"EQ_MILITARY_SCANNER_FILTER"];
    3984             : #else
    3985             :         return NO;
    3986             : #endif
    3987             : }
    3988             : 
    3989             : 
    3990             : - (BOOL) hasMilitaryJammer
    3991             : {
    3992             : #if USEMASC
    3993             :         return [self hasEquipmentItemProviding:@"EQ_MILITARY_JAMMER"];
    3994             : #else
    3995             :         return NO;
    3996             : #endif
    3997             : }
    3998             : 
    3999             : 
    4000             : - (BOOL) hasExpandedCargoBay
    4001             : {
    4002             :         /* Not 'providing' - controlled through scripts */
    4003             :         return [self hasEquipmentItem:@"EQ_CARGO_BAY"];
    4004             : }
    4005             : 
    4006             : 
    4007             : - (BOOL) hasShieldBooster
    4008             : {
    4009             :         /* Not 'providing' - controlled through scripts */
    4010             :         return [self hasEquipmentItem:@"EQ_SHIELD_BOOSTER"];
    4011             : }
    4012             : 
    4013             : 
    4014             : - (BOOL) hasMilitaryShieldEnhancer
    4015             : {
    4016             :         /* Not 'providing' - controlled through scripts */
    4017             :         return [self hasEquipmentItem:@"EQ_NAVAL_SHIELD_BOOSTER"];
    4018             : }
    4019             : 
    4020             : 
    4021             : - (BOOL) hasHeatShield
    4022             : {
    4023             :         return [self hasEquipmentItemProviding:@"EQ_HEAT_SHIELD"];
    4024             : }
    4025             : 
    4026             : 
    4027             : - (BOOL) hasFuelInjection
    4028             : {
    4029             :         return [self hasEquipmentItemProviding:@"EQ_FUEL_INJECTION"];
    4030             : }
    4031             : 
    4032             : 
    4033             : - (BOOL) hasCascadeMine
    4034             : {
    4035             :         /* TODO: this could be providing since theoretically OXP
    4036             :          * deployable mines could also do cascade effects, but there are
    4037             :          * probably better ways to manage OXP pylon AI */
    4038             :         return [self hasEquipmentItem:@"EQ_QC_MINE" includeWeapons:YES whileLoading:NO];
    4039             : }
    4040             : 
    4041             : 
    4042             : - (BOOL) hasEscapePod
    4043             : {
    4044             :         return [self hasEquipmentItemProviding:@"EQ_ESCAPE_POD"];
    4045             : }
    4046             : 
    4047             : 
    4048             : - (BOOL) hasDockingComputer
    4049             : {
    4050             :         return [self hasEquipmentItemProviding:@"EQ_DOCK_COMP"];
    4051             : }
    4052             : 
    4053             : 
    4054             : - (BOOL) hasGalacticHyperdrive
    4055             : {
    4056             :         return [self hasEquipmentItemProviding:@"EQ_GAL_DRIVE"];
    4057             : }
    4058             : 
    4059             : 
    4060             : - (float) shieldBoostFactor
    4061             : {
    4062             :         float boostFactor = 1.0f;
    4063             :         if ([self hasShieldBooster])  boostFactor += 1.0f;
    4064             :         if ([self hasMilitaryShieldEnhancer])  boostFactor += 1.0f;
    4065             :         
    4066             :         return boostFactor;
    4067             : }
    4068             : 
    4069             : 
    4070             : /* These next three are never called as of 12/12/2014, as NPCs don't
    4071             :  * have shields and PlayerEntity overrides these. */
    4072             : - (float) maxForwardShieldLevel
    4073             : {
    4074             :         return BASELINE_SHIELD_LEVEL * [self shieldBoostFactor];
    4075             : }
    4076             : 
    4077             : 
    4078             : - (float) maxAftShieldLevel
    4079             : {
    4080             :         return BASELINE_SHIELD_LEVEL * [self shieldBoostFactor];
    4081             : }
    4082             : 
    4083             : 
    4084             : - (float) shieldRechargeRate
    4085             : {
    4086             :         return [self hasMilitaryShieldEnhancer] ? 3.0f : 2.0f;
    4087             : }
    4088             : 
    4089             : 
    4090             : - (double) maxHyperspaceDistance
    4091             : {
    4092             :         return MAX_JUMP_RANGE;
    4093             : }
    4094             : 
    4095             : - (float) afterburnerFactor
    4096             : {
    4097             :         return afterburner_speed_factor;
    4098             : }
    4099             : 
    4100             : 
    4101             : - (float) afterburnerRate
    4102             : {
    4103             :         return afterburner_rate;
    4104             : }
    4105             : 
    4106             : 
    4107             : - (void) setAfterburnerFactor:(GLfloat)new
    4108             : {
    4109             :         afterburner_speed_factor = new;
    4110             : }
    4111             : 
    4112             : 
    4113             : - (void) setAfterburnerRate:(GLfloat)new
    4114             : {
    4115             :         afterburner_rate = new;
    4116             : }
    4117             : 
    4118             : 
    4119             : - (float) maxThrust
    4120             : {
    4121             :         return max_thrust;
    4122             : }
    4123             : 
    4124             : 
    4125             : - (void) setMaxThrust:(GLfloat)new
    4126             : {
    4127             :         max_thrust = new;
    4128             : }
    4129             : 
    4130             : 
    4131             : - (float) thrust
    4132             : {
    4133             :         return thrust;
    4134             : }
    4135             : 
    4136             : 
    4137             : ////////////////
    4138             : //            //
    4139             : // behaviours //
    4140             : //            //
    4141             : - (void) behaviour_stop_still:(double) delta_t
    4142             : {
    4143             :         stick_roll = 0.0;
    4144             :         stick_pitch = 0.0;
    4145             :         stick_yaw = 0.0;
    4146             :         [self applySticks:delta_t];
    4147             : 
    4148             :         
    4149             :         
    4150             : }
    4151             : 
    4152             : 
    4153             : - (void) behaviour_idle:(double) delta_t
    4154             : {
    4155             :         stick_yaw = 0.0;
    4156             :         if ((!isStation)&&(scanClass != CLASS_BUOY))
    4157             :         {
    4158             :                 stick_roll = 0.0;
    4159             :         }
    4160             :         else
    4161             :         {
    4162             :                 stick_roll = flightRoll;
    4163             :         }
    4164             :         if (scanClass != CLASS_BUOY)
    4165             :         {
    4166             :                 stick_pitch = 0.0;
    4167             :         }
    4168             :         else
    4169             :         {
    4170             :                 stick_pitch = flightPitch;
    4171             :         }
    4172             :         [self applySticks:delta_t];
    4173             :         
    4174             :         
    4175             : }
    4176             : 
    4177             : 
    4178             : - (void) behaviour_tumble:(double) delta_t
    4179             : {
    4180             :         [self applySticks:delta_t];
    4181             :         
    4182             :         
    4183             : }
    4184             : 
    4185             : 
    4186             : - (void) behaviour_tractored:(double) delta_t
    4187             : {
    4188             :         desired_range = collision_radius * 2.0;
    4189             :         ShipEntity* hauler = (ShipEntity*)[self owner];
    4190             :         if ((hauler)&&([hauler isShip]))
    4191             :         {
    4192             :                 _destination = [hauler absoluteTractorPosition];
    4193             :                 double  distance = [self rangeToDestination];
    4194             :                 if (distance < desired_range)
    4195             :                 {
    4196             :                         [self performTumble];
    4197             :                         [self setStatus:STATUS_IN_FLIGHT];
    4198             :                         [hauler scoopUp:self];
    4199             :                         return;
    4200             :                 }
    4201             :                 GLfloat tf = TRACTOR_FORCE / mass;
    4202             :                 // adjust for difference in velocity (spring rule)
    4203             :                 Vector dv = vector_between([self velocity], [hauler velocity]);
    4204             :                 GLfloat moment = delta_t * 0.25 * tf;
    4205             :                 velocity.x += moment * dv.x;
    4206             :                 velocity.y += moment * dv.y;
    4207             :                 velocity.z += moment * dv.z;
    4208             :                 // acceleration = force / mass
    4209             :                 // force proportional to distance (spring rule)
    4210             :                 HPVector dp = HPvector_between(position, _destination);
    4211             :                 moment = delta_t * 0.5 * tf;
    4212             :                 velocity.x += moment * dp.x;
    4213             :                 velocity.y += moment * dp.y;
    4214             :                 velocity.z += moment * dp.z;
    4215             :                 // force inversely proportional to distance
    4216             :                 GLfloat d2 = HPmagnitude2(dp);
    4217             :                 moment = (d2 > 0.0)? delta_t * 5.0 * tf / d2 : 0.0;
    4218             :                 if (d2 > 0.0)
    4219             :                 {
    4220             :                         velocity.x += moment * dp.x;
    4221             :                         velocity.y += moment * dp.y;
    4222             :                         velocity.z += moment * dp.z;
    4223             :                 }
    4224             :                 //
    4225             :                 if ([self status] == STATUS_BEING_SCOOPED)
    4226             :                 {
    4227             :                         BOOL lost_contact = (distance > hauler->collision_radius + collision_radius + 250.0f);    // 250m range for tractor beam
    4228             :                         if ([hauler isPlayer])
    4229             :                         {
    4230             :                                 switch ([(PlayerEntity*)hauler dialFuelScoopStatus])
    4231             :                                 {
    4232             :                                         case SCOOP_STATUS_NOT_INSTALLED:
    4233             :                                         case SCOOP_STATUS_FULL_HOLD:
    4234             :                                                 lost_contact = YES;     // don't draw
    4235             :                                                 break;
    4236             :                                                 
    4237             :                                         case SCOOP_STATUS_OKAY:
    4238             :                                         case SCOOP_STATUS_ACTIVE:
    4239             :                                                 break;
    4240             :                                 }
    4241             :                         }
    4242             :                         
    4243             :                         if (lost_contact)       // 250m range for tractor beam
    4244             :                         {
    4245             :                                 // escaped tractor beam
    4246             :                                 [self setStatus:STATUS_IN_FLIGHT];
    4247             :                                 behaviour = BEHAVIOUR_IDLE;
    4248             :                                 [self setThrust:[self maxThrust]]; // restore old thrust.
    4249             :                                 frustration = 0.0;
    4250             :                                 [self setOwner:self];
    4251             :                                 [shipAI exitStateMachineWithMessage:nil];       // exit nullAI.plist
    4252             :                                 return;
    4253             :                         }
    4254             :                         else if ([hauler isPlayer])
    4255             :                         {
    4256             :                                 [(PlayerEntity*)hauler setScoopsActive];
    4257             :                         }
    4258             :                 }
    4259             :         }
    4260             : 
    4261             : // being tractored; sticks ignored - CIM
    4262             :         flightYaw = 0.0;
    4263             :         
    4264             :         desired_speed = 0.0;
    4265             :         thrust = 25.0;  // used to damp velocity (must be less than hauler thrust)
    4266             :         
    4267             :         thrust = 0.0;   // must reset thrust now
    4268             : }
    4269             : 
    4270             : 
    4271             : - (void) behaviour_track_target:(double) delta_t
    4272             : {
    4273             :         if ([self primaryTarget] == nil)
    4274             :         {
    4275             :                 [self noteLostTargetAndGoIdle];
    4276             :                 return;
    4277             :         }
    4278             :         [self trackPrimaryTarget:delta_t:NO]; // applies sticks
    4279             :         if ([self hasProximityAlertIgnoringTarget:YES])
    4280             :         {
    4281             :                 [self avoidCollision];
    4282             :         }
    4283             :         
    4284             : }
    4285             : 
    4286             : 
    4287             : - (void) behaviour_intercept_target:(double) delta_t
    4288             : {
    4289             :         double  range = [self rangeToPrimaryTarget];
    4290             :         if (behaviour == BEHAVIOUR_INTERCEPT_TARGET)
    4291             :         {
    4292             :                 desired_speed = maxFlightSpeed;
    4293             :                 if (range < desired_range)
    4294             :                 {
    4295             :                         [shipAI reactToMessage:@"DESIRED_RANGE_ACHIEVED" context:@"BEHAVIOUR_INTERCEPT_TARGET"];
    4296             :                         [self doScriptEvent:OOJSID("shipAchievedDesiredRange")];
    4297             : 
    4298             :                 }
    4299             :                 desired_speed = maxFlightSpeed * [self trackPrimaryTarget:delta_t:NO];
    4300             :         }
    4301             :         else
    4302             :         {
    4303             :                 // = BEHAVIOUR_COLLECT_TARGET
    4304             :                 ShipEntity*     target = [self primaryTarget];
    4305             : // if somehow ended up in this state but target is not cargo, stop
    4306             : // trying to scoop it
    4307             :                 if (!target || [target scanClass] != CLASS_CARGO || [target cargoType] == CARGO_NOT_CARGO)
    4308             :                 {
    4309             :                         [self noteLostTargetAndGoIdle];
    4310             :                         return;
    4311             :                 }
    4312             :                 double target_speed = [target speed];
    4313             :                 double eta = range / (flightSpeed - target_speed);
    4314             :                 double last_success_factor = success_factor;
    4315             :                 double last_distance = last_success_factor;
    4316             :                 double  distance = [self rangeToDestination];
    4317             :                 success_factor = distance;
    4318             :                 //
    4319             :                 double slowdownTime = 96.0 / (thrust*SHIP_THRUST_FACTOR);       // more thrust implies better slowing
    4320             :                 double minTurnSpeedFactor = 0.005 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds
    4321             : 
    4322             :                 if ((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor))
    4323             :                         desired_speed = flightSpeed * 0.75;   // cut speed by 50% to a minimum minTurnSpeedFactor of speed
    4324             :                 else
    4325             :                         desired_speed = maxFlightSpeed;
    4326             : 
    4327             :                 if (desired_speed < target_speed)
    4328             :                 {
    4329             :                         desired_speed += target_speed;
    4330             :                         if (target_speed > maxFlightSpeed)
    4331             :                         {
    4332             :                                 [self noteLostTargetAndGoIdle];
    4333             :                                 return;
    4334             :                         }
    4335             :                 }
    4336             :                 if (desired_speed > maxFlightSpeed)
    4337             :                 { // never use injectors for scooping
    4338             :                         desired_speed = maxFlightSpeed;
    4339             :                 }
    4340             : 
    4341             :                 _destination = target->position;
    4342             :                 desired_range = 0.5 * target->collision_radius;
    4343             :                 [self trackDestination: delta_t : NO];
    4344             : 
    4345             :                 //
    4346             :                 if (distance < last_distance)        // improvement
    4347             :                 {
    4348             :                         frustration -= delta_t;
    4349             :                         if (frustration < 0.0)
    4350             :                                 frustration = 0.0;
    4351             :                 }
    4352             :                 else
    4353             :                 {
    4354             :                         frustration += delta_t * 0.9;
    4355             :                         if (frustration > 10.0)      // 10s of frustration
    4356             :                         {
    4357             :                                 [self noteFrustration:@"BEHAVIOUR_INTERCEPT_TARGET"];
    4358             :                                 frustration -= 5.0;     //repeat after another five seconds' frustration
    4359             :                         }
    4360             :                 }
    4361             :         }
    4362             :         if ([self hasProximityAlertIgnoringTarget:YES])
    4363             :         {
    4364             :                 [self avoidCollision];
    4365             :         }
    4366             :         
    4367             :         
    4368             : }
    4369             : 
    4370             : 
    4371             : - (void) behaviour_attack_break_off_target:(double) delta_t
    4372             : {
    4373             :         if (![self canStillTrackPrimaryTarget])
    4374             :         {
    4375             :                 [self noteLostTargetAndGoIdle];
    4376             :                 return;
    4377             :         }
    4378             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    4379             :         float   max_available_speed = maxFlightSpeed;
    4380             :         double  range = [self rangeToPrimaryTarget];
    4381             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    4382             : 
    4383             :         desired_speed = max_available_speed;
    4384             : 
    4385             :         Entity* target = [self primaryTarget];
    4386             : 
    4387             :         if (desired_speed > maxFlightSpeed)
    4388             :         {
    4389             :                 double target_speed = [target speed];
    4390             :                 if (desired_speed > target_speed * 3.0)
    4391             :                 {
    4392             :                         desired_speed = maxFlightSpeed; // don't overuse the injectors
    4393             :                 }
    4394             :         }
    4395             : 
    4396             :         if (cloakAutomatic) [self activateCloakingDevice];
    4397             :         if ([self hasProximityAlertIgnoringTarget:NO])
    4398             :         {
    4399             :                 [self avoidCollision];
    4400             :                 return;
    4401             :         }
    4402             : 
    4403             :         frustration += delta_t;
    4404             :         if (frustration > 15.0 && accuracy >= COMBAT_AI_DOGFIGHTER && !canBurn)
    4405             :         {
    4406             :                 desired_speed = maxFlightSpeed / 2.0;
    4407             :         }
    4408             :         double aspect = [self approachAspectToPrimaryTarget];
    4409             :         if (range > 3000.0 || ([target isShip] && [(ShipEntity*)target primaryTarget] != self) || frustration - floor(frustration) > fmin(1.6/max_flight_roll,aspect))
    4410             :         {
    4411             :                 [self trackPrimaryTarget:delta_t:YES];
    4412             :         }
    4413             :         else
    4414             :         {
    4415             : // less useful at long range if not under direct fire
    4416             :                 [self evasiveAction:delta_t];
    4417             :         }
    4418             : 
    4419             :         if (range > COMBAT_OUT_RANGE_FACTOR * weaponRange)
    4420             :         {
    4421             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    4422             :         }
    4423             :         else if (aspect < -0.75 && accuracy >= COMBAT_AI_DOGFIGHTER)
    4424             :         {
    4425             :                 behaviour = BEHAVIOUR_ATTACK_SLOW_DOGFIGHT;
    4426             :         }
    4427             :         else if (frustration > 10.0 && [self approachAspectToPrimaryTarget] < 0.85 && forward_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY)
    4428             :         {
    4429             :                 frustration = 0.0;
    4430             :                 if (accuracy >= COMBAT_AI_DOGFIGHTER)
    4431             :                 {
    4432             :                         behaviour = BEHAVIOUR_ATTACK_SLOW_DOGFIGHT;
    4433             :                 }
    4434             :                 else
    4435             :                 {
    4436             :                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
    4437             :                 }
    4438             :         }
    4439             : 
    4440             :         flightYaw = 0.0;
    4441             : }
    4442             : 
    4443             : 
    4444             : - (void) behaviour_attack_slow_dogfight:(double) delta_t
    4445             : {
    4446             :         if (![self canStillTrackPrimaryTarget])
    4447             :         {
    4448             :                 [self noteLostTargetAndGoIdle];
    4449             :                 return;
    4450             :         }
    4451             :         if ([self hasProximityAlertIgnoringTarget:YES])
    4452             :         {
    4453             :                 [self avoidCollision];
    4454             :                 return;
    4455             :         } 
    4456             :         double  range = [self rangeToPrimaryTarget];
    4457             :         ShipEntity*     target = [self primaryTarget];
    4458             :         double aspect = [self approachAspectToPrimaryTarget];
    4459             :         if (range < 2.5*(collision_radius+target->collision_radius) && [self proximityAlert] == target && aspect > 0) {
    4460             :                 desired_speed = maxFlightSpeed;
    4461             :                 [self avoidCollision];
    4462             :                 return;
    4463             :         }
    4464             :         if (aspect < -0.5 && range > COMBAT_IN_RANGE_FACTOR * weaponRange * 2.0)
    4465             :         {
    4466             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    4467             :         }
    4468             :         else if (aspect < -0.5)
    4469             :         {
    4470             : // mostly behind target - try to stay there and keep up
    4471             :                 desired_speed = fmin(maxFlightSpeed * 0.5,[target speed]*0.5);          
    4472             :         }
    4473             :         else if (aspect < 0.3)
    4474             :         {
    4475             : // to side of target - slow right down
    4476             :                 desired_speed = maxFlightSpeed * 0.1;
    4477             :         }
    4478             :         else
    4479             :         {
    4480             : // coming to front of target - accelerate for a quick getaway
    4481             :                 desired_speed = maxFlightSpeed * fmin(aspect*2.5,1.0);
    4482             :         }
    4483             :         if (aspect > 0.85)
    4484             :         {
    4485             :                 behaviour = BEHAVIOUR_ATTACK_BREAK_OFF_TARGET;
    4486             :         }
    4487             :         if (aspect > 0.0)
    4488             :         {
    4489             :                 frustration += delta_t;
    4490             :         }
    4491             :         else
    4492             :         {
    4493             :                 frustration -= delta_t;
    4494             :         }
    4495             :         if (frustration > 10.0)
    4496             :         {
    4497             :                 desired_speed /= 2.0;
    4498             :         }
    4499             :         else if (frustration < 0.0)
    4500             :                 frustration = 0.0;
    4501             :         
    4502             :         [self trackPrimaryTarget:delta_t:NO];
    4503             :         
    4504             :         if (missiles) [self considerFiringMissile:delta_t];
    4505             : 
    4506             :         if (cloakAutomatic) [self activateCloakingDevice];
    4507             : 
    4508             : }
    4509             : 
    4510             : 
    4511             : - (void) behaviour_evasive_action:(double) delta_t
    4512             : {
    4513             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    4514             :         float   max_available_speed = maxFlightSpeed;
    4515             : //      double  range = [self rangeToPrimaryTarget];
    4516             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    4517             :         desired_speed = max_available_speed;
    4518             :         if (desired_speed > maxFlightSpeed)
    4519             :         {
    4520             :                 ShipEntity*     target = [self primaryTarget];
    4521             :                 double target_speed = [target speed];
    4522             :                 if (desired_speed > target_speed)
    4523             :                 {
    4524             :                         desired_speed = maxFlightSpeed; // don't overuse the injectors
    4525             :                 }
    4526             :         }
    4527             :         
    4528             :         if (cloakAutomatic) [self activateCloakingDevice];
    4529             :         if ([self proximityAlert] != nil)
    4530             :         {
    4531             :                 [self avoidCollision];
    4532             :                 return;
    4533             :         }
    4534             : 
    4535             :         [self evasiveAction:delta_t];
    4536             : 
    4537             :         frustration += delta_t;
    4538             :         
    4539             :         if (frustration > 0.5)
    4540             :         {
    4541             :                 if (behaviour == BEHAVIOUR_FLEE_EVASIVE_ACTION)
    4542             :                 {
    4543             :                         [self setEvasiveJink:400.0];
    4544             :                         behaviour = BEHAVIOUR_FLEE_TARGET;
    4545             :                 }
    4546             :                 else
    4547             :                 {
    4548             :                         behaviour = BEHAVIOUR_ATTACK_TARGET;
    4549             :                 }
    4550             :         }
    4551             : 
    4552             :         flightYaw = 0.0;
    4553             : 
    4554             :         // probably only useful for Thargoids, except for the occasional opportunist
    4555             :         [self fireMainWeapon:[self rangeToPrimaryTarget]];
    4556             :         
    4557             : }
    4558             : 
    4559             : 
    4560             : - (void) behaviour_attack_target:(double) delta_t
    4561             : {
    4562             :         double  range = [self rangeToPrimaryTarget];
    4563             :         
    4564             :         if (cloakAutomatic) [self activateCloakingDevice];
    4565             : 
    4566             : /* Start of behaviour selection:
    4567             :  * Anything beyond the basics should require accuracy >= COMBAT_AI_ISNT_AWFUL
    4568             :  * Anything fancy should require accuracy >= COMBAT_AI_IS_SMART
    4569             :  * If precise aim is required, behaviour should have accuracy >= COMBAT_AI_TRACKS_CLOSER
    4570             :  * - CIM
    4571             :  */
    4572             : 
    4573             :         OOWeaponType forward_weapon_real_type = forward_weapon_type;
    4574             :         GLfloat forward_weapon_real_temp = forward_weapon_temp;
    4575             : 
    4576             : // if forward weapon is actually on a subent
    4577             :         if (isWeaponNone(forward_weapon_real_type))
    4578             :         {
    4579             :                 BOOL hasTurrets = NO;
    4580             :                 NSEnumerator    *subEnum = [self shipSubEntityEnumerator];
    4581             :                 ShipEntity              *se = nil;
    4582             :                 while (isWeaponNone(forward_weapon_real_type) && (se = [subEnum nextObject]))
    4583             :                 {
    4584             :                         forward_weapon_real_type = se->forward_weapon_type;
    4585             :                         forward_weapon_real_temp = se->forward_weapon_temp;
    4586             :                         if (se->behaviour == BEHAVIOUR_TRACK_AS_TURRET)
    4587             :                         {
    4588             :                                 hasTurrets = YES;
    4589             :                         }
    4590             :                 }
    4591             :                 if (isWeaponNone(forward_weapon_real_type) && hasTurrets)
    4592             :                 { // safety for ships only equipped with turrets
    4593             :                         forward_weapon_real_type = OOWeaponTypeFromEquipmentIdentifierSloppy(@"EQ_WEAPON_PULSE_LASER");
    4594             :                         forward_weapon_real_temp = COMBAT_AI_WEAPON_TEMP_USABLE * 0.9;
    4595             :                 }
    4596             :         }
    4597             : 
    4598             :         if ([forward_weapon_real_type isTurretLaser]) 
    4599             :         {
    4600             :                 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
    4601             :         } 
    4602             :         else 
    4603             :         {
    4604             :                 BOOL in_good_range = aim_tolerance*range < COMBAT_AI_CONFIDENCE_FACTOR;
    4605             : 
    4606             :                 BOOL aft_weapon_ready = !isWeaponNone(aft_weapon_type) && (aft_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY) && in_good_range;
    4607             :                 BOOL forward_weapon_ready = !isWeaponNone(forward_weapon_real_type) && (forward_weapon_real_temp < COMBAT_AI_WEAPON_TEMP_READY); // does not require in_good_range
    4608             :                 BOOL port_weapon_ready = !isWeaponNone(port_weapon_type) && (port_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY) && in_good_range;
    4609             :                 BOOL starboard_weapon_ready = !isWeaponNone(starboard_weapon_type) && (starboard_weapon_temp < COMBAT_AI_WEAPON_TEMP_READY) && in_good_range;
    4610             : // if no weapons cool enough to be good choices, be less picky
    4611             :                 BOOL weapons_heating = NO;
    4612             :                 if (!forward_weapon_ready && !aft_weapon_ready && !port_weapon_ready && !starboard_weapon_ready)
    4613             :                 {
    4614             :                         weapons_heating = YES;
    4615             :                         aft_weapon_ready = !isWeaponNone(aft_weapon_type) && (aft_weapon_temp < COMBAT_AI_WEAPON_TEMP_USABLE) && in_good_range;
    4616             :                         forward_weapon_ready = !isWeaponNone(forward_weapon_real_type) && (forward_weapon_real_temp < COMBAT_AI_WEAPON_TEMP_USABLE); // does not require in_good_range
    4617             :                         port_weapon_ready = !isWeaponNone(port_weapon_type) && (port_weapon_temp < COMBAT_AI_WEAPON_TEMP_USABLE) && in_good_range;
    4618             :                 starboard_weapon_ready = !isWeaponNone(starboard_weapon_type) && (starboard_weapon_temp < COMBAT_AI_WEAPON_TEMP_USABLE) && in_good_range;
    4619             :                 }
    4620             : 
    4621             :                 Entity* target = [self primaryTarget];
    4622             :                 double aspect = [self approachAspectToPrimaryTarget];
    4623             : 
    4624             :                 if (!forward_weapon_ready && !aft_weapon_ready && !port_weapon_ready && !starboard_weapon_ready)
    4625             :                 { // no usable weapons! Either not fitted or overheated
    4626             :                         
    4627             :                         // if unarmed
    4628             :                         if (isWeaponNone(forward_weapon_real_type) && 
    4629             :                                 isWeaponNone(aft_weapon_type) && 
    4630             :                                 isWeaponNone(port_weapon_type) && 
    4631             :                                 isWeaponNone(starboard_weapon_type))
    4632             :                         {
    4633             :                                 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
    4634             :                         }
    4635             :                         else if (aspect > 0)
    4636             :                         {
    4637             :                                 if (in_good_range)
    4638             :                                 {
    4639             :                                         if (accuracy >= COMBAT_AI_IS_SMART && randf() < 0.75)
    4640             :                                         {
    4641             :                                                 behaviour = BEHAVIOUR_EVASIVE_ACTION;
    4642             :                                         }
    4643             :                                         else 
    4644             :                                         {
    4645             :                                                 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
    4646             :                                         }
    4647             :                                 }
    4648             :                                 else 
    4649             :                                 {
    4650             :                                         // ready to get more accurate shots later
    4651             :                                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
    4652             :                                 }
    4653             :                         } 
    4654             :                         else
    4655             :                         {
    4656             :                                 // if target is running away, stay on target
    4657             :                                 // unless too close for safety
    4658             :                                 if (range < COMBAT_IN_RANGE_FACTOR * weaponRange) {
    4659             :                                         behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
    4660             :                                 } else {
    4661             :                                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
    4662             :                                 }
    4663             :                         }
    4664             :                 }
    4665             : // if our current target isn't targeting us, and we have some idea of how to fight, and our weapons are running hot, and we're fairly nearby
    4666             :                 else if (weapons_heating && accuracy >= COMBAT_AI_ISNT_AWFUL && [target isShip] && [(ShipEntity *)target primaryTarget] != self && range < COMBAT_OUT_RANGE_FACTOR * weaponRange) 
    4667             :                 {
    4668             : // then back off a bit for weapons to cool so we get a good attack run later, rather than weaving closer
    4669             :                         float relativeSpeed = magnitude(vector_subtract([self velocity], [target velocity]));
    4670             :                         [self setEvasiveJink:(range + COMBAT_JINK_OFFSET - relativeSpeed / max_flight_pitch)];
    4671             :                         behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
    4672             :                 }
    4673             :                 else 
    4674             :                 {
    4675             :                         BOOL nearby = range < COMBAT_IN_RANGE_FACTOR * getWeaponRangeFromType(forward_weapon_type);
    4676             :                         BOOL midrange = range < COMBAT_OUT_RANGE_FACTOR * getWeaponRangeFromType(aft_weapon_type);
    4677             : 
    4678             : 
    4679             :                         if (nearby && aft_weapon_ready)
    4680             :                         {
    4681             :                                 jink = kZeroVector; // almost all behaviours
    4682             :                                 behaviour = BEHAVIOUR_RUNNING_DEFENSE;
    4683             :                         }
    4684             :                         else if (nearby && (port_weapon_ready || starboard_weapon_ready))
    4685             :                         {
    4686             :                                 jink = kZeroVector; // almost all behaviours
    4687             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE;
    4688             :                         }
    4689             :                         else if (nearby)
    4690             :                         {
    4691             :                                 if (!pitching_over) // don't change jink in the middle of a sharp turn.
    4692             :                                 {
    4693             :                                         /*
    4694             :                                                 For most AIs, is behaviour_attack_target called as starting behaviour on every hit.
    4695             :                                                 Target can both fly towards or away from ourselves here. Both situations
    4696             :                                                 need a different jink.z for optimal collision avoidance at high speed approach and low speed dogfighting.
    4697             :                                                 The COMBAT_JINK_OFFSET intentionally over-compensates the range for collision radii to send ships towards
    4698             :                                                 the target at low speeds.
    4699             :                                         */
    4700             :                                         float relativeSpeed = magnitude(vector_subtract([self velocity], [target velocity]));
    4701             :                                         [self setEvasiveJink:(range + COMBAT_JINK_OFFSET - relativeSpeed / max_flight_pitch)];
    4702             :                                 }
    4703             :                                 // good pilots use behaviour_attack_break_off_target instead
    4704             :                                 if (accuracy >= COMBAT_AI_FLEES_BETTER)
    4705             :                                 {
    4706             :                                         behaviour = BEHAVIOUR_ATTACK_BREAK_OFF_TARGET;
    4707             :                                 }
    4708             :                                 else
    4709             :                                 {
    4710             :                                         behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
    4711             :                                 }
    4712             :                         }
    4713             :                         else if (forward_weapon_ready)
    4714             :                         {
    4715             :                                 jink = kZeroVector; // almost all behaviours
    4716             : 
    4717             :                                 // TODO: good pilots use behaviour_attack_sniper sometimes
    4718             :                                 if (getWeaponRangeFromType(forward_weapon_real_type) > 12500 && range > 12500)
    4719             :                                 {
    4720             :                                         behaviour = BEHAVIOUR_ATTACK_SNIPER;
    4721             :                                 }
    4722             : // generally not good tactics the next two
    4723             :                                 else if (accuracy < COMBAT_AI_ISNT_AWFUL && aspect < 0)
    4724             :                                 {
    4725             :                                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX;
    4726             :                                 }
    4727             :                                 else if (accuracy < COMBAT_AI_ISNT_AWFUL)
    4728             :                                 {
    4729             :                                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
    4730             :                                 }
    4731             :                                 else
    4732             :                                 {
    4733             :                                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
    4734             :                                 }
    4735             :                         }
    4736             :                         else if (port_weapon_ready || starboard_weapon_ready)
    4737             :                         {
    4738             :                                 jink = kZeroVector; // almost all behaviours
    4739             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE;
    4740             :                         }
    4741             :                         else if (aft_weapon_ready && midrange)
    4742             :                         {
    4743             :                                 jink = kZeroVector; // almost all behaviours
    4744             :                                 behaviour = BEHAVIOUR_RUNNING_DEFENSE;
    4745             :                         } 
    4746             :                         else
    4747             :                         {
    4748             :                                 jink = kZeroVector; // almost all behaviours
    4749             :                                 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
    4750             :                         }
    4751             :                 }
    4752             :         }
    4753             : 
    4754             :         frustration = 0.0;      // behaviour changed, so reset frustration
    4755             :         
    4756             : }
    4757             : 
    4758             : 
    4759             : - (void) behaviour_attack_broadside:(double) delta_t
    4760             : {
    4761             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    4762             :         float   max_available_speed = maxFlightSpeed;
    4763             :         double  range = [self rangeToPrimaryTarget];
    4764             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    4765             :         
    4766             :         if (cloakAutomatic) [self activateCloakingDevice];
    4767             : 
    4768             :         if (![self canStillTrackPrimaryTarget])
    4769             :         {
    4770             :                 [self noteLostTargetAndGoIdle];
    4771             :                 return;
    4772             :         }
    4773             :         
    4774             :         desired_speed = max_available_speed;
    4775             :         if (range < COMBAT_BROADSIDE_IN_RANGE_FACTOR * weaponRange)
    4776             :         {
    4777             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    4778             :         }
    4779             :         else
    4780             :         {
    4781             :                 if (port_weapon_temp < starboard_weapon_temp)
    4782             :                 {
    4783             :                         if (isWeaponNone(port_weapon_type))
    4784             :                         {
    4785             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT;
    4786             :                                 [self setWeaponDataFromType:starboard_weapon_type];
    4787             :                         }
    4788             :                         else
    4789             :                         {
    4790             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT;
    4791             :                                 [self setWeaponDataFromType:port_weapon_type];
    4792             :                         }
    4793             :                 }
    4794             :                 else
    4795             :                 {
    4796             :                         if (isWeaponNone(starboard_weapon_type))
    4797             :                         {
    4798             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT;
    4799             :                                 [self setWeaponDataFromType:starboard_weapon_type];
    4800             :                         }
    4801             :                         else
    4802             :                         {
    4803             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT;
    4804             :                                 [self setWeaponDataFromType:port_weapon_type];
    4805             :                         }
    4806             :                 }
    4807             :                 jink = kZeroVector;
    4808             :                 if (weapon_damage == 0.0)
    4809             :                 { // safety in case side lasers no longer exist
    4810             :                         behaviour = BEHAVIOUR_ATTACK_TARGET;
    4811             :                 }
    4812             :                 else if (range > 0.9 * weaponRange)
    4813             :                 {
    4814             :                         behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE;
    4815             :                 }
    4816             :         }
    4817             : 
    4818             :         frustration = 0.0;      // behaviour changed, so reset frustration
    4819             : 
    4820             :         
    4821             :         
    4822             : }
    4823             : 
    4824             : 
    4825             : - (void) behaviour_attack_broadside_left:(double) delta_t
    4826             : {
    4827             :         [self behaviour_attack_broadside_target:delta_t leftside:YES];
    4828             : }
    4829             : 
    4830             : 
    4831             : - (void) behaviour_attack_broadside_right:(double) delta_t
    4832             : {
    4833             :         [self behaviour_attack_broadside_target:delta_t leftside:NO];
    4834             : }
    4835             : 
    4836             : 
    4837             : - (void) behaviour_attack_broadside_target:(double) delta_t leftside:(BOOL) leftside
    4838             : {
    4839             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    4840             :         float   max_available_speed = maxFlightSpeed;
    4841             :         double  range = [self rangeToPrimaryTarget];
    4842             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    4843             :         if ([self primaryTarget] == nil)
    4844             :         {
    4845             :                 [self noteLostTargetAndGoIdle];
    4846             :                 return;
    4847             :         }
    4848             :         GLfloat currentWeaponRange = getWeaponRangeFromType(leftside?port_weapon_type:starboard_weapon_type);
    4849             :         if (range > COMBAT_BROADSIDE_RANGE_FACTOR * currentWeaponRange)
    4850             :         {
    4851             :                 behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE;
    4852             :                 return;
    4853             :         }
    4854             : 
    4855             : // can get closer on broadsides since there's less risk of a collision
    4856             :         if ((range < COMBAT_BROADSIDE_IN_RANGE_FACTOR * currentWeaponRange)||([self proximityAlert] != nil))
    4857             :         {
    4858             :                 if (![self hasProximityAlertIgnoringTarget:YES])
    4859             :                 {
    4860             :                         behaviour = BEHAVIOUR_ATTACK_TARGET;
    4861             :                 }
    4862             :                 else
    4863             :                 {
    4864             :                         [self avoidCollision];
    4865             :                         return;
    4866             :                 }
    4867             :         }
    4868             :         else
    4869             :         {
    4870             :                 if (![self canStillTrackPrimaryTarget])
    4871             :                 {
    4872             :                         [self noteLostTargetAndGoIdle];
    4873             :                         return;
    4874             :                 }
    4875             :         }
    4876             :         // control speed
    4877             :         //
    4878             :         BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed);
    4879             :         double slow_down_range = currentWeaponRange * COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0);
    4880             : //      double target_speed = [target speed];
    4881             :         if (range <= slow_down_range)
    4882             :                 desired_speed = fmin(0.8 * maxFlightSpeed, fmax((2.0-frustration)*maxFlightSpeed, 0.1 * maxFlightSpeed));   // within the weapon's range slow down to aim
    4883             :         else
    4884             :                 desired_speed = max_available_speed; // use afterburner to approach
    4885             : 
    4886             :         double last_success_factor = success_factor;
    4887             :         success_factor = [self trackSideTarget:delta_t:leftside];       // do the actual piloting
    4888             :         if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE)
    4889             :         { // will probably have more luck with the other laser or picking a different attack method
    4890             :                 if (leftside)
    4891             :                 {
    4892             :                         if (!isWeaponNone(starboard_weapon_type))
    4893             :                         {
    4894             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT;
    4895             :                         }
    4896             :                         else
    4897             :                         {
    4898             :                                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    4899             :                         }
    4900             :                 }
    4901             :                 else
    4902             :                 {
    4903             :                         if (!isWeaponNone(port_weapon_type))
    4904             :                         {
    4905             :                                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT;
    4906             :                         }
    4907             :                         else 
    4908             :                         {
    4909             :                                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    4910             :                         }
    4911             :                 }
    4912             :         }
    4913             : 
    4914             : /* FIXME: again, basically all of this next bit common with standard attack  */
    4915             :         if ((success_factor > 0.999)||(success_factor > last_success_factor))
    4916             :         {
    4917             :                 frustration -= delta_t;
    4918             :                 if (frustration < 0.0)
    4919             :                         frustration = 0.0;
    4920             :         }
    4921             :         else
    4922             :         {
    4923             :                 frustration += delta_t;
    4924             :                 if (frustration > 3.0)       // 3s of frustration
    4925             :                 {
    4926             :                         
    4927             :                         [self noteFrustration:@"BEHAVIOUR_ATTACK_BROADSIDE"];
    4928             :                         [self setEvasiveJink:1000.0];
    4929             :                         behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
    4930             :                         frustration = 0.0;
    4931             :                         desired_speed = maxFlightSpeed;
    4932             :                 }
    4933             :         }
    4934             : 
    4935             :         if (missiles) [self considerFiringMissile:delta_t];
    4936             : 
    4937             :         if (cloakAutomatic) [self activateCloakingDevice];
    4938             :         if (leftside)
    4939             :         {
    4940             :                 [self firePortWeapon:range];
    4941             :         }
    4942             :         else 
    4943             :         {
    4944             :                 [self fireStarboardWeapon:range];
    4945             :         }
    4946             :         
    4947             :         
    4948             : 
    4949             :         if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE)
    4950             :         {
    4951             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    4952             :         }
    4953             : }
    4954             : 
    4955             : 
    4956             : - (void) behaviour_close_to_broadside_range:(double) delta_t
    4957             : {
    4958             :         double  range = [self rangeToPrimaryTarget];
    4959             :         if ([self proximityAlert] != nil)
    4960             :         {
    4961             :                 if ([self proximityAlert] == [self primaryTarget])
    4962             :                 {
    4963             :                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; // this behaviour will handle proximity_alert.
    4964             :                         [self behaviour_attack_fly_from_target: delta_t]; // do it now.
    4965             :                 }
    4966             :                 else
    4967             :                 {
    4968             :                         [self avoidCollision];
    4969             :                 }
    4970             :                 return;
    4971             :         }
    4972             :         if (![self canStillTrackPrimaryTarget])
    4973             :         {
    4974             :                 [self noteLostTargetAndGoIdle];
    4975             :                 return;
    4976             :         }
    4977             : 
    4978             :         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
    4979             :         [self behaviour_fly_to_target_six:delta_t];
    4980             :         if (!isWeaponNone(port_weapon_type))
    4981             :         {
    4982             :                 [self setWeaponDataFromType:port_weapon_type];
    4983             :         }
    4984             :         else
    4985             :         {
    4986             :                 [self setWeaponDataFromType:starboard_weapon_type];
    4987             :         }
    4988             :         if (range <= COMBAT_BROADSIDE_RANGE_FACTOR * weaponRange)
    4989             :         {
    4990             :                 behaviour = BEHAVIOUR_ATTACK_BROADSIDE;
    4991             :         }
    4992             :         else
    4993             :         {
    4994             :                 behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE;
    4995             :         }
    4996             : }
    4997             : 
    4998             : 
    4999             : - (void) behaviour_close_with_target:(double) delta_t
    5000             : {
    5001             :         double  range = [self rangeToPrimaryTarget];
    5002             :         if ([self proximityAlert] != nil)
    5003             :         {
    5004             :                 if ([self proximityAlert] == [self primaryTarget])
    5005             :                 {
    5006             :                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; // this behaviour will handle proximity_alert.
    5007             :                         [self behaviour_attack_fly_from_target: delta_t]; // do it now.
    5008             :                 }
    5009             :                 else
    5010             :                 {
    5011             :                         [self avoidCollision];
    5012             :                 }
    5013             :                 return;
    5014             :         }
    5015             :         if (![self canStillTrackPrimaryTarget])
    5016             :         {
    5017             :                 [self noteLostTargetAndGoIdle];
    5018             :                 return;
    5019             :         }
    5020             :         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
    5021             :         double saved_frustration = frustration;
    5022             :         [self behaviour_fly_to_target_six:delta_t];
    5023             :         frustration = saved_frustration; // ignore fly-to-12 frustration
    5024             :         frustration += delta_t;
    5025             :         if (range <= COMBAT_IN_RANGE_FACTOR * weaponRange || frustration > 5.0)
    5026             :         {
    5027             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    5028             :         }
    5029             :         else
    5030             :         {
    5031             :                 behaviour = BEHAVIOUR_CLOSE_WITH_TARGET;
    5032             :         }
    5033             : 
    5034             : 
    5035             : }
    5036             : 
    5037             : 
    5038             : - (void) behaviour_attack_sniper:(double) delta_t
    5039             : {
    5040             :         if (![self canStillTrackPrimaryTarget])
    5041             :         {
    5042             :                 [self noteLostTargetAndGoIdle];
    5043             :                 return;
    5044             :         }
    5045             :         Entity* rawTarget = [self primaryTarget];
    5046             :         if (![rawTarget isShip])
    5047             :         {
    5048             :                 // can't attack a wormhole
    5049             :                 [self noteLostTargetAndGoIdle];
    5050             :                 return;
    5051             :         }
    5052             :         ShipEntity *target = (ShipEntity *)rawTarget;
    5053             : 
    5054             :         double  range = [self rangeToPrimaryTarget];
    5055             :         float   max_available_speed = maxFlightSpeed;
    5056             : 
    5057             :         if (range < 15000)
    5058             :         {
    5059             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    5060             :         }
    5061             :         else 
    5062             :         {
    5063             :                 if (range > weaponRange || range > scannerRange * 0.8)
    5064             :                 {
    5065             :                         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    5066             :                         if (canBurn && [target weaponRange] > weaponRange && range > weaponRange)
    5067             :                         {
    5068             :                                 // if outside maximum weapon range, but inside target weapon range
    5069             :                                 // close to fight ASAP!
    5070             :                                 max_available_speed *= [self afterburnerFactor];
    5071             :                         }
    5072             :                         desired_speed = max_available_speed;
    5073             :                 }
    5074             :                 else
    5075             :                 {
    5076             :                         desired_speed = max_available_speed / 10.0f;
    5077             :                 }
    5078             : 
    5079             :                 double last_success_factor = success_factor;
    5080             :                 success_factor = [self trackPrimaryTarget:delta_t:NO];
    5081             :                 
    5082             :                 if ((success_factor > 0.999)||(success_factor > last_success_factor))
    5083             :                 {
    5084             :                         frustration -= delta_t;
    5085             :                         if (frustration < 0.0)
    5086             :                                 frustration = 0.0;
    5087             :                 }
    5088             :                 else
    5089             :                 {
    5090             :                         frustration += delta_t;
    5091             :                         if (frustration > 3.0)       // 3s of frustration
    5092             :                         {
    5093             :                                 [self noteFrustration:@"BEHAVIOUR_ATTACK_SNIPER"];
    5094             :                                 [self setEvasiveJink:1000.0];
    5095             :                                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    5096             :                                 frustration = 0.0;
    5097             :                                 desired_speed = maxFlightSpeed;
    5098             :                         }
    5099             :                 }
    5100             : 
    5101             :         }
    5102             : 
    5103             :         if (missiles) [self considerFiringMissile:delta_t];
    5104             : 
    5105             :         if (cloakAutomatic) [self activateCloakingDevice];
    5106             :         [self fireMainWeapon:range];
    5107             : 
    5108             :         if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE && accuracy >= COMBAT_AI_ISNT_AWFUL)
    5109             :         {
    5110             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    5111             :         }
    5112             : 
    5113             : }
    5114             : 
    5115             : 
    5116             : - (void) behaviour_fly_to_target_six:(double) delta_t
    5117             : {
    5118             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    5119             :         float   max_available_speed = maxFlightSpeed;
    5120             :         double  range = [self rangeToPrimaryTarget];
    5121             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    5122             :         
    5123             :         // deal with collisions and lost targets
    5124             :         if ([self proximityAlert] != nil)
    5125             :         {
    5126             :                 if ([self proximityAlert] == [self primaryTarget])
    5127             :                 {
    5128             :                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET; // this behaviour will handle proximity_alert.
    5129             :                         [self behaviour_attack_fly_from_target: delta_t]; // do it now.
    5130             :                 }
    5131             :                 else
    5132             :                 {
    5133             :                         [self avoidCollision];
    5134             :                 }
    5135             :                 return;
    5136             :         }
    5137             :         if (![self canStillTrackPrimaryTarget])
    5138             :         {
    5139             :                 [self noteLostTargetAndGoIdle];
    5140             :                 return;
    5141             :         }
    5142             : 
    5143             :         // control speed
    5144             :         BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed);
    5145             :         BOOL closeQuickly = (canBurn && range > weaponRange);
    5146             :         double  slow_down_range = weaponRange * COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0);
    5147             :         if (closeQuickly)
    5148             :         {
    5149             :                 slow_down_range = weaponRange * COMBAT_OUT_RANGE_FACTOR;
    5150             :         }
    5151             :         double  back_off_range = weaponRange * COMBAT_OUT_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0);
    5152             :         Entity* rawTarget = [self primaryTarget];
    5153             :         if (![rawTarget isShip])
    5154             :         {
    5155             :                 [self noteLostTargetAndGoIdle];
    5156             :                 return;
    5157             :         }
    5158             :         ShipEntity*     target = (ShipEntity *)rawTarget;
    5159             :         double target_speed = [target speed];
    5160             :         double last_success_factor = success_factor;
    5161             :         double distance = [self rangeToDestination];
    5162             :         success_factor = distance;
    5163             :                 
    5164             :         if (range < slow_down_range && (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX))
    5165             :         {
    5166             :                 if (range < back_off_range)
    5167             :                 {
    5168             :                         desired_speed = fmax(0.9 * target_speed, 0.4 * maxFlightSpeed);
    5169             :                 } 
    5170             :                 else
    5171             :                 {
    5172             :                         desired_speed = fmax(target_speed * 1.2, maxFlightSpeed);
    5173             :                 }
    5174             :                 
    5175             :                 // avoid head-on collision
    5176             :                 if ((range < 0.5 * distance)&&(behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX))
    5177             :                         behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
    5178             :         }
    5179             :         else
    5180             :         {
    5181             :                 if (range < back_off_range)
    5182             :                 {
    5183             :                         desired_speed = fmax(0.9 * target_speed, 0.8 * maxFlightSpeed);
    5184             :                 } 
    5185             :                 else 
    5186             :                 {
    5187             :                         desired_speed = max_available_speed; // use afterburner to approach
    5188             :                 }
    5189             :         }
    5190             : 
    5191             : 
    5192             :         // if within 0.75km of the target's six or twelve, or if target almost at standstill for 62.5% of non-thargoid ships (!),
    5193             :         // then vector in attack. 
    5194             :         if (distance < 750.0 || (target_speed < 0.2 && ![self isThargoid] && ([self universalID] & 14) > 4))
    5195             :         {
    5196             :                 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
    5197             :                 frustration = 0.0;
    5198             :                 desired_speed = fmax(target_speed, 0.4 * maxFlightSpeed);   // within the weapon's range don't use afterburner
    5199             :         }
    5200             : 
    5201             :         // target-six
    5202             :         if (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX)
    5203             :         {
    5204             :                 // head for a point weapon-range * 0.5 to the six of the target
    5205             :                 //
    5206             :                 _destination = [target distance_six:0.5 * weaponRange];
    5207             :         }
    5208             :         // target-twelve
    5209             :         if (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE)
    5210             :         {
    5211             :                 if ([forward_weapon_type isTurretLaser])
    5212             :                 {
    5213             :                         // head for a point near the target, avoiding common Galcop weapon mount locations
    5214             :                         // TODO: this should account for weapon ranges
    5215             :                         GLfloat offset = 1000.0;
    5216             :                         GLfloat spacing = 2000.0;
    5217             :                         if (accuracy > 0.0) 
    5218             :                         {
    5219             :                                 offset = accuracy * 750.0;
    5220             :                                 spacing = 2000.0 + (accuracy * 500.0);
    5221             :                         }
    5222             :                         if (entity_personality & 1)
    5223             :                         { // half at random
    5224             :                                 offset = -offset;
    5225             :                         }
    5226             :                         _destination = [target distance_twelve:spacing withOffset:offset];
    5227             :                 }
    5228             :                 else 
    5229             :                 {
    5230             :                         // head for a point 1.25km above the target
    5231             :                         _destination = [target distance_twelve:1250 withOffset:0];
    5232             :                 }
    5233             :         }
    5234             : 
    5235             :         pitching_over = NO; // in case it's set from elsewhere
    5236             :         double confidenceFactor = [self trackDestination:delta_t :NO];
    5237             :         
    5238             :         if(success_factor > last_success_factor || confidenceFactor < 0.85) frustration += delta_t;
    5239             :         else if(frustration > 0.0) frustration -= delta_t * 0.75;
    5240             : 
    5241             :         double aspect = [self approachAspectToPrimaryTarget];
    5242             :         if(![forward_weapon_type isTurretLaser] && (frustration > 10 || aspect > 0.75))
    5243             :         {
    5244             :                 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
    5245             :         }
    5246             : 
    5247             :         // use weaponry
    5248             :         if (missiles) [self considerFiringMissile:delta_t];
    5249             : 
    5250             :         if (cloakAutomatic) [self activateCloakingDevice];
    5251             :         [self fireMainWeapon:range];
    5252             :         
    5253             :         
    5254             : 
    5255             :         if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE)
    5256             :         {
    5257             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    5258             :         }
    5259             : }
    5260             : 
    5261             : 
    5262             : - (void) behaviour_attack_mining_target:(double) delta_t
    5263             : {
    5264             :         double  range = [self rangeToPrimaryTarget];
    5265             :         if (![self canStillTrackPrimaryTarget])
    5266             :         {
    5267             :                 [self noteLostTargetAndGoIdle];
    5268             :                 desired_speed = maxFlightSpeed * 0.375;
    5269             :                 return;
    5270             :         }
    5271             :         else if ((range < 650) || ([self proximityAlert] != nil))
    5272             :         {
    5273             :                 if ([self proximityAlert] == NO_TARGET)
    5274             :                 {
    5275             :                         desired_speed = range * maxFlightSpeed / (650.0 * 16.0);
    5276             :                 }
    5277             :                 else
    5278             :                 {
    5279             :                         [self avoidCollision];
    5280             :                 }
    5281             :         }
    5282             :         else
    5283             :         {
    5284             :                 //we have a target, its within scanner range, and outside 650
    5285             :                 desired_speed = maxFlightSpeed * 0.875;
    5286             :         }
    5287             : 
    5288             :         [self trackPrimaryTarget:delta_t:NO];
    5289             : 
    5290             :         /* Don't open fire until within 3km - it doesn't take many mining
    5291             :          * laser shots to destroy an asteroid, but some of these mining
    5292             :          * ships are way too slow to effectively chase down the debris:
    5293             :          * wait until reasonably close before trying to split it. */
    5294             :         if (range < 3000)
    5295             :         {
    5296             :                 [self fireMainWeapon:range];
    5297             :         }
    5298             :         
    5299             :         
    5300             : }
    5301             : 
    5302             : 
    5303             : - (void) behaviour_attack_fly_to_target:(double) delta_t
    5304             : {
    5305             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    5306             :         float   max_available_speed = maxFlightSpeed;
    5307             :         double  range = [self rangeToPrimaryTarget];
    5308             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    5309             :         if ([self primaryTarget] == nil)
    5310             :         {
    5311             :                 [self noteLostTargetAndGoIdle];
    5312             :                 return;
    5313             :         }
    5314             :         Entity* rawTarget = [self primaryTarget];
    5315             :         if (![rawTarget isShip])
    5316             :         {
    5317             :                 // can't attack a wormhole
    5318             :                 [self noteLostTargetAndGoIdle];
    5319             :                 return;
    5320             :         }
    5321             : 
    5322             :         ShipEntity *target = (ShipEntity *)rawTarget;
    5323             :         if ((range < COMBAT_IN_RANGE_FACTOR * weaponRange)||([self proximityAlert] != nil))
    5324             :         {
    5325             :                 if (![self hasProximityAlertIgnoringTarget:YES])
    5326             :                 {
    5327             :                         behaviour = BEHAVIOUR_ATTACK_TARGET;
    5328             :                 }
    5329             :                 else
    5330             :                 {
    5331             :                         [self avoidCollision];
    5332             :                         return;
    5333             :                 }
    5334             :         }
    5335             :         else
    5336             :         {
    5337             :                 if (![self canStillTrackPrimaryTarget])
    5338             :                 {
    5339             :                         [self noteLostTargetAndGoIdle];
    5340             :                         return;
    5341             :                 }
    5342             :         }
    5343             : 
    5344             :         // control speed
    5345             :         //
    5346             :         BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed);
    5347             :         BOOL closeQuickly = (canBurn && [target weaponRange] > weaponRange && range > weaponRange);
    5348             :         double slow_down_range = weaponRange * COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0);
    5349             :         if (closeQuickly)
    5350             :         {
    5351             :                 slow_down_range = weaponRange * COMBAT_OUT_RANGE_FACTOR;
    5352             :         }
    5353             :         double  back_off_range = 10000 * COMBAT_OUT_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [self afterburnerFactor] : 1.0);
    5354             :         double target_speed = [target speed];
    5355             :         double aspect = [self approachAspectToPrimaryTarget];
    5356             : 
    5357             :         if (range <= slow_down_range)
    5358             :         {
    5359             :                 if (range < back_off_range)
    5360             :                 {
    5361             :                         if (accuracy < COMBAT_AI_IS_SMART || ([target primaryTarget] == self && aspect > 0.8) || aim_tolerance*range > COMBAT_AI_CONFIDENCE_FACTOR)
    5362             :                         {
    5363             :                                 if (accuracy >= COMBAT_AI_FLEES_BETTER && aspect > 0.8)
    5364             :                                 {
    5365             :                                         desired_speed = fmax(target_speed * 1.25, 0.8 * maxFlightSpeed);
    5366             :                                         // stay at high speed if might be taking return fire
    5367             :                                 }
    5368             :                                 else
    5369             :                                 {
    5370             :                                         desired_speed = fmax(target_speed * 1.05, 0.25 * maxFlightSpeed);   // within the weapon's range match speed
    5371             : 
    5372             :                                 }
    5373             :                         }
    5374             :                         else
    5375             :                         { // smart, and not being shot at right now - slow down to attack
    5376             :                                 desired_speed = fmax(0.1 * target_speed, 0.1 * maxFlightSpeed);
    5377             :                         }
    5378             :                 }
    5379             :                 else
    5380             :                 {
    5381             :                         if (accuracy < COMBAT_AI_IS_SMART || ([target isShip] && [(ShipEntity *)target primaryTarget] == self) || range > weaponRange / 2.0)
    5382             :                         {
    5383             :                                 desired_speed = fmax(target_speed * 1.5, maxFlightSpeed);
    5384             :                         }
    5385             :                         else
    5386             :                         { // smart, and not being shot at right now - slow down to attack
    5387             :                                 if (aspect > -0.25)
    5388             :                                 {
    5389             :                                         desired_speed = fmax(0.5 * target_speed, 0.5 * maxFlightSpeed);
    5390             :                                 }
    5391             :                                 else
    5392             :                                 {
    5393             :                                         desired_speed = fmax(1.25 * target_speed, 0.5 * maxFlightSpeed);
    5394             :                                 }
    5395             :                         }
    5396             :                 }
    5397             :         }
    5398             :         else
    5399             :         {
    5400             :                 if (closeQuickly)
    5401             :                 {
    5402             :                         desired_speed = max_available_speed; // use afterburner to approach
    5403             :                 }
    5404             :                 else
    5405             :                 {
    5406             :                         desired_speed = fmax(maxFlightSpeed,fmin(3.0 * target_speed, max_available_speed)); // possibly use afterburner to approach
    5407             :                 }
    5408             :         }
    5409             : 
    5410             : 
    5411             :         double last_success_factor = success_factor;
    5412             :         success_factor = [self trackPrimaryTarget:delta_t:NO];  // do the actual piloting
    5413             : 
    5414             :         if ((success_factor > 0.999)||(success_factor > last_success_factor))
    5415             :         {
    5416             :                 frustration -= delta_t;
    5417             :                 if (frustration < 0.0)
    5418             :                         frustration = 0.0;
    5419             :         }
    5420             :         else
    5421             :         {
    5422             :                 frustration += delta_t;
    5423             :                 if (frustration > 3.0)       // 3s of frustration
    5424             :                 {
    5425             :                         [self noteFrustration:@"BEHAVIOUR_ATTACK_FLY_TO_TARGET"];
    5426             :                         [self setEvasiveJink:1000.0];
    5427             :                         behaviour = BEHAVIOUR_ATTACK_TARGET;
    5428             :                         frustration = 0.0;
    5429             :                         desired_speed = maxFlightSpeed;
    5430             :                 }
    5431             :         }
    5432             : 
    5433             :         if (missiles) [self considerFiringMissile:delta_t];
    5434             : 
    5435             :         if (cloakAutomatic) [self activateCloakingDevice];
    5436             :         [self fireMainWeapon:range];
    5437             :         
    5438             :         
    5439             : 
    5440             :         if (weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE && accuracy >= COMBAT_AI_ISNT_AWFUL && aim_tolerance * range < COMBAT_AI_CONFIDENCE_FACTOR)
    5441             :         {
    5442             :                 // don't do this if the target is fleeing and the front laser is
    5443             :                 // the only weapon, or if we're too far away to use non-front
    5444             :                 // lasers effectively
    5445             :                 if (aspect < 0 || 
    5446             :                         !isWeaponNone(aft_weapon_type) ||
    5447             :                         !isWeaponNone(port_weapon_type) ||
    5448             :                         !isWeaponNone(starboard_weapon_type))
    5449             :                 {
    5450             :                         frustration = 0.0;
    5451             :                         behaviour = BEHAVIOUR_ATTACK_TARGET;
    5452             :                 }
    5453             :         }
    5454             :         else if (accuracy >= COMBAT_AI_FLEES_BETTER_2) 
    5455             :         {
    5456             :                 // if we're right in their gunsights, dodge!
    5457             :                 // need to dodge sooner if in aft sights
    5458             :                 if ([target behaviour] != BEHAVIOUR_FLEE_TARGET && [target behaviour] != BEHAVIOUR_FLEE_EVASIVE_ACTION)
    5459             :                 {
    5460             :                         if ((aspect > 0.99999 && !isWeaponNone([target weaponTypeForFacing:WEAPON_FACING_FORWARD strict:NO])) || (aspect < -0.999 && !isWeaponNone([target weaponTypeForFacing:WEAPON_FACING_AFT strict:NO])))
    5461             :                         {
    5462             :                                 frustration = 0.0;
    5463             :                                 behaviour = BEHAVIOUR_EVASIVE_ACTION;
    5464             :                         }
    5465             :                 }
    5466             :         }
    5467             : }
    5468             : 
    5469             : 
    5470             : - (void) behaviour_attack_fly_from_target:(double) delta_t
    5471             : {
    5472             :         double  range = [self rangeToPrimaryTarget];
    5473             :         double last_success_factor = success_factor;
    5474             :         success_factor = range;
    5475             :         
    5476             :         if ([self primaryTarget] == nil)
    5477             :         {
    5478             :                 [self noteLostTargetAndGoIdle];
    5479             :                 return;
    5480             :         }
    5481             :         if (last_success_factor > success_factor) // our target is closing in.
    5482             :         {
    5483             :                 frustration += delta_t;
    5484             :         }
    5485             :         else
    5486             :         { // not getting away fast enough?
    5487             :                 frustration += delta_t / 4.0 ;
    5488             :         }
    5489             : 
    5490             :         if (frustration > 10.0)
    5491             :         {
    5492             :                 if (randf() < 0.3) {
    5493             :                         desired_speed = maxFlightSpeed * (([self hasFuelInjection] && (fuel > MIN_FUEL)) ? [self afterburnerFactor] : 1);
    5494             :                 }
    5495             :                 else if (range > COMBAT_IN_RANGE_FACTOR * weaponRange && randf() < 0.3)
    5496             :                 {
    5497             :                         behaviour = BEHAVIOUR_ATTACK_TARGET;
    5498             :                 }
    5499             :                 GLfloat z = jink.z;
    5500             :                 if (randf() < 0.3)
    5501             :                 {
    5502             :                         z /= 2; // move the z-offset closer to the target to let him fly away from the target.
    5503             :                         desired_speed = flightSpeed * 2; // increase speed a bit.
    5504             :                 }
    5505             :                 [self setEvasiveJink:z];
    5506             : 
    5507             :                 frustration /= 2.0;
    5508             :         }
    5509             :         if (desired_speed > maxFlightSpeed)
    5510             :         {
    5511             :                 ShipEntity*     target = [self primaryTarget];
    5512             :                 double target_speed = [target speed];
    5513             :                 if (desired_speed > target_speed * 2.0)
    5514             :                 {
    5515             :                         desired_speed = maxFlightSpeed; // don't overuse the injectors
    5516             :                 }
    5517             :         }
    5518             :         else if (desired_speed < maxFlightSpeed * 0.5)
    5519             :         {
    5520             :                 desired_speed = maxFlightSpeed;
    5521             :         }
    5522             : 
    5523             :         if (range > COMBAT_OUT_RANGE_FACTOR * weaponRange + 15.0 * jink.x || 
    5524             :                         flightSpeed > (scannerRange - range) * max_flight_pitch / 6.28)
    5525             :         {
    5526             :                 jink = kZeroVector;
    5527             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    5528             :                 frustration = 0.0;
    5529             :         }
    5530             :         [self trackPrimaryTarget:delta_t:YES];
    5531             : 
    5532             :         if (missiles) [self considerFiringMissile:delta_t];
    5533             : 
    5534             :         if (cloakAutomatic) [self activateCloakingDevice];
    5535             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5536             :                 [self avoidCollision];
    5537             : 
    5538             :         if (accuracy >= COMBAT_AI_FLEES_BETTER_2) 
    5539             :         {
    5540             :                 double aspect = [self approachAspectToPrimaryTarget];
    5541             :                 // if we're right in their gunsights, dodge!
    5542             :                 // need to dodge sooner if in aft sights
    5543             :                 if (aspect > 0.99999 || aspect < -0.999) 
    5544             :                 {
    5545             :                         frustration = 0.0;
    5546             :                         behaviour = BEHAVIOUR_EVASIVE_ACTION;
    5547             :                 }
    5548             :         }
    5549             :         
    5550             : }
    5551             : 
    5552             : 
    5553             : - (void) behaviour_running_defense:(double) delta_t
    5554             : {
    5555             :         if (![self canStillTrackPrimaryTarget])
    5556             :         {
    5557             :                 [self noteLostTargetAndGoIdle];
    5558             :                 return;
    5559             :         }
    5560             : 
    5561             :         double  range = [self rangeToPrimaryTarget];
    5562             :         desired_speed = maxFlightSpeed; // not injectors
    5563             :         jink = kZeroVector;
    5564             :         if (range > weaponRange || range > 0.8 * scannerRange || range == 0)
    5565             :         {
    5566             :                 behaviour = BEHAVIOUR_CLOSE_WITH_TARGET;
    5567             :                 if ([forward_weapon_type isTurretLaser]) 
    5568             :                 {
    5569             :                                 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
    5570             :                 } 
    5571             :                 frustration = 0.0;
    5572             :         }
    5573             :         [self trackPrimaryTarget:delta_t:YES];
    5574             :         if ([forward_weapon_type isTurretLaser]) 
    5575             :         {
    5576             :                 // most Thargoids will only have the forward weapon
    5577             :                 [self fireMainWeapon:range];
    5578             :         }
    5579             :         else 
    5580             :         {
    5581             :                 [self fireAftWeapon:range];
    5582             :         }
    5583             :         if (cloakAutomatic) [self activateCloakingDevice];
    5584             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5585             :                 [self avoidCollision];
    5586             : 
    5587             :         if (behaviour != BEHAVIOUR_CLOSE_WITH_TARGET && weapon_temp > COMBAT_AI_WEAPON_TEMP_USABLE)
    5588             :         {
    5589             :                 behaviour = BEHAVIOUR_ATTACK_TARGET;
    5590             :         }
    5591             : 
    5592             :         // remember to look where you're going?
    5593             :         if (accuracy >= COMBAT_AI_ISNT_AWFUL && [self hasProximityAlertIgnoringTarget:YES])
    5594             :         {
    5595             :                 [self avoidCollision];
    5596             :         }
    5597             : 
    5598             : 
    5599             : }
    5600             : 
    5601             : 
    5602             : - (void) behaviour_flee_target:(double) delta_t
    5603             : {
    5604             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    5605             :         float   max_available_speed = maxFlightSpeed;
    5606             :         double  range = [self rangeToPrimaryTarget];
    5607             :         if ([self primaryTarget] == nil)
    5608             :         {
    5609             :                 [self noteLostTargetAndGoIdle];
    5610             :                 return;
    5611             :         }
    5612             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    5613             :         
    5614             :         double last_range = success_factor;
    5615             :         success_factor = range;
    5616             : 
    5617             :         if (range > desired_range || range == 0)
    5618             :                 [shipAI message:@"REACHED_SAFETY"];
    5619             :         else
    5620             :                 desired_speed = max_available_speed;
    5621             : 
    5622             :         if (range > last_range)      // improvement
    5623             :         {
    5624             :                 frustration -= 0.25 * delta_t;
    5625             :                 if (frustration < 0.0)
    5626             :                         frustration = 0.0;
    5627             :         }
    5628             :         else
    5629             :         {
    5630             :                 frustration += delta_t;
    5631             :                 if (frustration > 15.0)      // 15s of frustration
    5632             :                 {
    5633             :                         [self noteFrustration:@"BEHAVIOUR_FLEE_TARGET"];
    5634             :                         frustration = 0.0;
    5635             :                 }
    5636             :         }
    5637             : 
    5638             :         [self trackPrimaryTarget:delta_t:YES];
    5639             : 
    5640             :         Entity *target = [self primaryTarget];
    5641             : 
    5642             :         if (missiles && [target isShip] && [(ShipEntity *)target primaryTarget] == self)
    5643             :         {
    5644             :                 [self considerFiringMissile:delta_t];
    5645             :         }
    5646             : 
    5647             :         if (([self hasCascadeMine]) && (range < 10000.0) && canBurn)
    5648             :         {
    5649             :                 float   qbomb_chance = 0.01 * delta_t;
    5650             :                 if (randf() < qbomb_chance)
    5651             :                 {
    5652             :                         [self launchCascadeMine];
    5653             :                 }
    5654             :         }
    5655             : 
    5656             : // thargoids won't normally be fleeing, but if they do, they can still shoot
    5657             :         if ([forward_weapon_type isTurretLaser])
    5658             :         {
    5659             :                 [self fireMainWeapon:range];
    5660             :         }
    5661             : 
    5662             :         if (cloakAutomatic) [self activateCloakingDevice];
    5663             : 
    5664             :         // remember to look where you're going?
    5665             :         if (accuracy >= COMBAT_AI_ISNT_AWFUL && [self hasProximityAlertIgnoringTarget:YES])
    5666             :         {
    5667             :                 [self avoidCollision];
    5668             :         }
    5669             : 
    5670             : }
    5671             : 
    5672             : 
    5673             : - (void) behaviour_fly_range_from_destination:(double) delta_t
    5674             : {
    5675             :         double distance = [self rangeToDestination];
    5676             :         if (distance < desired_range)
    5677             :         {
    5678             :                 behaviour = BEHAVIOUR_FLY_FROM_DESTINATION;
    5679             :                 if (desired_speed < maxFlightSpeed) 
    5680             :                 {
    5681             :                         desired_speed = maxFlightSpeed;  // Not all AI define speed when flying away. Start with max speed to stay compatible with such AI's, but allow faster flight if it's (e.g.) used to flee from coordinates rather than entity
    5682             :                 }
    5683             :         }
    5684             :         else
    5685             :         {
    5686             :                 behaviour = BEHAVIOUR_FLY_TO_DESTINATION;
    5687             :         }
    5688             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5689             :         {
    5690             :                 [self avoidCollision];
    5691             :         }
    5692             :         frustration = 0.0;
    5693             : 
    5694             :         
    5695             :         
    5696             : }
    5697             : 
    5698             : 
    5699             : - (void) behaviour_face_destination:(double) delta_t
    5700             : {
    5701             :         double max_cos = MAX_COS;
    5702             :         double distance = [self rangeToDestination];
    5703             :         double old_pitch = flightPitch;
    5704             :         desired_speed = 0.0;
    5705             :         if (desired_range > 1.0 && distance > desired_range)
    5706             :         {
    5707             :                 max_cos = sqrt(1 - 0.90 * desired_range*desired_range/(distance * distance));   // Head for a point within 95% of desired_range (must match the value in trackDestination)
    5708             :         }
    5709             :         double confidenceFactor = [self trackDestination:delta_t:NO];
    5710             :         if (confidenceFactor >= max_cos && flightPitch == 0.0)
    5711             :         {
    5712             :                 // desired facing achieved and movement stabilised.
    5713             :                 [shipAI message:@"FACING_DESTINATION"];
    5714             :                 [self doScriptEvent:OOJSID("shipNowFacingDestination")];
    5715             :                 frustration = 0.0;
    5716             :                 if(docking_match_rotation)  // IDLE stops rotating while docking
    5717             :                 {
    5718             :                         behaviour = BEHAVIOUR_FLY_TO_DESTINATION;
    5719             :                 }
    5720             :                 else
    5721             :                 {
    5722             :                         behaviour = BEHAVIOUR_IDLE;
    5723             :                 }
    5724             :         }
    5725             : 
    5726             :         if(flightSpeed == 0) frustration += delta_t;
    5727             :         if (frustration > 15.0 / max_flight_pitch)   // allow more time for slow ships.
    5728             :         {
    5729             :                 frustration = 0.0;
    5730             :                 [self noteFrustration:@"BEHAVIOUR_FACE_DESTINATION"];
    5731             :                 if(flightPitch == old_pitch) flightPitch = 0.5 * max_flight_pitch; // hack to get out of frustration.
    5732             :         }       
    5733             :         
    5734             :         /* 2009-7-18 Eric: the condition check below is intended to eliminate the flippering between two positions for fast turning ships
    5735             :            during low FPS conditions. This flippering is particular frustrating on slow computers during docking. But with my current computer I can't
    5736             :            induce those low FPS conditions so I can't properly test if it helps.
    5737             :            I did try with the TAF time acceleration that also generated larger frame jumps and than it seemed to help.
    5738             :         */
    5739             :         if(flightSpeed == 0 && frustration > 5 && confidenceFactor > 0.5 && ((flightPitch > 0 && old_pitch < 0) || (flightPitch < 0 && old_pitch > 0)))
    5740             :         {
    5741             :                 flightPitch += 0.5 * old_pitch; // damping with last pitch value.
    5742             :         }
    5743             :         
    5744             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5745             :         {
    5746             :                 [self avoidCollision];
    5747             :         }
    5748             :         
    5749             :         
    5750             : }
    5751             : 
    5752             : 
    5753             : - (void) behaviour_land_on_planet:(double) delta_t
    5754             : {
    5755             :         double max_cos = MAX_COS2; // trackDestination returns the squared confidence in reverse mode.
    5756             :         desired_speed = 0.0;
    5757             :         
    5758             :         OOPlanetEntity* planet = [UNIVERSE entityForUniversalID:planetForLanding];
    5759             :         
    5760             :         if (![planet isPlanet]) 
    5761             :         {
    5762             :                 behaviour = BEHAVIOUR_IDLE;
    5763             :                 aiScriptWakeTime = 1; // reconsider JSAI
    5764             :                 [shipAI message:@"NO_PLANET_NEARBY"];
    5765             :                 return;
    5766             :         }
    5767             :                   
    5768             :         if (HPdistance(position, [planet position]) + [self collisionRadius] < [planet radius])
    5769             :         {
    5770             :                 // we have landed. (completely disappeared inside planet)
    5771             :                 [self landOnPlanet:planet];
    5772             :                 return;
    5773             :         }
    5774             : 
    5775             :         double confidenceFactor = [self trackDestination:delta_t:YES]; // turn away from destination
    5776             :         
    5777             :         if (confidenceFactor >= max_cos && flightSpeed == 0.0)
    5778             :         {
    5779             :                 // We are now turned away from planet. Start landing by flying backward.
    5780             :                 thrust = 0.0; // stop forward acceleration.
    5781             :                 if (magnitude2(velocity) < MAX_LANDING_SPEED2)
    5782             :                 {
    5783             :                         [self adjustVelocity:vector_multiply_scalar([self forwardVector], -max_thrust * delta_t)];
    5784             :                 }
    5785             :         }
    5786             :         
    5787             :         
    5788             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5789             :         {
    5790             :                 [self avoidCollision];
    5791             :         }
    5792             :         
    5793             :         
    5794             : }
    5795             : 
    5796             : 
    5797             : - (void) behaviour_formation_form_up:(double) delta_t
    5798             : {
    5799             :         // destination for each escort is set in update() from owner.
    5800             :         ShipEntity* leadShip = [self owner];
    5801             :         double distance = [self rangeToDestination];
    5802             :         double eta = (distance - desired_range) / flightSpeed;
    5803             :         if(eta < 0) eta = 0;
    5804             :         if ((eta < 5.0)&&(leadShip)&&(leadShip->isShip))
    5805             :                 desired_speed = [leadShip flightSpeed] * (1 + eta * 0.05);
    5806             :         else
    5807             :                 desired_speed = maxFlightSpeed;
    5808             : 
    5809             :         double last_distance = success_factor;
    5810             :         success_factor = distance;
    5811             : 
    5812             :         // do the actual piloting!!
    5813             :         [self trackDestination:delta_t: NO];
    5814             : 
    5815             :         eta = eta / 0.51;       // 2% safety margin assuming an average of half current speed
    5816             :         GLfloat slowdownTime = (thrust > 0.0)? flightSpeed / (thrust) : 4.0;
    5817             :         GLfloat minTurnSpeedFactor = 0.05 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds
    5818             : 
    5819             :         if ((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor))
    5820             :                 desired_speed = flightSpeed * 0.50;   // cut speed by 50% to a minimum minTurnSpeedFactor of speed
    5821             :                 
    5822             :         if (distance < last_distance)        // improvement
    5823             :         {
    5824             :                 frustration -= 0.25 * delta_t;
    5825             :                 if (frustration < 0.0)
    5826             :                         frustration = 0.0;
    5827             :         }
    5828             :         else
    5829             :         {
    5830             :                 frustration += delta_t;
    5831             :                 if (frustration > 15.0)
    5832             :                 {
    5833             :                         if (!leadShip) [self noteFrustration:@"BEHAVIOUR_FORMATION_FORM_UP"]; // escorts never reach their destination when following leader.
    5834             :                         else if (distance > 0.5 * scannerRange && !pitching_over) 
    5835             :                         {
    5836             :                                 pitching_over = YES; // Force the ship in a 180 degree turn. Do it here to allow escorts to break out formation for some seconds.
    5837             :                         }
    5838             :                         frustration = 0;
    5839             :                 }
    5840             :         }
    5841             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5842             :         {
    5843             :                 [self avoidCollision];
    5844             :         }
    5845             :         
    5846             :         
    5847             : }
    5848             : 
    5849             : 
    5850             : - (void) behaviour_fly_to_destination:(double) delta_t
    5851             : {
    5852             :         double distance = [self rangeToDestination];
    5853             :         // double desiredRange = (dockingInstructions != nil) ? 1.2 * desired_range : desired_range; // stop a bit earlyer when docking.
    5854             :         if (distance < desired_range) // + collision_radius)
    5855             :         {
    5856             :                 // desired range achieved
    5857             :                 [shipAI message:@"DESIRED_RANGE_ACHIEVED"];
    5858             :                 [self doScriptEvent:OOJSID("shipAchievedDesiredRange")];
    5859             : 
    5860             :                 if(!docking_match_rotation) // IDLE stops rotating while docking
    5861             :                 {
    5862             :                         behaviour = BEHAVIOUR_IDLE;
    5863             :                         desired_speed = 0.0;
    5864             :                 }
    5865             :                 frustration = 0.0;
    5866             :         }
    5867             :         else
    5868             :         {
    5869             :                 double last_distance = success_factor;
    5870             :                 success_factor = distance;
    5871             : 
    5872             :                 // do the actual piloting!!
    5873             :                 double confidenceFactor = [self trackDestination:delta_t: NO];
    5874             :                 if(confidenceFactor < 0.2) confidenceFactor = 0.2;  // don't allow small or negative values.
    5875             :                 
    5876             :                 /*      2009-07-19 Eric: Estimated Time of Arrival (eta) should also take the "angle to target" into account (confidenceFactor = cos(angle to target))
    5877             :                         and should not fuss about that last meter and use "distance + 1" instead of just "distance".
    5878             :                         trackDestination already did pitch regulation, use confidence here only for cutting down to high speeds.
    5879             :                         This should prevent ships crawling to their destination when they try to pull up close to their destination.
    5880             :                         
    5881             :                         To prevent ships circling around their target without reaching destination I added a limitation based on turnrate,
    5882             :                         speed and distance to target. Formula based on satelite orbit:
    5883             :                                         orbitspeed = turnrate (rad/sec) * radius (m)   or   flightSpeed = max_flight_pitch * 2 Pi * distance
    5884             :                         Speed must be significant lower when not flying in direction of target (low confidenceFactor) or it can never approach its destination 
    5885             :                         and the ships runs the risk flying in circles around the target. (exclude active escorts)
    5886             :                 */
    5887             :                 GLfloat eta = ((distance + 1) - desired_range) / (0.51 * flightSpeed * confidenceFactor);       // 2% safety margin assuming an average of half current speed
    5888             :                 GLfloat slowdownTime = (thrust > 0.0)? flightSpeed / (thrust) : 4.0;
    5889             :                 GLfloat minTurnSpeedFactor = 0.05 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds
    5890             :                 if (dockingInstructions != nil)
    5891             :                 {
    5892             :                         minTurnSpeedFactor /= 10.0;
    5893             :                         if (minTurnSpeedFactor * maxFlightSpeed > 20.0)
    5894             :                         {
    5895             :                                 minTurnSpeedFactor /= 10.0;
    5896             :                         }
    5897             :                 }
    5898             : 
    5899             : 
    5900             :                 if (((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor)) || (flightSpeed > max_flight_pitch * 5 * confidenceFactor * distance))
    5901             :                 {
    5902             :                         desired_speed = flightSpeed * 0.50;   // cut speed by 50% to a minimum minTurnSpeedFactor of speed
    5903             :                 }
    5904             : 
    5905             :                 /* Flight correction block to prevent one possible form of
    5906             :                  * crashes in late docking process */
    5907             :                 if (docking_match_rotation && confidenceFactor >= MAX_COS && dockingInstructions != nil && [dockingInstructions oo_intForKey:@"docking_stage"] >= 7)
    5908             :                 {
    5909             :                         // then at this point should be rotating to match the station
    5910             :                         StationEntity* station_for_docking = (StationEntity*)[self targetStation];
    5911             : 
    5912             :                         if ((station_for_docking)&&(station_for_docking->isStation))
    5913             :                         {
    5914             :                                 float rollMatch = dot_product([station_for_docking portUpVectorForShip:self],[self upVector]);
    5915             :                                 if (rollMatch < MAX_COS && rollMatch > -MAX_COS)
    5916             :                                 {
    5917             :                                         // not matching rotating - stop until corrected
    5918             :                                         desired_speed = 0.1;
    5919             :                                 }
    5920             :                                 else if (desired_speed <= 0.2)
    5921             :                                 {
    5922             :                                         // had previously paused, so return to normal speed
    5923             :                                         desired_speed = [dockingInstructions oo_floatForKey:@"speed"];
    5924             :                                 }
    5925             :                         }
    5926             :                 }
    5927             : 
    5928             : 
    5929             :         
    5930             :                 if (distance < last_distance)        // improvement
    5931             :                 {
    5932             :                         frustration -= 0.25 * delta_t;
    5933             :                         if (frustration < 0.0)
    5934             :                                 frustration = 0.0;
    5935             :                 }
    5936             :                 else
    5937             :                 {
    5938             :                         frustration += delta_t;
    5939             :                         if ((frustration > slowdownTime * 10.0 && slowdownTime > 0)||(frustration > 15.0))     // 10x slowdownTime or 15s of frustration
    5940             :                         {
    5941             :                                 [self noteFrustration:@"BEHAVIOUR_FLY_TO_DESTINATION"];
    5942             :                                 frustration -= slowdownTime * 5.0;      //repeat after another five units of frustration
    5943             :                         }
    5944             :                 }
    5945             :         }
    5946             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5947             :         {
    5948             :                 [self avoidCollision];
    5949             :         }
    5950             :         
    5951             :         
    5952             : }
    5953             : 
    5954             : 
    5955             : - (void) behaviour_fly_from_destination:(double) delta_t
    5956             : {
    5957             :         double distance = [self rangeToDestination];
    5958             :         if (distance > desired_range)
    5959             :         {
    5960             :                 // desired range achieved
    5961             :                 [shipAI message:@"DESIRED_RANGE_ACHIEVED"];
    5962             :                 [self doScriptEvent:OOJSID("shipAchievedDesiredRange")];
    5963             : 
    5964             :                 behaviour = BEHAVIOUR_IDLE;
    5965             :                 frustration = 0.0;
    5966             :                 desired_speed = 0.0;
    5967             :         }
    5968             : 
    5969             :         [self trackDestination:delta_t:YES];
    5970             :         if ([self hasProximityAlertIgnoringTarget:YES])
    5971             :         {
    5972             :                 [self avoidCollision];
    5973             :         }
    5974             :         
    5975             :         
    5976             : }
    5977             : 
    5978             : 
    5979             : - (void) behaviour_avoid_collision:(double) delta_t
    5980             : {
    5981             :         double distance = [self rangeToDestination];
    5982             :         if (distance > desired_range)
    5983             :         {
    5984             :                 [self resumePostProximityAlert];
    5985             :         }
    5986             :         else
    5987             :         {
    5988             :                 ShipEntity* prox_ship = (ShipEntity*)[self proximityAlert];
    5989             :                 if (prox_ship)
    5990             :                 {
    5991             :                         desired_range = prox_ship->collision_radius * PROXIMITY_AVOID_DISTANCE_FACTOR;
    5992             :                         _destination = prox_ship->position;
    5993             :                 }
    5994             :                 double dq = [self trackDestination:delta_t:YES]; // returns 0 when heading towards prox_ship
    5995             :                 // Heading towards target with desired_speed > 0, avoids collisions better than setting desired_speed to zero.
    5996             :                 // (tested with boa class cruiser on collisioncourse with buoy)
    5997             :                 desired_speed = maxFlightSpeed * (0.5 * dq + 0.5);
    5998             :         }
    5999             : 
    6000             :         
    6001             :         
    6002             : }
    6003             : 
    6004             : 
    6005             : - (void) behaviour_track_as_turret:(double) delta_t
    6006             : {
    6007             :         double aim = -2.0;
    6008             :         ShipEntity *turret_owner = (ShipEntity *)[self owner];
    6009             :         ShipEntity *turret_target = (ShipEntity *)[turret_owner primaryTarget];
    6010             :         if (turret_owner && turret_target && [turret_owner hasHostileTarget])
    6011             :         {
    6012             :                 aim = [self ballTrackLeadingTarget:delta_t atTarget:turret_target];
    6013             :                 if (aim > -1.0) // potential target
    6014             :                 {
    6015             :                         HPVector p = HPvector_subtract([turret_target position], [turret_owner position]);
    6016             :                         double cr = [turret_owner collisionRadius];
    6017             :                         
    6018             :                         if (aim > .95)
    6019             :                         {
    6020             :                                 [self fireTurretCannon:HPmagnitude(p) - cr];
    6021             :                         }
    6022             :                         return;
    6023             :                 }
    6024             :         }
    6025             :         
    6026             :         // can't fire on primary target; track secondary targets instead
    6027             :         NSEnumerator *targetEnum = [turret_owner defenseTargetEnumerator];
    6028             :         Entity *target = nil;
    6029             :         while ((target = [[targetEnum nextObject] weakRefUnderlyingObject]))
    6030             :         {
    6031             :                 // defense targets cannot be tracked while cloaked
    6032             :                 if ([target scanClass] == CLASS_NO_DRAW || [(ShipEntity *)target isCloaked] || [target energy] <= 0.0)
    6033             :                 {
    6034             :                         [turret_owner removeDefenseTarget:target];
    6035             :                 }
    6036             :                 else 
    6037             :                 {
    6038             :                         double range = [turret_owner rangeToSecondaryTarget:target];
    6039             :                         if (range < weaponRange)
    6040             :                         {
    6041             :                                 aim = [self ballTrackLeadingTarget:delta_t atTarget:target];
    6042             :                                 if (aim > -1.0)
    6043             :                                 { // tracking...
    6044             :                                         HPVector p = HPvector_subtract([target position], [turret_owner position]);
    6045             :                                         double cr = [turret_owner collisionRadius];
    6046             :                 
    6047             :                                         if (aim > .95)
    6048             :                                         { // fire!
    6049             :                                                 [self fireTurretCannon:HPmagnitude(p) - cr];
    6050             :                                         }
    6051             :                                         return;
    6052             :                                 }
    6053             :                                 // else that target is out of range, try the next priority defense target
    6054             :                         }
    6055             :                         else if (range > scannerRange)
    6056             :                         {
    6057             :                                 [turret_owner removeDefenseTarget:target];
    6058             :                         }
    6059             :                 }
    6060             :         }
    6061             : 
    6062             :         // turrets now don't return to neutral facing if no suitable target
    6063             :         // better for shooting at targets that are on edge of fire arc
    6064             : }
    6065             : 
    6066             : 
    6067             : - (void) behaviour_fly_thru_navpoints:(double) delta_t
    6068             : {
    6069             :         int navpoint_plus_index = (next_navpoint_index + 1) % number_of_navpoints;
    6070             :         HPVector d1 = navpoints[next_navpoint_index];           // head for this one
    6071             :         HPVector d2 = navpoints[navpoint_plus_index];   // but be facing this one
    6072             :         
    6073             :         HPVector rel = HPvector_between(d1, position);  // vector from d1 to position 
    6074             :         HPVector ref = HPvector_between(d2, d1);                // vector from d2 to d1
    6075             :         ref = HPvector_normal(ref);
    6076             :         
    6077             :         HPVector xp = make_HPvector(ref.y * rel.z - ref.z * rel.y, ref.z * rel.x - ref.x * rel.z, ref.x * rel.y - ref.y * rel.x);       
    6078             :         
    6079             :         GLfloat v0 = 0.0;
    6080             :         
    6081             :         GLfloat r0 = HPdot_product(rel, ref);   // proportion of rel in direction ref
    6082             :         
    6083             :         // if r0 is negative then we're the wrong side of things
    6084             :         
    6085             :         GLfloat r1 = HPmagnitude(xp);   // distance of position from line
    6086             :         
    6087             :         BOOL in_cone = (r0 > 0.5 * r1);
    6088             :         
    6089             :         if (!in_cone)   // are we in the approach cone ?
    6090             :                 r1 = 25.0 * flightSpeed;        // aim a few km out!
    6091             :         else
    6092             :                 r1 *= 2.0;
    6093             :         
    6094             :         GLfloat dist2 = HPmagnitude2(rel);
    6095             :         
    6096             :         if (dist2 < desired_range * desired_range)
    6097             :         {
    6098             :                 // desired range achieved
    6099             :                 [self doScriptEvent:OOJSID("shipReachedNavPoint") andReactToAIMessage:@"NAVPOINT_REACHED"];
    6100             :                 if (navpoint_plus_index == 0)
    6101             :                 {
    6102             :                         [self doScriptEvent:OOJSID("shipReachedEndPoint") andReactToAIMessage:@"ENDPOINT_REACHED"];
    6103             :                         behaviour = BEHAVIOUR_IDLE;
    6104             :                 }
    6105             :                 next_navpoint_index = navpoint_plus_index;      // loop as required
    6106             :         }
    6107             :         else
    6108             :         {
    6109             :                 double last_success_factor = success_factor;
    6110             :                 double last_dist2 = last_success_factor;
    6111             :                 success_factor = dist2;
    6112             : 
    6113             :                 // set destination spline point from r1 and ref
    6114             :                 _destination = make_HPvector(d1.x + r1 * ref.x, d1.y + r1 * ref.y, d1.z + r1 * ref.z);
    6115             : 
    6116             :                 // do the actual piloting!!
    6117             :                 //
    6118             :                 // aim to within 1m
    6119             :                 GLfloat temp = desired_range;
    6120             :                 if (in_cone)
    6121             :                         desired_range = 1.0;
    6122             :                 else
    6123             :                         desired_range = 100.0;
    6124             :                 v0 = [self trackDestination:delta_t: NO];
    6125             :                 desired_range = temp;
    6126             :                 
    6127             :                 if (dist2 < last_dist2)      // improvement
    6128             :                 {
    6129             :                         frustration -= 0.25 * delta_t;
    6130             :                         if (frustration < 0.0)
    6131             :                                 frustration = 0.0;
    6132             :                 }
    6133             :                 else
    6134             :                 {
    6135             :                         frustration += delta_t;
    6136             :                         if (frustration > 15.0)      // 15s of frustration
    6137             :                         {
    6138             :                                 [self noteFrustration:@"BEHAVIOUR_FLY_THRU_NAVPOINTS"];
    6139             :                                 frustration -= 15.0;    //repeat after another 15s of frustration
    6140             :                         }
    6141             :                 }
    6142             :         }
    6143             :         
    6144             : 
    6145             :         
    6146             :         GLfloat temp = desired_speed;
    6147             :         desired_speed *= v0 * v0;
    6148             :         
    6149             :         desired_speed = temp;
    6150             : }
    6151             : 
    6152             : 
    6153             : - (void) behaviour_scripted_ai:(double) delta_t
    6154             : {
    6155             :         
    6156             :         JSContext       *context = OOJSAcquireContext();
    6157             :         jsval           rval = JSVAL_VOID;
    6158             :         jsval           deltaJS = JSVAL_VOID;
    6159             :         NSDictionary *result = nil;
    6160             :         
    6161             :         BOOL OK = JS_NewNumberValue(context, delta_t, &deltaJS);
    6162             :         if (OK)
    6163             :         {
    6164             :                 OK = [[self script] callMethod:OOJSID("scriptedAI")
    6165             :                                                          inContext:context
    6166             :                                                  withArguments:&deltaJS
    6167             :                                                                  count:1
    6168             :                                                                 result:&rval];
    6169             :         }
    6170             :         
    6171             :         if (!OK)
    6172             :         {
    6173             :                 OOLog(@"ai.error",@"Could not call scriptedAI in ship script of %@, reverting to idle",self);
    6174             :                 behaviour = BEHAVIOUR_IDLE;
    6175             :                 OOJSRelinquishContext(context);
    6176             :                 return;
    6177             :         }
    6178             : 
    6179             :         if (!JSVAL_IS_OBJECT(rval))
    6180             :         {
    6181             :                 OOLog(@"ai.error",@"Invalid return value of scriptedAI in ship script of %@, reverting to idle",self);
    6182             :                 behaviour = BEHAVIOUR_IDLE;
    6183             :                 OOJSRelinquishContext(context);
    6184             :                 return;
    6185             :         }
    6186             : 
    6187             :         result = OOJSNativeObjectFromJSObject(context, JSVAL_TO_OBJECT(rval));
    6188             :         OOJSRelinquishContext(context);
    6189             : 
    6190             :         // roll or roll factor
    6191             :         if ([result objectForKey:@"stickRollFactor"] != nil)
    6192             :         {
    6193             :                 stick_roll = [result oo_floatForKey:@"stickRollFactor"] * max_flight_roll;
    6194             :         } 
    6195             :         else 
    6196             :         {
    6197             :                 stick_roll = [result oo_floatForKey:@"stickRoll"];
    6198             :         }
    6199             :         if (stick_roll > max_flight_roll) 
    6200             :         {
    6201             :                 stick_roll = max_flight_roll;
    6202             :         }
    6203             :         else if (stick_roll < -max_flight_roll)
    6204             :         {
    6205             :                 stick_roll = -max_flight_roll;
    6206             :         }
    6207             : 
    6208             :         // pitch or pitch factor
    6209             :         if ([result objectForKey:@"stickPitchFactor"] != nil)
    6210             :         {
    6211             :                 stick_pitch = [result oo_floatForKey:@"stickPitchFactor"] * max_flight_pitch;
    6212             :         } 
    6213             :         else 
    6214             :         {
    6215             :                 stick_pitch = [result oo_floatForKey:@"stickPitch"];
    6216             :         }
    6217             :         if (stick_pitch > max_flight_pitch) 
    6218             :         {
    6219             :                 stick_pitch = max_flight_pitch;
    6220             :         }
    6221             :         else if (stick_pitch < -max_flight_pitch)
    6222             :         {
    6223             :                 stick_pitch = -max_flight_pitch;
    6224             :         }
    6225             : 
    6226             :         // yaw or yaw factor
    6227             :         if ([result objectForKey:@"stickYawFactor"] != nil)
    6228             :         {
    6229             :                 stick_yaw = [result oo_floatForKey:@"stickYawFactor"] * max_flight_yaw;
    6230             :         } 
    6231             :         else 
    6232             :         {
    6233             :                 stick_yaw = [result oo_floatForKey:@"stickYaw"];
    6234             :         }
    6235             :         if (stick_yaw > max_flight_yaw) 
    6236             :         {
    6237             :                 stick_yaw = max_flight_yaw;
    6238             :         }
    6239             :         else if (stick_yaw < -max_flight_yaw)
    6240             :         {
    6241             :                 stick_yaw = -max_flight_yaw;
    6242             :         }       
    6243             : 
    6244             :         // apply sticks to current flight profile
    6245             :         [self applySticks:delta_t];
    6246             : 
    6247             :         // desired speed
    6248             :         if ([result objectForKey:@"desiredSpeedFactor"] != nil)
    6249             :         {
    6250             :                 desired_speed = [result oo_floatForKey:@"desiredSpeedFactor"] * maxFlightSpeed;
    6251             :         }
    6252             :         else
    6253             :         {
    6254             :                 desired_speed = [result oo_floatForKey:@"desiredSpeed"];
    6255             :         }
    6256             : 
    6257             :         if (desired_speed < 0.0)
    6258             :         {
    6259             :                 desired_speed = 0.0;
    6260             :         }
    6261             :         // overspeed and injector use is handled by applyThrust
    6262             : 
    6263             :         if (behaviour == BEHAVIOUR_SCRIPTED_ATTACK_AI)
    6264             :         {
    6265             :                 NSString* chosen_weapon = [result oo_stringForKey:@"chosenWeapon" defaultValue:@"FORWARD"];
    6266             :                 double  range = [self rangeToPrimaryTarget];
    6267             :                         
    6268             :                 if ([chosen_weapon isEqualToString:@"FORWARD"])
    6269             :                 {
    6270             :                         [self fireMainWeapon:range];
    6271             :                 }
    6272             :                 else if ([chosen_weapon isEqualToString:@"AFT"])
    6273             :                 {
    6274             :                         [self fireAftWeapon:range];
    6275             :                 }
    6276             :                 else if ([chosen_weapon isEqualToString:@"PORT"])
    6277             :                 {
    6278             :                         [self firePortWeapon:range];
    6279             :                 }
    6280             :                 else if ([chosen_weapon isEqualToString:@"STARBOARD"])
    6281             :                 {
    6282             :                         [self fireStarboardWeapon:range];
    6283             :                 }
    6284             :         }
    6285             : }
    6286             : 
    6287             : - (float) reactionTime
    6288             : {
    6289             :         return reactionTime;
    6290             : }
    6291             : 
    6292             : 
    6293             : - (void) setReactionTime: (float) newReactionTime
    6294             : {
    6295             :         reactionTime = newReactionTime;
    6296             : }
    6297             : 
    6298             : 
    6299             : - (HPVector) calculateTargetPosition
    6300             : {
    6301             :         Entity *target = [self primaryTarget];
    6302             :         if (target == nil)
    6303             :         {
    6304             :                 return kZeroHPVector;
    6305             :         }
    6306             :         if (reactionTime <= 0.0)
    6307             :         {
    6308             :                 return [target position];
    6309             :         }
    6310             :         double t = [UNIVERSE getTime] - trackingCurveTimes[1];
    6311             :         return HPvector_add(HPvector_add(trackingCurveCoeffs[0], HPvector_multiply_scalar(trackingCurveCoeffs[1],t)), HPvector_multiply_scalar(trackingCurveCoeffs[2],t*t));
    6312             : }
    6313             : 
    6314             : 
    6315             : - (void) startTrackingCurve
    6316             : {
    6317             :         Entity *target = [self primaryTarget];
    6318             :         if (target == nil)
    6319             :         {
    6320             :                 return;
    6321             :         }
    6322             :         OOTimeAbsolute now = [UNIVERSE getTime];
    6323             :         trackingCurvePositions[0] = [target position];
    6324             :         trackingCurvePositions[1] = [target position];
    6325             :         trackingCurvePositions[2] = [target position];
    6326             :         trackingCurvePositions[3] = [target position];
    6327             :         trackingCurveTimes[0] = now;
    6328             :         trackingCurveTimes[1] = now - reactionTime/3.0;
    6329             :         trackingCurveTimes[2] = now - reactionTime*2.0/3.0;
    6330             :         trackingCurveTimes[3] = now - reactionTime;
    6331             :         [self calculateTrackingCurve];
    6332             :         return;
    6333             : }
    6334             : 
    6335             : 
    6336             : - (void) updateTrackingCurve
    6337             : {
    6338             :         Entity *target = [self primaryTarget];
    6339             :         OOTimeAbsolute now = [UNIVERSE getTime];
    6340             :         if (target == nil || reactionTime <= 0.0 || trackingCurveTimes[0] + reactionTime/3.0 > now) return;
    6341             :         trackingCurvePositions[3] = trackingCurvePositions[2];
    6342             :         trackingCurvePositions[2] = trackingCurvePositions[1];
    6343             :         trackingCurvePositions[1] = trackingCurvePositions[0];
    6344             :         if (EXPECT_NOT([target isShip] && [(ShipEntity *)target isCloaked]))
    6345             :         {
    6346             :                 // if target is cloaked, introduce some more inaccuracy
    6347             :                 // 0.02 seems to be enough to give them slight difficulty on
    6348             :                 // a straight-line target and real trouble on anything better
    6349             :                 trackingCurvePositions[0] = HPvector_add([target position],OOHPVectorRandomSpatial([(ShipEntity *)target flightSpeed]*reactionTime*0.02));
    6350             :         }
    6351             :         else
    6352             :         {
    6353             :                 trackingCurvePositions[0] = [target position];
    6354             :         }
    6355             :         trackingCurveTimes[3] = trackingCurveTimes[2];
    6356             :         trackingCurveTimes[2] = trackingCurveTimes[1];
    6357             :         trackingCurveTimes[1] = trackingCurveTimes[0];
    6358             :         trackingCurveTimes[0] = now;
    6359             :         [self calculateTrackingCurve];
    6360             :         return;
    6361             : }
    6362             : 
    6363             : - (void) calculateTrackingCurve
    6364             : {
    6365             :         if (reactionTime <= 0.0)
    6366             :         {
    6367             :                 trackingCurveCoeffs[0] = trackingCurvePositions[0];
    6368             :                 trackingCurveCoeffs[1] = kZeroHPVector;
    6369             :                 trackingCurveCoeffs[2] = kZeroHPVector;
    6370             :                 return;
    6371             :         }
    6372             :         double  t1 = trackingCurveTimes[2] - trackingCurveTimes[1],
    6373             :                 t2 = trackingCurveTimes[3] - trackingCurveTimes[1];
    6374             :         trackingCurveCoeffs[0] = trackingCurvePositions[1];
    6375             :         trackingCurveCoeffs[1] = HPvector_add(HPvector_add(
    6376             :                 HPvector_multiply_scalar(trackingCurvePositions[1], -(t1+t2)/(t1*t2)),
    6377             :                 HPvector_multiply_scalar(trackingCurvePositions[2], -t2/(t1*(t1-t2)))),
    6378             :                 HPvector_multiply_scalar(trackingCurvePositions[3], t1/(t2*(t1-t2))));
    6379             :         trackingCurveCoeffs[2] = HPvector_add(HPvector_add(
    6380             :                 HPvector_multiply_scalar(trackingCurvePositions[1], 1/(t1*t2)),
    6381             :                 HPvector_multiply_scalar(trackingCurvePositions[2], 1/(t1*(t1-t2)))),
    6382             :                 HPvector_multiply_scalar(trackingCurvePositions[3], -1/(t2*(t1-t2))));
    6383             :         return;
    6384             : }
    6385             : 
    6386           0 : - (void) drawImmediate:(bool)immediate translucent:(bool)translucent
    6387             : {
    6388             :         if ((no_draw_distance < cam_zero_distance) ||        // Done redundantly to skip subentities
    6389             :                 (cloaking_device_active && randf() > 0.10))
    6390             :         {
    6391             :                 // Don't draw.
    6392             :                 return;
    6393             :         }
    6394             :         
    6395             :         // Draw self.
    6396             :         [super drawImmediate:immediate translucent:translucent];
    6397             :         
    6398             : #ifndef NDEBUG
    6399             :         // Draw bounding boxes if we have to before going for the subentities.
    6400             :         // TODO: the translucent flag here makes very little sense. Something's wrong with the matrices.
    6401             :         if (translucent)  [self drawDebugStuff];
    6402             :         else if (gDebugFlags & DEBUG_BOUNDING_BOXES && ![self isSubEntity])
    6403             :         {
    6404             :                 OODebugDrawBoundingBox([self boundingBox]);
    6405             :                 OODebugDrawColoredBoundingBox(totalBoundingBox, [OOColor purpleColor]);
    6406             :         }
    6407             : #endif
    6408             :         
    6409             :         // Draw subentities.
    6410             :         if (!immediate) // TODO: is this relevant any longer?
    6411             :         {
    6412             :                 // save time by not copying the subentity array if it's empty - CIM
    6413             :                 if ([self subEntityCount] > 0) 
    6414             :                 { 
    6415             :                         Entity<OOSubEntity> *subEntity = nil;
    6416             :                         foreach (subEntity, [self subEntities])
    6417             :                         {
    6418             :                                 NSAssert3([subEntity owner] == self, @"Subentity ownership broke - %@ should be owned by %@ but is owned by %@.", subEntity, self, [subEntity owner]);
    6419             :                                 [subEntity drawSubEntityImmediate:immediate translucent:translucent];
    6420             :                         }
    6421             :                 }
    6422             :         }
    6423             : }
    6424             : 
    6425             : 
    6426             : #ifndef NDEBUG
    6427           0 : - (void) drawDebugStuff
    6428             : {
    6429             :         // HPVect: imprecise here - needs camera relative
    6430             :         if (0 && reportAIMessages)
    6431             :         {
    6432             :                 OODebugDrawPoint(HPVectorToVector(_destination), [OOColor blueColor]);
    6433             :                 OODebugDrawColoredLine(HPVectorToVector([self position]), HPVectorToVector(_destination), [OOColor colorWithWhite:0.15 alpha:1.0]);
    6434             :                 
    6435             :                 Entity *pTarget = [self primaryTarget];
    6436             :                 if (pTarget != nil)
    6437             :                 {
    6438             :                         OODebugDrawPoint(HPVectorToVector([pTarget position]), [OOColor redColor]);
    6439             :                         OODebugDrawColoredLine(HPVectorToVector([self position]), HPVectorToVector([pTarget position]), [OOColor colorWithRed:0.2 green:0.0 blue:0.0 alpha:1.0]);
    6440             :                 }
    6441             :                 
    6442             :                 Entity *sTarget = [self targetStation];
    6443             :                 if (sTarget != pTarget && [sTarget isStation])
    6444             :                 {
    6445             :                         OODebugDrawPoint(HPVectorToVector([sTarget position]), [OOColor cyanColor]);
    6446             :                 }
    6447             :                 
    6448             :                 Entity *fTarget = [self foundTarget];
    6449             :                 if (fTarget != nil && fTarget != pTarget && fTarget != sTarget)
    6450             :                 {
    6451             :                         OODebugDrawPoint(HPVectorToVector([fTarget position]), [OOColor magentaColor]);
    6452             :                 }
    6453             :         }
    6454             : }
    6455             : #endif
    6456             : 
    6457             : 
    6458           0 : - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent
    6459             : {
    6460             :         OOVerifyOpenGLState();
    6461             :         
    6462             :         if (cam_zero_distance > no_draw_distance) // this test provides an opportunity to do simple LoD culling
    6463             :         {
    6464             :                 return; // TOO FAR AWAY
    6465             :         }
    6466             :         OOGLPushModelView();
    6467             :         
    6468             :         // HPVect: need to make camera-relative
    6469             :         OOGLTranslateModelView(HPVectorToVector(position));
    6470             :         OOGLMultModelView(rotMatrix);
    6471             :         [self drawImmediate:immediate translucent:translucent];
    6472             :         
    6473             : #ifndef NDEBUG
    6474             :         if (gDebugFlags & DEBUG_BOUNDING_BOXES)
    6475             :         {
    6476             :                 OODebugDrawBoundingBox([self boundingBox]);
    6477             :         }
    6478             : #endif
    6479             :         
    6480             :         OOGLPopModelView();
    6481             :         
    6482             :         OOVerifyOpenGLState();  
    6483             : }
    6484             : 
    6485             : 
    6486           0 : static GLfloat cargo_color[4] =         { 0.9, 0.9, 0.9, 1.0};  // gray
    6487           0 : static GLfloat hostile_color[4] =       { 1.0, 0.25, 0.0, 1.0}; // red/orange
    6488           0 : static GLfloat neutral_color[4] =       { 1.0, 1.0, 0.0, 1.0};  // yellow
    6489           0 : static GLfloat friendly_color[4] =      { 0.0, 1.0, 0.0, 1.0};  // green
    6490           0 : static GLfloat missile_color[4] =       { 0.0, 1.0, 1.0, 1.0};  // cyan
    6491           0 : static GLfloat police_color1[4] =       { 0.5, 0.0, 1.0, 1.0};  // purpley-blue
    6492           0 : static GLfloat police_color2[4] =       { 1.0, 0.0, 0.5, 1.0};  // purpley-red
    6493           0 : static GLfloat jammed_color[4] =        { 0.0, 0.0, 0.0, 0.0};  // clear black
    6494           0 : static GLfloat mascem_color1[4] =       { 0.3, 0.3, 0.3, 1.0};  // dark gray
    6495           0 : static GLfloat mascem_color2[4] =       { 0.4, 0.1, 0.4, 1.0};  // purple
    6496           0 : static GLfloat scripted_color[4] =      { 0.0, 0.0, 0.0, 0.0};  // to be defined by script
    6497             : 
    6498             : - (GLfloat *) scannerDisplayColorForShip:(ShipEntity*)otherShip :(BOOL)isHostile :(BOOL)flash :(OOColor *)scannerDisplayColor1 :(OOColor *)scannerDisplayColor2 :(OOColor *)scannerDisplayColorH1 :(OOColor *)scannerDisplayColorH2
    6499             : {
    6500             :         if (isHostile)
    6501             :         {
    6502             :                 /* if there are any scripted scanner hostile display colours
    6503             :                  * for the ship, use them - otherwise fall through to the
    6504             :                  * normal scripted colours, then the scan class colours */
    6505             :                 if (scannerDisplayColorH1 || scannerDisplayColorH2)
    6506             :                 {
    6507             :                         if (scannerDisplayColorH1 && !scannerDisplayColorH2)
    6508             :                         {
    6509             :                                 [scannerDisplayColorH1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6510             :                         }
    6511             :                 
    6512             :                         if (!scannerDisplayColorH1 && scannerDisplayColorH2)
    6513             :                         {
    6514             :                                 [scannerDisplayColorH2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6515             :                         }
    6516             :                 
    6517             :                         if (scannerDisplayColorH1 && scannerDisplayColorH2)
    6518             :                         {
    6519             :                                 if (flash)
    6520             :                                         [scannerDisplayColorH1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6521             :                                 else
    6522             :                                         [scannerDisplayColorH2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6523             :                         }
    6524             :                 
    6525             :                         return scripted_color;
    6526             :                 }
    6527             :         }
    6528             : 
    6529             :         // if there are any scripted scanner display colors for the ship, use them
    6530             :         if (scannerDisplayColor1 || scannerDisplayColor2)
    6531             :         {
    6532             :                 if (scannerDisplayColor1 && !scannerDisplayColor2)
    6533             :                 {
    6534             :                         [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6535             :                 }
    6536             :                 
    6537             :                 if (!scannerDisplayColor1 && scannerDisplayColor2)
    6538             :                 {
    6539             :                         [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6540             :                 }
    6541             :                 
    6542             :                 if (scannerDisplayColor1 && scannerDisplayColor2)
    6543             :                 {
    6544             :                         if (flash)
    6545             :                                 [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6546             :                         else
    6547             :                                 [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
    6548             :                 }
    6549             :                 
    6550             :                 return scripted_color;
    6551             :         }
    6552             : 
    6553             :         // no scripted scanner display colors defined, proceed as per standard
    6554             :         if ([self isJammingScanning])
    6555             :         {
    6556             :                 if (![otherShip hasMilitaryScannerFilter])
    6557             :                         return jammed_color;
    6558             :                 else
    6559             :                 {
    6560             :                         if (flash)
    6561             :                                 return mascem_color1;
    6562             :                         else
    6563             :                         {
    6564             :                                 if (isHostile)
    6565             :                                         return hostile_color;
    6566             :                                 else
    6567             :                                         return mascem_color2;
    6568             :                         }
    6569             :                 }
    6570             :         }
    6571             : 
    6572             :         switch (scanClass)
    6573             :         {
    6574             :                 case CLASS_ROCK :
    6575             :                 case CLASS_CARGO :
    6576             :                         return cargo_color;
    6577             :                 case CLASS_THARGOID :
    6578             :                         if (flash)
    6579             :                                 return hostile_color;
    6580             :                         else
    6581             :                                 return friendly_color;
    6582             :                 case CLASS_MISSILE :
    6583             :                         return missile_color;
    6584             :                 case CLASS_STATION :
    6585             :                         return friendly_color;
    6586             :                 case CLASS_BUOY :
    6587             :                         if (flash)
    6588             :                                 return friendly_color;
    6589             :                         else
    6590             :                                 return neutral_color;
    6591             :                 case CLASS_POLICE :
    6592             :                 case CLASS_MILITARY :
    6593             :                         if ((isHostile)&&(flash))
    6594             :                                 return police_color2;
    6595             :                         else
    6596             :                                 return police_color1;
    6597             :                 case CLASS_MINE :
    6598             :                         if (flash)
    6599             :                                 return neutral_color;
    6600             :                         else
    6601             :                                 return hostile_color;
    6602             :                 default :
    6603             :                         if (isHostile)
    6604             :                                 return hostile_color;
    6605             :         }
    6606             :         return neutral_color;
    6607             : }
    6608             : 
    6609             : 
    6610             : - (void)setScannerDisplayColor1:(OOColor *)color
    6611             : {
    6612             :         DESTROY(scanner_display_color1);
    6613             :         
    6614             :         if (color == nil)  color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_display_color1"]];
    6615             :         scanner_display_color1 = [color retain];
    6616             : }
    6617             : 
    6618             : 
    6619             : - (void)setScannerDisplayColor2:(OOColor *)color
    6620             : {
    6621             :         DESTROY(scanner_display_color2);
    6622             :         
    6623             :         if (color == nil)  color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_display_color2"]];
    6624             :         scanner_display_color2 = [color retain];
    6625             : }
    6626             : 
    6627             : 
    6628             : - (OOColor *)scannerDisplayColor1
    6629             : {
    6630             :         return [[scanner_display_color1 retain] autorelease];
    6631             : }
    6632             : 
    6633             : 
    6634             : - (OOColor *)scannerDisplayColor2
    6635             : {
    6636             :         return [[scanner_display_color2 retain] autorelease];
    6637             : }
    6638             : 
    6639             : 
    6640             : - (void)setScannerDisplayColorHostile1:(OOColor *)color
    6641             : {
    6642             :         DESTROY(scanner_display_color_hostile1);
    6643             :         
    6644             :         if (color == nil)  color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_hostile_display_color1"]];
    6645             :         scanner_display_color_hostile1 = [color retain];
    6646             : }
    6647             : 
    6648             : 
    6649             : - (void)setScannerDisplayColorHostile2:(OOColor *)color
    6650             : {
    6651             :         DESTROY(scanner_display_color_hostile2);
    6652             :         
    6653             :         if (color == nil)  color = [OOColor colorWithDescription:[[self shipInfoDictionary] objectForKey:@"scanner_hostile_display_color2"]];
    6654             :         scanner_display_color_hostile2 = [color retain];
    6655             : }
    6656             : 
    6657             : 
    6658             : - (OOColor *)scannerDisplayColorHostile1
    6659             : {
    6660             :         return [[scanner_display_color_hostile1 retain] autorelease];
    6661             : }
    6662             : 
    6663             : 
    6664             : - (OOColor *)scannerDisplayColorHostile2
    6665             : {
    6666             :         return [[scanner_display_color_hostile2 retain] autorelease];
    6667             : }
    6668             : 
    6669             : 
    6670             : - (BOOL)isCloaked
    6671             : {
    6672             :         return cloaking_device_active;
    6673             : }
    6674             : 
    6675             : 
    6676             : - (BOOL) cloakPassive
    6677             : {
    6678             :         return cloakPassive;
    6679             : }
    6680             : 
    6681             : 
    6682             : - (void)setCloaked:(BOOL)cloak
    6683             : {
    6684             :         if (cloak)  [self activateCloakingDevice];
    6685             :         else  [self deactivateCloakingDevice];
    6686             : }
    6687             : 
    6688             : 
    6689             : - (BOOL)hasAutoCloak
    6690             : {
    6691             :         return cloakAutomatic;
    6692             : }
    6693             : 
    6694             : 
    6695             : - (void)setAutoCloak:(BOOL)automatic
    6696             : {
    6697             :         cloakAutomatic = !!automatic;
    6698             : }
    6699             : 
    6700             : 
    6701           0 : - (BOOL) isJammingScanning
    6702             : {
    6703             :         return ([self hasMilitaryJammer] && military_jammer_active);
    6704             : }
    6705             : 
    6706             : 
    6707           0 : - (void) addSubEntity:(Entity<OOSubEntity> *)sub
    6708             : {
    6709             :         if (sub == nil)  return;
    6710             :         
    6711             :         if (subEntities == nil)  subEntities = [[NSMutableArray alloc] init];
    6712             :         sub->isSubEntity = YES;
    6713             :         // Order matters - need consistent state in setOwner:. -- Ahruman 2008-04-20
    6714             :         [subEntities addObject:sub];
    6715             :         [sub setOwner:self];
    6716             :         
    6717             :         [self addSubentityToCollisionRadius:sub];
    6718             : }
    6719             : 
    6720             : 
    6721           0 : - (void) setOwner:(Entity *)who_owns_entity
    6722             : {
    6723             :         [super setOwner:who_owns_entity];
    6724             :         
    6725             :         /*      Reset shader binding target so that bind-to-super works.
    6726             :                 This is necessary since we don't know about the owner in
    6727             :                 setUpShipFromDictionary:, when the mesh is initially set up.
    6728             :                 -- Ahruman 2008-04-19
    6729             :         */
    6730             :         if (isSubEntity)
    6731             :         {
    6732             :                 [[self drawable] setBindingTarget:self];
    6733             :         }
    6734             : }
    6735             : 
    6736             : 
    6737             : - (void) applyThrust:(double) delta_t
    6738             : {
    6739             :         GLfloat dt_thrust = SHIP_THRUST_FACTOR * thrust * delta_t;
    6740             :         BOOL    canBurn = [self hasFuelInjection] && (fuel > MIN_FUEL);
    6741             :         BOOL    isUsingAfterburner = (canBurn && (flightSpeed > maxFlightSpeed) && (desired_speed >= flightSpeed));
    6742             :         float   max_available_speed = maxFlightSpeed;
    6743             :         if (canBurn) max_available_speed *= [self afterburnerFactor];
    6744             :         
    6745             :         if (thrust)
    6746             :         {
    6747             :                 // If we have Newtonian (non-thrust) velocity, brake it.
    6748             :                 GLfloat velmag = magnitude(velocity);
    6749             :                 if (velmag)
    6750             :                 {
    6751             :                         GLfloat vscale = fmaxf((velmag - dt_thrust) / velmag, 0.0f);
    6752             :                         scale_vector(&velocity, vscale);
    6753             :                 }
    6754             :         }
    6755             : 
    6756             :         if (behaviour == BEHAVIOUR_TUMBLE)  return;
    6757             : 
    6758             :         // check for speed
    6759             :         if (desired_speed > max_available_speed)
    6760             :                 desired_speed = max_available_speed;
    6761             : 
    6762             :         if (flightSpeed > desired_speed)
    6763             :         {
    6764             :                 [self decrease_flight_speed: dt_thrust];
    6765             :                 if (flightSpeed < desired_speed)   flightSpeed = desired_speed;
    6766             :         }
    6767             :         if (flightSpeed < desired_speed)
    6768             :         {
    6769             :                 [self increase_flight_speed: dt_thrust];
    6770             :                 if (flightSpeed > desired_speed)   flightSpeed = desired_speed;
    6771             :         }
    6772             :         [self moveForward: delta_t*flightSpeed];
    6773             : 
    6774             :         // burn fuel at the appropriate rate
    6775             :         if (isUsingAfterburner) // no fuelconsumption on slowdown
    6776             :         {
    6777             :                 fuel_accumulator -= delta_t * afterburner_rate;
    6778             :                 while (fuel_accumulator < 0.0)
    6779             :                 {
    6780             :                         fuel--;
    6781             :                         fuel_accumulator += 1.0;
    6782             :                 }
    6783             :         }
    6784             : }
    6785             : 
    6786             : 
    6787           0 : - (void) orientationChanged
    6788             : {
    6789             :         [super orientationChanged];
    6790             :         
    6791             :         v_forward   = vector_forward_from_quaternion(orientation);
    6792             :         v_up            = vector_up_from_quaternion(orientation);
    6793             :         v_right         = vector_right_from_quaternion(orientation);
    6794             : }
    6795             : 
    6796             : 
    6797           0 : - (void) applyRoll:(GLfloat) roll1 andClimb:(GLfloat) climb1
    6798             : {
    6799             :         Quaternion q1 = kIdentityQuaternion;
    6800             : 
    6801             :         if (!roll1 && !climb1 && !hasRotated)  return;
    6802             : 
    6803             :         if (roll1)  quaternion_rotate_about_z(&q1, -roll1);
    6804             :         if (climb1)  quaternion_rotate_about_x(&q1, -climb1);
    6805             : 
    6806             :         orientation = quaternion_multiply(q1, orientation);
    6807             :         [self orientationChanged];
    6808             : }
    6809             : 
    6810             : 
    6811           0 : - (void) applyRoll:(GLfloat) roll1 climb:(GLfloat) climb1 andYaw:(GLfloat) yaw1
    6812             : {
    6813             :         if ((roll1 == 0.0)&&(climb1 == 0.0)&&(yaw1 == 0.0)&&(!hasRotated))
    6814             :                 return;
    6815             : 
    6816             :         Quaternion q1 = kIdentityQuaternion;
    6817             : 
    6818             :         if (roll1)
    6819             :                 quaternion_rotate_about_z(&q1, -roll1);
    6820             :         if (climb1)
    6821             :                 quaternion_rotate_about_x(&q1, -climb1);
    6822             :         if (yaw1)
    6823             :                 quaternion_rotate_about_y(&q1, -yaw1);
    6824             : 
    6825             :         orientation = quaternion_multiply(q1, orientation);
    6826             :         [self orientationChanged];
    6827             : }
    6828             : 
    6829             : 
    6830             : - (void) applyAttitudeChanges:(double) delta_t
    6831             : {
    6832             :         [self applyRoll:flightRoll*delta_t climb:flightPitch*delta_t andYaw:flightYaw*delta_t];
    6833             : }
    6834             : 
    6835             : 
    6836             : - (void) avoidCollision
    6837             : {
    6838             :         if (scanClass == CLASS_MISSILE)
    6839             :                 return;                                         // missiles are SUPPOSED to collide!
    6840             :         
    6841             :         ShipEntity* prox_ship = (ShipEntity*)[self proximityAlert];
    6842             : 
    6843             :         if (prox_ship)
    6844             :         {
    6845             :                 if (previousCondition)
    6846             :                 {
    6847             :                         [previousCondition release];
    6848             :                         previousCondition = nil;
    6849             :                 }
    6850             : 
    6851             :                 previousCondition = [[NSMutableDictionary dictionaryWithCapacity:5] retain];
    6852             :                 
    6853             :                 [previousCondition oo_setInteger:behaviour forKey:@"behaviour"];
    6854             :                 if ([self primaryTarget] != nil)
    6855             :                 {
    6856             :                         // must use the weak ref here to prevent potential over-retention
    6857             :                         [previousCondition setObject:[[self primaryTarget] weakSelf] forKey:@"primaryTarget"];
    6858             :                 }
    6859             :                 [previousCondition oo_setFloat:desired_range forKey:@"desired_range"];
    6860             :                 [previousCondition oo_setFloat:desired_speed forKey:@"desired_speed"];
    6861             :                 [previousCondition oo_setHPVector:_destination forKey:@"destination"];
    6862             :                 
    6863             :                 _destination = [prox_ship position];
    6864             :                 _destination = OOHPVectorInterpolate(position, [prox_ship position], 0.5);              // point between us and them
    6865             :                 
    6866             :                 desired_range = prox_ship->collision_radius * PROXIMITY_AVOID_DISTANCE_FACTOR;
    6867             :                 
    6868             :                 behaviour = BEHAVIOUR_AVOID_COLLISION;
    6869             :                 pitching_over = YES;
    6870             :         }
    6871             : }
    6872             : 
    6873             : 
    6874             : - (void) resumePostProximityAlert
    6875             : {
    6876             :         if (!previousCondition)  return;
    6877             : 
    6878             :         behaviour =             [previousCondition oo_intForKey:@"behaviour"];
    6879             :         [_primaryTarget release];
    6880             :         _primaryTarget =        [[previousCondition objectForKey:@"primaryTarget"] weakRetain];
    6881             :         [self startTrackingCurve];
    6882             :         desired_range = [previousCondition oo_floatForKey:@"desired_range"];
    6883             :         desired_speed = [previousCondition oo_floatForKey:@"desired_speed"];
    6884             :         _destination =  [previousCondition oo_hpvectorForKey:@"destination"];
    6885             : 
    6886             :         [previousCondition release];
    6887             :         previousCondition = nil;
    6888             :         frustration = 0.0;
    6889             :         
    6890             :         DESTROY(_proximityAlert);
    6891             :         
    6892             :         //[shipAI message:@"RESTART_DOCKING"];        // if docking, start over, other AIs will ignore this message
    6893             : }
    6894             : 
    6895             : 
    6896             : - (double) messageTime
    6897             : {
    6898             :         return messageTime;
    6899             : }
    6900             : 
    6901             : 
    6902             : - (void) setMessageTime:(double) value
    6903             : {
    6904             :         messageTime = value;
    6905             : }
    6906             : 
    6907             : 
    6908             : - (OOShipGroup *) group
    6909             : {
    6910             :         return _group;
    6911             : }
    6912             : 
    6913             : 
    6914             : - (void) setGroup:(OOShipGroup *)group
    6915             : {
    6916             :         if (group != _group)
    6917             :         {
    6918             :                 if (_escortGroup != _group) 
    6919             :                 {
    6920             :                         if (self == [_group leader])  [_group setLeader:nil];
    6921             :                         [_group removeShip:self];
    6922             :                 }
    6923             :                 [_group release];
    6924             :                 [group addShip:self];
    6925             :                 _group = [group retain];
    6926             :                 
    6927             :                 [[group leader] updateEscortFormation];
    6928             :         }
    6929             : }
    6930             : 
    6931             : 
    6932             : - (OOShipGroup *) escortGroup
    6933             : {
    6934             :         if (_escortGroup == nil)
    6935             :         {
    6936             :                 _escortGroup = [[OOShipGroup alloc] initWithName:@"escort group"];
    6937             :                 [_escortGroup setLeader:self];
    6938             :         }
    6939             :         
    6940             :         return _escortGroup;
    6941             : }
    6942             : 
    6943             : 
    6944             : - (void) setEscortGroup:(OOShipGroup *)group
    6945             : {
    6946             :         if (group != _escortGroup)
    6947             :         {
    6948             :                 [_escortGroup release];
    6949             :                 _escortGroup = [group retain];
    6950             :                 [group setLeader:self]; // A ship is always leader of its own escort group.
    6951             :                 [self updateEscortFormation];
    6952             :         }
    6953             : }
    6954             : 
    6955             : 
    6956             : #ifndef NDEBUG
    6957           0 : - (OOShipGroup *) rawEscortGroup
    6958             : {
    6959             :         return _escortGroup;
    6960             : }
    6961             : #endif
    6962             : 
    6963             : 
    6964             : - (OOShipGroup *) stationGroup
    6965             : {
    6966             :         if (_group == nil)
    6967             :         {
    6968             :                 _group = [[OOShipGroup alloc] initWithName:@"station group"];
    6969             :                 [_group setLeader:self];
    6970             :         }
    6971             :         
    6972             :         return _group;
    6973             : }
    6974             : 
    6975             : 
    6976             : - (BOOL) hasEscorts
    6977             : {
    6978             :         if (_escortGroup == nil)  return NO;
    6979             :         return [_escortGroup count] > 1;     // If only one member, it's self.
    6980             : }
    6981             : 
    6982             : 
    6983             : - (NSEnumerator *) escortEnumerator
    6984             : {
    6985             :         if (_escortGroup == nil)  return [[NSArray array] objectEnumerator];
    6986             :         return [[_escortGroup mutationSafeEnumerator] ooExcludingObject:self];
    6987             : }
    6988             : 
    6989             : 
    6990             : - (NSArray *) escortArray
    6991             : {
    6992             :         if (_escortGroup == nil)  return [NSArray array];
    6993             :         return [[self escortEnumerator] allObjects];
    6994             : }
    6995             : 
    6996             : 
    6997             : - (uint8_t) escortCount
    6998             : {
    6999             :         if (_escortGroup == nil)  return 0;
    7000             :         return [_escortGroup count] - 1;
    7001             : }
    7002             : 
    7003             : 
    7004             : - (uint8_t) pendingEscortCount
    7005             : {
    7006             :         return _pendingEscortCount;
    7007             : }
    7008             : 
    7009             : 
    7010             : - (void) setPendingEscortCount:(uint8_t)count
    7011             : {
    7012             :         _pendingEscortCount = MIN(count, _maxEscortCount);
    7013             : }
    7014             : 
    7015             : 
    7016             : - (uint8_t) maxEscortCount
    7017             : {
    7018             :         return _maxEscortCount;
    7019             : }
    7020             : 
    7021             : 
    7022             : - (void) setMaxEscortCount:(uint8_t)newCount
    7023             : {
    7024             :         _maxEscortCount = newCount;
    7025             : }
    7026             : 
    7027             : 
    7028             : - (NSUInteger) turretCount
    7029             : {
    7030             :         NSUInteger count = 0;
    7031             :         NSEnumerator *subEnum = [self shipSubEntityEnumerator];
    7032             :         ShipEntity *se = nil;
    7033             :         while ((se = [subEnum nextObject]))
    7034             :         {
    7035             :                 if ([se isTurret])
    7036             :                 {
    7037             :                         count ++; 
    7038             :                 }
    7039             :         }
    7040             :         return count;
    7041             : }
    7042             : 
    7043             : 
    7044             : - (Entity*) proximityAlert
    7045             : {
    7046             :         Entity* prox = [_proximityAlert weakRefUnderlyingObject];
    7047             :         if (prox == nil)
    7048             :         {
    7049             :                 DESTROY(_proximityAlert);
    7050             :         }
    7051             :         return prox;
    7052             : }
    7053             : 
    7054             : 
    7055             : - (void) setProximityAlert:(ShipEntity*) other
    7056             : {
    7057             :         if (!other)
    7058             :         {
    7059             :                 DESTROY(_proximityAlert);
    7060             :                 return;
    7061             :         }
    7062             : 
    7063             :         if ([other mass] < 2000) // we are not alerted by small objects. (a cargopod has a mass of about 1000)
    7064             :                 return;
    7065             :         
    7066             : 
    7067             :         if (isStation) // stations don't worry about colliding with things
    7068             :                 return; 
    7069             : 
    7070             :         /* Ignore station collision warnings if launching or docking */
    7071             :         if ((other->isStation) && ([self status] == STATUS_LAUNCHING || 
    7072             :                                                            dockingInstructions != nil))
    7073             :         {
    7074             :                 return; 
    7075             :         }       
    7076             : 
    7077             :         if (!crew) // Ships without pilot (cargo, rocks, missiles, buoys etc) will not get alarmed. (escape-pods have pilots)
    7078             :                 return;
    7079             :         
    7080             :         // check vectors
    7081             :         Vector vdiff = HPVectorToVector(HPvector_between(position, other->position));
    7082             :         GLfloat d_forward = dot_product(vdiff, v_forward);
    7083             :         GLfloat d_up = dot_product(vdiff, v_up);
    7084             :         GLfloat d_right = dot_product(vdiff, v_right);
    7085             :         if ((d_forward > 0.0)&&(flightSpeed > 0.0))       // it's ahead of us and we're moving forward
    7086             :                 d_forward *= 0.25 * maxFlightSpeed / flightSpeed;       // extend the collision zone forward up to 400%
    7087             :         double d2 = d_forward * d_forward + d_up * d_up + d_right * d_right;
    7088             :         double cr2 = collision_radius * 2.0 + other->collision_radius;       cr2 *= cr2;     // check with twice the combined radius
    7089             : 
    7090             :         if (d2 > cr2) // we're okay
    7091             :         return;
    7092             : 
    7093             :         if (behaviour == BEHAVIOUR_AVOID_COLLISION)     //      already avoiding something
    7094             :         {
    7095             :                 ShipEntity* prox = (ShipEntity*)[self proximityAlert];
    7096             :                 if ((prox)&&(prox != other))
    7097             :                 {
    7098             :                         // check which subtends the greatest angle
    7099             :                         GLfloat sa_prox = prox->collision_radius * prox->collision_radius / HPdistance2(position, prox->position);
    7100             :                         GLfloat sa_other = other->collision_radius *  other->collision_radius / HPdistance2(position, other->position);
    7101             :                         if (sa_prox < sa_other)  return;
    7102             :                 }
    7103             :         }
    7104             :         [_proximityAlert release];
    7105             :         _proximityAlert = [other weakRetain];
    7106             : }
    7107             : 
    7108             : 
    7109             : - (NSString *) name
    7110             : {
    7111             :         return name;
    7112             : }
    7113             : 
    7114             : 
    7115             : - (NSString *) shipUniqueName
    7116             : {
    7117             :         return shipUniqueName;
    7118             : }
    7119             : 
    7120             : 
    7121             : - (NSString *) shipClassName
    7122             : {
    7123             :         return shipClassName;
    7124             : }
    7125             : 
    7126             : 
    7127             : - (NSString *) displayName
    7128             : {
    7129             :         if (displayName == nil || [displayName length] == 0)  
    7130             :         {
    7131             :                 if ([shipUniqueName length] == 0)
    7132             :                 {
    7133             :                         if (shipClassName == nil)
    7134             :                         {
    7135             :                                 return name;
    7136             :                         }
    7137             :                         else 
    7138             :                         {
    7139             :                                 return shipClassName;
    7140             :                         }
    7141             :                 }
    7142             :                 else
    7143             :                 {
    7144             :                         if (shipClassName == nil)
    7145             :                         {
    7146             :                                 return [NSString stringWithFormat:@"%@: %@",name,shipUniqueName];
    7147             :                         }
    7148             :                         else
    7149             :                         {
    7150             :                                 return [NSString stringWithFormat:@"%@: %@",shipClassName,shipUniqueName];
    7151             :                         }
    7152             :                 }
    7153             :         }
    7154             :         return displayName;
    7155             : }
    7156             : 
    7157             : 
    7158             : // needed so that scan_description = scan_description doesn't have odd effects
    7159             : - (NSString *)scanDescriptionForScripting
    7160             : {
    7161             :         return scan_description;
    7162             : }
    7163             : 
    7164             : 
    7165             : - (NSString *)scanDescription
    7166             : {
    7167             :         if (scan_description != nil)
    7168             :         {
    7169             :                 return scan_description;
    7170             :         }
    7171             :         else
    7172             :         {
    7173             :                 NSString *desc = nil;
    7174             :                 switch ([self scanClass])
    7175             :                 {
    7176             :                 case CLASS_NEUTRAL:
    7177             :                         {
    7178             :                                 int legal = [self legalStatus];
    7179             :                                 int legal_i = 0;
    7180             :                                 if (legal > 0)
    7181             :                                 {
    7182             :                                         legal_i =  (legal <= 50) ? 1 : 2;
    7183             :                                 }
    7184             :                                 desc = [[[UNIVERSE descriptions] oo_arrayForKey:@"legal_status"] oo_stringAtIndex:legal_i];
    7185             :                         }
    7186             :                         break;
    7187             :         
    7188             :                 case CLASS_THARGOID:
    7189             :                         desc = DESC(@"legal-desc-alien");
    7190             :                         break;
    7191             :                 
    7192             :                 case CLASS_POLICE:
    7193             :                         desc = DESC(@"legal-desc-system-vessel");
    7194             :                         break;
    7195             :                 
    7196             :                 case CLASS_MILITARY:
    7197             :                         desc = DESC(@"legal-desc-military-vessel");
    7198             :                         break;
    7199             :                 
    7200             :                 default:
    7201             :                         break;
    7202             :                 }
    7203             :                 return desc;
    7204             :         }
    7205             : }
    7206             : 
    7207             : 
    7208             : - (void) setName:(NSString *)inName
    7209             : {
    7210             :         [name release];
    7211             :         name = [inName copy];
    7212             : }
    7213             : 
    7214             : 
    7215             : - (void) setShipUniqueName:(NSString *)inName
    7216             : {
    7217             :         [shipUniqueName release];
    7218             :         shipUniqueName = [inName copy];
    7219             : }
    7220             : 
    7221             : 
    7222             : - (void) setShipClassName:(NSString *)inName
    7223             : {
    7224             :         [shipClassName release];
    7225             :         shipClassName = [inName copy];
    7226             : }
    7227             : 
    7228             : 
    7229             : - (void) setDisplayName:(NSString *)inName
    7230             : {
    7231             :         [displayName release];
    7232             :         displayName = [inName copy];
    7233             : }
    7234             : 
    7235             : 
    7236             : - (void) setScanDescription:(NSString *)inName
    7237             : {
    7238             :         [scan_description release];
    7239             :         scan_description = [inName copy];
    7240             : }
    7241             : 
    7242             : 
    7243             : - (NSString *) identFromShip:(ShipEntity*) otherShip
    7244             : {
    7245             :         if ([self isJammingScanning] && ![otherShip hasMilitaryScannerFilter])
    7246             :         {
    7247             :                 return DESC(@"unknown-target");
    7248             :         }
    7249             :         return [self displayName];
    7250             : }
    7251             : 
    7252             : 
    7253             : - (BOOL) hasRole:(NSString *)role
    7254             : {
    7255             :         return [roleSet hasRole:role] || [role isEqual:primaryRole] || [role isEqual:[self shipDataKeyAutoRole]];
    7256             : }
    7257             : 
    7258             : 
    7259             : - (OORoleSet *)roleSet
    7260             : {
    7261             :         if (roleSet == nil)  roleSet = [[OORoleSet alloc] initWithRoleString:primaryRole];
    7262             :         return [[roleSet roleSetWithAddedRoleIfNotSet:primaryRole probability:1.0] roleSetWithAddedRoleIfNotSet:[self shipDataKeyAutoRole] probability:1.0];
    7263             : }
    7264             : 
    7265             : 
    7266             : - (void) addRole:(NSString *)role
    7267             : {
    7268             :         [self addRole:role withProbability:0.0f];
    7269             : }
    7270             : 
    7271             : 
    7272             : - (void) addRole:(NSString *)role withProbability:(float)probability
    7273             : {
    7274             :         if (![self hasRole:role])
    7275             :         {
    7276             :                 OORoleSet *newRoles = nil;
    7277             :                 if (roleSet != nil)  newRoles = [roleSet roleSetWithAddedRole:role probability:probability];
    7278             :                 else  newRoles = [OORoleSet roleSetWithRole:role probability:probability];
    7279             :                 if (newRoles != nil)
    7280             :                 {
    7281             :                         [roleSet release];
    7282             :                         roleSet = [newRoles retain];
    7283             :                 }
    7284             :         }
    7285             : }
    7286             : 
    7287             : 
    7288             : - (void) removeRole:(NSString *)role
    7289             : {
    7290             :         if ([self hasRole:role])
    7291             :         {
    7292             :                 OORoleSet *newRoles = [roleSet roleSetWithRemovedRole:role];
    7293             :                 if (newRoles != nil)
    7294             :                 {
    7295             :                         [roleSet release];
    7296             :                         roleSet = [newRoles retain];
    7297             :                 }
    7298             :         }
    7299             : }
    7300             : 
    7301             : 
    7302             : - (NSString *)primaryRole
    7303             : {
    7304             :         if (primaryRole == nil)
    7305             :         {
    7306             :                 primaryRole = [roleSet anyRole];
    7307             :                 if (primaryRole == nil)  primaryRole = @"trader";
    7308             :                 [primaryRole retain];
    7309             :                 OOLog(@"ship.noPrimaryRole", @"%@ had no primary role, randomly selected \"%@\".", [self name], primaryRole);
    7310             :         }
    7311             :         
    7312             :         return primaryRole;
    7313             : }
    7314             : 
    7315             : 
    7316             : // Exposed to AI.
    7317             : - (void)setPrimaryRole:(NSString *)role
    7318             : {
    7319             :         if (![role isEqual:primaryRole])
    7320             :         {
    7321             :                 [primaryRole release];
    7322             :                 primaryRole = [role copy];
    7323             :         }
    7324             : }
    7325             : 
    7326             : 
    7327             : - (BOOL)hasPrimaryRole:(NSString *)role
    7328             : {
    7329             :         return [[self primaryRole] isEqual:role];
    7330             : }
    7331             : 
    7332             : 
    7333             : - (BOOL)isPolice
    7334             : {
    7335             :         //bounty hunters have a police role, but are not police, so we must test by scan class, not by role
    7336             :         return [self scanClass] == CLASS_POLICE;
    7337             : }
    7338             : 
    7339             : - (BOOL)isThargoid
    7340             : {
    7341             :         return [self scanClass] == CLASS_THARGOID;
    7342             : }
    7343             : 
    7344             : 
    7345             : - (BOOL)isTrader
    7346             : {
    7347             :         return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-trader"];
    7348             : }
    7349             : 
    7350             : 
    7351             : - (BOOL)isPirate
    7352             : {
    7353             :         return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-pirate"];
    7354             : }
    7355             : 
    7356             : 
    7357             : - (BOOL)isMissile
    7358             : {
    7359             :         return ([[self primaryRole] hasSuffix:@"MISSILE"] || [self hasPrimaryRole:@"missile"]);
    7360             : }
    7361             : 
    7362             : 
    7363             : - (BOOL)isMine
    7364             : {
    7365             :         return [[self primaryRole] hasSuffix:@"MINE"];
    7366             : }
    7367             : 
    7368             : 
    7369             : - (BOOL)isWeapon
    7370             : {
    7371             :         return [self isMissile] || [self isMine];
    7372             : }
    7373             : 
    7374             : 
    7375             : - (BOOL)isEscort
    7376             : {
    7377             :         return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-escort"];
    7378             : }
    7379             : 
    7380             : 
    7381             : - (BOOL)isShuttle
    7382             : {
    7383             :         return [UNIVERSE role:[self primaryRole] isInCategory:@"oolite-shuttle"];
    7384             : }
    7385             : 
    7386             : 
    7387             : - (BOOL)isTurret
    7388             : {
    7389             :         return behaviour == BEHAVIOUR_TRACK_AS_TURRET;
    7390             : }
    7391             : 
    7392             : 
    7393             : - (BOOL)isPirateVictim
    7394             : {
    7395             :         return [UNIVERSE roleIsPirateVictim:[self primaryRole]];
    7396             : }
    7397             : 
    7398             : 
    7399             : - (BOOL)isExplicitlyUnpiloted
    7400             : {
    7401             :         return _explicitlyUnpiloted;
    7402             : }
    7403             : 
    7404             : 
    7405             : - (BOOL)isUnpiloted
    7406             : {
    7407             :         return [self isExplicitlyUnpiloted] || [self isHulk] || [self scanClass] == CLASS_ROCK || [self scanClass] == CLASS_CARGO;
    7408             : }
    7409             : 
    7410             : 
    7411           0 : static BOOL IsBehaviourHostile(OOBehaviour behaviour)
    7412             : {
    7413             :         switch (behaviour)
    7414             :         {
    7415             :                 case BEHAVIOUR_ATTACK_TARGET:
    7416             :                 case BEHAVIOUR_ATTACK_FLY_TO_TARGET:
    7417             :                 case BEHAVIOUR_ATTACK_FLY_FROM_TARGET:
    7418             :                 case BEHAVIOUR_RUNNING_DEFENSE:
    7419             :                 case BEHAVIOUR_FLEE_TARGET:
    7420             :                 case BEHAVIOUR_ATTACK_BREAK_OFF_TARGET:
    7421             :                 case BEHAVIOUR_ATTACK_SLOW_DOGFIGHT:
    7422             :                 case BEHAVIOUR_EVASIVE_ACTION:
    7423             :                 case BEHAVIOUR_FLEE_EVASIVE_ACTION:
    7424             :                 case BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX:
    7425             :         //      case BEHAVIOUR_ATTACK_MINING_TARGET:
    7426             :                 case BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE:
    7427             :                 case BEHAVIOUR_ATTACK_BROADSIDE:
    7428             :                 case BEHAVIOUR_ATTACK_BROADSIDE_LEFT:
    7429             :                 case BEHAVIOUR_ATTACK_BROADSIDE_RIGHT:
    7430             :           case BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE:
    7431             :                 case BEHAVIOUR_CLOSE_WITH_TARGET:
    7432             :           case BEHAVIOUR_ATTACK_SNIPER:
    7433             :                 case BEHAVIOUR_SCRIPTED_ATTACK_AI:
    7434             :                         return YES;
    7435             :                         
    7436             :                 default:
    7437             :                         return NO;
    7438             :         }
    7439             :         
    7440             :         return 100 < behaviour && behaviour < 120;
    7441             : }
    7442             : 
    7443             : 
    7444             : // Exposed to shaders.
    7445             : - (BOOL) hasHostileTarget
    7446             : {
    7447             :         Entity *t = [self primaryTarget];
    7448             :         if (t == nil || ![t isShip])
    7449             :         {
    7450             :                 return NO;
    7451             :         }
    7452             :         if ([self isMissile])
    7453             :         {
    7454             :                 return YES;     // missiles are always fired against a hostile target
    7455             :         }
    7456             :         if ((behaviour == BEHAVIOUR_AVOID_COLLISION)&&(previousCondition))
    7457             :         {
    7458             :                 int old_behaviour = [previousCondition oo_intForKey:@"behaviour"];
    7459             :                 return IsBehaviourHostile(old_behaviour);
    7460             :         }
    7461             :         return IsBehaviourHostile(behaviour);
    7462             : }
    7463             : 
    7464             : 
    7465             : - (BOOL) isHostileTo:(Entity *)entity
    7466             : {
    7467             :         return ([self hasHostileTarget] && [self primaryTarget] == entity);
    7468             : }
    7469             : 
    7470             : - (GLfloat) weaponRange
    7471             : {
    7472             :         return weaponRange;
    7473             : }
    7474             : 
    7475             : 
    7476             : - (void) setWeaponRange: (GLfloat) value
    7477             : {
    7478             :         weaponRange = value;
    7479             : }
    7480             : 
    7481             : 
    7482             : - (void) setWeaponDataFromType: (OOWeaponType) weapon_type
    7483             : {
    7484             :         weaponRange = getWeaponRangeFromType(weapon_type);
    7485             :         weapon_energy_use = [weapon_type weaponEnergyUse];
    7486             :         weapon_recharge_rate = [weapon_type weaponRechargeRate];
    7487             :         weapon_shot_temperature = [weapon_type weaponShotTemperature];
    7488             :         weapon_damage = [weapon_type weaponDamage];
    7489             : 
    7490             :         if (default_laser_color == nil)
    7491             :         {
    7492             :                 OOColor *wcol = [weapon_type weaponColor];
    7493             :                 if (wcol != nil)
    7494             :                 {
    7495             :                         [self setLaserColor:wcol];
    7496             :                 }
    7497             :         }
    7498             : 
    7499             : }
    7500             : 
    7501             : 
    7502             : - (float) energyRechargeRate
    7503             : {
    7504             :         return energy_recharge_rate;
    7505             : }
    7506             : 
    7507             : 
    7508             : - (void) setEnergyRechargeRate:(GLfloat)new
    7509             : {
    7510             :         energy_recharge_rate = new;
    7511             : }
    7512             : 
    7513             : 
    7514             : - (float) weaponRechargeRate
    7515             : {
    7516             :         return weapon_recharge_rate;
    7517             : }
    7518             : 
    7519             : 
    7520             : - (void) setWeaponRechargeRate:(float)value
    7521             : {
    7522             :         weapon_recharge_rate = value;
    7523             : }
    7524             : 
    7525             : 
    7526             : - (void) setWeaponEnergy:(float)value
    7527             : {
    7528             :         weapon_damage = value;
    7529             : }
    7530             : 
    7531             : 
    7532             : -       (OOWeaponFacing) currentWeaponFacing
    7533             : {
    7534             :         return currentWeaponFacing;
    7535             : }
    7536             : 
    7537             : 
    7538             : - (GLfloat) scannerRange
    7539             : {
    7540             :         return scannerRange;
    7541             : }
    7542             : 
    7543             : 
    7544             : - (void) setScannerRange: (GLfloat) value
    7545             : {
    7546             :         scannerRange = value;
    7547             : }
    7548             : 
    7549             : 
    7550             : - (Vector) reference
    7551             : {
    7552             :         return reference;
    7553             : }
    7554             : 
    7555             : 
    7556             : - (void) setReference:(Vector) v
    7557             : {
    7558             :         reference = v;
    7559             : }
    7560             : 
    7561             : 
    7562             : - (BOOL) reportAIMessages
    7563             : {
    7564             :         return reportAIMessages;
    7565             : }
    7566             : 
    7567             : 
    7568             : - (void) setReportAIMessages:(BOOL) yn
    7569             : {
    7570             :         reportAIMessages = yn;
    7571             : }
    7572             : 
    7573             : 
    7574             : - (void) transitionToAegisNone
    7575             : {
    7576             :         if (!suppressAegisMessages && aegis_status != AEGIS_NONE)
    7577             :         {
    7578             :                 Entity<OOStellarBody> *lastAegisLock = [self lastAegisLock];
    7579             :                 if (lastAegisLock != nil)
    7580             :                 {
    7581             :                         [self doScriptEvent:OOJSID("shipExitedPlanetaryVicinity") withArgument:lastAegisLock];
    7582             :                         
    7583             :                         if (lastAegisLock == [UNIVERSE sun])
    7584             :                         {
    7585             :                                 [shipAI message:@"AWAY_FROM_SUN"];
    7586             :                         }
    7587             :                         else
    7588             :                         {
    7589             :                                 [shipAI message:@"AWAY_FROM_PLANET"];
    7590             :                         }
    7591             :                 }
    7592             : 
    7593             :                 if (aegis_status != AEGIS_CLOSE_TO_ANY_PLANET)
    7594             :                 {
    7595             :                         [shipAI message:@"AEGIS_NONE"];
    7596             :                 }
    7597             :         }
    7598             :         aegis_status = AEGIS_NONE;
    7599             : }
    7600             : 
    7601             : 
    7602           0 : static float SurfaceDistanceSqaredV(HPVector reference, Entity<OOStellarBody> *stellar)
    7603             : {
    7604             :         float centerDistance = HPmagnitude2(HPvector_subtract([stellar position], reference));
    7605             :         float r = [stellar radius];
    7606             :         /*      1.35: empirical value used to help determine proximity when non-nested
    7607             :                         planets are close to each other
    7608             :                 */
    7609             :         return centerDistance - 1.35 * r * r;
    7610             : }
    7611             : 
    7612             : 
    7613           0 : static float SurfaceDistanceSqared(Entity *reference, Entity<OOStellarBody> *stellar)
    7614             : {
    7615             :         return SurfaceDistanceSqaredV([reference position], stellar);
    7616             : }
    7617             : 
    7618             : 
    7619           0 : NSComparisonResult ComparePlanetsBySurfaceDistance(id i1, id i2, void* context)
    7620             : {
    7621             :         HPVector p = [(ShipEntity*) context position];
    7622             :         OOPlanetEntity* e1 = i1;
    7623             :         OOPlanetEntity* e2 = i2;
    7624             :         
    7625             :         float p1 = SurfaceDistanceSqaredV(p, e1);
    7626             :         float p2 = SurfaceDistanceSqaredV(p, e2);
    7627             :         
    7628             :         if (p1 < p2) return NSOrderedAscending;
    7629             :         if (p1 > p2) return NSOrderedDescending;
    7630             :         
    7631             :         return NSOrderedSame;
    7632             : }
    7633             : 
    7634             : 
    7635             : - (OOPlanetEntity *) findNearestPlanet
    7636             : {
    7637             :         /*
    7638             :                 Performance note: this method is called every frame by every ship, and
    7639             :                 has a significant profiler presence.
    7640             :                 -- Ahruman 2012-09-13
    7641             :         */
    7642             :         OOPlanetEntity *planet = nil, *bestPlanet = nil;
    7643             :         float bestRange = INFINITY;
    7644             :         HPVector myPosition = [self position];
    7645             :         
    7646             :         // valgrind complains about this line here. Might be compiler/GNUstep bug? 
    7647             :         // should we go back to a traditional enumerator? - CIM
    7648             :         // similar complaints about the other foreach() in this file
    7649             :         foreach (planet, [UNIVERSE planets])
    7650             :         {
    7651             :                 // Ignore miniature planets.
    7652             :                 if ([planet planetType] == STELLAR_TYPE_MINIATURE)  continue;
    7653             :                 
    7654             :                 float range = SurfaceDistanceSqaredV(myPosition, planet);
    7655             :                 if (range < bestRange)
    7656             :                 {
    7657             :                         bestPlanet = planet;
    7658             :                         bestRange = range;
    7659             :                 }
    7660             :         }
    7661             :         
    7662             :         return bestPlanet;
    7663             : }
    7664             : 
    7665             : 
    7666             : - (Entity<OOStellarBody> *) findNearestStellarBody
    7667             : {
    7668             :         Entity<OOStellarBody> *match = [self findNearestPlanet];
    7669             :         OOSunEntity *sun = [UNIVERSE sun];
    7670             :         
    7671             :         if (sun != nil)
    7672             :         {
    7673             :                 if (match == nil ||
    7674             :                         SurfaceDistanceSqared(self, sun) < SurfaceDistanceSqared(self, match))
    7675             :                 {
    7676             :                         match = sun;
    7677             :                 }
    7678             :         }
    7679             :         
    7680             :         return match;
    7681             : }
    7682             : 
    7683             : 
    7684             : - (OOPlanetEntity *) findNearestPlanetExcludingMoons
    7685             : {
    7686             :         OOPlanetEntity          *result = nil;
    7687             :         OOPlanetEntity          *planet = nil;
    7688             :         NSArray                         *bodies = nil;
    7689             :         NSArray                         *planets = nil;
    7690             :         unsigned                        i;
    7691             :         
    7692             :         bodies = [UNIVERSE planets];
    7693             :         planets = [NSMutableArray arrayWithCapacity:[bodies count]];
    7694             : 
    7695             :         for (i=0; i < [bodies count]; i++)
    7696             :         {
    7697             :                 planet = [bodies objectAtIndex:i];
    7698             :                 if([planet planetType] == STELLAR_TYPE_NORMAL_PLANET)
    7699             :                                         planets = [planets arrayByAddingObject:planet];
    7700             :         }
    7701             :         
    7702             :         if ([planets count] == 0)  return nil;
    7703             :         
    7704             :         planets = [planets sortedArrayUsingFunction:ComparePlanetsBySurfaceDistance context:self];
    7705             :         result = [planets objectAtIndex:0];
    7706             :                 
    7707             :         return result;
    7708             : }
    7709             : 
    7710             : 
    7711             : - (OOAegisStatus) checkForAegis
    7712             : {
    7713             :         Entity<OOStellarBody>     *nearest = [self findNearestStellarBody];
    7714             :         BOOL                                    sunGoneNova = [[UNIVERSE sun] goneNova];
    7715             :         
    7716             :         if (nearest == nil)
    7717             :         {
    7718             :                 if (aegis_status != AEGIS_NONE)
    7719             :                 {
    7720             :                         // Planet disappeared!
    7721             :                         [self transitionToAegisNone];
    7722             :                 }
    7723             :                 return AEGIS_NONE;
    7724             :         }
    7725             :         // check planet
    7726             :         float                   cr = [nearest radius];
    7727             :         float                   cr2 = cr * cr;
    7728             :         OOAegisStatus   result = AEGIS_NONE;
    7729             :         float                   d2 = HPmagnitude2(HPvector_subtract([nearest position], [self position]));
    7730             :         // not scannerRange: aegis shouldn't depend on that
    7731             :         float           sd2 = SCANNER_MAX_RANGE2 * 10.0f;
    7732             : 
    7733             :         // check if nearing a surface
    7734             :         unsigned wasNearPlanetSurface = isNearPlanetSurface;    // isNearPlanetSurface is a bit flag, not an actual BOOL
    7735             :         isNearPlanetSurface = (d2 - cr2) < (250000.0f + 1000.0f * cr); //less than 500m from the surface: (a+b)*(a+b) = a*a+b*b +2*a*b
    7736             : 
    7737             : 
    7738             :         if (EXPECT_NOT((wasNearPlanetSurface != isNearPlanetSurface) && !suppressAegisMessages))
    7739             :         {
    7740             :                 if (isNearPlanetSurface)
    7741             :                 {
    7742             :                         [self doScriptEvent:OOJSID("shipApproachingPlanetSurface") withArgument:nearest];
    7743             :                         [shipAI reactToMessage:@"APPROACHING_SURFACE" context:@"flight update"];
    7744             :                 }
    7745             :                 else
    7746             :                 {
    7747             :                         [self doScriptEvent:OOJSID("shipLeavingPlanetSurface") withArgument:nearest];
    7748             :                         [shipAI reactToMessage:@"LEAVING_SURFACE" context:@"flight update"];
    7749             :                 }
    7750             :         }
    7751             :         
    7752             :         // being close to the station takes precedence over planets
    7753             :         StationEntity   *the_station = [UNIVERSE station];
    7754             :         if (the_station)
    7755             :         {
    7756             :                 sd2 = HPmagnitude2(HPvector_subtract([the_station position], [self position]));
    7757             :         }
    7758             :         // again, notional scanner range is intentional
    7759             :         if (sd2 < SCANNER_MAX_RANGE2 * 4.0f) // double scanner range
    7760             :         {
    7761             :                 result = AEGIS_IN_DOCKING_RANGE;
    7762             :         }
    7763             :         else if (EXPECT_NOT(isNearPlanetSurface || d2 < cr2 * 9.0f)) // to 3x radius of any planet/moon - or 500m of tiny ones,
    7764             :         {
    7765             :                 result = AEGIS_CLOSE_TO_ANY_PLANET;
    7766             :                 if (EXPECT((OOPlanetEntity *)nearest == [UNIVERSE planet]))
    7767             :                 {
    7768             :                         result = AEGIS_CLOSE_TO_MAIN_PLANET;
    7769             :                 }
    7770             :         }
    7771             :         // need to do this check separately from above case to avoid oddity where
    7772             :         // main planet and small moon are at just the wrong distance. - CIM
    7773             :         if (result != AEGIS_CLOSE_TO_MAIN_PLANET && result != AEGIS_IN_DOCKING_RANGE && !sunGoneNova)
    7774             :         {
    7775             :                 // are we also close to the main planet?
    7776             :                 OOPlanetEntity *mainPlanet = [UNIVERSE planet];
    7777             :                 d2 = HPmagnitude2(HPvector_subtract([mainPlanet position], [self position]));
    7778             :                 cr2 = [mainPlanet radius];
    7779             :                 cr2 *= cr2;     
    7780             :                 if (d2 < cr2 * 9.0f)
    7781             :                 {
    7782             :                         nearest = mainPlanet;
    7783             :                         result = AEGIS_CLOSE_TO_MAIN_PLANET;
    7784             :                 }
    7785             :         }
    7786             : 
    7787             : 
    7788             :         /*      Rewrote aegis stuff and tested it against redux.oxp that adds multiple planets and moons.
    7789             :                 Made sure AI scripts can differentiate between MAIN and NON-MAIN planets so they can decide
    7790             :                 if they can dock at the systemStation or just any station.
    7791             :                 Added sun detection so route2Patrol can turn before they heat up in the sun.
    7792             :                 -- Eric 2009-07-11
    7793             :                 
    7794             :                 More rewriting of the aegis stuff, it's now a bit faster and works properly when moving
    7795             :                 from one secondary planet/moon vicinity to another one.  -- Kaks 20120917
    7796             :         */
    7797             :         if (EXPECT(!suppressAegisMessages))
    7798             :         {
    7799             :                 // script/AI messages on change in status
    7800             :                 if (EXPECT_NOT(aegis_status == AEGIS_IN_DOCKING_RANGE && result != aegis_status))
    7801             :                 {
    7802             :                         [self doScriptEvent:OOJSID("shipExitedStationAegis") withArgument:the_station];
    7803             :                         [shipAI message:@"AEGIS_LEAVING_DOCKING_RANGE"];
    7804             :                 }
    7805             :                 
    7806             :                 if (EXPECT_NOT(result == AEGIS_IN_DOCKING_RANGE && aegis_status != result))
    7807             :                 {
    7808             :                         [self doScriptEvent:OOJSID("shipEnteredStationAegis") withArgument:the_station];
    7809             :                         [shipAI message:@"AEGIS_IN_DOCKING_RANGE"];
    7810             :                         
    7811             :                         if([self lastAegisLock] == nil && !sunGoneNova) // With small main planets the station aegis can come before planet aegis
    7812             :                         {
    7813             :                                 [self doScriptEvent:OOJSID("shipEnteredPlanetaryVicinity") withArgument:[UNIVERSE planet]];
    7814             :                                 [self setLastAegisLock:[UNIVERSE planet]];
    7815             :                         }
    7816             :                 }
    7817             :                 else if (EXPECT_NOT(result == AEGIS_NONE && aegis_status != result))
    7818             :                 {
    7819             :                         if([self lastAegisLock] == nil && !sunGoneNova)
    7820             :                         {
    7821             :                                 [self setLastAegisLock:[UNIVERSE planet]];  // in case of a first launch from a near-planet station.
    7822             :                         }
    7823             :                         [self transitionToAegisNone];
    7824             :                 }
    7825             :                 // approaching..
    7826             :                 else if (EXPECT_NOT((result == AEGIS_CLOSE_TO_ANY_PLANET || result == AEGIS_CLOSE_TO_MAIN_PLANET) && [self lastAegisLock] != nearest))
    7827             :                 {
    7828             :                         if(aegis_status != AEGIS_NONE && [self lastAegisLock] != nil)   // we were close to another stellar body
    7829             :                         {
    7830             :                                 [self doScriptEvent:OOJSID("shipExitedPlanetaryVicinity") withArgument:[self lastAegisLock]];
    7831             :                                 [shipAI message:@"AWAY_FROM_PLANET"]; // fires for suns, planets and moons.
    7832             :                         }
    7833             :                         [self doScriptEvent:OOJSID("shipEnteredPlanetaryVicinity") withArgument:nearest];
    7834             :                         [self setLastAegisLock:nearest];
    7835             :                         
    7836             :                         if (EXPECT_NOT([nearest isSun]))
    7837             :                         {
    7838             :                                 [shipAI message:@"CLOSE_TO_SUN"];
    7839             :                         }
    7840             :                         else
    7841             :                         {
    7842             :                                 [shipAI message:@"CLOSE_TO_PLANET"];
    7843             :                                 
    7844             :                                 if (EXPECT(result == AEGIS_CLOSE_TO_MAIN_PLANET))
    7845             :                                 {
    7846             :                                         // It's been years since 1.71 - it should be safe enough to comment out the line below for 1.77/1.78 -- Kaks 20120917
    7847             :                                         //[shipAI message:@"AEGIS_CLOSE_TO_PLANET"];      // fires only for main planets, kept for compatibility with pre-1.72 AI plists.
    7848             :                                         [shipAI message:@"AEGIS_CLOSE_TO_MAIN_PLANET"];  // fires only for main planet.
    7849             :                                 }
    7850             :                                 else if (EXPECT_NOT([nearest planetType] == STELLAR_TYPE_MOON))
    7851             :                                 {
    7852             :                                         [shipAI message:@"CLOSE_TO_MOON"];
    7853             :                                 }
    7854             :                                 else
    7855             :                                 {
    7856             :                                         [shipAI message:@"CLOSE_TO_SECONDARY_PLANET"];
    7857             :                                 }
    7858             :                         }
    7859             :                 }
    7860             :                 
    7861             : 
    7862             :         }
    7863             :         if (result == AEGIS_NONE)
    7864             :         {
    7865             :                 [self setLastAegisLock:nil];
    7866             :         }
    7867             : 
    7868             :         aegis_status = result;  // put this here
    7869             :         return result;
    7870             : }
    7871             : 
    7872             : 
    7873             : - (void) forceAegisCheck
    7874             : {
    7875             :         _nextAegisCheck = -1.0f;
    7876             : }
    7877             : 
    7878             : 
    7879             : - (BOOL) withinStationAegis
    7880             : {
    7881             :         return aegis_status == AEGIS_IN_DOCKING_RANGE;
    7882             : }
    7883             : 
    7884             : 
    7885           0 : - (Entity<OOStellarBody> *) lastAegisLock
    7886             : {
    7887             :         Entity<OOStellarBody> *stellar = [_lastAegisLock weakRefUnderlyingObject];
    7888             :         if (stellar == nil)
    7889             :         {
    7890             :                 [_lastAegisLock release];
    7891             :                 _lastAegisLock = nil;
    7892             :         }
    7893             :         
    7894             :         return stellar;
    7895             : }
    7896             : 
    7897             : 
    7898             : - (void) setLastAegisLock:(Entity<OOStellarBody> *)lastAegisLock
    7899             : {
    7900             :         [_lastAegisLock release];
    7901             :         _lastAegisLock = [lastAegisLock weakRetain];
    7902             : }
    7903             : 
    7904             : 
    7905             : - (OOSystemID) homeSystem
    7906             : {
    7907             :         return home_system;
    7908             : }
    7909             : 
    7910             : 
    7911             : - (OOSystemID) destinationSystem
    7912             : {
    7913             :         return destination_system;
    7914             : }
    7915             : 
    7916             : 
    7917             : - (void) setHomeSystem:(OOSystemID)s
    7918             : {
    7919             :         home_system = s;
    7920             : }
    7921             : 
    7922             : 
    7923             : - (void) setDestinationSystem:(OOSystemID)s
    7924             : {
    7925             :         destination_system = s;
    7926             : }
    7927             : 
    7928             : 
    7929           0 : - (void) setStatus:(OOEntityStatus) stat
    7930             : {
    7931             :         if ([self status] == stat) return;
    7932             :         [super setStatus:stat];
    7933             :         if (stat == STATUS_LAUNCHING)
    7934             :         {
    7935             :                 launch_time = [UNIVERSE getTime];
    7936             :         }
    7937             : }
    7938             : 
    7939             : - (void) setLaunchDelay:(double)delay
    7940             : {
    7941             :         launch_delay = delay;
    7942             : }
    7943             : 
    7944             : 
    7945             : - (NSArray *) crew
    7946             : {
    7947             :         return crew;
    7948             : }
    7949             : 
    7950             : 
    7951             : - (void) setCrew:(NSArray *)crewArray
    7952             : {
    7953             :         if ([self isExplicitlyUnpiloted])
    7954             :         {
    7955             :                 //unpiloted ships cannot have crew
    7956             :                 // but may have crew before isExplicitlyUnpiloted set, so force *that* to clear too
    7957             :                 [crew autorelease];
    7958             :                 crew = nil;
    7959             :                 return;
    7960             :         }
    7961             :         //do not set to hulk here when crew is nil (or 0).  Some things like missiles have no crew.
    7962             :         [crew autorelease];
    7963             :         crew = [crewArray copy];
    7964             : }
    7965             : 
    7966             : 
    7967             : - (void) setSingleCrewWithRole:(NSString *)crewRole
    7968             : {
    7969             :         if (![self isUnpiloted])
    7970             :         {
    7971             :                 OOCharacter *crewMember = [OOCharacter randomCharacterWithRole:crewRole
    7972             :                                                                                                  andOriginalSystem:[self homeSystem]];
    7973             :                 [self setCrew:[NSArray arrayWithObject:crewMember]];
    7974             :         }
    7975             : }
    7976             : 
    7977             : 
    7978             : - (NSArray *) crewForScripting
    7979             : {
    7980             :         if (crew == nil)
    7981             :         {
    7982             :                 return nil;
    7983             :         }
    7984             :         OOCharacter *crewMember = nil;
    7985             :         NSMutableArray *result = [NSMutableArray arrayWithCapacity:4];
    7986             :         foreach (crewMember, crew)
    7987             :         {
    7988             :                 NSDictionary *crewDict = [crewMember infoForScripting];
    7989             :                 [result addObject:crewDict];
    7990             :         }
    7991             :         return result;
    7992             : }
    7993             : 
    7994             : 
    7995             : 
    7996             : - (void) setStateMachine:(NSString *)smName
    7997             : {
    7998             :         [self setAITo:smName];
    7999             : }
    8000             : 
    8001             : 
    8002             : - (void) setAI:(AI *)ai
    8003             : {
    8004             :         [ai retain];
    8005             :         if (shipAI)
    8006             :         {
    8007             :                 [shipAI clearAllData];
    8008             :                 [shipAI autorelease];
    8009             :         }
    8010             :         shipAI = ai;
    8011             : }
    8012             : 
    8013             : 
    8014             : - (AI *) getAI
    8015             : {
    8016             :         return shipAI;
    8017             : }
    8018             : 
    8019             : 
    8020             : - (BOOL) hasAutoAI
    8021             : {
    8022             :         return  [[self shipInfoDictionary] oo_fuzzyBooleanForKey:@"auto_ai" defaultValue:YES];
    8023             : }
    8024             : 
    8025             : 
    8026             : - (BOOL) hasNewAI
    8027             : {
    8028             :         return [[[self getAI] name] isEqualToString:@"nullAI.plist"];
    8029             : }
    8030             : 
    8031             : 
    8032             : - (BOOL) hasAutoWeapons
    8033             : {
    8034             :         return  [[self shipInfoDictionary] oo_fuzzyBooleanForKey:@"auto_weapons" defaultValue:NO];
    8035             : }
    8036             : 
    8037             : 
    8038             : - (void) setShipScript:(NSString *)script_name
    8039             : {
    8040             :         NSMutableDictionary             *properties = nil;
    8041             :         NSArray                                 *actions = nil;
    8042             :         
    8043             :         properties = [NSMutableDictionary dictionary];
    8044             :         [properties setObject:self forKey:@"ship"];
    8045             :         
    8046             :         [script autorelease];
    8047             :         script = [OOScript jsScriptFromFileNamed:script_name properties:properties];
    8048             :         
    8049             :         if (script == nil)
    8050             :         {
    8051             :                 actions = [shipinfoDictionary oo_arrayForKey:@"launch_actions"];
    8052             :                 if (actions)
    8053             :                 {
    8054             :                         OOStandardsDeprecated([NSString stringWithFormat:@"The launch_actions ship key is deprecated on %@.",[self displayName]]);
    8055             :                         if (!OOEnforceStandards()) 
    8056             :                         {
    8057             :                                 [properties setObject:actions forKey:@"legacy_launchActions"];        
    8058             :                         }
    8059             :                 }
    8060             : 
    8061             :                 actions = [shipinfoDictionary oo_arrayForKey:@"script_actions"];
    8062             :                 if (actions)
    8063             :                 {
    8064             :                         OOStandardsDeprecated([NSString stringWithFormat:@"The script_actions ship key is deprecated on %@.",[self displayName]]);
    8065             :                         if (!OOEnforceStandards()) 
    8066             :                         {
    8067             :                                 [properties setObject:actions forKey:@"legacy_scriptActions"];        
    8068             :                         }
    8069             :                 }
    8070             : 
    8071             :                 actions = [shipinfoDictionary oo_arrayForKey:@"death_actions"];
    8072             :                 if (actions)
    8073             :                 {
    8074             :                         OOStandardsDeprecated([NSString stringWithFormat:@"The death_actions ship key is deprecated on %@.",[self displayName]]);
    8075             :                         if (!OOEnforceStandards()) 
    8076             :                         {
    8077             :                                 [properties setObject:actions forKey:@"legacy_deathActions"]; 
    8078             :                         }
    8079             :                 }
    8080             : 
    8081             :                 actions = [shipinfoDictionary oo_arrayForKey:@"setup_actions"];
    8082             :                 if (actions)
    8083             :                 {
    8084             :                         OOStandardsDeprecated([NSString stringWithFormat:@"The setup_actions ship key is deprecated on %@.",[self displayName]]);
    8085             :                         if (!OOEnforceStandards()) 
    8086             :                         {
    8087             :                                 [properties setObject:actions forKey:@"legacy_setupActions"]; 
    8088             :                         }
    8089             :                 }
    8090             :                 
    8091             :                 script = [OOScript jsScriptFromFileNamed:@"oolite-default-ship-script.js"
    8092             :                                                                           properties:properties];
    8093             :         }
    8094             :         [script retain];
    8095             : }
    8096             : 
    8097             : 
    8098             : - (double)frustration
    8099             : {
    8100             :         return frustration;
    8101             : }
    8102             : 
    8103             : 
    8104             : - (OOFuelQuantity) fuel
    8105             : {
    8106             :         return fuel;
    8107             : }
    8108             : 
    8109             : 
    8110             : - (void) setFuel:(OOFuelQuantity) amount
    8111             : {
    8112             :         if (amount > [self fuelCapacity])  amount = [self fuelCapacity];
    8113             :         
    8114             :         fuel = amount;
    8115             : }
    8116             : 
    8117             : 
    8118             : - (OOFuelQuantity) fuelCapacity
    8119             : {
    8120             :         // FIXME: shipdata.plist can allow greater fuel quantities (without extending hyperspace range). Need some consistency here.
    8121             :         return PLAYER_MAX_FUEL;
    8122             : }
    8123             : 
    8124             : 
    8125             : - (GLfloat) fuelChargeRate
    8126             : {
    8127             :         GLfloat         rate = 1.0; // Standard (& strict play) charge rate.
    8128             :         
    8129             : #if MASS_DEPENDENT_FUEL_PRICES
    8130             :         
    8131             :         if (EXPECT(PLAYER != nil && mass> 0 && mass != [PLAYER baseMass]))
    8132             :         {
    8133             :                 rate = calcFuelChargeRate(mass);
    8134             :         }
    8135             : 
    8136             :         OOLog(@"fuelPrices", @"\"%@\" fuel charge rate: %.2f (mass ratio: %.2f/%.2f)", [self shipDataKey], rate, mass, [PLAYER baseMass]);
    8137             : #endif
    8138             :         
    8139             :         return rate;
    8140             : }
    8141             : 
    8142             : 
    8143             : - (void) applySticks:(double)delta_t
    8144             : {
    8145             :         
    8146             :         double  rate1 = 2.0 * delta_t; //roll 
    8147             :         double  rate2 = 4.0 * delta_t; //pitch
    8148             :         double  rate3 = 4.0 * delta_t; //yaw
    8149             : 
    8150             :         if (((stick_roll > 0.0)&&(flightRoll < 0.0))||((stick_roll < 0.0)&&(flightRoll > 0.0)))
    8151             :                 rate1 *= 4.0;   // much faster correction
    8152             :         if (((stick_pitch > 0.0)&&(flightPitch < 0.0))||((stick_pitch < 0.0)&&(flightPitch > 0.0)))
    8153             :                 rate2 *= 4.0;   // much faster correction
    8154             :         if (((stick_yaw > 0.0)&&(flightYaw < 0.0))||((stick_yaw < 0.0)&&(flightYaw > 0.0)))
    8155             :                 rate3 *= 4.0;   // much faster correction
    8156             : 
    8157             :         if (accuracy >= COMBAT_AI_TRACKS_CLOSER) 
    8158             :         {
    8159             :                 if (stick_roll == 0.0)
    8160             :                         rate1 *= 2.0;   // faster correction
    8161             :                 if (stick_pitch == 0.0)
    8162             :                         rate2 *= 2.0;   // faster correction
    8163             :                 if (stick_yaw == 0.0)
    8164             :                         rate3 *= 2.0;   // faster correction
    8165             :         }
    8166             : 
    8167             :         // apply stick movement limits
    8168             :         if (flightRoll < stick_roll - rate1)
    8169             :         {
    8170             :                 flightRoll = flightRoll + rate1;
    8171             :         }
    8172             :         else if (flightRoll > stick_roll + rate1)
    8173             :         {
    8174             :                 flightRoll = flightRoll - rate1;
    8175             :         }
    8176             :         else
    8177             :         {
    8178             :                 flightRoll = stick_roll;
    8179             :         }
    8180             : 
    8181             :         if (flightPitch < stick_pitch - rate2)
    8182             :         {
    8183             :                 flightPitch = flightPitch + rate2;
    8184             :         }
    8185             :         else if (flightPitch > stick_pitch + rate2)
    8186             :         {
    8187             :                 flightPitch = flightPitch - rate2;
    8188             :         }
    8189             :         else
    8190             :         {
    8191             :                 flightPitch = stick_pitch;
    8192             :         }
    8193             : 
    8194             :         if (flightYaw < stick_yaw - rate3)
    8195             :         {
    8196             :                 flightYaw = flightYaw + rate3;
    8197             :         }
    8198             :         else if (flightYaw > stick_yaw + rate3)
    8199             :         {
    8200             :                 flightYaw = flightYaw - rate3;
    8201             :         }
    8202             :         else
    8203             :         {
    8204             :                 flightYaw = stick_yaw;
    8205             :         }
    8206             : 
    8207             : }
    8208             : 
    8209             : 
    8210             : - (void) setRoll:(double) amount
    8211             : {
    8212             :         flightRoll = amount * M_PI / 2.0;
    8213             : }
    8214             : 
    8215             : 
    8216             : - (void) setRawRoll:(double) amount
    8217             : {
    8218             :         flightRoll = amount;
    8219             : }
    8220             : 
    8221             : 
    8222             : - (void) setPitch:(double) amount
    8223             : {
    8224             :         flightPitch = amount * M_PI / 2.0;
    8225             : }
    8226             : 
    8227             : 
    8228             : - (void) setYaw:(double) amount
    8229             : {
    8230             :         flightYaw = amount * M_PI / 2.0;
    8231             : }
    8232             : 
    8233             : 
    8234             : - (void) setThrust:(double) amount
    8235             : {
    8236             :         thrust = amount;
    8237             : }
    8238             : 
    8239             : 
    8240             : - (void) setThrustForDemo:(float) factor
    8241             : {
    8242             :         flightSpeed = factor * maxFlightSpeed;
    8243             : }
    8244             : 
    8245             : 
    8246             : - (void) setBounty:(OOCreditsQuantity) amount
    8247             : {
    8248             :         [self setBounty:amount withReason:kOOLegalStatusReasonUnknown];
    8249             : }
    8250             : 
    8251             : 
    8252             : - (void) setBounty:(OOCreditsQuantity) amount withReason:(OOLegalStatusReason)reason
    8253             : {
    8254             :         if ([self isSubEntity]) 
    8255             :         {
    8256             :                 [[self parentEntity] setBounty:amount withReason:reason];
    8257             :         }
    8258             :         else 
    8259             :         {
    8260             :                 if ((scanClass == CLASS_THARGOID || scanClass == CLASS_STATION) && reason != kOOLegalStatusReasonSetup && reason != kOOLegalStatusReasonByScript)
    8261             :                 {
    8262             :                         return; // no standard bounties for Thargoids / Stations
    8263             :                 }
    8264             :                 if (scanClass == CLASS_POLICE && amount != 0)
    8265             :                 {
    8266             :                         return; // police never have bounties
    8267             :                 }
    8268             :                 NSString* nReason = OOStringFromLegalStatusReason(reason);
    8269             :                 [self setBounty:amount withReasonAsString:nReason];
    8270             :         }
    8271             : }
    8272             : 
    8273             : - (void) setBounty:(OOCreditsQuantity) amount withReasonAsString:(NSString*)reason
    8274             : {
    8275             :         if ([self isSubEntity]) 
    8276             :         {
    8277             :                 [[self parentEntity] setBounty:amount withReasonAsString:reason];
    8278             :         }
    8279             :         else 
    8280             :         {
    8281             :                 JSContext *context = OOJSAcquireContext();
    8282             :         
    8283             :                 jsval amountVal = JSVAL_VOID;
    8284             :                 JS_NewNumberValue(context, (int)amount-(int)bounty, &amountVal);
    8285             : 
    8286             :                 bounty = amount; // can't set the new bounty until the size of the change is known
    8287             : 
    8288             :                 jsval reasonVal = OOJSValueFromNativeObject(context,reason);
    8289             :                 
    8290             :                 ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal);
    8291             :                 
    8292             :                 OOJSRelinquishContext(context);
    8293             : 
    8294             :         }
    8295             : }
    8296             : 
    8297             : 
    8298             : 
    8299             : - (OOCreditsQuantity) bounty
    8300             : {
    8301             :         if ([self isSubEntity]) 
    8302             :         {
    8303             :                 return [[self parentEntity] bounty];
    8304             :         }
    8305             :         else 
    8306             :         {               
    8307             :                 return bounty;
    8308             :         }
    8309             : }
    8310             : 
    8311             : 
    8312             : - (int) legalStatus
    8313             : {
    8314             :         if (scanClass == CLASS_THARGOID)
    8315             :                 return 5 * collision_radius;
    8316             :         if (scanClass == CLASS_ROCK)
    8317             :                 return 0;
    8318             :         return (int)[self bounty];
    8319             : }
    8320             : 
    8321             : 
    8322             : - (void) setCommodity:(OOCommodityType)co_type andAmount:(OOCargoQuantity)co_amount
    8323             : {
    8324             :         if (co_type != nil)
    8325             :         {
    8326             :                 /* The tmp variable is needed as scoopUp can cause the method
    8327             :                  * to be passed a reference to self.commodity_type, so DESTROY
    8328             :                  * then copying the parameter segfaults */
    8329             :                 NSString *tmp = [co_type copy];
    8330             :                 DESTROY(commodity_type);
    8331             :                 commodity_type = tmp;
    8332             :                 commodity_amount = co_amount;
    8333             :         }
    8334             : }
    8335             : 
    8336             : 
    8337             : - (void) setCommodityForPod:(OOCommodityType)co_type andAmount:(OOCargoQuantity)co_amount
    8338             : {
    8339             :         // can be nil for pods
    8340             :         if (co_type == nil)
    8341             :         {
    8342             :                 DESTROY(commodity_type);
    8343             :                 commodity_amount = 0;
    8344             :                 return;
    8345             :         }
    8346             :         // pod content should never be greater than 1 ton or this will give cargo counting problems elsewhere in the code.
    8347             :         // so do first a mass check for cargo added by script/plist.
    8348             :         OOMassUnit      unit = [[UNIVERSE commodityMarket] massUnitForGood:co_type];
    8349             :         if (unit == UNITS_TONS && co_amount > 1) co_amount = 1;
    8350             :         else if (unit == UNITS_KILOGRAMS && co_amount > 1000) co_amount = 1000;
    8351             :         else if (unit == UNITS_GRAMS && co_amount > 1000000) co_amount = 1000000;
    8352             :         [self setCommodity:co_type andAmount:co_amount];
    8353             : }
    8354             : 
    8355             : 
    8356             : - (OOCommodityType) commodityType
    8357             : {
    8358             :         return commodity_type;
    8359             : }
    8360             : 
    8361             : 
    8362             : - (OOCargoQuantity) commodityAmount
    8363             : {
    8364             :         return commodity_amount;
    8365             : }
    8366             : 
    8367             : 
    8368             : - (OOCargoQuantity) maxAvailableCargoSpace
    8369             : {
    8370             :         return max_cargo - equipment_weight;
    8371             : }
    8372             : 
    8373             : 
    8374             : - (void) setMaxAvailableCargoSpace:(OOCargoQuantity)new
    8375             : {
    8376             :         max_cargo = new + equipment_weight;
    8377             : }
    8378             : 
    8379             : 
    8380             : - (OOCargoQuantity) availableCargoSpace
    8381             : {
    8382             :         // OOCargoQuantity is unsigned, we need to check for underflows.
    8383             :         if (EXPECT_NOT([self cargoQuantityOnBoard] + equipment_weight >= max_cargo)) return 0;
    8384             :         return [self maxAvailableCargoSpace] - [self cargoQuantityOnBoard];
    8385             : }
    8386             : 
    8387             : 
    8388             : - (OOCargoQuantity) cargoQuantityOnBoard
    8389             : {
    8390             :         NSUInteger result = [[self cargo] count];
    8391             :         NSAssert(result < UINT32_MAX, @"Cargo quantity out of bounds.");
    8392             :         return (OOCargoQuantity)result;
    8393             : }
    8394             : 
    8395             : 
    8396             : 
    8397             : - (OOCargoType) cargoType
    8398             : {
    8399             :         return cargo_type;
    8400             : }
    8401             : 
    8402             : 
    8403             : /* Note: this array probably contains some template cargo pods. Do not
    8404             :  * pass it to Javascript without reifying them first. */
    8405             : - (NSMutableArray*) cargo
    8406             : {
    8407             :         return cargo;
    8408             : }
    8409             : 
    8410             : 
    8411             : - (NSArray *) cargoListForScripting
    8412             : {
    8413             :         NSMutableArray          *list = [NSMutableArray array];
    8414             :         
    8415             :         OOCommodityType         good = nil;
    8416             :         NSArray                         *goods = [[UNIVERSE commodityMarket] goods];
    8417             :         NSUInteger                      i, j, commodityCount = [goods count];
    8418             :         OOCargoQuantity         quantityInHold[commodityCount];
    8419             : 
    8420             :         for (i = 0; i < commodityCount; i++)
    8421             :         {
    8422             :                 quantityInHold[i] = 0;
    8423             :         }
    8424             :         for (i = 0; i < [cargo count]; i++)
    8425             :         {
    8426             :                 ShipEntity *container = [cargo objectAtIndex:i];
    8427             :                 j = [goods indexOfObject:[container commodityType]];
    8428             :                 quantityInHold[j] += [container commodityAmount];
    8429             :         }
    8430             :         
    8431             :         for (i = 0; i < commodityCount; i++)
    8432             :         {
    8433             :                 if (quantityInHold[i] > 0)
    8434             :                 {
    8435             :                         NSMutableDictionary     *commodity = [NSMutableDictionary dictionaryWithCapacity:4];
    8436             :                         good = [goods objectAtIndex:i];
    8437             :                         // commodity, quantity - keep consistency between .manifest and .contracts
    8438             :                         [commodity setObject:good forKey:@"commodity"];
    8439             :                         [commodity setObject:[NSNumber numberWithUnsignedInt:quantityInHold[i]] forKey:@"quantity"];
    8440             :                         [commodity setObject:[[UNIVERSE commodityMarket] nameForGood:good] forKey:@"displayName"]; 
    8441             :                         [commodity setObject:DisplayStringForMassUnitForCommodity(good) forKey:@"unit"]; 
    8442             :                         [list addObject:commodity];
    8443             :                 }
    8444             :         }
    8445             : 
    8446             :         return [[list copy] autorelease];       // return an immutable copy
    8447             : }
    8448             : 
    8449             : - (void) setCargo:(NSArray *) some_cargo
    8450             : {
    8451             :         [cargo removeAllObjects];
    8452             :         [cargo addObjectsFromArray:some_cargo];
    8453             : }
    8454             : 
    8455             : 
    8456             : - (BOOL) addCargo:(NSArray *) some_cargo
    8457             : {
    8458             :         if ([cargo count] + [some_cargo count] > [self maxAvailableCargoSpace])
    8459             :         {
    8460             :                 return NO;
    8461             :         }
    8462             :         else
    8463             :         {
    8464             :                 [cargo addObjectsFromArray:some_cargo];
    8465             :                 return YES;
    8466             :         }
    8467             : }
    8468             : 
    8469             : 
    8470             : - (BOOL) removeCargo:(OOCommodityType)commodity amount:(OOCargoQuantity) amount
    8471             : {
    8472             :         OOCargoQuantity found = 0;
    8473             :         ShipEntity *pod = nil;
    8474             :         foreach (pod, cargo)
    8475             :         {
    8476             :                 if ([[pod commodityType] isEqualToString:commodity])
    8477             :                 {
    8478             :                         found++;
    8479             :                 }
    8480             :         }
    8481             :         if (found < amount)
    8482             :         {
    8483             :                 // don't remove any if there aren't enough to remove the full amount
    8484             :                 return NO;
    8485             :         }
    8486             :         
    8487             :         NSUInteger i = [cargo count] - 1;
    8488             :         // iterate downwards to be safe removing during iteration
    8489             :         while (amount > 0)
    8490             :         {
    8491             :                 if ([[[cargo objectAtIndex:i] commodityType] isEqualToString:commodity])
    8492             :                 {
    8493             :                         amount--;
    8494             :                         [cargo removeObjectAtIndex:i];
    8495             :                 }
    8496             :                 // check above means array index can't underflow here
    8497             :                 i--;
    8498             :         }
    8499             : 
    8500             :         return YES;
    8501             : }
    8502             : 
    8503             : 
    8504             : 
    8505             : - (BOOL) showScoopMessage
    8506             : {
    8507             :         return hasScoopMessage;
    8508             : }
    8509             : 
    8510             : 
    8511             : - (OOCargoFlag) cargoFlag
    8512             : {
    8513             :         return cargo_flag;
    8514             : }
    8515             : 
    8516             : 
    8517             : - (void) setCargoFlag:(OOCargoFlag) flag
    8518             : {
    8519             :         if (cargo_flag != flag)
    8520             :         {
    8521             :                 cargo_flag = flag;
    8522             :                 NSArray *newCargo = nil;
    8523             :                 unsigned num = 0;
    8524             :                 if (likely_cargo > 0)
    8525             :                 {
    8526             :                         num = likely_cargo * (0.5+randf());
    8527             :                         if (num > [self maxAvailableCargoSpace])
    8528             :                         {
    8529             :                                 num = [self maxAvailableCargoSpace];
    8530             :                         }
    8531             :                 }
    8532             :                 else
    8533             :                 {
    8534             :                         num = [self maxAvailableCargoSpace];
    8535             :                 }
    8536             :                 if (num > 200)
    8537             :                 {
    8538             :                         num = 200; 
    8539             :                         /* no core NPC ship carries this much when generated (the
    8540             :                          * Anaconda could, but doesn't): let's not waste time generating
    8541             :                          * thousands of pods - even if they are semi-virtual - for some
    8542             :                          * massive OXP ship */
    8543             :                 }
    8544             :                 if (num > 0)
    8545             :                 {
    8546             :                         switch (cargo_flag)
    8547             :                         {
    8548             :                         case CARGO_FLAG_FULL_UNIFORM:
    8549             :                                 newCargo = [UNIVERSE getContainersOfCommodity:[shipinfoDictionary oo_stringForKey:@"cargo_carried"] :num];
    8550             :                                 break;
    8551             :                         case CARGO_FLAG_FULL_PLENTIFUL:
    8552             :                                 newCargo = [UNIVERSE getContainersOfGoods:num scarce:NO legal:YES];
    8553             :                                 break;
    8554             :                         case CARGO_FLAG_FULL_SCARCE:
    8555             :                                 newCargo = [UNIVERSE getContainersOfGoods:num scarce:YES legal:YES];
    8556             :                                 break;
    8557             :                         case CARGO_FLAG_FULL_MEDICAL:
    8558             :                                 newCargo = [UNIVERSE getContainersOfCommodity:@"Narcotics" :num];
    8559             :                                 break;
    8560             :                         case CARGO_FLAG_FULL_CONTRABAND:
    8561             :                                 newCargo = [UNIVERSE getContainersOfGoods:num scarce:YES legal:NO];
    8562             :                                 break;
    8563             :                         case CARGO_FLAG_PIRATE:
    8564             :                                 newCargo = [UNIVERSE getContainersOfGoods:(Ranrot() % (1+num/2)) scarce:YES legal:NO];
    8565             :                                 break;
    8566             :                         case CARGO_FLAG_FULL_PASSENGERS:
    8567             :                                 // TODO: allow passengers to survive
    8568             :                         case CARGO_FLAG_NONE:
    8569             :                         default:
    8570             :                                 break;
    8571             :                         }
    8572             :                 }
    8573             :                 [self setCargo:newCargo];
    8574             :         }
    8575             : }
    8576             : 
    8577             : 
    8578             : - (void) setSpeed:(double) amount
    8579             : {
    8580             :         flightSpeed = amount;
    8581             : }
    8582             : 
    8583             : 
    8584             : - (void) setDesiredSpeed:(double) amount
    8585             : {
    8586             :         desired_speed = amount;
    8587             : }
    8588             : 
    8589             : 
    8590             : - (double) desiredSpeed
    8591             : {
    8592             :         return desired_speed;
    8593             : }
    8594             : 
    8595             : 
    8596             : - (double) desiredRange
    8597             : {
    8598             :         return desired_range;
    8599             : }
    8600             : 
    8601             : 
    8602             : - (void) setDesiredRange:(double) amount
    8603             : {
    8604             :         desired_range = amount;
    8605             : }
    8606             : 
    8607             : 
    8608             : - (double) cruiseSpeed
    8609             : {
    8610             :         return cruiseSpeed;
    8611             : }
    8612             : 
    8613             : 
    8614             : - (void) increase_flight_speed:(double) delta
    8615             : {
    8616             :         double factor = 1.0;
    8617             :         if (desired_speed > maxFlightSpeed && [self hasFuelInjection] && fuel > MIN_FUEL) factor = [self afterburnerFactor];
    8618             : 
    8619             :         if (flightSpeed < maxFlightSpeed * factor)
    8620             :                 flightSpeed += delta * factor;
    8621             :         else
    8622             :                 flightSpeed = maxFlightSpeed * factor;
    8623             : }
    8624             : 
    8625             : 
    8626             : - (void) decrease_flight_speed:(double) delta
    8627             : {
    8628             :         double factor = 1.0;
    8629             :         if (flightSpeed > maxFlightSpeed) 
    8630             :         {
    8631             :                 factor = MIN_HYPERSPEED_FACTOR;
    8632             :         }
    8633             : 
    8634             :         if (flightSpeed > factor * delta)
    8635             :         {
    8636             :                 flightSpeed -= factor * delta;
    8637             :         }
    8638             :         else
    8639             :         {
    8640             :                 flightSpeed = 0;
    8641             :         }
    8642             : }
    8643             : 
    8644             : 
    8645             : - (void) increase_flight_roll:(double) delta
    8646             : {
    8647             :         flightRoll += delta;
    8648             :         if (flightRoll > max_flight_roll)
    8649             :                 flightRoll = max_flight_roll;
    8650             :         else if (flightRoll < -max_flight_roll)
    8651             :                 flightRoll = -max_flight_roll;
    8652             : }
    8653             : 
    8654             : 
    8655             : - (void) decrease_flight_roll:(double) delta
    8656             : {
    8657             :         flightRoll -= delta;
    8658             :         if (flightRoll > max_flight_roll)
    8659             :                 flightRoll = max_flight_roll;
    8660             :         else if (flightRoll < -max_flight_roll)
    8661             :                 flightRoll = -max_flight_roll;
    8662             : }
    8663             : 
    8664             : 
    8665             : - (void) increase_flight_pitch:(double) delta
    8666             : {
    8667             :         flightPitch += delta;
    8668             :         if (flightPitch > max_flight_pitch)
    8669             :                 flightPitch = max_flight_pitch;
    8670             :         else if (flightPitch < -max_flight_pitch)
    8671             :                 flightPitch = -max_flight_pitch;
    8672             : }
    8673             : 
    8674             : 
    8675             : - (void) decrease_flight_pitch:(double) delta
    8676             : {
    8677             :         flightPitch -= delta;
    8678             :         if (flightPitch > max_flight_pitch)
    8679             :                 flightPitch = max_flight_pitch;
    8680             :         else if (flightPitch < -max_flight_pitch)
    8681             :                 flightPitch = -max_flight_pitch;
    8682             : }
    8683             : 
    8684             : 
    8685             : - (void) increase_flight_yaw:(double) delta
    8686             : {
    8687             :         flightYaw += delta;
    8688             :         if (flightYaw > max_flight_yaw)
    8689             :                 flightYaw = max_flight_yaw;
    8690             :         else if (flightYaw < -max_flight_yaw)
    8691             :                 flightYaw = -max_flight_yaw;
    8692             : }
    8693             : 
    8694             : 
    8695             : - (void) decrease_flight_yaw:(double) delta
    8696             : {
    8697             :         flightYaw -= delta;
    8698             :         if (flightYaw > max_flight_yaw)
    8699             :                 flightYaw = max_flight_yaw;
    8700             :         else if (flightYaw < -max_flight_yaw)
    8701             :                 flightYaw = -max_flight_yaw;
    8702             : }
    8703             : 
    8704             : 
    8705             : - (GLfloat) flightRoll
    8706             : {
    8707             :         return flightRoll;
    8708             : }
    8709             : 
    8710             : 
    8711             : - (GLfloat) flightPitch
    8712             : {
    8713             :         return flightPitch;
    8714             : }
    8715             : 
    8716             : 
    8717             : - (GLfloat) flightYaw
    8718             : {
    8719             :         return flightYaw;
    8720             : }
    8721             : 
    8722             : 
    8723             : - (GLfloat) flightSpeed
    8724             : {
    8725             :         return flightSpeed;
    8726             : }
    8727             : 
    8728             : 
    8729             : - (GLfloat) maxFlightPitch
    8730             : {
    8731             :         return max_flight_pitch;
    8732             : }
    8733             : 
    8734             : 
    8735             : - (GLfloat) maxFlightSpeed
    8736             : {
    8737             :         return maxFlightSpeed;
    8738             : }
    8739             : 
    8740             : 
    8741             : - (GLfloat) maxFlightRoll
    8742             : {
    8743             :         return max_flight_roll;
    8744             : }
    8745             : 
    8746             : 
    8747             : - (GLfloat) maxFlightYaw
    8748             : {
    8749             :         return max_flight_yaw;
    8750             : }
    8751             : 
    8752             : 
    8753             : - (void) setMaxFlightPitch:(GLfloat)new
    8754             : {
    8755             :         max_flight_pitch = new;
    8756             : }
    8757             : 
    8758             : 
    8759             : - (void) setMaxFlightSpeed:(GLfloat)new
    8760             : {
    8761             :         maxFlightSpeed = new;
    8762             : }
    8763             : 
    8764             : 
    8765             : - (void) setMaxFlightRoll:(GLfloat)new
    8766             : {
    8767             :         max_flight_roll = new;
    8768             : }
    8769             : 
    8770             : 
    8771             : - (void) setMaxFlightYaw:(GLfloat)new
    8772             : {
    8773             :         max_flight_yaw = new;
    8774             : }
    8775             : 
    8776             : 
    8777             : - (GLfloat) speedFactor
    8778             : {
    8779             :         if (maxFlightSpeed <= 0.0)  return 0.0;
    8780             :         return flightSpeed / maxFlightSpeed;
    8781             : }
    8782             : 
    8783             : 
    8784             : - (GLfloat) temperature
    8785             : {
    8786             :         return ship_temperature;
    8787             : }
    8788             : 
    8789             : 
    8790             : - (void) setTemperature:(GLfloat) value
    8791             : {
    8792             :         ship_temperature = value;
    8793             : }
    8794             : 
    8795             : 
    8796             : - (float) randomEjectaTemperature
    8797             : {
    8798             :         return [self randomEjectaTemperatureWithMaxFactor:0.99f];
    8799             : }
    8800             : 
    8801             : 
    8802             : - (float) randomEjectaTemperatureWithMaxFactor:(float)factor
    8803             : {
    8804             :         const float kRange = 0.02f;
    8805             :         factor -= kRange;
    8806             :         
    8807             :         float parentTemp = [self temperature];
    8808             :         float adjusted = parentTemp * (bellf(5) * (kRange * 2.0f) - kRange + factor);
    8809             :         if (adjusted > SHIP_MAX_CABIN_TEMP)
    8810             :         {
    8811             :                 adjusted = SHIP_MAX_CABIN_TEMP;
    8812             :         }
    8813             :         
    8814             :         // Interpolate so that result == parentTemp when parentTemp is SHIP_MIN_CABIN_TEMP
    8815             :         float interp = OOClamp_0_1_f((parentTemp - SHIP_MIN_CABIN_TEMP) / (SHIP_MAX_CABIN_TEMP - SHIP_MIN_CABIN_TEMP));
    8816             :         
    8817             :         return OOLerp(SHIP_MIN_CABIN_TEMP, adjusted, interp);
    8818             : }
    8819             : 
    8820             : 
    8821             : - (GLfloat) heatInsulation
    8822             : {
    8823             :         return _heatInsulation;
    8824             : }
    8825             : 
    8826             : 
    8827             : - (void) setHeatInsulation:(GLfloat) value
    8828             : {
    8829             :         _heatInsulation = value;
    8830             : }
    8831             : 
    8832             : 
    8833             : - (int) damage
    8834             : {
    8835             :         return (int)(100 - (100 * energy / maxEnergy));
    8836             : }
    8837             : 
    8838             : 
    8839             : - (void) dealEnergyDamage:(GLfloat) baseDamage atRange:(GLfloat) range withBias:(GLfloat) velocityBias
    8840             : {
    8841             :         // this is limited to the player's scanner range
    8842             :         GLfloat maxRange = fmin(range * sqrt(baseDamage), SCANNER_MAX_RANGE);
    8843             :         
    8844             :         OOLog(@"missile.damage.calc", @"Range: %f | Damage: %f | MaxRange: %f",range,baseDamage,maxRange);
    8845             : 
    8846             :         NSArray *targets = [UNIVERSE entitiesWithinRange:maxRange ofEntity:self];
    8847             :         if ([targets count] > 0)
    8848             :         {
    8849             :                 unsigned i;
    8850             :                 for (i = 0; i < [targets count]; i++)
    8851             :                 {
    8852             :                         Entity *e2 = [targets objectAtIndex:i];
    8853             :                         Vector p2 = [self vectorTo:e2];
    8854             :                         double ecr = [e2 collisionRadius];
    8855             :                         double d = (magnitude(p2) - ecr) / range;
    8856             :                         // base damage within defined range, inverse-square falloff outside
    8857             :                         double localDamage = baseDamage;
    8858             :                         OOLog(@"missile.damage.calc", @"Base damage: %f",baseDamage);
    8859             :                         if (velocityBias > 0)
    8860             :                         {
    8861             :                                 Vector v2 = vector_subtract([self velocity], [e2 velocity]);
    8862             :                                 double vSign = dot_product(vector_normal([self velocity]), vector_normal(p2));
    8863             :                                 // vSign should always be positive for the missile's actual target
    8864             :         // but might be negative for other nearby ships which are
    8865             :         // actually moving further away from the missile
    8866             : //                              double vMag = vSign > 0.0 ? magnitude(v2) : -magnitude(v2);
    8867             :                                 double vMag = vSign * magnitude(v2);
    8868             :                                 if (vMag > 1000.0) {
    8869             :                                         vMag = 1000.0; 
    8870             : // cap effective closing speed to 1.0LM or injector-collisions can still do
    8871             : // ridiculous damage
    8872             :                                 }
    8873             : 
    8874             :                                 localDamage += vMag * velocityBias;
    8875             :                                 OOLog(@"missile.damage.calc",@"Velocity magnitude + sign: %f , %f",magnitude(v2),vSign);
    8876             :                                 OOLog(@"missile.damage.calc",@"Velocity magnitude factor: %f",vMag);
    8877             :                                 OOLog(@"missile.damage.calc",@"Velocity corrected damage: %f",localDamage);
    8878             :                         }
    8879             :                         double damage = (d > 1) ? localDamage / (d * d) : localDamage;
    8880             :                         OOLog(@"missile.damage.calc",@"%f at range %f (d=%f)",damage,magnitude(p2)-ecr,d);
    8881             :                         if (damage > 0.0)
    8882             :                         {
    8883             :                                 if ([self owner])
    8884             :                                 {
    8885             :                                         [e2 takeEnergyDamage:damage from:self becauseOf:[self owner] weaponIdentifier:[self primaryRole]];
    8886             :                                 } 
    8887             :                                 else
    8888             :                                 {
    8889             :                                         [e2 takeEnergyDamage:damage from:self becauseOf:self weaponIdentifier:[self primaryRole]];
    8890             :                                 }
    8891             :                         }
    8892             :                 }
    8893             :         }
    8894             :         
    8895             :         /* the actual damage can't go more than S_M_R, so cap the range
    8896             :          * for exploding purposes so that the visual appearance isn't
    8897             :          * larger than that */
    8898             :         if (range > SCANNER_MAX_RANGE / 4.0)
    8899             :         {
    8900             :                 range = SCANNER_MAX_RANGE / 4.0;
    8901             :         }
    8902             :         // and a visual sign of the explosion
    8903             :         // "fireball" explosion effect
    8904             :         NSDictionary *explosion = [UNIVERSE explosionSetting:@"oolite-default-ship-explosion"];
    8905             :         [UNIVERSE addEntity:[OOExplosionCloudEntity explosionCloudFromEntity:self withSize:range*3.0 andSettings:explosion]];
    8906             : 
    8907             : }
    8908             : 
    8909             : 
    8910             : // dealEnergyDamage preferred
    8911             : // Exposed to AI
    8912             : - (void) dealEnergyDamageWithinDesiredRange
    8913             : {
    8914             :         OOStandardsDeprecated([NSString stringWithFormat:@"dealEnergyDamageWithinDesiredRange is deprecated for %@",self]);
    8915             :         // not over scannerRange
    8916             :         NSArray* targets = [UNIVERSE entitiesWithinRange:(desired_range < SCANNER_MAX_RANGE ? desired_range : SCANNER_MAX_RANGE) ofEntity:self];
    8917             :         if ([targets count] > 0)
    8918             :         {
    8919             :                 unsigned i;
    8920             :                 for (i = 0; i < [targets count]; i++)
    8921             :                 {
    8922             :                         Entity *e2 = [targets objectAtIndex:i];
    8923             :                         Vector p2 = [self vectorTo:e2];
    8924             :                         double ecr = [e2 collisionRadius];
    8925             :                         double d = (magnitude(p2) - ecr) * 2.6; // 2.6 is a correction constant to stay in limits of the old code.
    8926             :                         double damage = (d > 0) ? weapon_damage * desired_range / (d * d) : weapon_damage;
    8927             :                         [e2 takeEnergyDamage:damage from:self becauseOf:[self owner] weaponIdentifier:[self primaryRole]];
    8928             :                 }
    8929             :         }
    8930             : }
    8931             : 
    8932             : 
    8933             : - (void) dealMomentumWithinDesiredRange:(double)amount
    8934             : {
    8935             :         NSArray* targets = [UNIVERSE entitiesWithinRange:desired_range ofEntity:self];
    8936             :         if ([targets count] > 0)
    8937             :         {
    8938             :                 unsigned i;
    8939             :                 for (i = 0; i < [targets count]; i++)
    8940             :                 {
    8941             :                         ShipEntity *e2 = (ShipEntity*)[targets objectAtIndex:i];
    8942             :                         if ([e2 isShip] && [e2 isInSpace])
    8943             :                         {
    8944             :                                 Vector p2 = [self vectorTo:e2];
    8945             :                                 double ecr = [e2 collisionRadius];
    8946             :                                 double d2 = magnitude2(p2) - ecr * ecr;
    8947             :                                 // limit momentum transfer to relatively sensible levels
    8948             :                                 if (d2 < 0.1)
    8949             :                                 {
    8950             :                                         d2 = 0.1;
    8951             :                                 }
    8952             :                                 double moment = amount*desired_range/d2;
    8953             :                                 [e2 addImpactMoment:vector_normal(p2) fraction:moment];
    8954             :                         }
    8955             :                 }
    8956             :         }
    8957             : }
    8958             : 
    8959             : 
    8960             : - (BOOL) isHulk
    8961             : {
    8962             :         return isHulk;
    8963             : }
    8964             : 
    8965             : 
    8966             : - (void) setHulk:(BOOL)isNowHulk
    8967             : {
    8968             :         if (![self isSubEntity]) 
    8969             :         {
    8970             :                 isHulk = isNowHulk;
    8971             :         }
    8972             : }
    8973             : 
    8974             : 
    8975             : - (void) noteTakingDamage:(double)amount from:(Entity *)entity type:(OOShipDamageType)type
    8976             : {
    8977             :         if (amount < 0 || (amount == 0 && [[UNIVERSE gameController] isGamePaused]))  return;
    8978             :         
    8979             :         JSContext *context = OOJSAcquireContext();
    8980             :         
    8981             :         jsval amountVal = JSVAL_VOID;
    8982             :         JS_NewNumberValue(context, amount, &amountVal);
    8983             :         jsval entityVal = OOJSValueFromNativeObject(context, entity);
    8984             :         jsval typeVal = OOJSValueFromShipDamageType(context, type);
    8985             :         
    8986             :         ShipScriptEvent(context, self, "shipTakingDamage", amountVal, entityVal, typeVal);
    8987             :         OOJSRelinquishContext(context);
    8988             :         
    8989             :         if ([entity isShip]) {
    8990             : //              ShipEntity* attacker = (ShipEntity *)entity;
    8991             :                 if ([self hasHostileTarget] && accuracy >= COMBAT_AI_IS_SMART && (randf()*10.0 < accuracy || desired_speed < 0.5 * maxFlightSpeed) && behaviour != BEHAVIOUR_EVASIVE_ACTION && behaviour != BEHAVIOUR_FLEE_EVASIVE_ACTION && behaviour != BEHAVIOUR_SCRIPTED_ATTACK_AI)
    8992             :                 {
    8993             :                         if (behaviour == BEHAVIOUR_FLEE_TARGET)
    8994             :                         {
    8995             : // jink should be sufficient to avoid being hit most of the time
    8996             : // if not, this will make a sharp turn and then select a new jink position
    8997             :                                 behaviour = BEHAVIOUR_FLEE_EVASIVE_ACTION;
    8998             :                         }
    8999             :                         else
    9000             :                         {
    9001             :                                 behaviour = BEHAVIOUR_EVASIVE_ACTION;
    9002             :                         }
    9003             :                         frustration = 0.0;
    9004             :                 }
    9005             :         }
    9006             : 
    9007             : }
    9008             : 
    9009             : 
    9010             : - (void) noteKilledBy:(Entity *)whom damageType:(OOShipDamageType)type
    9011             : {
    9012             :         if ([self status] == STATUS_DEAD)  return;
    9013             :         
    9014             :         [PLAYER setScriptTarget:self];
    9015             :         
    9016             :         JSContext *context = OOJSAcquireContext();
    9017             :         
    9018             :         jsval whomVal = OOJSValueFromNativeObject(context, whom);
    9019             :         jsval typeVal = OOJSValueFromShipDamageType(context, type);
    9020             :         OOEntityStatus originalStatus = [self status];
    9021             :         [self setStatus:STATUS_DEAD];
    9022             :         
    9023             :         ShipScriptEvent(context, self, "shipDied", whomVal, typeVal);
    9024             :         if ([whom isShip])
    9025             :         {
    9026             :                 jsval selfVal = OOJSValueFromNativeObject(context, self);
    9027             :                 ShipScriptEvent(context, (ShipEntity *)whom, "shipKilledOther", selfVal, typeVal);
    9028             :         }
    9029             :         
    9030             :         [self setStatus:originalStatus];
    9031             :         OOJSRelinquishContext(context);
    9032             : }
    9033             : 
    9034             : 
    9035             : - (void) getDestroyedBy:(Entity *)whom damageType:(OOShipDamageType)type
    9036             : {
    9037             :         [self noteKilledBy:whom damageType:type];
    9038             :         [self abortDocking];
    9039             :         [self becomeExplosion];
    9040             : }
    9041             : 
    9042             : 
    9043           0 : - (void) rescaleBy:(GLfloat)factor
    9044             : {
    9045             :         [self rescaleBy:factor writeToCache:YES];
    9046             : }
    9047             : 
    9048             : 
    9049           0 : - (void) rescaleBy:(GLfloat)factor writeToCache:(BOOL)writeToCache
    9050             : {
    9051             :         _scaleFactor *= factor;
    9052             :         OOMesh *mesh = nil;
    9053             : 
    9054             :         NSDictionary *shipDict = [self shipInfoDictionary];
    9055             :         NSString *modelName = [shipDict oo_stringForKey:@"model"];
    9056             :         if (modelName != nil)
    9057             :         {
    9058             :                 mesh = [OOMesh meshWithName:modelName
    9059             :                                                    cacheKey:[NSString stringWithFormat:@"%@-%.3f",_shipKey,_scaleFactor]
    9060             :                                  materialDictionary:[shipDict oo_dictionaryForKey:@"materials"]
    9061             :                                   shadersDictionary:[shipDict oo_dictionaryForKey:@"shaders"]
    9062             :                                                          smooth:[shipDict oo_boolForKey:@"smooth" defaultValue:NO]
    9063             :                                            shaderMacros:OODefaultShipShaderMacros()
    9064             :                                            shaderBindingTarget:self
    9065             :                                                 scaleFactor:factor
    9066             :                                          cacheWriteable:writeToCache];
    9067             : 
    9068             :                 if (mesh == nil)  return;
    9069             :                 [self setMesh:mesh];
    9070             :         }
    9071             : 
    9072             :         // rescale subentities
    9073             :         Entity<OOSubEntity>       *se = nil;
    9074             :         foreach (se, [self subEntities])
    9075             :         {
    9076             :                 [se setPosition:HPvector_multiply_scalar([se position], factor)];
    9077             :                 [se rescaleBy:factor writeToCache:writeToCache];
    9078             :         }
    9079             :         
    9080             :         // rescale mass
    9081             :         mass *= factor * factor * factor;
    9082             : }
    9083             : 
    9084             : 
    9085           0 : - (void) releaseCargoPodsDebris
    9086             : {
    9087             :         HPVector xposition = position;
    9088             :         NSUInteger i;
    9089             :         Vector v;
    9090             :         Quaternion q;
    9091             :         int speed_low = 200;
    9092             : 
    9093             :         NSArray *jetsam = nil;  // this will contain the stuff to get thrown out
    9094             :         unsigned cargo_chance = 70;
    9095             :         jetsam = [NSArray arrayWithArray:cargo];   // what the ship is carrying
    9096             :         [cargo removeAllObjects];   // dispense with it!
    9097             :         unsigned limit = 15;
    9098             :         //  Throw out cargo
    9099             :         NSUInteger n_jetsam = [jetsam count];
    9100             :                                         
    9101             :         for (i = 0; i < n_jetsam; i++)
    9102             :         {
    9103             :                 if (Ranrot() % 100 < cargo_chance)  //  chance of any given piece of cargo surviving decompression
    9104             :                 {
    9105             :                         // a higher chance of getting at least a couple of bits of cargo out
    9106             :                         if (cargo_chance > 10)
    9107             :                         {
    9108             :                                 if (EXPECT_NOT([self isPlayer]))
    9109             :                                 {
    9110             :                                         cargo_chance -= 20;
    9111             :                                 }
    9112             :                                 else
    9113             :                                 {
    9114             :                                         cargo_chance -= 30;
    9115             :                                 }
    9116             :                         }
    9117             :                         limit--;
    9118             :                         ShipEntity* cargoObj = [jetsam objectAtIndex:i];
    9119             :                         ShipEntity* container = [UNIVERSE reifyCargoPod:cargoObj];
    9120             :                         /* TODO: this debris position/velocity setting code is
    9121             :                          * duplicated - sometimes not very cleanly - all over the
    9122             :                          * place. Unify to a single function - CIM */
    9123             :                         HPVector  rpos = xposition;
    9124             :                         Vector  rrand = OORandomPositionInBoundingBox(boundingBox);
    9125             :                         rpos.x += rrand.x;      rpos.y += rrand.y;      rpos.z += rrand.z;
    9126             :                         rpos.x += (ranrot_rand() % 7) - 3;
    9127             :                         rpos.y += (ranrot_rand() % 7) - 3;
    9128             :                         rpos.z += (ranrot_rand() % 7) - 3;
    9129             :                         [container setPosition:rpos];
    9130             :                         v.x = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
    9131             :                         v.y = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
    9132             :                         v.z = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
    9133             :                         [container setVelocity:vector_add(v,[self velocity])];
    9134             :                         quaternion_set_random(&q);
    9135             :                         [container setOrientation:q];
    9136             :                                                         
    9137             :                         [container setTemperature:[self randomEjectaTemperature]];
    9138             :                         [container setScanClass: CLASS_CARGO];
    9139             :                         [UNIVERSE addEntity:container]; // STATUS_IN_FLIGHT, AI state GLOBAL
    9140             : 
    9141             :                         AI *containerAI = [container getAI];
    9142             :                         if ([containerAI hasSuspendedStateMachines]) // check if new or recycled cargo.
    9143             :                         {
    9144             :                                 [containerAI exitStateMachineWithMessage:nil];
    9145             :                                 [container setThrust:[container maxThrust]]; // restore old value. Was set to zero on previous scooping.
    9146             :                                 [container setOwner:container];
    9147             :                         }
    9148             :                 }
    9149             :                 if (limit <= 0)
    9150             :                 {
    9151             :                         break; // even really big ships won't have too much cargo survive an explosion
    9152             :                 }
    9153             :         }
    9154             : 
    9155             : }
    9156             : 
    9157             : 
    9158             : - (void) setIsWreckage:(BOOL)isw
    9159             : {
    9160             :         isWreckage = isw;
    9161             : }
    9162             : 
    9163             : 
    9164             : - (BOOL) showDamage
    9165             : {
    9166             :         return _showDamage;
    9167             : }
    9168             : 
    9169             : 
    9170             : - (void) becomeExplosion
    9171             : {
    9172             :         
    9173             :         // check if we're destroying a subentity
    9174             :         ShipEntity *parent = [self parentEntity];
    9175             :         if (parent != nil)
    9176             :         {
    9177             :                 ShipEntity *this_ship = [self retain];
    9178             :                 HPVector this_pos = [self absolutePositionForSubentity];
    9179             :                 
    9180             :                 // remove this ship from its parent's subentity list
    9181             :                 [parent subEntityDied:self];
    9182             :                 [UNIVERSE addEntity:this_ship];
    9183             :                 [this_ship setPosition:this_pos];
    9184             :                 [this_ship release];
    9185             :                 if ([parent isPlayer])
    9186             :                 {
    9187             :                         // make the parent ship less reliable.
    9188             :                         [(PlayerEntity *)parent adjustTradeInFactorBy:-PLAYER_SHIP_SUBENTITY_TRADE_IN_VALUE];
    9189             :                 }
    9190             :         }
    9191             :         
    9192             :         HPVector xposition = position;
    9193             :         NSUInteger i;
    9194             :         Vector v;
    9195             :         Quaternion q;
    9196             :         int speed_low = 200;
    9197             :         GLfloat n_alloys = sqrtf(sqrtf(mass / 6000.0f));
    9198             :         NSUInteger numAlloys = 0;
    9199             :         BOOL canReleaseSubWreckage = isWreckage && ([UNIVERSE detailLevel] >= DETAIL_LEVEL_EXTRAS);
    9200             : 
    9201             :         if ([self status] == STATUS_DEAD)
    9202             :         {
    9203             :                 [UNIVERSE removeEntity:self];
    9204             :                 return;
    9205             :         }
    9206             :         [self setStatus:STATUS_DEAD];
    9207             :         
    9208             :         @try
    9209             :         {
    9210             :                 if ([self isThargoid] && [roleSet hasRole:@"thargoid-mothership"])  [self broadcastThargoidDestroyed];
    9211             :                 
    9212             :                 if (!suppressExplosion && ([self isVisible] || HPdistance2([self position], [PLAYER position]) < SCANNER_MAX_RANGE2))
    9213             :                 {
    9214             :                         if (!isWreckage && mass > 500000.0f && randf() < 0.25f) // big!
    9215             :                         {
    9216             :                                 // draw an expanding ring
    9217             :                                 OORingEffectEntity *ring = [OORingEffectEntity ringFromEntity:self];
    9218             :                                 [ring setVelocity:vector_multiply_scalar([self velocity], 0.25f)];
    9219             :                                 [UNIVERSE addEntity:ring];
    9220             :                         }
    9221             :                         
    9222             :                         BOOL add_debris = (UNIVERSE->n_entities < 0.95 * UNIVERSE_MAX_ENTITIES) &&
    9223             :                                                                           ([UNIVERSE getTimeDelta] < 0.125);   // FPS > 8
    9224             :                         
    9225             :                         
    9226             :                         // There are several parts to explosions, show only the main
    9227             :                         // explosion effect if UNIVERSE is almost full.
    9228             :                         
    9229             :                         if (add_debris)
    9230             :                         {
    9231             :                                 if ([UNIVERSE reducedDetail])
    9232             :                                 {
    9233             :                                         // Quick explosion effects for reduced detail mode
    9234             :                                         
    9235             :                                         // 1. fast sparks
    9236             :                                         [UNIVERSE addEntity:[OOSmallFragmentBurstEntity fragmentBurstFromEntity:self]];
    9237             :                                         // 2. slow clouds
    9238             :                                         [UNIVERSE addEntity:[OOBigFragmentBurstEntity fragmentBurstFromEntity:self]];
    9239             :                                         // 3. flash
    9240             :                                         [UNIVERSE addEntity:[OOFlashEffectEntity explosionFlashFromEntity:self]];
    9241             :                                         /* This mode used to be the default for
    9242             :                                          * cargo/munitions but this now must be explicitly
    9243             :                                          * specified. */
    9244             :                                 }
    9245             :                                 else
    9246             :                                 {
    9247             :                                         NSString *explosionKey = @"oolite-default-ship-explosion";
    9248             :                                         NSDictionary *explosion = nil;
    9249             :                                         if (explosionType == nil)
    9250             :                                         {
    9251             :                                                 explosion = [UNIVERSE explosionSetting:explosionKey];
    9252             :                                                 [UNIVERSE addEntity:[OOExplosionCloudEntity explosionCloudFromEntity:self withSettings:explosion]];
    9253             :                                                 // 3. flash
    9254             :                                                 [UNIVERSE addEntity:[OOFlashEffectEntity explosionFlashFromEntity:self]];
    9255             :                                         }
    9256             :                                         for (NSUInteger i=0;i<[explosionType count];i++)
    9257             :                                         {
    9258             :                                                 explosionKey = [explosionType oo_stringAtIndex:i defaultValue:nil];
    9259             :                                                 if (explosionKey != nil)
    9260             :                                                 {
    9261             :                                                         // three special-case builtins
    9262             :                                                         if ([explosionKey isEqualToString:@"oolite-builtin-flash"])
    9263             :                                                         {
    9264             :                                                                 [UNIVERSE addEntity:[OOFlashEffectEntity explosionFlashFromEntity:self]];
    9265             :                                                         }
    9266             :                                                         else if ([explosionKey isEqualToString:@"oolite-builtin-slowcloud"])
    9267             :                                                         {
    9268             :                                                                 [UNIVERSE addEntity:[OOBigFragmentBurstEntity fragmentBurstFromEntity:self]];
    9269             :                                                         }
    9270             :                                                         else if ([explosionKey isEqualToString:@"oolite-builtin-fastspark"])
    9271             :                                                         {
    9272             :                                                                 [UNIVERSE addEntity:[OOSmallFragmentBurstEntity fragmentBurstFromEntity:self]];
    9273             :                                                         }
    9274             :                                                         else
    9275             :                                                         {
    9276             :                                                                 explosion = [UNIVERSE explosionSetting:explosionKey];
    9277             :                                                                 [UNIVERSE addEntity:[OOExplosionCloudEntity explosionCloudFromEntity:self withSettings:explosion]];
    9278             :                                                         }
    9279             :                                                 }
    9280             :                                         }
    9281             :                                         // "fireball" explosion effect
    9282             : 
    9283             :                                 }
    9284             :                         }
    9285             :                          
    9286             :                         // If UNIVERSE is nearing limit for entities don't add to it!
    9287             :                         if (add_debris)
    9288             :                         {
    9289             :                                 // we need to throw out cargo at this point.
    9290             :                                 [self releaseCargoPodsDebris];
    9291             :                                 
    9292             :                                 //  Throw out rocks and alloys to be scooped up
    9293             :                                 if ([self hasRole:@"asteroid"] || [self isBoulder])
    9294             :                                 {
    9295             :                                         if (!noRocks && (being_mined || randf() < 0.20))
    9296             :                                         {
    9297             :                                                 NSString *defaultRole = @"boulder";
    9298             :                                                 float defaultSpeed = 50.0;
    9299             :                                                 if ([self isBoulder])
    9300             :                                                 {
    9301             :                                                         defaultRole = @"splinter";
    9302             :                                                         defaultSpeed = 20.0;
    9303             :                                                         if (likely_cargo == 0)
    9304             :                                                         {
    9305             :                                                                 likely_cargo = 4; // compatibility with older boulders
    9306             :                                                         }
    9307             :                                                 }
    9308             :                                                 else if ([[self primaryAggressor] isPlayer])
    9309             :                                                 {
    9310             :                                                         [PLAYER addRoleForMining];
    9311             :                                                 }
    9312             :                                                 NSUInteger n_rocks = 2 + (Ranrot() % (likely_cargo + 1));
    9313             :                                                 
    9314             :                                                 NSString *debrisRole = [[self shipInfoDictionary] oo_stringForKey:@"debris_role" defaultValue:defaultRole];
    9315             :                                                 for (i = 0; i < n_rocks; i++)
    9316             :                                                 {
    9317             :                                                         ShipEntity* rock = [UNIVERSE newShipWithRole:debrisRole];   // retain count = 1
    9318             :                                                         if (rock)
    9319             :                                                         {
    9320             :                                                                 float  r_speed = [rock maxFlightSpeed] > 0 ? 2.0 * [rock maxFlightSpeed] : defaultSpeed;
    9321             :                                                                 float cr = (collision_radius < rock->collision_radius) ? collision_radius : 2 * rock->collision_radius;
    9322             :                                                                 v.x = ((randf() * r_speed) - r_speed / 2);
    9323             :                                                                 v.y = ((randf() * r_speed) - r_speed / 2);
    9324             :                                                                 v.z = ((randf() * r_speed) - r_speed / 2);
    9325             :                                                                 [rock setVelocity:vector_add(v,[self velocity])];
    9326             :                                                                 HPVector rpos = HPvector_add(xposition,vectorToHPVector(vector_multiply_scalar(vector_normal(v),cr)));
    9327             :                                                                 [rock setPosition:rpos];
    9328             : 
    9329             :                                                                 quaternion_set_random(&q);
    9330             :                                                                 [rock setOrientation:q];
    9331             :                                                                 
    9332             :                                                                 [rock setTemperature:[self randomEjectaTemperature]];
    9333             :                                                                 if ([self isBoulder])
    9334             :                                                                 {
    9335             :                                                                         [rock setScanClass: CLASS_CARGO];
    9336             :                                                                         [rock setBounty: 0 withReason:kOOLegalStatusReasonSetup];
    9337             :                                                                         // only make the rock have minerals if something isn't already defined for the rock
    9338             :                                                                         if ([[rock shipInfoDictionary] oo_stringForKey:@"cargo_carried"] == nil)
    9339             :                                                                                 [rock setCommodity:@"minerals" andAmount: 1];
    9340             :                                                                 }
    9341             :                                                                 else
    9342             :                                                                 {
    9343             :                                                                         [rock setScanClass:CLASS_ROCK];
    9344             :                                                                         [rock setIsBoulder:YES];
    9345             :                                                                 }
    9346             :                                                                 [UNIVERSE addEntity:rock];      // STATUS_IN_FLIGHT, AI state GLOBAL
    9347             :                                                                 [rock release];
    9348             :                                                         }
    9349             :                                                 }
    9350             :                                         }
    9351             :                                         return;
    9352             :                                 }
    9353             : 
    9354             :                                 // throw out burning chunks of wreckage
    9355             :                                 //
    9356             :                                 if ((n_alloys && canFragment) || canReleaseSubWreckage)
    9357             :                                 {
    9358             :                                         NSUInteger n_wreckage = 0;
    9359             :                                         
    9360             :                                         if (UNIVERSE->n_entities < 0.50 * UNIVERSE_MAX_ENTITIES)
    9361             :                                         {
    9362             :                                                 // Create wreckage only when UNIVERSE is less than half full.
    9363             :                                                 // (condition set in r906 - was < 0.75 before) --Kaks 2011.10.17
    9364             :                                                 NSUInteger maxWrecks = 3;
    9365             :                                                 if (n_alloys == 0)
    9366             :                                                 {
    9367             :                                                         // must be sub-wreckage here
    9368             :                                                         n_wreckage = (mass > 600.0 && randf() < 0.2)?2:0;
    9369             :                                                 }
    9370             :                                                 else
    9371             :                                                 {
    9372             :                                                         n_wreckage = (n_alloys < maxWrecks)? floorf(randf()*(n_alloys+2)) : maxWrecks;
    9373             :                                                 }
    9374             :                                         }
    9375             :                                         
    9376             :                                         for (i = 0; i < n_wreckage; i++)
    9377             :                                         {
    9378             :                                                 Vector r1 = [octree randomPoint];
    9379             :                                                 Vector dir = quaternion_rotate_vector([self normalOrientation], r1);
    9380             :                                                 HPVector rpos = HPvector_add(vectorToHPVector(dir), xposition);
    9381             :                                                 GLfloat lifetime = 750.0 * randf() + 250.0 * i + 100.0;
    9382             :                                                 ShipEntity *wreck = [UNIVERSE addWreckageFrom:self withRole:@"wreckage" at:rpos scale:1.0 lifetime:lifetime/2];
    9383             : 
    9384             :                                                 [wreck setVelocity:vector_add([wreck velocity],vector_multiply_scalar(vector_normal(dir),randf()*[wreck collisionRadius]))];
    9385             : 
    9386             :                                         }
    9387             :                                         n_alloys = randf() * n_alloys;
    9388             :                                 }
    9389             :                         } 
    9390             : 
    9391             :                         if (!canFragment)
    9392             :                         {
    9393             :                                 n_alloys = 0.0;
    9394             :                         }
    9395             :                         // If UNIVERSE is almost full, don't create more than 1 piece of scrap metal.
    9396             :                         else if (!add_debris)
    9397             :                         {
    9398             :                                 n_alloys = (n_alloys > 1.0) ? 1.0 : 0.0;
    9399             :                         }
    9400             : 
    9401             :                         // now convert to uint
    9402             :                         numAlloys = floorf(n_alloys);
    9403             : 
    9404             :                         // Throw out scrap metal
    9405             :                         //
    9406             :                         for (i = 0; i < numAlloys; i++)
    9407             :                         {
    9408             :                                 ShipEntity* plate = [UNIVERSE newShipWithRole:@"alloy"];   // retain count = 1
    9409             :                                 if (plate)
    9410             :                                 {
    9411             :                                         HPVector  rpos = xposition;
    9412             :                                         Vector  rrand = OORandomPositionInBoundingBox(boundingBox);
    9413             :                                         rpos.x += rrand.x;      rpos.y += rrand.y;      rpos.z += rrand.z;
    9414             :                                         rpos.x += (ranrot_rand() % 7) - 3;
    9415             :                                         rpos.y += (ranrot_rand() % 7) - 3;
    9416             :                                         rpos.z += (ranrot_rand() % 7) - 3;
    9417             :                                         [plate setPosition:rpos];
    9418             :                                         v.x = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
    9419             :                                         v.y = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
    9420             :                                         v.z = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
    9421             :                                         [plate setVelocity:vector_add(v,[self velocity])];
    9422             :                                         quaternion_set_random(&q);
    9423             :                                         [plate setOrientation:q];
    9424             :                                         
    9425             :                                         [plate setTemperature:[self randomEjectaTemperature]];
    9426             :                                         [plate setScanClass: CLASS_CARGO];
    9427             :                                         [plate setCommodity:@"alloys" andAmount:1];
    9428             :                                         [UNIVERSE addEntity:plate];     // STATUS_IN_FLIGHT, AI state GLOBAL
    9429             :                                         
    9430             :                                         [plate release];
    9431             :                                 }
    9432             :                         }
    9433             :                 }
    9434             :                 
    9435             :                 // Explode subentities.
    9436             :                 NSEnumerator    *subEnum = nil;
    9437             :                 ShipEntity              *se = nil;
    9438             :                 for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
    9439             :                 {
    9440             :                         [se setSuppressExplosion:suppressExplosion];
    9441             :                         [se becomeExplosion];
    9442             :                 }
    9443             :                 [self clearSubEntities];
    9444             : 
    9445             :                 // momentum from explosions
    9446             :                 if (!suppressExplosion)
    9447             :                 {
    9448             :                         desired_range = collision_radius * 2.5f;
    9449             :                         [self dealMomentumWithinDesiredRange:0.125f * mass];
    9450             :                 }
    9451             :                 
    9452             :                 if (self != PLAYER)     // was if !isPlayer - but I think this may cause ghosts (Who's "I"? -- Ahruman)
    9453             :                 {
    9454             :                         if (isPlayer)
    9455             :                         {
    9456             :         #ifndef NDEBUG
    9457             :                                 OOLog(@"becomeExplosion.suspectedGhost.confirm", @"%@", @"Ship spotted with isPlayer set when not actually the player.");
    9458             :         #endif
    9459             :                                 isPlayer = NO;
    9460             :                         }
    9461             :                 }
    9462             :         }
    9463             :         @finally
    9464             :         {
    9465             :                 if (self != PLAYER)
    9466             :                 {
    9467             :                         [UNIVERSE removeEntity:self];
    9468             :                 }
    9469             :         }
    9470             : }
    9471             : 
    9472             : 
    9473             : // Exposed to AI
    9474             : - (void) becomeEnergyBlast
    9475             : {
    9476             :         [UNIVERSE addEntity:[OOQuiriumCascadeEntity quiriumCascadeFromShip:self]];
    9477             :         [self broadcastEnergyBlastImminent];
    9478             :         [self noteKilledBy:nil damageType:kOODamageTypeCascadeWeapon];
    9479             :         [UNIVERSE removeEntity:self];
    9480             : }
    9481             : 
    9482             : 
    9483             : // Exposed to AI
    9484             : - (void) broadcastEnergyBlastImminent
    9485             : {
    9486             :         // anyone further away than typical scanner range probably doesn't need to hear
    9487             :         NSArray* targets = [UNIVERSE entitiesWithinRange:SCANNER_MAX_RANGE ofEntity:self];
    9488             :         if ([targets count] > 0)
    9489             :         {
    9490             :                 unsigned i;
    9491             :                 for (i = 0; i < [targets count]; i++)
    9492             :                 {
    9493             :                         Entity *e2 = [targets objectAtIndex:i];
    9494             :                         if ([e2 isShip]) 
    9495             :                         {
    9496             :                                 ShipEntity *se = (ShipEntity *)e2;
    9497             :                                 [se setFoundTarget:self];
    9498             :                                 [se reactToAIMessage:@"CASCADE_WEAPON_DETECTED" context:@"nearby Q-mine"];
    9499             :                                 [se doScriptEvent:OOJSID("cascadeWeaponDetected") withArgument:self];
    9500             :                         }
    9501             :                 }
    9502             :         }
    9503             : }
    9504             : 
    9505             : 
    9506             : - (void) removeExhaust:(OOExhaustPlumeEntity *)exhaust
    9507             : {
    9508             :         [subEntities removeObject:exhaust];
    9509             :         [exhaust setOwner:nil];
    9510             : }
    9511             : 
    9512             : 
    9513             : - (void) removeFlasher:(OOFlasherEntity *)flasher
    9514             : {
    9515             :         [subEntities removeObject:flasher];
    9516             :         [flasher setOwner:nil];
    9517             : }
    9518             : 
    9519             : 
    9520           0 : - (void)subEntityDied:(ShipEntity *)sub
    9521             : {
    9522             :         if ([self subEntityTakingDamage] == sub)  [self setSubEntityTakingDamage:nil];
    9523             :         
    9524             :         [sub setOwner:nil];
    9525             :         // TODO? Recalculating collision radius should increase collision testing efficiency,
    9526             :         // but for most ship models the difference would be marginal. -- Kaks 20110429
    9527             :         mass -= [sub mass]; // missing subents affect fuel charge rate, etc..
    9528             :         [subEntities removeObject:sub];
    9529             : }
    9530             : 
    9531             : 
    9532           0 : - (void)subEntityReallyDied:(ShipEntity *)sub
    9533             : {
    9534             :         if ([self subEntityTakingDamage] == sub)  [self setSubEntityTakingDamage:nil];
    9535             :         
    9536             :         if ([self hasSubEntity:sub])
    9537             :         {
    9538             :                 OOLogERR(@"shipEntity.bug.subEntityRetainUnderflow", @"Subentity of %@ died while still in subentity list! This is bad. Leaking subentity list to avoid crash. %@", self, @"This is an internal error, please report it.");
    9539             :                 
    9540             :                 // Leak subentity list.
    9541             :                 subEntities = nil;
    9542             :         }
    9543             : }
    9544             : 
    9545             : 
    9546             : - (Vector) positionOffsetForAlignment:(NSString*) align
    9547             : {
    9548             :         NSString* padAlign = [NSString stringWithFormat:@"%@---", align];
    9549             :         Vector result = kZeroVector;
    9550             :         switch ([padAlign characterAtIndex:0])
    9551             :         {
    9552             :                 case (unichar)'c':
    9553             :                 case (unichar)'C':
    9554             :                         result.x = 0.5 * (boundingBox.min.x + boundingBox.max.x);
    9555             :                         break;
    9556             :                 case (unichar)'M':
    9557             :                         result.x = boundingBox.max.x;
    9558             :                         break;
    9559             :                 case (unichar)'m':
    9560             :                         result.x = boundingBox.min.x;
    9561             :                         break;
    9562             :         }
    9563             :         switch ([padAlign characterAtIndex:1])
    9564             :         {
    9565             :                 case (unichar)'c':
    9566             :                 case (unichar)'C':
    9567             :                         result.y = 0.5 * (boundingBox.min.y + boundingBox.max.y);
    9568             :                         break;
    9569             :                 case (unichar)'M':
    9570             :                         result.y = boundingBox.max.y;
    9571             :                         break;
    9572             :                 case (unichar)'m':
    9573             :                         result.y = boundingBox.min.y;
    9574             :                         break;
    9575             :         }
    9576             :         switch ([padAlign characterAtIndex:2])
    9577             :         {
    9578             :                 case (unichar)'c':
    9579             :                 case (unichar)'C':
    9580             :                         result.z = 0.5 * (boundingBox.min.z + boundingBox.max.z);
    9581             :                         break;
    9582             :                 case (unichar)'M':
    9583             :                         result.z = boundingBox.max.z;
    9584             :                         break;
    9585             :                 case (unichar)'m':
    9586             :                         result.z = boundingBox.min.z;
    9587             :                         break;
    9588             :         }
    9589             :         return result;
    9590             : }
    9591             : 
    9592             : 
    9593             : Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q, NSString* align)
    9594             : {
    9595             :         NSString* padAlign = [NSString stringWithFormat:@"%@---", align];
    9596             :         Vector i = vector_right_from_quaternion(q);
    9597             :         Vector j = vector_up_from_quaternion(q);
    9598             :         Vector k = vector_forward_from_quaternion(q);
    9599             :         BoundingBox arbb = [ship findBoundingBoxRelativeToPosition:kZeroHPVector InVectors:i :j :k];
    9600             :         Vector result = kZeroVector;
    9601             :         switch ([padAlign characterAtIndex:0])
    9602             :         {
    9603             :                 case (unichar)'c':
    9604             :                 case (unichar)'C':
    9605             :                         result.x = 0.5 * (arbb.min.x + arbb.max.x);
    9606             :                         break;
    9607             :                 case (unichar)'M':
    9608             :                         result.x = arbb.max.x;
    9609             :                         break;
    9610             :                 case (unichar)'m':
    9611             :                         result.x = arbb.min.x;
    9612             :                         break;
    9613             :         }
    9614             :         switch ([padAlign characterAtIndex:1])
    9615             :         {
    9616             :                 case (unichar)'c':
    9617             :                 case (unichar)'C':
    9618             :                         result.y = 0.5 * (arbb.min.y + arbb.max.y);
    9619             :                         break;
    9620             :                 case (unichar)'M':
    9621             :                         result.y = arbb.max.y;
    9622             :                         break;
    9623             :                 case (unichar)'m':
    9624             :                         result.y = arbb.min.y;
    9625             :                         break;
    9626             :         }
    9627             :         switch ([padAlign characterAtIndex:2])
    9628             :         {
    9629             :                 case (unichar)'c':
    9630             :                 case (unichar)'C':
    9631             :                         result.z = 0.5 * (arbb.min.z + arbb.max.z);
    9632             :                         break;
    9633             :                 case (unichar)'M':
    9634             :                         result.z = arbb.max.z;
    9635             :                         break;
    9636             :                 case (unichar)'m':
    9637             :                         result.z = arbb.min.z;
    9638             :                         break;
    9639             :         }
    9640             :         return result;
    9641             : }
    9642             : 
    9643             : 
    9644             : - (void) becomeLargeExplosion:(double)factor
    9645             : {
    9646             :         
    9647             :         if ([self status] == STATUS_DEAD)  return;
    9648             :         [self setStatus:STATUS_DEAD];
    9649             :         
    9650             :         @try
    9651             :         {
    9652             :                 // two parts to the explosion:
    9653             :                 // 1. fast sparks
    9654             :                 float how_many = factor;
    9655             :                 while (how_many > 0.5f)
    9656             :                 {
    9657             :                         [UNIVERSE addEntity:[OOSmallFragmentBurstEntity fragmentBurstFromEntity:self]];
    9658             :                         how_many -= 1.0f;
    9659             :                 }
    9660             :                 // 2. slow clouds
    9661             :                 how_many = factor;
    9662             :                 while (how_many > 0.5f)
    9663             :                 {
    9664             :                         [UNIVERSE addEntity:[OOBigFragmentBurstEntity fragmentBurstFromEntity:self]];
    9665             :                         how_many -= 1.0f;
    9666             :                 }
    9667             : 
    9668             :                 [self releaseCargoPodsDebris];
    9669             :                 
    9670             :                 NSEnumerator    *subEnum = nil;
    9671             :                 ShipEntity              *se = nil;
    9672             :                 for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
    9673             :                 {
    9674             :                         [se setSuppressExplosion:suppressExplosion];
    9675             :                         [se becomeExplosion];
    9676             :                 }
    9677             :                 [self clearSubEntities];
    9678             :                 
    9679             :         }
    9680             :         @finally
    9681             :         {
    9682             :                 if (!isPlayer)  [UNIVERSE removeEntity:self];
    9683             :         }
    9684             : }
    9685             : 
    9686             : 
    9687             : - (void) collectBountyFor:(ShipEntity *)other
    9688             : {
    9689             :         if ([other isPolice])   // oops, we shot a copper!
    9690             :         {
    9691             :                 [self markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice];
    9692             :         }
    9693             : }
    9694             : 
    9695             : 
    9696           0 : - (NSComparisonResult) compareBeaconCodeWith:(Entity<OOBeaconEntity> *) other
    9697             : {
    9698             :         return [[self beaconCode] compare:[other beaconCode] options: NSCaseInsensitiveSearch];
    9699             : }
    9700             : 
    9701             : 
    9702             : // for shaders, equivalent to 1.76's NPC laserHeatLevel
    9703             : - (GLfloat) weaponRecoveryTime
    9704             : {
    9705             :         float result = (weapon_recharge_rate - [self shotTime]) / weapon_recharge_rate;
    9706             :         return OOClamp_0_1_f(result);
    9707             : }
    9708             : 
    9709             : 
    9710             : - (GLfloat)laserHeatLevel
    9711             : {
    9712             :         GLfloat result = weapon_temp / NPC_MAX_WEAPON_TEMP;
    9713             :         return OOClamp_0_1_f(result);
    9714             : }
    9715             : 
    9716             : 
    9717             : - (GLfloat)laserHeatLevelAft
    9718             : {
    9719             :         GLfloat result = aft_weapon_temp / NPC_MAX_WEAPON_TEMP;
    9720             :         return OOClamp_0_1_f(result);
    9721             : }
    9722             : 
    9723             : 
    9724             : - (GLfloat)laserHeatLevelForward
    9725             : {
    9726             :         GLfloat result = forward_weapon_temp / NPC_MAX_WEAPON_TEMP;
    9727             :         if (isWeaponNone(forward_weapon_type)) 
    9728             :         { // must check subents
    9729             :                 OOWeaponType forward_weapon_real_type = nil;
    9730             :                 NSEnumerator    *subEnum = [self shipSubEntityEnumerator];
    9731             :                 ShipEntity              *se = nil;
    9732             :                 while (isWeaponNone(forward_weapon_real_type) && (se = [subEnum nextObject]))
    9733             :                 {
    9734             :                         if (!isWeaponNone(se->forward_weapon_type))
    9735             :                         {
    9736             :                                 forward_weapon_real_type = se->forward_weapon_type;
    9737             :                                 result = se->forward_weapon_temp / NPC_MAX_WEAPON_TEMP;
    9738             :                         }
    9739             :                 }
    9740             :         }
    9741             :         return OOClamp_0_1_f(result);
    9742             : }
    9743             : 
    9744             : 
    9745             : - (GLfloat)laserHeatLevelPort
    9746             : {
    9747             :         GLfloat result = port_weapon_temp / NPC_MAX_WEAPON_TEMP;
    9748             :         return OOClamp_0_1_f(result);
    9749             : }
    9750             : 
    9751             : 
    9752             : - (GLfloat)laserHeatLevelStarboard
    9753             : {
    9754             :         GLfloat result = starboard_weapon_temp / NPC_MAX_WEAPON_TEMP;
    9755             :         return OOClamp_0_1_f(result);
    9756             : }
    9757             : 
    9758             : 
    9759             : - (GLfloat)hullHeatLevel
    9760             : {
    9761             :         GLfloat result = (GLfloat)ship_temperature / (GLfloat)SHIP_MAX_CABIN_TEMP;
    9762             :         return OOClamp_0_1_f(result);
    9763             : }
    9764             : 
    9765             : 
    9766             : - (GLfloat)entityPersonality
    9767             : {
    9768             :         return entity_personality / (float)ENTITY_PERSONALITY_MAX;
    9769             : }
    9770             : 
    9771             : 
    9772             : - (GLint)entityPersonalityInt
    9773             : {
    9774             :         return entity_personality;
    9775             : }
    9776             : 
    9777             : 
    9778           0 : - (uint32_t) randomSeedForShaders
    9779             : {
    9780             :         return entity_personality * 0x00010001;
    9781             : }
    9782             : 
    9783             : 
    9784             : - (void) setEntityPersonalityInt:(uint16_t)value
    9785             : {
    9786             :         if (value <= ENTITY_PERSONALITY_MAX)
    9787             :         {
    9788             :                 entity_personality = value;
    9789             :                 [[self mesh] rebindMaterials];
    9790             :         }
    9791             : }
    9792             : 
    9793             : 
    9794             : - (void)setSuppressExplosion:(BOOL)suppress
    9795             : {
    9796             :         suppressExplosion = !!suppress;
    9797             : }
    9798             : 
    9799             : 
    9800             : - (void) resetExhaustPlumes
    9801             : {
    9802             :         NSEnumerator *exEnum = nil;
    9803             :         OOExhaustPlumeEntity *exEnt = nil;
    9804             :         
    9805             :         for (exEnum = [self exhaustEnumerator]; (exEnt = [exEnum nextObject]); )
    9806             :         {
    9807             :                 [exEnt resetPlume];
    9808             :         }
    9809             : }
    9810             : 
    9811             : 
    9812             : /*-----------------------------------------
    9813             : 
    9814             :         AI piloting methods
    9815             : 
    9816             : -----------------------------------------*/
    9817             : 
    9818             : 
    9819             : - (void) checkScanner
    9820             : {
    9821             :         Entity* scan;
    9822             :         n_scanned_ships = 0;
    9823             :         //
    9824             :         scan = z_previous;      while ((scan)&&(scan->isShip == NO)) scan = scan->z_previous;     // skip non-ships
    9825             :         GLfloat scannerRange2 = scannerRange * scannerRange;
    9826             :         while ((scan)&&(scan->position.z > position.z - scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER))
    9827             :         {
    9828             :                 // can't scan cloaked ships
    9829             :                 if (scan->isShip && ![(ShipEntity*)scan isCloaked] && [self isValidTarget:scan])
    9830             :                 {
    9831             :                         distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position);
    9832             :                         if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
    9833             :                                 scanned_ships[n_scanned_ships++] = (ShipEntity*)scan;
    9834             :                 }
    9835             :                 scan = scan->z_previous;     while ((scan)&&(scan->isShip == NO)) scan = scan->z_previous;
    9836             :         }
    9837             :         //
    9838             :         scan = z_next;  while ((scan)&&(scan->isShip == NO)) scan = scan->z_next; // skip non-ships
    9839             :         while ((scan)&&(scan->position.z < position.z + scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER))
    9840             :         {
    9841             :                 if (scan->isShip && ![(ShipEntity*)scan isCloaked] && [self isValidTarget:scan])
    9842             :                 {
    9843             :                         distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position);
    9844             :                         if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
    9845             :                                 scanned_ships[n_scanned_ships++] = (ShipEntity*)scan;
    9846             :                 }
    9847             :                 scan = scan->z_next; while ((scan)&&(scan->isShip == NO)) scan = scan->z_next; // skip non-ships
    9848             :         }
    9849             :         //
    9850             :         scanned_ships[n_scanned_ships] = nil;   // terminate array
    9851             : }
    9852             : 
    9853             : 
    9854             : - (void) checkScannerIgnoringUnpowered
    9855             : {
    9856             :         Entity* scan;
    9857             :         n_scanned_ships = 0;
    9858             :         //
    9859             :         GLfloat scannerRange2 = scannerRange * scannerRange;
    9860             :         scan = z_previous;      
    9861             :         while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO)))  
    9862             :         {
    9863             :                 scan = scan->z_previous;     // skip non-ships
    9864             :         }
    9865             :         while ((scan)&&(scan->position.z > position.z - scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER))
    9866             :         {
    9867             :                 if (scan->isShip && ![(ShipEntity*)scan isCloaked])
    9868             :                 {
    9869             :                         distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position);
    9870             :                         if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
    9871             :                                 scanned_ships[n_scanned_ships++] = (ShipEntity*)scan;
    9872             :                 }
    9873             :                 scan = scan->z_previous;
    9874             :                 while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO)))  
    9875             :                 {
    9876             :                         scan = scan->z_previous;     // skip non-ships
    9877             :                 }
    9878             :         }
    9879             :         //
    9880             :         scan = z_next;  
    9881             :         while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO)))  
    9882             :         {
    9883             :                 scan = scan->z_next; // skip non-ships
    9884             :         }
    9885             : 
    9886             :         while ((scan)&&(scan->position.z < position.z + scannerRange)&&(n_scanned_ships < MAX_SCAN_NUMBER))
    9887             :         {
    9888             :                 if (scan->isShip && ![(ShipEntity*)scan isCloaked])
    9889             :                 {
    9890             :                         distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->position);
    9891             :                         if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
    9892             :                                 scanned_ships[n_scanned_ships++] = (ShipEntity*)scan;
    9893             :                 }
    9894             :                 scan = scan->z_next;
    9895             :                 while ((scan)&&((scan->isShip == NO)||(scan->scanClass==CLASS_ROCK)||(scan->scanClass==CLASS_CARGO)))  
    9896             :                 {
    9897             :                         scan = scan->z_next; // skip non-ships
    9898             :                 }
    9899             :         }
    9900             :         //
    9901             :         scanned_ships[n_scanned_ships] = nil;   // terminate array
    9902             : }
    9903             : 
    9904             : 
    9905             : - (ShipEntity**) scannedShips
    9906             : {
    9907             :         scanned_ships[n_scanned_ships] = nil;   // terminate array
    9908             :         return scanned_ships;
    9909             : }
    9910             : 
    9911             : 
    9912             : - (int) numberOfScannedShips
    9913             : {
    9914             :         return n_scanned_ships;
    9915             : }
    9916             : 
    9917             : 
    9918             : - (Entity *) foundTarget
    9919             : {
    9920             :         Entity *result = [_foundTarget weakRefUnderlyingObject];
    9921             :         if (result == nil || ![self isValidTarget:result])
    9922             :         {
    9923             :                 DESTROY(_foundTarget);
    9924             :                 return nil;
    9925             :         }
    9926             :         return result;
    9927             : }
    9928             : 
    9929             : 
    9930             : - (void) setFoundTarget:(Entity *) targetEntity
    9931             : {
    9932             :         [_foundTarget release];
    9933             :         _foundTarget = [targetEntity weakRetain];
    9934             : }
    9935             : 
    9936             : 
    9937             : - (Entity *) primaryAggressor
    9938             : {
    9939             :         Entity *result = [_primaryAggressor weakRefUnderlyingObject];
    9940             :         if (result == nil || ![self isValidTarget:result])
    9941             :         {
    9942             :                 DESTROY(_primaryAggressor);
    9943             :                 return nil;
    9944             :         }
    9945             :         return result;
    9946             : }
    9947             : 
    9948             : 
    9949             : - (void) setPrimaryAggressor:(Entity *) targetEntity
    9950             : {
    9951             :         [_primaryAggressor release];
    9952             :         _primaryAggressor = [targetEntity weakRetain];
    9953             : }
    9954             : 
    9955             : 
    9956             : - (Entity *) lastEscortTarget
    9957             : {
    9958             :         Entity *result = [_lastEscortTarget weakRefUnderlyingObject];
    9959             :         if (result == nil || ![self isValidTarget:result])
    9960             :         {
    9961             :                 DESTROY(_lastEscortTarget);
    9962             :                 return nil;
    9963             :         }
    9964             :         return result;
    9965             : }
    9966             : 
    9967             : 
    9968             : - (void) setLastEscortTarget:(Entity *) targetEntity
    9969             : {
    9970             :         [_lastEscortTarget release];
    9971             :         _lastEscortTarget = [targetEntity weakRetain];
    9972             : }
    9973             : 
    9974             : 
    9975             : - (Entity *) thankedShip
    9976             : {
    9977             :         Entity *result = [_thankedShip weakRefUnderlyingObject];
    9978             :         if (result == nil || ![self isValidTarget:result])
    9979             :         {
    9980             :                 DESTROY(_thankedShip);
    9981             :                 return nil;
    9982             :         }
    9983             :         return result;
    9984             : }
    9985             : 
    9986             : 
    9987             : - (void) setThankedShip:(Entity *) targetEntity
    9988             : {
    9989             :         [_thankedShip release];
    9990             :         _thankedShip = [targetEntity weakRetain];
    9991             : }
    9992             : 
    9993             : 
    9994             : - (Entity *) rememberedShip
    9995             : {
    9996             :         Entity *result = [_rememberedShip weakRefUnderlyingObject];
    9997             :         if (result == nil || ![self isValidTarget:result])
    9998             :         {
    9999             :                 DESTROY(_rememberedShip);
   10000             :                 return nil;
   10001             :         }
   10002             :         return result;
   10003             : }
   10004             : 
   10005             : 
   10006             : - (void) setRememberedShip:(Entity *) targetEntity
   10007             : {
   10008             :         [_rememberedShip release];
   10009             :         _rememberedShip = [targetEntity weakRetain];
   10010             : }
   10011             : 
   10012             : 
   10013             : - (StationEntity *) targetStation
   10014             : {
   10015             :         StationEntity *result = [_targetStation weakRefUnderlyingObject];
   10016             :         if (result == nil || ![self isValidTarget:result])
   10017             :         {
   10018             :                 DESTROY(_targetStation);
   10019             :                 return nil;
   10020             :         }
   10021             :         return result;
   10022             : }
   10023             : 
   10024             : 
   10025             : - (void) setTargetStation:(Entity *) targetEntity
   10026             : {
   10027             :         [_targetStation release];
   10028             :         _targetStation = [targetEntity weakRetain];
   10029             : }
   10030             : 
   10031             : /* Now we use weakrefs rather than universal ID this function checks
   10032             :  * for targets which may have a valid reference but are not currently
   10033             :  * targetable. */
   10034             : - (BOOL) isValidTarget:(Entity *)target
   10035             : {
   10036             :         if (target == nil) 
   10037             :         {
   10038             :                 return NO;
   10039             :         }
   10040             :         if ([target isShip])
   10041             :         {
   10042             :                 OOEntityStatus tstatus = [target status];
   10043             :                 if (tstatus == STATUS_ENTERING_WITCHSPACE || tstatus == STATUS_IN_HOLD || tstatus == STATUS_DOCKED || tstatus == STATUS_DEAD)
   10044             :         // 2013-01-13, Eric: added STATUS_DEAD because I keep seeing ships locked on dead ships in attack mode.
   10045             :                 {
   10046             :                         return NO;
   10047             :                 }
   10048             :                 return YES;
   10049             :         }
   10050             :         if ([target isWormhole] && [target scanClass] != CLASS_NO_DRAW)
   10051             :         {
   10052             :                 return YES;
   10053             :         }
   10054             :         return NO;
   10055             : }
   10056             : 
   10057             : 
   10058             : - (void) addTarget:(Entity *) targetEntity
   10059             : {
   10060             :         if (targetEntity == self)  return;
   10061             :         if (targetEntity != nil) 
   10062             :         {
   10063             :                 DESTROY(_primaryTarget);
   10064             :                 _primaryTarget = [targetEntity weakRetain];
   10065             :                 [self startTrackingCurve];
   10066             :         }
   10067             :         
   10068             :         [[self shipSubEntityEnumerator] makeObjectsPerformSelector:@selector(addTarget:) withObject:targetEntity];
   10069             :         if (![self isSubEntity])  [self doScriptEvent:OOJSID("shipTargetAcquired") withArgument:targetEntity];
   10070             : }
   10071             : 
   10072             : 
   10073             : - (void) removeTarget:(Entity *) targetEntity
   10074             : {
   10075             :         if(targetEntity != nil) [self noteLostTarget];
   10076             :         else DESTROY(_primaryTarget);
   10077             :         // targetEntity == nil is currently only true for mounted player missiles. 
   10078             :         // we don't want to send lostTarget messages while the missile is mounted.
   10079             :         
   10080             :         [[self shipSubEntityEnumerator] makeObjectsPerformSelector:@selector(removeTarget:) withObject:targetEntity];
   10081             : }
   10082             : 
   10083             : 
   10084             : /* Checks if the primary target is still trackable.
   10085             :  * 
   10086             :  * 1.80 behaviour: still exists, is in scanner range, is not cloaked
   10087             :  *
   10088             :  * 1.81 (planned) behaviour:
   10089             :  * - track cloaked ships once primary targeted (but no missiles, and can't keep as a mere defense target, and probably some other penalties)
   10090             :  * - track ships at 120% scanner range *if* they are also primary aggressor
   10091             :  *
   10092             :  * But first, just switch over to this method on 1.80 behaviour and check
   10093             :  * that things still work.
   10094             :  */
   10095             : - (BOOL) canStillTrackPrimaryTarget
   10096             : {
   10097             :         Entity *target = (Entity *)[self primaryTargetWithoutValidityCheck];
   10098             :         if (target == nil)
   10099             :         {
   10100             :                 return NO;
   10101             :         }
   10102             :         if (![self isValidTarget:target])
   10103             :         {
   10104             :                 return NO;
   10105             :         }
   10106             :         double range2 = HPmagnitude2(HPvector_subtract([target position], position));
   10107             :         if (range2 > scannerRange * scannerRange * 1.5625) 
   10108             :         {
   10109             :                 // 1.5625 = 1.25*1.25
   10110             :                 return NO;
   10111             :         }
   10112             :         // 1.81: can retain cloaked ships as a *primary* target now
   10113             : /*      if ([target isShip] && [(ShipEntity*)target isCloaked])
   10114             :         {
   10115             :                 return NO;
   10116             :                 } */
   10117             :         return YES;
   10118             : }
   10119             : 
   10120             : 
   10121             : - (id) primaryTarget
   10122             : {
   10123             :         id result = [_primaryTarget weakRefUnderlyingObject];
   10124             :         if ((result == nil && _primaryTarget != nil)
   10125             :                         || ![self isValidTarget:result])
   10126             :         {
   10127             :                 DESTROY(_primaryTarget);
   10128             :                 return nil;
   10129             :         }
   10130             :         else if (EXPECT_NOT(result == self))
   10131             :         {
   10132             :                 /*      Added in response to a crash report showing recursion in
   10133             :                         [PlayerEntity hasHostileTarget].
   10134             :                         -- Ahruman 2009-12-17
   10135             :                 */
   10136             :                 DESTROY(_primaryTarget);
   10137             :         }
   10138             :         return result;
   10139             : }
   10140             : 
   10141             : 
   10142             : // used when we need to check the target - perhaps for a potential
   10143             : // noteTargetLost - without invalidating the target first
   10144             : - (id) primaryTargetWithoutValidityCheck
   10145             : {
   10146             :         id result = [_primaryTarget weakRefUnderlyingObject];
   10147             :         if (EXPECT_NOT(result == self))
   10148             :         {
   10149             :                 // just in case
   10150             :                 DESTROY(_primaryTarget);
   10151             :                 return nil;
   10152             :         }
   10153             :         return result;
   10154             : }
   10155             : 
   10156             : 
   10157             : - (BOOL) isFriendlyTo:(ShipEntity *)otherShip
   10158             : {
   10159             :         BOOL isFriendly = NO;
   10160             :         OOShipGroup     *myGroup = [self group];
   10161             :         OOShipGroup     *otherGroup = [otherShip group];
   10162             :         
   10163             :         if ((otherShip == self) ||
   10164             :                 ([self isPolice] && [otherShip isPolice]) ||
   10165             :                 ([self isThargoid] && [otherShip isThargoid]) ||
   10166             :                 (myGroup != nil && otherGroup != nil && (myGroup == otherGroup || [otherGroup leader] == self)) ||
   10167             :                 ([self scanClass] == CLASS_MILITARY && [otherShip scanClass] == CLASS_MILITARY))
   10168             :         {
   10169             :                 isFriendly = YES;
   10170             :         }
   10171             :         
   10172             :         return isFriendly;
   10173             : }
   10174             : 
   10175             : 
   10176             : - (ShipEntity *) shipHitByLaser
   10177             : {
   10178             :         return [_shipHitByLaser weakRefUnderlyingObject];
   10179             : }
   10180             : 
   10181             : 
   10182           0 : - (void) setShipHitByLaser:(ShipEntity *)ship
   10183             : {
   10184             :         if (ship != [self shipHitByLaser])
   10185             :         {
   10186             :                 [_shipHitByLaser release];
   10187             :                 _shipHitByLaser = [ship weakRetain];
   10188             :         }
   10189             : }
   10190             : 
   10191             : 
   10192             : - (void) noteLostTarget
   10193             : {
   10194             :         id target = nil;
   10195             :         if ([self primaryTarget] != nil)
   10196             :         {
   10197             :                 ShipEntity* ship = [self primaryTarget];
   10198             :                 if ([self isDefenseTarget:ship]) 
   10199             :                 {
   10200             :                         [self removeDefenseTarget:ship];
   10201             :                 }
   10202             :                 // for compatibility with 1.76 behaviour of this function, only pass
   10203             :                 // the target as a function parameter if the target is still a potential
   10204             :                 // valid target (e.g. not scooped, docked, hyperspaced, etc.)
   10205             :                 target = (ship && ship->isShip && [self isValidTarget:ship]) ? (id)ship : nil;
   10206             :                 if ([self primaryAggressor] == ship) 
   10207             :                 {
   10208             :                         DESTROY(_primaryAggressor);
   10209             :                 }
   10210             :                 DESTROY(_primaryTarget);
   10211             :         }
   10212             :         // always do target lost
   10213             :         [self doScriptEvent:OOJSID("shipTargetLost") withArgument:target];
   10214             :         if (target == nil) [shipAI message:@"TARGET_LOST"];   // stale target? no major urgency.
   10215             :         else [shipAI reactToMessage:@"TARGET_LOST" context:@"flight updates"];      // execute immediately otherwise.
   10216             : }
   10217             : 
   10218             : 
   10219             : - (void) noteLostTargetAndGoIdle
   10220             : {
   10221             :         behaviour = BEHAVIOUR_IDLE;
   10222             :         frustration = 0.0;
   10223             :         [self noteLostTarget];
   10224             : }
   10225             : 
   10226             : - (void) noteTargetDestroyed:(ShipEntity *)target
   10227             : {
   10228             :         [self collectBountyFor:(ShipEntity *)target];
   10229             :         if ([self primaryTarget] == target)
   10230             :         {
   10231             :                 [self removeTarget:target];
   10232             :                 [self doScriptEvent:OOJSID("shipTargetDestroyed") withArgument:target];
   10233             :                 [shipAI message:@"TARGET_DESTROYED"];
   10234             :         }
   10235             :         if ([self isDefenseTarget:target]) 
   10236             :         {
   10237             :                 [self removeDefenseTarget:target];
   10238             :                 [shipAI message:@"DEFENSE_TARGET_DESTROYED"];
   10239             :                 [self doScriptEvent:OOJSID("defenseTargetDestroyed") withArgument:target];
   10240             :         }
   10241             : }
   10242             : 
   10243             : 
   10244             : - (OOBehaviour) behaviour
   10245             : {
   10246             :         return behaviour;
   10247             : }
   10248             : 
   10249             : 
   10250             : - (void) setBehaviour:(OOBehaviour) cond
   10251             : {
   10252             :         if (cond != behaviour)
   10253             :         {
   10254             :                 frustration = 0.0;      // change is a GOOD thing
   10255             :                 behaviour = cond;
   10256             :         }
   10257             : }
   10258             : 
   10259             : 
   10260             : - (HPVector) destination
   10261             : {
   10262             :         return _destination;
   10263             : }
   10264             : 
   10265             : - (HPVector) coordinates
   10266             : {
   10267             :         return coordinates;
   10268             : }
   10269             : 
   10270             : - (void) setCoordinate:(HPVector) coord // The name "setCoordinates" is already used by AI scripting.
   10271             : {
   10272             :         coordinates = coord;
   10273             : }
   10274             : 
   10275             : - (HPVector) distance_six: (GLfloat) dist
   10276             : {
   10277             :         HPVector six = position;
   10278             :         six.x -= dist * v_forward.x;    six.y -= dist * v_forward.y;    six.z -= dist * v_forward.z;
   10279             :         return six;
   10280             : }
   10281             : 
   10282             : 
   10283             : - (HPVector) distance_twelve: (GLfloat) dist withOffset:(GLfloat)offset
   10284             : {
   10285             :         HPVector twelve = position;
   10286             :         twelve.x += dist * v_up.x;      twelve.y += dist * v_up.y;      twelve.z += dist * v_up.z;
   10287             :         twelve.x += offset * v_right.x; twelve.y += offset * v_right.y; twelve.z += offset * v_right.z;
   10288             :         return twelve;
   10289             : }
   10290             : 
   10291             : 
   10292             : - (void) trackOntoTarget:(double) delta_t withDForward: (GLfloat) dp
   10293             : {
   10294             :         Vector vector_to_target;
   10295             :         Quaternion q_minarc;
   10296             :         //
   10297             :         Entity* target = [self primaryTarget];
   10298             :         //
   10299             :         if (!target)
   10300             :                 return;
   10301             : 
   10302             :         vector_to_target = [self vectorTo:target];
   10303             :         //
   10304             :         GLfloat range2 =                magnitude2(vector_to_target);
   10305             :         GLfloat targetRadius =  0.75 * target->collision_radius;
   10306             :         GLfloat max_cos =               sqrt(1 - targetRadius*targetRadius/range2);
   10307             :         
   10308             :         if (dp > max_cos)
   10309             :                 return; // ON TARGET!
   10310             :         
   10311             :         if (vector_to_target.x||vector_to_target.y||vector_to_target.z)
   10312             :                 vector_to_target = vector_normal(vector_to_target);
   10313             :         else
   10314             :                 vector_to_target.z = 1.0;
   10315             :         
   10316             :         q_minarc = quaternion_rotation_between(v_forward, vector_to_target);
   10317             :         
   10318             :         orientation = quaternion_multiply(q_minarc, orientation);
   10319             :         [self orientationChanged];
   10320             :         
   10321             :         flightRoll = 0.0;
   10322             :         flightPitch = 0.0;
   10323             :         flightYaw = 0.0;
   10324             :         stick_roll = 0.0;
   10325             :         stick_pitch = 0.0;
   10326             :         stick_yaw = 0.0;
   10327             : }
   10328             : 
   10329             : 
   10330             : - (double) ballTrackLeadingTarget:(double) delta_t atTarget:(Entity *)target
   10331             : {
   10332             :         if (!target)
   10333             :         {
   10334             :                 return -2.0; // no target
   10335             :         }
   10336             : 
   10337             :         Vector          vector_to_target;
   10338             :         Vector          axis_to_track_by;
   10339             :         Vector          my_aim = vector_forward_from_quaternion(orientation);
   10340             :         Vector          my_ref = reference;
   10341             :         double          aim_cos, ref_cos;
   10342             :         Vector          leading = [target velocity];
   10343             : 
   10344             :         // need to get vector to target in terms of this entities coordinate system
   10345             :         HPVector my_position = [self absolutePositionForSubentity];
   10346             :         vector_to_target = HPVectorToVector(HPvector_subtract([target position], my_position));
   10347             :         // this is in absolute coordinates, so now rotate it
   10348             : 
   10349             :         Entity          *last = nil;
   10350             :         Entity          *father = [self parentEntity];
   10351             : 
   10352             :         Quaternion  q = kIdentityQuaternion;
   10353             :         while ((father)&&(father != last) && (father != NO_TARGET))
   10354             :         {
   10355             :                 /* Fix orientation */
   10356             :                 Quaternion fo = [father normalOrientation];
   10357             :                 fo.w = -fo.w;
   10358             :                 /* The below code works for player turrets where the
   10359             :                  * orientation is different, but not for NPC turrets. Taking
   10360             :                  * the normal orientation with -w works: there is probably a
   10361             :                  * neater way which someone who understands quaternions can
   10362             :                  * find, but this works well enough for 1.82 - CIM */
   10363             :                 q = quaternion_multiply(q,quaternion_conjugate(fo));
   10364             :                 last = father;
   10365             :                 if (![last isSubEntity]) break;
   10366             :                 father = [father owner];
   10367             :         }
   10368             :         q = quaternion_conjugate(q);
   10369             :         // q now contains the rotation to the turret's reference system
   10370             : 
   10371             :         vector_to_target = quaternion_rotate_vector(q,vector_to_target);
   10372             : 
   10373             :         leading = quaternion_rotate_vector(q,leading);
   10374             :         // rotate the vector to target and its velocity
   10375             :         
   10376             :         if (magnitude(vector_to_target) > weaponRange * 1.01)
   10377             :         {
   10378             :                 return -2.0; // out of range
   10379             :         }
   10380             : 
   10381             :         float lead = magnitude(vector_to_target) / TURRET_SHOT_SPEED;
   10382             :                 
   10383             :         vector_to_target = vector_add(vector_to_target, vector_multiply_scalar(leading, lead));
   10384             :         vector_to_target = vector_normal_or_fallback(vector_to_target, kBasisZVector);
   10385             :                 
   10386             :         // do the tracking!
   10387             :         aim_cos = dot_product(vector_to_target, my_aim);
   10388             :         ref_cos = dot_product(vector_to_target, my_ref);
   10389             : 
   10390             :         
   10391             :         if (ref_cos > TURRET_MINIMUM_COS)  // target is forward of self
   10392             :         {
   10393             :                 axis_to_track_by = cross_product(vector_to_target, my_aim);
   10394             :         }
   10395             :         else
   10396             :         {
   10397             :                 return -2.0; // target is out of fire arc
   10398             :         }
   10399             :         
   10400             :         quaternion_rotate_about_axis(&orientation, axis_to_track_by, thrust * delta_t);
   10401             :         [self orientationChanged];
   10402             :         
   10403             :         [self setStatus:STATUS_ACTIVE];
   10404             :         
   10405             :         return aim_cos;
   10406             : }
   10407             : 
   10408             : 
   10409             : - (void) setEvasiveJink:(GLfloat) z
   10410             : {
   10411             :         if (accuracy < COMBAT_AI_ISNT_AWFUL)
   10412             :         {
   10413             :                 jink = kZeroVector;
   10414             :         }
   10415             :         else 
   10416             :         {
   10417             :                 jink.x = (ranrot_rand() % 256) - 128.0;
   10418             :                 jink.y = (ranrot_rand() % 256) - 128.0;
   10419             :                 jink.z = z;
   10420             :                 
   10421             :                 // make sure we don't accidentally have near-zero jink
   10422             :                 if (jink.x < 0.0) 
   10423             :                 {
   10424             :                         jink.x -= 128.0;
   10425             :                 }
   10426             :                 else
   10427             :                 {
   10428             :                         jink.x += 128.0;
   10429             :                 }
   10430             :                 if (jink.y < 0) 
   10431             :                 {
   10432             :                         jink.y -= 128.0;
   10433             :                 }
   10434             :                 else
   10435             :                 {
   10436             :                         jink.y += 128.0;
   10437             :                 }
   10438             :         }
   10439             : }
   10440             : 
   10441             : 
   10442             : - (void) evasiveAction:(double) delta_t
   10443             : {
   10444             :         stick_roll = flightRoll;        //desired roll and pitch
   10445             :         stick_pitch = flightPitch;
   10446             : 
   10447             :         ShipEntity* target = [self primaryTarget];
   10448             :         if (!target)   // leave now!
   10449             :         {
   10450             :                 [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage:
   10451             :                 return;
   10452             :         }
   10453             : 
   10454             :         double agreement = dot_product(v_right,target->v_right);
   10455             :         if (agreement > -0.3 && agreement < 0.3)
   10456             :         {
   10457             :                 stick_roll = 0.0;
   10458             :         }
   10459             :         else
   10460             :         {
   10461             :                 if (stick_roll >= 0.0) {
   10462             :                         stick_roll = max_flight_roll;
   10463             :                 } else {
   10464             :                         stick_roll = -max_flight_roll;
   10465             :                 }
   10466             :         }
   10467             :         if (stick_pitch >= 0.0) {
   10468             :                 stick_pitch = max_flight_pitch;
   10469             :         } else {
   10470             :                 stick_pitch = -max_flight_pitch;
   10471             :         }
   10472             :         
   10473             :   [self applySticks:delta_t];
   10474             : }
   10475             : 
   10476             : 
   10477             : - (double) trackPrimaryTarget:(double) delta_t :(BOOL) retreat
   10478             : {
   10479             :         Entity* target = [self primaryTarget];
   10480             : 
   10481             :         if (!target)   // leave now!
   10482             :         {
   10483             :                 [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage:
   10484             :                 return 0.0;
   10485             :         }
   10486             : 
   10487             :         if (![self canStillTrackPrimaryTarget])
   10488             :         {
   10489             :                 [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage:
   10490             :                 return 0.0;
   10491             :         }
   10492             : 
   10493             :         /* 1.81 change: do the above check first: if a missile can't be
   10494             :          * fired outside scanner range it should self-destruct if the
   10495             :          * target gets far enough away (it's going to miss anyway) -
   10496             :          * CIM */
   10497             :         if (scanClass == CLASS_MISSILE)
   10498             :                 return [self missileTrackPrimaryTarget: delta_t];
   10499             : 
   10500             :         GLfloat  d_forward, d_up, d_right;
   10501             :         
   10502             :         Vector  relPos = HPVectorToVector(HPvector_subtract([self calculateTargetPosition], position));
   10503             :         
   10504             :         double  range2 = HPmagnitude2(HPvector_subtract([target position], position));
   10505             : 
   10506             :         //jink if retreating
   10507             :         if (retreat) // calculate jink position when flying away from target.
   10508             :         {
   10509             :                 Vector vx, vy, vz;
   10510             :                 if (target->isShip)
   10511             :                 {
   10512             :                         ShipEntity* targetShip = (ShipEntity*)target;
   10513             :                         vx = targetShip->v_right;
   10514             :                         vy = targetShip->v_up;
   10515             :                         vz = targetShip->v_forward;
   10516             :                 }
   10517             :                 else
   10518             :                 {
   10519             :                         Quaternion q = target->orientation;
   10520             :                         vx = vector_right_from_quaternion(q);
   10521             :                         vy = vector_up_from_quaternion(q);
   10522             :                         vz = vector_forward_from_quaternion(q);
   10523             :                 }
   10524             :                 
   10525             :                 BOOL avoidCollision = NO;
   10526             :                 if (range2 < collision_radius * target->collision_radius * 100.0) // Check direction within 10 * collision radius.
   10527             :                 {
   10528             :                         Vector targetDirection = kBasisZVector;
   10529             :                         if (!vector_equal(relPos, kZeroVector))  targetDirection = vector_normal(relPos);
   10530             :                         avoidCollision  =  (dot_product(targetDirection, v_forward) > -0.1); // is flying toward target or only slightly outward.
   10531             :                 }
   10532             :                 
   10533             :                 GLfloat dist_adjust_factor = 1.0;
   10534             :                 if (accuracy >= COMBAT_AI_FLEES_BETTER)
   10535             :                 {
   10536             :                         double  range = magnitude(relPos);
   10537             :                         if (range > 2000.0)
   10538             :                         {
   10539             :                                 dist_adjust_factor = range / 2000.0;
   10540             :                                 if (accuracy >= COMBAT_AI_FLEES_BETTER_2)
   10541             :                                 {
   10542             :                                         dist_adjust_factor *= 3;
   10543             :                                 }
   10544             :                         }
   10545             :                         if (jink.x == 0.0 && behaviour != BEHAVIOUR_RUNNING_DEFENSE)
   10546             :                         { // test for zero jink and correct
   10547             :                                 [self setEvasiveJink:400.0];
   10548             :                         }
   10549             :                 }
   10550             : 
   10551             :                 if (!avoidCollision)  // it is safe to jink
   10552             :                 {
   10553             :                         relPos.x += (jink.x * vx.x + jink.y * vy.x + jink.z * vz.x) * dist_adjust_factor;
   10554             :                         relPos.y += (jink.x * vx.y + jink.y * vy.y + jink.z * vz.y) * dist_adjust_factor;
   10555             :                         relPos.z += (jink.x * vx.z + jink.y * vy.z + jink.z * vz.z);
   10556             :                 }
   10557             : 
   10558             :         }
   10559             : 
   10560             :         if (!vector_equal(relPos, kZeroVector))  relPos = vector_normal(relPos);
   10561             :         else  relPos.z = 1.0;
   10562             : 
   10563             :         double  max_cos = [self currentAimTolerance];
   10564             : 
   10565             :         stick_roll = 0.0;       //desired roll and pitch
   10566             :         stick_pitch = 0.0;
   10567             : 
   10568             :         double reverse = (retreat)? -1.0: 1.0;
   10569             : 
   10570             :         double min_d = 0.004; // ~= 40m at 10km
   10571             :         int max_factor = 8;
   10572             :         double r_max_factor = 0.125;
   10573             :         if (!retreat)
   10574             :         {       
   10575             :                 if (accuracy >= COMBAT_AI_TRACKS_CLOSER)
   10576             :                 { 
   10577             :                         // much greater precision in combat
   10578             :                         if (max_flight_pitch > 1.0)
   10579             :                         {
   10580             :                                 max_factor = floor(max_flight_pitch/0.125);
   10581             :                                 r_max_factor = 1.0/max_factor;
   10582             :                         }
   10583             :                         min_d = 0.0004; // 10 times more precision ~= 4m at 10km
   10584             :                         max_factor *= 3;
   10585             :                         r_max_factor /= 3.0;
   10586             :                 }
   10587             :                 else if (accuracy >= COMBAT_AI_ISNT_AWFUL)
   10588             :                 {
   10589             :                         // slowly improve precision to target, but only if missing
   10590             :                         min_d -= 0.0001 * [self missedShots];
   10591             :                         if (min_d < 0.001)
   10592             :                         {
   10593             :                                 min_d = 0.001;
   10594             :                                 max_factor *= 2;
   10595             :                                 r_max_factor /= 2.0;
   10596             :                         }
   10597             :                 }
   10598             :         }
   10599             : 
   10600             :         d_right         =   dot_product(relPos, v_right);
   10601             :         d_up            =   dot_product(relPos, v_up);
   10602             :         d_forward   =   dot_product(relPos, v_forward); // == cos of angle between v_forward and vector to target
   10603             : 
   10604             :         if (d_forward * reverse > max_cos)   // on_target!
   10605             :         {
   10606             :                 return d_forward;
   10607             :         }
   10608             : 
   10609             :         // begin rule-of-thumb manoeuvres
   10610             :         stick_pitch = 0.0;
   10611             :         stick_roll = 0.0;
   10612             : 
   10613             : 
   10614             :         if ((reverse * d_forward < -0.5) && !pitching_over) // we're going the wrong way!
   10615             :                 pitching_over = YES;
   10616             : 
   10617             :         if (pitching_over)
   10618             :         {
   10619             :                 if (reverse * d_up > 0) // pitch up
   10620             :                         stick_pitch = -max_flight_pitch;
   10621             :                 else
   10622             :                         stick_pitch = max_flight_pitch;
   10623             :                 pitching_over = (reverse * d_forward < 0.707);
   10624             :         }
   10625             : 
   10626             :         // check if we are flying toward the destination..
   10627             :         if ((d_forward < max_cos)||(retreat))        // not on course so we must adjust controls..
   10628             :         {
   10629             :                 if (d_forward < -max_cos)  // hack to avoid just flying away from the destination
   10630             :                 {
   10631             :                         d_up = min_d * 2.0;
   10632             :                 }
   10633             : 
   10634             :                 if (d_up > min_d)
   10635             :                 {
   10636             :                         int factor = sqrt(fabs(d_right) / fabs(min_d));
   10637             :                         if (factor > max_factor)
   10638             :                                 factor = max_factor;
   10639             :                         if (d_right > min_d)
   10640             :                                 stick_roll = - max_flight_roll * r_max_factor * factor; // note#
   10641             :                         if (d_right < -min_d)
   10642             :                                 stick_roll = + max_flight_roll * r_max_factor * factor; // note#
   10643             :                 }
   10644             :                 if (d_up < -min_d)
   10645             :                 {
   10646             :                         int factor = sqrt(fabs(d_right) / fabs(min_d));
   10647             :                         if (factor > max_factor)
   10648             :                                 factor = max_factor;
   10649             :                         if (d_right > min_d)
   10650             :                                 stick_roll = + max_flight_roll * r_max_factor * factor; // note#
   10651             :                         if (d_right < -min_d)
   10652             :                                 stick_roll = - max_flight_roll * r_max_factor * factor; // note#
   10653             :                 }
   10654             : 
   10655             :                 if (stick_roll == 0.0)
   10656             :                 {
   10657             :                         int factor = sqrt(fabs(d_up) / fabs(min_d));
   10658             :                         if (factor > max_factor)
   10659             :                                 factor = max_factor;
   10660             :                         if (d_up > min_d)
   10661             :                                 stick_pitch = - max_flight_pitch * reverse * r_max_factor * factor;
   10662             :                         if (d_up < -min_d)
   10663             :                                 stick_pitch = + max_flight_pitch * reverse * r_max_factor * factor;
   10664             :                 }
   10665             : 
   10666             :                 if (accuracy >= COMBAT_AI_ISNT_AWFUL)
   10667             :                 {
   10668             :                         // don't overshoot target (helps accuracy at low frame rates)
   10669             :                         if (fabs(d_right) < fabs(stick_roll) * delta_t) 
   10670             :                         {
   10671             :                                 stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1);
   10672             :                         }
   10673             :                         if (fabs(d_up) < fabs(stick_pitch) * delta_t) 
   10674             :                         {
   10675             :                                 stick_pitch = fabs(d_up) / delta_t * (stick_pitch<0 ? -0.9 : 0.9);
   10676             :                         }
   10677             :                 }
   10678             : 
   10679             :         }
   10680             :         /*      #  note
   10681             :                 Eric 9-9-2010: Removed the "reverse" variable from the stick_roll calculation. This was mathematical wrong and
   10682             :                 made the ship roll in the wrong direction, preventing the ship to fly away in a straight line from the target.
   10683             :                 This means all the places were a jink was set, this jink never worked correctly. The main reason a ship still
   10684             :                 managed to turn at close range was probably by the fail-safe mechanisme with the "pitching_over" variable.
   10685             :                 The jink was programmed to do nothing within 500 meters of the ship and just fly away in direct line from the target
   10686             :                 in that range. Because of the bug the ships always rolled to the wrong side needed to fly away in direct line
   10687             :                 resulting in making it a difficult target.
   10688             :                 After fixing the bug, the ship realy flew away in direct line during the first 500 meters, making it a easy target
   10689             :                 for the player. All jink settings are retested and changed to give a turning behaviour that felt like the old
   10690             :                 situation, but now more deliberately set.
   10691             :          */
   10692             : 
   10693             :         // end rule-of-thumb manoeuvres
   10694             :         stick_yaw = 0.0;
   10695             :         
   10696             :         [self applySticks:delta_t];
   10697             : 
   10698             :         if (retreat)
   10699             :                 d_forward *= d_forward; // make positive AND decrease granularity
   10700             : 
   10701             :         if (d_forward < 0.0)
   10702             :                 return 0.0;
   10703             : 
   10704             :         if ((!flightRoll)&&(!flightPitch))      // no correction
   10705             :                 return 1.0;
   10706             : 
   10707             :         return d_forward;
   10708             : }
   10709             : 
   10710             : 
   10711             : - (double) trackSideTarget:(double) delta_t :(BOOL) leftside
   10712             : {
   10713             :         Entity* target = [self primaryTarget];
   10714             : 
   10715             :         if (!target)   // leave now!
   10716             :         {
   10717             :                 [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage:
   10718             :                 return 0.0;
   10719             :         }
   10720             : 
   10721             :         if (![self canStillTrackPrimaryTarget])
   10722             :         {
   10723             :                 [self noteLostTargetAndGoIdle]; // NOTE: was AI message: rather than reactToMessage:
   10724             :                 return 0.0;
   10725             :         }
   10726             : 
   10727             : 
   10728             :         if (scanClass == CLASS_MISSILE) // never?
   10729             :                 return [self missileTrackPrimaryTarget: delta_t];
   10730             : 
   10731             :         GLfloat  d_forward, d_up, d_right;
   10732             :         
   10733             :         Vector  relPos = HPVectorToVector(HPvector_subtract([self calculateTargetPosition], position));
   10734             : 
   10735             : 
   10736             :         if (!vector_equal(relPos, kZeroVector))  relPos = vector_normal(relPos);
   10737             :         else  relPos.z = 1.0;
   10738             : 
   10739             : // worse shots with side lasers than fore/aft, in general
   10740             : 
   10741             :         double  max_cos = [self currentAimTolerance];
   10742             : 
   10743             :         stick_roll = 0.0;       //desired roll and pitch
   10744             :         stick_pitch = 0.0;
   10745             :         stick_yaw = 0.0;
   10746             : 
   10747             :         double reverse = (leftside)? -1.0: 1.0;
   10748             : 
   10749             :         double min_d = 0.004;
   10750             :         if (accuracy >= COMBAT_AI_TRACKS_CLOSER) 
   10751             :         {
   10752             :                 min_d = 0.002;
   10753             :         }
   10754             :         int max_factor = 8;
   10755             :         double r_max_factor = 0.125;
   10756             : 
   10757             :         d_right         =   dot_product(relPos, v_right);
   10758             :         d_up            =   dot_product(relPos, v_up);
   10759             :         d_forward   =   dot_product(relPos, v_forward); // == cos of angle between v_forward and vector to target
   10760             : 
   10761             :         if (d_right * reverse > max_cos)     // on_target!
   10762             :         {
   10763             :                 return d_right * reverse;
   10764             :         }
   10765             : 
   10766             :         // begin rule-of-thumb manoeuvres
   10767             :         stick_pitch = 0.0;
   10768             :         stick_roll = 0.0;
   10769             :         stick_yaw = 0.0;
   10770             : 
   10771             :         // check if we are flying toward the destination..
   10772             :         if ((d_right * reverse < max_cos))   // not on course so we must adjust controls..
   10773             :         {
   10774             :                 if (d_right < -max_cos)  // hack to avoid just pointing away from the destination
   10775             :                 {
   10776             :                         d_forward = min_d * 2.0;
   10777             :                 }
   10778             : 
   10779             :                 if (d_forward > min_d)
   10780             :                 {
   10781             :                         int factor = sqrt(fabs(d_up) / fabs(min_d));
   10782             :                         if (factor > max_factor)
   10783             :                                 factor = max_factor;
   10784             :                         if (d_up > min_d)
   10785             :                                 stick_pitch = + max_flight_pitch * r_max_factor * factor; // note#
   10786             :                         if (d_up < -min_d)
   10787             :                                 stick_pitch = - max_flight_pitch * r_max_factor * factor; // note#
   10788             :                 }
   10789             :                 if (d_forward < -min_d)
   10790             :                 {
   10791             :                         int factor = sqrt(fabs(d_up) / fabs(min_d));
   10792             :                         if (factor > max_factor)
   10793             :                                 factor = max_factor;
   10794             :                         if (d_up > min_d)
   10795             :                                 stick_pitch = + max_flight_pitch * r_max_factor * factor; // note#
   10796             :                         if (d_up < -min_d)
   10797             :                                 stick_pitch = - max_flight_pitch * r_max_factor * factor; // note#
   10798             :                 }
   10799             : 
   10800             :                 if (fabs(stick_pitch) == 0.0 || fabs(d_forward) > 0.5)
   10801             :                 {
   10802             :                         stick_pitch = 0.0;
   10803             :                         int factor = sqrt(fabs(d_forward) / fabs(min_d));
   10804             :                         if (factor > max_factor)
   10805             :                                 factor = max_factor;
   10806             :                         if (d_forward > min_d)
   10807             :                                 stick_yaw = - max_flight_yaw * reverse * r_max_factor * factor;
   10808             :                         if (d_forward < -min_d)
   10809             :                         {
   10810             :                                 if (factor < max_factor/2.0) // compensate for forward thrust
   10811             :                                         factor *= 2.0;
   10812             :                                 stick_yaw = + max_flight_yaw * reverse * r_max_factor * factor;
   10813             :                         }
   10814             :                 }
   10815             :         }
   10816             : 
   10817             : 
   10818             :         // end rule-of-thumb manoeuvres
   10819             : 
   10820             :         [self applySticks:delta_t];
   10821             : 
   10822             :         if ((!flightPitch)&&(!flightYaw))       // no correction
   10823             :                 return 1.0;
   10824             : 
   10825             :         return d_right * reverse;
   10826             : }
   10827             : 
   10828             : 
   10829             : 
   10830             : - (double) missileTrackPrimaryTarget:(double) delta_t
   10831             : {
   10832             :         Vector  relPos;
   10833             :         GLfloat  d_forward, d_up, d_right;
   10834             :         ShipEntity  *target = [self primaryTarget];
   10835             :         BOOL    inPursuit = YES;
   10836             : 
   10837             :         if (!target || ![target isShip])   // leave now!
   10838             :                 return 0.0;
   10839             : 
   10840             :         double  damping = 0.5 * delta_t;
   10841             : 
   10842             :         stick_roll = 0.0;       //desired roll and pitch
   10843             :         stick_pitch = 0.0;
   10844             :         stick_yaw = 0.0;
   10845             : 
   10846             :         relPos = [self vectorTo:target];
   10847             :         
   10848             :         // Adjust missile course by taking into account target's velocity and missile
   10849             :         // accuracy. Modification on original code contributed by Cmdr James.
   10850             : 
   10851             :         float missileSpeed = (float)[self speed];
   10852             : 
   10853             :         // Avoid getting ourselves in a divide by zero situation by setting a missileSpeed
   10854             :         // low threshold. Arbitrarily chosen 0.01, since it seems to work quite well.
   10855             :         // Missile accuracy is already clamped within the 0.0 to 10.0 range at initialization,
   10856             :         // but doing these calculations every frame when accuracy equals 0.0 just wastes cycles.
   10857             :         if (missileSpeed > 0.01f && accuracy > 0.0f)
   10858             :         {
   10859             :                 inPursuit = (dot_product([target forwardVector], v_forward) > 0.0f);
   10860             :                 if (inPursuit)
   10861             :                 {
   10862             :                         Vector leading = [target velocity]; 
   10863             :                         float lead = magnitude(relPos) / missileSpeed; 
   10864             :                         
   10865             :                         // Adjust where we are going to take into account target's velocity.
   10866             :                         // Use accuracy value to determine how well missile will track target.
   10867             :                         relPos.x += (lead * leading.x * (accuracy / 10.0f)); 
   10868             :                         relPos.y += (lead * leading.y * (accuracy / 10.0f)); 
   10869             :                         relPos.z += (lead * leading.z * (accuracy / 10.0f));
   10870             :                 }
   10871             :         }
   10872             : 
   10873             :         if (!vector_equal(relPos, kZeroVector))  relPos = vector_normal(relPos);
   10874             :         else  relPos.z = 1.0;
   10875             : 
   10876             :         d_right         =   dot_product(relPos, v_right);               // = cosine of angle between angle to target and v_right
   10877             :         d_up            =   dot_product(relPos, v_up);          // = cosine of angle between angle to target and v_up
   10878             :         d_forward   =   dot_product(relPos, v_forward); // = cosine of angle between angle to target and v_forward
   10879             : 
   10880             :         // begin rule-of-thumb manoeuvres
   10881             : 
   10882             :         stick_roll = 0.0;
   10883             : 
   10884             :         if (pitching_over)
   10885             :                 pitching_over = (stick_pitch != 0.0);
   10886             : 
   10887             :         if ((d_forward < -pitch_tolerance) && (!pitching_over))
   10888             :         {
   10889             :                 pitching_over = YES;
   10890             :                 if (d_up >= 0)
   10891             :                         stick_pitch = -max_flight_pitch;
   10892             :                 if (d_up < 0)
   10893             :                         stick_pitch = max_flight_pitch;
   10894             :         }
   10895             : 
   10896             :         if (pitching_over)
   10897             :         {
   10898             :                 pitching_over = (d_forward < 0.5);
   10899             :         }
   10900             :         else
   10901             :         {
   10902             :                 stick_pitch = -max_flight_pitch * d_up;
   10903             :                 stick_roll = -max_flight_roll * d_right;
   10904             :         }
   10905             : 
   10906             :         // end rule-of-thumb manoeuvres
   10907             : 
   10908             :         // apply damping
   10909             :         if (flightRoll < 0)
   10910             :                 flightRoll += (flightRoll < -damping) ? damping : -flightRoll;
   10911             :         if (flightRoll > 0)
   10912             :                 flightRoll -= (flightRoll > damping) ? damping : flightRoll;
   10913             :         if (flightPitch < 0)
   10914             :                 flightPitch += (flightPitch < -damping) ? damping : -flightPitch;
   10915             :         if (flightPitch > 0)
   10916             :                 flightPitch -= (flightPitch > damping) ? damping : flightPitch;
   10917             : 
   10918             :         
   10919             :         [self applySticks:delta_t];
   10920             : 
   10921             :         //
   10922             :         //  return target confidence 0.0 .. 1.0
   10923             :         //
   10924             :         if (d_forward < 0.0)
   10925             :                 return 0.0;
   10926             :         return d_forward;
   10927             : }
   10928             : 
   10929             : 
   10930             : - (double) trackDestination:(double) delta_t :(BOOL) retreat
   10931             : {
   10932             :         Vector  relPos;
   10933             :         GLfloat  d_forward, d_up, d_right;
   10934             : 
   10935             :         BOOL    we_are_docking = (nil != dockingInstructions);
   10936             : 
   10937             :         stick_roll = 0.0;       //desired roll and pitch
   10938             :         stick_pitch = 0.0;
   10939             :         stick_yaw = 0.0;
   10940             : 
   10941             :         double reverse = 1.0;
   10942             :         double reversePlayer = 1.0;
   10943             : 
   10944             :         double min_d = 0.004;
   10945             :         double max_cos = MAX_COS;  // should match default value of max_cos in behaviour_fly_to_destination!
   10946             :         double precision = we_are_docking ? 0.25 : 0.9025; // lower values force a direction closer to the target. (resp. 50% and 95% within range)
   10947             : 
   10948             :         if (retreat)
   10949             :                 reverse = -reverse;
   10950             : 
   10951             :         if (isPlayer)
   10952             :         {
   10953             :                 reverse = -reverse;
   10954             :                 reversePlayer = -1;
   10955             :         }
   10956             : 
   10957             :         relPos = HPVectorToVector(HPvector_subtract(_destination, position));
   10958             :         double range2 = magnitude2(relPos);
   10959             :         double desired_range2 = desired_range*desired_range;
   10960             :         
   10961             :         /*      2009-7-18 Eric: We need to aim well inide the desired_range sphere round the target and not at the surface of the sphere. 
   10962             :                 Because of the framerate most ships normally overshoot the target and they end up flying clearly on a path
   10963             :                 through the sphere. Those ships give no problems, but ships with a very low turnrate will aim close to the surface and will than
   10964             :                 have large trouble with reaching their destination. When those ships enter the slowdown range, they have almost no speed vector
   10965             :                 in the direction of the target. I now used 95% of desired_range to aim at, but a smaller value might even be better. 
   10966             :         */
   10967             :         if (range2 > desired_range2) 
   10968             :         {
   10969             :                 max_cos = sqrt(1 - precision * desired_range2/range2);  // Head for a point within 95% of desired_range.
   10970             :                 if (max_cos >= 0.99999)
   10971             :                 {
   10972             :                         max_cos = 0.99999;
   10973             :                 }
   10974             :         }
   10975             : 
   10976             :         if (!vector_equal(relPos, kZeroVector))  relPos = vector_normal(relPos);
   10977             :         else  relPos.z = 1.0;
   10978             : 
   10979             :         d_right         =   dot_product(relPos, v_right);
   10980             :         d_up            =   dot_product(relPos, v_up);
   10981             :         d_forward   =   dot_product(relPos, v_forward); // == cos of angle between v_forward and vector to target
   10982             : 
   10983             :         // begin rule-of-thumb manoeuvres
   10984             :         stick_pitch = 0.0;
   10985             :         stick_roll = 0.0;
   10986             :         
   10987             :         // pitching_over is currently only set in behaviour_formation_form_up, for escorts and in avoidCollision.
   10988             :         // This allows for immediate pitch corrections instead of first waiting untill roll has completed.
   10989             :         if (pitching_over)
   10990             :         {
   10991             :                 if (reverse * d_up > 0) // pitch up
   10992             :                         stick_pitch = -max_flight_pitch;
   10993             :                 else
   10994             :                         stick_pitch = max_flight_pitch;
   10995             :                 pitching_over = (reverse * d_forward < 0.707);
   10996             :         }
   10997             : 
   10998             :         // check if we are flying toward (or away from) the destination..
   10999             :         if ((d_forward < max_cos)||(retreat))        // not on course so we must adjust controls..
   11000             :         {
   11001             : 
   11002             :                 if (d_forward <= -max_cos || (retreat && d_forward >= max_cos))  // hack to avoid just flying away from the destination
   11003             :                 {
   11004             :                         d_up = min_d * 2.0;
   11005             :                 } 
   11006             : 
   11007             :                 if (d_up > min_d)
   11008             :                 {
   11009             :                         int factor = sqrt(fabs(d_right) / fabs(min_d));
   11010             :                         if (factor > 8)
   11011             :                                 factor = 8;
   11012             :                         if (d_right > min_d)
   11013             :                                 stick_roll = - max_flight_roll * reversePlayer * 0.125 * factor;  // only reverse sign for the player;
   11014             :                         if (d_right < -min_d)
   11015             :                                 stick_roll = + max_flight_roll * reversePlayer * 0.125 * factor;
   11016             :                         if (fabs(d_right) < fabs(stick_roll) * delta_t) 
   11017             :                                 stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1); // don't overshoot heading
   11018             :                 }
   11019             : 
   11020             :                 if (d_up < -min_d)
   11021             :                 {
   11022             :                         int factor = sqrt(fabs(d_right) / fabs(min_d));
   11023             :                         if (factor > 8)
   11024             :                                 factor = 8;
   11025             :                         if (d_right > min_d)
   11026             :                                 stick_roll = + max_flight_roll * reversePlayer * 0.125 * factor;  // only reverse sign for the player;
   11027             :                         if (d_right < -min_d)
   11028             :                                 stick_roll = - max_flight_roll * reversePlayer * 0.125 * factor;
   11029             :                         if (fabs(d_right) < fabs(stick_roll) * delta_t) 
   11030             :                                 stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1); // don't overshoot heading
   11031             :                 }
   11032             : 
   11033             :                 if (stick_roll == 0.0)
   11034             :                 {
   11035             :                         int factor = sqrt(fabs(d_up) / fabs(min_d));
   11036             :                         if (factor > 8)
   11037             :                                 factor = 8;
   11038             :                         if (d_up > min_d)
   11039             :                                 stick_pitch = - max_flight_pitch * reverse * 0.125 * factor;  //pitch_pitch * reverse;
   11040             :                         if (d_up < -min_d)
   11041             :                                 stick_pitch = + max_flight_pitch * reverse * 0.125 * factor;
   11042             :                         if (fabs(d_up) < fabs(stick_pitch) * delta_t) 
   11043             :                                 stick_pitch = fabs(d_up) / delta_t * (stick_pitch<0 ? -1 : 1); // don't overshoot heading
   11044             :                 }
   11045             : 
   11046             :                 if (stick_pitch == 0.0)
   11047             :                 {
   11048             :                         // not sufficiently on course yet, but min_d is too high
   11049             :                         // turn anyway slightly to adjust
   11050             :                         stick_pitch = 0.01;
   11051             :                 }
   11052             :         }
   11053             : 
   11054             :         if (we_are_docking && docking_match_rotation && (d_forward > max_cos))
   11055             :         {
   11056             :                 /* we are docking and need to consider the rotation/orientation of the docking port */
   11057             :                 StationEntity* station_for_docking = (StationEntity*)[self targetStation];
   11058             : 
   11059             :                 if ((station_for_docking)&&(station_for_docking->isStation))
   11060             :                 {
   11061             :                         stick_roll = [self rollToMatchUp:[station_for_docking portUpVectorForShip:self] rotating:[station_for_docking flightRoll]];
   11062             :                 }
   11063             :         }
   11064             : 
   11065             :         // end rule-of-thumb manoeuvres
   11066             : 
   11067             :         [self applySticks:delta_t];
   11068             : 
   11069             :         if (retreat)
   11070             :                 d_forward *= d_forward; // make positive AND decrease granularity
   11071             : 
   11072             :         if (d_forward < 0.0)
   11073             :                 return 0.0;
   11074             : 
   11075             :         if ((!flightRoll)&&(!flightPitch))      // no correction
   11076             :                 return 1.0;
   11077             : 
   11078             :         return d_forward;
   11079             : }
   11080             : 
   11081             : 
   11082             : - (GLfloat) rollToMatchUp:(Vector)up_vec rotating:(GLfloat)match_roll
   11083             : {
   11084             :         GLfloat cosTheta = dot_product(up_vec, v_up);   // == cos of angle between up vectors
   11085             :         GLfloat sinTheta = dot_product(up_vec, v_right);
   11086             : 
   11087             :         if (!isPlayer)
   11088             :         {
   11089             :                 match_roll = -match_roll;       // make necessary corrections for a different viewpoint
   11090             :                 sinTheta = -sinTheta;
   11091             :         }
   11092             : 
   11093             :         if (cosTheta < 0.0f)
   11094             :         {
   11095             :                 cosTheta = -cosTheta;
   11096             :                 sinTheta = -sinTheta;
   11097             :         }
   11098             : 
   11099             :         if (sinTheta > 0.0f)
   11100             :         {
   11101             :                 // increase roll rate
   11102             :                 return cosTheta * cosTheta * match_roll + sinTheta * sinTheta * max_flight_roll;
   11103             :         }
   11104             :         else
   11105             :         {
   11106             :                 // decrease roll rate
   11107             :                 return cosTheta * cosTheta * match_roll - sinTheta * sinTheta * max_flight_roll;
   11108             :         }
   11109             : }
   11110             : 
   11111             : 
   11112             : - (GLfloat) rangeToDestination
   11113             : {
   11114             :         return HPdistance(position, _destination);
   11115             : }
   11116             : 
   11117             : 
   11118             : - (NSArray *) collisionExceptions
   11119             : {
   11120             :         if (_collisionExceptions == nil)
   11121             :         {
   11122             :                 return [NSArray array];
   11123             :         }
   11124             :         return [_collisionExceptions allObjects];
   11125             : }
   11126             : 
   11127             : 
   11128             : - (void) addCollisionException:(ShipEntity *)ship
   11129             : {
   11130             :         if (_collisionExceptions == nil)
   11131             :         {
   11132             :                 // Allocate lazily for the benefit of the ships that never need this.
   11133             :                 _collisionExceptions = [[OOWeakSet alloc] init];
   11134             :         }
   11135             :         [_collisionExceptions addObject:ship];
   11136             : }
   11137             : 
   11138             : 
   11139             : - (void) removeCollisionException:(ShipEntity *)ship
   11140             : {
   11141             :         if (_collisionExceptions != nil)
   11142             :         {
   11143             :                 [_collisionExceptions removeObject:ship];
   11144             :         }
   11145             : }
   11146             : 
   11147             : 
   11148             : - (BOOL) collisionExceptedFor:(ShipEntity *)ship
   11149             : {
   11150             :         if (_collisionExceptions == nil)
   11151             :         {
   11152             :                 return NO;
   11153             :         }
   11154             :         return [_collisionExceptions containsObject:ship];
   11155             : }
   11156             : 
   11157             : 
   11158             : 
   11159             : - (NSUInteger) defenseTargetCount
   11160             : {
   11161             :         return [_defenseTargets count];
   11162             : }
   11163             : 
   11164             : 
   11165             : - (NSArray *) allDefenseTargets
   11166             : {
   11167             :         return [_defenseTargets allObjects];
   11168             : }
   11169             : 
   11170             : 
   11171             : - (NSEnumerator *) defenseTargetEnumerator
   11172             : {
   11173             :         return [_defenseTargets objectEnumerator];
   11174             : }
   11175             : 
   11176             : 
   11177             : - (BOOL) addDefenseTarget:(Entity *)target
   11178             : {
   11179             :         if ([self defenseTargetCount] >= MAX_TARGETS)
   11180             :         {
   11181             :                 return NO;
   11182             :         }
   11183             :         // primary target can be a wormhole, defense targets shouldn't be
   11184             :         if (target == nil || [self isDefenseTarget:target] || ![target isShip])
   11185             :         {
   11186             :                 return NO;
   11187             :         }
   11188             :         if (_defenseTargets == nil)
   11189             :         {
   11190             :                 // Allocate lazily for the benefit of the ships that never get in fights.
   11191             :                 _defenseTargets = [[OOWeakSet alloc] init];
   11192             :         }
   11193             :         
   11194             :         [_defenseTargets addObject:target];
   11195             :         return YES;
   11196             : }
   11197             : 
   11198             : 
   11199             : - (void) validateDefenseTargets
   11200             : {
   11201             :         if (_defenseTargets == nil)
   11202             :         {
   11203             :                 return;
   11204             :         }
   11205             :         // get enumerator from array as we'll be modifying original during enumeration
   11206             :         NSEnumerator *defTargets = [[self allDefenseTargets] objectEnumerator];
   11207             :         Entity *target = nil;
   11208             :         while ((target = [[defTargets nextObject] weakRefUnderlyingObject]))
   11209             :         {
   11210             :                 if ([target status] == STATUS_DEAD)
   11211             :                 {
   11212             :                         [self removeDefenseTarget:target];
   11213             :                 }
   11214             :         }
   11215             : }
   11216             : 
   11217             : 
   11218             : - (BOOL) isDefenseTarget:(Entity *)target
   11219             : {
   11220             :         return [_defenseTargets containsObject:target];
   11221             : }
   11222             : 
   11223             : 
   11224             : // exposed to AI (as alias of clearDefenseTargets)
   11225             : - (void) removeAllDefenseTargets
   11226             : {
   11227             :         [_defenseTargets removeAllObjects];
   11228             : }
   11229             : 
   11230             : 
   11231             : - (void) removeDefenseTarget:(Entity *)target
   11232             : {
   11233             :         [_defenseTargets removeObject:target];
   11234             : }
   11235             : 
   11236             : 
   11237             : - (double) rangeToPrimaryTarget
   11238             : {
   11239             :         return [self rangeToSecondaryTarget:[self primaryTarget]];
   11240             : }
   11241             : 
   11242             : 
   11243             : - (double) rangeToSecondaryTarget:(Entity *)target
   11244             : {
   11245             :         double dist;
   11246             :         Vector delta;
   11247             :         if (target == nil)   // leave now!
   11248             :                 return 0.0;
   11249             :         delta = HPVectorToVector(HPvector_subtract(target->position, position));
   11250             :         dist = magnitude(delta);
   11251             :         dist -= target->collision_radius;
   11252             :         dist -= collision_radius;
   11253             :         return dist;
   11254             : }
   11255             : 
   11256             : 
   11257             : - (double) approachAspectToPrimaryTarget
   11258             : {
   11259             :         Vector delta;
   11260             :         Entity  *target = [self primaryTarget];
   11261             :         if (target == nil || ![target isShip])   // leave now!
   11262             :         {
   11263             :                 return 0.0;
   11264             :         }
   11265             :         ShipEntity  *ship_target = (ShipEntity *)target;
   11266             : 
   11267             :         delta = HPVectorToVector(HPvector_subtract(position, target->position));
   11268             :         
   11269             :         return dot_product(vector_normal(delta), ship_target->v_forward);
   11270             : }
   11271             : 
   11272             : 
   11273             : - (BOOL) hasProximityAlertIgnoringTarget:(BOOL)ignore_target
   11274             : {
   11275             :         if (([self proximityAlert] != nil)&&(!ignore_target || ([self proximityAlert] != [self primaryTarget])))
   11276             :         {
   11277             :                 return YES;
   11278             :         }
   11279             :         return NO;
   11280             : }
   11281             : 
   11282             : 
   11283             : // lower is better. Defines angular size of circle in which ship
   11284             : // thinks is on target
   11285             : - (GLfloat) currentAimTolerance
   11286             : {
   11287             :         GLfloat basic_aim = aim_tolerance;
   11288             :         GLfloat best_cos = 0.99999; // ~45m in 10km (track won't go better than 40)
   11289             :         if (accuracy >= COMBAT_AI_ISNT_AWFUL)
   11290             :         { 
   11291             :                 // better general targeting
   11292             :                 best_cos = 0.999999; // ~14m in 10km (track won't go better than 10)
   11293             :                 // if missing, aim better!
   11294             :                 basic_aim /= 1.0 + ((GLfloat)[self missedShots] / 4.0);
   11295             :         }
   11296             :         if (accuracy >= COMBAT_AI_TRACKS_CLOSER)
   11297             :         { 
   11298             :                 // deadly shots
   11299             :                 best_cos = 0.9999999; // ~4m in 10km (track won't go better than 4)
   11300             :                 // and start with extremely good aim circle
   11301             :                 basic_aim /= 5.0;
   11302             :         }
   11303             :         if (currentWeaponFacing == WEAPON_FACING_AFT && accuracy < COMBAT_AI_ISNT_AWFUL)
   11304             :         { // bad shots with aft lasers
   11305             :                 basic_aim *= 1.3;
   11306             :         }
   11307             :         else if (currentWeaponFacing == WEAPON_FACING_PORT || currentWeaponFacing == WEAPON_FACING_STARBOARD)
   11308             :         { // everyone a bit worse with side lasers
   11309             :                 if (accuracy < COMBAT_AI_ISNT_AWFUL) 
   11310             :                 { // especially these
   11311             :                         basic_aim *= 1.3 + randf();
   11312             :                 }
   11313             :                 else
   11314             :                 {
   11315             :                         basic_aim *= 1.3;
   11316             :                 }
   11317             :         }
   11318             :         // only apply glare if ship is not shadowed
   11319             :         if (isSunlit) {
   11320             :                 OOSunEntity *sun = [UNIVERSE sun];
   11321             :                 if (sun)
   11322             :                 {
   11323             :                         GLfloat sunGlareAngularSize = atan([sun radius]/HPdistance([self position], [sun position])) * SUN_GLARE_MULT_FACTOR + (SUN_GLARE_ADD_FACTOR);
   11324             :                         GLfloat glareLevel = [self lookingAtSunWithThresholdAngleCos:cos(sunGlareAngularSize)] * (1.0f - [self sunGlareFilter]);
   11325             :                         if (glareLevel > 0.1f)
   11326             :                         {
   11327             :                                 // looking towards sun can seriously mess up aim (glareLevel 0..1)
   11328             :                                 basic_aim *= (1.0 + glareLevel*3.0);
   11329             : //                              OOLog(@"aim.debug",@"Sun glare affecting aim: %f for %@",glareLevel,self);
   11330             :                                 if (glareLevel > 0.5f)
   11331             :                                 {
   11332             :                                         // strong glare makes precise targeting impossible
   11333             :                                         best_cos = 0.99999;
   11334             :                                 }
   11335             :                         }
   11336             :                 }
   11337             :         }
   11338             : 
   11339             : 
   11340             :         GLfloat max_cos = sqrt(1-(basic_aim * basic_aim / 100000000.0));
   11341             : 
   11342             :         if (max_cos < best_cos)
   11343             :         {
   11344             :                 return max_cos;
   11345             :         }
   11346             :         return best_cos;
   11347             : }
   11348             : 
   11349             : 
   11350             : // much simpler than player version
   11351             : - (GLfloat) lookingAtSunWithThresholdAngleCos:(GLfloat) thresholdAngleCos
   11352             : {
   11353             :         OOSunEntity     *sun = [UNIVERSE sun];
   11354             :         GLfloat measuredCos = 999.0f, measuredCosAbs;
   11355             :         GLfloat sunBrightness = 0.0f;
   11356             :         Vector relativePosition, unitRelativePosition;
   11357             :         
   11358             :         if (EXPECT_NOT(!sun))  return 0.0f;
   11359             :         
   11360             :         relativePosition = HPVectorToVector(HPvector_subtract([self position], [sun position]));
   11361             :         unitRelativePosition = vector_normal_or_zbasis(relativePosition);
   11362             :         switch (currentWeaponFacing)
   11363             :         {
   11364             :                 case WEAPON_FACING_FORWARD:
   11365             :                         measuredCos = -dot_product(unitRelativePosition, v_forward);
   11366             :                         break;
   11367             :                 case WEAPON_FACING_AFT:
   11368             :                         measuredCos = +dot_product(unitRelativePosition, v_forward);
   11369             :                         break;
   11370             :                 case WEAPON_FACING_PORT:
   11371             :                         measuredCos = +dot_product(unitRelativePosition, v_right);
   11372             :                         break;
   11373             :                 case WEAPON_FACING_STARBOARD:
   11374             :                         measuredCos = -dot_product(unitRelativePosition, v_right);
   11375             :                         break;
   11376             :                 default:
   11377             :                         break;
   11378             :         }
   11379             :         measuredCosAbs = fabs(measuredCos);
   11380             :         if (thresholdAngleCos <= measuredCosAbs && measuredCosAbs <= 1.0f)        // angle from viewpoint to sun <= desired threshold
   11381             :         {
   11382             :                 sunBrightness =  (measuredCos - thresholdAngleCos) / (1.0f - thresholdAngleCos);
   11383             :                 if (sunBrightness < 0.0f)  sunBrightness = 0.0f;
   11384             :         }
   11385             :         return sunBrightness * sunBrightness * sunBrightness;
   11386             : }
   11387             : 
   11388             : 
   11389             : - (BOOL) onTarget:(OOWeaponFacing)direction withWeapon:(OOWeaponType)weapon_type
   11390             : {
   11391             :         // initialize dq to a value that would normally return NO; dq is handled inside the defaultless switch(direction) statement
   11392             :         // and should alaways be recalculated anyway. Initialization here needed to silence compiler warning - Nikos 20120526
   11393             :         GLfloat dq = -1.0f;
   11394             :         GLfloat d2, radius, astq;
   11395             :         Vector rel_pos, urp;
   11396             :         if ([weapon_type isTurretLaser])
   11397             :         {
   11398             :                 return YES;
   11399             :         }
   11400             :         
   11401             :         Entity  *target = [self primaryTarget];
   11402             :         if (target == nil)  return NO;
   11403             :         if ([target status] == STATUS_DEAD)  return NO;
   11404             :         
   11405             :         if (isSunlit && (target->isSunlit == NO) && (randf() < 0.75))
   11406             :         {
   11407             :                 return NO;      // 3/4 of the time you can't see from a lit place into a darker place
   11408             :         }
   11409             :         radius = target->collision_radius;
   11410             :         rel_pos = HPVectorToVector(HPvector_subtract([self calculateTargetPosition], position));
   11411             :         d2 = magnitude2(rel_pos);
   11412             :         urp = vector_normal_or_zbasis(rel_pos);
   11413             :         
   11414             :         switch (direction)
   11415             :         {
   11416             :                 case WEAPON_FACING_FORWARD:
   11417             :                         dq = +dot_product(urp, v_forward);              // cosine of angle between v_forward and unit relative position
   11418             :                         break;
   11419             :                         
   11420             :                 case WEAPON_FACING_AFT:
   11421             :                         dq = -dot_product(urp, v_forward);              // cosine of angle between v_forward and unit relative position
   11422             :                         break;
   11423             :                         
   11424             :                 case WEAPON_FACING_PORT:
   11425             :                         dq = -dot_product(urp, v_right);                // cosine of angle between v_right and unit relative position
   11426             :                         break;
   11427             :                         
   11428             :                 case WEAPON_FACING_STARBOARD:
   11429             :                         dq = +dot_product(urp, v_right);                // cosine of angle between v_right and unit relative position
   11430             :                         break;
   11431             :                         
   11432             :                 case WEAPON_FACING_NONE:
   11433             :                         break;
   11434             :         }
   11435             : 
   11436             :         if (dq < 0.0)  return NO;
   11437             :         
   11438             :         GLfloat aim = [self currentAimTolerance];
   11439             :         if (dq > aim*aim) return YES;
   11440             : 
   11441             :         // cosine of 1/3 of half angle subtended by target (mostly they'll
   11442             :         // fire sooner anyway due to currentAimTolerance, but this should
   11443             :         // almost always be a solid hit)
   11444             :         astq = sqrt(1.0 - radius * radius / (d2 * 9));  
   11445             : 
   11446             :         return (fabs(dq) >= astq);
   11447             : }
   11448             : 
   11449             : 
   11450           0 : - (BOOL) fireWeapon:(OOWeaponType)weapon_type direction:(OOWeaponFacing)direction range:(double)range
   11451             : {
   11452             :         weapon_temp = 0.0;
   11453             :         switch (direction)
   11454             :         {
   11455             :                 case WEAPON_FACING_FORWARD:
   11456             :                         weapon_temp = forward_weapon_temp;
   11457             :                         break;
   11458             :                         
   11459             :                 case WEAPON_FACING_AFT:
   11460             :                         weapon_temp = aft_weapon_temp;
   11461             :                         break;
   11462             :                         
   11463             :                 case WEAPON_FACING_PORT:
   11464             :                         weapon_temp = port_weapon_temp;
   11465             :                         break;
   11466             :                         
   11467             :                 case WEAPON_FACING_STARBOARD:
   11468             :                         weapon_temp = starboard_weapon_temp;
   11469             :                         break;
   11470             :                         
   11471             :                 case WEAPON_FACING_NONE:
   11472             :                         break;
   11473             :         }
   11474             :         if (weapon_temp / NPC_MAX_WEAPON_TEMP >= WEAPON_COOLING_CUTOUT) return NO;
   11475             : 
   11476             :         NSUInteger multiplier = 1;
   11477             :         if (_multiplyWeapons)
   11478             :         {
   11479             :                 // multiple fitted
   11480             :                 multiplier = [[self laserPortOffset:direction] count];
   11481             :         }
   11482             : 
   11483             :         if (energy <= weapon_energy_use * multiplier) return NO;
   11484             :         if ([self shotTime] < weapon_recharge_rate)  return NO;
   11485             :         if (![weapon_type isTurretLaser])
   11486             :         { // thargoid laser may just pick secondary target in this case
   11487             :                 if (range > randf() * weaponRange * (accuracy+7.5))  return NO;
   11488             :                 if (range > weaponRange)  return NO;
   11489             :         }
   11490             :         if (![self onTarget:direction withWeapon:weapon_type])  return NO;
   11491             :         
   11492             :         BOOL fired = NO;
   11493             :         if (!isWeaponNone(weapon_type))
   11494             :         {
   11495             :                 if ([weapon_type isTurretLaser])
   11496             :                 {
   11497             :                         [self fireDirectLaserShot:range];
   11498             :                         fired = YES;
   11499             :                 }
   11500             :                 else
   11501             :                 {
   11502             :                         [self fireLaserShotInDirection:direction weaponIdentifier:[weapon_type identifier]];
   11503             :                         fired = YES;
   11504             :                 }
   11505             :         }
   11506             : 
   11507             :         if (fired)
   11508             :         {
   11509             :                 energy -= weapon_energy_use * multiplier;
   11510             :                 switch (direction)
   11511             :                 {
   11512             :                         case WEAPON_FACING_FORWARD:
   11513             :                                 forward_weapon_temp += weapon_shot_temperature * multiplier;
   11514             :                                 break;
   11515             :                                 
   11516             :                         case WEAPON_FACING_AFT:
   11517             :                                 aft_weapon_temp += weapon_shot_temperature * multiplier;
   11518             :                                 break;
   11519             :                                 
   11520             :                         case WEAPON_FACING_PORT:
   11521             :                                 port_weapon_temp += weapon_shot_temperature * multiplier;
   11522             :                                 break;
   11523             :                                 
   11524             :                         case WEAPON_FACING_STARBOARD:
   11525             :                                 starboard_weapon_temp += weapon_shot_temperature * multiplier;
   11526             :                                 break;
   11527             :                                 
   11528             :                         case WEAPON_FACING_NONE:
   11529             :                                 break;
   11530             :                 }
   11531             :         }
   11532             :         
   11533             :         if (direction == WEAPON_FACING_FORWARD)
   11534             :         {
   11535             :                 //can we fire lasers from our subentities?
   11536             :                 NSEnumerator    *subEnum = nil;
   11537             :                 ShipEntity              *se = nil;
   11538             :                 for (subEnum = [self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
   11539             :                 {
   11540             :                         if ([se fireSubentityLaserShot:range])
   11541             :                         {
   11542             :                                 fired = YES;
   11543             :                         }
   11544             :                 }
   11545             :         }
   11546             :         
   11547             :         if (fired && cloaking_device_active && cloakPassive)
   11548             :         {
   11549             :                 [self deactivateCloakingDevice];
   11550             :         }
   11551             :         
   11552             :         return fired;
   11553             : }
   11554             : 
   11555             : 
   11556             : - (BOOL) fireMainWeapon:(double)range
   11557             : {
   11558             :         // set the values from forward_weapon_type.
   11559             :         // OXPs can override the default front laser energy damage.
   11560             :         currentWeaponFacing = WEAPON_FACING_FORWARD;
   11561             :         [self setWeaponDataFromType:forward_weapon_type];
   11562             : 
   11563             : //  weapon damage override no longer effective
   11564             : //      weapon_damage = weapon_damage_override;
   11565             :         
   11566             :         BOOL result = [self fireWeapon:forward_weapon_type direction:WEAPON_FACING_FORWARD range:range];
   11567             :         if (isWeaponNone(forward_weapon_type))
   11568             :         {
   11569             :                 // need to check subentities to avoid AI oddities
   11570             :                 // will already have fired them by now, though
   11571             :                 NSEnumerator    *subEnum = [self shipSubEntityEnumerator];
   11572             :                 ShipEntity              *se = nil;
   11573             :                 OOWeaponType                    weapon_type = nil;
   11574             :                 BOOL hasTurrets = NO;
   11575             :                 while (isWeaponNone(weapon_type) && (se = [subEnum nextObject]))
   11576             :                 {
   11577             :                         weapon_type = se->forward_weapon_type;
   11578             :                         weapon_temp = se->forward_weapon_temp;
   11579             :                         if (se->behaviour == BEHAVIOUR_TRACK_AS_TURRET)
   11580             :                         {
   11581             :                                 hasTurrets = YES;
   11582             :                         }
   11583             :                 }
   11584             :                 if (isWeaponNone(weapon_type) && hasTurrets)
   11585             :                 { /* no forward weapon but has turrets, so set up range calculations accordingly
   11586             :                      note: this was hard-coded to 10000.0, although turrets have a notably 
   11587             :                      shorter range. We are using a multiplier of 1.667 in order to not change
   11588             :                      something that already works, but probably it would be best to use
   11589             :                      TURRET_SHOT_RANGE * COMBAT_WEAPON_RANGE_FACTOR here
   11590             :                   */
   11591             :                          weaponRange = TURRET_SHOT_RANGE * 1.667;
   11592             :                 }
   11593             :                 else
   11594             :                 {
   11595             :                         [self setWeaponDataFromType:weapon_type];
   11596             :                 }
   11597             :         }
   11598             :         return result;
   11599             : }
   11600             : 
   11601             : 
   11602             : - (BOOL) fireAftWeapon:(double)range
   11603             : {
   11604             :         // set the values from aft_weapon_type.
   11605             :         currentWeaponFacing = WEAPON_FACING_AFT;
   11606             :         [self setWeaponDataFromType:aft_weapon_type];
   11607             :         
   11608             :         return [self fireWeapon:aft_weapon_type direction:WEAPON_FACING_AFT range:range];
   11609             : }
   11610             : 
   11611             : 
   11612             : - (BOOL) firePortWeapon:(double)range
   11613             : {
   11614             :         // set the values from port_weapon_type.
   11615             :         currentWeaponFacing = WEAPON_FACING_PORT;
   11616             :         [self setWeaponDataFromType:port_weapon_type];
   11617             :         
   11618             :         return [self fireWeapon:port_weapon_type direction:WEAPON_FACING_PORT range:range];
   11619             : }
   11620             : 
   11621             : 
   11622             : - (BOOL) fireStarboardWeapon:(double)range
   11623             : {
   11624             :         // set the values from starboard_weapon_type.
   11625             :         currentWeaponFacing = WEAPON_FACING_STARBOARD;
   11626             :         [self setWeaponDataFromType:starboard_weapon_type];
   11627             :         
   11628             :         return [self fireWeapon:starboard_weapon_type direction:WEAPON_FACING_STARBOARD range:range];
   11629             : }
   11630             : 
   11631             : 
   11632             : - (OOTimeDelta) shotTime
   11633             : {
   11634             :         return shot_time;
   11635             : }
   11636             : 
   11637             : 
   11638             : - (void) resetShotTime
   11639             : {
   11640             :         shot_time = 0.0;
   11641             : }
   11642             : 
   11643             : 
   11644             : - (BOOL) fireTurretCannon:(double) range
   11645             : {
   11646             :         if ([self shotTime] < weapon_recharge_rate)
   11647             :                 return NO;
   11648             :         if (range > weaponRange * 1.01) // 1% more than max range - open up just slightly early
   11649             :                 return NO;
   11650             :         ShipEntity *root = [self rootShipEntity];
   11651             :         if ([root isPlayer] && ![PLAYER weaponsOnline])
   11652             :                 return NO;
   11653             : 
   11654             :         if ([root isCloaked] && [root cloakPassive])
   11655             :         {
   11656             :                 // can't fire turrets while cloaked
   11657             :                 return NO;
   11658             :         }
   11659             : 
   11660             :         Vector          vel;    
   11661             :         HPVector                origin = [self position];
   11662             : 
   11663             :         Entity          *last = nil;
   11664             :         Entity          *father = [self parentEntity];
   11665             :         OOMatrix        r_mat;
   11666             : 
   11667             :         vel = vector_forward_from_quaternion(orientation);              // Facing
   11668             :         // adjust velocity and position vectors to absolute coordinates
   11669             :         while ((father)&&(father != last) && (father != NO_TARGET))
   11670             :         {
   11671             :                 r_mat = [father drawRotationMatrix];
   11672             :                 origin = HPvector_add(OOHPVectorMultiplyMatrix(origin, r_mat), [father position]);
   11673             :                 vel = OOVectorMultiplyMatrix(vel, r_mat);
   11674             :                 last = father;
   11675             :                 if (![last isSubEntity]) break;
   11676             :                 father = [father owner];
   11677             :         }
   11678             :         
   11679             :         origin = HPvector_add(origin, vectorToHPVector(vector_multiply_scalar(vel, collision_radius + 0.5)));   // Start just outside collision sphere
   11680             :         vel = vector_multiply_scalar(vel, TURRET_SHOT_SPEED);   // Shot velocity
   11681             :         
   11682             :         OOPlasmaShotEntity *shot = [[OOPlasmaShotEntity alloc] initWithPosition:origin
   11683             :                                                                                                                                    velocity:vel
   11684             :                                                                                                                                          energy:weapon_damage
   11685             :                                                                                                                                    duration:weaponRange/TURRET_SHOT_SPEED
   11686             :                                                                                                                                           color:laser_color];
   11687             :         
   11688             :         [shot autorelease];
   11689             :         [UNIVERSE addEntity:shot];
   11690             :         [shot setOwner:[self rootShipEntity]];  // has to be done AFTER adding shot to the UNIVERSE
   11691             :         
   11692             :         [self resetShotTime];
   11693             :         return YES;
   11694             : }
   11695             : 
   11696             : 
   11697             : - (void) setLaserColor:(OOColor *) color
   11698             : {
   11699             :         if (color)
   11700             :         {
   11701             :                 [laser_color release];
   11702             :                 laser_color = [color retain];
   11703             :         }
   11704             : }
   11705             : 
   11706             : 
   11707             : - (void) setExhaustEmissiveColor:(OOColor *) color
   11708             : {
   11709             :         if (color)
   11710             :         {
   11711             :                 [exhaust_emissive_color release];
   11712             :                 exhaust_emissive_color = [color retain];
   11713             :         }
   11714             : }
   11715             : 
   11716             : 
   11717             : - (OOColor *)laserColor
   11718             : {
   11719             :         return [[laser_color retain] autorelease];
   11720             : }
   11721             : 
   11722             : 
   11723             : - (OOColor *)exhaustEmissiveColor
   11724             : {
   11725             :         return [[exhaust_emissive_color retain] autorelease];
   11726             : }
   11727             : 
   11728             : 
   11729             : - (BOOL) fireSubentityLaserShot:(double)range
   11730             : {
   11731             :         [self setShipHitByLaser:nil];
   11732             :         
   11733             :         if (isWeaponNone(forward_weapon_type))  return NO;
   11734             :         [self setWeaponDataFromType:forward_weapon_type];
   11735             :         
   11736             :         ShipEntity *parent = [self owner];
   11737             :         NSAssert([parent isShipWithSubEntityShip:self], @"-fireSubentityLaserShot: called on ship which is not a subentity.");
   11738             : 
   11739             :         // subentity lasers still draw power from the main entity
   11740             :         if ([parent energy] <= weapon_energy_use) return NO;
   11741             :         if ([self shotTime] < weapon_recharge_rate)  return NO;
   11742             :         if (forward_weapon_temp > WEAPON_COOLING_CUTOUT * NPC_MAX_WEAPON_TEMP)  return NO;
   11743             :         if (range > weaponRange)  return NO;
   11744             : 
   11745             :         forward_weapon_temp += weapon_shot_temperature;
   11746             :         [parent setEnergy:([parent energy] - weapon_energy_use)];
   11747             : 
   11748             :         GLfloat hitAtRange = weaponRange;
   11749             :         OOWeaponFacing direction = WEAPON_FACING_FORWARD;
   11750             :         ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:direction offset:kZeroVector gettingRangeFound:&hitAtRange];
   11751             :         [self setShipHitByLaser:victim];
   11752             :         
   11753             :         OOLaserShotEntity *shot = [OOLaserShotEntity laserFromShip:self direction:direction offset:kZeroVector];
   11754             :         [shot setColor:laser_color];
   11755             :         [shot setScanClass:CLASS_NO_DRAW];
   11756             :         
   11757             :         if (victim != nil)
   11758             :         {
   11759             :                 [self adjustMissedShots:-1];
   11760             :                 
   11761             :                 if ([self isPlayer])
   11762             :                 {
   11763             :                         [PLAYER addRoleForAggression:victim];
   11764             :                 }
   11765             : 
   11766             :                 ShipEntity *subent = [victim subEntityTakingDamage];
   11767             :                 if (subent != nil && [victim isFrangible])
   11768             :                 {
   11769             :                         // do 1% bleed-through damage...
   11770             :                         [victim takeEnergyDamage:0.01 * weapon_damage from:self becauseOf:parent weaponIdentifier:[[self weaponTypeForFacing:WEAPON_FACING_FORWARD strict:YES] identifier]];
   11771             :                         victim = subent;
   11772             :                 }
   11773             :                 
   11774             :                 if (hitAtRange < weaponRange)
   11775             :                 {
   11776             :                         [victim takeEnergyDamage:weapon_damage from:self becauseOf:parent weaponIdentifier:[[self weaponTypeForFacing:WEAPON_FACING_FORWARD strict:YES] identifier]];  // a very palpable hit
   11777             :                         
   11778             :                         [shot setRange:hitAtRange];
   11779             :                         Vector vd = vector_forward_from_quaternion([shot orientation]);
   11780             :                         HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hitAtRange)));
   11781             :                         [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:weapon_damage color:laser_color];
   11782             :                 }
   11783             :         }
   11784             :         else
   11785             :         {
   11786             :                 [self adjustMissedShots:+1];
   11787             : 
   11788             :                 // see ATTACKER_MISSED section of main entity laser routine
   11789             :                 if (![parent isCloaked])
   11790             :                 {
   11791             :                         victim = [parent primaryTarget];
   11792             :                         
   11793             :                         Vector shotDirection = vector_forward_from_quaternion([shot orientation]);
   11794             :                         Vector victimDirection = vector_normal(HPVectorToVector(HPvector_subtract([victim position], [parent position])));
   11795             :                         if (dot_product(shotDirection, victimDirection) > 0.995)     // Within 84.26 degrees
   11796             :                         {
   11797             :                                 if ([self isPlayer])
   11798             :                                 {
   11799             :                                         [PLAYER addRoleForAggression:victim];
   11800             :                                 }
   11801             :                                 [victim setPrimaryAggressor:parent];
   11802             :                                 [victim setFoundTarget:parent];
   11803             :                                 [victim reactToAIMessage:@"ATTACKER_MISSED" context:@"attacker narrowly misses"];
   11804             :                                 [victim doScriptEvent:OOJSID("shipBeingAttackedUnsuccessfully") withArgument:parent];
   11805             : 
   11806             :                         }
   11807             :                 }
   11808             :         }
   11809             :         
   11810             :         [UNIVERSE addEntity:shot];
   11811             :         [self resetShotTime];
   11812             :         
   11813             :         return YES;
   11814             : }
   11815             : 
   11816             : 
   11817             : - (BOOL) fireDirectLaserShot:(double)range
   11818             : {
   11819             :         Entity                  *my_target = [self primaryTarget];
   11820             :         if (my_target == nil)  return [self fireDirectLaserDefensiveShot];
   11821             :         if (range > randf() * weaponRange * (accuracy+5.5))  return [self fireDirectLaserDefensiveShot];
   11822             :         if (range > weaponRange)  return [self fireDirectLaserDefensiveShot];
   11823             :         return [self fireDirectLaserShotAt:my_target];
   11824             : }
   11825             : 
   11826             : 
   11827             : - (BOOL) fireDirectLaserDefensiveShot
   11828             : {
   11829             :         NSEnumerator *targetEnum = [self defenseTargetEnumerator];
   11830             :         Entity *target = nil;
   11831             :         while ((target = [[targetEnum nextObject] weakRefUnderlyingObject]))
   11832             :         {
   11833             :                 // can't fire defensively at cloaked ships
   11834             :                 if ([target scanClass] == CLASS_NO_DRAW || [(ShipEntity *)target isCloaked] || [target energy] <= 0.0)
   11835             :                 {
   11836             :                         [self removeDefenseTarget:target];
   11837             :                 }
   11838             :                 else 
   11839             :                 {
   11840             :                         double range = [self rangeToSecondaryTarget:target];
   11841             :                         if (range < weaponRange)
   11842             :                         {
   11843             :                                 return [self fireDirectLaserShotAt:target];
   11844             :                         }
   11845             :                         else if (range > scannerRange)
   11846             :                         {
   11847             :                                 [self removeDefenseTarget:target];
   11848             :                         }
   11849             :                 }
   11850             :         }
   11851             :         return NO;
   11852             : }
   11853             : 
   11854             : 
   11855             : - (BOOL) fireDirectLaserShotAt:(Entity *)my_target
   11856             : {
   11857             :         GLfloat                 hit_at_range;
   11858             :         double                  range_limit2 = weaponRange*weaponRange;
   11859             :         Vector                  r_pos;
   11860             :         
   11861             :         r_pos = vector_normal_or_zbasis([self vectorTo:my_target]);
   11862             : 
   11863             :         Quaternion              q_laser = quaternion_rotation_between(r_pos, kBasisZVector);
   11864             : 
   11865             :         GLfloat acc_factor = (10.0 - accuracy) * 0.001;
   11866             : 
   11867             :         q_laser.x += acc_factor * (randf() - 0.5);      // randomise aim a little (+/- 0.005 at accuracy 0, never miss at accuracy 10)
   11868             :         q_laser.y += acc_factor * (randf() - 0.5);
   11869             :         q_laser.z += acc_factor * (randf() - 0.5);
   11870             :         quaternion_normalize(&q_laser);
   11871             : 
   11872             :         Quaternion q_save = orientation;        // save rotation
   11873             :         orientation = q_laser;                  // face in direction of laser
   11874             :         // weapon offset for thargoid lasers is always zero
   11875             :         ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:WEAPON_FACING_FORWARD offset:kZeroVector gettingRangeFound:&hit_at_range];
   11876             :         [self setShipHitByLaser:victim];
   11877             :         orientation = q_save;                   // restore rotation
   11878             : 
   11879             :         Vector  vel = vector_multiply_scalar(v_forward, flightSpeed);
   11880             :         
   11881             :         // do special effects laser line
   11882             :         OOLaserShotEntity *shot = [OOLaserShotEntity laserFromShip:self direction:WEAPON_FACING_FORWARD offset:kZeroVector];
   11883             :         [shot setColor:laser_color];
   11884             :         [shot setScanClass: CLASS_NO_DRAW];
   11885             :         [shot setPosition: position];
   11886             :         [shot setOrientation: q_laser];
   11887             :         [shot setVelocity: vel];
   11888             :         
   11889             :         if (victim != nil)
   11890             :         {
   11891             :                 ShipEntity *subent = [victim subEntityTakingDamage];
   11892             :                 if (subent != nil && [victim isFrangible])
   11893             :                 {
   11894             :                         // do 1% bleed-through damage...
   11895             :                         [victim takeEnergyDamage: 0.01 * weapon_damage from:self becauseOf:self weaponIdentifier:[[self weaponTypeForFacing:WEAPON_FACING_FORWARD strict:YES] identifier]];
   11896             :                         victim = subent;
   11897             :                 }
   11898             : 
   11899             :                 if (hit_at_range * hit_at_range < range_limit2)
   11900             :                 {
   11901             :                         [victim takeEnergyDamage:weapon_damage from:self becauseOf:self weaponIdentifier:[[self weaponTypeForFacing:WEAPON_FACING_FORWARD strict:YES] identifier]];     // a very palpable hit
   11902             : 
   11903             :                         [shot setRange:hit_at_range];
   11904             :                         Vector vd = vector_forward_from_quaternion([shot orientation]);
   11905             :                         HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hit_at_range)));
   11906             :                         [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:weapon_damage color:laser_color];
   11907             :                 }
   11908             :         }
   11909             :         
   11910             :         [UNIVERSE addEntity:shot];
   11911             :         
   11912             :         [self resetShotTime];
   11913             :         
   11914             :         return YES;
   11915             : }
   11916             : 
   11917             : 
   11918             : - (NSArray *) laserPortOffset:(OOWeaponFacing)direction
   11919             : {
   11920             :         NSArray *laserPortOffset = nil;
   11921             :         switch (direction)
   11922             :         {
   11923             :                 case WEAPON_FACING_FORWARD:
   11924             :                 case WEAPON_FACING_NONE:
   11925             :                         laserPortOffset = forwardWeaponOffset;
   11926             :                         break;
   11927             :                         
   11928             :                 case WEAPON_FACING_AFT:
   11929             :                         laserPortOffset = aftWeaponOffset;
   11930             :                         break;
   11931             :                         
   11932             :                 case WEAPON_FACING_PORT:
   11933             :                         laserPortOffset = portWeaponOffset;
   11934             :                         break;
   11935             :                         
   11936             :                 case WEAPON_FACING_STARBOARD:
   11937             :                         laserPortOffset = starboardWeaponOffset;
   11938             :                         break;
   11939             :         }
   11940             :         return laserPortOffset;
   11941             : }
   11942             : 
   11943             : 
   11944             : - (BOOL) fireLaserShotInDirection:(OOWeaponFacing)direction weaponIdentifier:(NSString *)weaponIdentifier
   11945             : {
   11946             :         double                  range_limit2 = weaponRange * weaponRange;
   11947             :         GLfloat                 hit_at_range;
   11948             :         NSUInteger              i, barrels;
   11949             :         Vector                  vel = vector_multiply_scalar(v_forward, flightSpeed);
   11950             :         NSArray                 *laserPortOffsets = [self laserPortOffset:direction];
   11951             :         OOLaserShotEntity *shot = nil;
   11952             : 
   11953             :         barrels = [laserPortOffsets count];
   11954             :         NSMutableArray *shotEntities = [NSMutableArray arrayWithCapacity:barrels];
   11955             : 
   11956             :         
   11957             :         GLfloat                 effective_damage = weapon_damage;
   11958             :         if (barrels > 1 && !_multiplyWeapons)
   11959             :         {
   11960             :                 // then divide the shot power between the shots
   11961             :                 effective_damage /= (GLfloat)barrels;
   11962             :         }
   11963             :         
   11964             :         for (i=0;i<barrels;i++)
   11965             :         {
   11966             :                 Vector                  laserPortOffset = [laserPortOffsets oo_vectorAtIndex:i];
   11967             :         
   11968             :                 last_shot_time = [UNIVERSE getTime];
   11969             : 
   11970             :                 ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:direction offset:laserPortOffset gettingRangeFound:&hit_at_range];
   11971             :                 [self setShipHitByLaser:victim];
   11972             :         
   11973             :                 shot = [OOLaserShotEntity laserFromShip:self direction:direction offset:laserPortOffset];
   11974             :                 if ([self isPlayer])
   11975             :                 {
   11976             :                         [shotEntities addObject:shot];
   11977             :                 }
   11978             :         
   11979             :                 [shot setColor:laser_color];
   11980             :                 [shot setScanClass: CLASS_NO_DRAW];
   11981             :                 [shot setVelocity: vel];
   11982             :         
   11983             :                 if (victim != nil)
   11984             :                 {
   11985             :                         [self adjustMissedShots:-1];
   11986             :                         if ([self isPlayer])
   11987             :                         {
   11988             :                                 [PLAYER addRoleForAggression:victim];
   11989             :                         }
   11990             :                 
   11991             :                         /*      CRASH in [victim->sub_entities containsObject:subent] here (1.69, OS X/x86).
   11992             :                                 Analysis: Crash is in _freedHandler called from CFEqual, indicating either a dead
   11993             :                                 object in victim->sub_entities or dead victim->subentity_taking_damage. I suspect
   11994             :                                 the latter. Probable solution: dying subentities must cause parent to clean up
   11995             :                                 properly. This was probably obscured by the entity recycling scheme in the past.
   11996             :                                 Fix: made subentity_taking_damage a weak reference accessed via a method.
   11997             :                                 -- Ahruman 20070706, 20080304
   11998             :                         */
   11999             :                         ShipEntity *subent = [victim subEntityTakingDamage];
   12000             :                         if (subent != nil && [victim isFrangible])
   12001             :                         {
   12002             :                                 // do 1% bleed-through damage...
   12003             :                                 [victim takeEnergyDamage: 0.01 * effective_damage from:self becauseOf:self weaponIdentifier:weaponIdentifier];
   12004             :                                 victim = subent;
   12005             :                         }
   12006             :                 
   12007             :                         if (hit_at_range * hit_at_range < range_limit2)
   12008             :                         {
   12009             :                                 [victim takeEnergyDamage:effective_damage from:self becauseOf:self weaponIdentifier:weaponIdentifier];  // a very palpable hit
   12010             : 
   12011             :                                 [shot setRange:hit_at_range];
   12012             :                                 Vector vd = vector_forward_from_quaternion([shot orientation]);
   12013             :                                 HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hit_at_range)));
   12014             :                                 [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:effective_damage color:laser_color];
   12015             :                         }
   12016             :                 }
   12017             :                 else
   12018             :                 {
   12019             :                         [self adjustMissedShots:+1];
   12020             : 
   12021             :                         // shot missed
   12022             :                         if (![self isCloaked])
   12023             :                         {
   12024             :                                 victim = [self primaryTarget];
   12025             :                                 if ([victim isShip]) // it might not be - fixes crash bug
   12026             :                                 {
   12027             : 
   12028             :                                         /* player currently gets a bit of an advantage here if
   12029             :                                          * they ambush without having their target actually
   12030             :                                          * targeted. Though in those circumstances they
   12031             :                                          * shouldn't be missing their first shot anyway. */
   12032             :                                         if (dot_product(vector_forward_from_quaternion([shot orientation]),vector_normal([self vectorTo:victim])) > 0.995)
   12033             :                                         {
   12034             :                                                 /* plausibly aimed at target. Allows reaction
   12035             :                                                  * before attacker actually hits. But we need to
   12036             :                                                  * be able to distinguish in AI from ATTACKED so
   12037             :                                                  * that ships in combat aren't bothered by
   12038             :                                                  * amateurs. So should only respond to
   12039             :                                                  * ATTACKER_MISSED if not already fighting */
   12040             :                                                 if ([self isPlayer])
   12041             :                                                 {
   12042             :                                                         [PLAYER addRoleForAggression:victim];
   12043             :                                                 }
   12044             :                                                 [victim setPrimaryAggressor:self];
   12045             :                                                 [victim setFoundTarget:self];
   12046             :                                                 [victim reactToAIMessage:@"ATTACKER_MISSED" context:@"attacker narrowly misses"];
   12047             :                                                 [victim doScriptEvent:OOJSID("shipBeingAttackedUnsuccessfully") withArgument:self];
   12048             :                                         }
   12049             :                                 }
   12050             :                         }
   12051             :                 }
   12052             :         
   12053             :                 [UNIVERSE addEntity:shot];
   12054             : 
   12055             :         }
   12056             :         
   12057             :         if ([self isPlayer])
   12058             :         {
   12059             :                 [(PlayerEntity *)self setLastShot:shotEntities];
   12060             :         }
   12061             :         
   12062             :         [self resetShotTime];
   12063             : 
   12064             :         return YES;
   12065             : }
   12066             : 
   12067             : 
   12068             : - (void) adjustMissedShots:(int) delta
   12069             : {
   12070             :         if ([self isSubEntity])
   12071             :         {
   12072             :                 [[self owner] adjustMissedShots:delta];
   12073             :         }
   12074             :         else
   12075             :         {
   12076             :                 _missed_shots += delta;
   12077             :                 if (_missed_shots < 0)
   12078             :                 {
   12079             :                         _missed_shots = 0;
   12080             :                 }
   12081             :         }
   12082             : }
   12083             : 
   12084             : 
   12085             : - (int) missedShots
   12086             : {
   12087             :         if ([self isSubEntity])
   12088             :         {
   12089             :                 return [[self owner] missedShots];
   12090             :         }
   12091             :         else
   12092             :         {
   12093             :                 return _missed_shots;
   12094             :         }
   12095             : }
   12096             : 
   12097             : 
   12098           0 : - (void) throwSparks
   12099             : {
   12100             :         Vector offset =
   12101             :         {
   12102             :                 randf() * (boundingBox.max.x - boundingBox.min.x) + boundingBox.min.x,
   12103             :                 randf() * (boundingBox.max.y - boundingBox.min.y) + boundingBox.min.y,
   12104             :                 randf() * boundingBox.max.z + boundingBox.min.z // rear section only
   12105             :         };
   12106             :         HPVector origin = HPvector_add(position, vectorToHPVector(quaternion_rotate_vector([self normalOrientation], offset)));
   12107             : 
   12108             :         float   w = boundingBox.max.x - boundingBox.min.x;
   12109             :         float   h = boundingBox.max.y - boundingBox.min.y;
   12110             :         float   m = (w < h) ? 0.25 * w: 0.25 * h;
   12111             :         
   12112             :         float   sz = m * (1 + randf() + randf());       // half minimum dimension on average
   12113             :         
   12114             :         Vector vel = vector_multiply_scalar(HPVectorToVector(HPvector_subtract(origin, position)), 2.0);
   12115             :         
   12116             :         OOColor *color = [OOColor colorWithHue:0.08 + 0.17 * randf() saturation:1.0 brightness:1.0 alpha:1.0];
   12117             :         
   12118             :         OOSparkEntity *spark = [[OOSparkEntity alloc] initWithPosition:origin
   12119             :                                                                                                                   velocity:vel
   12120             :                                                                                                                   duration:2.0 + 3.0 * randf()
   12121             :                                                                                                                           size:sz
   12122             :                                                                                                                          color:color];
   12123             :         
   12124             :         [spark setOwner:self];
   12125             :         [UNIVERSE addEntity:spark];
   12126             :         [spark release];
   12127             : 
   12128             :         next_spark_time = randf();
   12129             : }
   12130             : 
   12131             : 
   12132             : - (void) considerFiringMissile:(double)delta_t
   12133             : {
   12134             :         int missile_chance = 0;
   12135             :         int rhs = 3.2 / delta_t;
   12136             :         if (rhs) missile_chance = 1 + (ranrot_rand() % rhs);
   12137             : 
   12138             :         double hurt_factor = 16 * pow(energy/maxEnergy, 4.0);
   12139             :         if (missiles > missile_chance * hurt_factor)
   12140             :         {
   12141             :                 [self fireMissile];
   12142             :         }
   12143             : }
   12144             : 
   12145             : 
   12146             : - (Vector) missileLaunchPosition
   12147             : {
   12148             :         Vector start;
   12149             :         // default launching position
   12150             :         start.x = 0.0f;                                         // in the middle
   12151             :         start.y = boundingBox.min.y - 4.0f;     // 4m below bounding box
   12152             :         start.z = boundingBox.max.z + 1.0f;     // 1m ahead of bounding box
   12153             :         
   12154             :         // custom launching position
   12155             :         start = [shipinfoDictionary oo_vectorForKey:@"missile_launch_position" defaultValue:start];
   12156             :         if (EXPECT_NOT(_scaleFactor != 1.0))
   12157             :         {
   12158             :                 start = vector_multiply_scalar(start,_scaleFactor);
   12159             :         }
   12160             :         
   12161             :         if (start.x == 0.0f && start.y == 0.0f && start.z <= 0.0f) // The kZeroVector as start is illegal also.
   12162             :         {
   12163             :                 OOLog(@"ship.missileLaunch.invalidPosition", @"***** ERROR: The missile_launch_position defines a position %@ behind the %@. In future versions such missiles may explode on launch because they have to travel through the ship.", VectorDescription(start), self);
   12164             :                 start.x = 0.0f;
   12165             :                 start.y = boundingBox.min.y - 4.0f;
   12166             :                 start.z = boundingBox.max.z + 1.0f;
   12167             :         }
   12168             :         return start;
   12169             : }
   12170             : 
   12171             : 
   12172             : - (ShipEntity *) fireMissile
   12173             : {
   12174             :         return [self fireMissileWithIdentifier:nil andTarget:[self primaryTarget]];
   12175             : }
   12176             : 
   12177             : 
   12178             : - (ShipEntity *) fireMissileWithIdentifier:(NSString *) identifier andTarget:(Entity *) target
   12179             : {
   12180             :         // both players and NPCs!
   12181             :         //
   12182             :         ShipEntity              *missile = nil;
   12183             :         ShipEntity              *target_ship = nil;
   12184             :         
   12185             :         Vector                  vel;
   12186             :         Vector                  start, v_eject;
   12187             :         
   12188             :         if ([UNIVERSE getTime] < missile_launch_time) return nil;
   12189             : 
   12190             :         start = [self missileLaunchPosition];
   12191             :         
   12192             :         double  throw_speed = 250.0f;
   12193             :         
   12194             :         if      ((missiles <= 0)||(target == nil)||([target scanClass] == CLASS_NO_DRAW))    // no missile lock!
   12195             :                 return nil;
   12196             :         
   12197             :         if ([target isShip])
   12198             :         {
   12199             :                 target_ship = (ShipEntity*)target;
   12200             :                 if ([target_ship isCloaked])
   12201             :                 {
   12202             :                         return nil;
   12203             :                 }
   12204             :                 // missile fire requires being in scanner range
   12205             :                 if (HPmagnitude2(HPvector_subtract([target_ship position], position)) > scannerRange * scannerRange)
   12206             :                 {
   12207             :                         return nil;
   12208             :                 }
   12209             :                 if (![self hasMilitaryScannerFilter] && [target_ship isJammingScanning]) 
   12210             :                 {
   12211             :                         return nil;
   12212             :                 }
   12213             :         }
   12214             :         
   12215             :         unsigned i;
   12216             :         if (identifier == nil)
   12217             :         {
   12218             :                 // use a random missile from the list
   12219             :                 i = floor(randf()*(double)missiles);
   12220             :                 identifier = [missile_list[i] identifier];
   12221             :                 missile = [UNIVERSE newShipWithRole:identifier];
   12222             :                 if (EXPECT_NOT(missile == nil)) // invalid missile role.
   12223             :                 {
   12224             :                         // remove that invalid missile role from the missiles list.
   12225             :                         while ( ++i < missiles ) missile_list[i - 1] = missile_list[i];
   12226             :                         missiles--;
   12227             :                 }
   12228             :         }
   12229             :         else
   12230             :                 missile = [UNIVERSE newShipWithRole:identifier];
   12231             :         
   12232             :         if (EXPECT_NOT(missile == nil)) return nil;
   12233             :         
   12234             :         // By definition, the player will always have the specified missile.
   12235             :         // What if the NPC didn't actually have the specified missile to begin with?
   12236             :         if (!isPlayer && ![self removeExternalStore:[OOEquipmentType equipmentTypeWithIdentifier:identifier]])
   12237             :         {
   12238             :                 [missile release];
   12239             :                 return nil;
   12240             :         }
   12241             :         
   12242             :         double mcr = missile->collision_radius;
   12243             :         v_eject = vector_normal(start);
   12244             :         vel = kZeroVector;      // starting velocity
   12245             :         
   12246             :         // check if start is within bounding box...
   12247             :         while ( (start.x > boundingBox.min.x - mcr)&&(start.x < boundingBox.max.x + mcr)&&
   12248             :                         (start.y > boundingBox.min.y - mcr)&&(start.y < boundingBox.max.y + mcr)&&
   12249             :                         (start.z > boundingBox.min.z - mcr)&&(start.z < boundingBox.max.z + mcr) )
   12250             :         {
   12251             :                 start = vector_add(start, vector_multiply_scalar(v_eject, mcr));
   12252             :         }
   12253             :         
   12254             :         vel = vector_add(vel, vector_multiply_scalar(v_forward, flightSpeed + throw_speed));
   12255             :         
   12256             :         Quaternion q1 = [self normalOrientation];
   12257             :         HPVector origin = HPvector_add(position, vectorToHPVector(quaternion_rotate_vector(q1, start)));
   12258             :         
   12259             :         if (isPlayer) [missile setScanClass: CLASS_MISSILE];
   12260             :         
   12261             : // special cases
   12262             :         
   12263             :         //We don't want real missiles in a group. Missiles could become escorts when the group is also used as escortGroup.
   12264             :         if ([missile scanClass] == CLASS_THARGOID) 
   12265             :         {
   12266             :                 if([self group] == nil) [self setGroup:[OOShipGroup groupWithName:@"thargoid group"]];
   12267             :                 
   12268             :                 ShipEntity      *thisGroupLeader = [_group leader];
   12269             :                 
   12270             :                 if ([thisGroupLeader escortGroup] != _group) // avoid adding tharons to escort groups
   12271             :                 {
   12272             :                         [missile setGroup:[self group]];
   12273             :                 }
   12274             :         }
   12275             :         
   12276             :         // is this a submunition?
   12277             :         if (![self isMissileFlagSet])  [missile setOwner:self];
   12278             :         else  [missile setOwner:[self owner]];
   12279             : 
   12280             : // end special cases
   12281             : 
   12282             :         [missile setPosition:origin];
   12283             :         [missile addTarget:target];     
   12284             :         [missile setOrientation:q1];
   12285             :         [missile setIsMissileFlag:YES];
   12286             :         [missile setVelocity:vel];
   12287             :         [missile setSpeed:150.0f];
   12288             :         [missile setDistanceTravelled:0.0f];
   12289             :         [missile resetShotTime];
   12290             :         missile_launch_time = [UNIVERSE getTime] + missile_load_time; // set minimum launchtime for the next missile.
   12291             :         
   12292             :         [UNIVERSE addEntity:missile];   // STATUS_IN_FLIGHT, AI state GLOBAL
   12293             :         [missile release]; //release
   12294             :         
   12295             :         // missile lives on after UNIVERSE addEntity
   12296             :         if ([missile isMissile] && [target isShip])
   12297             :         {
   12298             :                 [self doScriptEvent:OOJSID("shipFiredMissile") withArgument:missile andArgument:target_ship];
   12299             :                 [target_ship setPrimaryAggressor:self];
   12300             :                 [target_ship doScriptEvent:OOJSID("shipAttackedWithMissile") withArgument:missile andArgument:self];
   12301             :                 [target_ship reactToAIMessage:@"INCOMING_MISSILE" context:@"someone's shooting at me!"];
   12302             :                 if (cloaking_device_active && cloakPassive)
   12303             :                 {
   12304             :                         // parity between player &NPCs, only deactivate cloak for missiles
   12305             :                         [self deactivateCloakingDevice];
   12306             :                 }
   12307             :         }
   12308             :         else
   12309             :         {
   12310             :                 [self doScriptEvent:OOJSID("shipReleasedEquipment") withArgument:missile];
   12311             :         }
   12312             :         
   12313             :         return missile;
   12314             : }
   12315             : 
   12316             : 
   12317             : - (BOOL) isMissileFlagSet
   12318             : {
   12319             :         return isMissile; // were we created using fireMissile? (for tracking submunitions and preventing collisions at launch)
   12320             : }
   12321             : 
   12322             : 
   12323             : - (void) setIsMissileFlag:(BOOL)newValue
   12324             : {
   12325             :         isMissile = !!newValue; // set the isMissile flag, used for tracking submunitions and preventing collisions at launch.
   12326             : }
   12327             : 
   12328             : 
   12329             : - (OOTimeDelta) missileLoadTime
   12330             : {
   12331             :         return missile_load_time;
   12332             : }
   12333             : 
   12334             : 
   12335             : - (void) setMissileLoadTime:(OOTimeDelta)newMissileLoadTime
   12336             : {
   12337             :         missile_load_time = fmax(0.0, newMissileLoadTime);
   12338             : }
   12339             : 
   12340             : 
   12341             : // reactions to ECM that are not dependent on current AI state here
   12342             : - (void) noticeECM
   12343             : {
   12344             :         if (accuracy >= COMBAT_AI_ISNT_AWFUL && missiles > 0 && [[missile_list[0] identifier] isEqualTo:@"EQ_MISSILE"])
   12345             :         {
   12346             : // if we're being ECMd, and our missiles appear to be standard, and we
   12347             : // have some combat sense, wait a bit before firing the next one!
   12348             :                 missile_launch_time = [UNIVERSE getTime] + fmax(2.0,missile_load_time); // set minimum launchtime for the next missile.
   12349             :         }
   12350             : }
   12351             : 
   12352             : 
   12353             : // Exposed to AI
   12354             : - (BOOL) fireECM
   12355             : {
   12356             :         if (![self hasECM])  return NO;
   12357             :         
   12358             :         OOECMBlastEntity *ecmDevice = [[OOECMBlastEntity alloc] initFromShip:self];
   12359             :         [UNIVERSE addEntity:ecmDevice];
   12360             :         [ecmDevice release];
   12361             :         return YES;
   12362             : }
   12363             : 
   12364             : 
   12365             : - (BOOL) activateCloakingDevice
   12366             : {
   12367             :         if (![self hasCloakingDevice] || cloaking_device_active)  return cloaking_device_active; // no changes.
   12368             :         
   12369             :         if (!cloaking_device_active)  cloaking_device_active = (energy > CLOAKING_DEVICE_START_ENERGY * maxEnergy);
   12370             :         if (cloaking_device_active)  [self doScriptEvent:OOJSID("shipCloakActivated")];
   12371             :         return cloaking_device_active;
   12372             : }
   12373             : 
   12374             : 
   12375             : - (void) deactivateCloakingDevice
   12376             : {
   12377             :         if ([self hasCloakingDevice] && cloaking_device_active)
   12378             :         {
   12379             :                 cloaking_device_active = NO;
   12380             :                 [self doScriptEvent:OOJSID("shipCloakDeactivated")];
   12381             :         }
   12382             : }
   12383             : 
   12384             : 
   12385             : - (BOOL) launchCascadeMine
   12386             : {
   12387             :         if (![self hasCascadeMine])  return NO;
   12388             :         [self setSpeed: maxFlightSpeed + 300];
   12389             :         ShipEntity*     bomb = [UNIVERSE newShipWithRole:@"energy-bomb"];
   12390             :         if (bomb == nil)  return NO;
   12391             :         
   12392             :         [self removeEquipmentItem:@"EQ_QC_MINE"];
   12393             :         
   12394             :         double  start = collision_radius + bomb->collision_radius;
   12395             :         Quaternion  random_direction;
   12396             :         Vector  vel;
   12397             :         HPVector  rpos;
   12398             :         double random_roll =    randf() - 0.5;  //  -0.5 to +0.5
   12399             :         double random_pitch =   randf() - 0.5;  //  -0.5 to +0.5
   12400             :         quaternion_set_random(&random_direction);
   12401             :         
   12402             :         rpos = HPvector_subtract([self position], vectorToHPVector(vector_multiply_scalar(v_forward, start)));
   12403             :         
   12404             :         double  eject_speed = -800.0;
   12405             :         vel = vector_multiply_scalar(v_forward, [self flightSpeed] + eject_speed);
   12406             :         eject_speed *= 0.5 * (randf() - 0.5);   //  -0.25x .. +0.25x
   12407             :         vel = vector_add(vel, vector_multiply_scalar(v_up, eject_speed));
   12408             :         eject_speed *= 0.5 * (randf() - 0.5);   //  -0.0625x .. +0.0625x
   12409             :         vel = vector_add(vel, vector_multiply_scalar(v_right, eject_speed));
   12410             :         
   12411             :         [bomb setPosition:rpos];
   12412             :         [bomb setOrientation:random_direction];
   12413             :         [bomb setRoll:random_roll];
   12414             :         [bomb setPitch:random_pitch];
   12415             :         [bomb setVelocity:vel];
   12416             :         [bomb setScanClass:CLASS_MINE];
   12417             :         [bomb setEnergy:5.0];   // 5 second countdown
   12418             :         [bomb setBehaviour:BEHAVIOUR_ENERGY_BOMB_COUNTDOWN];
   12419             :         [bomb setOwner:self];
   12420             :         [UNIVERSE addEntity:bomb];      // STATUS_IN_FLIGHT, AI state GLOBAL
   12421             :         [bomb release];
   12422             :         
   12423             :         if (cloaking_device_active && cloakPassive)
   12424             :         {
   12425             :                 [self deactivateCloakingDevice];
   12426             :         }
   12427             :         
   12428             :         if (self != PLAYER)     // get the heck out of here
   12429             :         {
   12430             :                 [self addTarget:bomb];
   12431             :                 [self setBehaviour:BEHAVIOUR_FLEE_TARGET];
   12432             :                 frustration = 0.0;
   12433             :         }
   12434             :         return YES;
   12435             : }
   12436             : 
   12437             : 
   12438             : - (ShipEntity*)launchEscapeCapsule
   12439             : {
   12440             :         ShipEntity              *result = nil;
   12441             :         ShipEntity                      *mainPod = nil;
   12442             :         unsigned                        n_pods, i;
   12443             :         NSMutableArray          *passengers = nil;
   12444             :         
   12445             :         /*
   12446             :                 CHANGE: both player & NPCs can now launch escape pods in interstellar
   12447             :                 space. -- Kaks 20101113
   12448             :         */
   12449             :         
   12450             :         // check number of pods aboard -- require at least one.
   12451             :         n_pods = [shipinfoDictionary oo_unsignedIntForKey:@"has_escape_pod"];
   12452             :         if (n_pods > 65) n_pods = 65; // maximum of 64 passengers.
   12453             :         if (n_pods > 1) passengers = [NSMutableArray arrayWithCapacity:n_pods-1];
   12454             :         
   12455             :         if (crew)       // transfer crew
   12456             :         {
   12457             :                 // make sure crew inherit any legalStatus
   12458             :                 for (i = 0; i < [crew count]; i++)
   12459             :                 {
   12460             :                         OOCharacter *ch = (OOCharacter*)[crew objectAtIndex:i];
   12461             :                         [ch setLegalStatus: [self legalStatus] | [ch legalStatus]];
   12462             :                 }
   12463             :                 mainPod = [self launchPodWithCrew:crew];
   12464             :                 if (mainPod)
   12465             :                 {
   12466             :                         result = mainPod;
   12467             :                         [self setCrew:nil];
   12468             :                         [self setHulk:YES]; // we are without crew now.
   12469             :                 }
   12470             :         }
   12471             :         
   12472             :         // launch other pods (passengers)
   12473             :         for (i = 1; i < n_pods; i++)
   12474             :         {
   12475             :                 ShipEntity      *passenger = nil;
   12476             :                 passenger = [self launchPodWithCrew:[NSArray arrayWithObject:[OOCharacter randomCharacterWithRole:@"passenger" andOriginalSystem:gen_rnd_number()]]];
   12477             :                 [passengers addObject:passenger];
   12478             :         }
   12479             :         
   12480             :         if (mainPod) [self doScriptEvent:OOJSID("shipLaunchedEscapePod") withArgument:mainPod andArgument:passengers];
   12481             :         
   12482             :         return result;
   12483             : }
   12484             : 
   12485             : 
   12486             : // This is a documented AI method; do not change semantics. (Note: AIs don't have access to the return value.)
   12487             : - (OOCommodityType) dumpCargo
   12488             : {
   12489             :         ShipEntity *jetto = [self dumpCargoItem:nil];
   12490             :         if (jetto != nil)
   12491             :         {
   12492             :                 return [jetto commodityType];
   12493             :         }
   12494             :         else
   12495             :         {
   12496             :                 return nil;
   12497             :         }
   12498             : }
   12499             : 
   12500             : 
   12501             : - (ShipEntity *) dumpCargoItem:(OOCommodityType)preferred
   12502             : {
   12503             :         ShipEntity                              *jetto = nil;
   12504             :         NSUInteger                               i = 0;
   12505             :         
   12506             :         if (([cargo count] > 0)&&([UNIVERSE getTime] - cargo_dump_time > 0.5))  // space them 0.5s or 10m apart
   12507             :         {
   12508             :                 if (preferred == nil)
   12509             :                 {
   12510             :                         jetto = [[[cargo objectAtIndex:0] retain] autorelease];
   12511             :                 }
   12512             :                 else
   12513             :                 {
   12514             :                         BOOL found = NO;
   12515             :                         for (i=0;i<[cargo count];i++)
   12516             :                         {
   12517             :                                 if ([[[cargo objectAtIndex:i] commodityType] isEqualToString:preferred])
   12518             :                                 {
   12519             :                                         jetto = [[[cargo objectAtIndex:i] retain] autorelease];
   12520             :                                         found = YES;
   12521             :                                         break;
   12522             :                                 }
   12523             :                         }
   12524             :                         if (found == NO)
   12525             :                         {
   12526             :                                 // dump anything
   12527             :                                 jetto = [[[cargo objectAtIndex:0] retain] autorelease];
   12528             :                                 i = 0;
   12529             :                         }
   12530             :                 }
   12531             :                 if (jetto != nil)
   12532             :                 {
   12533             :                         [self dumpItem:jetto];  // CLASS_CARGO, STATUS_IN_FLIGHT, AI state GLOBAL
   12534             :                         [cargo removeObjectAtIndex:i];
   12535             :                         [self broadcastAIMessage:@"CARGO_DUMPED"]; // goes only to 16 nearby ships in range, but that should be enough.
   12536             :                         unsigned i;
   12537             :                         // only send script event to powered entities
   12538             :                         [self checkScannerIgnoringUnpowered];
   12539             :                         for (i = 0; i < n_scanned_ships ; i++)
   12540             :                         {
   12541             :                                 ShipEntity* other = scanned_ships[i];
   12542             :                                 [other doScriptEvent:OOJSID("cargoDumpedNearby") withArgument:jetto andArgument:self];
   12543             :                                 
   12544             :                         }
   12545             :                 }
   12546             :         }
   12547             :         
   12548             :         return jetto;
   12549             : }
   12550             : 
   12551             : 
   12552             : - (OOCargoType) dumpItem: (ShipEntity*) cargoObj
   12553             : {
   12554             :         if (!cargoObj)
   12555             :                 return 0;
   12556             : 
   12557             :         ShipEntity* jetto = [UNIVERSE reifyCargoPod:cargoObj];
   12558             : 
   12559             :         int             result = [jetto cargoType];
   12560             :         AI              *jettoAI = nil;
   12561             :         Vector  start;
   12562             :         
   12563             :         // players get to see their old ship sailing forth, while NPCs run away more efficiently!
   12564             :         // cargo is ejected at higher speed from any ship
   12565             :         double  eject_speed = EXPECT_NOT([jetto crew] && [jetto isPlayer]) ? 20.0 : 100.0;
   12566             :         double  eject_reaction = -eject_speed * [jetto mass] / [self mass];
   12567             :         double  jcr = jetto->collision_radius;
   12568             :         
   12569             :         Quaternion  jetto_orientation = kIdentityQuaternion;
   12570             :         Vector  vel, v_eject, v_eject_normal;
   12571             :         HPVector  rpos = [self absolutePositionForSubentity];
   12572             :         double jetto_roll =     0;
   12573             :         double jetto_pitch = 0;
   12574             :         
   12575             :         // default launching position
   12576             :         start.x = 0.0;                                          // in the middle
   12577             :         start.y = 0.0;                                          //
   12578             :         start.z = boundingBox.min.z - jcr;      // 1m behind of bounding box
   12579             :         
   12580             :         // custom launching position
   12581             :         start = [shipinfoDictionary oo_vectorForKey:@"aft_eject_position" defaultValue:start];
   12582             :         
   12583             :         v_eject = vector_normal(start);
   12584             :         
   12585             :         // check if start is within bounding box...
   12586             :         while ( (start.x > boundingBox.min.x - jcr)&&(start.x < boundingBox.max.x + jcr)&&
   12587             :                         (start.y > boundingBox.min.y - jcr)&&(start.y < boundingBox.max.y + jcr)&&
   12588             :                         (start.z > boundingBox.min.z - jcr)&&(start.z < boundingBox.max.z + jcr))
   12589             :         {
   12590             :                 start = vector_add(start, vector_multiply_scalar(v_eject, jcr));
   12591             :         }
   12592             :         
   12593             :         v_eject = quaternion_rotate_vector([self normalOrientation], start);
   12594             :         rpos = HPvector_add(rpos, vectorToHPVector(v_eject));
   12595             :         v_eject = vector_normal(v_eject);
   12596             :         v_eject_normal = v_eject;
   12597             :         
   12598             :         v_eject.x += (randf() - randf())/eject_speed;
   12599             :         v_eject.y += (randf() - randf())/eject_speed;
   12600             :         v_eject.z += (randf() - randf())/eject_speed;
   12601             :         
   12602             :         vel = vector_add(vector_multiply_scalar(v_forward, flightSpeed), vector_multiply_scalar(v_eject, eject_speed));
   12603             :         velocity = vector_add(velocity, vector_multiply_scalar(v_eject, eject_reaction));
   12604             :         
   12605             :         [jetto setPosition:rpos];
   12606             :         if ([jetto crew]) // jetto has a crew, so assume it is an escape pod.
   12607             :         {
   12608             :                 // orient the pod away from the ship to avoid colliding with it.
   12609             :                 jetto_orientation = quaternion_rotation_between(v_eject_normal, kBasisZVector);
   12610             :         }
   12611             :         else
   12612             :         {
   12613             :                 // It is true cargo, let it tumble.
   12614             :                 jetto_roll =    ((ranrot_rand() % 1024) - 512.0)/1024.0;  //  -0.5 to +0.5
   12615             :                 jetto_pitch =   ((ranrot_rand() % 1024) - 512.0)/1024.0;  //  -0.5 to +0.5
   12616             :                 quaternion_set_random(&jetto_orientation);
   12617             :         }
   12618             :         
   12619             :         [jetto setOrientation:jetto_orientation];
   12620             :         [jetto setRoll:jetto_roll];
   12621             :         [jetto setPitch:jetto_pitch];
   12622             :         [jetto setVelocity:vel];
   12623             :         [jetto setScanClass: CLASS_CARGO];
   12624             :         [jetto setTemperature:[self randomEjectaTemperature]];
   12625             :         [UNIVERSE addEntity:jetto];     // STATUS_IN_FLIGHT, AI state GLOBAL
   12626             :         
   12627             :         jettoAI = [jetto getAI];
   12628             :         if ([jettoAI hasSuspendedStateMachines]) // check if this was previous scooped cargo.
   12629             :         {
   12630             :                 [jetto setThrust:[jetto maxThrust]]; // restore old thrust.
   12631             :                 [jetto setOwner:jetto];
   12632             :                 [jettoAI exitStateMachineWithMessage:nil]; // exit nullAI.
   12633             :         }
   12634             :         [jetto doScriptEvent:OOJSID("shipWasDumped") withArgument:self];
   12635             :         [self doScriptEvent:OOJSID("shipDumpedCargo") withArgument:jetto];
   12636             :         
   12637             :         cargo_dump_time = [UNIVERSE getTime];
   12638             :         return result;
   12639             : }
   12640             : 
   12641             : 
   12642             : - (void) manageCollisions
   12643             : {
   12644             :         // deal with collisions
   12645             :         //
   12646             :         Entity*         ent;
   12647             :         ShipEntity* other_ship;
   12648             :         
   12649             :         while ([collidingEntities count] > 0)
   12650             :         {
   12651             :                 // EMMSTRAN: investigate if doing this backwards would be more efficient. (Not entirely obvious, NSArray is kinda funky.) -- Ahruman 2011-02-12
   12652             :                 ent = [[[collidingEntities objectAtIndex:0] retain] autorelease];
   12653             :                 [collidingEntities removeObjectAtIndex:0];
   12654             :                 if (ent)
   12655             :                 {
   12656             :                         if ([ent isShip])
   12657             :                         {
   12658             :                                 other_ship = (ShipEntity *)ent;
   12659             :                                 [self collideWithShip:other_ship];
   12660             :                         }
   12661             :                         else if ([ent isStellarObject])
   12662             :                         {
   12663             :                                 [self getDestroyedBy:ent damageType:[ent isSun] ? kOODamageTypeHitASun : kOODamageTypeHitAPlanet];
   12664             :                                 if (self == PLAYER)  [self retain];
   12665             :                         }
   12666             :                         else if ([ent isWormhole])
   12667             :                         {
   12668             :                                 if( [self isPlayer] ) [self enterWormhole:(WormholeEntity*)ent];
   12669             :                                 else [self enterWormhole:(WormholeEntity*)ent replacing:NO];
   12670             :                         }
   12671             :                 }
   12672             :         }
   12673             : }
   12674             : 
   12675             : 
   12676             : - (BOOL) collideWithShip:(ShipEntity *)other
   12677             : {
   12678             :         HPVector  hploc;
   12679             :         Vector loc;
   12680             :         double  dam1, dam2;
   12681             :         
   12682             :         if (!other)
   12683             :                 return NO;
   12684             :         
   12685             :         ShipEntity* otherParent = [other parentEntity];
   12686             :         BOOL otherIsStation = other == [UNIVERSE station];
   12687             :         // calculate line of centers using centres
   12688             :         hploc = HPvector_normal_or_zbasis(HPvector_subtract([other absolutePositionForSubentity], position));
   12689             :         loc = HPVectorToVector(hploc);
   12690             : 
   12691             :         
   12692             :         if ([self canScoop:other])
   12693             :         {
   12694             :                 [self scoopIn:other];
   12695             :                 return NO;
   12696             :         }
   12697             :         if ([other canScoop:self])
   12698             :         {
   12699             :                 [other scoopIn:self];
   12700             :                 return NO;
   12701             :         }
   12702             :         if (universalID == NO_TARGET)
   12703             :                 return NO;
   12704             :         if (other->universalID == NO_TARGET)
   12705             :                 return NO;
   12706             : 
   12707             :         // find velocity along line of centers
   12708             :         //
   12709             :         // momentum = mass x velocity
   12710             :         // ke = mass x velocity x velocity
   12711             :         //
   12712             :         GLfloat m1 = mass;                      // mass of self
   12713             :         GLfloat m2 = [other mass];      // mass of other
   12714             : 
   12715             :         // starting velocities:
   12716             :         Vector  v, vel1b =      [self velocity];
   12717             :         
   12718             :         if (otherParent != nil)
   12719             :         {
   12720             :                 // Subentity
   12721             :                 /*      TODO: if the subentity is rotating (subentityRotationalVelocity is
   12722             :                         not 1 0 0 0) we should calculate the tangential velocity from the
   12723             :                         other's position relative to our absolute position and add that in.
   12724             :                 */
   12725             :                 v = [otherParent velocity];
   12726             :         }
   12727             :         else
   12728             :         {
   12729             :                 v = [other velocity];
   12730             :         }
   12731             : 
   12732             :         v = vector_subtract(vel1b, v);
   12733             :         
   12734             :         GLfloat v2b = dot_product(v, loc);                      // velocity of other along loc before collision
   12735             :         
   12736             :         GLfloat v1a = sqrt(v2b * v2b * m2 / m1);        // velocity of self along loc after elastic collision
   12737             :         if (v2b < 0.0f)      v1a = -v1a;                                     // in same direction as v2b
   12738             :         
   12739             :         // are they moving apart at over 1m/s already?
   12740             :         if (v2b < 0.0f)
   12741             :         {
   12742             :                 if (v2b < -1.0f)  return NO;
   12743             :                 else
   12744             :                 {
   12745             :                         position = HPvector_subtract(position, hploc);  // adjust self position
   12746             :                         v = kZeroVector;        // go for the 1m/s solution
   12747             :                 }
   12748             :         }
   12749             : 
   12750             :         // convert change in velocity into damage energy (KE)
   12751             :         dam1 = m2 * v2b * v2b / 50000000;
   12752             :         dam2 = m1 * v2b * v2b / 50000000;
   12753             :         
   12754             :         // calculate adjustments to velocity after collision
   12755             :         Vector vel1a = vector_multiply_scalar(loc, -v1a);
   12756             :         Vector vel2a = vector_multiply_scalar(loc, v2b);
   12757             : 
   12758             :         if (magnitude2(v) <= 0.1)    // virtually no relative velocity - we must provide at least 1m/s to avoid conjoined objects
   12759             :         {
   12760             :                 vel1a = vector_multiply_scalar(loc, -1);
   12761             :                 vel2a = loc;
   12762             :         }
   12763             : 
   12764             :         // apply change in velocity
   12765             :         if (otherParent != nil)
   12766             :         {
   12767             :                 [otherParent adjustVelocity:vel2a];     // move the otherParent not the subentity
   12768             :         }
   12769             :         else
   12770             :         {
   12771             :                 [other adjustVelocity:vel2a];
   12772             :         }
   12773             :         
   12774             :         [self adjustVelocity:vel1a];
   12775             :         
   12776             :         BOOL selfDestroyed = (dam1 > energy);
   12777             :         BOOL otherDestroyed = (dam2 > [other energy]) && !otherIsStation;
   12778             :         
   12779             :         if (dam1 > 0.05)
   12780             :         {
   12781             :                 [self takeScrapeDamage: dam1 from:other];
   12782             :                 if (selfDestroyed)      // inelastic! - take xplosion velocity damage instead
   12783             :                 {
   12784             :                         vel2a = vector_multiply_scalar(vel2a, -1);
   12785             :                         [other adjustVelocity:vel2a];
   12786             :                 }
   12787             :         }
   12788             :         
   12789             :         if (dam2 > 0.05)
   12790             :         {
   12791             :                 if (otherParent != nil && ![otherParent isFrangible])
   12792             :                 {
   12793             :                         [otherParent takeScrapeDamage: dam2 from:self];
   12794             :                 }
   12795             :                 else
   12796             :                 {
   12797             :                         [other  takeScrapeDamage: dam2 from:self];
   12798             :                 }
   12799             :                 
   12800             :                 if (otherDestroyed)     // inelastic! - take explosion velocity damage instead
   12801             :                 {
   12802             :                         vel1a = vector_multiply_scalar(vel1a, -1);
   12803             :                         [self adjustVelocity:vel1a];
   12804             :                 }
   12805             :         }
   12806             :         
   12807             :         if (!selfDestroyed && !otherDestroyed)
   12808             :         {
   12809             :                 float t = 10.0 * [UNIVERSE getTimeDelta];       // 10 ticks
   12810             :                 
   12811             :                 HPVector pos1a = HPvector_add([self position], vectorToHPVector(vector_multiply_scalar(loc, t * v1a)));
   12812             :                 [self setPosition:pos1a];
   12813             :                 
   12814             :                 if (!otherIsStation)
   12815             :                 {
   12816             :                         HPVector pos2a = HPvector_add([other position], vectorToHPVector(vector_multiply_scalar(loc, t * v2b)));
   12817             :                         [other setPosition:pos2a];
   12818             :                 }
   12819             :         }
   12820             :         
   12821             :         // remove self from other's collision list
   12822             :         [[other collisionArray] removeObject:self];
   12823             :         
   12824             :         [self doScriptEvent:OOJSID("shipCollided") withArgument:other andReactToAIMessage:@"COLLISION"];
   12825             :         [other doScriptEvent:OOJSID("shipCollided") withArgument:self andReactToAIMessage:@"COLLISION"];
   12826             :         
   12827             :         return YES;
   12828             : }
   12829             : 
   12830             : 
   12831             : - (Vector) thrustVector
   12832             : {
   12833             :         return vector_multiply_scalar(v_forward, flightSpeed);
   12834             : }
   12835             : 
   12836             : 
   12837           0 : - (Vector) velocity
   12838             : {
   12839             :         return vector_add([super velocity], [self thrustVector]);
   12840             : }
   12841             : 
   12842             : 
   12843             : - (void) setTotalVelocity:(Vector)vel
   12844             : {
   12845             :         [self setVelocity:vector_subtract(vel, [self thrustVector])];
   12846             : }
   12847             : 
   12848             : 
   12849             : - (void) adjustVelocity:(Vector) xVel
   12850             : {
   12851             :         velocity = vector_add(velocity, xVel);
   12852             : }
   12853             : 
   12854             : 
   12855             : - (void) addImpactMoment:(Vector) moment fraction:(GLfloat) howmuch
   12856             : {
   12857             :         velocity = vector_add(velocity, vector_multiply_scalar(moment, howmuch / mass));
   12858             : }
   12859             : 
   12860             : 
   12861             : - (BOOL) canScoop:(ShipEntity*)other
   12862             : {
   12863             :         if (other == nil)                                                       return NO;
   12864             :         if (![self hasCargoScoop])                                              return NO;
   12865             :         if ([cargo count] >= [self maxAvailableCargoSpace])  return NO;
   12866             :         if (scanClass == CLASS_CARGO)                           return NO;  // we have no power so we can't scoop
   12867             :         if ([other scanClass] != CLASS_CARGO)           return NO;
   12868             :         if ([other cargoType] == CARGO_NOT_CARGO)       return NO;
   12869             :         
   12870             :         if ([other isStation])                                          return NO;
   12871             : 
   12872             :         HPVector  loc = HPvector_between(position, [other position]);
   12873             :         
   12874             :         if (dot_product(v_forward, HPVectorToVector(loc)) < 0.0f)            return NO;  // Must be in front of us
   12875             :         if ([self isPlayer] && dot_product(v_up, HPVectorToVector(loc)) > 0.0f)  return NO;  // player has to scoop on underside, give more flexibility to NPCs
   12876             :         
   12877             :         return YES;
   12878             : }
   12879             : 
   12880             : 
   12881             : - (void) getTractoredBy:(ShipEntity *)other
   12882             : {
   12883             :         if([self status] == STATUS_BEING_SCOOPED) return; // both cargo and ship call this. Act only once.
   12884             :         desired_speed = 0.0;
   12885             :         [self setAITo:@"nullAI.plist"];       // prevent AI from changing status or behaviour.
   12886             :         behaviour = BEHAVIOUR_TRACTORED;
   12887             :         [self setStatus:STATUS_BEING_SCOOPED];
   12888             :         [self addTarget:other];
   12889             :         [self setOwner:other];
   12890             :         // should we make this an all rather than first 16? - CIM
   12891             :         // made it ignore other cargopods and similar at least. - CIM 28/7/2013
   12892             :         [self checkScannerIgnoringUnpowered]; 
   12893             :         unsigned i;
   12894             :         ShipEntity *scooper;
   12895             :         for (i = 0; i < n_scanned_ships ; i++)
   12896             :         {
   12897             :                 scooper = (ShipEntity *)scanned_ships[i];
   12898             :                 // 'Dibs!' - Stops other ships from trying to scoop/shoot this cargo.
   12899             :                 if (other != scooper && (id) self == [scooper primaryTarget])
   12900             :                 {
   12901             :                         [scooper noteLostTarget];
   12902             :                 }
   12903             :         }
   12904             : }
   12905             : 
   12906             : 
   12907             : - (void) scoopIn:(ShipEntity *)other
   12908             : {
   12909             :         [other getTractoredBy:self];
   12910             : }
   12911             : 
   12912             : 
   12913           0 : - (void) suppressTargetLost
   12914             : {
   12915             :         
   12916             : }
   12917             : 
   12918             : 
   12919             : - (void) scoopUp:(ShipEntity *)other
   12920             : {
   12921             :         [self scoopUpProcess:other processEvents:YES processMessages:YES];
   12922             : }
   12923             : 
   12924             : 
   12925             : - (void) scoopUpProcess:(ShipEntity *)other processEvents:(BOOL) procEvents processMessages:(BOOL) procMessages
   12926             : {
   12927             :         if (other == nil)  return;
   12928             :         
   12929             :         OOCommodityType co_type = nil;
   12930             :         OOCargoQuantity co_amount;
   12931             :         
   12932             :         // don't even think of trying to scoop if the cargo hold is already full
   12933             :         if (max_cargo && [cargo count] >= [self maxAvailableCargoSpace])
   12934             :         {
   12935             :                 [other setStatus:STATUS_IN_FLIGHT];
   12936             :                 return;
   12937             :         }
   12938             :         
   12939             :         switch ([other cargoType])
   12940             :         {
   12941             :                 case CARGO_RANDOM:
   12942             :                         co_type = [other commodityType];
   12943             :                         co_amount = [other commodityAmount];
   12944             :                         break;
   12945             :                 
   12946             :                 case CARGO_SCRIPTED_ITEM:
   12947             :                         {
   12948             :                                 //scripting
   12949             :                                 PlayerEntity *player = PLAYER;
   12950             :                                 [player setScriptTarget:self];
   12951             :                                 if (procEvents)
   12952             :                                 {
   12953             :                                         [other doScriptEvent:OOJSID("shipWasScooped") withArgument:self];
   12954             :                                 }
   12955             :                                 
   12956             :                                 if ([other commodityType] != nil)
   12957             :                                 {
   12958             :                                         co_type = [other commodityType];
   12959             :                                         co_amount = [other commodityAmount];
   12960             :                                         // don't show scoop message now, will happen later.
   12961             :                                 }
   12962             :                                 else
   12963             :                                 {
   12964             :                                         if (isPlayer && [other showScoopMessage] && procMessages)
   12965             :                                         {
   12966             :                                                 [UNIVERSE clearPreviousMessage];
   12967             :                                                 NSString *shipName = [other displayName];
   12968             :                                                 [UNIVERSE addMessage:OOExpandKey(@"scripted-item-scooped", shipName) forCount:4];
   12969             :                                         }
   12970             :                                         [other setCommodityForPod:nil andAmount:0];
   12971             :                                         co_amount = 0;
   12972             :                                         co_type = nil;
   12973             :                                 }
   12974             :                         }
   12975             :                         break;
   12976             :                 
   12977             :                 default :
   12978             :                         co_amount = 0;
   12979             :                         co_type = nil;
   12980             :                         break;
   12981             :         }
   12982             :         
   12983             :         /*      Bug: docking failed due to NSRangeException while looking for element
   12984             :                 NSNotFound of cargo mainfest in -[PlayerEntity unloadCargoPods].
   12985             :                 Analysis: bad cargo pods being generated due to
   12986             :                 -[Universe commodityForName:] looking in wrong place for names.
   12987             :                 Fix 1: fix -[Universe commodityForName:].
   12988             :                 Fix 2: catch NSNotFound here and substitute random cargo type.
   12989             :                 -- Ahruman 20070714
   12990             :         */
   12991             :         if (co_type == nil && co_amount > 0)
   12992             :         {
   12993             :                 co_type = [UNIVERSE getRandomCommodity];
   12994             :                 co_amount = [UNIVERSE getRandomAmountOfCommodity:co_type];
   12995             :         }
   12996             :         
   12997             :         if (co_amount > 0)
   12998             :         {
   12999             :                 [other setCommodity:co_type andAmount:co_amount];   // belt and braces setting this!
   13000             :                 cargo_flag = CARGO_FLAG_CANISTERS;
   13001             :                 
   13002             :                 if (isPlayer)
   13003             :                 {
   13004             :                         if ([other crew])                       
   13005             :                         {
   13006             :                                 if ([other showScoopMessage] && procMessages)
   13007             :                                 {
   13008             :                                         [UNIVERSE clearPreviousMessage];
   13009             :                                         unsigned i;
   13010             :                                         for (i = 0; i < [[other crew] count]; i++)
   13011             :                                         {
   13012             :                                                 OOCharacter *rescuee = [[other crew] objectAtIndex:i];
   13013             :                                                 NSString *characterName = [rescuee name];
   13014             :                                                 if ([rescuee legalStatus])
   13015             :                                                 {
   13016             :                                                         [UNIVERSE addMessage:OOExpandKey(@"scoop-captured-character", characterName) forCount: 4.5];
   13017             :                                                 }
   13018             :                                                 else if ([rescuee insuranceCredits])
   13019             :                                                 {
   13020             :                                                         [UNIVERSE addMessage:OOExpandKey(@"scoop-rescued-character", characterName) forCount: 4.5];
   13021             :                                                 }
   13022             :                                                 else
   13023             :                                                 {
   13024             :                                                         [UNIVERSE addMessage: DESC(@"scoop-got-slave") forCount: 4.5];
   13025             :                                                 }
   13026             :                                         }
   13027             :                                 }
   13028             :                                 if (procEvents) 
   13029             :                                 {
   13030             :                                         [(PlayerEntity *)self playEscapePodScooped];
   13031             :                                 }
   13032             :                         }
   13033             :                         else
   13034             :                         {
   13035             :                                 if ([other showScoopMessage] && procMessages)
   13036             :                                 {
   13037             :                                         [UNIVERSE clearPreviousMessage];
   13038             :                                         [UNIVERSE addMessage:[UNIVERSE describeCommodity:co_type amount:co_amount] forCount:4.5];
   13039             :                                 }
   13040             :                         }
   13041             :                 }
   13042             :                 [cargo insertObject:other atIndex:0];   // places most recently scooped object at eject position
   13043             :                 [other setStatus:STATUS_IN_HOLD];
   13044             :                 [other performTumble];
   13045             :                 [shipAI message:@"CARGO_SCOOPED"];
   13046             :                 if (max_cargo && [cargo count] >= [self maxAvailableCargoSpace])  [shipAI message:@"HOLD_FULL"];
   13047             :         }
   13048             :         if (procEvents)
   13049             :         {
   13050             :                 [self doScriptEvent:OOJSID("shipScoopedOther") withArgument:other]; // always fire, even without commodity.
   13051             :         }
   13052             : 
   13053             :         // if shipScoopedOther does something strange to the object, we must
   13054             :         // then remove it from the hold, or it will be over-retained
   13055             :         if ([other status] != STATUS_IN_HOLD) 
   13056             :         {
   13057             :                 if ([cargo containsObject:other])
   13058             :                 {
   13059             :                         [cargo removeObject:other];
   13060             :                 }
   13061             :         }
   13062             : 
   13063             :         [[other collisionArray] removeObject:self];                     // so it can't be scooped twice!
   13064             :         // make sure other ships trying to scoop it lose it
   13065             :         // probably already happened, but some may have acquired it
   13066             :         // after the scooping started, and they might get stuck in a scooping
   13067             :         // attempt as a result
   13068             :         [self checkScannerIgnoringUnpowered];
   13069             :         unsigned i;
   13070             :         ShipEntity *scooper;
   13071             :         for (i = 0; i < n_scanned_ships ; i++)
   13072             :         {
   13073             :                 scooper = (ShipEntity *)scanned_ships[i];
   13074             :                 if (self != scooper && (id) other == [scooper primaryTargetWithoutValidityCheck])
   13075             :                 {
   13076             :                         [scooper noteLostTarget];
   13077             :                 }
   13078             :         }
   13079             : 
   13080             :         [self suppressTargetLost];
   13081             :         [UNIVERSE removeEntity:other];
   13082             : }
   13083             : 
   13084             : 
   13085             : - (BOOL) cascadeIfAppropriateWithDamageAmount:(double)amount cascadeOwner:(Entity *)owner
   13086             : {
   13087             :         BOOL cascade = NO;
   13088             :         switch ([self scanClass])
   13089             :         {
   13090             :                 case CLASS_WORMHOLE:
   13091             :                 case CLASS_ROCK:
   13092             :                 case CLASS_CARGO:
   13093             :                 case CLASS_VISUAL_EFFECT:
   13094             :                 case CLASS_BUOY:
   13095             :                         // does not normally cascade
   13096             :                         if ((fuel > MIN_FUEL) || isStation) 
   13097             :                         {
   13098             :                                 //we have fuel onboard so we can still go pop, or we are a station which can
   13099             :                         }
   13100             :                         else break;
   13101             :                         
   13102             :                 case CLASS_STATION:
   13103             :                 case CLASS_MINE:
   13104             :                 case CLASS_PLAYER:
   13105             :                 case CLASS_POLICE:
   13106             :                 case CLASS_MILITARY:
   13107             :                 case CLASS_THARGOID:
   13108             :                 case CLASS_MISSILE:
   13109             :                 case CLASS_NOT_SET:
   13110             :                 case CLASS_NO_DRAW:
   13111             :                 case CLASS_NEUTRAL:
   13112             :                 case CLASS_TARGET:
   13113             :                         // ...start a chain reaction, if we're dying and have a non-trivial amount of energy.
   13114             :                         if (energy < amount && energy > 10 && [self countsAsKill])
   13115             :                         {
   13116             :                                 cascade = YES;  // confirm we're cascading, then try to add our cascade to UNIVERSE.
   13117             :                                 [UNIVERSE addEntity:[OOQuiriumCascadeEntity quiriumCascadeFromShip:self]];
   13118             :                         }
   13119             :                         break;
   13120             :                         //no default thanks, we want the compiler to tell us if we missed a case.
   13121             :         }
   13122             :         return cascade;
   13123             : }
   13124             : 
   13125             : 
   13126           0 : - (void) takeEnergyDamage:(double)amount from:(Entity *)ent becauseOf:(Entity *)other weaponIdentifier:(NSString *)weaponIdentifier
   13127             : {
   13128             :         if ([self status] == STATUS_DEAD)  return;
   13129             :         if (amount <= 0.0)  return;
   13130             :         
   13131             :         BOOL energyMine = [ent isCascadeWeapon];
   13132             :         BOOL cascade = NO;
   13133             :         if (energyMine)
   13134             :         {
   13135             :                 cascade = [self cascadeIfAppropriateWithDamageAmount:amount cascadeOwner:[ent owner]];
   13136             :         }
   13137             :         
   13138             :         energy -= amount;
   13139             :         /* Heat increase from energy impacts will never directly cause
   13140             :          * overheating - too easy for missile hits to cause an uncredited
   13141             :          * death by overheating - CIM */
   13142             :         if (ship_temperature < SHIP_MAX_CABIN_TEMP)
   13143             :         {
   13144             :                 ship_temperature += amount * SHIP_ENERGY_DAMAGE_TO_HEAT_FACTOR / [self heatInsulation];
   13145             :                 if (ship_temperature > SHIP_MAX_CABIN_TEMP)
   13146             :                 {
   13147             :                         ship_temperature = SHIP_MAX_CABIN_TEMP;
   13148             :                 }
   13149             :         }
   13150             : 
   13151             : 
   13152             :         being_mined = NO;
   13153             :         ShipEntity *hunter = nil;
   13154             :         
   13155             :         hunter = [other rootShipEntity];
   13156             :         if (hunter == nil && [other isShip]) hunter = (ShipEntity *)other;
   13157             :         
   13158             :         // must check for this before potentially deleting 'other' for cloaking
   13159             :         if ((other)&&([other isShip]))
   13160             :         {
   13161             :                 being_mined = [(ShipEntity *)other isMining];
   13162             :         }
   13163             : 
   13164             :         if (hunter !=nil && [self owner] != hunter) // our owner could be the same entity as the one responsible for our taking damage in the case of submunitions
   13165             :         {
   13166             :                 if ([hunter isCloaked])
   13167             :                 {
   13168             :                         [self doScriptEvent:OOJSID("shipBeingAttackedByCloaked") andReactToAIMessage:@"ATTACKED_BY_CLOAKED"];
   13169             :                         
   13170             :                         // lose it!
   13171             :                         other = nil;
   13172             :                         hunter = nil;
   13173             :                 }
   13174             :         }
   13175             :         else
   13176             :         {
   13177             :                 hunter = nil;
   13178             :         }
   13179             :         
   13180             :         // if the other entity is a ship note it as an aggressor
   13181             :         if (hunter != nil)
   13182             :         {
   13183             :                 BOOL iAmTheLaw = [self isPolice];
   13184             :                 BOOL uAreTheLaw = [hunter isPolice];
   13185             :                 
   13186             :                 DESTROY(_lastEscortTarget);     // we're being attacked, escorts can scramble!
   13187             :                 
   13188             :                 [self setPrimaryAggressor:hunter];
   13189             :                 [self setFoundTarget:hunter];
   13190             : 
   13191             :                 // firing on an innocent ship is an offence
   13192             :                 [self broadcastHitByLaserFrom: hunter];
   13193             : 
   13194             :                 // tell ourselves we've been attacked
   13195             :                 if (energy > 0)
   13196             :                 {
   13197             :                         [self respondToAttackFrom:ent becauseOf:hunter];
   13198             :                 }
   13199             : 
   13200             :                 OOShipGroup *group = [self group];
   13201             :                 // JSAIs manage group notifications themselves
   13202             :                 if (![self hasNewAI])
   13203             :                 {
   13204             :                         // additionally, tell our group we've been attacked
   13205             :                         if (group != nil && group != [hunter group] && !(iAmTheLaw || uAreTheLaw))
   13206             :                         {
   13207             :                                 if ([self isTrader] || [self isEscort])
   13208             :                                 {
   13209             :                                         ShipEntity *groupLeader = [group leader];
   13210             :                                         if (groupLeader != self)
   13211             :                                         {
   13212             :                                                 [groupLeader setFoundTarget:hunter];
   13213             :                                                 [groupLeader setPrimaryAggressor:hunter];
   13214             :                                                 [groupLeader respondToAttackFrom:ent becauseOf:hunter];
   13215             :                                                 //unsetting group leader for carriers can break stuff
   13216             :                                         }
   13217             :                                 }
   13218             :                                 if ([self isPirate])
   13219             :                                 {
   13220             :                                         NSEnumerator            *groupEnum = nil;
   13221             :                                         ShipEntity                      *otherPirate = nil;
   13222             :                                 
   13223             :                                         for (groupEnum = [group mutationSafeEnumerator]; (otherPirate = [groupEnum nextObject]); )
   13224             :                                         {
   13225             :                                                 if (otherPirate != self && randf() < 0.5)    // 50% chance they'll help
   13226             :                                                 {
   13227             :                                                         [otherPirate setFoundTarget:hunter];
   13228             :                                                         [otherPirate setPrimaryAggressor:hunter];
   13229             :                                                         [otherPirate respondToAttackFrom:ent becauseOf:hunter];
   13230             :                                                 }
   13231             :                                         }
   13232             :                                 }
   13233             :                                 else if (iAmTheLaw)
   13234             :                                 {
   13235             :                                         NSEnumerator            *groupEnum = nil;
   13236             :                                         ShipEntity                      *otherPolice = nil;
   13237             :                                 
   13238             :                                         for (groupEnum = [group mutationSafeEnumerator]; (otherPolice = [groupEnum nextObject]); )
   13239             :                                         {
   13240             :                                                 if (otherPolice != self)
   13241             :                                                 {
   13242             :                                                         [otherPolice setFoundTarget:hunter];
   13243             :                                                         [otherPolice setPrimaryAggressor:hunter];
   13244             :                                                         [otherPolice respondToAttackFrom:ent becauseOf:hunter];
   13245             :                                                 }
   13246             :                                         }
   13247             :                                 }
   13248             :                         }
   13249             :                 }
   13250             : 
   13251             :                 // if I'm a copper and you're not, then mark the other as an offender!
   13252             :                 if (iAmTheLaw && !uAreTheLaw)
   13253             :                 {
   13254             :                         // JSAI's can choose not to do this for friendly fire purposes
   13255             :                         if (![self hasNewAI]) 
   13256             :                         {
   13257             :                                 [hunter markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice];
   13258             :                         }
   13259             :                 }
   13260             : 
   13261             :                 if ((group != nil && [hunter group] == group) || (iAmTheLaw && uAreTheLaw))
   13262             :                 {
   13263             :                         // avoid shooting each other
   13264             :                         if ([hunter behaviour] == BEHAVIOUR_ATTACK_FLY_TO_TARGET)       // avoid me please!
   13265             :                         {
   13266             :                                 [hunter setBehaviour:BEHAVIOUR_ATTACK_FLY_FROM_TARGET];
   13267             :                                 [hunter setDesiredSpeed:[hunter maxFlightSpeed]];
   13268             :                         }
   13269             :                 }
   13270             : 
   13271             :         }
   13272             :         
   13273             :         OOShipDamageType damageType = kOODamageTypeEnergy;
   13274             :         if (suppressExplosion)  damageType = kOODamageTypeRemoved;
   13275             :         else if (energyMine)  damageType = kOODamageTypeCascadeWeapon;
   13276             :         
   13277             :         if (!suppressExplosion)
   13278             :         {
   13279             :                 [self noteTakingDamage:amount from:other type:damageType];
   13280             :                 if (cascade) energy = 0.0; // explicit set energy to zero in case an oxp raised the energy in previous line.
   13281             :         }
   13282             : 
   13283             :         // die if I'm out of energy
   13284             :         if (energy <= 0.0)
   13285             :         {
   13286             :                 // backup check just in case scripts have reduced energy
   13287             :                 if (self != [UNIVERSE station]) 
   13288             :                 {
   13289             :                         if (hunter != nil)  [hunter noteTargetDestroyed:self];
   13290             :                         [self getDestroyedBy:other damageType:damageType];
   13291             :                 }
   13292             :         }
   13293             :         else
   13294             :         {
   13295             :                 // warn if I'm low on energy
   13296             :                 if (energy < maxEnergy * 0.25)
   13297             :                 {
   13298             :                         [self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"];
   13299             :                 }
   13300             :                 if ((energy < maxEnergy *0.125 || (energy < 64 && energy < amount*2)) && [self hasEscapePod] && (ranrot_rand() & 3) == 0)  // 25% chance he gets to an escape pod
   13301             :                 {
   13302             :                         [self abandonShip];
   13303             :                 }
   13304             :         }
   13305             : }
   13306             : 
   13307             : 
   13308             : - (BOOL) abandonShip
   13309             : {
   13310             :         BOOL OK = NO;
   13311             :         if ([self isPlayer] && [(PlayerEntity *)self isDocked])
   13312             :         {
   13313             :                 OOLog(@"ShipEntity.abandonShip.failed", @"%@", @"Player cannot abandon ship while docked.");
   13314             :                 return OK;
   13315             :         }
   13316             :         
   13317             :         if (![self hasEscapePod])
   13318             :         {
   13319             :                 OOLog(@"ShipEntity.abandonShip.failed", @"Ship abandonment was requested for %@, but this ship does not carry escape pod(s).", self);
   13320             :                 return OK;
   13321             :         }
   13322             :                 
   13323             :         if (EXPECT([self launchEscapeCapsule] != NO_TARGET))    // -launchEscapeCapsule takes care of everything for the player
   13324             :         {
   13325             :                 if (![self isPlayer])
   13326             :                 {
   13327             :                         OK = YES;
   13328             :                         // if multiple items providing escape pod, remove all of them (NPC process)
   13329             :                         while ([self hasEquipmentItemProviding:@"EQ_ESCAPE_POD"])
   13330             :                         {
   13331             :                                 [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_ESCAPE_POD"]];
   13332             :                         }
   13333             :                         [self setAITo:@"nullAI.plist"];
   13334             :                         behaviour = BEHAVIOUR_IDLE;
   13335             :                         frustration = 0.0;
   13336             :                         [self setScanClass: CLASS_CARGO];                       // we're unmanned now!
   13337             :                         thrust = thrust * 0.5;
   13338             :                         if (thrust > 5) thrust = 5; // 5 is the thrust of an escape-capsule
   13339             :                         desired_speed = 0.0;
   13340             :                         if ([self group]) [self setGroup:nil]; // remove self from group.
   13341             :                         if (![self isSubEntity] && [self owner]) [self setOwner:nil]; //unset owner, but not if we are a subent
   13342             :                         if ([self hasEscorts])
   13343             :                         {
   13344             :                                 OOShipGroup                     *escortGroup = [self escortGroup];
   13345             :                                 NSEnumerator            *escortEnum = nil;
   13346             :                                 ShipEntity                      *escort = nil;
   13347             :                                 // Note: works on escortArray rather than escortEnumerator because escorts may be mutated.
   13348             :                                 for (escortEnum = [[self escortArray] objectEnumerator]; (escort = [escortEnum nextObject]); )
   13349             :                                 {
   13350             :                                         // act individually now!
   13351             :                                         if ([escort group] == escortGroup)  [escort setGroup:nil];
   13352             :                                         if ([escort owner] == self)  [escort setOwner:escort];
   13353             :                                 }
   13354             :                                 
   13355             :                                 // We now have no escorts.
   13356             :                                 [_escortGroup release];
   13357             :                                 _escortGroup = nil;
   13358             :                         }
   13359             :                 }
   13360             :         }
   13361             :         else if (EXPECT([self isSubEntity]))
   13362             :         {
   13363             :                 // may still have launched passenger pods even if no crew
   13364             :                 // if multiple items providing escape pod, remove all of them (NPC process)
   13365             :                 while ([self hasEquipmentItemProviding:@"EQ_ESCAPE_POD"])
   13366             :                 {
   13367             :                         [self removeEquipmentItem:[self equipmentItemProviding:@"EQ_ESCAPE_POD"]];
   13368             :                 }
   13369             : 
   13370             :         }
   13371             :         else
   13372             :         {
   13373             :                 // this shouldn't happen any more!
   13374             :                 OOLog(@"ShipEntity.abandonShip.notPossible", @"Ship %@ cannot be abandoned at this time.", self);
   13375             :         }
   13376             :         return OK;
   13377             : }
   13378             : 
   13379             : 
   13380             : - (void) takeScrapeDamage:(double) amount from:(Entity *)ent
   13381             : {
   13382             :         if ([self status] == STATUS_DEAD)  return;
   13383             : 
   13384             :         if ([self status] == STATUS_LAUNCHING|| [ent status] == STATUS_LAUNCHING)
   13385             :         {
   13386             :                 // no collisions during launches please
   13387             :                 return;
   13388             :         }
   13389             :         
   13390             :         energy -= amount;
   13391             :         [self noteTakingDamage:amount from:ent type:kOODamageTypeScrape];
   13392             :         
   13393             :         // oops we hit too hard!!!
   13394             :         if (energy <= 0.0)
   13395             :         {
   13396             :                 float frag_chance = [ent mass]*10/[self mass];
   13397             :                 /* impacts from heavier entities produce fragments
   13398             :                  * impacts from lighter entities might do but not always
   13399             :                  * asteroid-asteroid impacts likely to fragment
   13400             :                  * ship-asteroid impacts might, or might just vaporise it
   13401             :                  * projectile weapons just get the default chance
   13402             :                  */
   13403             :                 if (randf() < frag_chance)
   13404             :                 {
   13405             :                         being_mined = YES;  // same as using a mining laser
   13406             :                 }
   13407             :                 if ([ent isShip])
   13408             :                 {
   13409             :                         [(ShipEntity *)ent noteTargetDestroyed:self];
   13410             :                 }
   13411             :                 [self getDestroyedBy:ent damageType:kOODamageTypeScrape];
   13412             :         }
   13413             :         else
   13414             :         {
   13415             :                 // warn if I'm low on energy
   13416             :                 if (energy < maxEnergy * 0.25)
   13417             :                 {
   13418             :                         [self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"];
   13419             :                 }
   13420             :         }
   13421             : }
   13422             : 
   13423             : 
   13424             : - (void) takeHeatDamage:(double)amount
   13425             : {
   13426             :         if ([self status] == STATUS_DEAD)  return;
   13427             : 
   13428             :         if ([self isSubEntity])
   13429             :         {
   13430             :                 ShipEntity* owner = [self owner];
   13431             :                 if (![owner isFrangible]) 
   13432             :                 {
   13433             :                         return;
   13434             :                 }
   13435             :         }
   13436             :         
   13437             :         energy -= amount;
   13438             :         throw_sparks = YES;
   13439             :         
   13440             :         [self noteTakingDamage:amount from:nil type:kOODamageTypeHeat];
   13441             :         
   13442             :         // oops we're burning up!
   13443             :         if (energy <= 0.0)
   13444             :         {
   13445             :                 [self getDestroyedBy:nil damageType:kOODamageTypeHeat];
   13446             :         }
   13447             :         else
   13448             :         {
   13449             :                 // warn if I'm low on energy
   13450             :                 if (energy < maxEnergy * 0.25)
   13451             :                 {
   13452             :                         [self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"];
   13453             :                 }
   13454             :         }
   13455             : }
   13456             : 
   13457             : 
   13458             : - (void) enterDock:(StationEntity *)station
   13459             : {
   13460             :         // throw these away now we're docked...
   13461             :         if (dockingInstructions != nil)
   13462             :         {
   13463             :                 [dockingInstructions autorelease];
   13464             :                 dockingInstructions = nil;
   13465             :         }
   13466             :         
   13467             :         [self doScriptEvent:OOJSID("shipWillDockWithStation") withArgument:station];
   13468             :         [self doScriptEvent:OOJSID("shipDockedWithStation") withArgument:station];
   13469             :         [shipAI message:@"DOCKED"];
   13470             :         [station noteDockedShip:self];
   13471             :         [UNIVERSE removeEntity:self];
   13472             : }
   13473             : 
   13474             : 
   13475             : - (void) leaveDock:(StationEntity *)station
   13476             : {
   13477             :         // This code is never used. Currently npc ships are only launched from the stations launch queue.
   13478             :         if (station == nil)  return;
   13479             :         
   13480             :         [station launchShip:self];
   13481             : 
   13482             : }
   13483             : 
   13484             : 
   13485             : - (void) enterWormhole:(WormholeEntity *) w_hole
   13486             : {
   13487             :         [self enterWormhole:w_hole replacing:YES];
   13488             : }
   13489             : 
   13490             : 
   13491             : - (void) enterWormhole:(WormholeEntity *) w_hole replacing:(BOOL)replacing
   13492             : {
   13493             :         if (w_hole == nil)  return;
   13494             :         if ([self status] == STATUS_ENTERING_WITCHSPACE)
   13495             :         {
   13496             :                 return; // has already entered a different wormhole
   13497             :         }
   13498             :         // Replacement ships now handled by system repopulator
   13499             : 
   13500             :         // MKW 2011.02.27 - Moved here from ShipEntityAI so escorts reliably follow
   13501             :         //                  mother in all wormhole cases, not just when the ship
   13502             :         //                  creates the wormhole.
   13503             :         [self addTarget:w_hole];
   13504             :         [self setFoundTarget:w_hole];
   13505             :         [shipAI reactToMessage:@"WITCHSPACE OKAY" context:@"performHyperSpaceExit"];        // must be a reaction, the ship is about to disappear
   13506             :         
   13507             :         // CIM 2012.07.22 above only covers those cases where ship expected to leave
   13508             :         if ([[self escortArray] count] > 1)
   13509             :         {
   13510             :                 // so wormhole escorts anyway if it leaves unexpectedly.
   13511             :                 [self wormholeEscorts];
   13512             :         }
   13513             : 
   13514             :         if ([self scriptedMisjump])
   13515             :         {
   13516             :                 [self setScriptedMisjump:NO];
   13517             :                 [w_hole setMisjumpWithRange:[self scriptedMisjumpRange]];
   13518             :                 [self setScriptedMisjumpRange:0.5];
   13519             :         }
   13520             :         [w_hole suckInShip: self];      // removes ship from universe
   13521             : }
   13522             : 
   13523             : 
   13524             : - (void) enterWitchspace
   13525             : {
   13526             :         [UNIVERSE addWitchspaceJumpEffectForShip:self];
   13527             :         [shipAI message:@"ENTERED_WITCHSPACE"];
   13528             :         
   13529             :         if (![[UNIVERSE sun] willGoNova])
   13530             :         {
   13531             :                 // if the sun's not going nova, add a new ship like this one leaving.
   13532             :                 [UNIVERSE witchspaceShipWithPrimaryRole:[self primaryRole]];
   13533             :         }
   13534             :         
   13535             :         [UNIVERSE removeEntity:self];
   13536             : }
   13537             : 
   13538             : 
   13539             : - (void) leaveWitchspace
   13540             : {
   13541             :         Quaternion      q1;
   13542             :         quaternion_set_random(&q1);
   13543             :         Vector          v1 = vector_forward_from_quaternion(q1);
   13544             :         double          d1 = 0.0;
   13545             :         
   13546             :         GLfloat min_d1 = [UNIVERSE safeWitchspaceExitDistance];
   13547             : 
   13548             :         while (fabs(d1) < min_d1)
   13549             :         {
   13550             :                 // not scannerRange - has no effect on witchspace exit
   13551             :                 d1 = SCANNER_MAX_RANGE * (randf() - randf());
   13552             :         }
   13553             :         
   13554             :         HPVector exitposition = [UNIVERSE getWitchspaceExitPosition];
   13555             :         exitposition.x += v1.x * d1; // randomise exit position
   13556             :         exitposition.y += v1.y * d1;
   13557             :         exitposition.z += v1.z * d1;
   13558             :         [self setPosition:exitposition];
   13559             :         [self witchspaceLeavingEffects];
   13560             : }
   13561             : 
   13562             : 
   13563             : - (BOOL) witchspaceLeavingEffects
   13564             : {
   13565             :         // all ships exiting witchspace will share the same orientation.
   13566             :         orientation = [UNIVERSE getWitchspaceExitRotation];
   13567             :         flightRoll = 0.0;
   13568             :         stick_roll = 0.0;
   13569             :         flightPitch = 0.0;
   13570             :         stick_pitch = 0.0;
   13571             :         flightYaw = 0.0;
   13572             :         stick_yaw = 0.0;
   13573             :         flightSpeed = 50.0; // constant speed same for all ships
   13574             : // was a quarter of max speed, so the Anaconda speeds up and most
   13575             : // others slow down - CIM
   13576             : // will be overridden if left witchspace via a genuine wormhole
   13577             :         velocity = kZeroVector;
   13578             :         if (![UNIVERSE addEntity:self]) // AI and status get initialised here
   13579             :         {
   13580             :                 return NO;
   13581             :         }
   13582             :         [self setStatus:STATUS_EXITING_WITCHSPACE];
   13583             :         [shipAI message:@"EXITED_WITCHSPACE"];
   13584             :         
   13585             :         [UNIVERSE addWitchspaceJumpEffectForShip:self];
   13586             :         [self setStatus:STATUS_IN_FLIGHT];
   13587             :         return YES;
   13588             : }
   13589             : 
   13590             : 
   13591             : - (void) markAsOffender:(int)offence_value
   13592             : {
   13593             :         [self markAsOffender:offence_value withReason:kOOLegalStatusReasonUnknown];
   13594             : }
   13595             : 
   13596             : 
   13597             : - (void) markAsOffender:(int)offence_value withReason:(OOLegalStatusReason)reason
   13598             : {
   13599             :         if (![self isPolice] && ![self isCloaked] && self != [UNIVERSE station])
   13600             :         {
   13601             :                 if ([self isSubEntity]) 
   13602             :                 {
   13603             :                         [[self parentEntity] markAsOffender:offence_value withReason:reason];
   13604             :                 }
   13605             :                 else
   13606             :                 {
   13607             :                         if ((scanClass == CLASS_THARGOID || scanClass == CLASS_STATION) && reason != kOOLegalStatusReasonSetup && reason != kOOLegalStatusReasonByScript)
   13608             :                         {
   13609             :                                 return; // no non-scripted bounties for thargoids and stations
   13610             :                         }
   13611             : 
   13612             :                         JSContext *context = OOJSAcquireContext();
   13613             :         
   13614             :                         jsval amountVal = JSVAL_VOID;
   13615             :                         JS_NewNumberValue(context, (bounty | offence_value)-bounty, &amountVal);
   13616             : 
   13617             :                         bounty |= offence_value; // can't set the new bounty until the size of the change is known
   13618             : 
   13619             :                         jsval reasonVal = OOJSValueFromLegalStatusReason(context, reason);
   13620             :                 
   13621             :                         ShipScriptEvent(context, self, "shipBountyChanged", amountVal, reasonVal);
   13622             :                 
   13623             :                         OOJSRelinquishContext(context);
   13624             :                 
   13625             :                 }
   13626             :         }
   13627             : }
   13628             : 
   13629             : 
   13630             : // Exposed to AI
   13631             : - (void) switchLightsOn
   13632             : {
   13633             :         NSEnumerator    *subEnum = nil;
   13634             :         OOFlasherEntity *se = nil;
   13635             :         ShipEntity              *sub = nil;
   13636             :         
   13637             :         _lightsActive = YES;
   13638             :         
   13639             :         for (subEnum = [self flasherEnumerator]; (se = [subEnum nextObject]); )
   13640             :         {
   13641             :                 [se setActive:YES];
   13642             :         }
   13643             :         for (subEnum = [self shipSubEntityEnumerator]; (sub = [subEnum nextObject]); )
   13644             :         {
   13645             :                 [sub switchLightsOn];
   13646             :         }
   13647             : }
   13648             : 
   13649             : // Exposed to AI
   13650             : - (void) switchLightsOff
   13651             : {
   13652             :         NSEnumerator    *subEnum = nil;
   13653             :         OOFlasherEntity *se = nil;
   13654             :         ShipEntity              *sub = nil;
   13655             :         
   13656             :         _lightsActive = NO;
   13657             :         
   13658             :         for (subEnum = [self flasherEnumerator]; (se = [subEnum nextObject]); )
   13659             :         {
   13660             :                 [se setActive:NO];
   13661             :         }
   13662             :         for (subEnum = [self shipSubEntityEnumerator]; (sub = [subEnum nextObject]); )
   13663             :         {
   13664             :                 [sub switchLightsOff];
   13665             :         }
   13666             : }
   13667             : 
   13668             : 
   13669             : - (BOOL) lightsActive
   13670             : {
   13671             :         return _lightsActive;
   13672             : }
   13673             : 
   13674             : 
   13675             : - (void) setDestination:(HPVector) dest
   13676             : {
   13677             :         _destination = dest;
   13678             :         frustration = 0.0;      // new destination => no frustration!
   13679             : }
   13680             : 
   13681             : 
   13682             : - (void) setEscortDestination:(HPVector) dest
   13683             : {
   13684             :         _destination = dest; // don't reset frustration for escorts.
   13685             : }
   13686             : 
   13687             : 
   13688             : - (BOOL) canAcceptEscort:(ShipEntity *)potentialEscort
   13689             : {
   13690             :         if (dockingInstructions) // we are busy with docking.
   13691             :         {
   13692             :                 return NO;
   13693             :         }
   13694             :         if (scanClass != [potentialEscort scanClass]) // this makes sure that wingman can only select police, thargons only thargoids.
   13695             :         {
   13696             :                 return NO;
   13697             :         }
   13698             :         if ([self bounty] == 0 && [potentialEscort bounty] != 0) // clean mothers can only accept clean escorts
   13699             :         {
   13700             :                 return NO;
   13701             :         }
   13702             :         if (![self isEscort]) // self is NOT wingman or escort or thargon
   13703             :         {
   13704             :                 return [potentialEscort isEscort]; // is wingman or escort or thargon
   13705             :         }
   13706             :         return NO;
   13707             : }
   13708             :         
   13709             : 
   13710             : - (BOOL) acceptAsEscort:(ShipEntity *) other_ship
   13711             : {
   13712             :         // can't pair with self
   13713             :         if (self == other_ship)  return NO;
   13714             :         
   13715             :         // no longer in flight, probably entered wormhole without telling escorts.
   13716             :         if ([self status] != STATUS_IN_FLIGHT)  return NO;
   13717             :         
   13718             :         //increased stack depth at which it can accept escorts to avoid rejections at this stage.
   13719             :         //doesn't seem to have any adverse effect for now. - Kaks.
   13720             :         if ([shipAI stackDepth] > 3)
   13721             :         {
   13722             :                 OOLog(@"ship.escort.reject", @"%@ rejecting escort %@ because AI stack depth is %lu.",self, other_ship, [shipAI stackDepth]);
   13723             :                 return NO;
   13724             :         }
   13725             :         
   13726             :         if ([self canAcceptEscort:other_ship])
   13727             :         {
   13728             :                 OOShipGroup *escortGroup = [self escortGroup];
   13729             :                 
   13730             :                 if ([escortGroup containsShip:other_ship])  return YES;
   13731             :                 
   13732             :                 // check total number acceptable
   13733             :                 // the system's patrols don't have escorts set inside their dictionary, but accept max escorts.
   13734             :                 if (_maxEscortCount == 0 && ([self hasPrimaryRole:@"police"] || [self hasPrimaryRole:@"hunter"] || [self hasRole:@"thargoid-mothership"])) 
   13735             :                 {
   13736             :                         _maxEscortCount = MAX_ESCORTS;
   13737             :                 }
   13738             :                 
   13739             :                 NSUInteger maxEscorts = _maxEscortCount;        // never bigger than MAX_ESCORTS.
   13740             :                 NSUInteger escortCount = [escortGroup count] - 1;       // always 0 or higher.
   13741             :                 
   13742             :                 if (escortCount < maxEscorts)
   13743             :                 {
   13744             :                         [other_ship setGroup:escortGroup];
   13745             :                         if ([self group] == nil)
   13746             :                         {
   13747             :                                 [self setGroup:escortGroup];
   13748             :                         }
   13749             :                         else if ([self group] != escortGroup)  [[self group] addShip:other_ship];
   13750             :                         
   13751             :                         if (([other_ship maxFlightSpeed] < cruiseSpeed) && ([other_ship maxFlightSpeed] > cruiseSpeed * 0.3))
   13752             :                         {
   13753             :                                 cruiseSpeed = [other_ship maxFlightSpeed] * 0.99;
   13754             :                         }
   13755             :                         
   13756             :                         OOLog(@"ship.escort.accept", @"%@ accepting escort %@.", self, other_ship);
   13757             :                         
   13758             :                         [self doScriptEvent:OOJSID("shipAcceptedEscort") withArgument:other_ship];
   13759             :                         [other_ship doScriptEvent:OOJSID("escortAccepted") withArgument:self];
   13760             :                         [shipAI message:@"ACCEPTED_ESCORT"];
   13761             :                         return YES;
   13762             :                 }
   13763             :                 else
   13764             :                 {
   13765             :                         OOLog(@"ship.escort.reject", @"%@ already got max escorts(%ld). Escort rejected: %@.", self, escortCount, other_ship);
   13766             :                 }
   13767             :         }
   13768             :         else
   13769             :         {
   13770             :                 OOLog(@"ship.escort.reject", @"%@ failed canAcceptEscort for escort %@.", self, other_ship);
   13771             :         }
   13772             : 
   13773             :         
   13774             :         return NO;
   13775             : }
   13776             : 
   13777             : 
   13778             : // Exposed to AI
   13779             : - (void) updateEscortFormation
   13780             : {
   13781             :         _escortPositionsValid = NO;
   13782             : }
   13783             : 
   13784             : 
   13785             : /*
   13786             :         NOTE: it's tempting to call refreshEscortPositions from coordinatesForEscortPosition:
   13787             :         as needed, but that would cause unnecessary extra work if the formation
   13788             :         callback itself calls updateEscortFormation.
   13789             : */
   13790           0 : - (void) refreshEscortPositions
   13791             : {
   13792             :         if (!_escortPositionsValid)
   13793             :         {
   13794             :                 JSContext                       *context = OOJSAcquireContext();
   13795             :                 jsval                           result;
   13796             :                 jsval                           args[] = { INT_TO_JSVAL(0), INT_TO_JSVAL(_maxEscortCount) };
   13797             :                 BOOL                            OK;
   13798             :                 
   13799             :                 // Reset validity first so updateEscortFormation can be called from the update callback.
   13800             :                 _escortPositionsValid = YES;
   13801             :                 
   13802             :                 uint8_t i;
   13803             :                 for (i = 0; i < _maxEscortCount; i++)
   13804             :                 {
   13805             :                         args[0] = INT_TO_JSVAL(i);
   13806             :                         OK = [script callMethod:OOJSID("coordinatesForEscortPosition")
   13807             :                                                   inContext:context
   13808             :                                           withArguments:args count:sizeof args / sizeof *args
   13809             :                                                          result:&result];
   13810             :                         
   13811             :                         if (OK)  OK = JSValueToVector(context, result, &_escortPositions[i]);
   13812             :                         
   13813             :                         if (!OK)  _escortPositions[i] = kZeroVector;
   13814             :                 }
   13815             :                 
   13816             :                 OOJSRelinquishContext(context);
   13817             :         }
   13818             : }
   13819             : 
   13820             : 
   13821           0 : - (HPVector) coordinatesForEscortPosition:(unsigned)idx
   13822             : {
   13823             :         /*
   13824             :                 This function causes problems with Thargoids: their missiles (aka Thargons) are automatically
   13825             :                 added to the escorts group, and when a mother ship dies all thargons will attach themselves
   13826             :                 as escorts to the surviving battleships. This can lead to huge escort groups.
   13827             :                 TODO: better handling of Thargoid groups:
   13828             :                         - put thargons (& all other thargon missiles) in their own non-escort group perhaps?
   13829             :         */
   13830             :         
   13831             :         // The _escortPositions array is always MAX_ESCORTS long.
   13832             :         // Kludge: return the same last escort position if we have escorts above MAX_ESCORTS...
   13833             :         idx = MIN(idx, (unsigned)(MAX_ESCORTS - 1));
   13834             :         
   13835             :         return HPvector_add(self->position, vectorToHPVector(quaternion_rotate_vector([self normalOrientation], _escortPositions[idx])));
   13836             : }
   13837             : 
   13838             : 
   13839             : // Exposed to AI
   13840             : - (void) deployEscorts
   13841             : {
   13842             :         NSEnumerator    *escortEnum = nil;
   13843             :         ShipEntity              *escort = nil;
   13844             :         ShipEntity              *target = nil;
   13845             :         NSMutableSet    *idleEscorts = nil;
   13846             :         unsigned                deployCount;
   13847             :         
   13848             :         if ([self primaryTarget] == nil || _escortGroup == nil)  return;
   13849             :         
   13850             :         OOShipGroup *escortGroup = [self escortGroup];
   13851             :         NSUInteger escortCount = [escortGroup count] - 1;  // escorts minus leader.
   13852             :         if (escortCount == 0)  return;
   13853             :         
   13854             :         if ([self group] == nil)  [self setGroup:escortGroup];
   13855             :         
   13856             :         if ([self primaryTarget] == [self lastEscortTarget])
   13857             :         {
   13858             :                 // already deployed escorts onto this target!
   13859             :                 return;
   13860             :         }
   13861             :         
   13862             :         [self setLastEscortTarget:[self primaryTarget]];
   13863             :         
   13864             :         // Find idle escorts
   13865             :         idleEscorts = [NSMutableSet set];
   13866             :         for (escortEnum = [self escortEnumerator]; (escort = [escortEnum nextObject]); )
   13867             :         {
   13868             :                 if (![[[escort getAI] name] isEqualToString:@"interceptAI.plist"] && ![escort hasNewAI])
   13869             :                 {
   13870             :                         [idleEscorts addObject:escort];
   13871             :                 }
   13872             :                 else if ([escort hasNewAI])
   13873             :                 {
   13874             :                         // JS-based escorts get a help request
   13875             :                         [escort doScriptEvent:OOJSID("helpRequestReceived") withArgument:self andArgument:[self primaryTarget]];
   13876             :                 }
   13877             :         }
   13878             :         
   13879             :         escortCount = [idleEscorts count];
   13880             :         if (escortCount == 0)  return;
   13881             :         
   13882             :         deployCount = ranrot_rand() % escortCount + 1;
   13883             :         
   13884             :         // Deploy deployCount idle escorts.
   13885             :         target = [self primaryTarget];
   13886             :         for (escortEnum = [idleEscorts objectEnumerator]; (escort = [escortEnum nextObject]); )
   13887             :         {
   13888             :                 [escort addTarget:target];
   13889             :                 [escort setAITo:@"interceptAI.plist"];
   13890             :                 [escort doScriptEvent:OOJSID("escortAttack") withArgument:target];
   13891             :                 
   13892             :                 if (--deployCount == 0)  break;
   13893             :         }
   13894             :         
   13895             :         [self updateEscortFormation];
   13896             : }
   13897             : 
   13898             : 
   13899             : // Exposed to AI
   13900             : - (void) dockEscorts
   13901             : {
   13902             :         if (![self hasEscorts])  return;
   13903             :         
   13904             :         OOShipGroup                     *escortGroup = [self escortGroup];
   13905             :         NSEnumerator            *escortEnum = nil;
   13906             :         ShipEntity                      *escort = nil;
   13907             :         ShipEntity                      *target = [self primaryTarget];
   13908             :         unsigned                        i = 0;
   13909             :         // Note: works on escortArray rather than escortEnumerator because escorts may be mutated.
   13910             :         for (escortEnum = [[self escortArray] objectEnumerator]; (escort = [escortEnum nextObject]); )
   13911             :         {
   13912             :                 float           delay = i++ * 3.0 + 1.5;                // send them off at three second intervals
   13913             :                 AI                      *ai = [escort getAI];
   13914             :                 
   13915             :                 // act individually now!
   13916             :                 if ([escort group] == escortGroup)  [escort setGroup:nil];
   13917             :                 if ([escort owner] == self)  [escort setOwner:escort];
   13918             :                 if(target && [target isStation]) [escort setTargetStation:target];
   13919             :                 // JSAI: handles own delay
   13920             :                 if (![escort hasNewAI])
   13921             :                 {
   13922             :                         [escort setAITo:@"dockingAI.plist"];
   13923             :                         [ai setState:@"ABORT" afterDelay:delay + 0.25];
   13924             :                 }
   13925             :                 [escort doScriptEvent:OOJSID("escortDock") withArgument:[NSNumber numberWithFloat:delay]];
   13926             :         }
   13927             :         
   13928             :         // We now have no escorts.
   13929             :         [_escortGroup release];
   13930             :         _escortGroup = nil;
   13931             : }
   13932             : 
   13933             : 
   13934           0 : - (void) setTargetToNearestStationIncludingHostiles:(BOOL) includeHostiles
   13935             : {
   13936             :         // check if the groupID (parent ship) points to a station...
   13937             :         Entity          *mother = [[self group] leader];
   13938             :         if ([mother isStation])
   13939             :         {
   13940             :                 [self addTarget:mother];
   13941             :                 [self setTargetStation:mother];
   13942             :                 return; // head for mother!
   13943             :         }
   13944             : 
   13945             :         /*- selects the nearest station it can find -*/
   13946             :         if (!UNIVERSE)
   13947             :                 return;
   13948             :         int                     ent_count = UNIVERSE->n_entities;
   13949             :         Entity          **uni_entities = UNIVERSE->sortedEntities;   // grab the public sorted list
   13950             :         Entity          *my_entities[ent_count];
   13951             :         int i;
   13952             :         int station_count = 0;
   13953             :         for (i = 0; i < ent_count; i++)
   13954             :                 if (uni_entities[i]->isStation)
   13955             :                         my_entities[station_count++] = [uni_entities[i] retain];                //      retained
   13956             :         //
   13957             :         StationEntity *thing = nil, *station = nil;
   13958             :         double range2, nearest2 = SCANNER_MAX_RANGE2 * 1000000.0; // 1000x typical scanner range (25600 km), squared.
   13959             :         for (i = 0; i < station_count; i++)
   13960             :         {
   13961             :                 thing = (StationEntity *)my_entities[i];
   13962             :                 range2 = HPdistance2(position, thing->position);
   13963             :                 if (range2 < nearest2 && (includeHostiles || ![thing isHostileTo:self]))
   13964             :                 {
   13965             :                         station = thing;
   13966             :                         nearest2 = range2;
   13967             :                 }
   13968             :         }
   13969             :         for (i = 0; i < station_count; i++)
   13970             :                 [my_entities[i] release];               //      released
   13971             :         //
   13972             :         if (station)
   13973             :         {
   13974             :                 [self addTarget:station];
   13975             :                 [self setTargetStation:station];
   13976             :         }
   13977             :         else
   13978             :         {
   13979             :                 [shipAI message:@"NO_STATION_FOUND"];
   13980             :         }
   13981             : }
   13982             : 
   13983             : 
   13984             : // Exposed to AI
   13985             : - (void) setTargetToNearestFriendlyStation
   13986             : {
   13987             :         [self setTargetToNearestStationIncludingHostiles:NO];
   13988             : }
   13989             : 
   13990             : 
   13991             : // Exposed to AI
   13992             : - (void) setTargetToNearestStation
   13993             : {
   13994             :         [self setTargetToNearestStationIncludingHostiles:YES];
   13995             : }
   13996             : 
   13997             : 
   13998             : // Exposed to AI
   13999             : - (void) setTargetToSystemStation
   14000             : {
   14001             :         StationEntity* system_station = [UNIVERSE station];
   14002             :         
   14003             :         if (!system_station)
   14004             :         {
   14005             :                 [shipAI message:@"NOTHING_FOUND"];
   14006             :                 [shipAI message:@"NO_STATION_FOUND"];
   14007             :                 DESTROY(_primaryTarget);
   14008             :                 [self setTargetStation:nil];
   14009             :                 return;
   14010             :         }
   14011             :         
   14012             :         if (!system_station->isStation)
   14013             :         {
   14014             :                 [shipAI message:@"NOTHING_FOUND"];
   14015             :                 [shipAI message:@"NO_STATION_FOUND"];
   14016             :                 DESTROY(_primaryTarget);
   14017             :                 [self setTargetStation:nil];
   14018             :                 return;
   14019             :         }
   14020             :         
   14021             :         [self addTarget:system_station];
   14022             :         [self setTargetStation:system_station];
   14023             :         return;
   14024             : }
   14025             : 
   14026             : 
   14027             : - (void) landOnPlanet:(OOPlanetEntity *)planet
   14028             : {
   14029             :         if (planet && [self isShuttle])
   14030             :         {
   14031             :                 [planet welcomeShuttle:self];
   14032             :         }
   14033             :         [self doScriptEvent:OOJSID("shipLandedOnPlanet") withArgument:planet andReactToAIMessage:@"LANDED_ON_PLANET"];
   14034             :         
   14035             : #ifndef NDEBUG
   14036             :         if ([self reportAIMessages])
   14037             :         {
   14038             :                 OOLog(@"planet.collide.shuttleLanded", @"DEBUG: %@ landed on planet %@", self, planet);
   14039             :         }
   14040             : #endif
   14041             :         
   14042             :         [UNIVERSE removeEntity:self];
   14043             : }
   14044             : 
   14045             : 
   14046             : // Exposed to AI
   14047             : - (void) abortDocking
   14048             : {
   14049             :         [[UNIVERSE findEntitiesMatchingPredicate:IsStationPredicate
   14050             :                                                                    parameter:nil
   14051             :                                                                          inRange:-1
   14052             :                                                                         ofEntity:nil]
   14053             :                         makeObjectsPerformSelector:@selector(abortDockingForShip:) withObject:self];
   14054             : }
   14055             : 
   14056             : 
   14057             : - (NSDictionary *) dockingInstructions
   14058             : {
   14059             :         return dockingInstructions;
   14060             : }
   14061             : 
   14062             : 
   14063             : - (void) broadcastThargoidDestroyed
   14064             : {
   14065             :         [[UNIVERSE findShipsMatchingPredicate:HasRolePredicate
   14066             :                                                            parameter:@"tharglet"
   14067             :                                                                  inRange:SCANNER_MAX_RANGE
   14068             :                                                                 ofEntity:self]
   14069             :                         makeObjectsPerformSelector:@selector(sendAIMessage:) withObject:@"THARGOID_DESTROYED"];
   14070             : }
   14071             : 
   14072             : 
   14073           0 : static BOOL AuthorityPredicate(Entity *entity, void *parameter)
   14074             : {
   14075             :         ShipEntity                      *victim = parameter;
   14076             :         
   14077             :         // Select main station, if victim is in aegis
   14078             :         if (entity == [UNIVERSE station] && [victim withinStationAegis])
   14079             :         {
   14080             :                 return YES;
   14081             :         }
   14082             :         
   14083             :         // Select police units in typical scanner range
   14084             :         if ([entity scanClass] == CLASS_POLICE &&
   14085             :                 HPdistance2([victim position], [entity position]) < SCANNER_MAX_RANGE2)
   14086             :         {
   14087             :                 return YES;
   14088             :         }
   14089             :         
   14090             :         // Reject others
   14091             :         return NO;
   14092             : }
   14093             : 
   14094             : 
   14095             : - (void) broadcastHitByLaserFrom:(ShipEntity *) aggressor_ship
   14096             : {
   14097             :         /*-- If you're clean, locates all police and stations in range and tells them OFFENCE_COMMITTED --*/
   14098             :         if (!UNIVERSE)  return;
   14099             :         if ([self bounty])  return;
   14100             :         if (!aggressor_ship)  return;
   14101             :         
   14102             :         if (    (scanClass == CLASS_NEUTRAL)||
   14103             :                         (scanClass == CLASS_STATION)||
   14104             :                         (scanClass == CLASS_BUOY)||
   14105             :                         (scanClass == CLASS_POLICE)||
   14106             :                         (scanClass == CLASS_MILITARY)||
   14107             :                         (scanClass == CLASS_PLAYER))    // only for active ships...
   14108             :         {
   14109             :                 NSArray                 *authorities = nil;
   14110             :                 NSEnumerator    *authEnum = nil;
   14111             :                 ShipEntity              *auth = nil;
   14112             :                 
   14113             :                 authorities = [UNIVERSE findShipsMatchingPredicate:AuthorityPredicate
   14114             :                                                                                                  parameter:self
   14115             :                                                                                                    inRange:-1
   14116             :                                                                                                   ofEntity:nil];
   14117             :                 authEnum = [authorities objectEnumerator];
   14118             :                 while ((auth = [authEnum nextObject]))
   14119             :                 {
   14120             :                         [auth setFoundTarget:aggressor_ship];
   14121             :                         [auth doScriptEvent:OOJSID("offenceCommittedNearby") withArgument:aggressor_ship andArgument:self];
   14122             :                         [auth reactToAIMessage:@"OFFENCE_COMMITTED" context:@"combat update"];
   14123             :                 }
   14124             :         }
   14125             : }
   14126             : 
   14127             : 
   14128             : - (void) sendMessage:(NSString *) message_text toShip:(ShipEntity*) other_ship withUnpilotedOverride:(BOOL)unpilotedOverride
   14129             : {
   14130             :         if (!other_ship || !message_text) return;
   14131             :         if (!crew && !unpilotedOverride) return;
   14132             :         
   14133             :         double d2 = HPdistance2(position, [other_ship position]);
   14134             :         if (d2 > scannerRange * scannerRange)
   14135             :                 return;                                 // out of comms range
   14136             : 
   14137             :         NSString *expandedMessage = OOExpand(message_text); // consistent with broadcast message.
   14138             : 
   14139             :         if (other_ship->isPlayer)
   14140             :         {
   14141             :                 [self setCommsMessageColor];
   14142             :                 [(PlayerEntity *)other_ship receiveCommsMessage:expandedMessage from:self];
   14143             :                 messageTime = 6.0;
   14144             :                 [UNIVERSE resetCommsLogColor];
   14145             :         }
   14146             :         else
   14147             :                 [other_ship receiveCommsMessage:expandedMessage from:self];
   14148             : }
   14149             : 
   14150             : 
   14151             : - (void) sendExpandedMessage:(NSString *)message_text toShip:(ShipEntity *)other_ship
   14152             : {
   14153             :         if (!other_ship || !crew)
   14154             :                 return; // nobody to receive or send the signal
   14155             :         if ((lastRadioMessage) && (messageTime > 0.0) && [message_text isEqual:lastRadioMessage])
   14156             :                 return; // don't send the same message too often
   14157             :         [lastRadioMessage autorelease];
   14158             :         lastRadioMessage = [message_text retain];
   14159             : 
   14160             :         double d2 = HPdistance2(position, [other_ship position]);
   14161             :         if (d2 > scannerRange * scannerRange)
   14162             :         {
   14163             :                 // out of comms range
   14164             :                 return;
   14165             :         }
   14166             :         
   14167             :         Random_Seed very_random_seed;
   14168             :         very_random_seed.a = rand() & 255;
   14169             :         very_random_seed.b = rand() & 255;
   14170             :         very_random_seed.c = rand() & 255;
   14171             :         very_random_seed.d = rand() & 255;
   14172             :         very_random_seed.e = rand() & 255;
   14173             :         very_random_seed.f = rand() & 255;
   14174             :         seed_RNG_only_for_planet_description(very_random_seed);
   14175             :         
   14176             :         NSDictionary *specials = [NSDictionary dictionaryWithObjectsAndKeys:
   14177             :                                                           [self displayName], @"[self:name]",
   14178             :                                                           [other_ship identFromShip: self], @"[target:name]",
   14179             :                                                           nil];
   14180             :         NSString *expandedMessage = OOExpandDescriptionString(OOStringExpanderDefaultRandomSeed(), message_text, specials, nil, nil, kOOExpandNoOptions);
   14181             :         
   14182             :         [self sendMessage:expandedMessage toShip:other_ship withUnpilotedOverride:NO];
   14183             : }
   14184             : 
   14185             : 
   14186             : - (void) broadcastAIMessage:(NSString *) ai_message
   14187             : {
   14188             :         NSString *expandedMessage = OOExpand(ai_message);
   14189             : 
   14190             :         [self checkScanner];
   14191             :         unsigned i;
   14192             :         for (i = 0; i < n_scanned_ships ; i++)
   14193             :         {
   14194             :                 ShipEntity* ship = scanned_ships[i];
   14195             :                 [[ship getAI] message: expandedMessage];
   14196             :         }
   14197             : }
   14198             : 
   14199             : 
   14200             : - (void) broadcastMessage:(NSString *) message_text withUnpilotedOverride:(BOOL) unpilotedOverride
   14201             : {
   14202             :         NSString *expandedMessage = OOExpand(message_text); // consistent with broadcast message.
   14203             : 
   14204             : 
   14205             :         if (!crew && !unpilotedOverride)
   14206             :                 return; // nobody to send the signal and no override for unpiloted craft is set
   14207             : 
   14208             :         [self checkScanner];
   14209             :         unsigned i;
   14210             :         for (i = 0; i < n_scanned_ships ; i++)
   14211             :         {
   14212             :                 ShipEntity* ship = scanned_ships[i];
   14213             :                 if (![ship isPlayer]) [ship receiveCommsMessage:expandedMessage from:self];
   14214             :         }
   14215             :         
   14216             :         PlayerEntity *player = PLAYER; // make sure that the player always receives a message when in range
   14217             :         // SCANNER_MAX_RANGE2 because it's the player's scanner range
   14218             :         // which is important
   14219             :         if (HPdistance2(position, [player position]) < SCANNER_MAX_RANGE2)
   14220             :         {
   14221             :                 [self setCommsMessageColor];
   14222             :                 [player receiveCommsMessage:expandedMessage from:self];
   14223             :                 messageTime = 6.0;
   14224             :                 [UNIVERSE resetCommsLogColor];
   14225             :         }
   14226             : }
   14227             : 
   14228             : 
   14229             : - (void) setCommsMessageColor
   14230             : {
   14231             :         float hue = 0.0625f * (universalID & 15);
   14232             :         [[UNIVERSE commLogGUI] setTextColor:[OOColor colorWithHue:hue saturation:0.375f brightness:1.0f alpha:1.0f]];
   14233             :         if (scanClass == CLASS_THARGOID)
   14234             :                 [[UNIVERSE commLogGUI] setTextColor:[OOColor greenColor]];
   14235             :         if (scanClass == CLASS_POLICE)
   14236             :                 [[UNIVERSE commLogGUI] setTextColor:[OOColor cyanColor]];
   14237             : }
   14238             : 
   14239             : 
   14240             : - (void) receiveCommsMessage:(NSString *) message_text from:(ShipEntity *) other
   14241             : {
   14242             :         // Too complex for AI scripts to handle, JS event only.
   14243             :         [self doScriptEvent:OOJSID("commsMessageReceived") withArgument:message_text andArgument:other];
   14244             : }
   14245             : 
   14246             : 
   14247             : - (void) commsMessage:(NSString *)valueString withUnpilotedOverride:(BOOL)unpilotedOverride
   14248             : {
   14249             :         Random_Seed very_random_seed;
   14250             :         very_random_seed.a = rand() & 255;
   14251             :         very_random_seed.b = rand() & 255;
   14252             :         very_random_seed.c = rand() & 255;
   14253             :         very_random_seed.d = rand() & 255;
   14254             :         very_random_seed.e = rand() & 255;
   14255             :         very_random_seed.f = rand() & 255;
   14256             :         seed_RNG_only_for_planet_description(very_random_seed);
   14257             :         
   14258             :         [self broadcastMessage:valueString withUnpilotedOverride:unpilotedOverride];
   14259             : }
   14260             : 
   14261             : 
   14262             : - (BOOL) markedForFines
   14263             : {
   14264             :         return being_fined;
   14265             : }
   14266             : 
   14267             : 
   14268             : - (BOOL) markForFines
   14269             : {
   14270             :         if (being_fined)
   14271             :                 return NO;      // can't mark twice
   14272             :         being_fined = ([self legalStatus] > 0);
   14273             :         return being_fined;
   14274             : }
   14275             : 
   14276             : 
   14277             : - (BOOL) isMining
   14278             : {
   14279             :         return ((behaviour == BEHAVIOUR_ATTACK_MINING_TARGET)&&([forward_weapon_type isMiningLaser]));
   14280             : }
   14281             : 
   14282             : 
   14283             : - (void) interpretAIMessage:(NSString *)ms
   14284             : {
   14285             :         if ([ms hasPrefix:AIMS_AGGRESSOR_SWITCHED_TARGET])
   14286             :         {
   14287             :                 // if I'm under attack send a thank-you message to the rescuer
   14288             :                 //
   14289             :                 NSArray* tokens = ScanTokensFromString(ms);
   14290             :                 int switcher_id = [(NSString*)[tokens objectAtIndex:1] intValue]; // Attacker that switched targets.
   14291             :                 Entity* switcher = [UNIVERSE entityForUniversalID:switcher_id];
   14292             :                 int rescuer_id = [(NSString*)[tokens objectAtIndex:2] intValue]; // New primary target of attacker. 
   14293             :                 Entity* rescuer = [UNIVERSE entityForUniversalID:rescuer_id];
   14294             :                 if ((switcher == [self primaryAggressor])&&(switcher == [self primaryTarget])&&(switcher)&&(rescuer)&&(rescuer->isShip)&&([self thankedShip] != rescuer)&&(scanClass != CLASS_THARGOID))
   14295             :                 {
   14296             :                         ShipEntity* rescueShip = (ShipEntity*)rescuer;
   14297             : //                      ShipEntity* switchingShip = (ShipEntity*)switcher;
   14298             :                         if (scanClass == CLASS_POLICE)
   14299             :                         {
   14300             :                                 [self sendExpandedMessage:@"[police-thanks-for-assist]" toShip:rescueShip];
   14301             :                                 [rescueShip setBounty:[rescueShip bounty] * 0.80 withReason:kOOLegalStatusReasonAssistingPolice];       // lower bounty by 20%
   14302             :                         }
   14303             :                         else
   14304             :                         {
   14305             :                                 [self sendExpandedMessage:@"[thanks-for-assist]" toShip:rescueShip];
   14306             :                         }
   14307             :                         [self setThankedShip:rescuer];
   14308             :                 }
   14309             :         }
   14310             : }
   14311             : 
   14312             : 
   14313           0 : - (BoundingBox) findBoundingBoxRelativeTo:(Entity *)other InVectors:(Vector) _i :(Vector) _j :(Vector) _k
   14314             : {
   14315             :         HPVector  opv = other ? other->position : position;
   14316             :         return [self findBoundingBoxRelativeToPosition:opv InVectors:_i :_j :_k];
   14317             : }
   14318             : 
   14319             : 
   14320             : // Exposed to AI and legacy scripts.
   14321             : - (void) spawn:(NSString *)roles_number
   14322             : {
   14323             :         NSArray         *tokens = ScanTokensFromString(roles_number);
   14324             :         NSString        *roleString = nil;
   14325             :         NSString        *numberString = nil;
   14326             :         NSUInteger      number;
   14327             :         
   14328             :         if ([tokens count] != 2)
   14329             :         {
   14330             :                 OOLog(kOOLogSyntaxAddShips, @"***** Could not spawn: \"%@\" (must be two tokens, role and number)",roles_number);
   14331             :                 return;
   14332             :         }
   14333             :         
   14334             :         roleString = [tokens oo_stringAtIndex:0];
   14335             :         numberString = [tokens oo_stringAtIndex:1];
   14336             :         
   14337             :         number = [numberString intValue];
   14338             :         
   14339             :         [self spawnShipsWithRole:roleString count:number];
   14340             : }
   14341             : 
   14342             : 
   14343             : - (int) checkShipsInVicinityForWitchJumpExit
   14344             : {
   14345             :         // checks if there are any large masses close by
   14346             :         // since we want to place the space station at least 10km away
   14347             :         // the formula we'll use is K x m / d2 < 1.0
   14348             :         // (m = mass, d2 = distance squared)
   14349             :         // coriolis station is mass 455,223,200
   14350             :         // 10km is 10,000m,
   14351             :         // 10km squared is 100,000,000
   14352             :         // therefore K is 0.22 (approx)
   14353             : 
   14354             :         int result = NO_TARGET;
   14355             : 
   14356             :         GLfloat k = 0.1;
   14357             : 
   14358             :         int                     ent_count =             UNIVERSE->n_entities;
   14359             :         Entity**        uni_entities =  UNIVERSE->sortedEntities;    // grab the public sorted list
   14360             :         ShipEntity*     my_entities[ent_count];
   14361             :         int i;
   14362             : 
   14363             :         int ship_count = 0;
   14364             :         for (i = 0; i < ent_count; i++)
   14365             :                 if ((uni_entities[i]->isShip)&&(uni_entities[i] != self))
   14366             :                         my_entities[ship_count++] = (ShipEntity*)[uni_entities[i] retain];              //      retained
   14367             :         //
   14368             :         for (i = 0; (i < ship_count)&&(result == NO_TARGET) ; i++)
   14369             :         {
   14370             :                 ShipEntity* ship = my_entities[i];
   14371             :                 HPVector delta = HPvector_between(position, ship->position);
   14372             :                 GLfloat d2 = HPmagnitude2(delta);
   14373             :                 if (![ship isPlayer] || ![PLAYER isDocked])
   14374             :                 { // player doesn't block if docked
   14375             :                         if ((k * [ship mass] > d2)&&(d2 < SCANNER_MAX_RANGE2))    // if you go off (typical) scanner from a blocker - it ceases to block
   14376             :                                 result = [ship universalID];
   14377             :                 }
   14378             :         }
   14379             :         for (i = 0; i < ship_count; i++)
   14380             :                 [my_entities[i] release];       //              released
   14381             : 
   14382             :         return result;
   14383             : }
   14384             : 
   14385             : 
   14386             : - (BOOL) trackCloseContacts
   14387             : {
   14388             :         return trackCloseContacts;
   14389             : }
   14390             : 
   14391             : 
   14392             : - (void) setTrackCloseContacts:(BOOL) value
   14393             : {
   14394             :         if (value == (BOOL)trackCloseContacts)  return;
   14395             :         
   14396             :         trackCloseContacts = value;
   14397             :         [closeContactsInfo release];
   14398             :         
   14399             :         if (trackCloseContacts)
   14400             :         {
   14401             :                 closeContactsInfo = [[NSMutableDictionary alloc] init];
   14402             :         }
   14403             :         else
   14404             :         {
   14405             :                 closeContactsInfo = nil;
   14406             :         }
   14407             : }
   14408             : 
   14409             : 
   14410             : #if OO_SALVAGE_SUPPORT
   14411             : // Never used.
   14412             : - (void) claimAsSalvage
   14413             : {
   14414             :         // Create a bouy and beacon where the hulk is.
   14415             :         // Get the main GalCop station to launch a pilot boat to deliver a pilot to the hulk.
   14416             :         OOLog(@"claimAsSalvage.called", @"claimAsSalvage called on %@ %@", [self name], [self roleSet]);
   14417             :         
   14418             :         // Not an abandoned hulk, so don't allow the salvage
   14419             :         if (![self isHulk])
   14420             :         {
   14421             :                 OOLog(@"claimAsSalvage.failed.notHulk", @"claimAsSalvage failed because not a hulk");
   14422             :                 return;
   14423             :         }
   14424             : 
   14425             :         // Set target to main station, and return now if it can't be found
   14426             :         [self setTargetToSystemStation];
   14427             :         if ([self primaryTarget] == nil)
   14428             :         {
   14429             :                 OOLog(@"claimAsSalvage.failed.noStation", @"claimAsSalvage failed because did not find a station");
   14430             :                 return;
   14431             :         }
   14432             : 
   14433             :         // Get the station to launch a pilot boat to bring a pilot out to the hulk (use a viper for now)
   14434             :         StationEntity *station = (StationEntity *)[self primaryTarget];
   14435             :         OOLog(@"claimAsSalvage.requestingPilot", @"claimAsSalvage asking station to launch a pilot boat");
   14436             :         [station launchShipWithRole:@"pilot"];
   14437             :         [self setReportAIMessages:YES];
   14438             :         OOLog(@"claimAsSalvage.success", @"claimAsSalvage setting own state machine to capturedShipAI.plist");
   14439             :         [self setAITo:@"capturedShipAI.plist"];
   14440             : }
   14441             : 
   14442             : 
   14443             : - (void) sendCoordinatesToPilot
   14444             : {
   14445             :         Entity          *scan;
   14446             :         ShipEntity      *scanShip, *pilot;
   14447             :         
   14448             :         n_scanned_ships = 0;
   14449             :         scan = z_previous;
   14450             :         OOLog(@"ship.pilotage", @"searching for pilot boat");
   14451             :         while (scan &&(scan->isShip == NO))
   14452             :         {
   14453             :                 scan = scan->z_previous;     // skip non-ships
   14454             :         }
   14455             : 
   14456             :         pilot = nil;
   14457             :         while (scan)
   14458             :         {
   14459             :                 if (scan->isShip)
   14460             :                 {
   14461             :                         scanShip = (ShipEntity *)scan;
   14462             :                         
   14463             :                         if ([self hasRole:@"pilot"] == YES)
   14464             :                         {
   14465             :                                 if ([scanShip primaryTarget] == nil)
   14466             :                                 {
   14467             :                                         OOLog(@"ship.pilotage", @"found pilot boat with no target, will use this one");
   14468             :                                         pilot = scanShip;
   14469             :                                         [pilot setPrimaryRole:@"pilot"];
   14470             :                                         break;
   14471             :                                 }
   14472             :                         }
   14473             :                 }
   14474             :                 scan = scan->z_previous;
   14475             :                 while (scan && (scan->isShip == NO))
   14476             :                 {
   14477             :                         scan = scan->z_previous;
   14478             :                 }
   14479             :         }
   14480             : 
   14481             :         if (pilot != nil)
   14482             :         {
   14483             :                 OOLog(@"ship.pilotage", @"becoming pilot target and setting AI");
   14484             :                 [pilot setReportAIMessages:YES];
   14485             :                 [pilot addTarget:self];
   14486             :                 [pilot setAITo:@"pilotAI.plist"];
   14487             :                 [self reactToAIMessage:@"FOUND_PILOT" context:@"flight update"];
   14488             :         }
   14489             : }
   14490             : 
   14491             : 
   14492             : - (void) pilotArrived
   14493             : {
   14494             :         [self setHulk:NO];
   14495             :         [self reactToAIMessage:@"PILOT_ARRIVED" context:@"flight update"];
   14496             : }
   14497             : #endif
   14498             : 
   14499             : 
   14500             : #ifndef NDEBUG
   14501           0 : - (void)dumpSelfState
   14502             : {
   14503             :         NSMutableArray          *flags = nil;
   14504             :         NSString                        *flagsString = nil;
   14505             :         
   14506             :         [super dumpSelfState];
   14507             :         
   14508             :         OOLog(@"dumpState.shipEntity", @"Type: %@", [self shipDataKey]);
   14509             :         OOLog(@"dumpState.shipEntity", @"Name: %@", name);
   14510             :         OOLog(@"dumpState.shipEntity", @"Display Name: %@", [self displayName]);
   14511             :         OOLog(@"dumpState.shipEntity", @"Roles: %@", [self roleSet]);
   14512             :         OOLog(@"dumpState.shipEntity", @"Primary role: %@", primaryRole);
   14513             :         OOLog(@"dumpState.shipEntity", @"Script: %@", script);
   14514             :         OOLog(@"dumpState.shipEntity", @"Subentity count: %lu", [self subEntityCount]);
   14515             :         OOLog(@"dumpState.shipEntity", @"Behaviour: %@", OOStringFromBehaviour(behaviour));
   14516             :         id target = [self primaryTarget];
   14517             :         if (target == nil)  target = @"<none>";
   14518             :         OOLog(@"dumpState.shipEntity", @"Target: %@", target);
   14519             :         OOLog(@"dumpState.shipEntity", @"Destination: %@", HPVectorDescription(_destination));
   14520             :         OOLog(@"dumpState.shipEntity", @"Other destination: %@", HPVectorDescription(coordinates));
   14521             :         OOLog(@"dumpState.shipEntity", @"Waypoint count: %u", number_of_navpoints);
   14522             :         OOLog(@"dumpState.shipEntity", @"Desired speed: %g", desired_speed);
   14523             :         OOLog(@"dumpState.shipEntity", @"Thrust: %g", thrust);
   14524             :         if ([self escortCount] != 0)  OOLog(@"dumpState.shipEntity", @"Escort count: %u", [self escortCount]);
   14525             :         OOLog(@"dumpState.shipEntity", @"Fuel: %i", fuel);
   14526             :         OOLog(@"dumpState.shipEntity", @"Fuel accumulator: %g", fuel_accumulator);
   14527             :         OOLog(@"dumpState.shipEntity", @"Missile count: %u", missiles);
   14528             :         
   14529             :         if (shipAI != nil && OOLogWillDisplayMessagesInClass(@"dumpState.shipEntity.ai"))
   14530             :         {
   14531             :                 OOLog(@"dumpState.shipEntity.ai", @"%@", @"AI:");
   14532             :                 OOLogPushIndent();
   14533             :                 OOLogIndent();
   14534             :                 @try
   14535             :                 {
   14536             :                         [shipAI dumpState];
   14537             :                 }
   14538             :                 @catch (id exception) {}
   14539             :                 OOLogPopIndent();
   14540             :         }
   14541             :         OOLog(@"dumpState.shipEntity", @"Accuracy: %g", accuracy);
   14542             :         OOLog(@"dumpState.shipEntity", @"Jink position: %@", VectorDescription(jink));
   14543             :         OOLog(@"dumpState.shipEntity", @"Frustration: %g", frustration);
   14544             :         OOLog(@"dumpState.shipEntity", @"Success factor: %g", success_factor);
   14545             :         OOLog(@"dumpState.shipEntity", @"Shots fired: %u", shot_counter);
   14546             :         OOLog(@"dumpState.shipEntity", @"Time since shot: %g", [self shotTime]);
   14547             :         OOLog(@"dumpState.shipEntity", @"Spawn time: %g (%g seconds ago)", [self spawnTime], [self timeElapsedSinceSpawn]);
   14548             :         if ([self isBeacon])
   14549             :         {
   14550             :                 OOLog(@"dumpState.shipEntity", @"Beacon code: %@", [self beaconCode]);
   14551             :         }
   14552             :         OOLog(@"dumpState.shipEntity", @"Hull temperature: %g", ship_temperature);
   14553             :         OOLog(@"dumpState.shipEntity", @"Heat insulation: %g", [self heatInsulation]);
   14554             :         
   14555             :         flags = [NSMutableArray array];
   14556           0 :         #define ADD_FLAG_IF_SET(x)              if (x) { [flags addObject:@#x]; }
   14557             :         ADD_FLAG_IF_SET(military_jammer_active);
   14558             :         ADD_FLAG_IF_SET(docking_match_rotation);
   14559             :         ADD_FLAG_IF_SET(pitching_over);
   14560             :         ADD_FLAG_IF_SET(reportAIMessages);
   14561             :         ADD_FLAG_IF_SET(being_mined);
   14562             :         ADD_FLAG_IF_SET(being_fined);
   14563             :         ADD_FLAG_IF_SET(isHulk);
   14564             :         ADD_FLAG_IF_SET(trackCloseContacts);
   14565             :         ADD_FLAG_IF_SET(isNearPlanetSurface);
   14566             :         ADD_FLAG_IF_SET(isFrangible);
   14567             :         ADD_FLAG_IF_SET(cloaking_device_active);
   14568             :         ADD_FLAG_IF_SET(canFragment);
   14569             :         ADD_FLAG_IF_SET([self proximityAlert] != nil);
   14570             :         flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)@"none";
   14571             :         OOLog(@"dumpState.shipEntity", @"Flags: %@", flagsString);
   14572             : }
   14573             : #endif
   14574             : 
   14575             : 
   14576             : - (OOJSScript *)script
   14577             : {
   14578             :         return script;
   14579             : }
   14580             : 
   14581             : 
   14582             : - (NSDictionary *)scriptInfo
   14583             : {
   14584             :         return (scriptInfo != nil) ? scriptInfo : (NSDictionary *)[NSDictionary dictionary];
   14585             : }
   14586             : 
   14587             : 
   14588             : - (void) overrideScriptInfo:(NSDictionary *)override
   14589             : {
   14590             :         if (scriptInfo == nil)  scriptInfo = [override retain];
   14591             :         else if (override != nil)
   14592             :         {
   14593             :                 NSMutableDictionary *newInfo = [NSMutableDictionary dictionaryWithDictionary:scriptInfo];
   14594             :                 [newInfo addEntriesFromDictionary:override];
   14595             :                 [scriptInfo release];
   14596             :                 scriptInfo = [newInfo copy];
   14597             :         }
   14598             : }
   14599             : 
   14600             : 
   14601             : - (Entity *)entityForShaderProperties
   14602             : {
   14603             :         return [self rootShipEntity];
   14604             : }
   14605             : 
   14606             : - (void) setDemoShip: (OOScalar) rate
   14607             : {
   14608             :         demoStartOrientation = orientation;
   14609             :         demoRate = rate;
   14610             :         isDemoShip = YES;
   14611             :         [self setPitch: 0.0f];
   14612             :         [self setRoll: 0.0f];
   14613             : }
   14614             : 
   14615             : - (BOOL) isDemoShip
   14616             : {
   14617             :         return isDemoShip;
   14618             : }
   14619             : 
   14620             : - (void) setDemoStartTime: (OOTimeAbsolute) time
   14621             : {
   14622             :         demoStartTime = time;
   14623             : }
   14624             : 
   14625           0 : - (OOTimeAbsolute) getDemoStartTime
   14626             : {
   14627             :         return demoStartTime;
   14628             : }
   14629             : 
   14630             : // *** Script event dispatch.
   14631             : - (void) doScriptEvent:(jsid)message
   14632             : {
   14633             :         JSContext *context = OOJSAcquireContext();
   14634             :         [self doScriptEvent:message inContext:context withArguments:NULL count:0];
   14635             :         OOJSRelinquishContext(context);
   14636             : }
   14637             : 
   14638             : 
   14639             : - (void) doScriptEvent:(jsid)message withArgument:(id)argument
   14640             : {
   14641             :         JSContext *context = OOJSAcquireContext();
   14642             :         
   14643             :         jsval value = OOJSValueFromNativeObject(context, argument);
   14644             :         [self doScriptEvent:message inContext:context withArguments:&value count:1];
   14645             :         
   14646             :         OOJSRelinquishContext(context);
   14647             : }
   14648             : 
   14649             : 
   14650             : - (void) doScriptEvent:(jsid)message
   14651             :                   withArgument:(id)argument1
   14652             :                    andArgument:(id)argument2
   14653             : {
   14654             :         JSContext *context = OOJSAcquireContext();
   14655             :         
   14656             :         jsval argv[2] = { OOJSValueFromNativeObject(context, argument1), OOJSValueFromNativeObject(context, argument2) };
   14657             :         [self doScriptEvent:message inContext:context withArguments:argv count:2];
   14658             :         
   14659             :         OOJSRelinquishContext(context);
   14660             : }
   14661             : 
   14662             : 
   14663             : - (void) doScriptEvent:(jsid)message withArguments:(NSArray *)arguments
   14664             : {
   14665             :         JSContext                               *context = OOJSAcquireContext();
   14666             :         uintN                                   i, argc;
   14667             :         jsval                                   *argv = NULL;
   14668             :         
   14669             :         // Convert arguments to JS values and make them temporarily un-garbage-collectable.
   14670             :         argc = (uintN)[arguments count];
   14671             :         if (argc != 0)
   14672             :         {
   14673             :                 argv = malloc(sizeof *argv * argc);
   14674             :                 if (argv != NULL)
   14675             :                 {
   14676             :                         for (i = 0; i != argc; ++i)
   14677             :                         {
   14678             :                                 argv[i] = [[arguments objectAtIndex:i] oo_jsValueInContext:context];
   14679             :                                 OOJSAddGCValueRoot(context, &argv[i], "event parameter");
   14680             :                         }
   14681             :                 }
   14682             :                 else  argc = 0;
   14683             :         }
   14684             :         
   14685             :         [self doScriptEvent:message inContext:context withArguments:argv count:argc];
   14686             :         
   14687             :         // Re-garbage-collectibalize the arguments and free the array.
   14688             :         if (argv != NULL)
   14689             :         {
   14690             :                 for (i = 0; i != argc; ++i)
   14691             :                 {
   14692             :                         JS_RemoveValueRoot(context, &argv[i]);
   14693             :                 }
   14694             :                 free(argv);
   14695             :         }
   14696             :         
   14697             :         OOJSRelinquishContext(context);
   14698             : }
   14699             : 
   14700             : 
   14701             : - (void) doScriptEvent:(jsid)message withArguments:(jsval *)argv count:(uintN)argc
   14702             : {
   14703             :         JSContext *context = OOJSAcquireContext();
   14704             :         [self doScriptEvent:message inContext:context withArguments:argv count:argc];
   14705             :         OOJSRelinquishContext(context);
   14706             : }
   14707             : 
   14708             : 
   14709             : - (void) doScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc
   14710             : {
   14711             :         // This method is a bottleneck so that PlayerEntity can override at one point.
   14712             :         [script callMethod:message inContext:context withArguments:argv count:argc result:NULL];
   14713             :         [aiScript callMethod:message inContext:context withArguments:argv count:argc result:NULL];
   14714             : }
   14715             : 
   14716             : 
   14717             : - (void) reactToAIMessage:(NSString *)message context:(NSString *)debugContext
   14718             : {
   14719             :         [shipAI reactToMessage:message context:debugContext];
   14720             : }
   14721             : 
   14722             : 
   14723             : - (void) sendAIMessage:(NSString *)message
   14724             : {
   14725             :         [shipAI message:message];       
   14726             : }
   14727             : 
   14728             : 
   14729             : - (void) doScriptEvent:(jsid)scriptEvent andReactToAIMessage:(NSString *)aiMessage
   14730             : {
   14731             :         [self doScriptEvent:scriptEvent];
   14732             :         [self reactToAIMessage:aiMessage context:nil];
   14733             : }
   14734             : 
   14735             : 
   14736             : - (void) doScriptEvent:(jsid)scriptEvent withArgument:(id)argument andReactToAIMessage:(NSString *)aiMessage
   14737             : {
   14738             :         [self doScriptEvent:scriptEvent withArgument:argument];
   14739             :         [self reactToAIMessage:aiMessage context:nil];
   14740             : }
   14741             : 
   14742             : 
   14743             : // exposed for shaders; fake alert level
   14744             : // since NPCs don't have torus drive, they're never at condition green
   14745             : - (OOAlertCondition) alertCondition
   14746             : {
   14747             :         if ([self status] == STATUS_DOCKED) 
   14748             :         {
   14749             :                 return ALERT_CONDITION_DOCKED;
   14750             :         }
   14751             :         if ([self hasHostileTarget] || energy < maxEnergy / 4)
   14752             :         {
   14753             :                 return ALERT_CONDITION_RED;
   14754             :         }
   14755             :         return ALERT_CONDITION_YELLOW;
   14756             : }
   14757             : 
   14758             : 
   14759             : - (OOAlertCondition) realAlertCondition
   14760             : {
   14761             :         if ([self status] == STATUS_DOCKED) 
   14762             :         {
   14763             :                 return ALERT_CONDITION_DOCKED;
   14764             :         }
   14765             :         if ([self hasHostileTarget])
   14766             :         {
   14767             :                 return ALERT_CONDITION_RED;
   14768             :         }
   14769             :         else
   14770             :         {
   14771             :                 NSEnumerator *sEnum = [_defenseTargets objectEnumerator];
   14772             :                 ShipEntity *ship = nil;
   14773             :                 double scanrange2 = scannerRange * scannerRange;
   14774             :                 while ((ship = [sEnum nextObject]))
   14775             :                 {
   14776             :                         if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline]))
   14777             :                         {
   14778             :                                 if (HPdistance2([ship position],position) < scanrange2)
   14779             :                                 {
   14780             :                                         return ALERT_CONDITION_RED;
   14781             :                                 }
   14782             :                         }
   14783             :                 }
   14784             :                 // also need to check primary target separately
   14785             :                 if ([self hasHostileTarget])
   14786             :                 {
   14787             :                         Entity *ptarget = [self primaryTargetWithoutValidityCheck];
   14788             :                         if (ptarget != nil && [ptarget isShip])
   14789             :                         {
   14790             :                                 ship = (ShipEntity *)ptarget;
   14791             :                                 if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline]))
   14792             :                                 {
   14793             :                                         if (HPdistance2([ship position],position) < scanrange2 * 1.5625)
   14794             :                                         {
   14795             :                                                 return ALERT_CONDITION_RED;
   14796             :                                         }
   14797             :                                 }
   14798             :                         }
   14799             :                 }
   14800             :                 if (_group)
   14801             :                 {
   14802             :                         sEnum = [_group objectEnumerator];
   14803             :                         while ((ship = [sEnum nextObject]))
   14804             :                         {
   14805             :                                 if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline]))
   14806             :                                 {
   14807             :                                         if (HPdistance2([ship position],position) < scanrange2)
   14808             :                                         {
   14809             :                                                 return ALERT_CONDITION_RED;
   14810             :                                         }
   14811             :                                 }
   14812             :                         }
   14813             :                 }
   14814             :                 if (_escortGroup && _group != _escortGroup)
   14815             :                 {
   14816             :                         sEnum = [_escortGroup objectEnumerator];
   14817             :                         while ((ship = [sEnum nextObject]))
   14818             :                         {
   14819             :                                 if ([ship hasHostileTarget] || ([ship isPlayer] && [PLAYER weaponsOnline]))
   14820             :                                 {
   14821             :                                         if (HPdistance2([ship position],position) < scanrange2)
   14822             :                                         {
   14823             :                                                 return ALERT_CONDITION_RED;
   14824             :                                         }
   14825             :                                 }
   14826             :                         }
   14827             :                 }
   14828             :         }
   14829             :         return ALERT_CONDITION_YELLOW;
   14830             : }
   14831             : 
   14832             : 
   14833             : // Exposed to AI and scripts.
   14834           0 : - (void) doNothing
   14835             : {
   14836             :         
   14837             : }
   14838             : 
   14839             : 
   14840             : #ifndef NDEBUG
   14841           0 : - (NSString *) descriptionForObjDump
   14842             : {
   14843             :         NSString *desc = [super descriptionForObjDump];
   14844             :         desc = [NSString stringWithFormat:@"%@ mass %g", desc, [self mass]];
   14845             :         if (![self isPlayer])
   14846             :         {
   14847             :                 desc = [NSString stringWithFormat:@"%@ AI: %@", desc, [[self getAI] shortDescriptionComponents]];
   14848             :         }
   14849             :         return desc;
   14850             : }
   14851             : #endif
   14852             : 
   14853             : @end
   14854             : 
   14855             : 
   14856             : @implementation Entity (SubEntityRelationship)
   14857             : 
   14858             : - (BOOL) isShipWithSubEntityShip:(Entity *)other
   14859             : {
   14860             :         return NO;
   14861             : }
   14862             : 
   14863             : 
   14864           0 : - (void) drawSubEntityImmediate:(bool)immediate translucent:(bool)translucent
   14865             : {
   14866             :         // Do nothing.
   14867             : }
   14868             : 
   14869             : @end
   14870             : 
   14871             : 
   14872             : @implementation ShipEntity (SubEntityRelationship)
   14873             : 
   14874           0 : - (BOOL) isShipWithSubEntityShip:(Entity *)other
   14875             : {
   14876             :         assert ([self isShip]);
   14877             :         
   14878             :         if (![other isShip])  return NO;
   14879             :         if (![other isSubEntity])  return NO;
   14880             :         if ([other owner] != self)  return NO;
   14881             :         
   14882             : #ifndef NDEBUG
   14883             :         // Sanity check; this should always be true.
   14884             :         if (![self hasSubEntity:(ShipEntity *)other])
   14885             :         {
   14886             :                 OOLogERR(@"ship.subentity.sanityCheck.failed", @"%@ thinks it's a subentity of %@, but the supposed parent does not agree. %@", [other shortDescription], [self shortDescription], @"This is an internal error, please report it.");
   14887             :                 [other setOwner:nil];
   14888             :                 return NO;
   14889             :         }
   14890             : #endif
   14891             :         
   14892             :         return YES;
   14893             : }
   14894             : 
   14895             : @end
   14896             : 
   14897             : 
   14898           0 : NSDictionary *OODefaultShipShaderMacros(void)
   14899             : {
   14900             :         static NSDictionary             *macros = nil;
   14901             :         
   14902             :         if (macros == nil)
   14903             :         {
   14904             :                 macros = [[[ResourceManager materialDefaults] oo_dictionaryForKey:@"ship-prefix-macros" defaultValue:[NSDictionary dictionary]] retain];
   14905             :         }
   14906             :         
   14907             :         return macros;
   14908             : }
   14909             : 
   14910             : // is this the right place for this function now? - CIM
   14911           0 : BOOL OOUniformBindingPermitted(NSString *propertyName, id bindingTarget)
   14912             : {
   14913             :         static NSSet                    *entityWhitelist = nil;
   14914             :         static NSSet                    *shipWhitelist = nil;
   14915             :         static NSSet                    *playerShipWhitelist = nil;
   14916             :         static NSSet                    *visualEffectWhitelist = nil;
   14917             :         
   14918             :         if (entityWhitelist == nil)
   14919             :         {
   14920             :                 NSDictionary *wlDict = [ResourceManager whitelistDictionary];
   14921             :                 entityWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_entity_binding_methods"]];
   14922             :                 shipWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_ship_binding_methods"]];
   14923             :                 playerShipWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_player_ship_binding_methods"]];
   14924             :                 visualEffectWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_visual_effect_binding_methods"]];
   14925             :         }
   14926             :         
   14927             :         if ([bindingTarget isKindOfClass:[Entity class]])
   14928             :         {
   14929             :                 if ([entityWhitelist containsObject:propertyName])  return YES;
   14930             :                 if ([bindingTarget isShip])
   14931             :                 {
   14932             :                         if ([shipWhitelist containsObject:propertyName])  return YES;
   14933             :                 }
   14934             :                 if ([bindingTarget isPlayerLikeShip])
   14935             :                 {
   14936             :                         if ([playerShipWhitelist containsObject:propertyName])  return YES;
   14937             :                 }
   14938             :                 if ([bindingTarget isVisualEffect])
   14939             :                 {
   14940             :                         if ([visualEffectWhitelist containsObject:propertyName])  return YES;
   14941             :                 }
   14942             :         }
   14943             :         
   14944             :         return NO;
   14945             : }
   14946             : 
   14947             : 
   14948           0 : GLfloat getWeaponRangeFromType(OOWeaponType weapon_type)
   14949             : {
   14950             :         return [weapon_type weaponRange];
   14951             : }
   14952             : 
   14953             : 
   14954           0 : BOOL isWeaponNone(OOWeaponType weapon)
   14955             : {
   14956             :         return weapon == nil || [[weapon identifier] isEqualToString:@"EQ_WEAPON_NONE"];
   14957             : }

Generated by: LCOV version 1.14