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