48#import "MyOpenGLView.h"
104#if MASS_DEPENDENT_FUEL_PRICES
105static GLfloat calcFuelChargeRate (GLfloat myMass)
107#define kMassCharge 0.65
108#define kBaseCharge (1.0 - kMassCharge)
110 GLfloat baseMass = [PLAYER baseMass];
112 if (myMass <= 0.0 || baseMass <=0.0)
return 1.0;
114 GLfloat result = (kMassCharge * myMass / baseMass) + kBaseCharge;
117 result = roundf(result * 100.0f) / 100.0f;
120 if (result > 3.0f) result = 3.0f;
121 else if (result < 0.33f) result = 0.33f;
131@interface ShipEntity (Private)
140- (void) rescaleBy:(GLfloat)factor;
141- (void) rescaleBy:(GLfloat)factor writeToCache:(BOOL)writeToCache;
143- (BOOL) setUpOneSubentity:(NSDictionary *) subentDict;
144- (BOOL) setUpOneFlasher:(NSDictionary *) subentDict;
148- (void) addSubEntity:(
Entity<OOSubEntity> *) subent;
151- (HPVector) coordinatesForEscortPosition:(
unsigned)idx;
153- (void) setUpOneEscort:(
ShipEntity *)escorter inGroup:(
OOShipGroup *)escortGroup withRole:(NSString *)escortRole atPosition:(HPVector)ex_pos andCount:(uint8_t)currentEscortCount;
155- (void) addSubentityToCollisionRadius:(
Entity<OOSubEntity> *) subent;
156- (
ShipEntity *) launchPodWithCrew:(NSArray *)podCrew;
159- (
OOEquipmentType *) generateMissileEquipmentTypeFrom:(NSString *)role;
163- (void) noteFrustration:(NSString *)context;
182 return [
self initWithKey:@"" definition:nil];
186- (id) initBypassForPlayer
193- (id)initWithKey:(NSString *)key definition:(NSDictionary *)dict
197 NSParameterAssert(dict !=
nil);
200 if (
self ==
nil)
return nil;
202 _shipKey = [key retain];
206 [
self setStatus:STATUS_IN_FLIGHT];
209 weapon_recharge_rate = 6.0;
214 forward_weapon_temp = 0.0f;
215 aft_weapon_temp = 0.0f;
216 port_weapon_temp = 0.0f;
217 starboard_weapon_temp = 0.0f;
219 _nextAegisCheck = -0.1f;
220 aiScriptWakeTime = 0;
222 if (![
self setUpShipFromDictionary:dict])
229 if (
self !=
nil && !isfinite(maxFlightSpeed))
231 OOLog(
@"ship.sanityCheck.failed",
@"Ship %@ %@ infinite top speed, clamped to 300.",
self,
@"generated with");
232 maxFlightSpeed = 300;
240- (BOOL) setUpFromDictionary:(NSDictionary *) shipDict
248 shipinfoDictionary = [shipDict copy];
249 if (shipinfoDictionary ==
nil) shipinfoDictionary = [[NSDictionary alloc] init];
250 shipDict = shipinfoDictionary;
253 haveExecutedSpawnAction = NO;
254 haveStartedJSAI = NO;
255 scripted_misjump = NO;
256 _scriptedMisjumpRange = 0.5;
258 isNearPlanetSurface = NO;
259 suppressAegisMessages = NO;
261 suppressExplosion = NO;
266 _scaleFactor = [shipDict oo_floatForKey:@"model_scale_factor" defaultValue:1.0f];
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];
274 cruiseSpeed = maxFlightSpeed*0.8f;
276 max_thrust = [shipDict oo_floatForKey:@"thrust" defaultValue:15.0f];
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)
283 OOLog(
@"ship.setup.injectorSpeed",
@"injector_speed_factor cannot be lower than 1.0 for %@",
self);
284 afterburner_speed_factor = 1.0;
286#if OO_VARIABLE_TORUS_SPEED
289 OOLog(
@"ship.setup.injectorSpeed",
@"injector_speed_factor cannot be higher than minimum torus speed factor (%f) for %@.",
MIN_HYPERSPEED_FACTOR,
self);
295 OOLog(
@"ship.setup.injectorSpeed",
@"injector_speed_factor cannot be higher than torus speed factor (%f) for %@.",
HYPERSPEED_FACTOR,
self);
300 maxEnergy = [shipDict oo_floatForKey:@"max_energy" defaultValue:200.0f];
301 energy_recharge_rate = [shipDict oo_floatForKey:@"energy_recharge_rate" defaultValue:1.0f];
303 _showDamage = [shipDict oo_boolForKey:@"show_damage" defaultValue:(energy_recharge_rate > 0)];
305 [
self setThrowSparks:[shipDict oo_boolForKey:@"throw_sparks" defaultValue:NO]];
307 weapon_facings = [shipDict oo_intForKey:@"weapon_facings" defaultValue:VALID_WEAPON_FACINGS] &
VALID_WEAPON_FACINGS;
309 forward_weapon_type =
OOWeaponTypeFromString([shipDict oo_stringForKey:
@"forward_weapon_type" defaultValue:
@"EQ_WEAPON_NONE"]);
311 aft_weapon_type =
OOWeaponTypeFromString([shipDict oo_stringForKey:
@"aft_weapon_type" defaultValue:
@"EQ_WEAPON_NONE"]);
313 port_weapon_type =
OOWeaponTypeFromString([shipDict oo_stringForKey:
@"port_weapon_type" defaultValue:
@"EQ_WEAPON_NONE"]);
315 starboard_weapon_type =
OOWeaponTypeFromString([shipDict oo_stringForKey:
@"starboard_weapon_type" defaultValue:
@"EQ_WEAPON_NONE"]);
317 cloaking_device_active = NO;
318 military_jammer_active = NO;
319 cloakPassive = [shipDict oo_boolForKey:@"cloak_passive" defaultValue:YES];
320 cloakAutomatic = [shipDict oo_boolForKey:@"cloak_automatic" defaultValue:YES];
322 missiles = [shipDict oo_intForKey:@"missiles" defaultValue:0];
332 for (i = 0; i < missiles; i++)
334 missile_list[i] =
nil;
336 max_missiles = [shipDict oo_intForKey:@"max_missiles" defaultValue: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]);
340 missile_launch_time = [UNIVERSE getTime] + missile_load_time;
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)
358 if ([shipDict oo_fuzzyBooleanForKey:
@"has_energy_bomb"])
364 [
self addEquipmentItem:@"EQ_QC_MINE" inContext:@"npc"];
368 if ([shipDict oo_fuzzyBooleanForKey:
@"has_fuel_injection"]) [
self addEquipmentItem:
@"EQ_FUEL_INJECTION" inContext:
@"npc"];
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"];
377 canFragment = [shipDict oo_fuzzyBooleanForKey:@"fragment_chance" defaultValue:0.9];
381 isFrangible = [shipDict oo_boolForKey:@"frangible" defaultValue:YES];
383 max_cargo = [shipDict oo_unsignedIntForKey:@"max_cargo"];
384 extra_cargo = [shipDict oo_unsignedIntForKey:@"extra_cargo" defaultValue:15];
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;
390 name = [[shipDict oo_stringForKey:@"name" defaultValue:@"?"] copy];
392 [shipUniqueName autorelease];
393 shipUniqueName = [[shipDict oo_stringForKey:@"ship_name" defaultValue:@""] copy];
395 [shipClassName autorelease];
396 shipClassName = [[shipDict oo_stringForKey:@"ship_class_name" defaultValue:name] copy];
398 [displayName autorelease];
399 displayName = [[shipDict oo_stringForKey:@"display_name" defaultValue:nil] copy];
402 NSString *modelName = [shipDict oo_stringForKey:@"model"];
403 if (modelName !=
nil)
408 cacheKey:[NSString stringWithFormat:@"%@-%.3f",_shipKey,_scaleFactor]
411 smooth:[shipDict oo_boolForKey:@"smooth" defaultValue:NO]
417 if (mesh ==
nil)
return NO;
421 float density = [shipDict oo_floatForKey:@"density" defaultValue:1.0f];
422 if (octree) mass = (GLfloat)(density * 20.0f * [octree volume]);
427 if (default_laser_color ==
nil)
433 [
self setLaserColor:default_laser_color];
437 defaultExhaustEmissiveColorComponents.
r = 0.7f;
438 defaultExhaustEmissiveColorComponents.
g = 0.9f;
439 defaultExhaustEmissiveColorComponents.
b = 1.0f;
440 defaultExhaustEmissiveColorComponents.
a = 0.9f;
443 [
self setExhaustEmissiveColor:color];
445 [
self clearSubEntities];
446 [
self setUpSubEntities];
452 BOOL hasTurrets = NO;
453 NSEnumerator *subEnum = [
self shipSubEntityEnumerator];
455 while (
isWeaponNone(weapon_type) && (se = [subEnum nextObject]))
458 if (se->
behaviour == BEHAVIOUR_TRACK_AS_TURRET)
474 [
self setWeaponDataFromType:weapon_type];
479 [
self setWeaponDataFromType:forward_weapon_type];
484 if ([shipDict objectForKey:
@"rotational_velocity"])
486 subentityRotationalVelocity = [shipDict oo_quaternionForKey:@"rotational_velocity"];
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];
498 tractor_position = vector_multiply_scalar([shipDict oo_vectorForKey:
@"scoop_position"],_scaleFactor);
503 [
self setSunGlareFilter:[shipDict oo_floatForKey:@"sun_glare_filter" defaultValue:0.97f]];
506 scriptInfo = [[shipDict oo_dictionaryForKey:@"script_info" defaultValue:nil] retain];
508 explosionType = [[shipDict oo_arrayForKey:@"explosion_type" defaultValue:nil] retain];
519- (BOOL) setUpShipFromDictionary:(NSDictionary *) shipDict
523 if (![
self setUpFromDictionary:shipDict]) return NO;
532 reference = v_forward;
538 scanClass =
OOScanClassFromString([shipDict oo_stringForKey:
@"scan_class" defaultValue:
@"CLASS_NOT_SET"]);
541 if (scanClass == CLASS_NOT_SET)
543 scanClass =
OOScanClassFromString([shipDict oo_stringForKey:
@"scanClass" defaultValue:
@"CLASS_NOT_SET"]);
546 [scan_description autorelease];
547 scan_description = [[shipDict oo_stringForKey:@"scan_description" defaultValue:nil] copy];
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"];
557 forward_weapon_temp = 0.0f;
558 aft_weapon_temp = 0.0f;
559 port_weapon_temp = 0.0f;
560 starboard_weapon_temp = 0.0f;
564 if (weapon_damage == 0.0)
566 weapon_damage_override = weapon_damage = [shipDict oo_floatForKey:@"weapon_energy" defaultValue:0];
570 weapon_damage_override = 0;
573 scannerRange = [shipDict oo_floatForKey:@"scanner_range" defaultValue:(float)SCANNER_MAX_RANGE];
575 fuel = [shipDict oo_unsignedShortForKey:@"fuel"];
577 fuel_accumulator = 1.0;
579 [
self setBounty:[shipDict oo_unsignedIntForKey:@"bounty" defaultValue:0] withReason:kOOLegalStatusReasonSetup];
581 [shipAI autorelease];
582 shipAI = [[
AI alloc] init];
583 [shipAI setOwner:self];
584 [
self setAITo:[shipDict oo_stringForKey:@"ai_type" defaultValue:@"nullAI.plist"]];
586 likely_cargo = [shipDict oo_unsignedIntForKey:@"likely_cargo"];
587 noRocks = [shipDict oo_fuzzyBooleanForKey:@"no_boulders"];
589 commodity_amount = 0;
590 commodity_type =
nil;
591 NSString *cargoString = [shipDict oo_stringForKey:@"cargo_carried"];
592 if (cargoString !=
nil)
594 if ([cargoString isEqualToString:
@"SCARCE_GOODS"])
598 else if ([cargoString isEqualToString:
@"PLENTIFUL_GOODS"])
608 NSScanner *scanner = [NSScanner scannerWithString:cargoString];
609 if ([scanner scanInt:&c_amount])
611 [scanner ooliteScanCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:NULL];
612 c_commodity = [[scanner string] substringFromIndex:[scanner scanLocation]];
613 if ([[
UNIVERSE commodities] goodDefined:c_commodity])
615 [
self setCommodityForPod:c_commodity andAmount:c_amount];
619 c_commodity = [[UNIVERSE commodities] goodNamed:c_commodity];
620 if ([[
UNIVERSE commodities] goodDefined:c_commodity])
622 [
self setCommodityForPod:c_commodity andAmount:c_amount];
629 c_commodity = [shipDict oo_stringForKey:@"cargo_carried"];
630 if ([[
UNIVERSE commodities] goodDefined:c_commodity])
632 [
self setCommodityForPod:c_commodity andAmount:c_amount];
636 c_commodity = [[UNIVERSE commodities] goodNamed:c_commodity];
637 if ([[
UNIVERSE commodities] goodDefined:c_commodity])
639 [
self setCommodityForPod:c_commodity andAmount:c_amount];
646 cargoString = [shipDict oo_stringForKey:@"cargo_type"];
649 if (cargo !=
nil) [cargo autorelease];
650 cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo];
652 [
self setUpCargoType:cargoString];
654 else if (scanClass != CLASS_CARGO)
656 if (cargo !=
nil) [cargo autorelease];
657 cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo];
662 hasScoopMessage = [shipDict oo_boolForKey:@"has_scoop_message" defaultValue:YES];
667 [primaryRole release];
670 [
self setOwner:self];
671 [
self setHulk:[shipDict oo_boolForKey:@"is_hulk"]];
674 [
self setScannerDisplayColor1:nil];
675 [
self setScannerDisplayColor2:nil];
677 [
self setScannerDisplayColorHostile1:nil];
678 [
self setScannerDisplayColorHostile2:nil];
682 _missileRole = [shipDict oo_stringForKey:@"missile_role"];
684 for (i = 0, j = 0; i < missiles; i++)
686 missile_list[i] = [
self selectMissile];
688 if (missile_list[i] ==
nil && j < 3)
696 if (missile_list[i] ==
nil)
710 accuracy = [shipDict oo_floatForKey:@"accuracy" defaultValue:-100.0f];
711 if (accuracy < -5.0f || accuracy > 10.0f)
713 accuracy = (
randf() * 10.0)-5.0;
715 if (accuracy < 0.0f && (scanClass == CLASS_MILITARY || scanClass == CLASS_POLICE))
717 accuracy = -accuracy;
720 if (scanClass == CLASS_MISSILE)
722 accuracy = OOClamp_0_max_f(accuracy, 10.0f);
724 [
self setAccuracy:accuracy];
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)
740 [
self setBeaconCode:[shipDict oo_stringForKey:@"beacon"]];
741 [
self setBeaconLabel:[shipDict oo_stringForKey:@"beacon_label" defaultValue:[shipDict oo_stringForKey:@"beacon"]]];
745 [
self setTrackCloseContacts:[shipDict oo_boolForKey:@"track_contacts" defaultValue:NO]];
748 [
self setHeatInsulation:[shipDict oo_floatForKey:@"heat_insulation" defaultValue:[
self hasHeatShield] ? 2.0 : 1.0]];
751 _explicitlyUnpiloted = [shipDict oo_fuzzyBooleanForKey:@"unpiloted"];
752 if (_explicitlyUnpiloted)
759 NSDictionary *cdict = [[UNIVERSE characters] objectForKey:[shipDict oo_stringForKey:@"pilot"]];
763 [
self setCrew:[NSArray arrayWithObject:pilot]];
767 [
self setShipScript:[shipDict oo_stringForKey:@"script"]];
769 home_system = [UNIVERSE currentSystemID];
770 destination_system = [UNIVERSE currentSystemID];
772 reactionTime = [shipDict oo_floatForKey: @"reaction_time" defaultValue: COMBAT_AI_STANDARD_REACTION_TIME];
780- (void) setSubIdx:(NSUInteger)value
792- (NSUInteger) maxShipSubEntities
794 return _maxShipSubIdx;
798- (NSString *) repeatString:(NSString *)str times:(NSUInteger)times
800 if (times == 0)
return @"";
802 NSMutableString *result = [NSMutableString stringWithCapacity:[str length] * times];
804 for (NSUInteger i = 0; i < times; i++)
806 [result appendString:str];
813- (NSString *) serializeShipSubEntities
815 NSMutableString *result = [NSMutableString stringWithCapacity:4];
816 NSEnumerator *subEnum =
nil;
818 NSUInteger diff, i = 0;
820 for (subEnum = [
self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
824 [result appendString:[
self repeatString:@"0" times:diff]];
825 [result appendString:@"1"];
828 [result appendString:[
self repeatString:@"0" times:[
self maxShipSubEntities] - i]];
833- (void) deserializeShipSubEntitiesFrom:(NSString *)string
835 NSArray *subEnts = [[
self shipSubEntityEnumerator] allObjects];
836 NSInteger i,idx, start = [subEnts count] - 1;
837 NSInteger strMaxIdx = [string length] - 1;
841 for (i = start; i >= 0; i--)
845 if (idx <= strMaxIdx && [[
string substringWithRange:NSMakeRange(idx, 1)] isEqualToString:
@"0"])
855- (BOOL) setUpSubEntities
860 NSDictionary *shipDict = [
self shipInfoDictionary];
861 NSArray *plumes = [shipDict oo_arrayForKey:@"exhaust"];
863 _profileRadius = collision_radius;
866 for (i = 0; i < [plumes count]; i++)
870 [
self addSubEntity:exhaust];
873 NSArray *subs = [shipDict oo_arrayForKey:@"subentities"];
875 totalBoundingBox = boundingBox;
877 for (i = 0; i < [subs count]; i++)
879 [
self setUpOneSubentity:[subs oo_dictionaryAtIndex:i]];
890- (GLfloat) frustumRadius
893 NSEnumerator *exEnum =
nil;
895 for (exEnum = [
self exhaustEnumerator]; (exEnt = [exEnum nextObject]); )
897 if ([exEnt findCollisionRadius] > exhaust_length)
902 return _profileRadius + exhaust_length;
906- (BOOL) setUpOneSubentity:(NSDictionary *) subentDict
910 NSString *type =
nil;
912 type = [subentDict oo_stringForKey:@"type"];
913 if ([type isEqualToString:
@"flasher"])
915 return [
self setUpOneFlasher:subentDict];
919 return [
self setUpOneStandardSubentity:subentDict asTurret:[type isEqualToString:@"ball_turret"]];
926- (BOOL) setUpOneFlasher:(NSDictionary *) subentDict
929 [flasher
setPosition:HPvector_multiply_scalar([subentDict oo_hpvectorForKey:@"position"],_scaleFactor)];
931 [
self addSubEntity:flasher];
936- (BOOL) setUpOneStandardSubentity:(NSDictionary *)subentDict asTurret:(BOOL)asTurret
939 NSString *subentKey =
nil;
940 HPVector subPosition;
941 Quaternion subOrientation;
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);
949 if (!asTurret && [
self isStation] && [subentDict oo_boolForKey:
@"is_dock"])
951 subentity = [UNIVERSE newDockWithName:subentKey andScaleFactor:_scaleFactor];
955 subentity = [UNIVERSE newSubentityWithName:subentKey andScaleFactor:_scaleFactor];
957 if (subentity ==
nil) {
958 OOLog(
@"setup.ship.badEntry.subentities",
@"Failed to set up entity %@",subentKey);
962 subPosition = HPvector_multiply_scalar([subentDict oo_hpvectorForKey:
@"position"],_scaleFactor);
963 subOrientation = [subentDict oo_quaternionForKey:@"orientation"];
967 [subentity
setReference:vector_forward_from_quaternion(subOrientation)];
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]];
986 [
self addSubEntity:subentity];
992 bounding_box_add_vector(&totalBoundingBox, sebb.max);
993 bounding_box_add_vector(&totalBoundingBox, sebb.min);
995 if (!asTurret && [
self isStation] && [subentDict oo_boolForKey:
@"is_dock"])
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];
1001 BOOL virtual_dock = [subentDict oo_boolForKey:@"_is_virtual_dock" defaultValue:NO];
1008 [subentity
setDisplayName:[subentDict oo_stringForKey:@"dock_label" defaultValue:@"the docking bay"]];
1011 [subentity release];
1017- (BOOL) isTemplateCargoPod
1019 return [[
self primaryRole] isEqualToString:@"oolite-template-cargopod"];
1023- (void) setUpCargoType:(NSString *) cargoString
1030 commodity_amount = 1;
1032 commodity_type =
@"slaves";
1037 commodity_amount = 1;
1039 commodity_type =
@"alloys";
1044 commodity_amount = 1;
1046 commodity_type =
@"minerals";
1051 commodity_amount = 1;
1053 commodity_type =
@"alien_items";
1058 commodity_amount = 1;
1064 if (commodity_amount == 0) commodity_amount = 1;
1085 [weakSelf weakRefDrop];
1089 [
self setTrackCloseContacts:NO];
1090 [[
self parentEntity] subEntityReallyDied:self];
1091 [
self clearSubEntities];
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);
1119 DESTROY(_collisionExceptions);
1124 DESTROY(starboardWeaponOffset);
1128 [
self setSubEntityTakingDamage:nil];
1129 [
self removeAllEquipment];
1131 [_group removeShip:self];
1133 [_escortGroup removeShip:self];
1148- (void) removeScript
1150 [script autorelease];
1155- (void) clearSubEntities
1157 [subEntities makeObjectsPerformSelector:@selector(setOwner:) withObject:nil];
1158 [subEntities release];
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]);
1169- (Quaternion) subEntityRotationalVelocity
1171 return subentityRotationalVelocity;
1175- (void) setSubEntityRotationalVelocity:(Quaternion)rv
1177 subentityRotationalVelocity = rv;
1181- (NSString *)descriptionComponents
1183 if (![
self isSubEntity])
1185 return [NSString stringWithFormat:@"\"%@\" %@", [
self name], [
super descriptionComponents]];
1190 NSString *subtype =
nil;
1191 if ([
self behaviour] == BEHAVIOUR_TRACK_AS_TURRET) subtype =
@"(turret)";
1192 else subtype =
@"(subentity)";
1194 return [NSString stringWithFormat:@"\"%@\" position: %@ %@", [
self name], HPVectorDescription([
self position]), subtype];
1199- (NSString *) shortDescriptionComponents
1201 return [NSString stringWithFormat:@"\"%@\"", [
self name]];
1205- (GLfloat) sunGlareFilter
1207 return sunGlareFilter;
1211- (void) setSunGlareFilter:(GLfloat)newValue
1213 sunGlareFilter = OOClamp_0_1_f(newValue);
1223- (void) setAccuracy:(GLfloat) new_accuracy
1225 if (new_accuracy < 0.0f && scanClass == CLASS_MISSILE)
1229 else if (new_accuracy < -5.0f)
1231 new_accuracy = -5.0;
1233 else if (new_accuracy > 10.0f)
1235 new_accuracy = 10.0;
1237 accuracy = new_accuracy;
1238 pitch_tolerance = 0.01 * (85.0f + accuracy);
1240 aim_tolerance = 240.0 - (18.0f * accuracy);
1244 missile_load_time = 2.0;
1250 return (
OOMesh *)[
self drawable];
1254- (void)setMesh:(
OOMesh *)mesh
1256 if (mesh != [
self mesh])
1258 [
self setDrawable:mesh];
1259 [octree autorelease];
1260 octree = [[mesh
octree] retain];
1265- (BoundingBox) totalBoundingBox
1267 return totalBoundingBox;
1271- (Vector) forwardVector
1283- (Vector) rightVector
1289- (BOOL) scriptedMisjump
1291 return scripted_misjump;
1295- (void) setScriptedMisjump:(BOOL)newValue
1297 scripted_misjump = !!newValue;
1301- (GLfloat) scriptedMisjumpRange
1303 return _scriptedMisjumpRange;
1307- (void) setScriptedMisjumpRange:(GLfloat)newValue
1309 _scriptedMisjumpRange = newValue;
1313- (NSArray *) subEntities
1315 return [[subEntities copy] autorelease];
1319- (NSUInteger) subEntityCount
1321 return [subEntities count];
1325- (BOOL) hasSubEntity:(
Entity<OOSubEntity> *)sub
1327 return [subEntities containsObject:sub];
1331- (NSEnumerator *)subEntityEnumerator
1333 return [[
self subEntities] objectEnumerator];
1337- (NSEnumerator *)shipSubEntityEnumerator
1339 return [[
self subEntities] objectEnumeratorFilteredWithSelector:@selector(isShip)];
1343- (NSEnumerator *)flasherEnumerator
1345 return [[
self subEntities] objectEnumeratorFilteredWithSelector:@selector(isFlasher)];
1349- (NSEnumerator *)exhaustEnumerator
1351 return [[
self subEntities] objectEnumeratorFilteredWithSelector:@selector(isExhaust)];
1357 ShipEntity *result = [_subEntityTakingDamage weakRefUnderlyingObject];
1362 if ([result parentEntity] !=
self) result =
nil;
1366 if (result ==
nil) [
self setSubEntityTakingDamage:nil];
1372- (void) setSubEntityTakingDamage:(
ShipEntity *)sub
1378 if (![
self hasSubEntity:sub])
1380 OOLog(
@"ship.subentity.sanityCheck.failed.details",
@"Attempt to set subentity taking damage of %@ to %@, which is not a subentity.", [
self shortDescription], sub);
1383 else if (![sub isShip])
1385 OOLog(
@"ship.subentity.sanityCheck.failed",
@"Attempt to set subentity taking damage of %@ to %@, which is not a ship.", [
self shortDescription], sub);
1391 [_subEntityTakingDamage release];
1410 return aiScriptWakeTime;
1416 aiScriptWakeTime = t;
1420- (BoundingBox)findBoundingBoxRelativeToPosition:(HPVector)opv InVectors:(Vector) _i :(Vector) _j :(Vector) _k
1423 return [[
self mesh] findBoundingBoxRelativeToPosition:HPVectorToVector(opv)
1425 selfPosition:HPVectorToVector(position)
1426 selfBasis:v_right :v_up :v_forward];
1438 return [octree volume];
1442- (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1
1444 Vector u0 = HPVectorToVector(HPvector_between(position, v0));
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));
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];
1452- (GLfloat) doesHitLine:(HPVector)v0 :(HPVector)v1 :(
ShipEntity **)hitEntity
1456 Vector u0 = HPVectorToVector(HPvector_between(position, v0));
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));
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];
1464 hitEntity[0] =
self;
1467 NSEnumerator *subEnum =
nil;
1469 for (subEnum = [
self shipSubEntityEnumerator]; (se = [subEnum nextObject]); )
1473 u0 = HPVectorToVector(HPvector_between(p0, v0));
1474 u1 = HPVectorToVector(HPvector_between(p0, v1));
1475 w0 = resolveVectorInIJK(u0, ijk);
1476 w1 = resolveVectorInIJK(u1, ijk);
1478 GLfloat hitSub = [se->octree isHitByLine:w0 :w1];
1479 if (hitSub && (hit_distance == 0 || hit_distance > hitSub))
1481 hit_distance = hitSub;
1489 return hit_distance;
1493- (GLfloat)doesHitLine:(HPVector)v0 :(HPVector)v1 withPosition:(HPVector)o andIJK:(Vector)i :(Vector)j :(Vector)k
1495 Vector u0 = HPVectorToVector(HPvector_between(o, v0));
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));
1498 Vector w1 = make_vector(dot_product(u1, j), dot_product(u1, j), dot_product(u1, k));
1499 return [octree isHitByLine:w0 :w1];
1503- (void) wasAddedToUniverse
1505 [
super wasAddedToUniverse];
1512 if (([
self status] == STATUS_IN_FLIGHT || [
self status] == STATUS_LAUNCHING) && _pendingEscortCount != 0)
1514 [
self setUpEscorts];
1523 _pendingEscortCount = 0;
1528 [subEntities makeObjectsPerformSelector:@selector(wasAddedToUniverse)];
1530 [
self resetExhaustPlumes];
1534- (void)wasRemovedFromUniverse
1536 [subEntities makeObjectsPerformSelector:@selector(wasRemovedFromUniverse)];
1540- (HPVector)absoluteTractorPosition
1542 return HPvector_add(position, vectorToHPVector(
quaternion_rotate_vector([
self normalOrientation], tractor_position)));
1546- (NSString *) beaconCode
1552- (void) setBeaconCode:(NSString *)bcode
1554 if ([bcode length] == 0) bcode =
nil;
1556 if (_beaconCode != bcode)
1558 [_beaconCode release];
1559 _beaconCode = [bcode copy];
1564 if (bcode !=
nil && (_beaconLabel ==
nil || [_beaconLabel length] == 0))
1566 [
self setBeaconLabel:bcode];
1571- (NSString *) beaconLabel
1573 return _beaconLabel;
1577- (void) setBeaconLabel:(NSString *)blabel
1579 if ([blabel length] == 0) blabel =
nil;
1581 if (_beaconLabel != blabel)
1583 [_beaconLabel release];
1584 _beaconLabel = [OOExpand(blabel) retain];
1591 return cam_zero_distance <= no_draw_distance;
1597 return [
self beaconCode] !=
nil;
1601- (
id <OOHUDBeaconIcon>) beaconDrawable
1603 if (_beaconDrawable ==
nil)
1605 NSString *beaconCode = [
self beaconCode];
1606 NSUInteger length = [beaconCode length];
1610 NSArray *iconData = [[UNIVERSE descriptions] oo_arrayForKey:beaconCode];
1611 if (iconData !=
nil) _beaconDrawable = [[
OOPolygonSprite alloc] initWithDataArray:iconData outlineWidth:0.5 name:beaconCode];
1614 if (_beaconDrawable ==
nil)
1616 if (length > 0) _beaconDrawable = [[beaconCode substringToIndex:1] retain];
1617 else _beaconDrawable =
@"";
1621 return _beaconDrawable;
1625- (
Entity <OOBeaconEntity> *) prevBeacon
1627 return [_prevBeacon weakRefUnderlyingObject];
1631- (
Entity <OOBeaconEntity> *) nextBeacon
1633 return [_nextBeacon weakRefUnderlyingObject];
1637- (void) setPrevBeacon:(
Entity <OOBeaconEntity> *)beaconShip
1639 if (beaconShip != [
self prevBeacon])
1641 [_prevBeacon release];
1642 _prevBeacon = [beaconShip weakRetain];
1647- (void) setNextBeacon:(
Entity <OOBeaconEntity> *)beaconShip
1649 if (beaconShip != [
self nextBeacon])
1651 [_nextBeacon release];
1652 _nextBeacon = [beaconShip weakRetain];
1657#define kBoulderRole (@"boulder")
1659- (void) setIsBoulder:(BOOL)flag
1661 if (flag) [
self addRole:kBoulderRole];
1662 else [
self removeRole:kBoulderRole];
1668 return [roleSet hasRole:kBoulderRole];
1674 if ([
self hasRole:
@"asteroid"] || [
self isBoulder])
1685- (BOOL) countsAsKill
1687 return [[
self shipInfoDictionary] oo_boolForKey:@"counts_as_kill" defaultValue:YES];
1691- (void) setUpEscorts
1696 if ([
self isEscort])
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);
1703 if ([shipinfoDictionary objectForKey:
@"escort_roles"] !=
nil)
1705 [
self setUpMixedEscorts];
1709 NSString *defaultRole =
@"escort";
1710 NSString *escortRole =
nil;
1711 NSString *escortShipKey =
nil;
1713 if (_pendingEscortCount == 0)
return;
1715 if (_maxEscortCount < _pendingEscortCount)
1717 if ([
self hasPrimaryRole:
@"police"] || [
self hasPrimaryRole:
@"hunter"])
1720 [
self updateEscortFormation];
1724 _pendingEscortCount = _maxEscortCount;
1728 if ([
self isPolice]) defaultRole =
@"wingman";
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])
1735 if (![[
UNIVERSE newShipWithRole:escortRole] autorelease])
1737 escortRole = defaultRole;
1741 escortShipKey = [shipinfoDictionary oo_stringForKey:@"escort_ship" defaultValue:nil];
1742 if (escortShipKey ==
nil)
1743 escortShipKey = [shipinfoDictionary oo_stringForKey:@"escort-ship"];
1745 if (escortShipKey !=
nil)
1747 if (![[
UNIVERSE newShipWithName:escortShipKey] autorelease])
1749 escortShipKey =
nil;
1753 escortRole = [NSString stringWithFormat:@"[%@]",escortShipKey];
1758 if ([
self group] ==
nil)
1760 [
self setGroup:escortGroup];
1764 [
self refreshEscortPositions];
1766 uint8_t currentEscortCount = [escortGroup
count] - 1;
1768 while (_pendingEscortCount > 0 && ([
self isThargoid] || currentEscortCount < _maxEscortCount))
1771 HPVector ex_pos = [
self coordinatesForEscortPosition:currentEscortCount];
1775 escorter = [UNIVERSE newShipWithRole:escortRole];
1777 if (escorter ==
nil)
break;
1778 [
self setUpOneEscort:escorter inGroup:escortGroup withRole:escortRole atPosition:ex_pos andCount:currentEscortCount];
1782 _pendingEscortCount--;
1783 currentEscortCount = [escortGroup
count] - 1;
1786 _pendingEscortCount = 0;
1792 NSArray *escortRoles = [shipinfoDictionary oo_arrayForKey:@"escort_roles" defaultValue:nil];
1793 if (escortRoles ==
nil)
1795 OOLogWARN(
@"eship.setUp.escortShipRoles",
1796 @"Ship %@ has bad escort_roles definition.",
self);
1799 NSDictionary *escortDefinition =
nil;
1800 NSDictionary *systeminfo =
nil;
1803 systeminfo = [UNIVERSE currentSystemData];
1804 government = [systeminfo oo_unsignedCharForKey:KEY_GOVERNMENT];
1807 if ([
self group] ==
nil)
1809 [
self setGroup:escortGroup];
1813 [
self refreshEscortPositions];
1815 uint8_t currentEscortCount = [escortGroup
count] - 1;
1817 _maxEscortCount = 0;
1819 foreach (escortDefinition, escortRoles)
1827 int8_t min = [escortDefinition oo_intForKey:@"min" defaultValue:0];
1828 int8_t max = [escortDefinition oo_intForKey:@"max" defaultValue:2];
1829 NSString *escortRole = [escortDefinition oo_stringForKey:@"role" defaultValue:@"escort"];
1830 int8_t desired = max;
1833 for (i = min ; i < max ; i++)
1835 if (
Ranrot()%11 < government+2)
1841 for (i = 0; i < desired; i++)
1847 if (![escortRole isEqualToString:
@""])
1849 HPVector ex_pos = [
self coordinatesForEscortPosition:currentEscortCount];
1850 ShipEntity *escorter = [UNIVERSE newShipWithRole:escortRole];
1851 if (escorter ==
nil)
1855 [
self setUpOneEscort:escorter inGroup:escortGroup withRole:escortRole atPosition:ex_pos andCount:currentEscortCount];
1858 currentEscortCount++;
1863 _pendingEscortCount = 0;
1867- (void) setUpOneEscort:(
ShipEntity *)escorter inGroup:(
OOShipGroup *)escortGroup withRole:(NSString *)escortRole atPosition:(HPVector)ex_pos andCount:(uint8_t)currentEscortCount
1869 NSString *autoAI =
nil;
1870 NSString *pilotRole =
nil;
1871 NSDictionary *autoAIMap =
nil;
1872 NSDictionary *escortShipDict =
nil;
1874 NSString *defaultRole =
@"escort";
1876 if ([
self isPolice])
1878 defaultRole =
@"wingman";
1879 pilotRole =
@"police";
1883 pilotRole = bounty ?
@"pirate" :
@"hunter";
1891 ex_pos.x += dd * 6.0 * (
randf() - 0.5);
1892 ex_pos.y += dd * 6.0 * (
randf() - 0.5);
1893 ex_pos.z += dd * 6.0 * (
randf() - 0.5);
1898 ex_pos.x += dd * 12.0 * (
randf() - 0.5);
1899 ex_pos.y += dd * 12.0 * (
randf() - 0.5);
1900 ex_pos.z += dd * 12.0 * (
randf() - 0.5);
1905 if ([escorter crew] ==
nil)
1913 if (scanClass == CLASS_NOT_SET)
1915 scanClass = CLASS_NEUTRAL;
1924 autoAI = [autoAIMap oo_stringForKey:defaultRole];
1927 autoAI = [autoAIMap oo_stringForKey:@"escort" defaultValue:@"nullAI.plist"];
1930 escortAI = [escorter
getAI];
1933 if ( (escortRole && [escortShipDict oo_fuzzyBooleanForKey:
@"auto_ai" defaultValue:YES])
1934 || ([[escortAI name] isEqualToString:
@"nullAI.plist"] && ![autoAI isEqualToString:
@"nullAI.plist"]) )
1943 if ([
self status] == STATUS_DOCKED)
1945 [[
self owner] addShipToLaunchQueue:escorter withPriority:NO];
1949 [UNIVERSE addEntity:escorter];
1950 [escortAI
setState:@"FLYING_ESCORT"];
1954 if([escorter heatInsulation] < [
self heatInsulation]) [escorter
setHeatInsulation:[
self heatInsulation]];
1955 if(([escorter maxFlightSpeed] < cruiseSpeed) && ([escorter maxFlightSpeed] > cruiseSpeed * 0.3))
1973- (NSString *)shipDataKey
1979- (NSString *)shipDataKeyAutoRole
1981 return [[[NSString alloc] initWithFormat:@"[%@]",[
self shipDataKey]] autorelease];
1985- (void)setShipDataKey:(NSString *)key
1988 _shipKey = [key copy];
1992- (NSDictionary *)shipInfoDictionary
1994 return shipinfoDictionary;
1998#define MAKE_VECTOR_ARRAY(v) [[[OONativeVector alloc] initWithVector:v] autorelease]
2000- (NSArray *) getWeaponOffsetFrom:(NSDictionary *)dict withKey:(NSString *)key inMode:(NSString *)mode
2003 if ([
mode isEqualToString:
@"single"])
2005 offset = vector_multiply_scalar([dict oo_vectorForKey:key defaultValue:
kZeroVector],_scaleFactor);
2006 return [NSArray arrayWithObject:MAKE_VECTOR_ARRAY(offset)];
2010 NSArray *offsets = [dict oo_arrayForKey:key defaultValue:nil];
2011 if (offsets ==
nil) {
2013 return [NSArray arrayWithObject:MAKE_VECTOR_ARRAY(offset)];
2015 NSMutableArray *output = [NSMutableArray arrayWithCapacity:[offsets count]];
2017 for (i=0;i<[offsets count];i++) {
2018 offset = vector_multiply_scalar([offsets oo_vectorAtIndex:i defaultValue:
kZeroVector],_scaleFactor);
2019 [output addObject:MAKE_VECTOR_ARRAY(offset)];
2021 return [NSArray arrayWithArray:output];
2026- (NSArray *) aftWeaponOffset
2028 return aftWeaponOffset;
2032- (NSArray *) forwardWeaponOffset
2034 return forwardWeaponOffset;
2038- (NSArray *) portWeaponOffset
2040 return portWeaponOffset;
2044- (NSArray *) starboardWeaponOffset
2046 return starboardWeaponOffset;
2056- (BOOL) suppressFlightNotifications
2058 return suppressAegisMessages;
2064 if (cloaking_device_active)
return CLASS_NO_DRAW;
2072 int status = [
self status];
2073 if (status == STATUS_COCKPIT_DISPLAY || status == STATUS_DEAD || status == STATUS_BEING_SCOOPED)
2084 if (isMissile && [
self shotTime] < 0.25)
2103 Vector relative_position_of_other = resolveVectorInIJK(HPVectorToVector(HPvector_between(prime_position, other_position)), prime_ijk);
2104 Triangle relative_ijk_of_other;
2105 relative_ijk_of_other.v[0] = resolveVectorInIJK(other_ijk.v[0], prime_ijk);
2106 relative_ijk_of_other.v[1] = resolveVectorInIJK(other_ijk.v[1], prime_ijk);
2107 relative_ijk_of_other.v[2] = resolveVectorInIJK(other_ijk.v[2], prime_ijk);
2111 withOrigin:relative_position_of_other
2112 andIJK:relative_ijk_of_other])
2121 NSUInteger i, n_subs = [prime_subs count];
2122 for (i = 0; i < n_subs; i++)
2124 Entity* se = [prime_subs objectAtIndex:i];
2134 NSUInteger i, n_subs = [other_subs count];
2135 for (i = 0; i < n_subs; i++)
2137 Entity* se = [other_subs objectAtIndex:i];
2144 if ((prime_subs)&&(other_subs))
2146 NSUInteger i, n_osubs = [other_subs count];
2147 for (i = 0; i < n_osubs; i++)
2149 Entity* oe = [other_subs objectAtIndex:i];
2150 if ([oe isShip] && [oe canCollide])
2152 NSUInteger j, n_psubs = [prime_subs count];
2153 for (j = 0; j < n_psubs; j++)
2155 Entity* pe = [prime_subs objectAtIndex:j];
2168- (BOOL) checkCloseCollisionWith:(
Entity *)other
2170 if (other ==
nil)
return NO;
2171 if ([collidingEntities containsObject:other]) return NO;
2174 if ([other isShip]) otherShip = (
ShipEntity *)other;
2176 if ([
self canScoop:otherShip]) return YES;
2178 if (otherShip !=
nil && trackCloseContacts)
2182 HPVector otherPos = [otherShip
position];
2184 NSString *other_key = [NSString stringWithFormat:@"%d", otherID];
2186 if (![closeContactsInfo objectForKey:other_key] &&
2187 HPdistance2(position, otherPos) < collision_radius * collision_radius)
2190 Vector dpos = HPVectorToVector(HPvector_between(position, otherPos));
2191 Vector rpos = make_vector(dot_product(dpos, v_right), dot_product(dpos, v_up), dot_product(dpos, v_forward));
2192 [closeContactsInfo setObject:[NSString stringWithFormat:@"%f %f %f", rpos.x, rpos.y, rpos.z] forKey: other_key];
2197 [
self doScriptEvent:OOJSID("shipCloseContact") withArgument:otherShip andReactToAIMessage:@"CLOSE CONTACT"];
2198 _primaryTarget = temp;
2214 if (otherShip !=
nil)
2218 return (collider !=
nil);
2227- (BoundingBox)findSubentityBoundingBox
2229 return [[
self mesh] findSubentityBoundingBoxWithPosition:HPVectorToVector(position) rotMatrix:rotMatrix];
2233- (Triangle) absoluteIJKForSubentity
2240 while ((father)&&(father != last) && (father !=
NO_TARGET))
2248 if (![last isSubEntity])
break;
2249 father = [father
owner];
2255- (void) addSubentityToCollisionRadius:(
Entity<OOSubEntity> *)subent
2257 if (!subent)
return;
2259 double distance = HPmagnitude([subent position]) + [subent findCollisionRadius];
2260 if ([subent isKindOfClass:[
ShipEntity class]])
2262 if (distance > collision_radius)
2264 collision_radius = distance;
2267 mass += [subent mass];
2269 if (distance > _profileRadius)
2271 _profileRadius = distance;
2276- (
ShipEntity *) launchPodWithCrew:(NSArray *)podCrew
2280 pod = [UNIVERSE newShipWithRole:[shipinfoDictionary oo_stringForKey:@"escape_pod_role"]];
2284 pod = [UNIVERSE newShipWithRole:[shipinfoDictionary oo_stringForKey:@"escape_pod_model" defaultValue:@"escape-capsule"]];
2287 pod = [UNIVERSE newShipWithRole:@"escape-capsule"];
2288 OOLog(
@"shipEntity.noEscapePod",
@"Ship %@ has no correct escape_pod_role defined. Now using default capsule.",
self);
2295 [pod
setTemperature:[
self randomEjectaTemperatureWithMaxFactor:0.9]];
2299 [
self dumpItem:pod];
2307- (BOOL) validForAddToUniverse
2309 if (shipinfoDictionary ==
nil)
2311 OOLog(
@"shipEntity.notDict",
@"Ship %@ was not set up from dictionary.",
self);
2314 return [
super validForAddToUniverse];
2320 if (shipinfoDictionary ==
nil)
2322 OOLog(
@"shipEntity.notDict",
@"Ship %@ was not set up from dictionary.",
self);
2323 [UNIVERSE removeEntity:self];
2327 if (!isfinite(maxFlightSpeed))
2329 OOLog(
@"ship.sanityCheck.failed",
@"Ship %@ %@ infinite top speed, clamped to 300.",
self,
@"had");
2330 maxFlightSpeed = 300;
2333 bool isSubEnt = [
self isSubEntity];
2343 Quaternion q1 = make_quaternion(cos1, sin1*sqrt(3)/2, sin1/2, 0);
2344 Quaternion q2 = make_quaternion(cos2, -sin2*sqrt(4)/sqrt(5), 0, sin2*sqrt(1)/sqrt(5));
2345 [
self setOrientation: quaternion_multiply(q2, quaternion_multiply(q1, demoStartOrientation))];
2348 [
super update:delta_t];
2349 if ([
self subEntityCount] > 0)
2353 foreach (se, [
self subEntities])
2359 bounding_box_add_vector(&totalBoundingBox, sebb.max);
2360 bounding_box_add_vector(&totalBoundingBox, sebb.min);
2370 if (scanClass == CLASS_NOT_SET)
2372 scanClass = CLASS_NEUTRAL;
2373 OOLog(
@"ship.sanityCheck.failed",
@"Ship %@ %@ with scanClass CLASS_NOT_SET; forced to CLASS_NEUTRAL.",
self, [
self primaryRole]);
2376 [
self updateTrackingCurve];
2381 [
self manageCollisions];
2388 if (scanClass == CLASS_POLICE)
2392 [
self setBounty:0 withReason:kOOLegalStatusReasonPoliceAreClean];
2395 if ((target)&&([target scanClass] == CLASS_POLICE))
2397 [
self noteLostTarget];
2401 if (trackCloseContacts)
2405 NSString *other_key =
nil;
2409 NSDictionary *closeContactsTemp = [[NSDictionary alloc] initWithDictionary:closeContactsInfo];
2412 ShipEntity* other = [UNIVERSE entityForUniversalID:[other_key intValue]];
2415 if (HPdistance2(position, other->
position) > collision_radius * collision_radius)
2418 Vector dpos = HPVectorToVector(HPvector_between(position, other->
position));
2419 Vector pos1 = make_vector(dot_product(dpos, v_right), dot_product(dpos, v_up), dot_product(dpos, v_forward));
2420 Vector pos0 = {0, 0, 0};
2425 if ((pos0.x < 0.0)&&(pos1.x > 0.0))
2427 [
self doScriptEvent:OOJSID("shipTraversePositiveX") withArgument:other andReactToAIMessage:@"POSITIVE X TRAVERSE"];
2429 if ((pos0.x > 0.0)&&(pos1.x < 0.0))
2431 [
self doScriptEvent:OOJSID("shipTraverseNegativeX") withArgument:other andReactToAIMessage:@"NEGATIVE X TRAVERSE"];
2433 if ((pos0.y < 0.0)&&(pos1.y > 0.0))
2435 [
self doScriptEvent:OOJSID("shipTraversePositiveY") withArgument:other andReactToAIMessage:@"POSITIVE Y TRAVERSE"];
2437 if ((pos0.y > 0.0)&&(pos1.y < 0.0))
2439 [
self doScriptEvent:OOJSID("shipTraverseNegativeY") withArgument:other andReactToAIMessage:@"NEGATIVE Y TRAVERSE"];
2441 if ((pos0.z < 0.0)&&(pos1.z > 0.0))
2443 [
self doScriptEvent:OOJSID("shipTraversePositiveZ") withArgument:other andReactToAIMessage:@"POSITIVE Z TRAVERSE"];
2445 if ((pos0.z > 0.0)&&(pos1.z < 0.0))
2447 [
self doScriptEvent:OOJSID("shipTraverseNegativeZ") withArgument:other andReactToAIMessage:@"NEGATIVE Z TRAVERSE"];
2449 _primaryTarget = temp;
2450 [closeContactsInfo removeObjectForKey: other_key];
2455 [closeContactsInfo removeObjectForKey: other_key];
2458 [closeContactsTemp release];
2466 if (reportAIMessages && (debugLastBehaviour != behaviour))
2469 debugLastBehaviour = behaviour;
2478 starboard_weapon_temp = fmaxf(starboard_weapon_temp - (
float)(
WEAPON_COOLING_FACTOR * delta_t), 0.0f);
2481 shot_time += delta_t;
2484 if (messageTime > 0.0)
2486 messageTime -= delta_t;
2487 if (messageTime < 0.0) messageTime = 0.0;
2493 double external_temp = 0.0;
2498 double sun_zd = HPdistance2(position, [sun position]);
2500 double alt1 = sun_cr * sun_cr / sun_zd;
2502 if ([sun goneNova]) external_temp *= 100;
2504 if ([
self hasFuelScoop] && alt1 > 0.75 && [
self fuel] < [
self fuelCapacity])
2506 fuel_accumulator += (float)(delta_t * flightSpeed * 0.010 / [
self fuelChargeRate]);
2508 while (fuel_accumulator > 1.0f)
2510 [
self setFuel:[
self fuel] + 1];
2511 fuel_accumulator -= 1.0f;
2512 [
self doScriptEvent:OOJSID("shipScoopedFuel")];
2519 float heatThreshold = [
self heatInsulation] * 100.0f;
2520 if (external_temp > heatThreshold && external_temp > ship_temperature)
2526 ship_temperature += (external_temp - heatThreshold - ship_temperature) * delta_t *
SHIP_COOLING_FACTOR / [
self heatInsulation];
2533 ship_temperature = [[
self owner] temperature];
2537 [
self takeHeatDamage: delta_t * ship_temperature];
2540 if ((energy < maxEnergy * 0.20)&&_showDamage)
2546 next_spark_time -= delta_t;
2547 if (next_spark_time < 0.0)
2558 if ([
self hasCloakingDevice])
2560 if (cloaking_device_active)
2565 [
self deactivateCloakingDevice];
2566 if (energy < 0) energy = 0;
2572 if ([
self hasMilitaryJammer])
2574 if (military_jammer_active)
2579 military_jammer_active = NO;
2580 if (energy < 0) energy = 0;
2586 military_jammer_active = YES;
2597 if (_nextAegisCheck < distanceTravelled || !vector_equal([super velocity],
kZeroVector))
2599 aegis_status = [
self checkForAegis];
2603 _nextAegisCheck = distanceTravelled + 1000.0;
2608 _nextAegisCheck = distanceTravelled + 100.0;
2614 if (!haveExecutedSpawnAction)
2618 if (script !=
nil && (status == STATUS_IN_FLIGHT ||
2619 status == STATUS_LAUNCHING ||
2620 status == STATUS_BEING_SCOOPED ||
2621 (status == STATUS_ACTIVE &&
self == [
UNIVERSE station])
2624 [PLAYER setScriptTarget:self];
2625 [
self doScriptEvent:OOJSID("shipSpawned")];
2626 if ([
self status] != STATUS_DEAD) [PLAYER doScriptEvent:OOJSID("shipSpawned") withArgument:self];
2628 haveExecutedSpawnAction = YES;
2631 if (!haveStartedJSAI && [
self status] != STATUS_LAUNCHING)
2633 haveStartedJSAI = YES;
2634 [
self doScriptEvent:OOJSID("aiStarted")];
2639 if ([
self status] == STATUS_LAUNCHING)
2641 if ([
UNIVERSE getTime] > launch_time + launch_delay)
2643 StationEntity *stationLaunchedFrom = [UNIVERSE nearestEntityMatchingPredicate:IsStationPredicate parameter:NULL relativeToEntity:self];
2644 [
self setStatus:STATUS_IN_FLIGHT];
2646 haveStartedJSAI = YES;
2647 [
self doScriptEvent:OOJSID("aiStarted")];
2648 [
self doScriptEvent:OOJSID("shipLaunchedFromStation") withArgument:stationLaunchedFrom];
2649 [shipAI reactToMessage:@"LAUNCHED OKAY" context:@"launched"];
2655 [
self applyAttitudeChanges:delta_t];
2656 [
self applyThrust:delta_t];
2657 if (energy < maxEnergy)
2659 energy += energy_recharge_rate * delta_t;
2660 if (energy > maxEnergy)
2663 [
self doScriptEvent:OOJSID("shipEnergyBecameFull")];
2664 [shipAI message:@"ENERGY_FULL"];
2668 if ([
self subEntityCount] > 0)
2672 foreach (se, [
self subEntities])
2678 [
super update:delta_t];
2686 if ([
self status] == STATUS_BEING_SCOOPED)
2689 if (behaviour != BEHAVIOUR_TRACTORED || [
self owner] ==
nil || [
self owner] ==
self || [
self owner] ==
NO_TARGET)
2692 [
self setStatus:STATUS_IN_FLIGHT];
2693 behaviour = BEHAVIOUR_IDLE;
2695 [
self setOwner:self];
2696 [shipAI exitStateMachineWithMessage:nil];
2700 if ([
self status] == STATUS_COCKPIT_DISPLAY)
2703 [
self applyAttitudeChanges:delta_t];
2704 GLfloat range2 = 0.1 * HPdistance2(position, _destination) / (collision_radius * collision_radius);
2705 if ((range2 > 1.0)||(velocity.z > 0.0)) range2 = 1.0;
2706 position = HPvector_add(position, vectorToHPVector(vector_multiply_scalar(velocity, range2 * delta_t)));
2710 [
self processBehaviour:delta_t];
2713 if (energy < maxEnergy)
2715 energy += energy_recharge_rate * delta_t;
2716 if (energy > maxEnergy)
2719 [
self doScriptEvent:OOJSID("shipEnergyBecameFull")];
2720 [shipAI message:@"ENERGY_FULL"];
2727 [
self refreshEscortPositions];
2728 if ([
self hasEscorts])
2733 foreach (escort, [
self escortArray])
2738 ShipEntity *leader = [[
self escortGroup] leader];
2739 if (leader !=
nil && ([leader scanClass] != [
self scanClass])) {
2740 OOLog(
@"ship.sanityCheck.failed",
@"Ship %@ escorting %@ with wrong scanclass!",
self, leader);
2741 [[
self escortGroup] removeShip:self];
2742 [
self setEscortGroup:nil];
2752 Quaternion qf = subentityRotationalVelocity;
2753 qf.w *= (1.0 - delta_t);
2757 [
self setOrientation:quaternion_multiply(qf, orientation)];
2761 totalBoundingBox = boundingBox;
2764 [
super update:delta_t];
2768 if ([
self subEntityCount] > 0)
2772 foreach (se, [
self subEntities])
2778 bounding_box_add_vector(&totalBoundingBox, sebb.max);
2779 bounding_box_add_vector(&totalBoundingBox, sebb.min);
2784 if (aiScriptWakeTime > 0 && [
PLAYER clockTimeAdjusted] > aiScriptWakeTime)
2786 aiScriptWakeTime = 0;
2787 [
self doScriptEvent:OOJSID("aiAwoken")];
2794 BOOL applyThrust = YES;
2797 case BEHAVIOUR_TUMBLE :
2798 [
self behaviour_tumble: delta_t];
2801 case BEHAVIOUR_STOP_STILL :
2802 case BEHAVIOUR_STATION_KEEPING :
2803 [
self behaviour_stop_still: delta_t];
2806 case BEHAVIOUR_IDLE :
2807 if ([
self isSubEntity])
2811 [
self behaviour_idle: delta_t];
2814 case BEHAVIOUR_TRACTORED :
2815 [
self behaviour_tractored: delta_t];
2818 case BEHAVIOUR_TRACK_TARGET :
2819 [
self behaviour_track_target: delta_t];
2822 case BEHAVIOUR_INTERCEPT_TARGET :
2823 case BEHAVIOUR_COLLECT_TARGET :
2824 [
self behaviour_intercept_target: delta_t];
2827 case BEHAVIOUR_ATTACK_TARGET :
2828 [
self behaviour_attack_target: delta_t];
2831 case BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX :
2832 case BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE :
2833 [
self behaviour_fly_to_target_six: delta_t];
2836 case BEHAVIOUR_ATTACK_MINING_TARGET :
2837 [
self behaviour_attack_mining_target: delta_t];
2840 case BEHAVIOUR_ATTACK_FLY_TO_TARGET :
2841 [
self behaviour_attack_fly_to_target: delta_t];
2844 case BEHAVIOUR_ATTACK_FLY_FROM_TARGET :
2845 [
self behaviour_attack_fly_from_target: delta_t];
2848 case BEHAVIOUR_ATTACK_BREAK_OFF_TARGET :
2849 [
self behaviour_attack_break_off_target: delta_t];
2852 case BEHAVIOUR_ATTACK_SLOW_DOGFIGHT :
2853 [
self behaviour_attack_slow_dogfight: delta_t];
2856 case BEHAVIOUR_RUNNING_DEFENSE :
2857 [
self behaviour_running_defense: delta_t];
2860 case BEHAVIOUR_ATTACK_BROADSIDE :
2861 [
self behaviour_attack_broadside: delta_t];
2864 case BEHAVIOUR_ATTACK_BROADSIDE_LEFT :
2865 [
self behaviour_attack_broadside_left: delta_t];
2868 case BEHAVIOUR_ATTACK_BROADSIDE_RIGHT :
2869 [
self behaviour_attack_broadside_right: delta_t];
2872 case BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE :
2873 [
self behaviour_close_to_broadside_range: delta_t];
2876 case BEHAVIOUR_CLOSE_WITH_TARGET :
2877 [
self behaviour_close_with_target: delta_t];
2880 case BEHAVIOUR_ATTACK_SNIPER :
2881 [
self behaviour_attack_sniper: delta_t];
2884 case BEHAVIOUR_EVASIVE_ACTION :
2885 case BEHAVIOUR_FLEE_EVASIVE_ACTION :
2886 [
self behaviour_evasive_action: delta_t];
2889 case BEHAVIOUR_FLEE_TARGET :
2890 [
self behaviour_flee_target: delta_t];
2893 case BEHAVIOUR_FLY_RANGE_FROM_DESTINATION :
2894 [
self behaviour_fly_range_from_destination: delta_t];
2897 case BEHAVIOUR_FACE_DESTINATION :
2898 [
self behaviour_face_destination: delta_t];
2901 case BEHAVIOUR_LAND_ON_PLANET :
2902 [
self behaviour_land_on_planet: delta_t];
2905 case BEHAVIOUR_FORMATION_FORM_UP :
2906 [
self behaviour_formation_form_up: delta_t];
2909 case BEHAVIOUR_FLY_TO_DESTINATION :
2910 [
self behaviour_fly_to_destination: delta_t];
2913 case BEHAVIOUR_FLY_FROM_DESTINATION :
2914 case BEHAVIOUR_FORMATION_BREAK :
2915 [
self behaviour_fly_from_destination: delta_t];
2918 case BEHAVIOUR_AVOID_COLLISION :
2919 [
self behaviour_avoid_collision: delta_t];
2922 case BEHAVIOUR_TRACK_AS_TURRET :
2924 [
self behaviour_track_as_turret: delta_t];
2927 case BEHAVIOUR_FLY_THRU_NAVPOINTS :
2928 [
self behaviour_fly_thru_navpoints: delta_t];
2931 case BEHAVIOUR_SCRIPTED_AI:
2932 case BEHAVIOUR_SCRIPTED_ATTACK_AI:
2933 [
self behaviour_scripted_ai: delta_t];
2936 case BEHAVIOUR_ENERGY_BOMB_COUNTDOWN:
2945 [
self applyAttitudeChanges:delta_t];
2946 [
self applyThrust:delta_t];
2952- (void)noteFrustration:(NSString *)context
2954 [shipAI reactToMessage:@"FRUSTRATED" context:context];
2955 [
self doScriptEvent:OOJSID("shipAIFrustrated") withArgument:context];
2959- (void)respondToAttackFrom:(
Entity *)from becauseOf:(
Entity *)other
2963 if ([other isKindOfClass:[
ShipEntity class]])
2968 if (![
self hasNewAI])
2973 if ([
self isPolice] && [hunter isPolice])
2981 if (group !=
nil && group == [hunter group])
2985 if (
randf() < (0.8 - (bounty/100)))
2992 if (hunter == groupLeader)
2995 [group removeShip:self];
3000 [group removeShip:hunter];
3014 [
self doScriptEvent:OOJSID("shipBeingAttacked") withArgument:source andReactToAIMessage:@"ATTACKED"];
3015 if ([source isShip]) [(
ShipEntity *)
source doScriptEvent:OOJSID("shipAttackedOther") withArgument:self];
3021- (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading
3023 if ([
self hasOneEquipmentItem:itemKey includeMissiles:includeWeapons whileLoading:loading]) return YES;
3027 NSString *damaged = [itemKey stringByAppendingString:@"_DAMAGED"];
3028 if ([_equipment containsObject:damaged]) return YES;
3037 if ([
self hasPrimaryWeapon:weaponType]) return YES;
3045- (BOOL) hasOneEquipmentItem:(NSString *)itemKey includeMissiles:(BOOL)includeMissiles whileLoading:(BOOL)loading
3047 if ([_equipment containsObject:itemKey]) return YES;
3051 NSString *damaged = [itemKey stringByAppendingString:@"_DAMAGED"];
3052 if ([_equipment containsObject:damaged]) return YES;
3055 if (includeMissiles && missiles > 0)
3058 if ([itemKey isEqualToString:
@"thargon"]) itemKey =
@"EQ_THARGON";
3059 for (i = 0; i < missiles; i++)
3061 if (missile_list[i] !=
nil && [[missile_list[i] identifier] isEqualTo:itemKey]) return YES;
3071 NSEnumerator *subEntEnum =
nil;
3074 if ([[forward_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
3075 [[aft_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
3076 [[port_weapon_type identifier] isEqualToString:[weaponType identifier]] ||
3077 [[starboard_weapon_type identifier] isEqualToString:[weaponType identifier]])
3082 for (subEntEnum = [
self shipSubEntityEnumerator]; (subEntity = [subEntEnum nextObject]); )
3084 if ([subEntity hasPrimaryWeapon:weaponType]) return YES;
3091- (NSUInteger) countEquipmentItem:(NSString *)eqkey
3094 NSUInteger
count = 0;
3095 foreach (eq, _equipment)
3097 if ([eqkey isEqualToString:eq])
3106- (BOOL) hasEquipmentItem:(
id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading
3109 if ([equipmentKeys isKindOfClass:[NSString class]])
3111 return [
self hasOneEquipmentItem:equipmentKeys includeWeapons:includeWeapons whileLoading:loading];
3115 NSParameterAssert([equipmentKeys isKindOfClass:[NSArray class]] || [equipmentKeys isKindOfClass:[NSSet class]]);
3118 foreach (key, equipmentKeys)
3120 if ([
self hasOneEquipmentItem:key includeWeapons:includeWeapons whileLoading:loading]) return YES;
3128- (BOOL) hasEquipmentItem:(
id)equipmentKeys
3130 return [
self hasEquipmentItem:equipmentKeys includeWeapons:NO whileLoading:NO];
3136- (BOOL) hasEquipmentItemProviding:(NSString *)equipmentType
3138 NSString *key =
nil;
3139 foreach (key, _equipment) {
3140 if ([key isEqualToString:equipmentType])
3148 if (et !=
nil && [et provides:equipmentType])
3158- (NSString *) equipmentItemProviding:(NSString *)equipmentType
3160 NSString *key =
nil;
3161 foreach (key, _equipment) {
3162 if ([key isEqualToString:equipmentType])
3165 return [[key copy] autorelease];
3170 if (et !=
nil && [et provides:equipmentType])
3172 return [[key copy] autorelease];
3180- (BOOL) hasAllEquipment:(
id)equipmentKeys includeWeapons:(BOOL)includeWeapons whileLoading:(BOOL)loading
3182 NSEnumerator *keyEnum =
nil;
3185 if (_equipment ==
nil)
return NO;
3188 if ([equipmentKeys isKindOfClass:[NSString class]]) equipmentKeys = [NSArray arrayWithObject:equipmentKeys];
3189 else if (![equipmentKeys isKindOfClass:[NSArray class]] && ![equipmentKeys isKindOfClass:[NSSet class]]) return NO;
3191 for (keyEnum = [equipmentKeys objectEnumerator]; (key = [keyEnum nextObject]); )
3193 if (![
self hasOneEquipmentItem:key includeWeapons:includeWeapons whileLoading:loading]) return NO;
3200- (BOOL) hasAllEquipment:(
id)equipmentKeys
3202 return [
self hasAllEquipment:equipmentKeys includeWeapons:NO whileLoading:NO];
3206- (BOOL) hasHyperspaceMotor
3208 return hyperspaceMotorSpinTime >= 0;
3212- (float) hyperspaceSpinTime
3214 return hyperspaceMotorSpinTime;
3218- (void) setHyperspaceSpinTime:(
float)new
3220 hyperspaceMotorSpinTime =
new;
3224- (BOOL) canAddEquipment:(NSString *)equipmentKey inContext:(NSString *)context
3226 if ([equipmentKey hasSuffix:
@"_DAMAGED"])
3228 equipmentKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]];
3231 NSString * lcEquipmentKey = [equipmentKey lowercaseString];
3232 if ([equipmentKey hasSuffix:
@"MISSILE"]||[equipmentKey hasSuffix:
@"MINE"]||([
self isThargoid] && ([lcEquipmentKey hasPrefix:
@"thargon"] || [lcEquipmentKey hasSuffix:
@"thargon"])))
3234 if (missiles >= max_missiles)
return NO;
3239 if (![eqType canCarryMultiple] && [
self hasEquipmentItem:equipmentKey]) return NO;
3240 if (![
self equipmentValidToAdd:equipmentKey inContext:context]) return NO;
3248 return weapon_facings;
3256 if (facing & weapon_facings)
3261 weaponType = forward_weapon_type;
3265 NSEnumerator *subEntEnum = [
self shipSubEntityEnumerator];
3267 while (
isWeaponNone(weaponType) && (subEntity = [subEntEnum nextObject]))
3275 weaponType = aft_weapon_type;
3279 weaponType = port_weapon_type;
3283 weaponType = starboard_weapon_type;
3297 return [
self weaponTypeIDForFacing:facing strict:strict];
3301- (NSArray *) missilesList
3304 return missile_list[0] !=
nil ? [NSArray arrayWithObjects:missile_list count:missiles] :
3309- (NSArray *) passengerListForScripting
3311 return [NSArray array];
3315- (NSArray *) parcelListForScripting
3317 return [NSArray array];
3321- (NSArray *) contractListForScripting
3323 return [NSArray array];
3327- (
OOEquipmentType *) generateMissileEquipmentTypeFrom:(NSString *)role
3342 NSArray *itemInfo = [NSArray arrayWithObjects:@"100", @"100000", @"Missile", role, @"Unidentified missile type.",
3343 [NSDictionary dictionaryWithObjectsAndKeys: @"true", @"is_external_store", nil], nil];
3350- (NSArray *) equipmentListForScripting
3353 NSMutableArray *quip = [NSMutableArray arrayWithCapacity:[eqTypes count]];
3357 foreach (eqType, eqTypes)
3360 if ([eqType canCarryMultiple])
3362 NSString *damagedIdentifier = [[eqType
identifier] stringByAppendingString:@"_DAMAGED"];
3363 NSUInteger i,
count = 0;
3364 count += [
self countEquipmentItem:[eqType identifier]];
3365 count += [
self countEquipmentItem:damagedIdentifier];
3366 for (i=0;i<
count;i++)
3368 [quip addObject:eqType];
3373 isDamaged = [
self hasEquipmentItem:[[eqType
identifier] stringByAppendingString:@"_DAMAGED"]];
3374 if ([
self hasEquipmentItem:[eqType identifier]] || isDamaged)
3376 [quip addObject:eqType];
3382 if ([
self passengerCapacity] > 0)
3386 [quip addObject:eqType];
3389 return [[quip copy] autorelease];
3393- (BOOL) equipmentValidToAdd:(NSString *)equipmentKey inContext:(NSString *)context
3395 return [
self equipmentValidToAdd:equipmentKey whileLoading:NO inContext:context];
3399- (BOOL) equipmentValidToAdd:(NSString *)equipmentKey whileLoading:(BOOL)loading inContext:(NSString *)context
3402 BOOL validationForDamagedEquipment = NO;
3404 if ([equipmentKey hasSuffix:
@"_DAMAGED"])
3406 equipmentKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]];
3410 if (eqType ==
nil)
return NO;
3417 if ([
self hasEquipmentItem:[eqType damagedIdentifier]])
3419 validationForDamagedEquipment = YES;
3424 if ([eqType requiresEmptyPylon] && [
self missileCount] >= [
self missileCapacity] && !loading)
return NO;
3425 if ([eqType requiresMountedPylon] && [
self missileCount] == 0 && !loading)
return NO;
3426 if ([
self availableCargoSpace] < [eqType requiredCargoSpace] && !validationForDamagedEquipment && !loading)
return NO;
3427 if ([eqType requiresEquipment] !=
nil && ![
self hasAllEquipment:[eqType requiresEquipment] includeWeapons:YES whileLoading:loading]) return NO;
3428 if ([eqType requiresAnyEquipment] !=
nil && ![
self hasEquipmentItem:[eqType requiresAnyEquipment] includeWeapons:YES whileLoading:loading]) return NO;
3429 if ([eqType incompatibleEquipment] !=
nil && [
self hasEquipmentItem:[eqType incompatibleEquipment] includeWeapons:YES whileLoading:loading]) return NO;
3430 if ([eqType requiresCleanLegalRecord] && [
self legalStatus] != 0 && !loading)
return NO;
3431 if ([eqType requiresNonCleanLegalRecord] && [
self legalStatus] == 0 && !loading)
return NO;
3432 if ([eqType requiresFreePassengerBerth] && [
self passengerCount] >= [
self passengerCapacity])
return NO;
3433 if ([eqType requiresFullFuel] && [
self fuel] < [
self fuelCapacity] && !loading)
return NO;
3434 if ([eqType requiresNonFullFuel] && [
self fuel] >= [
self fuelCapacity] && !loading)
return NO;
3439 if (condition_script !=
nil)
3441 OOJSScript *condScript = [UNIVERSE getConditionScript:condition_script];
3442 if (condScript !=
nil)
3446 JSBool allow_addition =
false;
3450 OK = [condScript
callMethod:OOJSID("allowAwardEquipment")
3455 if (OK) OK = JS_ValueToBoolean(JScontext, result, &allow_addition);
3459 if (OK && !allow_addition)
3470 if ([
self isPlayer])
3472 if (![eqType isAvailableToPlayer])
return NO;
3473 if (![eqType isAvailableToAll])
3478 NSMutableSet *options = [NSMutableSet setWithArray:[shipyardInfo oo_arrayForKey:KEY_OPTIONAL_EQUIPMENT]];
3479 [options addObjectsFromArray:[[shipyardInfo oo_dictionaryForKey:KEY_STANDARD_EQUIPMENT] oo_arrayForKey:KEY_EQUIPMENT_EXTRAS]];
3480 if (![options containsObject:equipmentKey]) return NO;
3485 if (![eqType isAvailableToNPCs])
return NO;
3492- (BOOL) setWeaponMount:(
OOWeaponFacing)facing toWeapon:(NSString *)eqKey
3495 if (weapon_facings & facing)
3501 forward_weapon_type = chosen_weapon;
3505 aft_weapon_type = chosen_weapon;
3509 port_weapon_type = chosen_weapon;
3513 starboard_weapon_type = chosen_weapon;
3529- (BOOL) addEquipmentItem:(NSString *)equipmentKey inContext:(NSString *)context
3531 return [
self addEquipmentItem:equipmentKey withValidation:YES inContext:context];
3535- (BOOL) addEquipmentItem:(NSString *)equipmentKey withValidation:(BOOL)validateAddition inContext:(NSString *)context
3538 NSString *lcEquipmentKey = [equipmentKey lowercaseString];
3539 NSString *damagedKey;
3540 BOOL isEqThargon = [lcEquipmentKey hasSuffix:@"thargon"] || [lcEquipmentKey hasPrefix:@"thargon"];
3541 BOOL isRepairedEquipment = NO;
3543 if([lcEquipmentKey isEqualToString:
@"thargon"]) equipmentKey =
@"EQ_THARGON";
3546 if (validateAddition == YES && ![
self canAddEquipment:equipmentKey inContext:context]) return NO;
3548 if ([equipmentKey hasSuffix:
@"_DAMAGED"])
3556 if (![eqType canCarryMultiple])
3558 damagedKey = [equipmentKey stringByAppendingString:@"_DAMAGED"];
3559 if ([_equipment containsObject:damagedKey])
3561 [_equipment removeObject:damagedKey];
3562 isRepairedEquipment = YES;
3568 if (eqType ==
nil)
return NO;
3571 if ([eqType isMissileOrMine] || ([
self isThargoid] && isEqThargon))
3573 if (missiles >= max_missiles)
return NO;
3575 missile_list[missiles] = eqType;
3581 if(isEqThargon)
return NO;
3584 if([equipmentKey hasPrefix:
@"EQ_WEAPON"] && ![equipmentKey hasSuffix:
@"_DAMAGED"])
3590 if (_equipment ==
nil) _equipment = [[NSMutableArray alloc] init];
3592 if (![equipmentKey isEqualToString:
@"EQ_PASSENGER_BERTH"] && !isRepairedEquipment)
3595 equipment_weight += [eqType requiredCargoSpace];
3596 if (equipment_weight > max_cargo)
3599 equipment_weight -= [eqType requiredCargoSpace];
3607 if ([equipmentKey isEqual:
@"EQ_CARGO_BAY"])
3609 max_cargo += extra_cargo;
3611 else if([equipmentKey isEqualToString:
@"EQ_SHIELD_BOOSTER"])
3613 maxEnergy += 256.0f;
3615 if([equipmentKey isEqualToString:
@"EQ_SHIELD_ENHANCER"])
3617 maxEnergy += 256.0f;
3618 energy_recharge_rate *= 1.5;
3622 [_equipment addObject:equipmentKey];
3623 [
self doScriptEvent:OOJSID("equipmentAdded") withArgument:equipmentKey];
3628- (NSEnumerator *) equipmentEnumerator
3630 return [_equipment objectEnumerator];
3634- (NSUInteger) equipmentCount
3636 return [_equipment count];
3640- (void) removeEquipmentItem:(NSString *)equipmentKey
3642 NSString *equipmentTypeCheckKey = equipmentKey;
3643 NSString *lcEquipmentKey = [equipmentKey lowercaseString];
3644 NSUInteger equipmentIndex = NSNotFound;
3646 if ([equipmentKey hasSuffix:
@"_DAMAGED"])
3648 equipmentTypeCheckKey = [equipmentKey substringToIndex:[equipmentKey length] - [@"_DAMAGED" length]];
3651 if (eqType ==
nil)
return;
3653 if ([eqType isMissileOrMine] || ([
self isThargoid] && ([lcEquipmentKey hasSuffix:
@"thargon"] || [lcEquipmentKey hasPrefix:
@"thargon"])))
3655 [
self removeExternalStore:eqType];
3659 if ([_equipment containsObject:equipmentKey])
3661 if (![equipmentKey isEqualToString:
@"EQ_PASSENGER_BERTH"])
3663 equipment_weight -= [eqType requiredCargoSpace];
3666 if ([equipmentKey isEqualToString:
@"EQ_CLOAKING_DEVICE"])
3668 if ([
self isCloaked]) [
self setCloaked:NO];
3673 if([equipmentKey isEqualToString:
@"EQ_SHIELD_BOOSTER"])
3675 maxEnergy -= 256.0f;
3676 if (maxEnergy < energy) energy = maxEnergy;
3678 else if([equipmentKey isEqualToString:
@"EQ_SHIELD_ENHANCER"])
3680 maxEnergy -= 256.0f;
3681 energy_recharge_rate /= 1.5;
3682 if (maxEnergy < energy) energy = maxEnergy;
3684 else if ([equipmentKey isEqual:
@"EQ_CARGO_BAY"])
3686 max_cargo -= extra_cargo;
3691 if (![equipmentKey hasSuffix:
@"_DAMAGED"] && ![eqType canCarryMultiple])
3693 NSString *damagedKey = [equipmentKey stringByAppendingString:@"_DAMAGED"];
3694 if ([_equipment containsObject:damagedKey])
3696 equipmentIndex = [_equipment indexOfObject:damagedKey];
3697 if (equipmentIndex != NSNotFound)
3700 [_equipment removeObjectAtIndex:equipmentIndex];
3702 equipment_weight -= [eqType requiredCargoSpace];
3705 equipmentIndex = [_equipment indexOfObject:equipmentKey];
3706 if (equipmentIndex != NSNotFound)
3708 [_equipment removeObjectAtIndex:equipmentIndex];
3711 [
self doScriptEvent:OOJSID("equipmentRemoved") withArgument:equipmentKey];
3714 if ([
self isPlayer] && [
self status] == STATUS_AUTOPILOT_ENGAGED && ![
self hasDockingComputer])
3720 if ([_equipment
count] == 0) [
self removeAllEquipment];
3730 for (i = 0; i < missiles; i++)
3732 if ([[missile_list[i] identifier] isEqualTo:identifier])
3735 while ( ++i < missiles ) missile_list[i - 1] = missile_list[i];
3747 NSString *eqRole =
nil;
3748 NSString *shipKey =
nil;
3751 BOOL isRandomMissile = [role isEqualToString:@"missile"];
3753 if (isRandomMissile)
3757 shipKey = [UNIVERSE randomShipKeyForRoleRespectingConditions:role];
3760 OOLogWARN(
@"ship.setUp.missiles",
@"%@ \"%@\
" used in ship \"%@\" needs a valid %@.plist entry.%@",
@"random missile", shipKey, [
self name],
@"shipdata",
@"Trying another missile.");
3766 shipKey = [UNIVERSE randomShipKeyForRoleRespectingConditions:role];
3769 OOLogWARN(
@"ship.setUp.missiles",
@"%@ \"%@\
" used in ship \"%@\" needs a valid %@.plist entry.%@",
@"missile_role", role, [
self name],
@"shipdata",
@" Using defaults instead.");
3778 missile = [UNIVERSE newShipWithName:shipKey];
3781 if (isRandomMissile)
3782 OOLogWARN(
@"ship.setUp.missiles",
@"%@ \"%@\
" used in ship \"%@\" needs a valid %@.plist entry.%@",
@"random missile", shipKey, [
self name],
@"shipdata",
@"Trying another missile.");
3784 OOLogWARN(
@"ship.setUp.missiles",
@"%@ \"%@\
" used in ship \"%@\" needs a valid %@.plist entry.%@",
@"missile_role", role, [
self name],
@"shipdata",
@" Using defaults instead.");
3787 if (isRandomMissile)
return [
self verifiedMissileTypeFromRole:role];
3795 foreach (value, [[missile roleSet] roles])
3797 role = (NSString *)value;
3800 if ([missileType isMissileOrMine])
break;
3803 if (![missileType isMissileOrMine])
3813 OOLogWARN(
@"ship.setUp.missiles",
@"%@ \"%@\
" used in ship \"%@\" needs a valid %@.plist entry.%@", (isRandomMissile ?
@"random missile" :
@"missile_role"), role, [
self name],
@"equipment",
@" Enabling compatibility mode.");
3814 missileType = [
self generateMissileEquipmentTypeFrom:role];
3822 if ([eqRole isEqualToString:
@""])
3825 if (isRandomMissile)
return [
self verifiedMissileTypeFromRole:role];
3838 NSString *role =
nil;
3839 double chance =
randf();
3840 BOOL thargoidMissile = NO;
3842 if ([
self isThargoid])
3844 if (_missileRole !=
nil) missileType = [
self verifiedMissileTypeFromRole:_missileRole];
3845 if (missileType ==
nil) {
3846 _missileRole =
@"EQ_THARGON";
3847 missileType = [
self verifiedMissileTypeFromRole:_missileRole];
3854 float randomSelectionChance = chance;
3855 if(![
self hasAutoWeapons]) randomSelectionChance = 0.0f;
3856 if (randomSelectionChance < 0.9f && _missileRole !=
nil)
3858 missileType = [
self verifiedMissileTypeFromRole:_missileRole];
3861 if (missileType ==
nil)
3863 if (chance < 0.9f && _missileRole !=
nil)
3869 if (chance > 0.8f) role =
@"missile";
3871 else role =
@"EQ_MISSILE";
3873 missileType = [
self verifiedMissileTypeFromRole:role];
3877 if (missileType ==
nil)
OOLogERR(
@"ship.setUp.missiles",
@"could not resolve missile / mine type for ship \"%@\
". Original missile role:\"%@\".", [
self name],_missileRole);
3879 role = [[missileType
identifier] lowercaseString];
3880 thargoidMissile = [
self isThargoid] && ([role hasSuffix:@"thargon"] || [role hasPrefix:@"thargon"]);
3882 if (thargoidMissile || (!thargoidMissile && [missileType isMissileOrMine]))
3888 OOLogWARN(
@"ship.setUp.missiles",
@"missile_role \"%@\
" is not a valid missile / mine type for ship \"%@\".%@", [missileType identifier] , [
self name],
@" No missile selected.");
3894- (void) removeAllEquipment
3896 [_equipment release];
3908- (NSUInteger) parcelCount
3914- (NSUInteger) passengerCount
3920- (NSUInteger) passengerCapacity
3926- (NSUInteger) missileCount
3932- (NSUInteger) missileCapacity
3934 return max_missiles;
3938- (NSUInteger) extraCargo
3947 return [
self hasEquipmentItemProviding:@"EQ_FUEL_SCOOPS"] || [
self hasEquipmentItemProviding:@"EQ_CARGO_SCOOPS"];
3951- (BOOL) hasFuelScoop
3953 return [
self hasEquipmentItemProviding:@"EQ_FUEL_SCOOPS"];
3958- (BOOL) hasCargoScoop
3960 return [
self hasEquipmentItemProviding:@"EQ_CARGO_SCOOPS"];
3966 return [
self hasEquipmentItemProviding:@"EQ_ECM"];
3970- (BOOL) hasCloakingDevice
3973 return [
self hasEquipmentItem:@"EQ_CLOAKING_DEVICE"];
3977- (BOOL) hasMilitaryScannerFilter
3980 return [
self hasEquipmentItemProviding:@"EQ_MILITARY_SCANNER_FILTER"];
3987- (BOOL) hasMilitaryJammer
3990 return [
self hasEquipmentItemProviding:@"EQ_MILITARY_JAMMER"];
3997- (BOOL) hasExpandedCargoBay
4000 return [
self hasEquipmentItem:@"EQ_CARGO_BAY"];
4004- (BOOL) hasShieldBooster
4007 return [
self hasEquipmentItem:@"EQ_SHIELD_BOOSTER"];
4011- (BOOL) hasMilitaryShieldEnhancer
4014 return [
self hasEquipmentItem:@"EQ_NAVAL_SHIELD_BOOSTER"];
4018- (BOOL) hasHeatShield
4020 return [
self hasEquipmentItemProviding:@"EQ_HEAT_SHIELD"];
4024- (BOOL) hasFuelInjection
4026 return [
self hasEquipmentItemProviding:@"EQ_FUEL_INJECTION"];
4030- (BOOL) hasCascadeMine
4035 return [
self hasEquipmentItem:@"EQ_QC_MINE" includeWeapons:YES whileLoading:NO];
4039- (BOOL) hasEscapePod
4041 return [
self hasEquipmentItemProviding:@"EQ_ESCAPE_POD"];
4045- (BOOL) hasDockingComputer
4047 return [
self hasEquipmentItemProviding:@"EQ_DOCK_COMP"];
4051- (BOOL) hasGalacticHyperdrive
4053 return [
self hasEquipmentItemProviding:@"EQ_GAL_DRIVE"];
4057- (float) shieldBoostFactor
4059 float boostFactor = 1.0f;
4060 if ([
self hasShieldBooster]) boostFactor += 1.0f;
4061 if ([
self hasMilitaryShieldEnhancer]) boostFactor += 1.0f;
4069- (float) maxForwardShieldLevel
4075- (float) maxAftShieldLevel
4081- (float) shieldRechargeRate
4083 return [
self hasMilitaryShieldEnhancer] ? 3.0f : 2.0f;
4087- (double) maxHyperspaceDistance
4092- (float) afterburnerFactor
4094 return afterburner_speed_factor;
4098- (float) afterburnerRate
4100 return afterburner_rate;
4104- (void) setAfterburnerFactor:(GLfloat)new
4106 afterburner_speed_factor =
new;
4110- (void) setAfterburnerRate:(GLfloat)new
4112 afterburner_rate =
new;
4122- (void) setMaxThrust:(GLfloat)new
4138- (void) behaviour_stop_still:(
double) delta_t
4143 [
self applySticks:delta_t];
4150- (void) behaviour_idle:(
double) delta_t
4153 if ((!isStation)&&(scanClass != CLASS_BUOY))
4159 stick_roll = flightRoll;
4161 if (scanClass != CLASS_BUOY)
4167 stick_pitch = flightPitch;
4169 [
self applySticks:delta_t];
4175- (void) behaviour_tumble:(
double) delta_t
4177 [
self applySticks:delta_t];
4183- (void) behaviour_tractored:(
double) delta_t
4185 desired_range = collision_radius * 2.0;
4187 if ((hauler)&&([hauler
isShip]))
4190 double distance = [
self rangeToDestination];
4191 if (distance < desired_range)
4193 [
self performTumble];
4194 [
self setStatus:STATUS_IN_FLIGHT];
4200 Vector dv = vector_between([
self velocity], [hauler velocity]);
4201 GLfloat moment = delta_t * 0.25 * tf;
4202 velocity.x += moment * dv.
x;
4203 velocity.y += moment * dv.
y;
4204 velocity.z += moment * dv.z;
4207 HPVector dp = HPvector_between(position, _destination);
4208 moment = delta_t * 0.5 * tf;
4209 velocity.x += moment * dp.
x;
4210 velocity.y += moment * dp.
y;
4211 velocity.z += moment * dp.z;
4213 GLfloat d2 = HPmagnitude2(dp);
4214 moment = (d2 > 0.0)? delta_t * 5.0 * tf / d2 : 0.0;
4217 velocity.x += moment * dp.
x;
4218 velocity.y += moment * dp.
y;
4219 velocity.z += moment * dp.z;
4222 if ([
self status] == STATUS_BEING_SCOOPED)
4224 BOOL lost_contact = (distance > hauler->
collision_radius + collision_radius + 250.0f);
4225 if ([hauler isPlayer])
4243 [
self setStatus:STATUS_IN_FLIGHT];
4244 behaviour = BEHAVIOUR_IDLE;
4245 [
self setThrust:[
self maxThrust]];
4247 [
self setOwner:self];
4248 [shipAI exitStateMachineWithMessage:nil];
4251 else if ([hauler isPlayer])
4261 desired_speed = 0.0;
4268- (void) behaviour_track_target:(
double) delta_t
4270 if ([
self primaryTarget] ==
nil)
4272 [
self noteLostTargetAndGoIdle];
4275 [
self trackPrimaryTarget:delta_t:NO];
4276 if ([
self hasProximityAlertIgnoringTarget:YES])
4278 [
self avoidCollision];
4284- (void) behaviour_intercept_target:(
double) delta_t
4286 double range = [
self rangeToPrimaryTarget];
4287 if (behaviour == BEHAVIOUR_INTERCEPT_TARGET)
4289 desired_speed = maxFlightSpeed;
4290 if (range < desired_range)
4292 [shipAI reactToMessage:@"DESIRED_RANGE_ACHIEVED" context:@"BEHAVIOUR_INTERCEPT_TARGET"];
4293 [
self doScriptEvent:OOJSID("shipAchievedDesiredRange")];
4296 desired_speed = maxFlightSpeed * [
self trackPrimaryTarget:delta_t:NO];
4304 if (!target || [target scanClass] != CLASS_CARGO || [target cargoType] ==
CARGO_NOT_CARGO)
4306 [
self noteLostTargetAndGoIdle];
4309 double target_speed = [target
speed];
4310 double eta = range / (flightSpeed - target_speed);
4311 double last_success_factor = success_factor;
4312 double last_distance = last_success_factor;
4313 double distance = [
self rangeToDestination];
4314 success_factor = distance;
4317 double minTurnSpeedFactor = 0.005 * max_flight_pitch * max_flight_roll;
4319 if ((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor))
4320 desired_speed = flightSpeed * 0.75;
4322 desired_speed = maxFlightSpeed;
4324 if (desired_speed < target_speed)
4326 desired_speed += target_speed;
4327 if (target_speed > maxFlightSpeed)
4329 [
self noteLostTargetAndGoIdle];
4333 if (desired_speed > maxFlightSpeed)
4335 desired_speed = maxFlightSpeed;
4340 [
self trackDestination: delta_t : NO];
4343 if (distance < last_distance)
4345 frustration -= delta_t;
4346 if (frustration < 0.0)
4351 frustration += delta_t * 0.9;
4352 if (frustration > 10.0)
4354 [
self noteFrustration:@"BEHAVIOUR_INTERCEPT_TARGET"];
4359 if ([
self hasProximityAlertIgnoringTarget:YES])
4361 [
self avoidCollision];
4368- (void) behaviour_attack_break_off_target:(
double) delta_t
4370 if (![
self canStillTrackPrimaryTarget])
4372 [
self noteLostTargetAndGoIdle];
4375 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
4376 float max_available_speed = maxFlightSpeed;
4377 double range = [
self rangeToPrimaryTarget];
4378 if (canBurn) max_available_speed *= [
self afterburnerFactor];
4380 desired_speed = max_available_speed;
4382 Entity* target = [
self primaryTarget];
4384 if (desired_speed > maxFlightSpeed)
4386 double target_speed = [target
speed];
4387 if (desired_speed > target_speed * 3.0)
4389 desired_speed = maxFlightSpeed;
4393 if (cloakAutomatic) [
self activateCloakingDevice];
4394 if ([
self hasProximityAlertIgnoringTarget:NO])
4396 [
self avoidCollision];
4400 frustration += delta_t;
4403 desired_speed = maxFlightSpeed / 2.0;
4405 double aspect = [
self approachAspectToPrimaryTarget];
4406 if (range > 3000.0 || ([target isShip] && [(
ShipEntity*)target primaryTarget] !=
self) || frustration - floor(frustration) > fmin(1.6/max_flight_roll,aspect))
4408 [
self trackPrimaryTarget:delta_t:YES];
4413 [
self evasiveAction:delta_t];
4418 behaviour = BEHAVIOUR_ATTACK_TARGET;
4422 behaviour = BEHAVIOUR_ATTACK_SLOW_DOGFIGHT;
4429 behaviour = BEHAVIOUR_ATTACK_SLOW_DOGFIGHT;
4433 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
4441- (void) behaviour_attack_slow_dogfight:(
double) delta_t
4443 if (![
self canStillTrackPrimaryTarget])
4445 [
self noteLostTargetAndGoIdle];
4448 if ([
self hasProximityAlertIgnoringTarget:YES])
4450 [
self avoidCollision];
4453 double range = [
self rangeToPrimaryTarget];
4455 double aspect = [
self approachAspectToPrimaryTarget];
4456 if (range < 2.5*(collision_radius+target->
collision_radius) && [
self proximityAlert] == target && aspect > 0) {
4457 desired_speed = maxFlightSpeed;
4458 [
self avoidCollision];
4463 behaviour = BEHAVIOUR_ATTACK_TARGET;
4465 else if (aspect < -0.5)
4468 desired_speed = fmin(maxFlightSpeed * 0.5,[target speed]*0.5);
4470 else if (aspect < 0.3)
4473 desired_speed = maxFlightSpeed * 0.1;
4478 desired_speed = maxFlightSpeed * fmin(aspect*2.5,1.0);
4482 behaviour = BEHAVIOUR_ATTACK_BREAK_OFF_TARGET;
4486 frustration += delta_t;
4490 frustration -= delta_t;
4492 if (frustration > 10.0)
4494 desired_speed /= 2.0;
4496 else if (frustration < 0.0)
4499 [
self trackPrimaryTarget:delta_t:NO];
4501 if (missiles) [
self considerFiringMissile:delta_t];
4503 if (cloakAutomatic) [
self activateCloakingDevice];
4508- (void) behaviour_evasive_action:(
double) delta_t
4510 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
4511 float max_available_speed = maxFlightSpeed;
4513 if (canBurn) max_available_speed *= [
self afterburnerFactor];
4514 desired_speed = max_available_speed;
4515 if (desired_speed > maxFlightSpeed)
4518 double target_speed = [target
speed];
4519 if (desired_speed > target_speed)
4521 desired_speed = maxFlightSpeed;
4525 if (cloakAutomatic) [
self activateCloakingDevice];
4526 if ([
self proximityAlert] !=
nil)
4528 [
self avoidCollision];
4532 [
self evasiveAction:delta_t];
4534 frustration += delta_t;
4536 if (frustration > 0.5)
4538 if (behaviour == BEHAVIOUR_FLEE_EVASIVE_ACTION)
4540 [
self setEvasiveJink:400.0];
4541 behaviour = BEHAVIOUR_FLEE_TARGET;
4545 behaviour = BEHAVIOUR_ATTACK_TARGET;
4552 [
self fireMainWeapon:[
self rangeToPrimaryTarget]];
4557- (void) behaviour_attack_target:(
double) delta_t
4559 double range = [
self rangeToPrimaryTarget];
4561 if (cloakAutomatic) [
self activateCloakingDevice];
4570 OOWeaponType forward_weapon_real_type = forward_weapon_type;
4571 GLfloat forward_weapon_real_temp = forward_weapon_temp;
4576 BOOL hasTurrets = NO;
4577 NSEnumerator *subEnum = [
self shipSubEntityEnumerator];
4579 while (
isWeaponNone(forward_weapon_real_type) && (se = [subEnum nextObject]))
4583 if (se->
behaviour == BEHAVIOUR_TRACK_AS_TURRET)
4588 if (
isWeaponNone(forward_weapon_real_type) && hasTurrets)
4595 if ([forward_weapon_real_type isTurretLaser])
4597 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
4608 BOOL weapons_heating = NO;
4609 if (!forward_weapon_ready && !aft_weapon_ready && !port_weapon_ready && !starboard_weapon_ready)
4611 weapons_heating = YES;
4618 Entity* target = [
self primaryTarget];
4619 double aspect = [
self approachAspectToPrimaryTarget];
4621 if (!forward_weapon_ready && !aft_weapon_ready && !port_weapon_ready && !starboard_weapon_ready)
4630 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
4632 else if (aspect > 0)
4638 behaviour = BEHAVIOUR_EVASIVE_ACTION;
4642 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
4648 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
4656 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
4658 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
4666 float relativeSpeed = magnitude(vector_subtract([
self velocity], [target velocity]));
4667 [
self setEvasiveJink:(range + COMBAT_JINK_OFFSET - relativeSpeed / max_flight_pitch)];
4668 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
4676 if (nearby && aft_weapon_ready)
4679 behaviour = BEHAVIOUR_RUNNING_DEFENSE;
4681 else if (nearby && (port_weapon_ready || starboard_weapon_ready))
4684 behaviour = BEHAVIOUR_ATTACK_BROADSIDE;
4697 float relativeSpeed = magnitude(vector_subtract([
self velocity], [target velocity]));
4698 [
self setEvasiveJink:(range + COMBAT_JINK_OFFSET - relativeSpeed / max_flight_pitch)];
4703 behaviour = BEHAVIOUR_ATTACK_BREAK_OFF_TARGET;
4707 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
4710 else if (forward_weapon_ready)
4717 behaviour = BEHAVIOUR_ATTACK_SNIPER;
4722 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX;
4726 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
4730 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
4733 else if (port_weapon_ready || starboard_weapon_ready)
4736 behaviour = BEHAVIOUR_ATTACK_BROADSIDE;
4738 else if (aft_weapon_ready && midrange)
4741 behaviour = BEHAVIOUR_RUNNING_DEFENSE;
4746 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
4756- (void) behaviour_attack_broadside:(
double) delta_t
4758 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
4759 float max_available_speed = maxFlightSpeed;
4760 double range = [
self rangeToPrimaryTarget];
4761 if (canBurn) max_available_speed *= [
self afterburnerFactor];
4763 if (cloakAutomatic) [
self activateCloakingDevice];
4765 if (![
self canStillTrackPrimaryTarget])
4767 [
self noteLostTargetAndGoIdle];
4771 desired_speed = max_available_speed;
4774 behaviour = BEHAVIOUR_ATTACK_TARGET;
4778 if (port_weapon_temp < starboard_weapon_temp)
4782 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT;
4783 [
self setWeaponDataFromType:starboard_weapon_type];
4787 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT;
4788 [
self setWeaponDataFromType:port_weapon_type];
4795 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT;
4796 [
self setWeaponDataFromType:starboard_weapon_type];
4800 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT;
4801 [
self setWeaponDataFromType:port_weapon_type];
4805 if (weapon_damage == 0.0)
4807 behaviour = BEHAVIOUR_ATTACK_TARGET;
4809 else if (range > 0.9 * weaponRange)
4811 behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE;
4822- (void) behaviour_attack_broadside_left:(
double) delta_t
4824 [
self behaviour_attack_broadside_target:delta_t leftside:YES];
4828- (void) behaviour_attack_broadside_right:(
double) delta_t
4830 [
self behaviour_attack_broadside_target:delta_t leftside:NO];
4834- (void) behaviour_attack_broadside_target:(
double) delta_t leftside:(BOOL) leftside
4836 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
4837 float max_available_speed = maxFlightSpeed;
4838 double range = [
self rangeToPrimaryTarget];
4839 if (canBurn) max_available_speed *= [
self afterburnerFactor];
4840 if ([
self primaryTarget] ==
nil)
4842 [
self noteLostTargetAndGoIdle];
4848 behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE;
4855 if (![
self hasProximityAlertIgnoringTarget:YES])
4857 behaviour = BEHAVIOUR_ATTACK_TARGET;
4861 [
self avoidCollision];
4867 if (![
self canStillTrackPrimaryTarget])
4869 [
self noteLostTargetAndGoIdle];
4875 BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed);
4876 double slow_down_range = currentWeaponRange *
COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [
self afterburnerFactor] : 1.0);
4878 if (range <= slow_down_range)
4879 desired_speed = fmin(0.8 * maxFlightSpeed, fmax((2.0-frustration)*maxFlightSpeed, 0.1 * maxFlightSpeed));
4881 desired_speed = max_available_speed;
4883 double last_success_factor = success_factor;
4884 success_factor = [
self trackSideTarget:delta_t:leftside];
4891 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_RIGHT;
4895 behaviour = BEHAVIOUR_ATTACK_TARGET;
4902 behaviour = BEHAVIOUR_ATTACK_BROADSIDE_LEFT;
4906 behaviour = BEHAVIOUR_ATTACK_TARGET;
4912 if ((success_factor > 0.999)||(success_factor > last_success_factor))
4914 frustration -= delta_t;
4915 if (frustration < 0.0)
4920 frustration += delta_t;
4921 if (frustration > 3.0)
4924 [
self noteFrustration:@"BEHAVIOUR_ATTACK_BROADSIDE"];
4925 [
self setEvasiveJink:1000.0];
4926 behaviour = BEHAVIOUR_ATTACK_FLY_FROM_TARGET;
4928 desired_speed = maxFlightSpeed;
4932 if (missiles) [
self considerFiringMissile:delta_t];
4934 if (cloakAutomatic) [
self activateCloakingDevice];
4937 [
self firePortWeapon:range];
4941 [
self fireStarboardWeapon:range];
4948 behaviour = BEHAVIOUR_ATTACK_TARGET;
4953- (void) behaviour_close_to_broadside_range:(
double) delta_t
4955 double range = [
self rangeToPrimaryTarget];
4956 if ([
self proximityAlert] !=
nil)
4958 if ([
self proximityAlert] == [
self primaryTarget])
4960 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
4961 [
self behaviour_attack_fly_from_target: delta_t];
4965 [
self avoidCollision];
4969 if (![
self canStillTrackPrimaryTarget])
4971 [
self noteLostTargetAndGoIdle];
4975 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
4976 [
self behaviour_fly_to_target_six:delta_t];
4979 [
self setWeaponDataFromType:port_weapon_type];
4983 [
self setWeaponDataFromType:starboard_weapon_type];
4987 behaviour = BEHAVIOUR_ATTACK_BROADSIDE;
4991 behaviour = BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE;
4996- (void) behaviour_close_with_target:(
double) delta_t
4998 double range = [
self rangeToPrimaryTarget];
4999 if ([
self proximityAlert] !=
nil)
5001 if ([
self proximityAlert] == [
self primaryTarget])
5003 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
5004 [
self behaviour_attack_fly_from_target: delta_t];
5008 [
self avoidCollision];
5012 if (![
self canStillTrackPrimaryTarget])
5014 [
self noteLostTargetAndGoIdle];
5017 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
5018 double saved_frustration = frustration;
5019 [
self behaviour_fly_to_target_six:delta_t];
5020 frustration = saved_frustration;
5021 frustration += delta_t;
5022 if (range <= COMBAT_IN_RANGE_FACTOR * weaponRange || frustration > 5.0)
5024 behaviour = BEHAVIOUR_ATTACK_TARGET;
5028 behaviour = BEHAVIOUR_CLOSE_WITH_TARGET;
5035- (void) behaviour_attack_sniper:(
double) delta_t
5037 if (![
self canStillTrackPrimaryTarget])
5039 [
self noteLostTargetAndGoIdle];
5042 Entity* rawTarget = [
self primaryTarget];
5043 if (![rawTarget isShip])
5046 [
self noteLostTargetAndGoIdle];
5051 double range = [
self rangeToPrimaryTarget];
5052 float max_available_speed = maxFlightSpeed;
5056 behaviour = BEHAVIOUR_ATTACK_TARGET;
5060 if (range > weaponRange || range > scannerRange * 0.8)
5062 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
5063 if (canBurn && [target weaponRange] > weaponRange && range > weaponRange)
5067 max_available_speed *= [
self afterburnerFactor];
5069 desired_speed = max_available_speed;
5073 desired_speed = max_available_speed / 10.0f;
5076 double last_success_factor = success_factor;
5077 success_factor = [
self trackPrimaryTarget:delta_t:NO];
5079 if ((success_factor > 0.999)||(success_factor > last_success_factor))
5081 frustration -= delta_t;
5082 if (frustration < 0.0)
5087 frustration += delta_t;
5088 if (frustration > 3.0)
5090 [
self noteFrustration:@"BEHAVIOUR_ATTACK_SNIPER"];
5091 [
self setEvasiveJink:1000.0];
5092 behaviour = BEHAVIOUR_ATTACK_TARGET;
5094 desired_speed = maxFlightSpeed;
5100 if (missiles) [
self considerFiringMissile:delta_t];
5102 if (cloakAutomatic) [
self activateCloakingDevice];
5103 [
self fireMainWeapon:range];
5107 behaviour = BEHAVIOUR_ATTACK_TARGET;
5113- (void) behaviour_fly_to_target_six:(
double) delta_t
5115 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
5116 float max_available_speed = maxFlightSpeed;
5117 double range = [
self rangeToPrimaryTarget];
5118 if (canBurn) max_available_speed *= [
self afterburnerFactor];
5121 if ([
self proximityAlert] !=
nil)
5123 if ([
self proximityAlert] == [
self primaryTarget])
5125 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
5126 [
self behaviour_attack_fly_from_target: delta_t];
5130 [
self avoidCollision];
5134 if (![
self canStillTrackPrimaryTarget])
5136 [
self noteLostTargetAndGoIdle];
5141 BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed);
5142 BOOL closeQuickly = (canBurn && range > weaponRange);
5143 double slow_down_range = weaponRange *
COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [
self afterburnerFactor] : 1.0);
5148 double back_off_range = weaponRange *
COMBAT_OUT_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [
self afterburnerFactor] : 1.0);
5149 Entity* rawTarget = [
self primaryTarget];
5150 if (![rawTarget isShip])
5152 [
self noteLostTargetAndGoIdle];
5156 double target_speed = [target
speed];
5157 double last_success_factor = success_factor;
5158 double distance = [
self rangeToDestination];
5159 success_factor = distance;
5161 if (range < slow_down_range && (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX))
5163 if (range < back_off_range)
5165 desired_speed = fmax(0.9 * target_speed, 0.4 * maxFlightSpeed);
5169 desired_speed = fmax(target_speed * 1.2, maxFlightSpeed);
5173 if ((range < 0.5 * distance)&&(behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX))
5174 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
5178 if (range < back_off_range)
5180 desired_speed = fmax(0.9 * target_speed, 0.8 * maxFlightSpeed);
5184 desired_speed = max_available_speed;
5191 if (distance < 750.0 || (target_speed < 0.2 && ![
self isThargoid] && ([
self universalID] & 14) > 4))
5193 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
5195 desired_speed = fmax(target_speed, 0.4 * maxFlightSpeed);
5199 if (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX)
5203 _destination = [target
distance_six:0.5 * weaponRange];
5206 if (behaviour == BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE)
5208 if ([forward_weapon_type isTurretLaser])
5213 GLfloat spacing = 2000.0;
5216 offset = accuracy * 750.0;
5217 spacing = 2000.0 + (accuracy * 500.0);
5219 if (entity_personality & 1)
5233 double confidenceFactor = [
self trackDestination:delta_t :NO];
5235 if(success_factor > last_success_factor || confidenceFactor < 0.85) frustration += delta_t;
5236 else if(frustration > 0.0) frustration -= delta_t * 0.75;
5238 double aspect = [
self approachAspectToPrimaryTarget];
5239 if(![forward_weapon_type isTurretLaser] && (frustration > 10 || aspect > 0.75))
5241 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET;
5245 if (missiles) [
self considerFiringMissile:delta_t];
5247 if (cloakAutomatic) [
self activateCloakingDevice];
5248 [
self fireMainWeapon:range];
5254 behaviour = BEHAVIOUR_ATTACK_TARGET;
5259- (void) behaviour_attack_mining_target:(
double) delta_t
5261 double range = [
self rangeToPrimaryTarget];
5262 if (![
self canStillTrackPrimaryTarget])
5264 [
self noteLostTargetAndGoIdle];
5265 desired_speed = maxFlightSpeed * 0.375;
5268 else if ((range < 650) || ([
self proximityAlert] !=
nil))
5272 desired_speed = range * maxFlightSpeed / (650.0 * 16.0);
5276 [
self avoidCollision];
5282 desired_speed = maxFlightSpeed * 0.875;
5285 [
self trackPrimaryTarget:delta_t:NO];
5293 [
self fireMainWeapon:range];
5300- (void) behaviour_attack_fly_to_target:(
double) delta_t
5302 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
5303 float max_available_speed = maxFlightSpeed;
5304 double range = [
self rangeToPrimaryTarget];
5305 if (canBurn) max_available_speed *= [
self afterburnerFactor];
5306 if ([
self primaryTarget] ==
nil)
5308 [
self noteLostTargetAndGoIdle];
5311 Entity* rawTarget = [
self primaryTarget];
5312 if (![rawTarget isShip])
5315 [
self noteLostTargetAndGoIdle];
5322 if (![
self hasProximityAlertIgnoringTarget:YES])
5324 behaviour = BEHAVIOUR_ATTACK_TARGET;
5328 [
self avoidCollision];
5334 if (![
self canStillTrackPrimaryTarget])
5336 [
self noteLostTargetAndGoIdle];
5343 BOOL isUsingAfterburner = canBurn && (flightSpeed > maxFlightSpeed);
5344 BOOL closeQuickly = (canBurn && [target
weaponRange] > weaponRange && range > weaponRange);
5345 double slow_down_range = weaponRange *
COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [
self afterburnerFactor] : 1.0);
5350 double back_off_range = 10000 *
COMBAT_OUT_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * [
self afterburnerFactor] : 1.0);
5351 double target_speed = [target
speed];
5352 double aspect = [
self approachAspectToPrimaryTarget];
5354 if (range <= slow_down_range)
5356 if (range < back_off_range)
5362 desired_speed = fmax(target_speed * 1.25, 0.8 * maxFlightSpeed);
5367 desired_speed = fmax(target_speed * 1.05, 0.25 * maxFlightSpeed);
5373 desired_speed = fmax(0.1 * target_speed, 0.1 * maxFlightSpeed);
5380 desired_speed = fmax(target_speed * 1.5, maxFlightSpeed);
5386 desired_speed = fmax(0.5 * target_speed, 0.5 * maxFlightSpeed);
5390 desired_speed = fmax(1.25 * target_speed, 0.5 * maxFlightSpeed);
5399 desired_speed = max_available_speed;
5403 desired_speed = fmax(maxFlightSpeed,fmin(3.0 * target_speed, max_available_speed));
5408 double last_success_factor = success_factor;
5409 success_factor = [
self trackPrimaryTarget:delta_t:NO];
5411 if ((success_factor > 0.999)||(success_factor > last_success_factor))
5413 frustration -= delta_t;
5414 if (frustration < 0.0)
5419 frustration += delta_t;
5420 if (frustration > 3.0)
5422 [
self noteFrustration:@"BEHAVIOUR_ATTACK_FLY_TO_TARGET"];
5423 [
self setEvasiveJink:1000.0];
5424 behaviour = BEHAVIOUR_ATTACK_TARGET;
5426 desired_speed = maxFlightSpeed;
5430 if (missiles) [
self considerFiringMissile:delta_t];
5432 if (cloakAutomatic) [
self activateCloakingDevice];
5433 [
self fireMainWeapon:range];
5448 behaviour = BEHAVIOUR_ATTACK_TARGET;
5455 if ([target behaviour] != BEHAVIOUR_FLEE_TARGET && [target behaviour] != BEHAVIOUR_FLEE_EVASIVE_ACTION)
5460 behaviour = BEHAVIOUR_EVASIVE_ACTION;
5467- (void) behaviour_attack_fly_from_target:(
double) delta_t
5469 double range = [
self rangeToPrimaryTarget];
5470 double last_success_factor = success_factor;
5471 success_factor = range;
5473 if ([
self primaryTarget] ==
nil)
5475 [
self noteLostTargetAndGoIdle];
5478 if (last_success_factor > success_factor)
5480 frustration += delta_t;
5484 frustration += delta_t / 4.0 ;
5487 if (frustration > 10.0)
5489 if (
randf() < 0.3) {
5490 desired_speed = maxFlightSpeed * (([
self hasFuelInjection] && (fuel >
MIN_FUEL)) ? [
self afterburnerFactor] : 1);
5494 behaviour = BEHAVIOUR_ATTACK_TARGET;
5500 desired_speed = flightSpeed * 2;
5502 [
self setEvasiveJink:z];
5506 if (desired_speed > maxFlightSpeed)
5509 double target_speed = [target
speed];
5510 if (desired_speed > target_speed * 2.0)
5512 desired_speed = maxFlightSpeed;
5515 else if (desired_speed < maxFlightSpeed * 0.5)
5517 desired_speed = maxFlightSpeed;
5521 flightSpeed > (scannerRange - range) * max_flight_pitch / 6.28)
5524 behaviour = BEHAVIOUR_ATTACK_TARGET;
5527 [
self trackPrimaryTarget:delta_t:YES];
5529 if (missiles) [
self considerFiringMissile:delta_t];
5531 if (cloakAutomatic) [
self activateCloakingDevice];
5532 if ([
self hasProximityAlertIgnoringTarget:YES])
5533 [
self avoidCollision];
5537 double aspect = [
self approachAspectToPrimaryTarget];
5540 if (aspect > 0.99999 || aspect < -0.999)
5543 behaviour = BEHAVIOUR_EVASIVE_ACTION;
5550- (void) behaviour_running_defense:(
double) delta_t
5552 if (![
self canStillTrackPrimaryTarget])
5554 [
self noteLostTargetAndGoIdle];
5558 double range = [
self rangeToPrimaryTarget];
5559 desired_speed = maxFlightSpeed;
5561 if (range > weaponRange || range > 0.8 * scannerRange || range == 0)
5563 behaviour = BEHAVIOUR_CLOSE_WITH_TARGET;
5564 if ([forward_weapon_type isTurretLaser])
5566 behaviour = BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE;
5570 [
self trackPrimaryTarget:delta_t:YES];
5571 if ([forward_weapon_type isTurretLaser])
5574 [
self fireMainWeapon:range];
5578 [
self fireAftWeapon:range];
5580 if (cloakAutomatic) [
self activateCloakingDevice];
5581 if ([
self hasProximityAlertIgnoringTarget:YES])
5582 [
self avoidCollision];
5586 behaviour = BEHAVIOUR_ATTACK_TARGET;
5592 [
self avoidCollision];
5599- (void) behaviour_flee_target:(
double) delta_t
5601 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
5602 float max_available_speed = maxFlightSpeed;
5603 double range = [
self rangeToPrimaryTarget];
5604 if ([
self primaryTarget] ==
nil)
5606 [
self noteLostTargetAndGoIdle];
5609 if (canBurn) max_available_speed *= [
self afterburnerFactor];
5611 double last_range = success_factor;
5612 success_factor = range;
5614 if (range > desired_range || range == 0)
5615 [shipAI message:@"REACHED_SAFETY"];
5617 desired_speed = max_available_speed;
5619 if (range > last_range)
5621 frustration -= 0.25 * delta_t;
5622 if (frustration < 0.0)
5627 frustration += delta_t;
5628 if (frustration > 15.0)
5630 [
self noteFrustration:@"BEHAVIOUR_FLEE_TARGET"];
5635 [
self trackPrimaryTarget:delta_t:YES];
5637 Entity *target = [
self primaryTarget];
5639 if (missiles && [target isShip] && [(
ShipEntity *)target primaryTarget] ==
self)
5641 [
self considerFiringMissile:delta_t];
5644 if (([
self hasCascadeMine]) && (range < 10000.0) && canBurn)
5646 float qbomb_chance = 0.01 * delta_t;
5647 if (
randf() < qbomb_chance)
5649 [
self launchCascadeMine];
5654 if ([forward_weapon_type isTurretLaser])
5656 [
self fireMainWeapon:range];
5659 if (cloakAutomatic) [
self activateCloakingDevice];
5664 [
self avoidCollision];
5670- (void) behaviour_fly_range_from_destination:(
double) delta_t
5672 double distance = [
self rangeToDestination];
5673 if (distance < desired_range)
5675 behaviour = BEHAVIOUR_FLY_FROM_DESTINATION;
5676 if (desired_speed < maxFlightSpeed)
5678 desired_speed = maxFlightSpeed;
5683 behaviour = BEHAVIOUR_FLY_TO_DESTINATION;
5685 if ([
self hasProximityAlertIgnoringTarget:YES])
5687 [
self avoidCollision];
5696- (void) behaviour_face_destination:(
double) delta_t
5699 double distance = [
self rangeToDestination];
5700 double old_pitch = flightPitch;
5701 desired_speed = 0.0;
5702 if (desired_range > 1.0 && distance > desired_range)
5704 max_cos = sqrt(1 - 0.90 * desired_range*desired_range/(distance * distance));
5706 double confidenceFactor = [
self trackDestination:delta_t:NO];
5707 if (confidenceFactor >= max_cos && flightPitch == 0.0)
5710 [shipAI message:@"FACING_DESTINATION"];
5711 [
self doScriptEvent:OOJSID("shipNowFacingDestination")];
5713 if(docking_match_rotation)
5715 behaviour = BEHAVIOUR_FLY_TO_DESTINATION;
5719 behaviour = BEHAVIOUR_IDLE;
5723 if(flightSpeed == 0) frustration += delta_t;
5724 if (frustration > 15.0 / max_flight_pitch)
5727 [
self noteFrustration:@"BEHAVIOUR_FACE_DESTINATION"];
5728 if(flightPitch == old_pitch) flightPitch = 0.5 * max_flight_pitch;
5736 if(flightSpeed == 0 && frustration > 5 && confidenceFactor > 0.5 && ((flightPitch > 0 && old_pitch < 0) || (flightPitch < 0 && old_pitch > 0)))
5738 flightPitch += 0.5 * old_pitch;
5741 if ([
self hasProximityAlertIgnoringTarget:YES])
5743 [
self avoidCollision];
5750- (void) behaviour_land_on_planet:(
double) delta_t
5753 desired_speed = 0.0;
5755 OOPlanetEntity* planet = [UNIVERSE entityForUniversalID:planetForLanding];
5757 if (![planet isPlanet])
5759 behaviour = BEHAVIOUR_IDLE;
5760 aiScriptWakeTime = 1;
5761 [shipAI message:@"NO_PLANET_NEARBY"];
5765 if (HPdistance(position, [planet position]) + [
self collisionRadius] < [planet radius])
5768 [
self landOnPlanet:planet];
5772 double confidenceFactor = [
self trackDestination:delta_t:YES];
5774 if (confidenceFactor >= max_cos && flightSpeed == 0.0)
5780 [
self adjustVelocity:vector_multiply_scalar([
self forwardVector], -max_thrust * delta_t)];
5785 if ([
self hasProximityAlertIgnoringTarget:YES])
5787 [
self avoidCollision];
5794- (void) behaviour_formation_form_up:(
double) delta_t
5798 double distance = [
self rangeToDestination];
5799 double eta = (distance - desired_range) / flightSpeed;
5800 if(eta < 0) eta = 0;
5801 if ((eta < 5.0)&&(leadShip)&&(leadShip->
isShip))
5802 desired_speed = [leadShip
flightSpeed] * (1 + eta * 0.05);
5804 desired_speed = maxFlightSpeed;
5806 double last_distance = success_factor;
5807 success_factor = distance;
5810 [
self trackDestination:delta_t: NO];
5813 GLfloat slowdownTime = (thrust > 0.0)? flightSpeed / (thrust) : 4.0;
5814 GLfloat minTurnSpeedFactor = 0.05 * max_flight_pitch * max_flight_roll;
5816 if ((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor))
5817 desired_speed = flightSpeed * 0.50;
5819 if (distance < last_distance)
5821 frustration -= 0.25 * delta_t;
5822 if (frustration < 0.0)
5827 frustration += delta_t;
5828 if (frustration > 15.0)
5830 if (!leadShip) [
self noteFrustration:@"BEHAVIOUR_FORMATION_FORM_UP"];
5831 else if (distance > 0.5 * scannerRange && !pitching_over)
5833 pitching_over = YES;
5838 if ([
self hasProximityAlertIgnoringTarget:YES])
5840 [
self avoidCollision];
5847- (void) behaviour_fly_to_destination:(
double) delta_t
5849 double distance = [
self rangeToDestination];
5851 if (distance < desired_range)
5854 [shipAI message:@"DESIRED_RANGE_ACHIEVED"];
5855 [
self doScriptEvent:OOJSID("shipAchievedDesiredRange")];
5857 if(!docking_match_rotation)
5859 behaviour = BEHAVIOUR_IDLE;
5860 desired_speed = 0.0;
5866 double last_distance = success_factor;
5867 success_factor = distance;
5870 double confidenceFactor = [
self trackDestination:delta_t: NO];
5871 if(confidenceFactor < 0.2) confidenceFactor = 0.2;
5884 GLfloat eta = ((distance + 1) - desired_range) / (0.51 * flightSpeed * confidenceFactor);
5885 GLfloat slowdownTime = (thrust > 0.0)? flightSpeed / (thrust) : 4.0;
5886 GLfloat minTurnSpeedFactor = 0.05 * max_flight_pitch * max_flight_roll;
5887 if (dockingInstructions !=
nil)
5889 minTurnSpeedFactor /= 10.0;
5890 if (minTurnSpeedFactor * maxFlightSpeed > 20.0)
5892 minTurnSpeedFactor /= 10.0;
5897 if (((eta < slowdownTime)&&(flightSpeed > maxFlightSpeed * minTurnSpeedFactor)) || (flightSpeed > max_flight_pitch * 5 * confidenceFactor * distance))
5899 desired_speed = flightSpeed * 0.50;
5904 if (docking_match_rotation && confidenceFactor >=
MAX_COS && dockingInstructions !=
nil && [dockingInstructions oo_intForKey:
@"docking_stage"] >= 7)
5909 if ((station_for_docking)&&(station_for_docking->
isStation))
5911 float rollMatch = dot_product([station_for_docking portUpVectorForShip:
self],[
self upVector]);
5912 if (rollMatch < MAX_COS && rollMatch > -
MAX_COS)
5915 desired_speed = 0.1;
5917 else if (desired_speed <= 0.2)
5920 desired_speed = [dockingInstructions oo_floatForKey:@"speed"];
5927 if (distance < last_distance)
5929 frustration -= 0.25 * delta_t;
5930 if (frustration < 0.0)
5935 frustration += delta_t;
5936 if ((frustration > slowdownTime * 10.0 && slowdownTime > 0)||(frustration > 15.0))
5938 [
self noteFrustration:@"BEHAVIOUR_FLY_TO_DESTINATION"];
5939 frustration -= slowdownTime * 5.0;
5943 if ([
self hasProximityAlertIgnoringTarget:YES])
5945 [
self avoidCollision];
5952- (void) behaviour_fly_from_destination:(
double) delta_t
5954 double distance = [
self rangeToDestination];
5955 if (distance > desired_range)
5958 [shipAI message:@"DESIRED_RANGE_ACHIEVED"];
5959 [
self doScriptEvent:OOJSID("shipAchievedDesiredRange")];
5961 behaviour = BEHAVIOUR_IDLE;
5963 desired_speed = 0.0;
5966 [
self trackDestination:delta_t:YES];
5967 if ([
self hasProximityAlertIgnoringTarget:YES])
5969 [
self avoidCollision];
5976- (void) behaviour_avoid_collision:(
double) delta_t
5978 double distance = [
self rangeToDestination];
5979 if (distance > desired_range)
5981 [
self resumePostProximityAlert];
5989 _destination = prox_ship->
position;
5991 double dq = [
self trackDestination:delta_t:YES];
5994 desired_speed = maxFlightSpeed * (0.5 * dq + 0.5);
6002- (void) behaviour_track_as_turret:(
double) delta_t
6007 if (turret_owner && turret_target && [turret_owner hasHostileTarget])
6009 aim = [
self ballTrackLeadingTarget:delta_t atTarget:turret_target];
6012 HPVector p = HPvector_subtract([turret_target position], [turret_owner position]);
6017 [
self fireTurretCannon:HPmagnitude(p) - cr];
6026 while ((target = [[targetEnum nextObject] weakRefUnderlyingObject]))
6029 if ([target scanClass] == CLASS_NO_DRAW || [(
ShipEntity *)target isCloaked] || [target energy] <= 0.0)
6036 if (range < weaponRange)
6038 aim = [
self ballTrackLeadingTarget:delta_t atTarget:target];
6041 HPVector p = HPvector_subtract([target position], [turret_owner position]);
6046 [
self fireTurretCannon:HPmagnitude(p) - cr];
6052 else if (range > scannerRange)
6064- (void) behaviour_fly_thru_navpoints:(
double) delta_t
6066 int navpoint_plus_index = (next_navpoint_index + 1) % number_of_navpoints;
6067 HPVector d1 = navpoints[next_navpoint_index];
6068 HPVector d2 = navpoints[navpoint_plus_index];
6070 HPVector rel = HPvector_between(d1, position);
6071 HPVector ref = HPvector_between(d2, d1);
6072 ref = HPvector_normal(ref);
6074 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 GLfloat r0 = HPdot_product(rel, ref);
6082 GLfloat r1 = HPmagnitude(xp);
6084 BOOL in_cone = (r0 > 0.5 * r1);
6087 r1 = 25.0 * flightSpeed;
6091 GLfloat dist2 = HPmagnitude2(rel);
6093 if (dist2 < desired_range * desired_range)
6096 [
self doScriptEvent:OOJSID("shipReachedNavPoint") andReactToAIMessage:@"NAVPOINT_REACHED"];
6097 if (navpoint_plus_index == 0)
6099 [
self doScriptEvent:OOJSID("shipReachedEndPoint") andReactToAIMessage:@"ENDPOINT_REACHED"];
6100 behaviour = BEHAVIOUR_IDLE;
6102 next_navpoint_index = navpoint_plus_index;
6106 double last_success_factor = success_factor;
6107 double last_dist2 = last_success_factor;
6108 success_factor = dist2;
6111 _destination = make_HPvector(d1.x + r1 * ref.x, d1.y + r1 * ref.y, d1.z + r1 * ref.z);
6116 GLfloat temp = desired_range;
6118 desired_range = 1.0;
6120 desired_range = 100.0;
6121 v0 = [
self trackDestination:delta_t: NO];
6122 desired_range = temp;
6124 if (dist2 < last_dist2)
6126 frustration -= 0.25 * delta_t;
6127 if (frustration < 0.0)
6132 frustration += delta_t;
6133 if (frustration > 15.0)
6135 [
self noteFrustration:@"BEHAVIOUR_FLY_THRU_NAVPOINTS"];
6136 frustration -= 15.0;
6143 GLfloat temp = desired_speed;
6144 desired_speed *= v0 * v0;
6146 desired_speed = temp;
6150- (void) behaviour_scripted_ai:(
double) delta_t
6154 jsval rval = JSVAL_VOID;
6155 jsval deltaJS = JSVAL_VOID;
6156 NSDictionary *result =
nil;
6158 BOOL OK = JS_NewNumberValue(context, delta_t, &deltaJS);
6161 OK = [[
self script] callMethod:OOJSID("scriptedAI")
6163 withArguments:&deltaJS
6170 OOLog(
@"ai.error",
@"Could not call scriptedAI in ship script of %@, reverting to idle",
self);
6171 behaviour = BEHAVIOUR_IDLE;
6176 if (!JSVAL_IS_OBJECT(rval))
6178 OOLog(
@"ai.error",
@"Invalid return value of scriptedAI in ship script of %@, reverting to idle",
self);
6179 behaviour = BEHAVIOUR_IDLE;
6188 if ([result objectForKey:
@"stickRollFactor"] !=
nil)
6190 stick_roll = [result oo_floatForKey:@"stickRollFactor"] * max_flight_roll;
6194 stick_roll = [result oo_floatForKey:@"stickRoll"];
6196 if (stick_roll > max_flight_roll)
6198 stick_roll = max_flight_roll;
6200 else if (stick_roll < -max_flight_roll)
6202 stick_roll = -max_flight_roll;
6206 if ([result objectForKey:
@"stickPitchFactor"] !=
nil)
6208 stick_pitch = [result oo_floatForKey:@"stickPitchFactor"] * max_flight_pitch;
6212 stick_pitch = [result oo_floatForKey:@"stickPitch"];
6214 if (stick_pitch > max_flight_pitch)
6216 stick_pitch = max_flight_pitch;
6218 else if (stick_pitch < -max_flight_pitch)
6220 stick_pitch = -max_flight_pitch;
6224 if ([result objectForKey:
@"stickYawFactor"] !=
nil)
6226 stick_yaw = [result oo_floatForKey:@"stickYawFactor"] * max_flight_yaw;
6230 stick_yaw = [result oo_floatForKey:@"stickYaw"];
6232 if (stick_yaw > max_flight_yaw)
6234 stick_yaw = max_flight_yaw;
6236 else if (stick_yaw < -max_flight_yaw)
6238 stick_yaw = -max_flight_yaw;
6242 [
self applySticks:delta_t];
6245 if ([result objectForKey:
@"desiredSpeedFactor"] !=
nil)
6247 desired_speed = [result oo_floatForKey:@"desiredSpeedFactor"] * maxFlightSpeed;
6251 desired_speed = [result oo_floatForKey:@"desiredSpeed"];
6254 if (desired_speed < 0.0)
6256 desired_speed = 0.0;
6260 if (behaviour == BEHAVIOUR_SCRIPTED_ATTACK_AI)
6262 NSString* chosen_weapon = [result oo_stringForKey:@"chosenWeapon" defaultValue:@"FORWARD"];
6263 double range = [
self rangeToPrimaryTarget];
6265 if ([chosen_weapon isEqualToString:
@"FORWARD"])
6267 [
self fireMainWeapon:range];
6269 else if ([chosen_weapon isEqualToString:
@"AFT"])
6271 [
self fireAftWeapon:range];
6273 else if ([chosen_weapon isEqualToString:
@"PORT"])
6275 [
self firePortWeapon:range];
6277 else if ([chosen_weapon isEqualToString:
@"STARBOARD"])
6279 [
self fireStarboardWeapon:range];
6284- (float) reactionTime
6286 return reactionTime;
6290- (void) setReactionTime: (
float) newReactionTime
6292 reactionTime = newReactionTime;
6296- (HPVector) calculateTargetPosition
6298 Entity *target = [
self primaryTarget];
6303 if (reactionTime <= 0.0)
6307 double t = [UNIVERSE getTime] - trackingCurveTimes[1];
6308 return HPvector_add(HPvector_add(trackingCurveCoeffs[0], HPvector_multiply_scalar(trackingCurveCoeffs[1],t)), HPvector_multiply_scalar(trackingCurveCoeffs[2],t*t));
6312- (void) startTrackingCurve
6314 Entity *target = [
self primaryTarget];
6320 trackingCurvePositions[0] = [target
position];
6321 trackingCurvePositions[1] = [target
position];
6322 trackingCurvePositions[2] = [target
position];
6323 trackingCurvePositions[3] = [target
position];
6324 trackingCurveTimes[0] = now;
6325 trackingCurveTimes[1] = now - reactionTime/3.0;
6326 trackingCurveTimes[2] = now - reactionTime*2.0/3.0;
6327 trackingCurveTimes[3] = now - reactionTime;
6328 [
self calculateTrackingCurve];
6333- (void) updateTrackingCurve
6335 Entity *target = [
self primaryTarget];
6337 if (target ==
nil || reactionTime <= 0.0 || trackingCurveTimes[0] + reactionTime/3.0 > now)
return;
6338 trackingCurvePositions[3] = trackingCurvePositions[2];
6339 trackingCurvePositions[2] = trackingCurvePositions[1];
6340 trackingCurvePositions[1] = trackingCurvePositions[0];
6350 trackingCurvePositions[0] = [target
position];
6352 trackingCurveTimes[3] = trackingCurveTimes[2];
6353 trackingCurveTimes[2] = trackingCurveTimes[1];
6354 trackingCurveTimes[1] = trackingCurveTimes[0];
6355 trackingCurveTimes[0] = now;
6356 [
self calculateTrackingCurve];
6360- (void) calculateTrackingCurve
6362 if (reactionTime <= 0.0)
6364 trackingCurveCoeffs[0] = trackingCurvePositions[0];
6369 double t1 = trackingCurveTimes[2] - trackingCurveTimes[1],
6370 t2 = trackingCurveTimes[3] - trackingCurveTimes[1];
6371 trackingCurveCoeffs[0] = trackingCurvePositions[1];
6372 trackingCurveCoeffs[1] = HPvector_add(HPvector_add(
6373 HPvector_multiply_scalar(trackingCurvePositions[1], -(t1+t2)/(t1*t2)),
6374 HPvector_multiply_scalar(trackingCurvePositions[2], -t2/(t1*(t1-t2)))),
6375 HPvector_multiply_scalar(trackingCurvePositions[3], t1/(t2*(t1-t2))));
6376 trackingCurveCoeffs[2] = HPvector_add(HPvector_add(
6377 HPvector_multiply_scalar(trackingCurvePositions[1], 1/(t1*t2)),
6378 HPvector_multiply_scalar(trackingCurvePositions[2], 1/(t1*(t1-t2)))),
6379 HPvector_multiply_scalar(trackingCurvePositions[3], -1/(t2*(t1-t2))));
6383- (void) drawImmediate:(
bool)immediate translucent:(
bool)translucent
6385 if ((no_draw_distance < cam_zero_distance) ||
6386 (cloaking_device_active &&
randf() > 0.10))
6393 [
super drawImmediate:immediate translucent:translucent];
6398 if (translucent) [
self drawDebugStuff];
6410 if ([
self subEntityCount] > 0)
6413 foreach (subEntity, [
self subEntities])
6415 NSAssert3([subEntity owner] ==
self,
@"Subentity ownership broke - %@ should be owned by %@ but is owned by %@.", subEntity,
self, [subEntity owner]);
6427 if (0 && reportAIMessages)
6432 Entity *pTarget = [
self primaryTarget];
6436 OODebugDrawColoredLine(HPVectorToVector([
self position]), HPVectorToVector([pTarget position]), [
OOColor colorWithRed:0.2 green:0.0 blue:0.0 alpha:1.0]);
6439 Entity *sTarget = [
self targetStation];
6440 if (sTarget != pTarget && [sTarget isStation])
6445 Entity *fTarget = [
self foundTarget];
6446 if (fTarget !=
nil && fTarget != pTarget && fTarget != sTarget)
6455- (void) drawSubEntityImmediate:(
bool)immediate translucent:(
bool)translucent
6459 if (cam_zero_distance > no_draw_distance)
6468 [
self drawImmediate:immediate translucent:translucent];
6495- (GLfloat *) scannerDisplayColorForShip:(
ShipEntity*)otherShip :(BOOL)isHostile :(BOOL)flash :(
OOColor *)scannerDisplayColor1 :(
OOColor *)scannerDisplayColor2 :(
OOColor *)scannerDisplayColorH1 :(
OOColor *)scannerDisplayColorH2
6502 if (scannerDisplayColorH1 || scannerDisplayColorH2)
6504 if (scannerDisplayColorH1 && !scannerDisplayColorH2)
6506 [scannerDisplayColorH1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
6509 if (!scannerDisplayColorH1 && scannerDisplayColorH2)
6511 [scannerDisplayColorH2
getRed:&scripted_color[0]
green:&scripted_color[1]
blue:&scripted_color[2]
alpha:&scripted_color[3]];
6514 if (scannerDisplayColorH1 && scannerDisplayColorH2)
6517 [scannerDisplayColorH1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
6519 [scannerDisplayColorH2
getRed:&scripted_color[0]
green:&scripted_color[1]
blue:&scripted_color[2]
alpha:&scripted_color[3]];
6527 if (scannerDisplayColor1 || scannerDisplayColor2)
6529 if (scannerDisplayColor1 && !scannerDisplayColor2)
6531 [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
6534 if (!scannerDisplayColor1 && scannerDisplayColor2)
6536 [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
6539 if (scannerDisplayColor1 && scannerDisplayColor2)
6542 [scannerDisplayColor1 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
6544 [scannerDisplayColor2 getRed:&scripted_color[0] green:&scripted_color[1] blue:&scripted_color[2] alpha:&scripted_color[3]];
6551 if ([
self isJammingScanning])
6553 if (![otherShip hasMilitaryScannerFilter])
6574 case CLASS_THARGOID :
6579 case CLASS_MISSILE :
6581 case CLASS_STATION :
6589 case CLASS_MILITARY :
6590 if ((isHostile)&&(flash))
6607- (void)setScannerDisplayColor1:(
OOColor *)color
6609 DESTROY(scanner_display_color1);
6612 scanner_display_color1 = [color retain];
6616- (void)setScannerDisplayColor2:(
OOColor *)color
6618 DESTROY(scanner_display_color2);
6621 scanner_display_color2 = [color retain];
6625- (
OOColor *)scannerDisplayColor1
6627 return [[scanner_display_color1 retain] autorelease];
6631- (
OOColor *)scannerDisplayColor2
6633 return [[scanner_display_color2 retain] autorelease];
6637- (void)setScannerDisplayColorHostile1:(
OOColor *)color
6639 DESTROY(scanner_display_color_hostile1);
6642 scanner_display_color_hostile1 = [color retain];
6646- (void)setScannerDisplayColorHostile2:(
OOColor *)color
6648 DESTROY(scanner_display_color_hostile2);
6651 scanner_display_color_hostile2 = [color retain];
6655- (
OOColor *)scannerDisplayColorHostile1
6657 return [[scanner_display_color_hostile1 retain] autorelease];
6661- (
OOColor *)scannerDisplayColorHostile2
6663 return [[scanner_display_color_hostile2 retain] autorelease];
6669 return cloaking_device_active;
6675 return cloakPassive;
6679- (void)setCloaked:(BOOL)cloak
6681 if (cloak) [
self activateCloakingDevice];
6682 else [
self deactivateCloakingDevice];
6688 return cloakAutomatic;
6692- (void)setAutoCloak:(BOOL)automatic
6694 cloakAutomatic = !!automatic;
6698- (BOOL) isJammingScanning
6700 return ([
self hasMilitaryJammer] && military_jammer_active);
6704- (void) addSubEntity:(
Entity<OOSubEntity> *)sub
6706 if (sub ==
nil)
return;
6708 if (subEntities ==
nil) subEntities = [[NSMutableArray alloc] init];
6711 [subEntities addObject:sub];
6712 [sub setOwner:self];
6714 [
self addSubentityToCollisionRadius:sub];
6718- (void) setOwner:(
Entity *)who_owns_entity
6720 [
super setOwner:who_owns_entity];
6729 [[
self drawable] setBindingTarget:self];
6734- (void) applyThrust:(
double) delta_t
6737 BOOL canBurn = [
self hasFuelInjection] && (fuel >
MIN_FUEL);
6738 BOOL isUsingAfterburner = (canBurn && (flightSpeed > maxFlightSpeed) && (desired_speed >= flightSpeed));
6739 float max_available_speed = maxFlightSpeed;
6740 if (canBurn) max_available_speed *= [
self afterburnerFactor];
6745 GLfloat velmag = magnitude(velocity);
6748 GLfloat vscale = fmaxf((velmag - dt_thrust) / velmag, 0.0f);
6749 scale_vector(&velocity, vscale);
6753 if (behaviour == BEHAVIOUR_TUMBLE)
return;
6756 if (desired_speed > max_available_speed)
6757 desired_speed = max_available_speed;
6759 if (flightSpeed > desired_speed)
6761 [
self decrease_flight_speed: dt_thrust];
6762 if (flightSpeed < desired_speed) flightSpeed = desired_speed;
6764 if (flightSpeed < desired_speed)
6766 [
self increase_flight_speed: dt_thrust];
6767 if (flightSpeed > desired_speed) flightSpeed = desired_speed;
6769 [
self moveForward: delta_t*flightSpeed];
6772 if (isUsingAfterburner)
6774 fuel_accumulator -= delta_t * afterburner_rate;
6775 while (fuel_accumulator < 0.0)
6778 fuel_accumulator += 1.0;
6784- (void) orientationChanged
6786 [
super orientationChanged];
6794- (void) applyRoll:(GLfloat) roll1 andClimb:(GLfloat) climb1
6798 if (!roll1 && !climb1 && !hasRotated)
return;
6804 [
self orientationChanged];
6808- (void) applyRoll:(GLfloat) roll1 climb:(GLfloat) climb1 andYaw:(GLfloat) yaw1
6810 if ((roll1 == 0.0)&&(climb1 == 0.0)&&(yaw1 == 0.0)&&(!hasRotated))
6823 [
self orientationChanged];
6827- (void) applyAttitudeChanges:(
double) delta_t
6829 [
self applyRoll:flightRoll*delta_t climb:flightPitch*delta_t andYaw:flightYaw*delta_t];
6833- (void) avoidCollision
6835 if (scanClass == CLASS_MISSILE)
6842 if (previousCondition)
6844 [previousCondition release];
6845 previousCondition =
nil;
6848 previousCondition = [[NSMutableDictionary dictionaryWithCapacity:5] retain];
6850 [previousCondition oo_setInteger:behaviour forKey:@"behaviour"];
6851 if ([
self primaryTarget] !=
nil)
6854 [previousCondition setObject:[[
self primaryTarget] weakSelf] forKey:@"primaryTarget"];
6856 [previousCondition oo_setFloat:desired_range forKey:@"desired_range"];
6857 [previousCondition oo_setFloat:desired_speed forKey:@"desired_speed"];
6858 [previousCondition oo_setHPVector:_destination forKey:@"destination"];
6860 _destination = [prox_ship
position];
6861 _destination = OOHPVectorInterpolate(position, [prox_ship position], 0.5);
6865 behaviour = BEHAVIOUR_AVOID_COLLISION;
6866 pitching_over = YES;
6871- (void) resumePostProximityAlert
6873 if (!previousCondition)
return;
6875 behaviour = [previousCondition oo_intForKey:@"behaviour"];
6876 [_primaryTarget release];
6877 _primaryTarget = [[previousCondition objectForKey:@"primaryTarget"] weakRetain];
6878 [
self startTrackingCurve];
6879 desired_range = [previousCondition oo_floatForKey:@"desired_range"];
6880 desired_speed = [previousCondition oo_floatForKey:@"desired_speed"];
6881 _destination = [previousCondition oo_hpvectorForKey:@"destination"];
6883 [previousCondition release];
6884 previousCondition =
nil;
6893- (double) messageTime
6899- (void) setMessageTime:(
double) value
6901 messageTime = value;
6913 if (group != _group)
6915 if (_escortGroup != _group)
6917 if (
self == [_group leader]) [_group setLeader:nil];
6918 [_group removeShip:self];
6922 _group = [group retain];
6931 if (_escortGroup ==
nil)
6933 _escortGroup = [[
OOShipGroup alloc] initWithName:@"escort group"];
6934 [_escortGroup setLeader:self];
6937 return _escortGroup;
6943 if (group != _escortGroup)
6945 [_escortGroup release];
6946 _escortGroup = [group retain];
6948 [
self updateEscortFormation];
6956 return _escortGroup;
6965 _group = [[
OOShipGroup alloc] initWithName:@"station group"];
6966 [_group setLeader:self];
6975 if (_escortGroup ==
nil)
return NO;
6976 return [_escortGroup count] > 1;
6980- (NSEnumerator *) escortEnumerator
6982 if (_escortGroup ==
nil)
return [[NSArray array] objectEnumerator];
6983 return [[_escortGroup mutationSafeEnumerator] ooExcludingObject:self];
6987- (NSArray *) escortArray
6989 if (_escortGroup ==
nil)
return [NSArray array];
6990 return [[
self escortEnumerator] allObjects];
6994- (uint8_t) escortCount
6996 if (_escortGroup ==
nil)
return 0;
6997 return [_escortGroup count] - 1;
7001- (uint8_t) pendingEscortCount
7003 return _pendingEscortCount;
7007- (void) setPendingEscortCount:(uint8_t)count
7009 _pendingEscortCount =
MIN(
count, _maxEscortCount);
7013- (uint8_t) maxEscortCount
7015 return _maxEscortCount;
7019- (void) setMaxEscortCount:(uint8_t)newCount
7021 _maxEscortCount = newCount;
7025- (NSUInteger) turretCount
7027 NSUInteger
count = 0;
7028 NSEnumerator *subEnum = [
self shipSubEntityEnumerator];
7030 while ((se = [subEnum nextObject]))
7041- (
Entity*) proximityAlert
7043 Entity* prox = [_proximityAlert weakRefUnderlyingObject];
7052- (void) setProximityAlert:(
ShipEntity*) other
7060 if ([other mass] < 2000)
7068 if ((other->
isStation) && ([
self status] == STATUS_LAUNCHING ||
7069 dockingInstructions !=
nil))
7078 Vector vdiff = HPVectorToVector(HPvector_between(position, other->
position));
7079 GLfloat d_forward = dot_product(vdiff, v_forward);
7080 GLfloat d_up = dot_product(vdiff, v_up);
7081 GLfloat d_right = dot_product(vdiff, v_right);
7082 if ((d_forward > 0.0)&&(flightSpeed > 0.0))
7083 d_forward *= 0.25 * maxFlightSpeed / flightSpeed;
7084 double d2 = d_forward * d_forward + d_up * d_up + d_right * d_right;
7085 double cr2 = collision_radius * 2.0 + other->collision_radius; cr2 *= cr2;
7090 if (behaviour == BEHAVIOUR_AVOID_COLLISION)
7093 if ((prox)&&(prox != other))
7098 if (sa_prox < sa_other)
return;
7101 [_proximityAlert release];
7112- (NSString *) shipUniqueName
7114 return shipUniqueName;
7118- (NSString *) shipClassName
7120 return shipClassName;
7124- (NSString *) displayName
7126 if (displayName ==
nil || [displayName length] == 0)
7128 if ([shipUniqueName length] == 0)
7130 if (shipClassName ==
nil)
7136 return shipClassName;
7141 if (shipClassName ==
nil)
7143 return [NSString stringWithFormat:@"%@: %@",name,shipUniqueName];
7147 return [NSString stringWithFormat:@"%@: %@",shipClassName,shipUniqueName];
7156- (NSString *)scanDescriptionForScripting
7158 return scan_description;
7162- (NSString *)scanDescription
7164 if (scan_description !=
nil)
7166 return scan_description;
7170 NSString *desc =
nil;
7171 switch ([
self scanClass])
7175 int legal = [
self legalStatus];
7179 legal_i = (legal <= 50) ? 1 : 2;
7181 desc = [[[UNIVERSE descriptions] oo_arrayForKey:@"legal_status"] oo_stringAtIndex:legal_i];
7185 case CLASS_THARGOID:
7186 desc =
DESC(
@"legal-desc-alien");
7190 desc =
DESC(
@"legal-desc-system-vessel");
7193 case CLASS_MILITARY:
7194 desc =
DESC(
@"legal-desc-military-vessel");
7205- (void) setName:(NSString *)inName
7208 name = [inName copy];
7212- (void) setShipUniqueName:(NSString *)inName
7214 [shipUniqueName release];
7215 shipUniqueName = [inName copy];
7219- (void) setShipClassName:(NSString *)inName
7221 [shipClassName release];
7222 shipClassName = [inName copy];
7226- (void) setDisplayName:(NSString *)inName
7228 [displayName release];
7229 displayName = [inName copy];
7233- (void) setScanDescription:(NSString *)inName
7235 [scan_description release];
7236 scan_description = [inName copy];
7240- (NSString *) identFromShip:(
ShipEntity*) otherShip
7242 if ([
self isJammingScanning] && ![otherShip hasMilitaryScannerFilter])
7244 return DESC(
@"unknown-target");
7246 return [
self displayName];
7250- (BOOL) hasRole:(NSString *)role
7252 return [roleSet hasRole:role] || [role isEqual:primaryRole] || [role isEqual:[
self shipDataKeyAutoRole]];
7258 if (roleSet ==
nil) roleSet = [[
OORoleSet alloc] initWithRoleString:primaryRole];
7259 return [[roleSet roleSetWithAddedRoleIfNotSet:primaryRole probability:1.0] roleSetWithAddedRoleIfNotSet:[
self shipDataKeyAutoRole] probability:1.0];
7263- (void) addRole:(NSString *)role
7265 [
self addRole:role withProbability:0.0f];
7269- (void) addRole:(NSString *)role withProbability:(
float)probability
7271 if (![
self hasRole:role])
7274 if (roleSet !=
nil) newRoles = [roleSet roleSetWithAddedRole:role probability:probability];
7276 if (newRoles !=
nil)
7279 roleSet = [newRoles retain];
7285- (void) removeRole:(NSString *)role
7287 if ([
self hasRole:role])
7289 OORoleSet *newRoles = [roleSet roleSetWithRemovedRole:role];
7290 if (newRoles !=
nil)
7293 roleSet = [newRoles retain];
7299- (NSString *)primaryRole
7301 if (primaryRole ==
nil)
7303 primaryRole = [roleSet anyRole];
7304 if (primaryRole ==
nil) primaryRole =
@"trader";
7305 [primaryRole retain];
7306 OOLog(
@"ship.noPrimaryRole",
@"%@ had no primary role, randomly selected \"%@\
".", [
self name], primaryRole);
7314- (void)setPrimaryRole:(NSString *)role
7316 if (![role isEqual:primaryRole])
7318 [primaryRole release];
7319 primaryRole = [role copy];
7324- (BOOL)hasPrimaryRole:(NSString *)role
7326 return [[
self primaryRole] isEqual:role];
7333 return [
self scanClass] == CLASS_POLICE;
7338 return [
self scanClass] == CLASS_THARGOID;
7344 return [UNIVERSE role:[
self primaryRole] isInCategory:@"oolite-trader"];
7350 return [UNIVERSE role:[
self primaryRole] isInCategory:@"oolite-pirate"];
7356 return ([[
self primaryRole] hasSuffix:
@"MISSILE"] || [
self hasPrimaryRole:
@"missile"]);
7362 return [[
self primaryRole] hasSuffix:@"MINE"];
7368 return [
self isMissile] || [
self isMine];
7374 return [UNIVERSE role:[
self primaryRole] isInCategory:@"oolite-escort"];
7380 return [UNIVERSE role:[
self primaryRole] isInCategory:@"oolite-shuttle"];
7386 return behaviour == BEHAVIOUR_TRACK_AS_TURRET;
7390- (BOOL)isPirateVictim
7392 return [UNIVERSE roleIsPirateVictim:[
self primaryRole]];
7396- (BOOL)isExplicitlyUnpiloted
7398 return _explicitlyUnpiloted;
7404 return [
self isExplicitlyUnpiloted] || [
self isHulk] || [
self scanClass] == CLASS_ROCK || [
self scanClass] == CLASS_CARGO;
7412 case BEHAVIOUR_ATTACK_TARGET:
7413 case BEHAVIOUR_ATTACK_FLY_TO_TARGET:
7414 case BEHAVIOUR_ATTACK_FLY_FROM_TARGET:
7415 case BEHAVIOUR_RUNNING_DEFENSE:
7416 case BEHAVIOUR_FLEE_TARGET:
7417 case BEHAVIOUR_ATTACK_BREAK_OFF_TARGET:
7418 case BEHAVIOUR_ATTACK_SLOW_DOGFIGHT:
7419 case BEHAVIOUR_EVASIVE_ACTION:
7420 case BEHAVIOUR_FLEE_EVASIVE_ACTION:
7421 case BEHAVIOUR_ATTACK_FLY_TO_TARGET_SIX:
7423 case BEHAVIOUR_ATTACK_FLY_TO_TARGET_TWELVE:
7424 case BEHAVIOUR_ATTACK_BROADSIDE:
7425 case BEHAVIOUR_ATTACK_BROADSIDE_LEFT:
7426 case BEHAVIOUR_ATTACK_BROADSIDE_RIGHT:
7427 case BEHAVIOUR_CLOSE_TO_BROADSIDE_RANGE:
7428 case BEHAVIOUR_CLOSE_WITH_TARGET:
7429 case BEHAVIOUR_ATTACK_SNIPER:
7430 case BEHAVIOUR_SCRIPTED_ATTACK_AI:
7437 return 100 < behaviour && behaviour < 120;
7442- (BOOL) hasHostileTarget
7444 Entity *t = [
self primaryTarget];
7445 if (t ==
nil || ![t isShip])
7449 if ([
self isMissile])
7453 if ((behaviour == BEHAVIOUR_AVOID_COLLISION)&&(previousCondition))
7455 int old_behaviour = [previousCondition oo_intForKey:@"behaviour"];
7456 return IsBehaviourHostile(old_behaviour);
7458 return IsBehaviourHostile(behaviour);
7462- (BOOL) isHostileTo:(
Entity *)entity
7464 return ([
self hasHostileTarget] && [
self primaryTarget] == entity);
7467- (GLfloat) weaponRange
7473- (void) setWeaponRange: (GLfloat) value
7475 weaponRange = value;
7479- (void) setWeaponDataFromType: (
OOWeaponType) weapon_type
7487 if (default_laser_color ==
nil)
7492 [
self setLaserColor:wcol];
7499- (float) energyRechargeRate
7501 return energy_recharge_rate;
7505- (void) setEnergyRechargeRate:(GLfloat)new
7507 energy_recharge_rate =
new;
7511- (float) weaponRechargeRate
7513 return weapon_recharge_rate;
7517- (void) setWeaponRechargeRate:(
float)value
7519 weapon_recharge_rate = value;
7523- (void) setWeaponEnergy:(
float)value
7525 weapon_damage = value;
7531 return currentWeaponFacing;
7535- (GLfloat) scannerRange
7537 return scannerRange;
7541- (void) setScannerRange: (GLfloat) value
7543 scannerRange = value;
7553- (void) setReference:(Vector) v
7559- (BOOL) reportAIMessages
7561 return reportAIMessages;
7565- (void) setReportAIMessages:(BOOL) yn
7567 reportAIMessages = yn;
7571- (void) transitionToAegisNone
7573 if (!suppressAegisMessages && aegis_status !=
AEGIS_NONE)
7576 if (lastAegisLock !=
nil)
7578 [
self doScriptEvent:OOJSID("shipExitedPlanetaryVicinity") withArgument:lastAegisLock];
7580 if (lastAegisLock == [
UNIVERSE sun])
7582 [shipAI message:@"AWAY_FROM_SUN"];
7586 [shipAI message:@"AWAY_FROM_PLANET"];
7592 [shipAI message:@"AEGIS_NONE"];
7601 float centerDistance = HPmagnitude2(HPvector_subtract([stellar position], reference));
7602 float r = [stellar radius];
7606 return centerDistance - 1.35 * r * r;
7612 return SurfaceDistanceSqaredV([reference position], stellar);
7616NSComparisonResult ComparePlanetsBySurfaceDistance(
id i1,
id i2,
void* context)
7622 float p1 = SurfaceDistanceSqaredV(p, e1);
7623 float p2 = SurfaceDistanceSqaredV(p, e2);
7625 if (p1 < p2)
return NSOrderedAscending;
7626 if (p1 > p2)
return NSOrderedDescending;
7628 return NSOrderedSame;
7640 float bestRange = INFINITY;
7641 HPVector myPosition = [
self position];
7646 foreach (planet, [
UNIVERSE planets])
7651 float range = SurfaceDistanceSqaredV(myPosition, planet);
7652 if (range < bestRange)
7654 bestPlanet = planet;
7663- (
Entity<OOStellarBody> *) findNearestStellarBody
7671 SurfaceDistanceSqared(
self, sun) < SurfaceDistanceSqared(
self, match))
7685 NSArray *bodies =
nil;
7686 NSArray *planets =
nil;
7689 bodies = [UNIVERSE planets];
7690 planets = [NSMutableArray arrayWithCapacity:[bodies count]];
7692 for (i=0; i < [bodies count]; i++)
7694 planet = [bodies objectAtIndex:i];
7696 planets = [planets arrayByAddingObject:planet];
7699 if ([planets
count] == 0)
return nil;
7701 planets = [planets sortedArrayUsingFunction:ComparePlanetsBySurfaceDistance context:self];
7702 result = [planets objectAtIndex:0];
7711 BOOL sunGoneNova = [[UNIVERSE sun] goneNova];
7718 [
self transitionToAegisNone];
7723 float cr = [nearest radius];
7724 float cr2 = cr * cr;
7726 float d2 = HPmagnitude2(HPvector_subtract([nearest position], [
self position]));
7731 unsigned wasNearPlanetSurface = isNearPlanetSurface;
7732 isNearPlanetSurface = (d2 - cr2) < (250000.0f + 1000.0f * cr);
7735 if (
EXPECT_NOT((wasNearPlanetSurface != isNearPlanetSurface) && !suppressAegisMessages))
7737 if (isNearPlanetSurface)
7739 [
self doScriptEvent:OOJSID("shipApproachingPlanetSurface") withArgument:nearest];
7740 [shipAI reactToMessage:@"APPROACHING_SURFACE" context:@"flight update"];
7744 [
self doScriptEvent:OOJSID("shipLeavingPlanetSurface") withArgument:nearest];
7745 [shipAI reactToMessage:@"LEAVING_SURFACE" context:@"flight update"];
7753 sd2 = HPmagnitude2(HPvector_subtract([the_station position], [
self position]));
7760 else if (
EXPECT_NOT(isNearPlanetSurface || d2 < cr2 * 9.0f))
7774 d2 = HPmagnitude2(HPvector_subtract([mainPlanet position], [
self position]));
7775 cr2 = [mainPlanet
radius];
7777 if (d2 < cr2 * 9.0f)
7779 nearest = mainPlanet;
7794 if (
EXPECT(!suppressAegisMessages))
7799 [
self doScriptEvent:OOJSID("shipExitedStationAegis") withArgument:the_station];
7800 [shipAI message:@"AEGIS_LEAVING_DOCKING_RANGE"];
7805 [
self doScriptEvent:OOJSID("shipEnteredStationAegis") withArgument:the_station];
7806 [shipAI message:@"AEGIS_IN_DOCKING_RANGE"];
7808 if([
self lastAegisLock] ==
nil && !sunGoneNova)
7810 [
self doScriptEvent:OOJSID("shipEnteredPlanetaryVicinity") withArgument:[UNIVERSE planet]];
7811 [
self setLastAegisLock:[UNIVERSE planet]];
7816 if([
self lastAegisLock] ==
nil && !sunGoneNova)
7818 [
self setLastAegisLock:[UNIVERSE planet]];
7820 [
self transitionToAegisNone];
7825 if(aegis_status !=
AEGIS_NONE && [
self lastAegisLock] !=
nil)
7827 [
self doScriptEvent:OOJSID("shipExitedPlanetaryVicinity") withArgument:[
self lastAegisLock]];
7828 [shipAI message:@"AWAY_FROM_PLANET"];
7830 [
self doScriptEvent:OOJSID("shipEnteredPlanetaryVicinity") withArgument:nearest];
7831 [
self setLastAegisLock:nearest];
7835 [shipAI message:@"CLOSE_TO_SUN"];
7839 [shipAI message:@"CLOSE_TO_PLANET"];
7845 [shipAI message:@"AEGIS_CLOSE_TO_MAIN_PLANET"];
7849 [shipAI message:@"CLOSE_TO_MOON"];
7853 [shipAI message:@"CLOSE_TO_SECONDARY_PLANET"];
7862 [
self setLastAegisLock:nil];
7865 aegis_status = result;
7870- (void) forceAegisCheck
7872 _nextAegisCheck = -1.0f;
7876- (BOOL) withinStationAegis
7887 [_lastAegisLock release];
7888 _lastAegisLock =
nil;
7895- (void) setLastAegisLock:(
Entity<OOStellarBody> *)lastAegisLock
7897 [_lastAegisLock release];
7898 _lastAegisLock = [lastAegisLock weakRetain];
7910 return destination_system;
7922 destination_system = s;
7928 if ([
self status] == stat)
return;
7929 [
super setStatus:stat];
7930 if (stat == STATUS_LAUNCHING)
7932 launch_time = [UNIVERSE getTime];
7936- (void) setLaunchDelay:(
double)delay
7938 launch_delay = delay;
7948- (void) setCrew:(NSArray *)crewArray
7950 if ([
self isExplicitlyUnpiloted])
7960 crew = [crewArray copy];
7964- (void) setSingleCrewWithRole:(NSString *)crewRole
7966 if (![
self isUnpiloted])
7970 [
self setCrew:[NSArray arrayWithObject:crewMember]];
7975- (NSArray *) crewForScripting
7982 NSMutableArray *result = [NSMutableArray arrayWithCapacity:4];
7983 foreach (crewMember, crew)
7986 [result addObject:crewDict];
7993- (void) setStateMachine:(NSString *)smName
7995 [
self setAITo:smName];
7999- (void) setAI:(
AI *)ai
8004 [shipAI clearAllData];
8005 [shipAI autorelease];
8019 return [[
self shipInfoDictionary] oo_fuzzyBooleanForKey:@"auto_ai" defaultValue:YES];
8025 return [[[
self getAI] name] isEqualToString:@"nullAI.plist"];
8029- (BOOL) hasAutoWeapons
8031 return [[
self shipInfoDictionary] oo_fuzzyBooleanForKey:@"auto_weapons" defaultValue:NO];
8035- (void) setShipScript:(NSString *)script_name
8037 NSMutableDictionary *properties =
nil;
8038 NSArray *actions =
nil;
8040 properties = [NSMutableDictionary dictionary];
8041 [properties setObject:self forKey:@"ship"];
8043 [script autorelease];
8048 actions = [shipinfoDictionary oo_arrayForKey:@"launch_actions"];
8051 OOStandardsDeprecated([NSString stringWithFormat:
@"The launch_actions ship key is deprecated on %@.",[
self displayName]]);
8054 [properties setObject:actions forKey:@"legacy_launchActions"];
8058 actions = [shipinfoDictionary oo_arrayForKey:@"script_actions"];
8061 OOStandardsDeprecated([NSString stringWithFormat:
@"The script_actions ship key is deprecated on %@.",[
self displayName]]);
8064 [properties setObject:actions forKey:@"legacy_scriptActions"];
8068 actions = [shipinfoDictionary oo_arrayForKey:@"death_actions"];
8071 OOStandardsDeprecated([NSString stringWithFormat:
@"The death_actions ship key is deprecated on %@.",[
self displayName]]);
8074 [properties setObject:actions forKey:@"legacy_deathActions"];
8078 actions = [shipinfoDictionary oo_arrayForKey:@"setup_actions"];
8081 OOStandardsDeprecated([NSString stringWithFormat:
@"The setup_actions ship key is deprecated on %@.",[
self displayName]]);
8084 [properties setObject:actions forKey:@"legacy_setupActions"];
8095- (double)frustration
8109 if (amount > [
self fuelCapacity]) amount = [
self fuelCapacity];
8122- (GLfloat) fuelChargeRate
8126#if MASS_DEPENDENT_FUEL_PRICES
8130 rate = calcFuelChargeRate(mass);
8133 OOLog(
@"fuelPrices",
@"\"%@\
" fuel charge rate: %.2f (mass ratio: %.2f/%.2f)", [
self shipDataKey], rate, mass, [
PLAYER baseMass]);
8140- (void) applySticks:(
double)delta_t
8143 double rate1 = 2.0 * delta_t;
8144 double rate2 = 4.0 * delta_t;
8145 double rate3 = 4.0 * delta_t;
8147 if (((stick_roll > 0.0)&&(flightRoll < 0.0))||((stick_roll < 0.0)&&(flightRoll > 0.0)))
8149 if (((stick_pitch > 0.0)&&(flightPitch < 0.0))||((stick_pitch < 0.0)&&(flightPitch > 0.0)))
8151 if (((stick_yaw > 0.0)&&(flightYaw < 0.0))||((stick_yaw < 0.0)&&(flightYaw > 0.0)))
8156 if (stick_roll == 0.0)
8158 if (stick_pitch == 0.0)
8160 if (stick_yaw == 0.0)
8165 if (flightRoll < stick_roll - rate1)
8167 flightRoll = flightRoll + rate1;
8169 else if (flightRoll > stick_roll + rate1)
8171 flightRoll = flightRoll - rate1;
8175 flightRoll = stick_roll;
8178 if (flightPitch < stick_pitch - rate2)
8180 flightPitch = flightPitch + rate2;
8182 else if (flightPitch > stick_pitch + rate2)
8184 flightPitch = flightPitch - rate2;
8188 flightPitch = stick_pitch;
8191 if (flightYaw < stick_yaw - rate3)
8193 flightYaw = flightYaw + rate3;
8195 else if (flightYaw > stick_yaw + rate3)
8197 flightYaw = flightYaw - rate3;
8201 flightYaw = stick_yaw;
8207- (void) setRoll:(
double) amount
8209 flightRoll = amount *
M_PI / 2.0;
8213- (void) setRawRoll:(
double) amount
8215 flightRoll = amount;
8219- (void) setPitch:(
double) amount
8221 flightPitch = amount *
M_PI / 2.0;
8225- (void) setYaw:(
double) amount
8227 flightYaw = amount *
M_PI / 2.0;
8231- (void) setThrust:(
double) amount
8237- (void) setThrustForDemo:(
float) factor
8239 flightSpeed = factor * maxFlightSpeed;
8245 [
self setBounty:amount withReason:kOOLegalStatusReasonUnknown];
8251 if ([
self isSubEntity])
8253 [[
self parentEntity] setBounty:amount withReason:reason];
8257 if ((scanClass == CLASS_THARGOID || scanClass == CLASS_STATION) && reason != kOOLegalStatusReasonSetup && reason != kOOLegalStatusReasonByScript)
8261 if (scanClass == CLASS_POLICE && amount != 0)
8266 [
self setBounty:amount withReasonAsString:nReason];
8270- (void) setBounty:(
OOCreditsQuantity) amount withReasonAsString:(NSString*)reason
8272 if ([
self isSubEntity])
8274 [[
self parentEntity] setBounty:amount withReasonAsString:reason];
8280 jsval amountVal = JSVAL_VOID;
8281 JS_NewNumberValue(context, (
int)amount-(
int)bounty, &amountVal);
8287 ShipScriptEvent(context,
self,
"shipBountyChanged", amountVal, reasonVal);
8298 if ([
self isSubEntity])
8300 return [[
self parentEntity] bounty];
8311 if (scanClass == CLASS_THARGOID)
8312 return 5 * collision_radius;
8313 if (scanClass == CLASS_ROCK)
8315 return (
int)[
self bounty];
8326 NSString *tmp = [co_type copy];
8328 commodity_type = tmp;
8329 commodity_amount = co_amount;
8340 commodity_amount = 0;
8345 OOMassUnit unit = [[UNIVERSE commodityMarket] massUnitForGood:co_type];
8346 if (unit ==
UNITS_TONS && co_amount > 1) co_amount = 1;
8347 else if (unit ==
UNITS_KILOGRAMS && co_amount > 1000) co_amount = 1000;
8348 else if (unit ==
UNITS_GRAMS && co_amount > 1000000) co_amount = 1000000;
8349 [
self setCommodity:co_type andAmount:co_amount];
8355 return commodity_type;
8361 return commodity_amount;
8367 return max_cargo - equipment_weight;
8373 max_cargo =
new + equipment_weight;
8380 if (
EXPECT_NOT([
self cargoQuantityOnBoard] + equipment_weight >= max_cargo))
return 0;
8381 return [
self maxAvailableCargoSpace] - [
self cargoQuantityOnBoard];
8387 NSUInteger result = [[
self cargo] count];
8388 NSAssert(result < UINT32_MAX,
@"Cargo quantity out of bounds.");
8402- (NSMutableArray*) cargo
8408- (NSArray *) cargoListForScripting
8410 NSMutableArray *list = [NSMutableArray array];
8413 NSArray *goods = [[UNIVERSE commodityMarket] goods];
8414 NSUInteger i, j, commodityCount = [goods count];
8417 for (i = 0; i < commodityCount; i++)
8419 quantityInHold[i] = 0;
8421 for (i = 0; i < [cargo count]; i++)
8423 ShipEntity *container = [cargo objectAtIndex:i];
8425 quantityInHold[j] += [container commodityAmount];
8428 for (i = 0; i < commodityCount; i++)
8430 if (quantityInHold[i] > 0)
8432 NSMutableDictionary *commodity = [NSMutableDictionary dictionaryWithCapacity:4];
8433 good = [goods objectAtIndex:i];
8435 [commodity setObject:good forKey:@"commodity"];
8436 [commodity setObject:[NSNumber numberWithUnsignedInt:quantityInHold[i]] forKey:@"quantity"];
8437 [commodity setObject:[[UNIVERSE commodityMarket] nameForGood:good] forKey:@"displayName"];
8438 [commodity setObject:DisplayStringForMassUnitForCommodity(good) forKey:@"unit"];
8439 [list addObject:commodity];
8443 return [[list copy] autorelease];
8446- (void) setCargo:(NSArray *) some_cargo
8448 [cargo removeAllObjects];
8449 [cargo addObjectsFromArray:some_cargo];
8453- (BOOL) addCargo:(NSArray *) some_cargo
8455 if ([cargo
count] + [some_cargo
count] > [
self maxAvailableCargoSpace])
8461 [cargo addObjectsFromArray:some_cargo];
8471 foreach (pod, cargo)
8473 if ([[pod commodityType] isEqualToString:commodity])
8484 NSUInteger i = [cargo count] - 1;
8488 if ([[[cargo objectAtIndex:i] commodityType] isEqualToString:commodity])
8491 [cargo removeObjectAtIndex:i];
8502- (BOOL) showScoopMessage
8504 return hasScoopMessage;
8516 if (cargo_flag != flag)
8519 NSArray *newCargo =
nil;
8521 if (likely_cargo > 0)
8523 num = likely_cargo * (0.5+
randf());
8524 if (num > [
self maxAvailableCargoSpace])
8526 num = [
self maxAvailableCargoSpace];
8531 num = [
self maxAvailableCargoSpace];
8546 newCargo = [UNIVERSE getContainersOfCommodity:[shipinfoDictionary oo_stringForKey:@"cargo_carried"] :num];
8549 newCargo = [UNIVERSE getContainersOfGoods:num scarce:NO legal:YES];
8552 newCargo = [UNIVERSE getContainersOfGoods:num scarce:YES legal:YES];
8555 newCargo = [UNIVERSE getContainersOfCommodity:@"Narcotics" :num];
8558 newCargo = [UNIVERSE getContainersOfGoods:num scarce:YES legal:NO];
8561 newCargo = [UNIVERSE getContainersOfGoods:(Ranrot() % (1+num/2)) scarce:YES legal:NO];
8570 [
self setCargo:newCargo];
8575- (void) setSpeed:(
double) amount
8577 flightSpeed = amount;
8581- (void) setDesiredSpeed:(
double) amount
8583 desired_speed = amount;
8587- (double) desiredSpeed
8589 return desired_speed;
8593- (double) desiredRange
8595 return desired_range;
8599- (void) setDesiredRange:(
double) amount
8601 desired_range = amount;
8605- (double) cruiseSpeed
8611- (void) increase_flight_speed:(
double) delta
8613 double factor = 1.0;
8614 if (desired_speed > maxFlightSpeed && [
self hasFuelInjection] && fuel >
MIN_FUEL) factor = [
self afterburnerFactor];
8616 if (flightSpeed < maxFlightSpeed * factor)
8617 flightSpeed += delta * factor;
8619 flightSpeed = maxFlightSpeed * factor;
8623- (void) decrease_flight_speed:(
double) delta
8625 double factor = 1.0;
8626 if (flightSpeed > maxFlightSpeed)
8631 if (flightSpeed > factor * delta)
8633 flightSpeed -= factor * delta;
8642- (void) increase_flight_roll:(
double) delta
8644 flightRoll += delta;
8645 if (flightRoll > max_flight_roll)
8646 flightRoll = max_flight_roll;
8647 else if (flightRoll < -max_flight_roll)
8648 flightRoll = -max_flight_roll;
8652- (void) decrease_flight_roll:(
double) delta
8654 flightRoll -= delta;
8655 if (flightRoll > max_flight_roll)
8656 flightRoll = max_flight_roll;
8657 else if (flightRoll < -max_flight_roll)
8658 flightRoll = -max_flight_roll;
8662- (void) increase_flight_pitch:(
double) delta
8664 flightPitch += delta;
8665 if (flightPitch > max_flight_pitch)
8666 flightPitch = max_flight_pitch;
8667 else if (flightPitch < -max_flight_pitch)
8668 flightPitch = -max_flight_pitch;
8672- (void) decrease_flight_pitch:(
double) delta
8674 flightPitch -= delta;
8675 if (flightPitch > max_flight_pitch)
8676 flightPitch = max_flight_pitch;
8677 else if (flightPitch < -max_flight_pitch)
8678 flightPitch = -max_flight_pitch;
8682- (void) increase_flight_yaw:(
double) delta
8685 if (flightYaw > max_flight_yaw)
8686 flightYaw = max_flight_yaw;
8687 else if (flightYaw < -max_flight_yaw)
8688 flightYaw = -max_flight_yaw;
8692- (void) decrease_flight_yaw:(
double) delta
8695 if (flightYaw > max_flight_yaw)
8696 flightYaw = max_flight_yaw;
8697 else if (flightYaw < -max_flight_yaw)
8698 flightYaw = -max_flight_yaw;
8702- (GLfloat) flightRoll
8708- (GLfloat) flightPitch
8714- (GLfloat) flightYaw
8720- (GLfloat) flightSpeed
8726- (GLfloat) maxFlightPitch
8728 return max_flight_pitch;
8732- (GLfloat) maxFlightSpeed
8734 return maxFlightSpeed;
8738- (GLfloat) maxFlightRoll
8740 return max_flight_roll;
8744- (GLfloat) maxFlightYaw
8746 return max_flight_yaw;
8750- (void) setMaxFlightPitch:(GLfloat)new
8752 max_flight_pitch =
new;
8756- (void) setMaxFlightSpeed:(GLfloat)new
8758 maxFlightSpeed =
new;
8762- (void) setMaxFlightRoll:(GLfloat)new
8764 max_flight_roll =
new;
8768- (void) setMaxFlightYaw:(GLfloat)new
8770 max_flight_yaw =
new;
8774- (GLfloat) speedFactor
8776 if (maxFlightSpeed <= 0.0)
return 0.0;
8777 return flightSpeed / maxFlightSpeed;
8781- (GLfloat) temperature
8783 return ship_temperature;
8787- (void) setTemperature:(GLfloat) value
8789 ship_temperature = value;
8793- (float) randomEjectaTemperature
8795 return [
self randomEjectaTemperatureWithMaxFactor:0.99f];
8799- (float) randomEjectaTemperatureWithMaxFactor:(
float)factor
8801 const float kRange = 0.02f;
8804 float parentTemp = [
self temperature];
8805 float adjusted = parentTemp * (
bellf(5) * (kRange * 2.0f) - kRange + factor);
8818- (GLfloat) heatInsulation
8820 return _heatInsulation;
8824- (void) setHeatInsulation:(GLfloat) value
8826 _heatInsulation = value;
8832 return (
int)(100 - (100 * energy / maxEnergy));
8836- (void) dealEnergyDamage:(GLfloat) baseDamage atRange:(GLfloat) range withBias:(GLfloat) velocityBias
8841 OOLog(
@"missile.damage.calc",
@"Range: %f | Damage: %f | MaxRange: %f",range,baseDamage,maxRange);
8843 NSArray *targets = [UNIVERSE entitiesWithinRange:maxRange ofEntity:self];
8844 if ([targets
count] > 0)
8847 for (i = 0; i < [targets count]; i++)
8849 Entity *e2 = [targets objectAtIndex:i];
8850 Vector p2 = [
self vectorTo:e2];
8852 double d = (magnitude(p2) - ecr) / range;
8854 double localDamage = baseDamage;
8855 OOLog(
@"missile.damage.calc",
@"Base damage: %f",baseDamage);
8856 if (velocityBias > 0)
8858 Vector v2 = vector_subtract([
self velocity], [e2 velocity]);
8859 double vSign = dot_product(vector_normal([
self velocity]), vector_normal(p2));
8864 double vMag = vSign * magnitude(v2);
8865 if (vMag > 1000.0) {
8871 localDamage += vMag * velocityBias;
8872 OOLog(
@"missile.damage.calc",
@"Velocity magnitude + sign: %f , %f",magnitude(v2),vSign);
8873 OOLog(
@"missile.damage.calc",
@"Velocity magnitude factor: %f",vMag);
8874 OOLog(
@"missile.damage.calc",
@"Velocity corrected damage: %f",localDamage);
8876 double damage = (d > 1) ? localDamage / (d * d) : localDamage;
8877 OOLog(
@"missile.damage.calc",
@"%f at range %f (d=%f)",damage,magnitude(p2)-ecr,d);
8901 NSDictionary *explosion = [UNIVERSE explosionSetting:@"oolite-default-ship-explosion"];
8909- (void) dealEnergyDamageWithinDesiredRange
8913 NSArray* targets = [UNIVERSE entitiesWithinRange:(desired_range < SCANNER_MAX_RANGE ? desired_range : SCANNER_MAX_RANGE) ofEntity:self];
8914 if ([targets
count] > 0)
8917 for (i = 0; i < [targets count]; i++)
8919 Entity *e2 = [targets objectAtIndex:i];
8920 Vector p2 = [
self vectorTo:e2];
8922 double d = (magnitude(p2) - ecr) * 2.6;
8923 double damage = (d > 0) ? weapon_damage * desired_range / (d * d) : weapon_damage;
8930- (void) dealMomentumWithinDesiredRange:(
double)amount
8932 NSArray* targets = [UNIVERSE entitiesWithinRange:desired_range ofEntity:self];
8933 if ([targets
count] > 0)
8936 for (i = 0; i < [targets count]; i++)
8939 if ([e2 isShip] && [e2 isInSpace])
8941 Vector p2 = [
self vectorTo:e2];
8943 double d2 = magnitude2(p2) - ecr * ecr;
8949 double moment = amount*desired_range/d2;
8963- (void) setHulk:(BOOL)isNowHulk
8965 if (![
self isSubEntity])
8974 if (amount < 0 || (amount == 0 && [[
UNIVERSE gameController] isGamePaused]))
return;
8978 jsval amountVal = JSVAL_VOID;
8979 JS_NewNumberValue(context, amount, &amountVal);
8983 ShipScriptEvent(context,
self,
"shipTakingDamage", amountVal, entityVal, typeVal);
8986 if ([entity isShip]) {
8988 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)
8990 if (behaviour == BEHAVIOUR_FLEE_TARGET)
8994 behaviour = BEHAVIOUR_FLEE_EVASIVE_ACTION;
8998 behaviour = BEHAVIOUR_EVASIVE_ACTION;
9009 if ([
self status] == STATUS_DEAD)
return;
9011 [PLAYER setScriptTarget:self];
9018 [
self setStatus:STATUS_DEAD];
9027 [
self setStatus:originalStatus];
9034 [
self noteKilledBy:whom damageType:type];
9035 [
self abortDocking];
9036 [
self becomeExplosion];
9040- (void) rescaleBy:(GLfloat)factor
9042 [
self rescaleBy:factor writeToCache:YES];
9046- (void) rescaleBy:(GLfloat)factor writeToCache:(BOOL)writeToCache
9048 _scaleFactor *= factor;
9051 NSDictionary *shipDict = [
self shipInfoDictionary];
9052 NSString *modelName = [shipDict oo_stringForKey:@"model"];
9053 if (modelName !=
nil)
9056 cacheKey:[NSString stringWithFormat:@"%@-%.3f",_shipKey,_scaleFactor]
9059 smooth:[shipDict oo_boolForKey:@"smooth" defaultValue:NO]
9065 if (mesh ==
nil)
return;
9066 [
self setMesh:mesh];
9071 foreach (se, [
self subEntities])
9074 [se rescaleBy:factor writeToCache:writeToCache];
9078 mass *= factor * factor * factor;
9082- (void) releaseCargoPodsDebris
9084 HPVector xposition = position;
9088 int speed_low = 200;
9090 NSArray *jetsam =
nil;
9091 unsigned cargo_chance = 70;
9092 jetsam = [NSArray arrayWithArray:cargo];
9093 [cargo removeAllObjects];
9094 unsigned limit = 15;
9096 NSUInteger n_jetsam = [jetsam count];
9098 for (i = 0; i < n_jetsam; i++)
9100 if (
Ranrot() % 100 < cargo_chance)
9103 if (cargo_chance > 10)
9115 ShipEntity* cargoObj = [jetsam objectAtIndex:i];
9116 ShipEntity* container = [UNIVERSE reifyCargoPod:cargoObj];
9120 HPVector rpos = xposition;
9122 rpos.x += rrand.
x; rpos.y += rrand.
y; rpos.z += rrand.z;
9127 v.x = 0.1 *((
ranrot_rand() % speed_low) - speed_low / 2);
9128 v.y = 0.1 *((
ranrot_rand() % speed_low) - speed_low / 2);
9129 v.z = 0.1 *((
ranrot_rand() % speed_low) - speed_low / 2);
9130 [container
setVelocity:vector_add(v,[
self velocity])];
9136 [UNIVERSE addEntity:container];
9138 AI *containerAI = [container
getAI];
9139 if ([containerAI hasSuspendedStateMachines])
9155- (void) setIsWreckage:(BOOL)isw
9167- (void) becomeExplosion
9175 HPVector this_pos = [
self absolutePositionForSubentity];
9179 [UNIVERSE addEntity:this_ship];
9181 [this_ship release];
9182 if ([parent isPlayer])
9185 [(
PlayerEntity *)
parent adjustTradeInFactorBy:-PLAYER_SHIP_SUBENTITY_TRADE_IN_VALUE];
9189 HPVector xposition = position;
9193 int speed_low = 200;
9194 GLfloat n_alloys = sqrtf(sqrtf(mass / 6000.0f));
9195 NSUInteger numAlloys = 0;
9196 BOOL canReleaseSubWreckage = isWreckage && ([UNIVERSE detailLevel] >=
DETAIL_LEVEL_EXTRAS);
9198 if ([
self status] == STATUS_DEAD)
9200 [UNIVERSE removeEntity:self];
9203 [
self setStatus:STATUS_DEAD];
9207 if ([
self isThargoid] && [roleSet hasRole:
@"thargoid-mothership"]) [
self broadcastThargoidDestroyed];
9209 if (!suppressExplosion && ([
self isVisible] || HPdistance2([
self position], [
PLAYER position]) <
SCANNER_MAX_RANGE2))
9211 if (!isWreckage && mass > 500000.0f &&
randf() < 0.25f)
9215 [ring
setVelocity:vector_multiply_scalar([
self velocity], 0.25f)];
9216 [UNIVERSE addEntity:ring];
9244 NSString *explosionKey =
@"oolite-default-ship-explosion";
9245 NSDictionary *explosion =
nil;
9246 if (explosionType ==
nil)
9248 explosion = [UNIVERSE explosionSetting:explosionKey];
9253 for (NSUInteger i=0;i<[explosionType count];i++)
9255 explosionKey = [explosionType oo_stringAtIndex:i defaultValue:nil];
9256 if (explosionKey !=
nil)
9259 if ([explosionKey isEqualToString:
@"oolite-builtin-flash"])
9263 else if ([explosionKey isEqualToString:
@"oolite-builtin-slowcloud"])
9267 else if ([explosionKey isEqualToString:
@"oolite-builtin-fastspark"])
9273 explosion = [UNIVERSE explosionSetting:explosionKey];
9287 [
self releaseCargoPodsDebris];
9290 if ([
self hasRole:
@"asteroid"] || [
self isBoulder])
9292 if (!noRocks && (being_mined ||
randf() < 0.20))
9294 NSString *defaultRole =
@"boulder";
9295 float defaultSpeed = 50.0;
9296 if ([
self isBoulder])
9298 defaultRole =
@"splinter";
9299 defaultSpeed = 20.0;
9300 if (likely_cargo == 0)
9305 else if ([[
self primaryAggressor] isPlayer])
9307 [PLAYER addRoleForMining];
9309 NSUInteger n_rocks = 2 + (
Ranrot() % (likely_cargo + 1));
9311 NSString *debrisRole = [[
self shipInfoDictionary] oo_stringForKey:@"debris_role" defaultValue:defaultRole];
9312 for (i = 0; i < n_rocks; i++)
9314 ShipEntity* rock = [UNIVERSE newShipWithRole:debrisRole];
9318 float cr = (collision_radius < rock->
collision_radius) ? collision_radius : 2 * rock->collision_radius;
9319 v.x = ((
randf() * r_speed) - r_speed / 2);
9320 v.y = ((
randf() * r_speed) - r_speed / 2);
9321 v.z = ((
randf() * r_speed) - r_speed / 2);
9323 HPVector rpos = HPvector_add(xposition,vectorToHPVector(vector_multiply_scalar(vector_normal(v),cr)));
9330 if ([
self isBoulder])
9335 if ([[rock shipInfoDictionary] oo_stringForKey:
@"cargo_carried"] ==
nil)
9336 [rock setCommodity:
@"minerals" andAmount: 1];
9343 [UNIVERSE addEntity:rock];
9353 if ((n_alloys && canFragment) || canReleaseSubWreckage)
9355 NSUInteger n_wreckage = 0;
9361 NSUInteger maxWrecks = 3;
9365 n_wreckage = (mass > 600.0 &&
randf() < 0.2)?2:0;
9369 n_wreckage = (n_alloys < maxWrecks)? floorf(
randf()*(n_alloys+2)) : maxWrecks;
9373 for (i = 0; i < n_wreckage; i++)
9375 Vector r1 = [octree randomPoint];
9377 HPVector rpos = HPvector_add(vectorToHPVector(dir), xposition);
9378 GLfloat lifetime = 750.0 *
randf() + 250.0 * i + 100.0;
9379 ShipEntity *wreck = [UNIVERSE addWreckageFrom:self withRole:@"wreckage" at:rpos scale:1.0 lifetime:lifetime/2];
9384 n_alloys =
randf() * n_alloys;
9393 else if (!add_debris)
9395 n_alloys = (n_alloys > 1.0) ? 1.0 : 0.0;
9399 numAlloys = floorf(n_alloys);
9403 for (i = 0; i < numAlloys; i++)
9405 ShipEntity* plate = [UNIVERSE newShipWithRole:@"alloy"];
9408 HPVector rpos = xposition;
9410 rpos.x += rrand.
x; rpos.y += rrand.
y; rpos.z += rrand.z;
9415 v.x = 0.1 *((
ranrot_rand() % speed_low) - speed_low / 2);
9416 v.y = 0.1 *((
ranrot_rand() % speed_low) - speed_low / 2);
9417 v.z = 0.1 *((
ranrot_rand() % speed_low) - speed_low / 2);
9418 [plate
setVelocity:vector_add(v,[
self velocity])];
9425 [UNIVERSE addEntity:plate];
9434 foreach (se, [
self shipSubEntityEnumerator])
9439 [
self clearSubEntities];
9442 if (!suppressExplosion)
9444 desired_range = collision_radius * 2.5f;
9445 [
self dealMomentumWithinDesiredRange:0.125f * mass];
9453 OOLog(
@"becomeExplosion.suspectedGhost.confirm",
@"%@",
@"Ship spotted with isPlayer set when not actually the player.");
9463 [UNIVERSE removeEntity:self];
9470- (void) becomeEnergyBlast
9473 [
self broadcastEnergyBlastImminent];
9474 [
self noteKilledBy:nil damageType:kOODamageTypeCascadeWeapon];
9475 [UNIVERSE removeEntity:self];
9480- (void) broadcastEnergyBlastImminent
9483 NSArray* targets = [UNIVERSE entitiesWithinRange:SCANNER_MAX_RANGE ofEntity:self];
9484 if ([targets
count] > 0)
9487 for (i = 0; i < [targets count]; i++)
9489 Entity *e2 = [targets objectAtIndex:i];
9504 [subEntities removeObject:exhaust];
9511 [subEntities removeObject:flasher];
9518 if ([
self subEntityTakingDamage] == sub) [
self setSubEntityTakingDamage:nil];
9524 [subEntities removeObject:sub];
9530 if ([
self subEntityTakingDamage] == sub) [
self setSubEntityTakingDamage:nil];
9532 if ([
self hasSubEntity:sub])
9534 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.");
9542- (Vector) positionOffsetForAlignment:(NSString*) align
9544 NSString* padAlign = [NSString stringWithFormat:@"%@---", align];
9546 switch ([padAlign characterAtIndex:0])
9550 result.x = 0.5 * (boundingBox.min.x + boundingBox.max.
x);
9553 result.x = boundingBox.max.x;
9556 result.x = boundingBox.min.x;
9559 switch ([padAlign characterAtIndex:1])
9563 result.y = 0.5 * (boundingBox.min.y + boundingBox.max.
y);
9566 result.y = boundingBox.max.y;
9569 result.y = boundingBox.min.y;
9572 switch ([padAlign characterAtIndex:2])
9576 result.z = 0.5 * (boundingBox.min.z + boundingBox.max.z);
9579 result.z = boundingBox.max.z;
9582 result.z = boundingBox.min.z;
9589Vector positionOffsetForShipInRotationToAlignment(
ShipEntity* ship, Quaternion q, NSString* align)
9591 NSString* padAlign = [NSString stringWithFormat:@"%@---", align];
9597 switch ([padAlign characterAtIndex:0])
9601 result.x = 0.5 * (arbb.min.x + arbb.max.x);
9604 result.x = arbb.max.x;
9607 result.x = arbb.min.x;
9610 switch ([padAlign characterAtIndex:1])
9614 result.y = 0.5 * (arbb.min.y + arbb.max.y);
9617 result.y = arbb.max.y;
9620 result.y = arbb.min.y;
9623 switch ([padAlign characterAtIndex:2])
9627 result.z = 0.5 * (arbb.min.z + arbb.max.z);
9630 result.z = arbb.max.z;
9633 result.z = arbb.min.z;
9640- (void) becomeLargeExplosion:(
double)factor
9643 if ([
self status] == STATUS_DEAD)
return;
9644 [
self setStatus:STATUS_DEAD];
9650 float how_many = factor;
9651 while (how_many > 0.5f)
9658 while (how_many > 0.5f)
9664 [
self releaseCargoPodsDebris];
9667 foreach (se, [
self shipSubEntityEnumerator])
9672 [
self clearSubEntities];
9677 if (!isPlayer) [UNIVERSE removeEntity:self];
9684 if ([other isPolice])
9686 [
self markAsOffender:64 withReason:kOOLegalStatusReasonAttackedPolice];
9691- (NSComparisonResult) compareBeaconCodeWith:(
Entity<OOBeaconEntity> *) other
9693 return [[
self beaconCode] compare:[other
beaconCode] options: NSCaseInsensitiveSearch];
9698- (GLfloat) weaponRecoveryTime
9700 float result = (weapon_recharge_rate - [
self shotTime]) / weapon_recharge_rate;
9701 return OOClamp_0_1_f(result);
9705- (GLfloat)laserHeatLevel
9708 return OOClamp_0_1_f(result);
9712- (GLfloat)laserHeatLevelAft
9715 return OOClamp_0_1_f(result);
9719- (GLfloat)laserHeatLevelForward
9725 NSEnumerator *subEnum = [
self shipSubEntityEnumerator];
9727 while (
isWeaponNone(forward_weapon_real_type) && (se = [subEnum nextObject]))
9736 return OOClamp_0_1_f(result);
9740- (GLfloat)laserHeatLevelPort
9743 return OOClamp_0_1_f(result);
9747- (GLfloat)laserHeatLevelStarboard
9750 return OOClamp_0_1_f(result);
9754- (GLfloat)hullHeatLevel
9757 return OOClamp_0_1_f(result);
9761- (GLfloat)entityPersonality
9767- (GLint)entityPersonalityInt
9769 return entity_personality;
9773- (uint32_t) randomSeedForShaders
9775 return entity_personality * 0x00010001;
9779- (void) setEntityPersonalityInt:(uint16_t)value
9783 entity_personality = value;
9784 [[
self mesh] rebindMaterials];
9789- (void)setSuppressExplosion:(BOOL)suppress
9791 suppressExplosion = !!suppress;
9795- (void) resetExhaustPlumes
9799 foreach (exEnt, [
self exhaustEnumerator])
9813- (void) checkScanner
9816 n_scanned_ships = 0;
9818 scan = z_previous;
while ((scan)&&(scan->
isShip == NO)) scan = scan->
z_previous;
9819 GLfloat scannerRange2 = scannerRange * scannerRange;
9823 if (scan->
isShip && ![(
ShipEntity*)scan isCloaked] && [
self isValidTarget:scan])
9825 distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->
position);
9826 if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
9827 scanned_ships[n_scanned_ships++] = (
ShipEntity*)scan;
9832 scan = z_next;
while ((scan)&&(scan->
isShip == NO)) scan = scan->
z_next;
9835 if (scan->
isShip && ![(
ShipEntity*)scan isCloaked] && [
self isValidTarget:scan])
9837 distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->
position);
9838 if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
9839 scanned_ships[n_scanned_ships++] = (
ShipEntity*)scan;
9844 scanned_ships[n_scanned_ships] =
nil;
9848- (void) checkScannerIgnoringUnpowered
9851 n_scanned_ships = 0;
9853 GLfloat scannerRange2 = scannerRange * scannerRange;
9863 distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->
position);
9864 if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
9865 scanned_ships[n_scanned_ships++] = (
ShipEntity*)scan;
9884 distance2_scanned_ships[n_scanned_ships] = HPdistance2(position, scan->
position);
9885 if (distance2_scanned_ships[n_scanned_ships] < scannerRange2)
9886 scanned_ships[n_scanned_ships++] = (
ShipEntity*)scan;
9895 scanned_ships[n_scanned_ships] =
nil;
9901 scanned_ships[n_scanned_ships] =
nil;
9902 return scanned_ships;
9906- (
int) numberOfScannedShips
9908 return n_scanned_ships;
9914 Entity *result = [_foundTarget weakRefUnderlyingObject];
9915 if (result ==
nil || ![
self isValidTarget:result])
9924- (void) setFoundTarget:(
Entity *) targetEntity
9926 [_foundTarget release];
9931- (
Entity *) primaryAggressor
9933 Entity *result = [_primaryAggressor weakRefUnderlyingObject];
9934 if (result ==
nil || ![
self isValidTarget:result])
9943- (void) setPrimaryAggressor:(
Entity *) targetEntity
9945 [_primaryAggressor release];
9946 _primaryAggressor = [targetEntity
weakRetain];
9950- (
Entity *) lastEscortTarget
9952 Entity *result = [_lastEscortTarget weakRefUnderlyingObject];
9953 if (result ==
nil || ![
self isValidTarget:result])
9962- (void) setLastEscortTarget:(
Entity *) targetEntity
9964 [_lastEscortTarget release];
9965 _lastEscortTarget = [targetEntity
weakRetain];
9971 Entity *result = [_thankedShip weakRefUnderlyingObject];
9972 if (result ==
nil || ![
self isValidTarget:result])
9981- (void) setThankedShip:(
Entity *) targetEntity
9983 [_thankedShip release];
9988- (
Entity *) rememberedShip
9990 Entity *result = [_rememberedShip weakRefUnderlyingObject];
9991 if (result ==
nil || ![
self isValidTarget:result])
10000- (void) setRememberedShip:(
Entity *) targetEntity
10002 [_rememberedShip release];
10003 _rememberedShip = [targetEntity
weakRetain];
10009 StationEntity *result = [_targetStation weakRefUnderlyingObject];
10010 if (result ==
nil || ![
self isValidTarget:result])
10019- (void) setTargetStation:(
Entity *) targetEntity
10021 [_targetStation release];
10028- (BOOL) isValidTarget:(
Entity *)target
10034 if ([target isShip])
10037 if (tstatus == STATUS_ENTERING_WITCHSPACE || tstatus == STATUS_IN_HOLD || tstatus == STATUS_DOCKED || tstatus == STATUS_DEAD)
10044 if ([target isWormhole] && [target scanClass] != CLASS_NO_DRAW)
10052- (void) addTarget:(
Entity *) targetEntity
10054 if (targetEntity ==
self)
return;
10055 if (targetEntity !=
nil)
10059 [
self startTrackingCurve];
10062 [[
self shipSubEntityEnumerator] makeObjectsPerformSelector:@selector(addTarget:) withObject:targetEntity];
10063 if (![
self isSubEntity]) [
self doScriptEvent:OOJSID("shipTargetAcquired") withArgument:targetEntity];
10067- (void) removeTarget:(
Entity *) targetEntity
10069 if(targetEntity !=
nil) [
self noteLostTarget];
10070 else DESTROY(_primaryTarget);
10074 [[
self shipSubEntityEnumerator] makeObjectsPerformSelector:@selector(removeTarget:) withObject:targetEntity];
10089- (BOOL) canStillTrackPrimaryTarget
10091 Entity *target = (
Entity *)[
self primaryTargetWithoutValidityCheck];
10096 if (![
self isValidTarget:target])
10100 double range2 = HPmagnitude2(HPvector_subtract([target position], position));
10101 if (range2 > scannerRange * scannerRange * 1.5625)
10115- (id) primaryTarget
10117 id result = [_primaryTarget weakRefUnderlyingObject];
10118 if ((result ==
nil && _primaryTarget !=
nil)
10119 || ![
self isValidTarget:result])
10138- (id) primaryTargetWithoutValidityCheck
10140 id result = [_primaryTarget weakRefUnderlyingObject];
10151- (BOOL) isFriendlyTo:(
ShipEntity *)otherShip
10153 BOOL isFriendly = NO;
10157 if ((otherShip ==
self) ||
10158 ([
self isPolice] && [otherShip isPolice]) ||
10159 ([
self isThargoid] && [otherShip isThargoid]) ||
10160 (myGroup !=
nil && otherGroup !=
nil && (myGroup == otherGroup || [otherGroup leader] ==
self)) ||
10161 ([
self scanClass] == CLASS_MILITARY && [otherShip scanClass] == CLASS_MILITARY))
10172 return [_shipHitByLaser weakRefUnderlyingObject];
10176- (void) setShipHitByLaser:(
ShipEntity *)ship
10178 if (ship != [
self shipHitByLaser])
10180 [_shipHitByLaser release];
10186- (void) noteLostTarget
10189 if ([
self primaryTarget] !=
nil)
10192 if ([
self isDefenseTarget:ship])
10194 [
self removeDefenseTarget:ship];
10199 target = (ship && ship->
isShip && [
self isValidTarget:ship]) ? (
id)ship :
nil;
10200 if ([
self primaryAggressor] == ship)
10207 [
self doScriptEvent:OOJSID("shipTargetLost") withArgument:target];
10208 if (target ==
nil) [shipAI message:@"TARGET_LOST"];
10209 else [shipAI reactToMessage:@"TARGET_LOST" context:@"flight updates"];
10213- (void) noteLostTargetAndGoIdle
10215 behaviour = BEHAVIOUR_IDLE;
10217 [
self noteLostTarget];
10220- (void) noteTargetDestroyed:(
ShipEntity *)target
10222 [
self collectBountyFor:(ShipEntity *)target];
10223 if ([
self primaryTarget] == target)
10225 [
self removeTarget:target];
10226 [
self doScriptEvent:OOJSID("shipTargetDestroyed") withArgument:target];
10227 [shipAI message:@"TARGET_DESTROYED"];
10229 if ([
self isDefenseTarget:target])
10231 [
self removeDefenseTarget:target];
10232 [shipAI message:@"DEFENSE_TARGET_DESTROYED"];
10233 [
self doScriptEvent:OOJSID("defenseTargetDestroyed") withArgument:target];
10246 if (cond != behaviour)
10254- (HPVector) destination
10256 return _destination;
10259- (HPVector) coordinates
10261 return coordinates;
10264- (void) setCoordinate:(HPVector) coord
10266 coordinates = coord;
10269- (HPVector) distance_six: (GLfloat) dist
10271 HPVector six = position;
10272 six.x -= dist * v_forward.
x; six.y -= dist * v_forward.
y; six.z -= dist * v_forward.z;
10277- (HPVector) distance_twelve: (GLfloat) dist withOffset:(GLfloat)offset
10279 HPVector twelve = position;
10280 twelve.x += dist * v_up.
x; twelve.y += dist * v_up.
y; twelve.z += dist * v_up.z;
10281 twelve.x +=
offset * v_right.
x; twelve.y +=
offset * v_right.
y; twelve.z +=
offset * v_right.z;
10286- (void) trackOntoTarget:(
double) delta_t withDForward: (GLfloat) dp
10288 Vector vector_to_target;
10289 Quaternion q_minarc;
10291 Entity* target = [
self primaryTarget];
10296 vector_to_target = [
self vectorTo:target];
10298 GLfloat range2 = magnitude2(vector_to_target);
10300 GLfloat max_cos = sqrt(1 - targetRadius*targetRadius/range2);
10305 if (vector_to_target.x||vector_to_target.y||vector_to_target.z)
10306 vector_to_target = vector_normal(vector_to_target);
10308 vector_to_target.z = 1.0;
10313 [
self orientationChanged];
10324- (double) ballTrackLeadingTarget:(
double) delta_t atTarget:(
Entity *)target
10331 Vector vector_to_target;
10332 Vector axis_to_track_by;
10334 Vector my_ref = reference;
10335 double aim_cos, ref_cos;
10336 Vector leading = [target
velocity];
10339 HPVector my_position = [
self absolutePositionForSubentity];
10340 vector_to_target = HPVectorToVector(HPvector_subtract([target position], my_position));
10344 Entity *father = [
self parentEntity];
10347 while ((father)&&(father != last) && (father !=
NO_TARGET))
10359 if (![last isSubEntity])
break;
10360 father = [father
owner];
10362 q = quaternion_conjugate(q);
10370 if (magnitude(vector_to_target) > weaponRange * 1.01)
10377 vector_to_target = vector_add(vector_to_target, vector_multiply_scalar(leading, lead));
10378 vector_to_target = vector_normal_or_fallback(vector_to_target,
kBasisZVector);
10381 aim_cos = dot_product(vector_to_target, my_aim);
10382 ref_cos = dot_product(vector_to_target, my_ref);
10387 axis_to_track_by = cross_product(vector_to_target, my_aim);
10395 [
self orientationChanged];
10397 [
self setStatus:STATUS_ACTIVE];
10403- (void) setEvasiveJink:(GLfloat) z
10436- (void) evasiveAction:(
double) delta_t
10438 stick_roll = flightRoll;
10439 stick_pitch = flightPitch;
10444 [
self noteLostTargetAndGoIdle];
10448 double agreement = dot_product(v_right,target->
v_right);
10449 if (agreement > -0.3 && agreement < 0.3)
10455 if (stick_roll >= 0.0) {
10456 stick_roll = max_flight_roll;
10458 stick_roll = -max_flight_roll;
10461 if (stick_pitch >= 0.0) {
10462 stick_pitch = max_flight_pitch;
10464 stick_pitch = -max_flight_pitch;
10467 [
self applySticks:delta_t];
10471- (double) trackPrimaryTarget:(
double) delta_t :(BOOL) retreat
10473 Entity* target = [
self primaryTarget];
10477 [
self noteLostTargetAndGoIdle];
10481 if (![
self canStillTrackPrimaryTarget])
10483 [
self noteLostTargetAndGoIdle];
10491 if (scanClass == CLASS_MISSILE)
10492 return [
self missileTrackPrimaryTarget: delta_t];
10494 GLfloat d_forward, d_up, d_right;
10496 Vector relPos = HPVectorToVector(HPvector_subtract([
self calculateTargetPosition], position));
10498 double range2 = HPmagnitude2(HPvector_subtract([target position], position));
10508 vy = targetShip->
v_up;
10519 BOOL avoidCollision = NO;
10520 if (range2 < collision_radius * target->collision_radius * 100.0)
10523 if (!vector_equal(relPos,
kZeroVector)) targetDirection = vector_normal(relPos);
10524 avoidCollision = (dot_product(targetDirection, v_forward) > -0.1);
10527 GLfloat dist_adjust_factor = 1.0;
10530 double range = magnitude(relPos);
10531 if (range > 2000.0)
10533 dist_adjust_factor = range / 2000.0;
10536 dist_adjust_factor *= 3;
10539 if (jink.x == 0.0 && behaviour != BEHAVIOUR_RUNNING_DEFENSE)
10541 [
self setEvasiveJink:400.0];
10545 if (!avoidCollision)
10547 relPos.x += (jink.
x * vx.
x + jink.
y * vy.
x + jink.z * vz.
x) * dist_adjust_factor;
10548 relPos.y += (jink.
x * vx.
y + jink.
y * vy.
y + jink.z * vz.
y) * dist_adjust_factor;
10549 relPos.z += (jink.
x * vx.z + jink.
y * vy.z + jink.z * vz.z);
10554 if (!vector_equal(relPos,
kZeroVector)) relPos = vector_normal(relPos);
10555 else relPos.z = 1.0;
10557 double max_cos = [
self currentAimTolerance];
10562 double reverse = (retreat)? -1.0: 1.0;
10564 double min_d = 0.004;
10565 int max_factor = 8;
10566 double r_max_factor = 0.125;
10572 if (max_flight_pitch > 1.0)
10574 max_factor = floor(max_flight_pitch/0.125);
10575 r_max_factor = 1.0/max_factor;
10579 r_max_factor /= 3.0;
10584 min_d -= 0.0001 * [
self missedShots];
10589 r_max_factor /= 2.0;
10594 d_right = dot_product(relPos, v_right);
10595 d_up = dot_product(relPos, v_up);
10596 d_forward = dot_product(relPos, v_forward);
10598 if (d_forward * reverse > max_cos)
10608 if ((reverse * d_forward < -0.5) && !pitching_over)
10609 pitching_over = YES;
10613 if (reverse * d_up > 0)
10614 stick_pitch = -max_flight_pitch;
10616 stick_pitch = max_flight_pitch;
10617 pitching_over = (reverse * d_forward < 0.707);
10621 if ((d_forward < max_cos)||(retreat))
10623 if (d_forward < -max_cos)
10625 d_up = min_d * 2.0;
10630 int factor = sqrt(fabs(d_right) / fabs(min_d));
10631 if (factor > max_factor)
10632 factor = max_factor;
10633 if (d_right > min_d)
10634 stick_roll = - max_flight_roll * r_max_factor * factor;
10635 if (d_right < -min_d)
10636 stick_roll = + max_flight_roll * r_max_factor * factor;
10640 int factor = sqrt(fabs(d_right) / fabs(min_d));
10641 if (factor > max_factor)
10642 factor = max_factor;
10643 if (d_right > min_d)
10644 stick_roll = + max_flight_roll * r_max_factor * factor;
10645 if (d_right < -min_d)
10646 stick_roll = - max_flight_roll * r_max_factor * factor;
10649 if (stick_roll == 0.0)
10651 int factor = sqrt(fabs(d_up) / fabs(min_d));
10652 if (factor > max_factor)
10653 factor = max_factor;
10655 stick_pitch = - max_flight_pitch * reverse * r_max_factor * factor;
10657 stick_pitch = + max_flight_pitch * reverse * r_max_factor * factor;
10663 if (fabs(d_right) < fabs(stick_roll) * delta_t)
10665 stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1);
10667 if (fabs(d_up) < fabs(stick_pitch) * delta_t)
10669 stick_pitch = fabs(d_up) / delta_t * (stick_pitch<0 ? -0.9 : 0.9);
10690 [
self applySticks:delta_t];
10693 d_forward *= d_forward;
10695 if (d_forward < 0.0)
10698 if ((!flightRoll)&&(!flightPitch))
10705- (double) trackSideTarget:(
double) delta_t :(BOOL) leftside
10707 Entity* target = [
self primaryTarget];
10711 [
self noteLostTargetAndGoIdle];
10715 if (![
self canStillTrackPrimaryTarget])
10717 [
self noteLostTargetAndGoIdle];
10722 if (scanClass == CLASS_MISSILE)
10723 return [
self missileTrackPrimaryTarget: delta_t];
10725 GLfloat d_forward, d_up, d_right;
10727 Vector relPos = HPVectorToVector(HPvector_subtract([
self calculateTargetPosition], position));
10730 if (!vector_equal(relPos,
kZeroVector)) relPos = vector_normal(relPos);
10731 else relPos.z = 1.0;
10735 double max_cos = [
self currentAimTolerance];
10741 double reverse = (leftside)? -1.0: 1.0;
10743 double min_d = 0.004;
10748 int max_factor = 8;
10749 double r_max_factor = 0.125;
10751 d_right = dot_product(relPos, v_right);
10752 d_up = dot_product(relPos, v_up);
10753 d_forward = dot_product(relPos, v_forward);
10755 if (d_right * reverse > max_cos)
10757 return d_right * reverse;
10766 if ((d_right * reverse < max_cos))
10768 if (d_right < -max_cos)
10770 d_forward = min_d * 2.0;
10773 if (d_forward > min_d)
10775 int factor = sqrt(fabs(d_up) / fabs(min_d));
10776 if (factor > max_factor)
10777 factor = max_factor;
10779 stick_pitch = + max_flight_pitch * r_max_factor * factor;
10781 stick_pitch = - max_flight_pitch * r_max_factor * factor;
10783 if (d_forward < -min_d)
10785 int factor = sqrt(fabs(d_up) / fabs(min_d));
10786 if (factor > max_factor)
10787 factor = max_factor;
10789 stick_pitch = + max_flight_pitch * r_max_factor * factor;
10791 stick_pitch = - max_flight_pitch * r_max_factor * factor;
10794 if (fabs(stick_pitch) == 0.0 || fabs(d_forward) > 0.5)
10797 int factor = sqrt(fabs(d_forward) / fabs(min_d));
10798 if (factor > max_factor)
10799 factor = max_factor;
10800 if (d_forward > min_d)
10801 stick_yaw = - max_flight_yaw * reverse * r_max_factor * factor;
10802 if (d_forward < -min_d)
10804 if (factor < max_factor/2.0)
10806 stick_yaw = + max_flight_yaw * reverse * r_max_factor * factor;
10814 [
self applySticks:delta_t];
10816 if ((!flightPitch)&&(!flightYaw))
10819 return d_right * reverse;
10824- (double) missileTrackPrimaryTarget:(
double) delta_t
10827 GLfloat d_forward, d_up, d_right;
10829 BOOL inPursuit = YES;
10831 if (!target || ![target isShip])
10834 double damping = 0.5 * delta_t;
10840 relPos = [
self vectorTo:target];
10845 float missileSpeed = (float)[
self speed];
10851 if (missileSpeed > 0.01f && accuracy > 0.0f)
10853 inPursuit = (dot_product([target forwardVector], v_forward) > 0.0f);
10856 Vector leading = [target
velocity];
10857 float lead = magnitude(relPos) / missileSpeed;
10861 relPos.x += (lead * leading.
x * (accuracy / 10.0f));
10862 relPos.y += (lead * leading.
y * (accuracy / 10.0f));
10863 relPos.z += (lead * leading.z * (accuracy / 10.0f));
10867 if (!vector_equal(relPos,
kZeroVector)) relPos = vector_normal(relPos);
10868 else relPos.z = 1.0;
10870 d_right = dot_product(relPos, v_right);
10871 d_up = dot_product(relPos, v_up);
10872 d_forward = dot_product(relPos, v_forward);
10879 pitching_over = (stick_pitch != 0.0);
10881 if ((d_forward < -pitch_tolerance) && (!pitching_over))
10883 pitching_over = YES;
10885 stick_pitch = -max_flight_pitch;
10887 stick_pitch = max_flight_pitch;
10892 pitching_over = (d_forward < 0.5);
10896 stick_pitch = -max_flight_pitch * d_up;
10897 stick_roll = -max_flight_roll * d_right;
10903 if (flightRoll < 0)
10904 flightRoll += (flightRoll < -damping) ? damping : -flightRoll;
10905 if (flightRoll > 0)
10906 flightRoll -= (flightRoll > damping) ? damping : flightRoll;
10907 if (flightPitch < 0)
10908 flightPitch += (flightPitch < -damping) ? damping : -flightPitch;
10909 if (flightPitch > 0)
10910 flightPitch -= (flightPitch > damping) ? damping : flightPitch;
10913 [
self applySticks:delta_t];
10918 if (d_forward < 0.0)
10924- (double) trackDestination:(
double) delta_t :(BOOL) retreat
10927 GLfloat d_forward, d_up, d_right;
10929 BOOL we_are_docking = (
nil != dockingInstructions);
10935 double reverse = 1.0;
10936 double reversePlayer = 1.0;
10938 double min_d = 0.004;
10940 double precision = we_are_docking ? 0.25 : 0.9025;
10943 reverse = -reverse;
10947 reverse = -reverse;
10948 reversePlayer = -1;
10951 relPos = HPVectorToVector(HPvector_subtract(_destination, position));
10952 double range2 = magnitude2(relPos);
10953 double desired_range2 = desired_range*desired_range;
10961 if (range2 > desired_range2)
10963 max_cos = sqrt(1 - precision * desired_range2/range2);
10964 if (max_cos >= 0.99999)
10970 if (!vector_equal(relPos,
kZeroVector)) relPos = vector_normal(relPos);
10971 else relPos.z = 1.0;
10973 d_right = dot_product(relPos, v_right);
10974 d_up = dot_product(relPos, v_up);
10975 d_forward = dot_product(relPos, v_forward);
10985 if (reverse * d_up > 0)
10986 stick_pitch = -max_flight_pitch;
10988 stick_pitch = max_flight_pitch;
10989 pitching_over = (reverse * d_forward < 0.707);
10993 if ((d_forward < max_cos)||(retreat))
10996 if (d_forward <= -max_cos || (retreat && d_forward >= max_cos))
10998 d_up = min_d * 2.0;
11003 int factor = sqrt(fabs(d_right) / fabs(min_d));
11006 if (d_right > min_d)
11007 stick_roll = - max_flight_roll * reversePlayer * 0.125 * factor;
11008 if (d_right < -min_d)
11009 stick_roll = + max_flight_roll * reversePlayer * 0.125 * factor;
11010 if (fabs(d_right) < fabs(stick_roll) * delta_t)
11011 stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1);
11016 int factor = sqrt(fabs(d_right) / fabs(min_d));
11019 if (d_right > min_d)
11020 stick_roll = + max_flight_roll * reversePlayer * 0.125 * factor;
11021 if (d_right < -min_d)
11022 stick_roll = - max_flight_roll * reversePlayer * 0.125 * factor;
11023 if (fabs(d_right) < fabs(stick_roll) * delta_t)
11024 stick_roll = fabs(d_right) / delta_t * (stick_roll<0 ? -1 : 1);
11027 if (stick_roll == 0.0)
11029 int factor = sqrt(fabs(d_up) / fabs(min_d));
11033 stick_pitch = - max_flight_pitch * reverse * 0.125 * factor;
11035 stick_pitch = + max_flight_pitch * reverse * 0.125 * factor;
11036 if (fabs(d_up) < fabs(stick_pitch) * delta_t)
11037 stick_pitch = fabs(d_up) / delta_t * (stick_pitch<0 ? -1 : 1);
11040 if (stick_pitch == 0.0)
11044 stick_pitch = 0.01;
11048 if (we_are_docking && docking_match_rotation && (d_forward > max_cos))
11053 if ((station_for_docking)&&(station_for_docking->
isStation))
11061 [
self applySticks:delta_t];
11064 d_forward *= d_forward;
11066 if (d_forward < 0.0)
11069 if ((!flightRoll)&&(!flightPitch))
11076- (GLfloat) rollToMatchUp:(Vector)up_vec rotating:(GLfloat)match_roll
11078 GLfloat cosTheta = dot_product(up_vec, v_up);
11079 GLfloat sinTheta = dot_product(up_vec, v_right);
11083 match_roll = -match_roll;
11084 sinTheta = -sinTheta;
11087 if (cosTheta < 0.0f)
11089 cosTheta = -cosTheta;
11090 sinTheta = -sinTheta;
11093 if (sinTheta > 0.0f)
11096 return cosTheta * cosTheta * match_roll + sinTheta * sinTheta * max_flight_roll;
11101 return cosTheta * cosTheta * match_roll - sinTheta * sinTheta * max_flight_roll;
11106- (GLfloat) rangeToDestination
11108 return HPdistance(position, _destination);
11112- (NSArray *) collisionExceptions
11114 if (_collisionExceptions ==
nil)
11116 return [NSArray array];
11118 return [_collisionExceptions allObjects];
11122- (void) addCollisionException:(
ShipEntity *)ship
11124 if (_collisionExceptions ==
nil)
11127 _collisionExceptions = [[
OOWeakSet alloc] init];
11129 [_collisionExceptions addObject:ship];
11133- (void) removeCollisionException:(
ShipEntity *)ship
11135 if (_collisionExceptions !=
nil)
11137 [_collisionExceptions removeObject:ship];
11142- (BOOL) collisionExceptedFor:(
ShipEntity *)ship
11144 if (_collisionExceptions ==
nil)
11148 return [_collisionExceptions containsObject:ship];
11153- (NSUInteger) defenseTargetCount
11155 return [_defenseTargets count];
11159- (NSArray *) allDefenseTargets
11161 return [_defenseTargets allObjects];
11165- (NSEnumerator *) defenseTargetEnumerator
11167 return [_defenseTargets objectEnumerator];
11171- (BOOL) addDefenseTarget:(
Entity *)target
11178 if (target ==
nil || [
self isDefenseTarget:target] || ![target isShip])
11182 if (_defenseTargets ==
nil)
11185 _defenseTargets = [[
OOWeakSet alloc] init];
11188 [_defenseTargets addObject:target];
11193- (void) validateDefenseTargets
11195 if (_defenseTargets ==
nil)
11200 NSEnumerator *defTargets = [[
self allDefenseTargets] objectEnumerator];
11202 while ((target = [[defTargets nextObject] weakRefUnderlyingObject]))
11204 if ([target status] == STATUS_DEAD)
11206 [
self removeDefenseTarget:target];
11212- (BOOL) isDefenseTarget:(
Entity *)target
11214 return [_defenseTargets containsObject:target];
11219- (void) removeAllDefenseTargets
11221 [_defenseTargets removeAllObjects];
11225- (void) removeDefenseTarget:(
Entity *)target
11227 [_defenseTargets removeObject:target];
11231- (double) rangeToPrimaryTarget
11233 return [
self rangeToSecondaryTarget:[
self primaryTarget]];
11237- (double) rangeToSecondaryTarget:(
Entity *)target
11243 delta = HPVectorToVector(HPvector_subtract(target->
position, position));
11244 dist = magnitude(delta);
11245 dist -= target->collision_radius;
11246 dist -= collision_radius;
11251- (double) approachAspectToPrimaryTarget
11254 Entity *target = [
self primaryTarget];
11255 if (target ==
nil || ![target isShip])
11261 delta = HPVectorToVector(HPvector_subtract(position, target->
position));
11263 return dot_product(vector_normal(delta), ship_target->
v_forward);
11267- (BOOL) hasProximityAlertIgnoringTarget:(BOOL)ignore_target
11269 if (([
self proximityAlert] !=
nil)&&(!ignore_target || ([
self proximityAlert] != [
self primaryTarget])))
11279- (GLfloat) currentAimTolerance
11281 GLfloat basic_aim = aim_tolerance;
11282 GLfloat best_cos = 0.99999;
11286 best_cos = 0.999999;
11288 basic_aim /= 1.0 + ((GLfloat)[
self missedShots] / 4.0);
11293 best_cos = 0.9999999;
11305 basic_aim *= 1.3 +
randf();
11317 GLfloat sunGlareAngularSize = atan([sun radius]/HPdistance([
self position], [sun position])) * SUN_GLARE_MULT_FACTOR + (SUN_GLARE_ADD_FACTOR);
11318 GLfloat glareLevel = [
self lookingAtSunWithThresholdAngleCos:cos(sunGlareAngularSize)] * (1.0f - [
self sunGlareFilter]);
11319 if (glareLevel > 0.1f)
11322 basic_aim *= (1.0 + glareLevel*3.0);
11324 if (glareLevel > 0.5f)
11327 best_cos = 0.99999;
11334 GLfloat max_cos = sqrt(1-(basic_aim * basic_aim / 100000000.0));
11336 if (max_cos < best_cos)
11345- (GLfloat) lookingAtSunWithThresholdAngleCos:(GLfloat) thresholdAngleCos
11348 GLfloat measuredCos = 999.0f, measuredCosAbs;
11349 GLfloat sunBrightness = 0.0f;
11350 Vector relativePosition, unitRelativePosition;
11354 relativePosition = HPVectorToVector(HPvector_subtract([
self position], [sun position]));
11355 unitRelativePosition = vector_normal_or_zbasis(relativePosition);
11356 switch (currentWeaponFacing)
11359 measuredCos = -dot_product(unitRelativePosition, v_forward);
11362 measuredCos = +dot_product(unitRelativePosition, v_forward);
11365 measuredCos = +dot_product(unitRelativePosition, v_right);
11368 measuredCos = -dot_product(unitRelativePosition, v_right);
11373 measuredCosAbs = fabs(measuredCos);
11374 if (thresholdAngleCos <= measuredCosAbs && measuredCosAbs <= 1.0f)
11376 sunBrightness = (measuredCos - thresholdAngleCos) / (1.0f - thresholdAngleCos);
11377 if (sunBrightness < 0.0f) sunBrightness = 0.0f;
11379 return sunBrightness * sunBrightness * sunBrightness;
11387 GLfloat dq = -1.0f;
11388 GLfloat d2, radius, astq;
11389 Vector rel_pos, urp;
11390 if ([weapon_type isTurretLaser])
11395 Entity *target = [
self primaryTarget];
11396 if (target ==
nil)
return NO;
11397 if ([target status] == STATUS_DEAD)
return NO;
11399 if (isSunlit && (target->
isSunlit == NO) && (
randf() < 0.75))
11404 rel_pos = HPVectorToVector(HPvector_subtract([
self calculateTargetPosition], position));
11405 d2 = magnitude2(rel_pos);
11406 urp = vector_normal_or_zbasis(rel_pos);
11411 dq = +dot_product(urp, v_forward);
11415 dq = -dot_product(urp, v_forward);
11419 dq = -dot_product(urp, v_right);
11423 dq = +dot_product(urp, v_right);
11430 if (dq < 0.0)
return NO;
11432 GLfloat aim = [
self currentAimTolerance];
11433 if (dq > aim*aim)
return YES;
11438 astq = sqrt(1.0 - radius * radius / (d2 * 9));
11440 return (fabs(dq) >= astq);
11450 weapon_temp = forward_weapon_temp;
11454 weapon_temp = aft_weapon_temp;
11458 weapon_temp = port_weapon_temp;
11462 weapon_temp = starboard_weapon_temp;
11470 NSUInteger multiplier = 1;
11471 if (_multiplyWeapons)
11474 multiplier = [[
self laserPortOffset:direction] count];
11477 if (energy <= weapon_energy_use * multiplier)
return NO;
11478 if ([
self shotTime] < weapon_recharge_rate)
return NO;
11479 if (![weapon_type isTurretLaser])
11481 if (range >
randf() * weaponRange * (accuracy+7.5))
return NO;
11482 if (range > weaponRange)
return NO;
11484 if (![
self onTarget:direction withWeapon:weapon_type]) return NO;
11489 if ([weapon_type isTurretLaser])
11491 [
self fireDirectLaserShot:range];
11496 [
self fireLaserShotInDirection:direction weaponIdentifier:[weapon_type
identifier]];
11503 energy -= weapon_energy_use * multiplier;
11507 forward_weapon_temp += weapon_shot_temperature * multiplier;
11511 aft_weapon_temp += weapon_shot_temperature * multiplier;
11515 port_weapon_temp += weapon_shot_temperature * multiplier;
11519 starboard_weapon_temp += weapon_shot_temperature * multiplier;
11531 foreach (se, [
self shipSubEntityEnumerator])
11533 if ([se fireSubentityLaserShot:range])
11540 if (fired && cloaking_device_active && cloakPassive)
11542 [
self deactivateCloakingDevice];
11549- (BOOL) fireMainWeapon:(
double)range
11554 [
self setWeaponDataFromType:forward_weapon_type];
11559 BOOL result = [
self fireWeapon:forward_weapon_type direction:WEAPON_FACING_FORWARD range:range];
11564 NSEnumerator *subEnum = [
self shipSubEntityEnumerator];
11567 BOOL hasTurrets = NO;
11568 while (
isWeaponNone(weapon_type) && (se = [subEnum nextObject]))
11572 if (se->
behaviour == BEHAVIOUR_TRACK_AS_TURRET)
11588 [
self setWeaponDataFromType:weapon_type];
11595- (BOOL) fireAftWeapon:(
double)range
11599 [
self setWeaponDataFromType:aft_weapon_type];
11601 return [
self fireWeapon:aft_weapon_type direction:WEAPON_FACING_AFT range:range];
11605- (BOOL) firePortWeapon:(
double)range
11609 [
self setWeaponDataFromType:port_weapon_type];
11611 return [
self fireWeapon:port_weapon_type direction:WEAPON_FACING_PORT range:range];
11615- (BOOL) fireStarboardWeapon:(
double)range
11619 [
self setWeaponDataFromType:starboard_weapon_type];
11621 return [
self fireWeapon:starboard_weapon_type direction:WEAPON_FACING_STARBOARD range:range];
11631- (void) resetShotTime
11637- (BOOL) fireTurretCannon:(
double) range
11639 if ([
self shotTime] < weapon_recharge_rate)
11641 if (range > weaponRange * 1.01)
11644 if ([root isPlayer] && ![
PLAYER weaponsOnline])
11647 if ([root isCloaked] && [root cloakPassive])
11654 HPVector
origin = [
self position];
11657 Entity *father = [
self parentEntity];
11662 while ((father)&&(father != last) && (father !=
NO_TARGET))
11668 if (![last isSubEntity])
break;
11669 father = [father
owner];
11672 origin = HPvector_add(
origin, vectorToHPVector(vector_multiply_scalar(vel, collision_radius + 0.5)));
11677 energy:weapon_damage
11678 duration:weaponRange/TURRET_SHOT_SPEED
11679 color:laser_color];
11681 [shot autorelease];
11682 [UNIVERSE addEntity:shot];
11683 [shot
setOwner:[
self rootShipEntity]];
11685 [
self resetShotTime];
11690- (void) setLaserColor:(
OOColor *) color
11694 [laser_color release];
11695 laser_color = [color retain];
11700- (void) setExhaustEmissiveColor:(
OOColor *) color
11704 [exhaust_emissive_color release];
11705 exhaust_emissive_color = [color retain];
11712 return [[laser_color retain] autorelease];
11716- (
OOColor *)exhaustEmissiveColor
11718 return [[exhaust_emissive_color retain] autorelease];
11722- (BOOL) fireSubentityLaserShot:(
double)range
11724 [
self setShipHitByLaser:nil];
11727 [
self setWeaponDataFromType:forward_weapon_type];
11730 NSAssert([parent isShipWithSubEntityShip:
self],
@"-fireSubentityLaserShot: called on ship which is not a subentity.");
11733 if ([parent energy] <= weapon_energy_use)
return NO;
11734 if ([
self shotTime] < weapon_recharge_rate)
return NO;
11736 if (range > weaponRange)
return NO;
11738 forward_weapon_temp += weapon_shot_temperature;
11741 GLfloat hitAtRange = weaponRange;
11743 ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:direction offset:kZeroVector gettingRangeFound:&hitAtRange];
11744 [
self setShipHitByLaser:victim];
11752 [
self adjustMissedShots:-1];
11754 if ([
self isPlayer])
11756 [PLAYER addRoleForAggression:victim];
11760 if (subent !=
nil && [victim isFrangible])
11767 if (hitAtRange < weaponRange)
11773 HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hitAtRange)));
11774 [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:weapon_damage color:laser_color];
11779 [
self adjustMissedShots:+1];
11782 if (![parent isCloaked])
11787 Vector victimDirection = vector_normal(HPVectorToVector(HPvector_subtract([victim position], [parent position])));
11788 if (dot_product(shotDirection, victimDirection) > 0.995)
11790 if ([
self isPlayer])
11792 [PLAYER addRoleForAggression:victim];
11803 [UNIVERSE addEntity:shot];
11804 [
self resetShotTime];
11810- (BOOL) fireDirectLaserShot:(
double)range
11812 Entity *my_target = [
self primaryTarget];
11813 if (my_target ==
nil)
return [
self fireDirectLaserDefensiveShot];
11814 if (range >
randf() * weaponRange * (accuracy+5.5))
return [
self fireDirectLaserDefensiveShot];
11815 if (range > weaponRange)
return [
self fireDirectLaserDefensiveShot];
11816 return [
self fireDirectLaserShotAt:my_target];
11820- (BOOL) fireDirectLaserDefensiveShot
11822 NSEnumerator *targetEnum = [
self defenseTargetEnumerator];
11824 while ((target = [[targetEnum nextObject] weakRefUnderlyingObject]))
11827 if ([target scanClass] == CLASS_NO_DRAW || [(
ShipEntity *)target isCloaked] || [target energy] <= 0.0)
11829 [
self removeDefenseTarget:target];
11833 double range = [
self rangeToSecondaryTarget:target];
11834 if (range < weaponRange)
11836 return [
self fireDirectLaserShotAt:target];
11838 else if (range > scannerRange)
11840 [
self removeDefenseTarget:target];
11848- (BOOL) fireDirectLaserShotAt:(
Entity *)my_target
11850 GLfloat hit_at_range;
11851 double range_limit2 = weaponRange*weaponRange;
11854 r_pos = vector_normal_or_zbasis([
self vectorTo:my_target]);
11858 GLfloat acc_factor = (10.0 - accuracy) * 0.001;
11860 q_laser.x += acc_factor * (
randf() - 0.5);
11861 q_laser.y += acc_factor * (
randf() - 0.5);
11862 q_laser.z += acc_factor * (
randf() - 0.5);
11863 quaternion_normalize(&q_laser);
11865 Quaternion q_save = orientation;
11866 orientation = q_laser;
11868 ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:WEAPON_FACING_FORWARD offset:kZeroVector gettingRangeFound:&hit_at_range];
11869 [
self setShipHitByLaser:victim];
11870 orientation = q_save;
11872 Vector vel = vector_multiply_scalar(v_forward, flightSpeed);
11885 if (subent !=
nil && [victim isFrangible])
11892 if (hit_at_range * hit_at_range < range_limit2)
11898 HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hit_at_range)));
11899 [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:weapon_damage color:laser_color];
11903 [UNIVERSE addEntity:shot];
11905 [
self resetShotTime];
11913 NSArray *laserPortOffset =
nil;
11918 laserPortOffset = forwardWeaponOffset;
11922 laserPortOffset = aftWeaponOffset;
11926 laserPortOffset = portWeaponOffset;
11930 laserPortOffset = starboardWeaponOffset;
11933 return laserPortOffset;
11937- (BOOL) fireLaserShotInDirection:(
OOWeaponFacing)direction weaponIdentifier:(NSString *)weaponIdentifier
11939 double range_limit2 = weaponRange * weaponRange;
11940 GLfloat hit_at_range;
11941 NSUInteger i, barrels;
11942 Vector vel = vector_multiply_scalar(v_forward, flightSpeed);
11943 NSArray *laserPortOffsets = [
self laserPortOffset:direction];
11946 barrels = [laserPortOffsets count];
11947 NSMutableArray *shotEntities = [NSMutableArray arrayWithCapacity:barrels];
11950 GLfloat effective_damage = weapon_damage;
11951 if (barrels > 1 && !_multiplyWeapons)
11954 effective_damage /= (GLfloat)barrels;
11957 for (i=0;i<barrels;i++)
11959 Vector laserPortOffset = [laserPortOffsets oo_vectorAtIndex:i];
11961 last_shot_time = [UNIVERSE getTime];
11963 ShipEntity *victim = [UNIVERSE firstShipHitByLaserFromShip:self inDirection:direction offset:laserPortOffset gettingRangeFound:&hit_at_range];
11964 [
self setShipHitByLaser:victim];
11967 if ([
self isPlayer])
11969 [shotEntities addObject:shot];
11978 [
self adjustMissedShots:-1];
11979 if ([
self isPlayer])
11981 [PLAYER addRoleForAggression:victim];
11993 if (subent !=
nil && [victim isFrangible])
12000 if (hit_at_range * hit_at_range < range_limit2)
12006 HPVector flash_pos = HPvector_add([shot position], vectorToHPVector(vector_multiply_scalar(vd, hit_at_range)));
12007 [UNIVERSE addLaserHitEffectsAt:flash_pos against:victim damage:effective_damage color:laser_color];
12012 [
self adjustMissedShots:+1];
12015 if (![
self isCloaked])
12017 victim = [
self primaryTarget];
12018 if ([victim isShip])
12033 if ([
self isPlayer])
12035 [PLAYER addRoleForAggression:victim];
12046 [UNIVERSE addEntity:shot];
12050 if ([
self isPlayer])
12055 [
self resetShotTime];
12061- (void) adjustMissedShots:(
int) delta
12063 if ([
self isSubEntity])
12065 [[
self owner] adjustMissedShots:delta];
12069 _missed_shots += delta;
12070 if (_missed_shots < 0)
12080 if ([
self isSubEntity])
12082 return [[
self owner] missedShots];
12086 return _missed_shots;
12091- (void) throwSparks
12095 randf() * (boundingBox.max.x - boundingBox.min.
x) + boundingBox.min.
x,
12096 randf() * (boundingBox.max.
y - boundingBox.min.
y) + boundingBox.min.
y,
12097 randf() * boundingBox.max.z + boundingBox.min.z
12101 float w = boundingBox.max.x - boundingBox.min.
x;
12102 float h = boundingBox.max.y - boundingBox.min.
y;
12103 float m = (w < h) ? 0.25 * w: 0.25 * h;
12107 Vector vel = vector_multiply_scalar(HPVectorToVector(HPvector_subtract(
origin, position)), 2.0);
12113 duration:2.0 + 3.0 * randf()
12118 [UNIVERSE addEntity:spark];
12121 next_spark_time =
randf();
12125- (void) considerFiringMissile:(
double)delta_t
12127 int missile_chance = 0;
12128 int rhs = 3.2 / delta_t;
12129 if (rhs) missile_chance = 1 + (
ranrot_rand() % rhs);
12131 double hurt_factor = 16 * pow(energy/maxEnergy, 4.0);
12132 if (missiles > missile_chance * hurt_factor)
12134 [
self fireMissile];
12139- (Vector) missileLaunchPosition
12144 start.y = boundingBox.min.y - 4.0f;
12145 start.z = boundingBox.max.z + 1.0f;
12148 start = [shipinfoDictionary oo_vectorForKey:@"missile_launch_position" defaultValue:start];
12151 start = vector_multiply_scalar(start,_scaleFactor);
12154 if (start.x == 0.0f && start.y == 0.0f && start.z <= 0.0f)
12156 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);
12158 start.y = boundingBox.min.y - 4.0f;
12159 start.z = boundingBox.max.z + 1.0f;
12167 return [
self fireMissileWithIdentifier:nil andTarget:[
self primaryTarget]];
12171- (
ShipEntity *) fireMissileWithIdentifier:(NSString *) identifier andTarget:(
Entity *) target
12179 Vector start, v_eject;
12181 if ([
UNIVERSE getTime] < missile_launch_time)
return nil;
12183 start = [
self missileLaunchPosition];
12185 double throw_speed = 250.0f;
12187 if ((missiles <= 0)||(target ==
nil)||([target scanClass] == CLASS_NO_DRAW))
12190 if ([target isShip])
12193 if ([target_ship isCloaked])
12198 if (HPmagnitude2(HPvector_subtract([target_ship position], position)) > scannerRange * scannerRange)
12202 if (![
self hasMilitaryScannerFilter] && [target_ship isJammingScanning])
12209 if (identifier ==
nil)
12212 i = floor(
randf()*(
double)missiles);
12213 identifier = [missile_list[i] identifier];
12214 missile = [UNIVERSE newShipWithRole:identifier];
12218 while ( ++i < missiles ) missile_list[i - 1] = missile_list[i];
12223 missile = [UNIVERSE newShipWithRole:identifier];
12229 if (!isPlayer && ![
self removeExternalStore:[
OOEquipmentType equipmentTypeWithIdentifier:identifier]])
12236 v_eject = vector_normal(start);
12240 while ( (start.x > boundingBox.min.x - mcr)&&(start.x < boundingBox.max.x + mcr)&&
12241 (start.y > boundingBox.min.y - mcr)&&(start.y < boundingBox.max.y + mcr)&&
12242 (start.z > boundingBox.min.z - mcr)&&(start.z < boundingBox.max.z + mcr) )
12244 start = vector_add(start, vector_multiply_scalar(v_eject, mcr));
12247 vel = vector_add(vel, vector_multiply_scalar(v_forward, flightSpeed + throw_speed));
12249 Quaternion q1 = [
self normalOrientation];
12257 if ([missile scanClass] == CLASS_THARGOID)
12261 ShipEntity *thisGroupLeader = [_group leader];
12263 if ([thisGroupLeader escortGroup] != _group)
12270 if (![
self isMissileFlagSet]) [missile
setOwner:self];
12271 else [missile
setOwner:[
self owner]];
12283 missile_launch_time = [UNIVERSE getTime] + missile_load_time;
12285 [UNIVERSE addEntity:missile];
12289 if ([missile isMissile] && [target isShip])
12291 [
self doScriptEvent:OOJSID("shipFiredMissile") withArgument:missile andArgument:target_ship];
12295 if (cloaking_device_active && cloakPassive)
12298 [
self deactivateCloakingDevice];
12303 [
self doScriptEvent:OOJSID("shipReleasedEquipment") withArgument:missile];
12310- (BOOL) isMissileFlagSet
12316- (void) setIsMissileFlag:(BOOL)newValue
12318 isMissile = !!newValue;
12324 return missile_load_time;
12328- (void) setMissileLoadTime:(
OOTimeDelta)newMissileLoadTime
12330 missile_load_time = fmax(0.0, newMissileLoadTime);
12337 if (accuracy >=
COMBAT_AI_ISNT_AWFUL && missiles > 0 && [[missile_list[0] identifier] isEqualTo:
@"EQ_MISSILE"])
12341 missile_launch_time = [UNIVERSE getTime] + fmax(2.0,missile_load_time);
12349 if (![
self hasECM])
return NO;
12352 [UNIVERSE addEntity:ecmDevice];
12353 [ecmDevice release];
12358- (BOOL) activateCloakingDevice
12360 if (![
self hasCloakingDevice] || cloaking_device_active)
return cloaking_device_active;
12363 if (cloaking_device_active) [
self doScriptEvent:OOJSID("shipCloakActivated")];
12364 return cloaking_device_active;
12368- (void) deactivateCloakingDevice
12370 if ([
self hasCloakingDevice] && cloaking_device_active)
12372 cloaking_device_active = NO;
12373 [
self doScriptEvent:OOJSID("shipCloakDeactivated")];
12378- (BOOL) launchCascadeMine
12380 if (![
self hasCascadeMine])
return NO;
12381 [
self setSpeed: maxFlightSpeed + 300];
12382 ShipEntity* bomb = [UNIVERSE newShipWithRole:@"energy-bomb"];
12383 if (bomb ==
nil)
return NO;
12385 [
self removeEquipmentItem:@"EQ_QC_MINE"];
12387 double start = collision_radius + bomb->collision_radius;
12388 Quaternion random_direction;
12391 double random_roll =
randf() - 0.5;
12392 double random_pitch =
randf() - 0.5;
12395 rpos = HPvector_subtract([
self position], vectorToHPVector(vector_multiply_scalar(v_forward, start)));
12397 double eject_speed = -800.0;
12398 vel = vector_multiply_scalar(v_forward, [
self flightSpeed] + eject_speed);
12399 eject_speed *= 0.5 * (
randf() - 0.5);
12400 vel = vector_add(vel, vector_multiply_scalar(v_up, eject_speed));
12401 eject_speed *= 0.5 * (
randf() - 0.5);
12402 vel = vector_add(vel, vector_multiply_scalar(v_right, eject_speed));
12413 [UNIVERSE addEntity:bomb];
12416 if (cloaking_device_active && cloakPassive)
12418 [
self deactivateCloakingDevice];
12423 [
self addTarget:bomb];
12424 [
self setBehaviour:BEHAVIOUR_FLEE_TARGET];
12435 unsigned n_pods, i;
12436 NSMutableArray *passengers =
nil;
12444 n_pods = [shipinfoDictionary oo_unsignedIntForKey:@"has_escape_pod"];
12445 if (n_pods > 65) n_pods = 65;
12446 if (n_pods > 1) passengers = [NSMutableArray arrayWithCapacity:n_pods-1];
12451 for (i = 0; i < [crew count]; i++)
12456 mainPod = [
self launchPodWithCrew:crew];
12460 [
self setCrew:nil];
12461 [
self setHulk:YES];
12466 for (i = 1; i < n_pods; i++)
12470 [passengers addObject:passenger];
12473 if (mainPod) [
self doScriptEvent:
OOJSID(
"shipLaunchedEscapePod") withArgument:mainPod andArgument:passengers];
12482 ShipEntity *jetto = [
self dumpCargoItem:nil];
12499 if (([cargo
count] > 0)&&([
UNIVERSE getTime] - cargo_dump_time > 0.5))
12501 if (preferred ==
nil)
12503 jetto = [[[cargo objectAtIndex:0] retain] autorelease];
12508 for (i=0;i<[cargo count];i++)
12510 if ([[[cargo objectAtIndex:i] commodityType] isEqualToString:preferred])
12512 jetto = [[[cargo objectAtIndex:i] retain] autorelease];
12520 jetto = [[[cargo objectAtIndex:0] retain] autorelease];
12526 [
self dumpItem:jetto];
12527 [cargo removeObjectAtIndex:i];
12528 [
self broadcastAIMessage:@"CARGO_DUMPED"];
12531 [
self checkScannerIgnoringUnpowered];
12532 for (i = 0; i < n_scanned_ships ; i++)
12550 ShipEntity* jetto = [UNIVERSE reifyCargoPod:cargoObj];
12558 double eject_speed =
EXPECT_NOT([jetto crew] && [jetto isPlayer]) ? 20.0 : 100.0;
12559 double eject_reaction = -eject_speed * [jetto mass] / [
self mass];
12563 Vector vel, v_eject, v_eject_normal;
12564 HPVector rpos = [
self absolutePositionForSubentity];
12565 double jetto_roll = 0;
12566 double jetto_pitch = 0;
12571 start.z = boundingBox.min.z - jcr;
12574 start = [shipinfoDictionary oo_vectorForKey:@"aft_eject_position" defaultValue:start];
12576 v_eject = vector_normal(start);
12579 while ( (start.x > boundingBox.min.x - jcr)&&(start.x < boundingBox.max.x + jcr)&&
12580 (start.y > boundingBox.min.y - jcr)&&(start.y < boundingBox.max.y + jcr)&&
12581 (start.z > boundingBox.min.z - jcr)&&(start.z < boundingBox.max.z + jcr))
12583 start = vector_add(start, vector_multiply_scalar(v_eject, jcr));
12587 rpos = HPvector_add(rpos, vectorToHPVector(v_eject));
12588 v_eject = vector_normal(v_eject);
12589 v_eject_normal = v_eject;
12591 v_eject.x += (
randf() -
randf())/eject_speed;
12592 v_eject.y += (
randf() -
randf())/eject_speed;
12593 v_eject.z += (
randf() -
randf())/eject_speed;
12595 vel = vector_add(vector_multiply_scalar(v_forward, flightSpeed), vector_multiply_scalar(v_eject, eject_speed));
12596 velocity = vector_add(velocity, vector_multiply_scalar(v_eject, eject_reaction));
12607 jetto_roll = ((
ranrot_rand() % 1024) - 512.0)/1024.0;
12608 jetto_pitch = ((
ranrot_rand() % 1024) - 512.0)/1024.0;
12618 [UNIVERSE addEntity:jetto];
12620 jettoAI = [jetto
getAI];
12621 if ([jettoAI hasSuspendedStateMachines])
12625 [jettoAI exitStateMachineWithMessage:nil];
12628 [
self doScriptEvent:OOJSID("shipDumpedCargo") withArgument:jetto];
12630 cargo_dump_time = [UNIVERSE getTime];
12635- (void) manageCollisions
12642 while ([collidingEntities
count] > 0)
12645 ent = [[[collidingEntities objectAtIndex:0] retain] autorelease];
12646 [collidingEntities removeObjectAtIndex:0];
12652 [
self collideWithShip:other_ship];
12654 else if ([ent isStellarObject])
12656 [
self getDestroyedBy:ent damageType:[ent
isSun] ? kOODamageTypeHitASun : kOODamageTypeHitAPlanet];
12657 if (
self ==
PLAYER) [
self retain];
12659 else if ([ent isWormhole])
12661 if( [
self isPlayer] ) [
self enterWormhole:(WormholeEntity*)ent];
12662 else [
self enterWormhole:(WormholeEntity*)ent replacing:NO];
12679 BOOL otherIsStation = other == [UNIVERSE station];
12681 hploc = HPvector_normal_or_zbasis(HPvector_subtract([other absolutePositionForSubentity], position));
12682 loc = HPVectorToVector(hploc);
12685 if ([
self canScoop:other])
12687 [
self scoopIn:other];
12690 if ([other canScoop:
self])
12706 GLfloat m2 = [other
mass];
12709 Vector v, vel1b = [
self velocity];
12711 if (otherParent !=
nil)
12725 v = vector_subtract(vel1b, v);
12727 GLfloat v2b = dot_product(v, loc);
12729 GLfloat v1a = sqrt(v2b * v2b * m2 / m1);
12730 if (v2b < 0.0f) v1a = -v1a;
12735 if (v2b < -1.0f)
return NO;
12738 position = HPvector_subtract(position, hploc);
12744 dam1 = m2 * v2b * v2b / 50000000;
12745 dam2 = m1 * v2b * v2b / 50000000;
12748 Vector vel1a = vector_multiply_scalar(loc, -v1a);
12749 Vector vel2a = vector_multiply_scalar(loc, v2b);
12751 if (magnitude2(v) <= 0.1)
12753 vel1a = vector_multiply_scalar(loc, -1);
12758 if (otherParent !=
nil)
12767 [
self adjustVelocity:vel1a];
12769 BOOL selfDestroyed = (dam1 > energy);
12770 BOOL otherDestroyed = (dam2 > [other
energy]) && !otherIsStation;
12774 [
self takeScrapeDamage: dam1 from:other];
12777 vel2a = vector_multiply_scalar(vel2a, -1);
12784 if (otherParent !=
nil && ![otherParent isFrangible])
12793 if (otherDestroyed)
12795 vel1a = vector_multiply_scalar(vel1a, -1);
12796 [
self adjustVelocity:vel1a];
12800 if (!selfDestroyed && !otherDestroyed)
12802 float t = 10.0 * [UNIVERSE getTimeDelta];
12804 HPVector pos1a = HPvector_add([
self position], vectorToHPVector(vector_multiply_scalar(loc, t * v1a)));
12805 [
self setPosition:pos1a];
12807 if (!otherIsStation)
12809 HPVector pos2a = HPvector_add([other position], vectorToHPVector(vector_multiply_scalar(loc, t * v2b)));
12817 [
self doScriptEvent:OOJSID("shipCollided") withArgument:other andReactToAIMessage:@"COLLISION"];
12824- (Vector) thrustVector
12826 return vector_multiply_scalar(v_forward, flightSpeed);
12832 return vector_add([super velocity], [
self thrustVector]);
12836- (void) setTotalVelocity:(Vector)vel
12838 [
self setVelocity:vector_subtract(vel, [
self thrustVector])];
12842- (void) adjustVelocity:(Vector) xVel
12844 velocity = vector_add(velocity, xVel);
12848- (void) addImpactMoment:(Vector) moment fraction:(GLfloat) howmuch
12850 velocity = vector_add(velocity, vector_multiply_scalar(moment, howmuch / mass));
12856 if (other ==
nil)
return NO;
12857 if (![
self hasCargoScoop])
return NO;
12858 if ([cargo
count] >= [
self maxAvailableCargoSpace])
return NO;
12859 if (scanClass == CLASS_CARGO)
return NO;
12860 if ([other scanClass] != CLASS_CARGO)
return NO;
12863 if ([other isStation])
return NO;
12865 HPVector loc = HPvector_between(position, [other position]);
12867 if (dot_product(v_forward, HPVectorToVector(loc)) < 0.0f)
return NO;
12868 if ([
self isPlayer] && dot_product(v_up, HPVectorToVector(loc)) > 0.0f)
return NO;
12876 if([
self status] == STATUS_BEING_SCOOPED)
return;
12877 desired_speed = 0.0;
12878 [
self setAITo:@"nullAI.plist"];
12879 behaviour = BEHAVIOUR_TRACTORED;
12880 [
self setStatus:STATUS_BEING_SCOOPED];
12881 [
self addTarget:other];
12882 [
self setOwner:other];
12885 [
self checkScannerIgnoringUnpowered];
12888 for (i = 0; i < n_scanned_ships ; i++)
12892 if (other != scooper && (
id)
self == [scooper primaryTarget])
12906- (void) suppressTargetLost
12914 [
self scoopUpProcess:other processEvents:YES processMessages:YES];
12918- (void) scoopUpProcess:(
ShipEntity *)other processEvents:(BOOL) procEvents processMessages:(BOOL) procMessages
12920 if (other ==
nil)
return;
12926 if (max_cargo && [cargo
count] >= [
self maxAvailableCargoSpace])
12932 switch ([other cargoType])
12949 if ([other commodityType] !=
nil)
12957 if (isPlayer && [other showScoopMessage] && procMessages)
12959 [UNIVERSE clearPreviousMessage];
12961 [UNIVERSE addMessage:OOExpandKey(@"scripted-item-scooped", shipName) forCount:4];
12984 if (co_type ==
nil && co_amount > 0)
12986 co_type = [UNIVERSE getRandomCommodity];
12987 co_amount = [UNIVERSE getRandomAmountOfCommodity:co_type];
12999 if ([other showScoopMessage] && procMessages)
13001 [UNIVERSE clearPreviousMessage];
13003 for (i = 0; i < [[other
crew] count]; i++)
13006 NSString *characterName = [rescuee
name];
13007 if ([rescuee legalStatus])
13009 [UNIVERSE addMessage:OOExpandKey(@"scoop-captured-character", characterName) forCount: 4.5];
13011 else if ([rescuee insuranceCredits])
13013 [UNIVERSE addMessage:OOExpandKey(@"scoop-rescued-character", characterName) forCount: 4.5];
13017 [UNIVERSE addMessage: DESC(@"scoop-got-slave") forCount: 4.5];
13028 if ([other showScoopMessage] && procMessages)
13030 [UNIVERSE clearPreviousMessage];
13031 [UNIVERSE addMessage:[UNIVERSE describeCommodity:co_type amount:co_amount] forCount:4.5];
13035 [cargo insertObject:other atIndex:0];
13038 [shipAI message:@"CARGO_SCOOPED"];
13039 if (max_cargo && [cargo
count] >= [
self maxAvailableCargoSpace]) [shipAI message:
@"HOLD_FULL"];
13043 [
self doScriptEvent:OOJSID("shipScoopedOther") withArgument:other];
13048 if ([other status] != STATUS_IN_HOLD)
13050 if ([cargo containsObject:other])
13052 [cargo removeObject:other];
13061 [
self checkScannerIgnoringUnpowered];
13064 for (i = 0; i < n_scanned_ships ; i++)
13067 if (
self != scooper && (
id) other == [scooper primaryTargetWithoutValidityCheck])
13073 [
self suppressTargetLost];
13074 [UNIVERSE removeEntity:other];
13078- (BOOL) cascadeIfAppropriateWithDamageAmount:(
double)amount cascadeOwner:(
Entity *)owner
13081 switch ([
self scanClass])
13083 case CLASS_WORMHOLE:
13086 case CLASS_VISUAL_EFFECT:
13089 if ((fuel >
MIN_FUEL) || isStation)
13095 case CLASS_STATION:
13099 case CLASS_MILITARY:
13100 case CLASS_THARGOID:
13101 case CLASS_MISSILE:
13102 case CLASS_NOT_SET:
13103 case CLASS_NO_DRAW:
13104 case CLASS_NEUTRAL:
13107 if (energy < amount && energy > 10 && [
self countsAsKill])
13119- (void) takeEnergyDamage:(
double)amount from:(
Entity *)ent becauseOf:(
Entity *)other weaponIdentifier:(NSString *)weaponIdentifier
13121 if ([
self status] == STATUS_DEAD)
return;
13122 if (amount <= 0.0)
return;
13128 cascade = [
self cascadeIfAppropriateWithDamageAmount:amount cascadeOwner:[ent
owner]];
13149 if (hunter ==
nil && [other isShip]) hunter = (
ShipEntity *)other;
13152 if ((other)&&([other
isShip]))
13157 if (hunter !=
nil && [
self owner] != hunter)
13159 if ([hunter isCloaked])
13161 [
self doScriptEvent:OOJSID("shipBeingAttackedByCloaked") andReactToAIMessage:@"ATTACKED_BY_CLOAKED"];
13176 BOOL iAmTheLaw = [
self isPolice];
13177 BOOL uAreTheLaw = [hunter
isPolice];
13181 [
self setPrimaryAggressor:hunter];
13182 [
self setFoundTarget:hunter];
13185 [
self broadcastHitByLaserFrom: hunter];
13190 [
self respondToAttackFrom:ent becauseOf:hunter];
13195 if (![
self hasNewAI])
13198 if (group !=
nil && group != [hunter group] && !(iAmTheLaw || uAreTheLaw))
13200 if ([
self isTrader] || [
self isEscort])
13203 if (groupLeader !=
self)
13211 if ([
self isPirate])
13213 NSEnumerator *groupEnum =
nil;
13216 for (groupEnum = [group mutationSafeEnumerator]; (otherPirate = [groupEnum nextObject]); )
13218 if (otherPirate !=
self &&
randf() < 0.5)
13226 else if (iAmTheLaw)
13228 NSEnumerator *groupEnum =
nil;
13231 for (groupEnum = [group mutationSafeEnumerator]; (otherPolice = [groupEnum nextObject]); )
13233 if (otherPolice !=
self)
13245 if (iAmTheLaw && !uAreTheLaw)
13248 if (![
self hasNewAI])
13254 if ((group !=
nil && [hunter group] == group) || (iAmTheLaw && uAreTheLaw))
13257 if ([hunter behaviour] == BEHAVIOUR_ATTACK_FLY_TO_TARGET)
13259 [hunter
setBehaviour:BEHAVIOUR_ATTACK_FLY_FROM_TARGET];
13267 if (suppressExplosion) damageType = kOODamageTypeRemoved;
13268 else if (energyMine) damageType = kOODamageTypeCascadeWeapon;
13270 if (!suppressExplosion)
13272 [
self noteTakingDamage:amount from:other type:damageType];
13273 if (cascade) energy = 0.0;
13283 [
self getDestroyedBy:other damageType:damageType];
13289 if (energy < maxEnergy * 0.25)
13291 [
self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"];
13293 if ((energy < maxEnergy *0.125 || (energy < 64 && energy < amount*2)) && [
self hasEscapePod] && (
ranrot_rand() & 3) == 0)
13295 [
self abandonShip];
13301- (BOOL) abandonShip
13304 if ([
self isPlayer] && [(
PlayerEntity *)
self isDocked])
13306 OOLog(
@"ShipEntity.abandonShip.failed",
@"%@",
@"Player cannot abandon ship while docked.");
13310 if (![
self hasEscapePod])
13312 OOLog(
@"ShipEntity.abandonShip.failed",
@"Ship abandonment was requested for %@, but this ship does not carry escape pod(s).",
self);
13318 if (![
self isPlayer])
13322 while ([
self hasEquipmentItemProviding:
@"EQ_ESCAPE_POD"])
13324 [
self removeEquipmentItem:[
self equipmentItemProviding:@"EQ_ESCAPE_POD"]];
13326 [
self setAITo:@"nullAI.plist"];
13327 behaviour = BEHAVIOUR_IDLE;
13329 [
self setScanClass: CLASS_CARGO];
13330 thrust = thrust * 0.5;
13331 if (thrust > 5) thrust = 5;
13332 desired_speed = 0.0;
13333 if ([
self group]) [
self setGroup:nil];
13334 if (![
self isSubEntity] && [
self owner]) [
self setOwner:nil];
13335 if ([
self hasEscorts])
13338 NSEnumerator *escortEnum =
nil;
13341 for (escortEnum = [[
self escortArray] objectEnumerator]; (escort = [escortEnum nextObject]); )
13344 if ([escort group] == escortGroup) [escort
setGroup:nil];
13345 if ([escort owner] ==
self) [escort
setOwner:escort];
13349 [_escortGroup release];
13350 _escortGroup =
nil;
13354 else if (
EXPECT([
self isSubEntity]))
13358 while ([
self hasEquipmentItemProviding:
@"EQ_ESCAPE_POD"])
13360 [
self removeEquipmentItem:[
self equipmentItemProviding:@"EQ_ESCAPE_POD"]];
13367 OOLog(
@"ShipEntity.abandonShip.notPossible",
@"Ship %@ cannot be abandoned at this time.",
self);
13373- (void) takeScrapeDamage:(
double) amount from:(
Entity *)ent
13375 if ([
self status] == STATUS_DEAD)
return;
13377 if ([
self status] == STATUS_LAUNCHING|| [ent status] == STATUS_LAUNCHING)
13384 [
self noteTakingDamage:amount from:ent type:kOODamageTypeScrape];
13389 float frag_chance = [ent
mass]*10/[
self mass];
13396 if (
randf() < frag_chance)
13404 [
self getDestroyedBy:ent damageType:kOODamageTypeScrape];
13409 if (energy < maxEnergy * 0.25)
13411 [
self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"];
13417- (void) takeHeatDamage:(
double)amount
13419 if ([
self status] == STATUS_DEAD)
return;
13421 if ([
self isSubEntity])
13424 if (![owner isFrangible])
13431 throw_sparks = YES;
13433 [
self noteTakingDamage:amount from:nil type:kOODamageTypeHeat];
13438 [
self getDestroyedBy:nil damageType:kOODamageTypeHeat];
13443 if (energy < maxEnergy * 0.25)
13445 [
self doScriptEvent:OOJSID("shipEnergyIsLow") andReactToAIMessage:@"ENERGY_LOW"];
13454 if (dockingInstructions !=
nil)
13456 [dockingInstructions autorelease];
13457 dockingInstructions =
nil;
13460 [
self doScriptEvent:OOJSID("shipWillDockWithStation") withArgument:station];
13461 [
self doScriptEvent:OOJSID("shipDockedWithStation") withArgument:station];
13462 [shipAI message:@"DOCKED"];
13464 [UNIVERSE removeEntity:self];
13471 if (station ==
nil)
return;
13480 [
self enterWormhole:w_hole replacing:YES];
13484- (void) enterWormhole:(
WormholeEntity *) w_hole replacing:(BOOL)replacing
13486 if (w_hole ==
nil)
return;
13487 if ([
self status] == STATUS_ENTERING_WITCHSPACE)
13496 [
self addTarget:w_hole];
13497 [
self setFoundTarget:w_hole];
13498 [shipAI reactToMessage:@"WITCHSPACE OKAY" context:@"performHyperSpaceExit"];
13501 if ([[
self escortArray]
count] > 1)
13504 [
self wormholeEscorts];
13507 if ([
self scriptedMisjump])
13509 [
self setScriptedMisjump:NO];
13511 [
self setScriptedMisjumpRange:0.5];
13517- (void) enterWitchspace
13519 [UNIVERSE addWitchspaceJumpEffectForShip:self];
13520 [shipAI message:@"ENTERED_WITCHSPACE"];
13525 [UNIVERSE witchspaceShipWithPrimaryRole:[
self primaryRole]];
13528 [UNIVERSE removeEntity:self];
13532- (void) leaveWitchspace
13539 GLfloat min_d1 = [UNIVERSE safeWitchspaceExitDistance];
13541 while (fabs(d1) < min_d1)
13547 HPVector exitposition = [UNIVERSE getWitchspaceExitPosition];
13548 exitposition.x += v1.
x * d1;
13549 exitposition.y += v1.
y * d1;
13550 exitposition.z += v1.z * d1;
13551 [
self setPosition:exitposition];
13552 [
self witchspaceLeavingEffects];
13556- (BOOL) witchspaceLeavingEffects
13559 orientation = [UNIVERSE getWitchspaceExitRotation];
13566 flightSpeed = 50.0;
13575 [
self setStatus:STATUS_EXITING_WITCHSPACE];
13576 [shipAI message:@"EXITED_WITCHSPACE"];
13578 [UNIVERSE addWitchspaceJumpEffectForShip:self];
13579 [
self setStatus:STATUS_IN_FLIGHT];
13584- (void) markAsOffender:(
int)offence_value
13586 [
self markAsOffender:offence_value withReason:kOOLegalStatusReasonUnknown];
13592 if (![
self isPolice] && ![
self isCloaked] &&
self != [
UNIVERSE station])
13594 if ([
self isSubEntity])
13596 [[
self parentEntity] markAsOffender:offence_value withReason:reason];
13600 if ((scanClass == CLASS_THARGOID || scanClass == CLASS_STATION) && reason != kOOLegalStatusReasonSetup && reason != kOOLegalStatusReasonByScript)
13607 jsval amountVal = JSVAL_VOID;
13608 JS_NewNumberValue(context, (bounty | offence_value)-bounty, &amountVal);
13610 bounty |= offence_value;
13614 ShipScriptEvent(context,
self,
"shipBountyChanged", amountVal, reasonVal);
13624- (void) switchLightsOn
13629 _lightsActive = YES;
13631 foreach (se, [
self flasherEnumerator])
13635 foreach (sub, [
self shipSubEntityEnumerator])
13642- (void) switchLightsOff
13647 _lightsActive = NO;
13649 foreach (se, [
self flasherEnumerator])
13653 foreach (sub, [
self shipSubEntityEnumerator])
13660- (BOOL) lightsActive
13662 return _lightsActive;
13666- (void) setDestination:(HPVector) dest
13668 _destination = dest;
13673- (void) setEscortDestination:(HPVector) dest
13675 _destination = dest;
13679- (BOOL) canAcceptEscort:(
ShipEntity *)potentialEscort
13681 if (dockingInstructions)
13685 if (scanClass != [potentialEscort scanClass])
13689 if ([
self bounty] == 0 && [potentialEscort bounty] != 0)
13693 if (![
self isEscort])
13695 return [potentialEscort
isEscort];
13701- (BOOL) acceptAsEscort:(
ShipEntity *) other_ship
13704 if (
self == other_ship)
return NO;
13707 if ([
self status] != STATUS_IN_FLIGHT)
return NO;
13711 if ([shipAI stackDepth] > 3)
13713 OOLog(
@"ship.escort.reject",
@"%@ rejecting escort %@ because AI stack depth is %llu.",
self, other_ship, [shipAI stackDepth]);
13717 if ([
self canAcceptEscort:other_ship])
13721 if ([escortGroup containsShip:other_ship]) return YES;
13725 if (_maxEscortCount == 0 && ([
self hasPrimaryRole:
@"police"] || [
self hasPrimaryRole:
@"hunter"] || [
self hasRole:
@"thargoid-mothership"]))
13730 NSUInteger maxEscorts = _maxEscortCount;
13731 NSUInteger escortCount = [escortGroup
count] - 1;
13733 if (escortCount < maxEscorts)
13735 [other_ship
setGroup:escortGroup];
13736 if ([
self group] ==
nil)
13738 [
self setGroup:escortGroup];
13740 else if ([
self group] != escortGroup) [[
self group] addShip:other_ship];
13742 if (([other_ship maxFlightSpeed] < cruiseSpeed) && ([other_ship maxFlightSpeed] > cruiseSpeed * 0.3))
13747 OOLog(
@"ship.escort.accept",
@"%@ accepting escort %@.",
self, other_ship);
13749 [
self doScriptEvent:OOJSID("shipAcceptedEscort") withArgument:other_ship];
13751 [shipAI message:@"ACCEPTED_ESCORT"];
13756 OOLog(
@"ship.escort.reject",
@"%@ already got max escorts(%llu). Escort rejected: %@.",
self, escortCount, other_ship);
13761 OOLog(
@"ship.escort.reject",
@"%@ failed canAcceptEscort for escort %@.",
self, other_ship);
13770- (void) updateEscortFormation
13772 _escortPositionsValid = NO;
13781- (void) refreshEscortPositions
13783 if (!_escortPositionsValid)
13787 jsval args[] = { INT_TO_JSVAL(0), INT_TO_JSVAL(_maxEscortCount) };
13791 _escortPositionsValid = YES;
13794 for (i = 0; i < _maxEscortCount; i++)
13796 args[0] = INT_TO_JSVAL(i);
13797 OK = [script callMethod:OOJSID("coordinatesForEscortPosition")
13799 withArguments:args count:sizeof args / sizeof *args
13812- (HPVector) coordinatesForEscortPosition:(
unsigned)idx
13831- (void) deployEscorts
13835 NSMutableSet *idleEscorts =
nil;
13836 unsigned deployCount;
13838 if ([
self primaryTarget] ==
nil || _escortGroup ==
nil)
return;
13841 NSUInteger escortCount = [escortGroup
count] - 1;
13842 if (escortCount == 0)
return;
13844 if ([
self group] ==
nil) [
self setGroup:escortGroup];
13846 if ([
self primaryTarget] == [
self lastEscortTarget])
13852 [
self setLastEscortTarget:[
self primaryTarget]];
13855 idleEscorts = [NSMutableSet set];
13856 foreach (escort, [
self escortEnumerator])
13858 if (![[[escort getAI] name] isEqualToString:
@"interceptAI.plist"] && ![escort hasNewAI])
13860 [idleEscorts addObject:escort];
13862 else if ([escort hasNewAI])
13869 escortCount = [idleEscorts count];
13870 if (escortCount == 0)
return;
13875 target = [
self primaryTarget];
13876 foreach (escort, idleEscorts)
13879 [escort
setAITo:@"interceptAI.plist"];
13882 if (--deployCount == 0)
break;
13885 [
self updateEscortFormation];
13890- (void) dockEscorts
13892 if (![
self hasEscorts])
return;
13899 foreach (escort, [
self escortArray])
13901 float delay = i++ * 3.0 + 1.5;
13905 if ([escort group] == escortGroup) [escort
setGroup:nil];
13906 if ([escort owner] ==
self) [escort
setOwner:escort];
13909 if (![escort hasNewAI])
13911 [escort
setAITo:@"dockingAI.plist"];
13918 [_escortGroup release];
13919 _escortGroup =
nil;
13923- (void) setTargetToNearestStationIncludingHostiles:(BOOL) includeHostiles
13926 Entity *mother = [[
self group] leader];
13927 if ([mother isStation])
13929 [
self addTarget:mother];
13930 [
self setTargetStation:mother];
13937 int ent_count =
UNIVERSE->n_entities;
13939 Entity *my_entities[ent_count];
13941 int station_count = 0;
13942 for (i = 0; i < ent_count; i++)
13943 if (uni_entities[i]->isStation)
13944 my_entities[station_count++] = [uni_entities[i] retain];
13948 for (i = 0; i < station_count; i++)
13951 range2 = HPdistance2(position, thing->position);
13952 if (range2 < nearest2 && (includeHostiles || ![thing isHostileTo:
self]))
13958 for (i = 0; i < station_count; i++)
13959 [my_entities[i] release];
13963 [
self addTarget:station];
13964 [
self setTargetStation:station];
13968 [shipAI message:@"NO_STATION_FOUND"];
13974- (void) setTargetToNearestFriendlyStation
13976 [
self setTargetToNearestStationIncludingHostiles:NO];
13981- (void) setTargetToNearestStation
13983 [
self setTargetToNearestStationIncludingHostiles:YES];
13988- (void) setTargetToSystemStation
13992 if (!system_station)
13994 [shipAI message:@"NOTHING_FOUND"];
13995 [shipAI message:@"NO_STATION_FOUND"];
13997 [
self setTargetStation:nil];
14003 [shipAI message:@"NOTHING_FOUND"];
14004 [shipAI message:@"NO_STATION_FOUND"];
14006 [
self setTargetStation:nil];
14010 [
self addTarget:system_station];
14011 [
self setTargetStation:system_station];
14018 if (planet && [
self isShuttle])
14022 [
self doScriptEvent:OOJSID("shipLandedOnPlanet") withArgument:planet andReactToAIMessage:@"LANDED_ON_PLANET"];
14025 if ([
self reportAIMessages])
14027 OOLog(
@"planet.collide.shuttleLanded",
@"DEBUG: %@ landed on planet %@",
self, planet);
14031 [UNIVERSE removeEntity:self];
14036- (void) abortDocking
14038 [[UNIVERSE findEntitiesMatchingPredicate:IsStationPredicate
14042 makeObjectsPerformSelector:@selector(abortDockingForShip:) withObject:self];
14046- (NSDictionary *) dockingInstructions
14048 return dockingInstructions;
14052- (void) broadcastThargoidDestroyed
14054 [[UNIVERSE findShipsMatchingPredicate:HasRolePredicate
14055 parameter:@"tharglet"
14056 inRange:SCANNER_MAX_RANGE
14058 makeObjectsPerformSelector:@selector(sendAIMessage:) withObject:@"THARGOID_DESTROYED"];
14062static BOOL AuthorityPredicate(
Entity *entity,
void *parameter)
14067 if (entity == [
UNIVERSE station] && [victim withinStationAegis])
14073 if ([entity scanClass] == CLASS_POLICE &&
14084- (void) broadcastHitByLaserFrom:(
ShipEntity *) aggressor_ship
14088 if ([
self bounty])
return;
14089 if (!aggressor_ship)
return;
14091 if ( (scanClass == CLASS_NEUTRAL)||
14092 (scanClass == CLASS_STATION)||
14093 (scanClass == CLASS_BUOY)||
14094 (scanClass == CLASS_POLICE)||
14095 (scanClass == CLASS_MILITARY)||
14096 (scanClass == CLASS_PLAYER))
14098 NSArray *authorities =
nil;
14101 authorities = [UNIVERSE findShipsMatchingPredicate:AuthorityPredicate
14105 foreach (auth, authorities)
14115- (void) sendMessage:(NSString *) message_text toShip:(
ShipEntity*) other_ship withUnpilotedOverride:(BOOL)unpilotedOverride
14117 if (!other_ship || !message_text)
return;
14118 if (!crew && !unpilotedOverride)
return;
14120 double d2 = HPdistance2(position, [other_ship position]);
14121 if (d2 > scannerRange * scannerRange)
14124 NSString *expandedMessage =
OOExpand(message_text);
14128 [
self setCommsMessageColor];
14131 [UNIVERSE resetCommsLogColor];
14138- (void) sendExpandedMessage:(NSString *)message_text toShip:(
ShipEntity *)other_ship
14140 if (!other_ship || !crew)
14142 if ((lastRadioMessage) && (messageTime > 0.0) && [message_text isEqual:lastRadioMessage])
14144 [lastRadioMessage autorelease];
14145 lastRadioMessage = [message_text retain];
14147 double d2 = HPdistance2(position, [other_ship position]);
14148 if (d2 > scannerRange * scannerRange)
14155 very_random_seed.
a = rand() & 255;
14156 very_random_seed.
b = rand() & 255;
14157 very_random_seed.
c = rand() & 255;
14158 very_random_seed.
d = rand() & 255;
14159 very_random_seed.
e = rand() & 255;
14160 very_random_seed.
f = rand() & 255;
14163 NSDictionary *specials = [NSDictionary dictionaryWithObjectsAndKeys:
14164 [
self displayName], @"[self:name]",
14169 [
self sendMessage:expandedMessage toShip:other_ship withUnpilotedOverride:NO];
14173- (void) broadcastAIMessage:(NSString *) ai_message
14175 NSString *expandedMessage =
OOExpand(ai_message);
14177 [
self checkScanner];
14179 for (i = 0; i < n_scanned_ships ; i++)
14187- (void) broadcastMessage:(NSString *) message_text withUnpilotedOverride:(BOOL) unpilotedOverride
14189 NSString *expandedMessage =
OOExpand(message_text);
14192 if (!crew && !unpilotedOverride)
14195 [
self checkScanner];
14197 for (i = 0; i < n_scanned_ships ; i++)
14200 if (![ship isPlayer]) [ship receiveCommsMessage:expandedMessage from:
self];
14208 [
self setCommsMessageColor];
14211 [UNIVERSE resetCommsLogColor];
14216- (void) setCommsMessageColor
14218 float hue = 0.0625f * (universalID & 15);
14220 if (scanClass == CLASS_THARGOID)
14222 if (scanClass == CLASS_POLICE)
14227- (void) receiveCommsMessage:(NSString *) message_text from:(
ShipEntity *) other
14230 [
self doScriptEvent:OOJSID("commsMessageReceived") withArgument:message_text andArgument:other];
14234- (void) commsMessage:(NSString *)valueString withUnpilotedOverride:(BOOL)unpilotedOverride
14237 very_random_seed.
a = rand() & 255;
14238 very_random_seed.
b = rand() & 255;
14239 very_random_seed.
c = rand() & 255;
14240 very_random_seed.
d = rand() & 255;
14241 very_random_seed.
e = rand() & 255;
14242 very_random_seed.
f = rand() & 255;
14245 [
self broadcastMessage:valueString withUnpilotedOverride:unpilotedOverride];
14249- (BOOL) markedForFines
14251 return being_fined;
14255- (BOOL) markForFines
14259 being_fined = ([
self legalStatus] > 0);
14260 return being_fined;
14266 return ((behaviour == BEHAVIOUR_ATTACK_MINING_TARGET)&&([forward_weapon_type isMiningLaser]));
14270- (void) interpretAIMessage:(NSString *)ms
14277 int switcher_id = [(NSString*)[tokens objectAtIndex:1] intValue];
14278 Entity* switcher = [UNIVERSE entityForUniversalID:switcher_id];
14279 int rescuer_id = [(NSString*)[tokens objectAtIndex:2] intValue];
14280 Entity* rescuer = [UNIVERSE entityForUniversalID:rescuer_id];
14281 if ((switcher == [
self primaryAggressor])&&(switcher == [
self primaryTarget])&&(switcher)&&(rescuer)&&(rescuer->
isShip)&&([
self thankedShip] != rescuer)&&(scanClass != CLASS_THARGOID))
14285 if (scanClass == CLASS_POLICE)
14287 [
self sendExpandedMessage:@"[police-thanks-for-assist]" toShip:rescueShip];
14292 [
self sendExpandedMessage:@"[thanks-for-assist]" toShip:rescueShip];
14294 [
self setThankedShip:rescuer];
14300- (BoundingBox) findBoundingBoxRelativeTo:(
Entity *)other InVectors:(Vector) _i :(Vector) _j :(Vector) _k
14302 HPVector opv = other ? other->
position : position;
14303 return [
self findBoundingBoxRelativeToPosition:opv InVectors:_i :_j :_k];
14308- (void) spawn:(NSString *)roles_number
14311 NSString *roleString =
nil;
14312 NSString *numberString =
nil;
14315 if ([tokens
count] != 2)
14321 roleString = [tokens oo_stringAtIndex:0];
14322 numberString = [tokens oo_stringAtIndex:1];
14324 number = [numberString intValue];
14326 [
self spawnShipsWithRole:roleString count:number];
14330- (
int) checkShipsInVicinityForWitchJumpExit
14345 int ent_count =
UNIVERSE->n_entities;
14350 int ship_count = 0;
14351 for (i = 0; i < ent_count; i++)
14352 if ((uni_entities[i]->isShip)&&(uni_entities[i] !=
self))
14353 my_entities[ship_count++] = (
ShipEntity*)[uni_entities[i] retain];
14355 for (i = 0; (i < ship_count)&&(result ==
NO_TARGET) ; i++)
14358 HPVector delta = HPvector_between(position, ship->
position);
14359 GLfloat d2 = HPmagnitude2(delta);
14360 if (![ship isPlayer] || ![
PLAYER isDocked])
14366 for (i = 0; i < ship_count; i++)
14367 [my_entities[i] release];
14373- (BOOL) trackCloseContacts
14375 return trackCloseContacts;
14379- (void) setTrackCloseContacts:(BOOL) value
14381 if (value == (BOOL)trackCloseContacts)
return;
14383 trackCloseContacts = value;
14384 [closeContactsInfo release];
14386 if (trackCloseContacts)
14388 closeContactsInfo = [[NSMutableDictionary alloc] init];
14392 closeContactsInfo =
nil;
14397#if OO_SALVAGE_SUPPORT
14399- (void) claimAsSalvage
14403 OOLog(
@"claimAsSalvage.called",
@"claimAsSalvage called on %@ %@", [
self name], [
self roleSet]);
14406 if (![
self isHulk])
14408 OOLog(
@"claimAsSalvage.failed.notHulk",
@"claimAsSalvage failed because not a hulk");
14413 [
self setTargetToSystemStation];
14414 if ([
self primaryTarget] ==
nil)
14416 OOLog(
@"claimAsSalvage.failed.noStation",
@"claimAsSalvage failed because did not find a station");
14422 OOLog(
@"claimAsSalvage.requestingPilot",
@"claimAsSalvage asking station to launch a pilot boat");
14424 [
self setReportAIMessages:YES];
14425 OOLog(
@"claimAsSalvage.success",
@"claimAsSalvage setting own state machine to capturedShipAI.plist");
14426 [
self setAITo:@"capturedShipAI.plist"];
14430- (void) sendCoordinatesToPilot
14435 n_scanned_ships = 0;
14437 OOLog(
@"ship.pilotage",
@"searching for pilot boat");
14438 while (scan &&(scan->
isShip == NO))
14450 if ([
self hasRole:
@"pilot"] == YES)
14452 if ([scanShip primaryTarget] ==
nil)
14454 OOLog(
@"ship.pilotage",
@"found pilot boat with no target, will use this one");
14462 while (scan && (scan->
isShip == NO))
14470 OOLog(
@"ship.pilotage",
@"becoming pilot target and setting AI");
14473 [pilot
setAITo:@"pilotAI.plist"];
14474 [
self reactToAIMessage:@"FOUND_PILOT" context:@"flight update"];
14479- (void) pilotArrived
14482 [
self reactToAIMessage:@"PILOT_ARRIVED" context:@"flight update"];
14488- (void)dumpSelfState
14490 NSMutableArray *flags =
nil;
14491 NSString *flagsString =
nil;
14493 [
super dumpSelfState];
14495 OOLog(
@"dumpState.shipEntity",
@"Type: %@", [
self shipDataKey]);
14496 OOLog(
@"dumpState.shipEntity",
@"Name: %@", name);
14497 OOLog(
@"dumpState.shipEntity",
@"Display Name: %@", [
self displayName]);
14498 OOLog(
@"dumpState.shipEntity",
@"Roles: %@", [
self roleSet]);
14499 OOLog(
@"dumpState.shipEntity",
@"Primary role: %@", primaryRole);
14500 OOLog(
@"dumpState.shipEntity",
@"Script: %@", script);
14501 OOLog(
@"dumpState.shipEntity",
@"Subentity count: %llu", [
self subEntityCount]);
14503 id target = [
self primaryTarget];
14504 if (target ==
nil) target =
@"<none>";
14505 OOLog(
@"dumpState.shipEntity",
@"Target: %@", target);
14506 OOLog(
@"dumpState.shipEntity",
@"Destination: %@", HPVectorDescription(_destination));
14507 OOLog(
@"dumpState.shipEntity",
@"Other destination: %@", HPVectorDescription(coordinates));
14508 OOLog(
@"dumpState.shipEntity",
@"Waypoint count: %u", number_of_navpoints);
14509 OOLog(
@"dumpState.shipEntity",
@"Desired speed: %g", desired_speed);
14510 OOLog(
@"dumpState.shipEntity",
@"Thrust: %g", thrust);
14511 if ([
self escortCount] != 0)
OOLog(
@"dumpState.shipEntity",
@"Escort count: %u", [
self escortCount]);
14512 OOLog(
@"dumpState.shipEntity",
@"Fuel: %i", fuel);
14513 OOLog(
@"dumpState.shipEntity",
@"Fuel accumulator: %g", fuel_accumulator);
14514 OOLog(
@"dumpState.shipEntity",
@"Missile count: %u", missiles);
14518 OOLog(
@"dumpState.shipEntity.ai",
@"%@",
@"AI:");
14523 [shipAI dumpState];
14525 @catch (
id exception) {}
14528 OOLog(
@"dumpState.shipEntity",
@"Accuracy: %g", accuracy);
14529 OOLog(
@"dumpState.shipEntity",
@"Jink position: %@", VectorDescription(jink));
14530 OOLog(
@"dumpState.shipEntity",
@"Frustration: %g", frustration);
14531 OOLog(
@"dumpState.shipEntity",
@"Success factor: %g", success_factor);
14532 OOLog(
@"dumpState.shipEntity",
@"Shots fired: %u", shot_counter);
14533 OOLog(
@"dumpState.shipEntity",
@"Time since shot: %g", [
self shotTime]);
14534 OOLog(
@"dumpState.shipEntity",
@"Spawn time: %g (%g seconds ago)", [
self spawnTime], [
self timeElapsedSinceSpawn]);
14535 if ([
self isBeacon])
14537 OOLog(
@"dumpState.shipEntity",
@"Beacon code: %@", [
self beaconCode]);
14539 OOLog(
@"dumpState.shipEntity",
@"Hull temperature: %g", ship_temperature);
14540 OOLog(
@"dumpState.shipEntity",
@"Heat insulation: %g", [
self heatInsulation]);
14542 flags = [NSMutableArray array];
14543 #define ADD_FLAG_IF_SET(x) if (x) { [flags addObject:@#x]; }
14557 flagsString = [flags count] ? [flags componentsJoinedByString:@", "] : (NSString *)
@"none";
14558 OOLog(
@"dumpState.shipEntity",
@"Flags: %@", flagsString);
14569- (NSDictionary *)scriptInfo
14571 return (scriptInfo !=
nil) ? scriptInfo : (NSDictionary *)[NSDictionary dictionary];
14575- (void) overrideScriptInfo:(NSDictionary *)override
14577 if (scriptInfo ==
nil) scriptInfo = [override retain];
14578 else if (
override !=
nil)
14580 NSMutableDictionary *newInfo = [NSMutableDictionary dictionaryWithDictionary:scriptInfo];
14581 [newInfo addEntriesFromDictionary:override];
14582 [scriptInfo release];
14583 scriptInfo = [newInfo copy];
14588- (
Entity *)entityForShaderProperties
14590 return [
self rootShipEntity];
14593- (void) setDemoShip: (
OOScalar) rate
14595 demoStartOrientation = orientation;
14598 [
self setPitch: 0.0f];
14599 [
self setRoll: 0.0f];
14609 demoStartTime = time;
14614 return demoStartTime;
14618- (void) doScriptEvent:(jsid)message
14621 [
self doScriptEvent:message inContext:context withArguments:NULL count:0];
14626- (void) doScriptEvent:(jsid)message withArgument:(
id)argument
14631 [
self doScriptEvent:message inContext:context withArguments:&value count:1];
14637- (void) doScriptEvent:(jsid)message
14638 withArgument:(
id)argument1
14639 andArgument:(
id)argument2
14644 [
self doScriptEvent:message inContext:context withArguments:argv count:2];
14650- (void) doScriptEvent:(jsid)message withArguments:(NSArray *)arguments
14654 jsval *argv = NULL;
14657 argc = (uintN)[arguments
count];
14660 argv = malloc(
sizeof *argv * argc);
14663 for (i = 0; i != argc; ++i)
14665 argv[i] = [[arguments objectAtIndex:i] oo_jsValueInContext:context];
14672 [
self doScriptEvent:message inContext:context withArguments:argv count:argc];
14677 for (i = 0; i != argc; ++i)
14679 JS_RemoveValueRoot(context, &argv[i]);
14688- (void) doScriptEvent:(jsid)message withArguments:(jsval *)argv count:(uintN)argc
14691 [
self doScriptEvent:message inContext:context withArguments:argv count:argc];
14696- (void) doScriptEvent:(jsid)message inContext:(JSContext *)context withArguments:(jsval *)argv count:(uintN)argc
14699 [script callMethod:message inContext:context withArguments:argv count:argc result:NULL];
14700 [aiScript callMethod:message inContext:context withArguments:argv count:argc result:NULL];
14704- (void) reactToAIMessage:(NSString *)message context:(NSString *)debugContext
14706 [shipAI reactToMessage:message context:debugContext];
14710- (void) sendAIMessage:(NSString *)message
14712 [shipAI message:message];
14716- (void) doScriptEvent:(jsid)scriptEvent andReactToAIMessage:(NSString *)aiMessage
14718 [
self doScriptEvent:scriptEvent];
14719 [
self reactToAIMessage:aiMessage context:nil];
14723- (void) doScriptEvent:(jsid)scriptEvent withArgument:(
id)argument andReactToAIMessage:(NSString *)aiMessage
14725 [
self doScriptEvent:scriptEvent withArgument:argument];
14726 [
self reactToAIMessage:aiMessage context:nil];
14734 if ([
self status] == STATUS_DOCKED)
14738 if ([
self hasHostileTarget] || energy < maxEnergy / 4)
14748 if ([
self status] == STATUS_DOCKED)
14752 if ([
self hasHostileTarget])
14758 NSEnumerator *sEnum = [_defenseTargets objectEnumerator];
14760 double scanrange2 = scannerRange * scannerRange;
14762 foreach (ship, sEnum)
14764 if ([ship hasHostileTarget] || ([ship isPlayer] && [
PLAYER weaponsOnline]))
14766 if (HPdistance2([ship position],position) < scanrange2)
14773 if ([
self hasHostileTarget])
14775 Entity *ptarget = [
self primaryTargetWithoutValidityCheck];
14776 if (ptarget !=
nil && [ptarget isShip])
14779 if ([ship hasHostileTarget] || ([ship isPlayer] && [
PLAYER weaponsOnline]))
14781 if (HPdistance2([ship position],position) < scanrange2 * 1.5625)
14790 sEnum = [_group objectEnumerator];
14791 while ((ship = [sEnum nextObject]))
14793 if ([ship hasHostileTarget] || ([ship isPlayer] && [
PLAYER weaponsOnline]))
14795 if (HPdistance2([ship position],position) < scanrange2)
14802 if (_escortGroup && _group != _escortGroup)
14804 sEnum = [_escortGroup objectEnumerator];
14805 while ((ship = [sEnum nextObject]))
14807 if ([ship hasHostileTarget] || ([ship isPlayer] && [
PLAYER weaponsOnline]))
14809 if (HPdistance2([ship position],position) < scanrange2)
14829- (NSString *) descriptionForObjDump
14831 NSString *desc = [
super descriptionForObjDump];
14832 desc = [NSString stringWithFormat:@"%@ mass %g", desc, [
self mass]];
14833 if (![
self isPlayer])
14835 desc = [NSString stringWithFormat:@"%@ AI: %@", desc, [[
self getAI] shortDescriptionComponents]];
14844@implementation Entity (SubEntityRelationship)
14846- (BOOL) isShipWithSubEntityShip:(
Entity *)other
14852- (void) drawSubEntityImmediate:(
bool)immediate translucent:(
bool)translucent
14860@implementation ShipEntity (SubEntityRelationship)
14862- (BOOL) isShipWithSubEntityShip:(
Entity *)other
14864 assert ([
self isShip]);
14866 if (![other isShip])
return NO;
14867 if (![other isSubEntity])
return NO;
14868 if ([other owner] !=
self)
return NO;
14872 if (![
self hasSubEntity:(
ShipEntity *)other])
14874 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.");
14888 static NSDictionary *macros =
nil;
14901 static NSSet *entityWhitelist =
nil;
14902 static NSSet *shipWhitelist =
nil;
14903 static NSSet *playerShipWhitelist =
nil;
14904 static NSSet *visualEffectWhitelist =
nil;
14906 if (entityWhitelist ==
nil)
14909 entityWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_entity_binding_methods"]];
14910 shipWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_ship_binding_methods"]];
14911 playerShipWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_player_ship_binding_methods"]];
14912 visualEffectWhitelist = [[NSSet alloc] initWithArray:[wlDict oo_arrayForKey:@"shader_visual_effect_binding_methods"]];
14915 if ([bindingTarget isKindOfClass:[
Entity class]])
14917 if ([entityWhitelist containsObject:propertyName])
return YES;
14918 if ([bindingTarget isShip])
14920 if ([shipWhitelist containsObject:propertyName])
return YES;
14922 if ([bindingTarget isPlayerLikeShip])
14924 if ([playerShipWhitelist containsObject:propertyName])
return YES;
14926 if ([bindingTarget isVisualEffect])
14928 if ([visualEffectWhitelist containsObject:propertyName])
return YES;
14944 return weapon ==
nil || [[weapon
identifier] isEqualToString:@"EQ_WEAPON_NONE"];
#define NO_DRAW_DISTANCE_FACTOR
OOScanClass OOScanClassFromString(NSString *string) PURE_FUNC
#define SCANNER_MAX_RANGE
#define SCANNER_MAX_RANGE2
#define ADD_FLAG_IF_SET(x)
#define foreachkey(VAR, DICT)
OOINLINE jsval OOJSValueFromLegalStatusReason(JSContext *context, OOLegalStatusReason value)
OOINLINE jsval OOJSValueFromShipDamageType(JSContext *context, OOShipDamageType value)
OOCargoType StringToCargoType(NSString *string) PURE_FUNC
NSString * OOStringFromLegalStatusReason(OOLegalStatusReason reason)
OOINLINE void OODebugDrawColoredBoundingBox(BoundingBox box, OOColor *color)
OOINLINE void OODebugDrawBoundingBox(BoundingBox box)
void OODebugDrawColoredLine(Vector start, Vector end, OOColor *color)
void OODebugDrawPoint(Vector position, OOColor *color)
void OOStandardsDeprecated(NSString *message)
BOOL OOEnforceStandards(void)
const HPVector kZeroHPVector
HPVector OOHPVectorRandomSpatial(OOHPScalar maxLength)
#define OOJS_PROFILE_EXIT
#define OOJS_PROFILE_ENTER
BOOL JSValueToVector(JSContext *context, jsval value, Vector *outVector) NONNULL_FUNC
OOINLINE jsval OOJSValueFromNativeObject(JSContext *context, id object)
id OOJSNativeObjectFromJSObject(JSContext *context, JSObject *object)
OOINLINE JSContext * OOJSAcquireContext(void)
OOINLINE void OOJSRelinquishContext(JSContext *context)
#define OOJSAddGCValueRoot(context, root, name)
void OOLogPushIndent(void)
#define OOLogWARN(class, format,...)
#define OOLogERR(class, format,...)
void OOLogPopIndent(void)
BOOL OOLogWillDisplayMessagesInClass(NSString *inMessageClass)
#define OOLog(class, format,...)
HPVector OOHPVectorMultiplyMatrix(HPVector v, OOMatrix m)
const OOMatrix kIdentityMatrix
Vector OOVectorMultiplyMatrix(Vector v, OOMatrix m)
void OOGLPushModelView(void)
void OOGLTranslateModelView(Vector vector)
void OOGLMultModelView(OOMatrix matrix)
OOMatrix OOGLPopModelView(void)
#define OOVerifyOpenGLState()
Vector vector_up_from_quaternion(Quaternion quat)
void quaternion_rotate_about_x(Quaternion *quat, OOScalar angle)
Vector vector_right_from_quaternion(Quaternion quat)
Vector vector_forward_from_quaternion(Quaternion quat)
void quaternion_rotate_about_z(Quaternion *quat, OOScalar angle)
void quaternion_set_random(Quaternion *quat)
Vector quaternion_rotate_vector(Quaternion q, Vector v)
const Quaternion kIdentityQuaternion
Quaternion quaternion_rotation_between(Vector v0, Vector v1)
void quaternion_rotate_about_y(Quaternion *quat, OOScalar angle)
const Quaternion kZeroQuaternion
void quaternion_rotate_about_axis(Quaternion *quat, Vector axis, OOScalar angle)
Quaternion quaternion_multiply(Quaternion q1, Quaternion q2)
@ STELLAR_TYPE_NORMAL_PLANET
Random_Seed OOStringExpanderDefaultRandomSeed(void)
NSString * OOExpandDescriptionString(Random_Seed seed, NSString *string, NSDictionary *overrides, NSDictionary *legacyLocals, NSString *systemName, OOExpandOptions options)
#define OOExpand(string,...)
NSMutableArray * ScanTokensFromString(NSString *values)
BOOL ScanVectorFromString(NSString *xyzString, Vector *outVector)
uint8_t OOWeaponFacingSet
NSString * OOCommodityType
@ AEGIS_CLOSE_TO_MAIN_PLANET
@ AEGIS_CLOSE_TO_ANY_PLANET
uint64_t OOCreditsQuantity
#define VALID_WEAPON_FACINGS
@ CARGO_FLAG_FULL_CONTRABAND
@ CARGO_FLAG_FULL_PLENTIFUL
@ CARGO_FLAG_FULL_UNIFORM
@ CARGO_FLAG_FULL_MEDICAL
@ CARGO_FLAG_FULL_PASSENGERS
@ WEAPON_FACING_STARBOARD
const Vector kBasisYVector
const Vector kBasisZVector
Vector OORandomPositionInBoundingBox(BoundingBox bb)
const Vector kBasisXVector
static GLfloat scripted_color[4]
static BOOL isHitByOctree(Octree_details axialDetails, Octree_details otherDetails, Vector delta, Triangle other_ijk)
static NSString *const kOOLogSyntaxAddShips
#define HYPERSPEED_FACTOR
@ SCOOP_STATUS_NOT_INSTALLED
#define MIN_HYPERSPEED_FACTOR
#define INITIAL_SHOT_TIME
#define COMBAT_BROADSIDE_IN_RANGE_FACTOR
BOOL isWeaponNone(OOWeaponType weapon)
#define COMBAT_AI_TRACKS_CLOSER
#define SHIP_COOLING_FACTOR
#define CLOAKING_DEVICE_MIN_ENERGY
#define COMBAT_AI_WEAPON_TEMP_READY
OOWeaponType OOWeaponTypeFromEquipmentIdentifierStrict(NSString *string) PURE_FUNC
#define COMBAT_BROADSIDE_RANGE_FACTOR
#define COMBAT_AI_WEAPON_TEMP_USABLE
#define ShipScriptEventNoCx(ship, event,...)
#define TURRET_SHOT_RANGE
#define COMBAT_AI_ISNT_AWFUL
#define TURRET_MINIMUM_COS
NSDictionary * OODefaultShipShaderMacros(void)
#define SHIP_MIN_CABIN_TEMP
#define CLOAKING_DEVICE_START_ENERGY
#define NPC_MAX_WEAPON_TEMP
#define COMBAT_AI_FLEES_BETTER_2
#define MILITARY_JAMMER_ENERGY_RATE
#define CLOAKING_DEVICE_ENERGY_RATE
#define AIMS_AGGRESSOR_SWITCHED_TARGET
#define COMBAT_AI_DOGFIGHTER
#define WEAPON_COOLING_FACTOR
#define ENTITY_PERSONALITY_MAX
GLfloat getWeaponRangeFromType(OOWeaponType weapon_type)
#define SHIP_MAX_CABIN_TEMP
#define MILITARY_JAMMER_MIN_ENERGY
#define COMBAT_AI_FLEES_BETTER
#define TURRET_SHOT_SPEED
#define COMBAT_IN_RANGE_FACTOR
#define COMBAT_OUT_RANGE_FACTOR
#define COMBAT_WEAPON_RANGE_FACTOR
#define COMBAT_AI_CONFIDENCE_FACTOR
#define COMBAT_AI_IS_SMART
NSString * OOStringFromBehaviour(OOBehaviour behaviour) CONST_FUNC
OOWeaponType OOWeaponTypeFromEquipmentIdentifierSloppy(NSString *string) PURE_FUNC
#define BASELINE_SHIELD_LEVEL
#define SHIPENTITY_MAX_MISSILES
#define MAX_LANDING_SPEED2
#define SHIP_THRUST_FACTOR
OOWeaponType OOWeaponTypeFromString(NSString *string) PURE_FUNC
#define ShipScriptEvent(context, ship, event,...)
#define WEAPON_COOLING_CUTOUT
#define SHIP_INSULATION_FACTOR
#define SHIP_ENERGY_DAMAGE_TO_HEAT_FACTOR
static ShipEntity * doOctreesCollide(ShipEntity *prime, ShipEntity *other)
static GLfloat mascem_color1[4]
static GLfloat scripted_color[4]
static GLfloat neutral_color[4]
static GLfloat cargo_color[4]
static GLfloat hostile_color[4]
static NSString *const kOOLogEntityBehaviourChanged
static GLfloat missile_color[4]
static GLfloat police_color1[4]
BOOL OOUniformBindingPermitted(NSString *propertyName, id bindingTarget)
static GLfloat mascem_color2[4]
static GLfloat jammed_color[4]
static GLfloat friendly_color[4]
static GLfloat police_color2[4]
static NSString *const kOOLogSyntaxAddShips
#define PROXIMITY_AVOID_DISTANCE_FACTOR
Entity< OOStellarBody > * lastAegisLock()
void refreshEscortPositions()
void exitStateMachineWithMessage:(NSString *message)
void message:(NSString *ms)
void setState:afterDelay:(NSString *stateName,[afterDelay] NSTimeInterval delay)
void setState:(NSString *stateName)
void drawSubEntityImmediate:translucent:(bool immediate, [translucent] bool translucent)
OOUniversalID universalID
HPVector absolutePositionForSubentity()
NSMutableArray * collisionArray()
void setVelocity:(Vector vel)
void setOrientation:(Quaternion quat)
GLfloat collisionRadius()
void setOwner:(Entity *ent)
void setScanClass:(OOScanClass sClass)
void setDistanceTravelled:(GLfloat value)
ShipEntity * rootShipEntity()
void setEnergy:(GLfloat amount)
Quaternion normalOrientation()
ShipEntity * parentEntity()
void takeEnergyDamage:from:becauseOf:weaponIdentifier:(double amount,[from] Entity *ent,[becauseOf] Entity *other,[weaponIdentifier] NSString *weaponIdentifier)
void setPosition:(HPVector posn)
OOMatrix drawRotationMatrix()
id fragmentBurstFromEntity:(Entity *entity)
OOCharacter * characterWithDictionary:(NSDictionary *c_dict)
void setLegalStatus:(int value)
NSDictionary * infoForScripting()
OOCharacter * randomCharacterWithRole:andOriginalSystem:(NSString *c_role,[andOriginalSystem] OOSystemID s)
OOColor * colorWithRGBAComponents:(OORGBAComponents components)
OOColor * brightColorWithDescription:(id description)
OOColor * colorWithDescription:(id description)
void getRed:green:blue:alpha:(float *red,[green] float *green,[blue] float *blue,[alpha] float *alpha)
OOColor * colorWithHue:saturation:brightness:alpha:(float hue,[saturation] float saturation,[brightness] float brightness,[alpha] float alpha)
NSString * conditionScript()
GLfloat weaponEnergyUse()
GLfloat weaponShotTemperature()
void addEquipmentWithInfo:(NSArray *itemInfo)
void setMissileRegistryRole:forShip:(NSString *roles,[forShip] NSString *shipKey)
NSString * getMissileRegistryRoleForShip:(NSString *shipKey)
OOEquipmentType * equipmentTypeWithIdentifier:(NSString *identifier)
NSArray * allEquipmentTypes()
GLfloat weaponRechargeRate()
double findCollisionRadius()
id exhaustForShip:withDefinition:andScale:(ShipEntity *ship,[withDefinition] NSArray *definition,[andScale] float scale)
instancetype explosionCloudFromEntity:withSettings:(Entity *entity,[withSettings] NSDictionary *settings)
instancetype explosionCloudFromEntity:withSize:andSettings:(Entity *entity,[withSize] float size,[andSettings] NSDictionary *settings)
instancetype explosionFlashFromEntity:(Entity *entity)
instancetype flasherWithDictionary:(NSDictionary *dictionary)
void setActive:(BOOL active)
void rescaleBy:(GLfloat factor)
BOOL callMethod:inContext:withArguments:count:result:(jsid methodID,[inContext] JSContext *context,[withArguments] jsval *argv,[count] intN argc,[result] jsval *outResult)
void setRange:(GLfloat range)
void setColor:(OOColor *color)
instancetype laserFromShip:direction:offset:(ShipEntity *ship,[direction] OOWeaponFacing direction,[offset] Vector offset)
instancetype meshWithName:cacheKey:materialDictionary:shadersDictionary:smooth:shaderMacros:shaderBindingTarget:scaleFactor:cacheWriteable:(NSString *name,[cacheKey] NSString *cacheKey,[materialDictionary] NSDictionary *materialDict,[shadersDictionary] NSDictionary *shadersDict,[smooth] BOOL smooth,[shaderMacros] NSDictionary *macros,[shaderBindingTarget] id< OOWeakReferenceSupport > object,[scaleFactor] float factor,[cacheWriteable] BOOL cacheWriteable)
void welcomeShuttle:(ShipEntity *shuttle)
instancetype quiriumCascadeFromShip:(ShipEntity *ship)
instancetype ringFromEntity:(Entity *sourceEntity)
instancetype roleSetWithString:(NSString *roleString)
instancetype roleSetWithRole:probability:(NSString *role,[probability] float probability)
id jsScriptFromFileNamed:properties:(NSString *fileName,[properties] NSDictionary *properties)
BOOL addShip:(ShipEntity *ship)
void setLeader:(ShipEntity *leader)
instancetype groupWithName:(NSString *name)
NSDictionary * shipyardInfoForKey:(NSString *key)
OOShipRegistry * sharedRegistry()
id fragmentBurstFromEntity:(Entity *entity)
void setScriptTarget:(ShipEntity *ship)
void receiveCommsMessage:from:(NSString *message_text, [from] ShipEntity *other)
NSDictionary * materialDefaults()
NSDictionary * whitelistDictionary()
NSDictionary * dictionaryFromFilesNamed:inFolder:andMerge:(NSString *fileName,[inFolder] NSString *folderName,[andMerge] BOOL mergeFiles)
void setWeaponEnergy:(float value)
void setSuppressExplosion:(BOOL suppress)
HPVector absoluteTractorPosition()
void setTargetStation:(Entity *targetEntity)
void doScriptEvent:withArgument:andReactToAIMessage:(jsid scriptEvent,[withArgument] id argument,[andReactToAIMessage] NSString *aiMessage)
HPVector distance_twelve:withOffset:(GLfloat dist,[withOffset] GLfloat offset)
void setBounty:withReason:(OOCreditsQuantity amount,[withReason] OOLegalStatusReason reason)
void addTarget:(Entity *targetEntity)
void setPrimaryAggressor:(Entity *targetEntity)
void setDesiredSpeed:(double amount)
NSDictionary * shipInfoDictionary()
void takeEnergyDamage:from:becauseOf:weaponIdentifier:(double amount, [from] Entity *ent, [becauseOf] Entity *other, [weaponIdentifier] NSString *weaponIdentifier)
void setStatus:(OOEntityStatus stat)
void setRoll:(double amount)
void setThrust:(double amount)
ShipEntity * subEntityTakingDamage()
void setSpeed:(double amount)
void takeScrapeDamage:from:(double amount,[from] Entity *ent)
void setSingleCrewWithRole:(NSString *crewRole)
void setGroup:(OOShipGroup *group)
void scoopIn:(ShipEntity *other)
void setEscortDestination:(HPVector dest)
void addImpactMoment:fraction:(Vector moment,[fraction] GLfloat howmuch)
static float SurfaceDistanceSqared(Entity *reference, Entity< OOStellarBody > *stellar)
void receiveCommsMessage:from:(NSString *message_text,[from] ShipEntity *other)
void setDisplayName:(NSString *inName)
double rangeToSecondaryTarget:(Entity *target)
void setPrimaryRole:(NSString *role)
void subEntityDied:(ShipEntity *sub)
NSEnumerator * defenseTargetEnumerator()
void setHeatInsulation:(GLfloat value)
void respondToAttackFrom:becauseOf:(Entity *from,[becauseOf] Entity *other)
void setPitch:(double amount)
HPVector distance_six:(GLfloat dist)
NSString * identFromShip:(ShipEntity *otherShip)
GLfloat forward_weapon_temp
void removeDefenseTarget:(Entity *target)
void setAITo:(NSString *aiString)
void noteTargetDestroyed:(ShipEntity *target)
void setWeaponRange:(GLfloat value)
void adjustVelocity:(Vector xVel)
void setWeaponRechargeRate:(float value)
void setEntityPersonalityInt:(uint16_t value)
void setReportAIMessages:(BOOL yn)
void updateEscortFormation()
void setSubIdx:(NSUInteger value)
OOCargoQuantity commodityAmount()
void overrideScriptInfo:(NSDictionary *override)
void update:(OOTimeDelta delta_t)
void setTemperature:(GLfloat value)
void setIsBoulder:(BOOL flag)
void setCrew:(NSArray *crewArray)
void setCommodityForPod:andAmount:(OOCommodityType co_type,[andAmount] OOCargoQuantity co_amount)
void markAsOffender:withReason:(int offence_value,[withReason] OOLegalStatusReason reason)
void setBehaviour:(OOBehaviour cond)
void doScriptEvent:withArgument:(jsid message,[withArgument] id argument)
void getTractoredBy:(ShipEntity *other)
void setIsMissileFlag:(BOOL newValue)
void scoopUp:(ShipEntity *other)
void switchAITo:(NSString *aiString)
Triangle absoluteIJKForSubentity()
void reactToAIMessage:context:(NSString *message,[context] NSString *debugContext)
void setFoundTarget:(Entity *targetEntity)
void setCommodity:andAmount:(OOCommodityType co_type,[andAmount] OOCargoQuantity co_amount)
OOWeaponType forward_weapon_type
NSMutableArray * subEntities
OOCommodityType commodityType()
void setOwner:(Entity *who_owns_entity)
BoundingBox findSubentityBoundingBox()
BoundingBox findBoundingBoxRelativeToPosition:InVectors:i:j:(HPVector opv,[InVectors] Vector,[i] Vector,[j] Vector k)
void setReference:(Vector v)
static float SurfaceDistanceSqaredV(HPVector reference, Entity< OOStellarBody > *stellar)
void doScriptEvent:withArgument:andArgument:(jsid message,[withArgument] id argument1,[andArgument] id argument2)
void launchShip:(ShipEntity *ship)
Vector portUpVectorForShip:(ShipEntity *ship)
void launchShipWithRole:(NSString *role)
void noteDockedShip:(ShipEntity *ship)
BOOL suckInShip:(ShipEntity *ship)
void setMisjumpWithRange:(GLfloat range)
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
void seed_RNG_only_for_planet_description(Random_Seed s_seed)