da2be13ec0
git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@186 127b21dd-08f5-0310-b4b7-95ae10353056
6295 lines
176 KiB
Objective-C
6295 lines
176 KiB
Objective-C
//
|
|
// ShipEntity.m
|
|
/*
|
|
*
|
|
* Oolite
|
|
*
|
|
* Created by Giles Williams on Sat Apr 03 2004.
|
|
* Copyright (c) 2004 for aegidian.org. All rights reserved.
|
|
*
|
|
|
|
Copyright (c) 2004, Giles C Williams
|
|
All rights reserved.
|
|
|
|
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License.
|
|
To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.0/
|
|
or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
|
|
|
|
You are free:
|
|
|
|
• to copy, distribute, display, and perform the work
|
|
• to make derivative works
|
|
|
|
Under the following conditions:
|
|
|
|
• Attribution. You must give the original author credit.
|
|
|
|
• Noncommercial. You may not use this work for commercial purposes.
|
|
|
|
• Share Alike. If you alter, transform, or build upon this work,
|
|
you may distribute the resulting work only under a license identical to this one.
|
|
|
|
For any reuse or distribution, you must make clear to others the license terms of this work.
|
|
|
|
Any of these conditions can be waived if you get permission from the copyright holder.
|
|
|
|
Your fair use and other rights are in no way affected by the above.
|
|
|
|
*/
|
|
|
|
#import "ShipEntity.h"
|
|
#import "entities.h"
|
|
|
|
#import "vector.h"
|
|
#import "Universe.h"
|
|
|
|
#import "AI.h"
|
|
#import "OOCharacter.h"
|
|
|
|
|
|
@implementation ShipEntity
|
|
|
|
- (id) init
|
|
{
|
|
self = [super init];
|
|
//
|
|
// scripting
|
|
launch_actions = [[NSMutableArray alloc] initWithCapacity:4];
|
|
script_actions = [[NSMutableArray alloc] initWithCapacity:4];
|
|
death_actions = [[NSMutableArray alloc] initWithCapacity:4];
|
|
//
|
|
// escorts
|
|
last_escort_target = NO_TARGET;
|
|
n_escorts = 0;
|
|
escortsAreSetUp = YES;
|
|
//
|
|
quaternion_set_identity(&q_rotation);
|
|
quaternion_into_gl_matrix(q_rotation, rotMatrix);
|
|
//
|
|
velocity = make_vector( 0.0, 0.0, 0.0);
|
|
momentum = make_vector( 0.0, 0.0, 0.0);
|
|
quaternion_set_identity(&subentity_rotational_velocity);
|
|
//
|
|
v_forward = vector_forward_from_quaternion(q_rotation);
|
|
v_up = vector_up_from_quaternion(q_rotation);
|
|
v_right = vector_right_from_quaternion(q_rotation);
|
|
//
|
|
reference = v_forward; // reference vector for (* turrets *)
|
|
owner_id = NO_TARGET; // owner_id for (* turrets *)
|
|
//
|
|
group_id = NO_TARGET;
|
|
//
|
|
position.x = 0.0;
|
|
position.y = 0.0;
|
|
position.z = 0.0;
|
|
//
|
|
max_flight_speed = 200.0;
|
|
max_flight_roll = 3.0;
|
|
max_flight_pitch = 1.5;
|
|
//
|
|
flight_speed = 0.0;
|
|
flight_roll = 0.0;
|
|
flight_pitch = 0.0;
|
|
//
|
|
thrust = 0.0;
|
|
//
|
|
pitching_over = NO;
|
|
//
|
|
energy = 100.0;
|
|
max_energy = 100.0;
|
|
energy_recharge_rate = 2.0;
|
|
//
|
|
forward_weapon_type = WEAPON_NONE;
|
|
aft_weapon_type = WEAPON_NONE;
|
|
weapon_energy = 8.0;
|
|
weapon_recharge_rate = 6.0;
|
|
weapon_offset_x = 10.0;
|
|
shot_time = 0.0;
|
|
//
|
|
launch_time = 0.0;
|
|
//
|
|
cargo_dump_time = 0.0;
|
|
//
|
|
missiles = 3;
|
|
has_ecm = NO;
|
|
has_scoop = NO;
|
|
has_escape_pod = NO;
|
|
has_energy_bomb = NO;
|
|
has_fuel_injection = NO;
|
|
has_cloaking_device = NO;
|
|
has_military_jammer = NO;
|
|
has_military_scanner_filter = NO;
|
|
fuel_accumulator = 1.0;
|
|
//
|
|
bounty = 0;
|
|
//
|
|
primaryTarget = NO_TARGET;
|
|
//
|
|
targetStation = NO_TARGET;
|
|
//
|
|
proximity_alert = NO_TARGET;
|
|
//
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
//
|
|
shipAI = [[AI alloc] init]; // alloc retains
|
|
[shipAI setOwner:self];
|
|
[shipAI setState:@"GLOBAL"];
|
|
//
|
|
max_cargo = RAIDER_MAX_CARGO;
|
|
likely_cargo = 0;
|
|
cargo_type = 0;
|
|
cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // alloc retains;
|
|
cargo_flag = CARGO_FLAG_NONE;
|
|
//
|
|
[self setOwner:self];
|
|
//
|
|
reportAImessages = NO;
|
|
//
|
|
sub_entities = nil;
|
|
//
|
|
previousCondition = nil;
|
|
//
|
|
patrol_counter = 0;
|
|
//
|
|
laser_color = [[NSColor redColor] retain];
|
|
//
|
|
scanner_range = 25600;
|
|
//
|
|
shipinfoDictionary = nil;
|
|
//
|
|
being_fined = NO;
|
|
//
|
|
message_time = 0.0;
|
|
//
|
|
next_spark_time = 0.0;
|
|
//
|
|
throw_sparks = NO;
|
|
//
|
|
pitch_tolerance = 0.01 * (80 +(ranrot_rand() & 15)); // 80%..95% accuracy in trackPrimaryTarget
|
|
//
|
|
thanked_ship_id = NO_TARGET;
|
|
//
|
|
scan_class = CLASS_NOT_SET;
|
|
//
|
|
beaconChar = 0;
|
|
//
|
|
isShip = YES;
|
|
//
|
|
isFrangible = YES;
|
|
//
|
|
dockingInstructions = nil;
|
|
//
|
|
crew = nil;
|
|
//
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc
|
|
{
|
|
if (shipinfoDictionary) [shipinfoDictionary release];
|
|
if (shipAI) [shipAI release];
|
|
if (cargo) [cargo release];
|
|
if (name) [name release];
|
|
if (roles) [roles release];
|
|
if (sub_entities) [sub_entities release];
|
|
if (laser_color) [laser_color release];
|
|
//scripting
|
|
if (launch_actions) [launch_actions release];
|
|
if (script_actions) [script_actions release];
|
|
if (death_actions) [death_actions release];
|
|
|
|
if (previousCondition) [previousCondition release];
|
|
|
|
if (collisionInfoForEntity)
|
|
[collisionInfoForEntity release];
|
|
|
|
if (dockingInstructions)
|
|
[dockingInstructions release];
|
|
|
|
if (crew) [crew release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSString*) description
|
|
{
|
|
NSString* result = [[NSString alloc] initWithFormat:@"<ShipEntity %@ %d (%@) %@>", name, universal_id, roles, (universe == nil)? @" (not in universe)":@""];
|
|
return [result autorelease];
|
|
}
|
|
|
|
- (void) setUniverse:(Universe *)univ
|
|
{
|
|
if (univ)
|
|
{
|
|
if (universe) [universe release];
|
|
universe = [univ retain];
|
|
}
|
|
else
|
|
{
|
|
if (universe) [universe release];
|
|
universe = nil;
|
|
}
|
|
|
|
//
|
|
// if we have a universal id then we can proceed to set up any
|
|
// stuff that happens when we get added to the universe
|
|
//
|
|
if (universal_id != NO_TARGET)
|
|
{
|
|
// set up escorts
|
|
//
|
|
if (status == STATUS_IN_FLIGHT) // just popped into existence
|
|
{
|
|
if ((!escortsAreSetUp)&&(n_escorts > 0))
|
|
[self setUpEscorts];
|
|
}
|
|
else
|
|
{
|
|
escortsAreSetUp = YES; // we don't do this ourself!
|
|
}
|
|
}
|
|
|
|
//
|
|
// set subentities universe
|
|
//
|
|
if (sub_entities != nil)
|
|
{
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
[(Entity *)[sub_entities objectAtIndex:i] setUniverse:univ];
|
|
}
|
|
}
|
|
//
|
|
[self resetTracking]; // resets stuff for tracking/exhausts
|
|
}
|
|
|
|
- (BOOL) isBeacon
|
|
{
|
|
return (beaconChar != 0);
|
|
}
|
|
|
|
- (char) beaconChar
|
|
{
|
|
return beaconChar;
|
|
}
|
|
|
|
- (void) setBeaconChar:(char) bchar
|
|
{
|
|
beaconChar = bchar;
|
|
}
|
|
|
|
- (int) nextBeaconID
|
|
{
|
|
return nextBeaconID;
|
|
}
|
|
|
|
- (void) setNextBeacon:(ShipEntity*) beaconShip
|
|
{
|
|
if (beaconShip == nil)
|
|
nextBeaconID = NO_TARGET;
|
|
else
|
|
nextBeaconID = [beaconShip universal_id];
|
|
}
|
|
|
|
- (void) setUpEscorts
|
|
{
|
|
NSString *escortRole = @"escort";
|
|
NSString *escortShipKey = nil;
|
|
|
|
if ([roles isEqual:@"trader"])
|
|
escortRole = @"escort";
|
|
|
|
if ([roles isEqual:@"police"])
|
|
escortRole = @"wingman";
|
|
|
|
if ([shipinfoDictionary objectForKey:@"escort-role"])
|
|
{
|
|
escortRole = (NSString*)[shipinfoDictionary objectForKey:@"escort-role"];
|
|
if (![[universe getShipWithRole:escortRole] autorelease])
|
|
escortRole = @"escort";
|
|
}
|
|
|
|
if ([shipinfoDictionary objectForKey:@"escort-ship"])
|
|
{
|
|
escortShipKey = (NSString*)[shipinfoDictionary objectForKey:@"escort-ship"];
|
|
if (![[universe getShip:escortShipKey] autorelease])
|
|
escortShipKey = nil;
|
|
}
|
|
|
|
// NSLog(@"DEBUG Setting up escorts for %@", self);
|
|
|
|
while (n_escorts > 0)
|
|
{
|
|
Vector ex_pos = [self getCoordinatesForEscortPosition:n_escorts - 1];
|
|
|
|
ShipEntity *escorter;
|
|
|
|
if (escortShipKey)
|
|
escorter = [universe getShip:escortShipKey]; // retained
|
|
else
|
|
escorter = [universe getShipWithRole:escortRole]; // retained
|
|
|
|
if (!escorter)
|
|
break;
|
|
|
|
// spread them around a little randomly
|
|
double dd = escorter->collision_radius;
|
|
ex_pos.x += dd * 6.0 * (randf() - 0.5);
|
|
ex_pos.y += dd * 6.0 * (randf() - 0.5);
|
|
ex_pos.z += dd * 6.0 * (randf() - 0.5);
|
|
|
|
|
|
[escorter setScanClass: CLASS_NEUTRAL];
|
|
[escorter setPosition:ex_pos];
|
|
|
|
[escorter setStatus:STATUS_IN_FLIGHT];
|
|
|
|
[escorter setRoles:escortRole];
|
|
|
|
[escorter setScanClass:scan_class]; // you are the same as I
|
|
|
|
//[escorter setReportAImessages: (i == 0) ? YES:NO ]; // debug
|
|
|
|
[universe addEntity:escorter];
|
|
[[escorter getAI] setStateMachine:@"escortAI.plist"]; // must happen after adding to the universe!
|
|
|
|
[escorter setGroup_id:universal_id];
|
|
[self setGroup_id:universal_id]; // make self part of same group
|
|
|
|
[escorter setOwner: self]; // make self group leader
|
|
|
|
[[escorter getAI] setState:@"FLYING_ESCORT"]; // begin immediately
|
|
|
|
if (bounty)
|
|
{
|
|
int extra = 1 | (ranrot_rand() & 15);
|
|
bounty += extra; // obviously we're dodgier than we thought!
|
|
[escorter setBounty: extra];
|
|
// NSLog(@"DEBUG setting bounty for %@ escorting %@ to %d", escorter, self, extra);
|
|
|
|
// [escorter setReportAImessages: YES ]; // debug
|
|
}
|
|
else
|
|
{
|
|
[escorter setBounty:0];
|
|
}
|
|
|
|
// NSLog(@"DEBUG set up escort ship %@ for %@", escorter, self);
|
|
|
|
[escorter release];
|
|
n_escorts--;
|
|
}
|
|
}
|
|
|
|
|
|
- (void) reinit
|
|
{
|
|
//
|
|
quaternion_set_identity(&q_rotation);
|
|
quaternion_into_gl_matrix(q_rotation, rotMatrix);
|
|
//
|
|
v_forward = vector_forward_from_quaternion(q_rotation);
|
|
v_up = vector_up_from_quaternion(q_rotation);
|
|
v_right = vector_right_from_quaternion(q_rotation);
|
|
//
|
|
reference = v_forward; // reference vector for (* turrets *)
|
|
owner_id = NO_TARGET; // owner_id for (* turrets *)
|
|
//
|
|
group_id = NO_TARGET;
|
|
//
|
|
if (launch_actions)
|
|
[launch_actions removeAllObjects];
|
|
if (script_actions)
|
|
[script_actions removeAllObjects];
|
|
if (death_actions)
|
|
[death_actions removeAllObjects];
|
|
//
|
|
last_escort_target = NO_TARGET;
|
|
n_escorts = 0;
|
|
escortsAreSetUp = YES;
|
|
//
|
|
position = make_vector( 0.0, 0.0, 0.0);
|
|
velocity = make_vector( 0.0, 0.0, 0.0);
|
|
momentum = make_vector( 0.0, 0.0, 0.0);
|
|
quaternion_set_identity(&subentity_rotational_velocity);
|
|
//
|
|
zero_distance = SCANNER_MAX_RANGE2 * 2.0; // beyond scanner range to avoid the momentary blip
|
|
//
|
|
max_flight_speed = 0.0;
|
|
max_flight_roll = 0.0;
|
|
max_flight_pitch = 0.0;
|
|
//
|
|
flight_speed = 0.0;
|
|
flight_roll = 0.0;
|
|
flight_pitch = 0.0;
|
|
//
|
|
thrust = 0.0;
|
|
//
|
|
pitching_over = NO;
|
|
//
|
|
energy = 0.0;
|
|
max_energy = 0.0;
|
|
energy_recharge_rate = 0.0;
|
|
//
|
|
forward_weapon_type = WEAPON_NONE;
|
|
aft_weapon_type = WEAPON_NONE;
|
|
weapon_energy = 0.0;
|
|
weapon_recharge_rate = 6.0;
|
|
weapon_offset_x = 0.0;
|
|
shot_time = 0.0;
|
|
//
|
|
launch_time = 0.0;
|
|
//
|
|
cargo_dump_time = 0.0;
|
|
//
|
|
missiles = 0;
|
|
has_ecm = NO;
|
|
has_scoop = NO;
|
|
has_escape_pod = NO;
|
|
has_energy_bomb = NO;
|
|
has_fuel_injection = NO;
|
|
has_cloaking_device = NO;
|
|
has_military_jammer = NO;
|
|
has_military_scanner_filter = NO;
|
|
fuel_accumulator = 1.0;
|
|
//
|
|
bounty = 0;
|
|
//
|
|
primaryTarget = NO_TARGET;
|
|
//
|
|
targetStation = NO_TARGET;
|
|
//
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
//
|
|
if (shipAI)
|
|
[shipAI autorelease];
|
|
shipAI = [[AI alloc] init]; // alloc retains
|
|
[shipAI setOwner:self];
|
|
[shipAI setState:@"GLOBAL"];
|
|
//
|
|
max_cargo = 0;
|
|
likely_cargo = 0;
|
|
cargo_type = 0;
|
|
cargo_flag = CARGO_FLAG_NONE;
|
|
if (!cargo)
|
|
cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // alloc retains;
|
|
[cargo removeAllObjects];
|
|
//
|
|
owner = NO_TARGET;
|
|
//
|
|
reportAImessages = NO;
|
|
//
|
|
if (previousCondition) [previousCondition autorelease];
|
|
previousCondition = nil;
|
|
//
|
|
if (sub_entities) [sub_entities autorelease];
|
|
sub_entities = nil;
|
|
//
|
|
scanner_range = 25600.0;
|
|
//
|
|
if (shipinfoDictionary)
|
|
[shipinfoDictionary autorelease];
|
|
shipinfoDictionary = nil;
|
|
//
|
|
being_fined = NO;
|
|
//
|
|
message_time = 0.0;
|
|
//
|
|
next_spark_time = 0.0;
|
|
//
|
|
throw_sparks = NO;
|
|
//
|
|
thanked_ship_id = NO_TARGET;
|
|
//
|
|
scan_class = CLASS_NOT_SET;
|
|
//
|
|
[collisionInfoForEntity removeAllObjects];
|
|
//
|
|
beaconChar = 0;
|
|
//
|
|
isShip = YES;
|
|
//
|
|
isFrangible = YES;
|
|
//
|
|
if (dockingInstructions)
|
|
[dockingInstructions autorelease];
|
|
dockingInstructions = nil;
|
|
//
|
|
if (crew)
|
|
[crew autorelease];
|
|
crew = nil;
|
|
|
|
}
|
|
|
|
- (id) initWithDictionary:(NSDictionary *) dict
|
|
{
|
|
self = [super init];
|
|
//
|
|
quaternion_set_identity(&q_rotation);
|
|
quaternion_into_gl_matrix(q_rotation, rotMatrix);
|
|
//
|
|
v_forward = vector_forward_from_quaternion(q_rotation);
|
|
v_up = vector_up_from_quaternion(q_rotation);
|
|
v_right = vector_right_from_quaternion(q_rotation);
|
|
//
|
|
position = make_vector( 0.0, 0.0, 0.0);
|
|
velocity = make_vector( 0.0, 0.0, 0.0);
|
|
//
|
|
flight_speed = 0.0;
|
|
flight_roll = 0.0;
|
|
flight_pitch = 0.0;
|
|
//
|
|
pitching_over = NO;
|
|
//
|
|
primaryTarget = NO_TARGET;
|
|
//
|
|
targetStation = NO_TARGET;
|
|
//
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
//
|
|
patrol_counter = 0;
|
|
//
|
|
scan_class = CLASS_NOT_SET;
|
|
//
|
|
crew = nil;
|
|
//
|
|
[self setUpShipFromDictionary:dict];
|
|
//
|
|
reportAImessages = NO;
|
|
//
|
|
being_fined = NO;
|
|
//
|
|
isShip = YES;
|
|
//
|
|
isFrangible = YES;
|
|
//
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) setUpShipFromDictionary:(NSDictionary *) dict
|
|
{
|
|
NSString* cargo_type_string;
|
|
NSString* weapon_type_string;
|
|
|
|
// reset all settings
|
|
[self reinit];
|
|
|
|
if (collisionInfoForEntity)
|
|
[collisionInfoForEntity removeAllObjects];
|
|
else
|
|
collisionInfoForEntity = [[NSMutableDictionary alloc] initWithCapacity:12];
|
|
|
|
shipinfoDictionary = [[NSDictionary alloc] initWithDictionary:dict]; // retained
|
|
|
|
//
|
|
// set things from dictionary from here out
|
|
//
|
|
if ([dict objectForKey:@"max_flight_speed"])
|
|
max_flight_speed = [(NSNumber *)[dict objectForKey:@"max_flight_speed"] doubleValue];
|
|
if ([dict objectForKey:@"max_flight_roll"])
|
|
max_flight_roll = [(NSNumber *)[dict objectForKey:@"max_flight_roll"] doubleValue];
|
|
if ([dict objectForKey:@"max_flight_pitch"])
|
|
max_flight_pitch = [(NSNumber *)[dict objectForKey:@"max_flight_pitch"] doubleValue];
|
|
//
|
|
if ([dict objectForKey:@"thrust"])
|
|
thrust = [(NSNumber *)[dict objectForKey:@"thrust"] doubleValue];
|
|
//
|
|
if ([dict objectForKey:@"accuracy"])
|
|
{
|
|
int accuracy = [(NSNumber *)[dict objectForKey:@"accuracy"] intValue];
|
|
if ((accuracy >= -5)&&(accuracy <= 10))
|
|
pitch_tolerance = 0.01 * (85 + accuracy);
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"max_energy"])
|
|
max_energy = [(NSNumber *)[dict objectForKey:@"max_energy"] doubleValue];
|
|
if ([dict objectForKey:@"energy_recharge_rate"])
|
|
energy_recharge_rate = [(NSNumber *)[dict objectForKey:@"energy_recharge_rate"] doubleValue];
|
|
energy = max_energy;
|
|
//
|
|
if ([dict objectForKey:@"weapon_offset_x"])
|
|
weapon_offset_x = [(NSNumber *)[dict objectForKey:@"weapon_offset_x"] doubleValue];
|
|
//
|
|
if ([dict objectForKey:@"aft_weapon_type"])
|
|
{
|
|
weapon_type_string = (NSString *)[dict objectForKey:@"aft_weapon_type"];
|
|
if ([weapon_type_string isEqual:@"WEAPON_PULSE_LASER"])
|
|
aft_weapon_type = WEAPON_PULSE_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_BEAM_LASER"])
|
|
aft_weapon_type = WEAPON_BEAM_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_MINING_LASER"])
|
|
aft_weapon_type = WEAPON_MINING_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_MILITARY_LASER"])
|
|
aft_weapon_type = WEAPON_MILITARY_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_THARGOID_LASER"])
|
|
aft_weapon_type = WEAPON_THARGOID_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_PLASMA_CANNON"])
|
|
aft_weapon_type = WEAPON_PLASMA_CANNON;
|
|
if ([weapon_type_string isEqual:@"WEAPON_NONE"])
|
|
aft_weapon_type = WEAPON_NONE;
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"forward_weapon_type"])
|
|
{
|
|
weapon_type_string = (NSString *)[dict objectForKey:@"forward_weapon_type"];
|
|
if ([weapon_type_string isEqual:@"WEAPON_PULSE_LASER"])
|
|
forward_weapon_type = WEAPON_PULSE_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_BEAM_LASER"])
|
|
forward_weapon_type = WEAPON_BEAM_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_MINING_LASER"])
|
|
forward_weapon_type = WEAPON_MINING_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_MILITARY_LASER"])
|
|
forward_weapon_type = WEAPON_MILITARY_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_THARGOID_LASER"])
|
|
forward_weapon_type = WEAPON_THARGOID_LASER;
|
|
if ([weapon_type_string isEqual:@"WEAPON_PLASMA_CANNON"])
|
|
forward_weapon_type = WEAPON_PLASMA_CANNON;
|
|
if ([weapon_type_string isEqual:@"WEAPON_NONE"])
|
|
forward_weapon_type = WEAPON_NONE;
|
|
[self set_weapon_data_from_type:forward_weapon_type];
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"weapon_energy"])
|
|
weapon_energy = [(NSNumber *)[dict objectForKey:@"weapon_energy"] doubleValue];
|
|
//
|
|
if ([dict objectForKey:@"scanner_range"])
|
|
scanner_range = [(NSNumber *)[dict objectForKey:@"scanner_range"] doubleValue];
|
|
//
|
|
if ([dict objectForKey:@"missiles"])
|
|
missiles = [(NSNumber *)[dict objectForKey:@"missiles"] intValue];
|
|
|
|
// upgrades:
|
|
// did use [NSNumber boolValue], but now have a random chance instead
|
|
//
|
|
if ([dict objectForKey:@"has_ecm"])
|
|
has_ecm = (randf() < [(NSNumber *)[dict objectForKey:@"has_ecm"] floatValue]);
|
|
if ([dict objectForKey:@"has_scoop"])
|
|
has_scoop = (randf() < [(NSNumber *)[dict objectForKey:@"has_scoop"] floatValue]);
|
|
if ([dict objectForKey:@"has_escape_pod"])
|
|
has_escape_pod = (randf() < [(NSNumber *)[dict objectForKey:@"has_escape_pod"] floatValue]);
|
|
if ([dict objectForKey:@"has_energy_bomb"])
|
|
has_energy_bomb = (randf() < [(NSNumber *)[dict objectForKey:@"has_energy_bomb"] floatValue]);
|
|
if ([dict objectForKey:@"has_fuel_injection"])
|
|
has_fuel_injection = (randf() < [(NSNumber *)[dict objectForKey:@"has_fuel_injection"] floatValue]);
|
|
//
|
|
if ([dict objectForKey:@"has_shield_booster"])
|
|
max_energy += (randf() < [(NSNumber *)[dict objectForKey:@"has_shield_booster"] floatValue])? 256:0;
|
|
if ([dict objectForKey:@"has_shield_enhancer"])
|
|
{
|
|
max_energy += (randf() < [(NSNumber *)[dict objectForKey:@"has_shield_enhancer"] floatValue])? 256:0;
|
|
energy_recharge_rate *= 1.5;
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"has_cloaking_device"])
|
|
has_cloaking_device = (randf() < [(NSNumber *)[dict objectForKey:@"has_cloaking_device"] floatValue]);
|
|
//
|
|
cloaking_device_active = NO;
|
|
//
|
|
if ([dict objectForKey:@"has_military_jammer"])
|
|
has_military_jammer = (randf() < [(NSNumber *)[dict objectForKey:@"has_military_jammer"] floatValue]);
|
|
//
|
|
military_jammer_active = NO;
|
|
//
|
|
if ([dict objectForKey:@"has_military_scanner_filter"])
|
|
has_military_scanner_filter = (randf() < [(NSNumber *)[dict objectForKey:@"has_military_scanner_filter"] floatValue]);
|
|
//
|
|
// /upgrades
|
|
|
|
if ([dict objectForKey:@"fuel"])
|
|
fuel = [(NSNumber *)[dict objectForKey:@"fuel"] intValue];
|
|
|
|
//
|
|
if ([dict objectForKey:@"bounty"])
|
|
bounty = [(NSNumber *)[dict objectForKey:@"bounty"] intValue];
|
|
//
|
|
if ([dict objectForKey:@"ai_type"])
|
|
{
|
|
if (shipAI)
|
|
[shipAI autorelease];
|
|
shipAI = [[AI alloc] init]; // alloc retains
|
|
[shipAI setOwner:self];
|
|
[shipAI setStateMachine:(NSString *)[dict objectForKey:@"ai_type"]];
|
|
[shipAI setState:@"GLOBAL"];
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"max_cargo"])
|
|
max_cargo = [(NSNumber *)[dict objectForKey:@"max_cargo"] intValue];
|
|
if ([dict objectForKey:@"likely_cargo"])
|
|
likely_cargo = [(NSNumber *)[dict objectForKey:@"likely_cargo"] intValue];
|
|
//
|
|
if ([dict objectForKey:@"cargo_carried"])
|
|
{
|
|
cargo_flag = CARGO_FLAG_FULL_UNIFORM;
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"cargo_type"])
|
|
{
|
|
cargo_type_string = (NSString *)[dict objectForKey:@"cargo_type"];
|
|
if ([cargo_type_string isEqual:@"CARGO_THARGOID"])
|
|
cargo_type = CARGO_THARGOID;
|
|
if ([cargo_type_string isEqual:@"CARGO_ALLOY"])
|
|
cargo_type = CARGO_ALLOY;
|
|
if ([cargo_type_string isEqual:@"CARGO_MINERALS"])
|
|
cargo_type = CARGO_MINERALS;
|
|
if ([cargo_type_string isEqual:@"CARGO_SLAVES"])
|
|
cargo_type = CARGO_SLAVES;
|
|
if ([cargo_type_string isEqual:@"CARGO_NOT_CARGO"])
|
|
cargo_type = CARGO_NOT_CARGO;
|
|
if ([cargo_type_string isEqual:@"CARGO_RANDOM"])
|
|
cargo_type = CARGO_RANDOM;
|
|
if ([cargo_type_string isEqual:@"CARGO_SCRIPTED_ITEM"])
|
|
cargo_type = CARGO_SCRIPTED_ITEM;
|
|
if (cargo)
|
|
[cargo autorelease];
|
|
cargo = [[NSMutableArray alloc] initWithCapacity:max_cargo]; // alloc retains;
|
|
}
|
|
//
|
|
// A HACK!! - must do this before the model is set
|
|
if ([dict objectForKey:@"smooth"])
|
|
is_smooth_shaded = YES;
|
|
else
|
|
is_smooth_shaded = NO;
|
|
//
|
|
// must do this next one before checking subentities
|
|
if ([dict objectForKey:@"model"])
|
|
[self setModel:(NSString *)[dict objectForKey:@"model"]];
|
|
//
|
|
if ([dict objectForKey:KEY_NAME])
|
|
{
|
|
if (name)
|
|
[name release];
|
|
name = [[NSString stringWithString:(NSString *)[dict objectForKey:KEY_NAME]] retain];
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"roles"])
|
|
{
|
|
if (roles)
|
|
[roles release];
|
|
roles = [[NSString stringWithString:(NSString *)[dict objectForKey:@"roles"]] retain];
|
|
}
|
|
//
|
|
[self setOwner:self];
|
|
//
|
|
if ([dict objectForKey:@"exhaust"])
|
|
{
|
|
int i;
|
|
NSArray *plumes = (NSArray *)[dict objectForKey:@"exhaust"];
|
|
for (i = 0; i < [plumes count]; i++)
|
|
{
|
|
ParticleEntity *exhaust = [[ParticleEntity alloc] initExhaustFromShip:self details:(NSString *)[plumes objectAtIndex:i]];
|
|
[self addExhaust:exhaust];
|
|
[exhaust release];
|
|
}
|
|
}
|
|
//
|
|
if ((universe)&&([dict objectForKey:@"subentities"]))
|
|
{
|
|
//NSLog(@"DEBUG adding subentity...");
|
|
int i;
|
|
NSArray *subs = (NSArray *)[dict objectForKey:@"subentities"];
|
|
for (i = 0; i < [subs count]; i++)
|
|
{
|
|
// NSArray* details = [(NSString *)[subs objectAtIndex:i] componentsSeparatedByString:@" "];
|
|
NSArray* details = [Entity scanTokensFromString:(NSString *)[subs objectAtIndex:i]];
|
|
|
|
if ([details count] == 8)
|
|
{
|
|
//NSLog(@"DEBUG adding subentity...");
|
|
Vector sub_pos, ref;
|
|
Quaternion sub_q;
|
|
Entity* subent;
|
|
NSString* subdesc = (NSString *)[details objectAtIndex:0];
|
|
sub_pos.x = [(NSString *)[details objectAtIndex:1] floatValue];
|
|
sub_pos.y = [(NSString *)[details objectAtIndex:2] floatValue];
|
|
sub_pos.z = [(NSString *)[details objectAtIndex:3] floatValue];
|
|
sub_q.w = [(NSString *)[details objectAtIndex:4] floatValue];
|
|
sub_q.x = [(NSString *)[details objectAtIndex:5] floatValue];
|
|
sub_q.y = [(NSString *)[details objectAtIndex:6] floatValue];
|
|
sub_q.z = [(NSString *)[details objectAtIndex:7] floatValue];
|
|
|
|
// NSLog(@"DEBUG adding subentity... %@ %f %f %f - %f %f %f %f", subdesc, sub_pos.x, sub_pos.y, sub_pos.z, sub_q.w, sub_q.x, sub_q.y, sub_q.z);
|
|
|
|
if ([subdesc isEqual:@"*FLASHER*"])
|
|
{
|
|
subent = [[ParticleEntity alloc] init]; // retained
|
|
[(ParticleEntity*)subent setColor:[NSColor colorWithCalibratedHue: sub_q.w/360.0 saturation:1.0 brightness:1.0 alpha:1.0]];
|
|
[(ParticleEntity*)subent setDuration: sub_q.x];
|
|
[(ParticleEntity*)subent setEnergy: 2.0 * sub_q.y];
|
|
[(ParticleEntity*)subent setSize:NSMakeSize( sub_q.z, sub_q.z)];
|
|
[(ParticleEntity*)subent setParticleType:PARTICLE_FLASHER];
|
|
[(ParticleEntity*)subent setStatus:STATUS_EFFECT];
|
|
[(ParticleEntity*)subent setPosition:sub_pos];
|
|
[subent setUniverse:universe];
|
|
}
|
|
else
|
|
{
|
|
quaternion_normalise(&sub_q);
|
|
|
|
// NSLog(@"DEBUG universe = %@", universe);
|
|
|
|
subent = [universe getShip:subdesc]; // retained
|
|
|
|
if (subent)
|
|
{
|
|
// NSLog(@"DEBUG adding subentity %@ %@ to new %@ at %.3f,%.3f,%.3f", subent, [(ShipEntity*)subent name], name, sub_pos.x, sub_pos.y, sub_pos.z );
|
|
[(ShipEntity*)subent setStatus:STATUS_INACTIVE];
|
|
//
|
|
ref = vector_forward_from_quaternion(sub_q); // VECTOR FORWARD
|
|
//
|
|
[(ShipEntity*)subent setReference: ref];
|
|
[(ShipEntity*)subent setPosition: sub_pos];
|
|
[(ShipEntity*)subent setQRotation: sub_q];
|
|
//
|
|
if ([[(ShipEntity*)subent roles] isEqual:@"docking-slit"])
|
|
[subent setStatus:STATUS_EFFECT]; // hack keeps docking slit visible when at reduced detail
|
|
else
|
|
[self addSolidSubentityToCollisionRadius:(ShipEntity*)subent]; // hack - ignore docking-slit for collision radius
|
|
}
|
|
//
|
|
}
|
|
//NSLog(@"DEBUG reference (%.1f,%.1f,%.1f)", ref.x, ref.y, ref.z);
|
|
if (sub_entities == nil)
|
|
sub_entities = [[NSArray arrayWithObject:subent] retain];
|
|
else
|
|
{
|
|
NSMutableArray *temp = [NSMutableArray arrayWithArray:sub_entities];
|
|
// if (subent != nil)
|
|
[temp addObject:subent];
|
|
[sub_entities release];
|
|
sub_entities = [[NSArray arrayWithArray:temp] retain];
|
|
}
|
|
|
|
[subent setOwner: self];
|
|
|
|
// NSLog(@"DEBUG added subentity %@ to position %.3f,%.3f,%.3f", subent, subent->position.x, subent->position.y, subent->position.z );
|
|
|
|
[subent release];
|
|
}
|
|
}
|
|
// NSLog(@"DEBUG %@ subentities : %@", name, sub_entities);
|
|
}
|
|
//
|
|
if ([dict objectForKey:@"frangible"]) // if an object has frangible == YES then it can have its subentities shot away!
|
|
isFrangible = [(NSNumber *)[dict objectForKey:@"frangible"] boolValue];
|
|
//
|
|
if ([dict objectForKey:@"laser_color"])
|
|
{
|
|
NSString *laser_color_string = (NSString *)[dict objectForKey:@"laser_color"];
|
|
SEL color_selector = NSSelectorFromString(laser_color_string);
|
|
if ([NSColor respondsToSelector:color_selector])
|
|
{
|
|
id color_thing = [NSColor performSelector:color_selector];
|
|
if ([color_thing isKindOfClass:[NSColor class]])
|
|
[self setLaserColor:(NSColor *)color_thing];
|
|
}
|
|
}
|
|
else
|
|
[self setLaserColor:[NSColor redColor]];
|
|
//
|
|
// scan class
|
|
if ([dict objectForKey:@"scanClass"])
|
|
{
|
|
NSString *s_class= (NSString *)[dict objectForKey:@"scanClass"];
|
|
|
|
//NSLog(@"----- initialising ship with scan class '%@'",s_class);
|
|
|
|
scan_class = CLASS_NEUTRAL;
|
|
if ([s_class isEqual:@"CLASS_STATION"])
|
|
scan_class = CLASS_STATION;
|
|
if ([s_class isEqual:@"CLASS_THARGOID"])
|
|
scan_class = CLASS_THARGOID;
|
|
if ([s_class isEqual:@"CLASS_TARGET"])
|
|
scan_class = CLASS_TARGET;
|
|
if ([s_class isEqual:@"CLASS_CARGO"])
|
|
scan_class = CLASS_CARGO;
|
|
if ([s_class isEqual:@"CLASS_POLICE"])
|
|
scan_class = CLASS_POLICE;
|
|
if ([s_class isEqual:@"CLASS_MILITARY"])
|
|
scan_class = CLASS_MILITARY;
|
|
if ([s_class isEqual:@"CLASS_BUOY"])
|
|
scan_class = CLASS_BUOY;
|
|
if ([s_class isEqual:@"CLASS_NO_DRAW"])
|
|
scan_class = CLASS_NO_DRAW;
|
|
if ([s_class isEqual:@"CLASS_NEUTRAL"])
|
|
scan_class = CLASS_NEUTRAL;
|
|
if ([s_class isEqual:@"CLASS_ROCK"])
|
|
scan_class = CLASS_ROCK;
|
|
}
|
|
else
|
|
scan_class = CLASS_NOT_SET;
|
|
//
|
|
// scripting
|
|
if ([dict objectForKey:KEY_LAUNCH_ACTIONS])
|
|
[launch_actions addObjectsFromArray:(NSArray *)[dict objectForKey:KEY_LAUNCH_ACTIONS]];
|
|
if ([dict objectForKey:KEY_SCRIPT_ACTIONS])
|
|
[script_actions addObjectsFromArray:(NSArray *)[dict objectForKey:KEY_SCRIPT_ACTIONS]];
|
|
if ([dict objectForKey:KEY_DEATH_ACTIONS])
|
|
[death_actions addObjectsFromArray:(NSArray *)[dict objectForKey:KEY_DEATH_ACTIONS]];
|
|
if ([dict objectForKey:KEY_SETUP_ACTIONS])
|
|
{
|
|
PlayerEntity* player = (PlayerEntity*)[universe entityZero];
|
|
[player setScript_target:self];
|
|
NSArray * setup_actions = (NSArray *)[dict objectForKey:KEY_SETUP_ACTIONS];
|
|
int i;
|
|
for (i = 0; i < [setup_actions count]; i++)
|
|
{
|
|
if ([[setup_actions objectAtIndex:i] isKindOfClass:[NSDictionary class]])
|
|
[player checkCouplet:(NSDictionary *)[setup_actions objectAtIndex:i] onEntity:self];
|
|
if ([[setup_actions objectAtIndex:i] isKindOfClass:[NSString class]])
|
|
[player scriptAction:(NSString *)[setup_actions objectAtIndex:i] onEntity:self];
|
|
}
|
|
}
|
|
|
|
// escorts
|
|
//
|
|
if ([dict objectForKey:@"escorts"])
|
|
{
|
|
n_escorts = [(NSNumber *)[dict objectForKey:@"escorts"] intValue];
|
|
//NSLog(@"DEBUG adding %d escorts for new %@", n_escorts, name);
|
|
escortsAreSetUp = (n_escorts == 0);
|
|
}
|
|
|
|
// beacons
|
|
//
|
|
if ([dict objectForKey:@"beacon"])
|
|
{
|
|
NSString* beaconCode = (NSString*)[dict objectForKey:@"beacon"];
|
|
const char* bcode = [beaconCode lossyCString];
|
|
beaconChar = bcode[0];
|
|
// NSLog(@"DEBUG new %@ is a beacon with code: %s", name, bcode);
|
|
}
|
|
else
|
|
{
|
|
beaconChar = 0;
|
|
}
|
|
|
|
// rotating subentities
|
|
//
|
|
if ([dict objectForKey:@"rotational_velocity"])
|
|
{
|
|
subentity_rotational_velocity = [Entity quaternionFromString: (NSString*)[dict objectForKey:@"rotational_velocity"]];
|
|
}
|
|
else
|
|
{
|
|
quaternion_set_identity(&subentity_rotational_velocity);
|
|
}
|
|
|
|
// set weapon offsets
|
|
[self setDefaultWeaponOffsets];
|
|
//
|
|
if ([dict objectForKey:@"weapon_position_forward"])
|
|
forwardWeaponOffset = [Entity vectorFromString: (NSString *)[dict objectForKey:@"weapon_position_forward"]];
|
|
if ([dict objectForKey:@"weapon_position_aft"])
|
|
aftWeaponOffset = [Entity vectorFromString: (NSString *)[dict objectForKey:@"weapon_position_aft"]];
|
|
if ([dict objectForKey:@"weapon_position_port"])
|
|
portWeaponOffset = [Entity vectorFromString: (NSString *)[dict objectForKey:@"weapon_position_port"]];
|
|
if ([dict objectForKey:@"weapon_position_starboard"])
|
|
starboardWeaponOffset = [Entity vectorFromString: (NSString *)[dict objectForKey:@"weapon_position_starboard"]];
|
|
//
|
|
}
|
|
|
|
|
|
- (void) setDefaultWeaponOffsets
|
|
{
|
|
forwardWeaponOffset = make_vector( 0.0, 0.0, 0.0);
|
|
aftWeaponOffset = make_vector( 0.0, 0.0, 0.0);
|
|
portWeaponOffset = make_vector( 0.0, 0.0, 0.0);
|
|
starboardWeaponOffset = make_vector( 0.0, 0.0, 0.0);
|
|
}
|
|
|
|
- (int) scanClass
|
|
{
|
|
if (cloaking_device_active)
|
|
return CLASS_NO_DRAW;
|
|
else
|
|
return scan_class;
|
|
}
|
|
|
|
//////////////////////////////////////////////
|
|
|
|
BOOL ship_canCollide (ShipEntity* ship)
|
|
{
|
|
int s_status = ship->status;
|
|
int s_scan_class = ship->scan_class;
|
|
if ((s_status == STATUS_DEMO)||(s_status == STATUS_DEAD))
|
|
return NO;
|
|
if (((s_scan_class == CLASS_THARGOID) || (s_scan_class == CLASS_MISSILE)) && (ship->shot_time < 0.25)) // not yet fused
|
|
return NO;
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) canCollide
|
|
{
|
|
return ship_canCollide(self);
|
|
}
|
|
|
|
- (BOOL) checkCloseCollisionWith:(Entity *)other
|
|
{
|
|
if (!other)
|
|
return NO;
|
|
if ([collidingEntities containsObject:other]) // we know about this already!
|
|
return NO;
|
|
|
|
if (other->isShip)
|
|
{
|
|
// check bounding spheres versus bounding spheres
|
|
ShipEntity* other_ship = (ShipEntity*)other;
|
|
int i,j;
|
|
NSArray* other_subs = other_ship->sub_entities;
|
|
int n_subs1 = [sub_entities count];
|
|
int n_subs2 = [other_subs count];
|
|
ShipEntity* entity1[ 1 + n_subs1 ];
|
|
ShipEntity* entity2[ 1 + n_subs2 ];
|
|
Vector sphere_positions1[ 1 + n_subs1 ];
|
|
Vector sphere_positions2[ 1 + n_subs2 ];
|
|
double sphere_rad1[ 1 + n_subs1 ];
|
|
double sphere_rad2[ 1 + n_subs2 ];
|
|
int n_spheres1 = 1;
|
|
int n_spheres2 = 1;
|
|
sphere_positions1[0] = position;
|
|
sphere_rad1[0] = actual_radius;
|
|
entity1[0] = self;
|
|
sphere_positions2[0] = other->position;
|
|
sphere_rad2[0] = other->actual_radius;
|
|
entity2[0] = other_ship;
|
|
for (i = 0; i < n_subs1; i++)
|
|
{
|
|
Entity* se = [sub_entities objectAtIndex:i];
|
|
if ((se)&&[se canCollide]&&(se->isShip))
|
|
{
|
|
entity1[n_spheres1] = (ShipEntity*)se;
|
|
sphere_positions1[n_spheres1] = [(ShipEntity*)se absolutePositionForSubentity];
|
|
sphere_rad1[n_spheres1] = se->actual_radius;
|
|
n_spheres1++;
|
|
}
|
|
}
|
|
for (i = 0; i < n_subs2; i++)
|
|
{
|
|
Entity* se = [other_subs objectAtIndex:i];
|
|
if ((se)&&[se canCollide]&&(se->isShip))
|
|
{
|
|
entity2[n_spheres2] = (ShipEntity*)se;
|
|
sphere_positions2[n_spheres2] = [(ShipEntity*)se absolutePositionForSubentity];
|
|
sphere_rad2[n_spheres2] = se->actual_radius;
|
|
n_spheres2++;
|
|
}
|
|
}
|
|
for (i = 0; i < n_spheres1; i++)
|
|
{
|
|
for (j = 0; j < n_spheres2; j++)
|
|
{
|
|
double d1 = sphere_rad1[i] + sphere_rad2[j];
|
|
double d2 = distance2( sphere_positions1[i], sphere_positions2[j]);
|
|
if (d2 < d1 * d1)
|
|
{
|
|
// NSLog(@"DEBUG performing further checks for collision between %@ and %@", entity1[i], entity2[j]);
|
|
|
|
BOOL collision = YES;
|
|
if (i == 0)
|
|
{
|
|
if (j == 0)
|
|
collision = [self checkBoundingBoxCollisionWith: other];
|
|
}
|
|
else
|
|
{
|
|
if (j == 0)
|
|
collision = [entity1[i] subentityCheckBoundingBoxCollisionWith: other];
|
|
}
|
|
if (collision)
|
|
{
|
|
collider = entity2[j];
|
|
return YES;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
collider = other;
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) checkBoundingBoxCollisionWith:(Entity *)other
|
|
{
|
|
if (other->isShip)
|
|
{
|
|
// check bounding boxes ...
|
|
//
|
|
// get bounding box relative to this ship's orientation
|
|
BoundingBox arbb = [other findBoundingBoxRelativeTo:self InVectors: v_right: v_up: v_forward];
|
|
|
|
// construct 6 rectangles based on the sides of the possibly overlapping bounding boxes
|
|
NSRect other_x_rect = NSMakeRect(arbb.min_z, arbb.min_y, arbb.max_z - arbb.min_z, arbb.max_y - arbb.min_y);
|
|
NSRect other_y_rect = NSMakeRect(arbb.min_x, arbb.min_z, arbb.max_x - arbb.min_x, arbb.max_z - arbb.min_z);
|
|
NSRect other_z_rect = NSMakeRect(arbb.min_x, arbb.min_y, arbb.max_x - arbb.min_x, arbb.max_y - arbb.min_y);
|
|
|
|
NSRect ship_x_rect = NSMakeRect(boundingBox.min_z, boundingBox.min_y, boundingBox.max_z - boundingBox.min_z, boundingBox.max_y - boundingBox.min_y);
|
|
NSRect ship_y_rect = NSMakeRect(boundingBox.min_x, boundingBox.min_z, boundingBox.max_x - boundingBox.min_x, boundingBox.max_z - boundingBox.min_z);
|
|
NSRect ship_z_rect = NSMakeRect(boundingBox.min_x, boundingBox.min_y, boundingBox.max_x - boundingBox.min_x, boundingBox.max_y - boundingBox.min_y);
|
|
|
|
if (NSIntersectsRect(ship_x_rect,other_x_rect) && NSIntersectsRect(ship_y_rect,other_y_rect) && NSIntersectsRect(ship_z_rect,other_z_rect))
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
if (other->isParticle)
|
|
{
|
|
// check bounding boxes ...
|
|
//
|
|
// get position relative to this ship's orientation
|
|
Vector r_pos = other->position;
|
|
double cr = other->collision_radius;
|
|
r_pos.x -= position.x; r_pos.y -= position.y; r_pos.z -= position.z;
|
|
if ((r_pos.x + cr > boundingBox.min_x)&&
|
|
(r_pos.x - cr < boundingBox.max_x)&&
|
|
(r_pos.y + cr > boundingBox.min_y)&&
|
|
(r_pos.y - cr < boundingBox.max_y)&&
|
|
(r_pos.z + cr > boundingBox.min_z)&&
|
|
(r_pos.z - cr < boundingBox.max_z))
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) subentityCheckBoundingBoxCollisionWith:(Entity *)other
|
|
{
|
|
// NSLog(@"DEBUG [%@ subentityCheckBoundingBoxCollisionWith:%@]", self, other);
|
|
|
|
BoundingBox sebb = [self findSubentityBoundingBox];
|
|
|
|
// NSLog(@"DEBUG bounding box for subentity: %@ [%.1fm %.1fm]x [%.1fm %.1fm]y [%.1fm %.1fm]z", self,
|
|
// sebb.min_x, sebb.max_x, sebb.min_y, sebb.max_y, sebb.min_z, sebb.max_z);
|
|
|
|
if (other->isShip)
|
|
{
|
|
// check bounding boxes ...
|
|
Entity* parent = [self owner];
|
|
if (!parent)
|
|
return NO;
|
|
Vector i = vector_right_from_quaternion(parent->q_rotation);
|
|
Vector j = vector_up_from_quaternion(parent->q_rotation);
|
|
Vector k = vector_forward_from_quaternion(parent->q_rotation);
|
|
|
|
//
|
|
// get bounding box relative to this ship's orientation
|
|
BoundingBox arbb = [other findBoundingBoxRelativeTo:parent InVectors: i: j: k];
|
|
|
|
// NSLog(@"DEBUG bounding box for other: %@ [%.1fm %.1fm]x [%.1fm %.1fm]y [%.1fm %.1fm]z", other,
|
|
// arbb.min_x, arbb.max_x, arbb.min_y, arbb.max_y, arbb.min_z, arbb.max_z);
|
|
|
|
// construct 6 rectangles based on the sides of the possibly overlapping bounding boxes
|
|
NSRect x_rect = NSMakeRect(sebb.min_z, sebb.min_y, sebb.max_z - sebb.min_z, sebb.max_y - sebb.min_y);
|
|
NSRect y_rect = NSMakeRect(sebb.min_x, sebb.min_z, sebb.max_x - sebb.min_x, sebb.max_z - sebb.min_z);
|
|
NSRect z_rect = NSMakeRect(sebb.min_x, sebb.min_y, sebb.max_x - sebb.min_x, sebb.max_y - sebb.min_y);
|
|
NSRect other_x_rect = NSMakeRect(arbb.min_z, arbb.min_y, arbb.max_z - arbb.min_z, arbb.max_y - arbb.min_y);
|
|
NSRect other_y_rect = NSMakeRect(arbb.min_x, arbb.min_z, arbb.max_x - arbb.min_x, arbb.max_z - arbb.min_z);
|
|
NSRect other_z_rect = NSMakeRect(arbb.min_x, arbb.min_y, arbb.max_x - arbb.min_x, arbb.max_y - arbb.min_y);
|
|
|
|
// NSLog(@"DEBUG intersects in x:%@: y:%@: z:%@",
|
|
// NSIntersectsRect(x_rect,other_x_rect)? @"YES": @"NO ",
|
|
// NSIntersectsRect(y_rect,other_y_rect)? @"YES": @"NO ",
|
|
// NSIntersectsRect(z_rect,other_z_rect)? @"YES": @"NO ");
|
|
|
|
if (NSIntersectsRect(x_rect,other_x_rect) && NSIntersectsRect(y_rect,other_y_rect) && NSIntersectsRect(z_rect,other_z_rect))
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
if (other->isParticle)
|
|
{
|
|
// check bounding boxes ...
|
|
//
|
|
// get position relative to this ship's orientation
|
|
Vector r_pos = other->position;
|
|
double cr = other->collision_radius;
|
|
r_pos.x -= position.x; r_pos.y -= position.y; r_pos.z -= position.z;
|
|
if ((r_pos.x + cr > sebb.min_x)&&
|
|
(r_pos.x - cr < sebb.max_x)&&
|
|
(r_pos.y + cr > sebb.min_y)&&
|
|
(r_pos.y - cr < sebb.max_y)&&
|
|
(r_pos.z + cr > sebb.min_z)&&
|
|
(r_pos.z - cr < sebb.max_z))
|
|
return YES;
|
|
else
|
|
return NO;
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (BoundingBox) findSubentityBoundingBox
|
|
{
|
|
BoundingBox result;
|
|
Vector v = vertices[0];
|
|
mult_vector_gl_matrix(&v, rotMatrix);
|
|
v.x += position.x; v.y += position.y; v.z += position.z;
|
|
bounding_box_reset_to_vector(&result,v);
|
|
int i;
|
|
for (i = 1; i < n_vertices; i++)
|
|
{
|
|
v = vertices[i];
|
|
mult_vector_gl_matrix(&v, rotMatrix);
|
|
v.x += position.x; v.y += position.y; v.z += position.z;
|
|
bounding_box_add_vector(&result,v);
|
|
}
|
|
|
|
// NSLog(@"DEBUG subentity bounding box for %@ of %@ is [%.1fm %.1fm]x [%.1fm %.1fm]y [%.1fm %.1fm]z", self, [self owner],
|
|
// result.min_x, result.max_x, result.min_y, result.max_y, result.min_z, result.max_z);
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BoundingBox) findSubentityBoundingBoxRelativeTo: (Entity*)other inVectors: (Vector)vi: (Vector)vj: (Vector)vk
|
|
{
|
|
Entity* parent = [self owner];
|
|
Vector othpos = other->position;
|
|
Vector parent_pos = parent->position;
|
|
Vector relpos = make_vector( parent_pos.x - othpos.x, parent_pos.y - othpos.y, parent_pos.z - othpos.z);
|
|
GLfloat* parent_rotmatrix = [parent rotationMatrix];
|
|
BoundingBox result;
|
|
Vector v, w;
|
|
v = vertices[0];
|
|
mult_vector_gl_matrix(&v, rotMatrix);
|
|
v.x += position.x; v.y += position.y; v.z += position.z;
|
|
mult_vector_gl_matrix(&v, parent_rotmatrix);
|
|
v.x += relpos.x; v.y += relpos.y; v.z += relpos.z;
|
|
w = make_vector( dot_product( v, vi), dot_product( v, vj), dot_product( v, vk));
|
|
bounding_box_reset_to_vector(&result,w);
|
|
int i;
|
|
for (i = 1; i < n_vertices; i++)
|
|
{
|
|
v = vertices[i];
|
|
mult_vector_gl_matrix(&v, rotMatrix);
|
|
v.x += position.x; v.y += position.y; v.z += position.z;
|
|
mult_vector_gl_matrix(&v, parent_rotmatrix);
|
|
v.x += relpos.x; v.y += relpos.y; v.z += relpos.z;
|
|
w = make_vector( dot_product( v, vi), dot_product( v, vj), dot_product( v, vk));
|
|
bounding_box_add_vector(&result,w);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BoundingBox) findSubentityBoundingBoxRelativeToPosition: (Vector)othpos inVectors: (Vector)vi: (Vector)vj: (Vector)vk
|
|
{
|
|
Entity* parent = [self owner];
|
|
Vector parent_pos = parent->position;
|
|
Vector relpos = make_vector( parent_pos.x - othpos.x, parent_pos.y - othpos.y, parent_pos.z - othpos.z);
|
|
GLfloat* parent_rotmatrix = [parent rotationMatrix];
|
|
BoundingBox result;
|
|
Vector v, w;
|
|
v = vertices[0];
|
|
mult_vector_gl_matrix(&v, rotMatrix);
|
|
v.x += position.x; v.y += position.y; v.z += position.z;
|
|
mult_vector_gl_matrix(&v, parent_rotmatrix);
|
|
v.x += relpos.x; v.y += relpos.y; v.z += relpos.z;
|
|
w = make_vector( dot_product( v, vi), dot_product( v, vj), dot_product( v, vk));
|
|
bounding_box_reset_to_vector(&result,w);
|
|
int i;
|
|
for (i = 1; i < n_vertices; i++)
|
|
{
|
|
v = vertices[i];
|
|
mult_vector_gl_matrix(&v, rotMatrix);
|
|
v.x += position.x; v.y += position.y; v.z += position.z;
|
|
mult_vector_gl_matrix(&v, parent_rotmatrix);
|
|
v.x += relpos.x; v.y += relpos.y; v.z += relpos.z;
|
|
w = make_vector( dot_product( v, vi), dot_product( v, vj), dot_product( v, vk));
|
|
bounding_box_add_vector(&result,w);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
- (Vector) absolutePositionForSubentity
|
|
{
|
|
Vector abspos = position;
|
|
Entity* father = [self owner];
|
|
while (father)
|
|
{
|
|
GLfloat* r_mat = [father rotationMatrix];
|
|
mult_vector_gl_matrix(&abspos, r_mat);
|
|
Vector pos = father->position;
|
|
abspos.x += pos.x; abspos.y += pos.y; abspos.z += pos.z;
|
|
if (father != [father owner])
|
|
father = [father owner];
|
|
else
|
|
father = nil;
|
|
}
|
|
return abspos;
|
|
}
|
|
|
|
- (Vector) absolutePositionForSubentityOffset:(Vector) offset
|
|
{
|
|
|
|
Vector off = offset;
|
|
mult_vector_gl_matrix(&off, rotMatrix);
|
|
Vector abspos = make_vector( position.x + off.x, position.y + off.y, position.z + off.z);
|
|
Entity* father = [self owner];
|
|
while (father)
|
|
{
|
|
GLfloat* r_mat = [father rotationMatrix];
|
|
mult_vector_gl_matrix(&abspos, r_mat);
|
|
Vector pos = father->position;
|
|
abspos.x += pos.x; abspos.y += pos.y; abspos.z += pos.z;
|
|
if (father != [father owner])
|
|
father = [father owner];
|
|
else
|
|
father = nil;
|
|
}
|
|
return abspos;
|
|
}
|
|
|
|
- (void) addSolidSubentityToCollisionRadius:(ShipEntity*) subent
|
|
{
|
|
if (!subent)
|
|
return;
|
|
// double old_cr = collision_radius;
|
|
double distance = sqrt(magnitude2(subent->position)) + subent->collision_radius;
|
|
if (distance > collision_radius)
|
|
collision_radius = distance;
|
|
// NSLog(@"DEBUG - Added the collision radius of %@ to %@ (was %.2fm now %.2fm)", subent, self, old_cr, collision_radius);
|
|
}
|
|
|
|
|
|
- (void) update:(double) delta_t
|
|
{
|
|
double damping = 0.5 * delta_t;
|
|
double confidenceFactor;
|
|
double targetCR;
|
|
#ifdef GNUSTEP
|
|
int missile_chance=0;
|
|
int rhs=(int)(32 * 0.1 / delta_t);
|
|
if(rhs)
|
|
{
|
|
missile_chance = 1 + (ranrot_rand() % rhs);
|
|
}
|
|
#else
|
|
int missile_chance = 1 + (ranrot_rand() % (int)( 32 * 0.1 / delta_t));
|
|
#endif
|
|
double hurt_factor = 16 * pow(energy/max_energy, 4.0);
|
|
double last_success_factor = success_factor;
|
|
//
|
|
BOOL canBurn = has_fuel_injection && (fuel > 1); // was &&(fuel > 0)
|
|
BOOL isUsingAfterburner = canBurn && (flight_speed > max_flight_speed);
|
|
//
|
|
double max_available_speed = (canBurn)? max_flight_speed * AFTERBURNER_FACTOR : max_flight_speed;
|
|
//
|
|
|
|
//
|
|
// deal with collisions
|
|
//
|
|
[self manageCollisions];
|
|
[self saveToLastFrame];
|
|
|
|
// super update
|
|
//
|
|
[super update:delta_t];
|
|
|
|
// DEBUGGING
|
|
//
|
|
if (reportAImessages && (debug_condition != condition))
|
|
{
|
|
NSLog(@"DEBUG %@ condition is now %d", self, condition);
|
|
debug_condition = condition;
|
|
}
|
|
|
|
// update time between shots
|
|
//
|
|
shot_time +=delta_t;
|
|
|
|
// handle radio message effects
|
|
//
|
|
if (message_time > 0.0)
|
|
{
|
|
message_time -= delta_t;
|
|
if (message_time < 0.0)
|
|
message_time = 0.0;
|
|
}
|
|
|
|
|
|
// burning effects
|
|
//
|
|
if ((throw_sparks)||(energy < max_energy * 0.20))
|
|
{
|
|
if (energy_recharge_rate > 0.0) // prevents asteroid etc. from burning
|
|
{
|
|
next_spark_time -= delta_t;
|
|
if (next_spark_time < 0.0)
|
|
[self throwSparks];
|
|
}
|
|
}
|
|
|
|
// cloaking device
|
|
if (has_cloaking_device)
|
|
{
|
|
if (cloaking_device_active)
|
|
{
|
|
energy -= delta_t * CLOAKING_DEVICE_ENERGY_RATE;
|
|
if (energy < CLOAKING_DEVICE_MIN_ENERGY)
|
|
[self deactivateCloakingDevice];
|
|
}
|
|
else
|
|
{
|
|
if (energy < max_energy)
|
|
{
|
|
energy += delta_t * CLOAKING_DEVICE_ENERGY_RATE;
|
|
if (energy > max_energy)
|
|
{
|
|
energy = max_energy;
|
|
[shipAI message:@"ENERGY_FULL"];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// military_jammer
|
|
if (has_military_jammer)
|
|
{
|
|
if (military_jammer_active)
|
|
{
|
|
energy -= delta_t * MILITARY_JAMMER_ENERGY_RATE;
|
|
if (energy < MILITARY_JAMMER_MIN_ENERGY)
|
|
military_jammer_active = NO;
|
|
}
|
|
else
|
|
{
|
|
if (energy > 1.5 * MILITARY_JAMMER_MIN_ENERGY)
|
|
military_jammer_active = YES;
|
|
}
|
|
}
|
|
|
|
// check outside factors
|
|
//
|
|
aegis_status = [self checkForAegis]; // is a station or something nearby??
|
|
|
|
//scripting
|
|
if ((status == STATUS_IN_FLIGHT)&&([launch_actions count]))
|
|
{
|
|
int i;
|
|
[(PlayerEntity *)[universe entityZero] setScript_target:self];
|
|
for (i = 0; i < [launch_actions count]; i++)
|
|
[(PlayerEntity *)[universe entityZero] scriptAction:(NSString *)[launch_actions objectAtIndex:i] onEntity:self];
|
|
[launch_actions removeAllObjects];
|
|
}
|
|
|
|
// behaviours according to status and condition
|
|
//
|
|
if (status == STATUS_LAUNCHING)
|
|
{
|
|
if ([universe getTime] > launch_time + LAUNCH_DELAY) // move for while before thinking
|
|
{
|
|
status = STATUS_IN_FLIGHT;
|
|
[shipAI reactToMessage: @"LAUNCHED OKAY"];
|
|
//accepts_escorts = YES;
|
|
}
|
|
else
|
|
{
|
|
// ignore condition just keep moving...
|
|
[self applyRoll:delta_t*flight_roll andClimb:delta_t*flight_pitch];
|
|
[self applyThrust:delta_t];
|
|
if (energy < max_energy)
|
|
{
|
|
energy += energy_recharge_rate * delta_t;
|
|
if (energy > max_energy)
|
|
{
|
|
energy = max_energy;
|
|
[shipAI message:@"ENERGY_FULL"];
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
if (status == STATUS_DEMO)
|
|
{
|
|
[self applyRoll:delta_t*flight_roll andClimb:delta_t*flight_pitch];
|
|
position.x += delta_t*velocity.x;
|
|
position.y += delta_t*velocity.y;
|
|
position.z += delta_t*velocity.z;
|
|
if (position.z <= collision_radius * 3.6)
|
|
{
|
|
position.z = collision_radius * 3.6;
|
|
velocity.z = 0.0;
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
double range = [self rangeToPrimaryTarget];
|
|
double distance = [self rangeToDestination];
|
|
double target_speed = max_flight_speed;
|
|
double slow_down_range = weapon_range * COMBAT_WEAPON_RANGE_FACTOR * ((isUsingAfterburner)? 3.0 * AFTERBURNER_FACTOR : 1.0);
|
|
double max_cos = 0.995;
|
|
|
|
ShipEntity* target = (ShipEntity*)[universe entityForUniversalID:primaryTarget];
|
|
|
|
targetCR = (target)? target->collision_radius: 0;
|
|
|
|
if ((target == nil)||(target->scan_class == CLASS_NO_DRAW))
|
|
{
|
|
// It's no longer a parrot, it has ceased to be, it has joined the choir invisible...
|
|
if (primaryTarget != NO_TARGET)
|
|
{
|
|
[shipAI reactToMessage:@"TARGET_LOST"];
|
|
primaryTarget = NO_TARGET;
|
|
}
|
|
else
|
|
{
|
|
target_speed = [(ShipEntity *)[universe entityForUniversalID:primaryTarget] flight_speed];
|
|
if (target_speed < max_flight_speed)
|
|
{
|
|
target_speed += max_flight_speed;
|
|
target_speed /= 2.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (condition)
|
|
{
|
|
case CONDITION_IDLE :
|
|
if ((!isStation)&&(scan_class != CLASS_BUOY))
|
|
{
|
|
// damp roll and pitch
|
|
if (flight_roll < 0)
|
|
flight_roll += (flight_roll < -damping) ? damping : -flight_roll;
|
|
if (flight_roll > 0)
|
|
flight_roll -= (flight_roll > damping) ? damping : flight_roll;
|
|
if (flight_pitch < 0)
|
|
flight_pitch += (flight_pitch < -damping) ? damping : -flight_pitch;
|
|
if (flight_pitch > 0)
|
|
flight_pitch -= (flight_pitch > damping) ? damping : flight_pitch;
|
|
}
|
|
case CONDITION_TUMBLE :
|
|
break;
|
|
|
|
case CONDITION_TRACK_TARGET :
|
|
[self trackPrimaryTarget:delta_t:NO];
|
|
if ((proximity_alert != NO_TARGET)&&(proximity_alert != primaryTarget))
|
|
[self avoidCollision];
|
|
break;
|
|
|
|
case CONDITION_INTERCEPT_TARGET :
|
|
case CONDITION_COLLECT_TARGET :
|
|
if (condition == CONDITION_INTERCEPT_TARGET)
|
|
{
|
|
desired_speed = max_flight_speed;
|
|
if (range < desired_range)
|
|
[shipAI reactToMessage:@"DESIRED_RANGE_ACHIEVED"];
|
|
desired_speed = max_flight_speed * [self trackPrimaryTarget:delta_t:NO];
|
|
}
|
|
else
|
|
{
|
|
target_speed = [target getVelocityAsSpeed];
|
|
double eta = range / (flight_speed - target_speed);
|
|
double last_distance = last_success_factor;
|
|
success_factor = distance;
|
|
//
|
|
double slowdownTime = 96.0 / thrust; // more thrust implies better slowing
|
|
double minTurnSpeedFactor = 0.005 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds
|
|
|
|
if ((eta < slowdownTime)&&(flight_speed > max_flight_speed * minTurnSpeedFactor))
|
|
desired_speed = flight_speed * 0.75; // cut speed by 50% to a minimum minTurnSpeedFactor of speed
|
|
else
|
|
desired_speed = max_flight_speed;
|
|
|
|
if (desired_speed < target_speed)
|
|
{
|
|
desired_speed += target_speed;
|
|
if (target_speed > max_flight_speed)
|
|
[shipAI reactToMessage:@"TARGET_LOST"];
|
|
}
|
|
//
|
|
// [self trackPrimaryTarget:delta_t:NO]; // we used to do this instead of the following...
|
|
if (target) // check introduced to stop crash at next line
|
|
{
|
|
destination = target->position; /* HEISENBUG crash here */
|
|
desired_range = 0.5 * target->actual_radius;
|
|
[self trackDestination: delta_t : NO];
|
|
}
|
|
//
|
|
if (distance < last_distance) // improvement
|
|
{
|
|
frustration -= delta_t;
|
|
if (frustration < 0.0)
|
|
frustration = 0.0;
|
|
}
|
|
else
|
|
{
|
|
frustration += delta_t;
|
|
if (frustration > 10.0) // 10s of frustration
|
|
{
|
|
[shipAI reactToMessage:@"FRUSTRATED"];
|
|
frustration -= 5.0; //repeat after another five seconds' frustration
|
|
}
|
|
}
|
|
}
|
|
if ((proximity_alert != NO_TARGET)&&(proximity_alert != primaryTarget))
|
|
[self avoidCollision];
|
|
break;
|
|
|
|
case CONDITION_ATTACK_TARGET :
|
|
[self activateCloakingDevice];
|
|
desired_speed = max_available_speed;
|
|
if (range < 0.035 * weapon_range)
|
|
condition = CONDITION_ATTACK_FLY_FROM_TARGET;
|
|
else
|
|
if (universal_id & 1) // 50% of ships are smart S.M.R.T. smart!
|
|
{
|
|
if (randf() < 0.75)
|
|
condition = CONDITION_ATTACK_FLY_TO_TARGET_SIX;
|
|
else
|
|
condition = CONDITION_ATTACK_FLY_TO_TARGET_TWELVE;
|
|
}
|
|
else
|
|
{
|
|
condition = CONDITION_ATTACK_FLY_TO_TARGET;
|
|
}
|
|
frustration = 0.0; // condition changed, so reset frustration
|
|
break;
|
|
|
|
case CONDITION_ATTACK_FLY_TO_TARGET_SIX :
|
|
case CONDITION_ATTACK_FLY_TO_TARGET_TWELVE :
|
|
|
|
// deal with collisions and lost targets
|
|
//
|
|
if (proximity_alert != NO_TARGET)
|
|
[self avoidCollision];
|
|
if (range > SCANNER_MAX_RANGE)
|
|
{
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
[shipAI reactToMessage:@"TARGET_LOST"];
|
|
}
|
|
|
|
// control speed
|
|
//
|
|
if (range < slow_down_range)
|
|
{
|
|
desired_speed = target_speed;
|
|
// avoid head-on collision
|
|
//
|
|
if ((range < 0.5 * distance)&&(condition == CONDITION_ATTACK_FLY_TO_TARGET_SIX))
|
|
condition = CONDITION_ATTACK_FLY_TO_TARGET_TWELVE;
|
|
}
|
|
else
|
|
desired_speed = max_available_speed; // use afterburner to approach
|
|
|
|
|
|
// if within 0.75km of the target's six or twelve then vector in attack
|
|
//
|
|
if (distance < 750.0)
|
|
{
|
|
condition = CONDITION_ATTACK_FLY_TO_TARGET;
|
|
frustration = 0.0;
|
|
desired_speed = target_speed; // within the weapon's range don't use afterburner
|
|
}
|
|
|
|
// target-six
|
|
if (condition == CONDITION_ATTACK_FLY_TO_TARGET_SIX)
|
|
{
|
|
// head for a point weapon-range * 0.5 to the six of the target
|
|
//
|
|
destination = [target distance_six:0.5 * weapon_range];
|
|
}
|
|
// target-twelve
|
|
if (condition == CONDITION_ATTACK_FLY_TO_TARGET_TWELVE)
|
|
{
|
|
// head for a point 1.25km above the target
|
|
//
|
|
destination = [target distance_twelve:1250];
|
|
}
|
|
|
|
[self trackDestination:delta_t :NO];
|
|
|
|
// use weaponry
|
|
//
|
|
if (missiles > missile_chance * hurt_factor)
|
|
{
|
|
//NSLog(@"]==> firing missile : missiles %d, missile_chance %d, hurt_factor %.3f", missiles, missile_chance, hurt_factor);
|
|
[self fireMissile];
|
|
}
|
|
[self activateCloakingDevice];
|
|
[self fireMainWeapon:range];
|
|
break;
|
|
|
|
case CONDITION_ATTACK_MINING_TARGET :
|
|
if ((range < 650)||(proximity_alert != NO_TARGET))
|
|
{
|
|
if (proximity_alert == NO_TARGET)
|
|
{
|
|
desired_speed = range * max_flight_speed / (650.0 * 16.0);
|
|
}
|
|
else
|
|
{
|
|
[self avoidCollision];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (range > SCANNER_MAX_RANGE)
|
|
{
|
|
condition = CONDITION_IDLE;
|
|
[shipAI reactToMessage:@"TARGET_LOST"];
|
|
}
|
|
desired_speed = max_flight_speed * 0.375;
|
|
}
|
|
[self trackPrimaryTarget:delta_t:NO];
|
|
[self fireMainWeapon:range];
|
|
break;
|
|
|
|
case CONDITION_ATTACK_FLY_TO_TARGET :
|
|
if ((range < COMBAT_IN_RANGE_FACTOR * weapon_range)||(proximity_alert != NO_TARGET))
|
|
{
|
|
if (proximity_alert == NO_TARGET)
|
|
{
|
|
if (aft_weapon_type == WEAPON_NONE)
|
|
{
|
|
jink.x = (ranrot_rand() % 256) - 128.0;
|
|
jink.y = (ranrot_rand() % 256) - 128.0;
|
|
jink.z = 1000.0;
|
|
condition = CONDITION_ATTACK_FLY_FROM_TARGET;
|
|
frustration = 0.0;
|
|
desired_speed = max_available_speed;
|
|
}
|
|
else
|
|
{
|
|
//NSLog(@"DEBUG >>>>> %@ %d entering running defense mode", name, universal_id);
|
|
|
|
jink = make_vector( 0.0, 0.0, 0.0);
|
|
condition = CONDITION_RUNNING_DEFENSE;
|
|
frustration = 0.0;
|
|
desired_speed = max_flight_speed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
[self avoidCollision];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (range > SCANNER_MAX_RANGE)
|
|
{
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
[shipAI reactToMessage:@"TARGET_LOST"];
|
|
}
|
|
}
|
|
|
|
// control speed
|
|
//
|
|
if (range <= slow_down_range)
|
|
desired_speed = target_speed; // within the weapon's range don't use afterburner
|
|
else
|
|
desired_speed = max_available_speed; // use afterburner to approach
|
|
|
|
last_success_factor = success_factor;
|
|
success_factor = [self trackPrimaryTarget:delta_t:NO]; // do the actual piloting
|
|
if ((success_factor > 0.999)||(success_factor > last_success_factor))
|
|
{
|
|
frustration -= delta_t;
|
|
if (frustration < 0.0)
|
|
frustration = 0.0;
|
|
}
|
|
else
|
|
{
|
|
frustration += delta_t;
|
|
if (frustration > 3.0) // 3s of frustration
|
|
{
|
|
[shipAI reactToMessage:@"FRUSTRATED"];
|
|
// THIS IS HERE AS A TEST ONLY
|
|
// BREAK OFF
|
|
jink.x = (ranrot_rand() % 256) - 128.0;
|
|
jink.y = (ranrot_rand() % 256) - 128.0;
|
|
jink.z = 1000.0;
|
|
condition = CONDITION_ATTACK_FLY_FROM_TARGET;
|
|
frustration = 0.0;
|
|
desired_speed = max_flight_speed;
|
|
}
|
|
}
|
|
|
|
if (missiles > missile_chance * hurt_factor)
|
|
{
|
|
//NSLog(@"]==> firing missile : missiles %d, missile_chance %d, hurt_factor %.3f", missiles, missile_chance, hurt_factor);
|
|
[self fireMissile];
|
|
}
|
|
[self activateCloakingDevice];
|
|
[self fireMainWeapon:range];
|
|
break;
|
|
|
|
case CONDITION_ATTACK_FLY_FROM_TARGET :
|
|
if (range > COMBAT_OUT_RANGE_FACTOR * weapon_range + 15.0 * jink.x)
|
|
{
|
|
jink.x = 0.0;
|
|
jink.y = 0.0;
|
|
jink.z = 0.0;
|
|
condition = CONDITION_ATTACK_TARGET;
|
|
frustration = 0.0;
|
|
}
|
|
[self trackPrimaryTarget:delta_t:YES];
|
|
if (missiles > missile_chance * hurt_factor)
|
|
{
|
|
//NSLog(@"]==> firing missile : missiles %d, missile_chance %d, hurt_factor %.3f", missiles, missile_chance, hurt_factor);
|
|
[self fireMissile];
|
|
}
|
|
[self activateCloakingDevice];
|
|
break;
|
|
|
|
case CONDITION_RUNNING_DEFENSE :
|
|
if (range > weapon_range)
|
|
{
|
|
jink.x = 0.0;
|
|
jink.y = 0.0;
|
|
jink.z = 0.0;
|
|
condition = CONDITION_ATTACK_FLY_TO_TARGET;
|
|
frustration = 0.0;
|
|
}
|
|
[self trackPrimaryTarget:delta_t:YES];
|
|
[self fireAftWeapon:range];
|
|
[self activateCloakingDevice];
|
|
break;
|
|
|
|
case CONDITION_FLEE_TARGET :
|
|
if (range > desired_range)
|
|
{
|
|
[shipAI message:@"REACHED_SAFETY"];
|
|
}
|
|
else
|
|
{
|
|
desired_speed = max_available_speed;
|
|
}
|
|
[self trackPrimaryTarget:delta_t:YES];
|
|
if (([(ShipEntity *)[self getPrimaryTarget] getPrimaryTarget] == self)&&(missiles > missile_chance * hurt_factor))
|
|
{
|
|
//NSLog(@"]==> firing missile : missiles %d, missile_chance %d, hurt_factor %.3f", missiles, missile_chance, hurt_factor);
|
|
[self fireMissile];
|
|
}
|
|
[self activateCloakingDevice];
|
|
break;
|
|
|
|
case CONDITION_FLY_RANGE_FROM_DESTINATION :
|
|
if (distance < desired_range)
|
|
condition = CONDITION_FLY_FROM_DESTINATION;
|
|
else
|
|
condition = CONDITION_FLY_TO_DESTINATION;
|
|
frustration = 0.0;
|
|
break;
|
|
|
|
case CONDITION_FACE_DESTINATION :
|
|
desired_speed = 0.0;
|
|
// NSLog(@"DEBUG >>>>> distance %.1f desired_range %.1f", distance, desired_range);
|
|
if (desired_range > 1.0)
|
|
max_cos = sqrt(1 - desired_range*desired_range/(distance * distance));
|
|
else
|
|
max_cos = 0.995; // 0.995 - cos(5 degrees) is close enough
|
|
confidenceFactor = [self trackDestination:delta_t:NO];
|
|
if (confidenceFactor > max_cos)
|
|
{
|
|
// desired facing achieved
|
|
[shipAI message:@"FACING_DESTINATION"];
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
}
|
|
if ((proximity_alert != NO_TARGET)&&(proximity_alert != primaryTarget))
|
|
[self avoidCollision];
|
|
break;
|
|
|
|
case CONDITION_FORMATION_FORM_UP :
|
|
// get updated destination from owner
|
|
{
|
|
double eta = (distance - desired_range) / flight_speed;
|
|
if (eta < 5.0)
|
|
desired_speed = [(ShipEntity *)[universe entityForUniversalID:owner] flight_speed] * 1.25;
|
|
else
|
|
desired_speed = max_flight_speed;
|
|
}
|
|
case CONDITION_FLY_TO_DESTINATION :
|
|
if (distance < desired_range)// + collision_radius)
|
|
{
|
|
// desired range achieved
|
|
[shipAI message:@"DESIRED_RANGE_ACHIEVED"];
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
desired_speed = 0.0;
|
|
}
|
|
else
|
|
{
|
|
double last_distance = last_success_factor;
|
|
double eta = distance / flight_speed;
|
|
|
|
success_factor = distance;
|
|
|
|
// do the actual piloting!!
|
|
[self trackDestination:delta_t:NO];
|
|
|
|
double slowdownTime = (thrust > 0.0)? flight_speed / thrust : 4.0; // 10% safety margin
|
|
double minTurnSpeedFactor = 0.05 * max_flight_pitch * max_flight_roll; // faster turning implies higher speeds
|
|
|
|
if ((eta < slowdownTime)&&(flight_speed > max_flight_speed * minTurnSpeedFactor))
|
|
desired_speed = flight_speed * 0.50; // cut speed by 50% to a minimum minTurnSpeedFactor of speed
|
|
|
|
if (distance < last_distance) // improvement
|
|
{
|
|
frustration -= delta_t;
|
|
if (frustration < 0.0)
|
|
frustration = 0.0;
|
|
}
|
|
else
|
|
{
|
|
// double old_frustration = frustration;
|
|
|
|
frustration += delta_t;
|
|
|
|
// if (floor(old_frustration * 5.0) < floor(frustration * 5.0))
|
|
// NSLog(@"DEBUG %@ has increasing frustration (%.4f)", self, frustration);
|
|
//
|
|
if ((frustration > slowdownTime * 10.0)||(frustration > 15.0)) // 10x slowdownTime or 15s of frustration
|
|
{
|
|
[shipAI reactToMessage:@"FRUSTRATED"];
|
|
frustration -= slowdownTime * 5.0; //repeat after another five units of frustration
|
|
}
|
|
}
|
|
}
|
|
if ((proximity_alert != NO_TARGET)&&(proximity_alert != primaryTarget))
|
|
[self avoidCollision];
|
|
break;
|
|
|
|
case CONDITION_FORMATION_BREAK :
|
|
case CONDITION_FLY_FROM_DESTINATION :
|
|
if (distance > desired_range)
|
|
{
|
|
// desired range achieved
|
|
[shipAI message:@"DESIRED_RANGE_ACHIEVED"];
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
desired_speed = 0.0;
|
|
}
|
|
else
|
|
{
|
|
// double eta = (desired_range - distance) / flight_speed;
|
|
desired_speed = max_flight_speed;
|
|
// don't cut speed on retreating...
|
|
// if ((eta < 1.0)&&(flight_speed > max_flight_speed*0.25))
|
|
// desired_speed = flight_speed * 0.5; // cut speed to a minimum of 1/4 speed
|
|
}
|
|
[self trackDestination:delta_t:YES];
|
|
if ((proximity_alert != NO_TARGET)&&(proximity_alert != primaryTarget))
|
|
[self avoidCollision];
|
|
break;
|
|
|
|
case CONDITION_AVOID_COLLISION :
|
|
if (distance > desired_range)
|
|
{
|
|
[self resumePostProximityAlert];
|
|
}
|
|
else
|
|
{
|
|
ShipEntity* prox_ship = [self proximity_alert];
|
|
if (prox_ship)
|
|
{
|
|
desired_range = prox_ship->collision_radius * PROXIMITY_AVOID_DISTANCE;
|
|
destination = prox_ship->position;
|
|
// destination = [prox_ship distance_six:-[prox_ship flight_speed]]; // where it'll be in one seconds's time
|
|
}
|
|
double dq = [self trackDestination:delta_t:YES];
|
|
if (dq >= 0)
|
|
dq = 0.5 * dq + 0.5;
|
|
else
|
|
dq = 0.0;
|
|
desired_speed = max_flight_speed * dq;
|
|
}
|
|
break;
|
|
|
|
case CONDITION_TRACK_AS_TURRET :
|
|
{
|
|
double aim = [self ballTrackLeadingTarget:delta_t];
|
|
ShipEntity* turret_owner = (ShipEntity *)[self owner];
|
|
ShipEntity* turret_target = (ShipEntity *)[turret_owner getPrimaryTarget];
|
|
//
|
|
if ((turret_owner)&&(turret_target)&&[turret_owner hasHostileTarget])
|
|
{
|
|
Vector p1 = turret_target->position;
|
|
Vector p0 = turret_owner->position;
|
|
double cr = turret_owner->collision_radius;
|
|
p1.x -= p0.x; p1.y -= p0.y; p1.z -= p0.z;
|
|
if (aim > .95)
|
|
{
|
|
[self fireTurretCannon: sqrt( magnitude2( p1)) - cr];
|
|
// NSLog(@"DEBUG BANG! BANG! BANG!");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONDITION_EXPERIMENTAL :
|
|
{
|
|
double aim = [self ballTrackTarget:delta_t];
|
|
if (aim > .95)
|
|
{
|
|
NSLog(@"DEBUG BANG! BANG! BANG!");
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
//
|
|
// in (almost) every case...
|
|
//
|
|
if (condition != CONDITION_TRACK_AS_TURRET)
|
|
{
|
|
[self applyRoll:delta_t*flight_roll andClimb:delta_t*flight_pitch];
|
|
[self applyThrust:delta_t];
|
|
}
|
|
//
|
|
//
|
|
if (energy < max_energy)
|
|
{
|
|
energy += energy_recharge_rate * delta_t;
|
|
if (energy > max_energy)
|
|
{
|
|
energy = max_energy;
|
|
[shipAI message:@"ENERGY_FULL"];
|
|
}
|
|
}
|
|
//
|
|
// subentity rotation
|
|
//
|
|
if ((subentity_rotational_velocity.x)||(subentity_rotational_velocity.y)||(subentity_rotational_velocity.z)||(subentity_rotational_velocity.w != 1.0))
|
|
{
|
|
Quaternion qf = subentity_rotational_velocity;
|
|
qf.w *= (1.0 - delta_t);
|
|
qf.x *= delta_t;
|
|
qf.y *= delta_t;
|
|
qf.z *= delta_t;
|
|
q_rotation = quaternion_multiply( qf, q_rotation);
|
|
}
|
|
//
|
|
//
|
|
// if (isStation && (sub_entities))
|
|
// NSLog(@"DEBUG %@ sub_entities %@", [self name], [sub_entities description]);
|
|
if (sub_entities)
|
|
{
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
// if (isStation && (sub_entities))
|
|
// NSLog(@"DEBUG %@ going to update sub_entity %@", [self name], [sub_entities objectAtIndex:i]);
|
|
[(Entity *)[sub_entities objectAtIndex:i] update:delta_t];
|
|
}
|
|
}
|
|
//
|
|
// update destination position for escorts
|
|
if (n_escorts > 0)
|
|
{
|
|
int i;
|
|
for (i = 0; i < n_escorts; i++)
|
|
{
|
|
ShipEntity *escorter = (ShipEntity *)[universe entityForUniversalID:escort_ids[i]];
|
|
// check it's still an escort ship
|
|
BOOL escorter_okay = YES;
|
|
if (!escorter)
|
|
escorter_okay = NO;
|
|
else
|
|
escorter_okay = escorter->isShip;
|
|
if (escorter_okay)
|
|
[escorter setDestination:[self getCoordinatesForEscortPosition:i]]; // update its destination
|
|
else
|
|
escort_ids[i--] = escort_ids[--n_escorts]; // remove the escort
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// override Entity saveToLastFrame
|
|
//
|
|
- (void) saveToLastFrame
|
|
{
|
|
double t_now = [universe getTime];
|
|
if (t_now >= track_time + 0.1) // update every 1/10 of a second
|
|
{
|
|
// save previous data
|
|
track_time = t_now;
|
|
track[track_index].position = position;
|
|
track[track_index].q_rotation = q_rotation;
|
|
track[track_index].timeframe = track_time;
|
|
track[track_index].k = v_forward;
|
|
//
|
|
if (sub_entities)
|
|
{
|
|
// NSLog(@"DEBUG %@'s subentities ...", self);
|
|
int i;
|
|
int n = [sub_entities count];
|
|
Frame thisFrame;
|
|
thisFrame.q_rotation = q_rotation;
|
|
thisFrame.timeframe = track_time;
|
|
thisFrame.k = v_forward;
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
Entity* se = (Entity*)[sub_entities objectAtIndex:i];
|
|
Vector sepos = se->position;
|
|
if ((se->isParticle)&&([(ParticleEntity*)se particleType] == PARTICLE_EXHAUST))
|
|
{
|
|
thisFrame.position = make_vector(
|
|
position.x + v_right.x * sepos.x + v_up.x * sepos.y + v_forward.x * sepos.z,
|
|
position.y + v_right.y * sepos.x + v_up.y * sepos.y + v_forward.y * sepos.z,
|
|
position.z + v_right.z * sepos.x + v_up.z * sepos.y + v_forward.z * sepos.z);
|
|
[se saveFrame:thisFrame atIndex:track_index]; // syncs subentity track_index to this entity
|
|
// NSLog(@"DEBUG ... %@ %@ [%.2f %.2f %.2f]", self, se, thisFrame.position.x - position.x, thisFrame.position.y - position.y, thisFrame.position.z - position.z);
|
|
}
|
|
}
|
|
}
|
|
//
|
|
track_index = (track_index + 1 ) & 0xff;
|
|
//
|
|
}
|
|
}
|
|
|
|
// reset position tracking
|
|
//
|
|
- (void) resetTracking
|
|
{
|
|
Frame resetFrame;
|
|
resetFrame.position = position;
|
|
resetFrame.q_rotation = q_rotation;
|
|
resetFrame.k = v_forward;
|
|
Vector vel = make_vector( v_forward.x * flight_speed, v_forward.y * flight_speed, v_forward.z * flight_speed);
|
|
[self resetFramesFromFrame:resetFrame withVelocity:vel];
|
|
if (sub_entities)
|
|
{
|
|
int i;
|
|
int n = [sub_entities count];
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
Entity* se = (Entity*)[sub_entities objectAtIndex:i];
|
|
Vector sepos = se->position;
|
|
if ((se->isParticle)&&([(ParticleEntity*)se particleType] == PARTICLE_EXHAUST))
|
|
{
|
|
resetFrame.position = make_vector(
|
|
position.x + v_right.x * sepos.x + v_up.x * sepos.y + v_forward.x * sepos.z,
|
|
position.y + v_right.y * sepos.x + v_up.y * sepos.y + v_forward.y * sepos.z,
|
|
position.z + v_right.z * sepos.x + v_up.z * sepos.y + v_forward.z * sepos.z);
|
|
[se resetFramesFromFrame:resetFrame withVelocity:vel];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// return a point 36u back from the front of the ship
|
|
// this equates with the centre point of a cobra mk3
|
|
//
|
|
- (Vector) getViewpointPosition
|
|
{
|
|
Vector viewpoint = position;
|
|
float nose = boundingBox.max_z - 36.0;
|
|
viewpoint.x += nose * v_forward.x; viewpoint.y += nose * v_forward.y; viewpoint.z += nose * v_forward.z;
|
|
return viewpoint;
|
|
}
|
|
|
|
- (void) drawEntity:(BOOL) immediate :(BOOL) translucent
|
|
{
|
|
if (zero_distance > no_draw_distance) return; // TOO FAR AWAY
|
|
|
|
if ([universe breakPatternHide]) return; // DON'T DRAW
|
|
|
|
if (cloaking_device_active && (randf() > 0.10)) return; // DON'T DRAW
|
|
|
|
if (!translucent)
|
|
[super drawEntity:immediate:translucent];
|
|
//debug
|
|
//NSLog(@"DEBUG drawn ship (%@ %d)", name, universal_id);
|
|
|
|
//
|
|
checkGLErrors([NSString stringWithFormat:@"ShipEntity after drawing Entity (main) %@", self]);
|
|
//
|
|
|
|
if (immediate)
|
|
return; // don't draw sub-entities when constructing a displayList
|
|
|
|
if (sub_entities)
|
|
{
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
Entity *se = (Entity *)[sub_entities objectAtIndex:i];
|
|
[se setOwner:self]; // refresh ownership
|
|
[se drawSubEntity:immediate:translucent];
|
|
}
|
|
}
|
|
//
|
|
checkGLErrors([NSString stringWithFormat:@"ShipEntity after drawing Entity (subentities) %@", self]);
|
|
//
|
|
}
|
|
|
|
static GLfloat cargo_color[4] = { 0.9, 0.9, 0.9, 1.0}; // gray
|
|
static GLfloat hostile_color[4] = { 1.0, 0.25, 0.0, 1.0}; // red/orange
|
|
static GLfloat neutral_color[4] = { 1.0, 1.0, 0.0, 1.0}; // yellow
|
|
static GLfloat friendly_color[4] = { 0.0, 1.0, 0.0, 1.0}; // green
|
|
static GLfloat missile_color[4] = { 0.0, 1.0, 1.0, 1.0}; // cyan
|
|
static GLfloat police_color1[4] = { 0.5, 0.0, 1.0, 1.0}; // purpley-blue
|
|
static GLfloat police_color2[4] = { 1.0, 0.0, 0.5, 1.0}; // purpley-red
|
|
static GLfloat jammed_color[4] = { 0.0, 0.0, 0.0, 0.0}; // clear black
|
|
static GLfloat mascem_color1[4] = { 0.3, 0.3, 0.3, 1.0}; // dark gray
|
|
static GLfloat mascem_color2[4] = { 0.4, 0.1, 0.4, 1.0}; // purple
|
|
|
|
- (GLfloat *) scannerDisplayColorForShip:(ShipEntity*)otherShip :(BOOL)isHostile :(BOOL)flash
|
|
{
|
|
|
|
if (has_military_jammer && military_jammer_active)
|
|
{
|
|
if (![otherShip hasMilitaryScannerFilter])
|
|
return jammed_color;
|
|
else
|
|
{
|
|
if (flash)
|
|
return mascem_color1;
|
|
else
|
|
{
|
|
if (isHostile)
|
|
return hostile_color;
|
|
else
|
|
return mascem_color2;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (scan_class)
|
|
{
|
|
case CLASS_ROCK :
|
|
case CLASS_CARGO :
|
|
return cargo_color;
|
|
case CLASS_THARGOID :
|
|
if (flash)
|
|
return hostile_color;
|
|
else
|
|
return friendly_color;
|
|
case CLASS_MISSILE :
|
|
return missile_color;
|
|
case CLASS_STATION :
|
|
return friendly_color;
|
|
case CLASS_BUOY :
|
|
if (flash)
|
|
return friendly_color;
|
|
else
|
|
return neutral_color;
|
|
case CLASS_POLICE :
|
|
case CLASS_MILITARY :
|
|
if ((isHostile)&&(flash))
|
|
return police_color2;
|
|
else
|
|
return police_color1;
|
|
case CLASS_MINE :
|
|
if (flash)
|
|
return neutral_color;
|
|
else
|
|
return hostile_color;
|
|
default :
|
|
if (isHostile)
|
|
return hostile_color;
|
|
}
|
|
return neutral_color;
|
|
}
|
|
|
|
- (BOOL) isJammingScanning
|
|
{
|
|
return (has_military_jammer && military_jammer_active);
|
|
}
|
|
|
|
- (BOOL) hasMilitaryScannerFilter
|
|
{
|
|
return has_military_scanner_filter;
|
|
}
|
|
|
|
- (void) addExhaust:(ParticleEntity *)exhaust
|
|
{
|
|
if (!exhaust)
|
|
return;
|
|
if (sub_entities == nil)
|
|
sub_entities = [[NSArray arrayWithObject:exhaust] retain];
|
|
else
|
|
{
|
|
NSMutableArray *temp = [NSMutableArray arrayWithArray:sub_entities];
|
|
[temp addObject:exhaust];
|
|
[sub_entities release];
|
|
sub_entities = [[NSArray arrayWithArray:temp] retain];
|
|
}
|
|
}
|
|
|
|
- (void) addExhaustAt:(Vector) ex_position withScale:(Vector) ex_scale
|
|
{
|
|
ParticleEntity *exhaust = [[ParticleEntity alloc] initExhaustFromShip:self offsetVector:ex_position scaleVector:ex_scale]; //retained
|
|
[exhaust setStatus:STATUS_EFFECT];
|
|
[self addExhaust:exhaust];
|
|
[exhaust release]; // released
|
|
}
|
|
|
|
|
|
- (void) applyThrust:(double) delta_t
|
|
{
|
|
double max_available_speed = (has_fuel_injection && (fuel > 1))? max_flight_speed * AFTERBURNER_FACTOR : max_flight_speed;
|
|
|
|
velocity.x += momentum.x / mass; momentum.x = 0;
|
|
velocity.y += momentum.y / mass; momentum.y = 0;
|
|
velocity.z += momentum.z / mass; momentum.z = 0;
|
|
|
|
position.x += delta_t*velocity.x;
|
|
position.y += delta_t*velocity.y;
|
|
position.z += delta_t*velocity.z;
|
|
|
|
if (velocity.x > 0.0)
|
|
velocity.x -= (velocity.x > delta_t*thrust) ? delta_t*thrust : velocity.x;
|
|
if (velocity.y > 0.0)
|
|
velocity.y -= (velocity.y > delta_t*thrust) ? delta_t*thrust : velocity.y;
|
|
if (velocity.z > 0.0)
|
|
velocity.z -= (velocity.z > delta_t*thrust) ? delta_t*thrust : velocity.z;
|
|
if (velocity.x < 0.0)
|
|
velocity.x -= (velocity.x < -delta_t*thrust) ? -delta_t*thrust : velocity.x;
|
|
if (velocity.y < 0.0)
|
|
velocity.y -= (velocity.y < -delta_t*thrust) ? -delta_t*thrust : velocity.y;
|
|
if (velocity.z < 0.0)
|
|
velocity.z -= (velocity.z < -delta_t*thrust) ? -delta_t*thrust : velocity.z;
|
|
|
|
if (condition == CONDITION_TUMBLE) return; //testing
|
|
|
|
|
|
// check for speed
|
|
if (desired_speed > max_available_speed)
|
|
desired_speed = max_available_speed;
|
|
|
|
if (flight_speed > desired_speed)
|
|
{
|
|
[self decrease_flight_speed:delta_t*thrust];
|
|
if (flight_speed < desired_speed) flight_speed = desired_speed;
|
|
}
|
|
if (flight_speed < desired_speed)
|
|
{
|
|
[self increase_flight_speed:delta_t*thrust];
|
|
if (flight_speed > desired_speed) flight_speed = desired_speed;
|
|
}
|
|
[self moveForward:delta_t*flight_speed];
|
|
|
|
// burn fuel at the appropriate rate
|
|
if ((flight_speed > max_flight_speed) && has_fuel_injection && (fuel > 0))
|
|
{
|
|
fuel_accumulator -= delta_t * AFTERBURNER_NPC_BURNRATE;
|
|
while (fuel_accumulator < 0.0)
|
|
{
|
|
if (fuel-- < 1)
|
|
max_available_speed = max_flight_speed;
|
|
fuel_accumulator += 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) applyRoll:(GLfloat) roll1 andClimb:(GLfloat) climb1
|
|
{
|
|
Quaternion q1;
|
|
|
|
if ((!roll1)&&(!climb1)&&(!has_rotated))
|
|
return;
|
|
|
|
quaternion_set_identity(&q1);
|
|
|
|
if (roll1)
|
|
quaternion_rotate_about_z( &q1, -roll1);
|
|
|
|
if (climb1)
|
|
quaternion_rotate_about_x( &q1, -climb1);
|
|
|
|
q_rotation = quaternion_multiply( q1, q_rotation);
|
|
quaternion_normalise(&q_rotation); // probably not strictly necessary but good to do to keep q_rotation sane
|
|
quaternion_into_gl_matrix(q_rotation, rotMatrix);
|
|
|
|
v_forward = vector_forward_from_quaternion(q_rotation);
|
|
v_up = vector_up_from_quaternion(q_rotation);
|
|
v_right = vector_right_from_quaternion(q_rotation);
|
|
}
|
|
|
|
|
|
- (void) avoidCollision
|
|
{
|
|
if (scan_class == CLASS_MISSILE)
|
|
return; // missiles are SUPPOSED to collide!
|
|
|
|
// NSLog(@"DEBUG ***** %@ in AVOID COLLISION!", self);
|
|
|
|
|
|
ShipEntity* prox_ship = [self proximity_alert];
|
|
|
|
if (prox_ship)
|
|
{
|
|
// if (self == [universe entityZero])
|
|
// NSLog(@"DEBUG ***** proximity alert for %@ %d against target %d", name, universal_id, proximity_alert);
|
|
|
|
if (previousCondition)
|
|
{
|
|
//
|
|
// NSLog(@"DEBUG ***** avoidCollision dropping previousCondition");
|
|
//
|
|
[previousCondition release];
|
|
previousCondition = nil;
|
|
}
|
|
|
|
previousCondition = [[NSMutableDictionary alloc] initWithCapacity:16];
|
|
|
|
[previousCondition setObject:[NSNumber numberWithInt:condition] forKey:@"condition"];
|
|
[previousCondition setObject:[NSNumber numberWithInt:primaryTarget] forKey:@"primaryTarget"];
|
|
[previousCondition setObject:[NSNumber numberWithFloat:desired_range] forKey:@"desired_range"];
|
|
[previousCondition setObject:[NSNumber numberWithFloat:desired_speed] forKey:@"desired_speed"];
|
|
[previousCondition setObject:[NSNumber numberWithFloat:destination.x] forKey:@"destination.x"];
|
|
[previousCondition setObject:[NSNumber numberWithFloat:destination.y] forKey:@"destination.y"];
|
|
[previousCondition setObject:[NSNumber numberWithFloat:destination.z] forKey:@"destination.z"];
|
|
|
|
destination = prox_ship->position;
|
|
destination.x += position.x; destination.y += position.y; destination.z += position.z;
|
|
destination.x *= 0.5; destination.y *= 0.5; destination.z *= 0.5; // point between us and them
|
|
|
|
desired_range = prox_ship->collision_radius * PROXIMITY_AVOID_DISTANCE;
|
|
|
|
condition = CONDITION_AVOID_COLLISION;
|
|
}
|
|
}
|
|
|
|
- (void) resumePostProximityAlert
|
|
{
|
|
if (!previousCondition)
|
|
return;
|
|
|
|
// NSLog(@"DEBUG ***** proximity alert for %@ %d over", name, universal_id, proximity_alert);
|
|
|
|
condition = [(NSNumber*)[previousCondition objectForKey:@"condition"] intValue];
|
|
primaryTarget = [(NSNumber*)[previousCondition objectForKey:@"primaryTarget"] intValue];
|
|
desired_range = [(NSNumber*)[previousCondition objectForKey:@"desired_range"] floatValue];
|
|
desired_speed = [(NSNumber*)[previousCondition objectForKey:@"desired_speed"] floatValue];
|
|
destination.x = [(NSNumber*)[previousCondition objectForKey:@"destination.x"] floatValue];
|
|
destination.y = [(NSNumber*)[previousCondition objectForKey:@"destination.y"] floatValue];
|
|
destination.z = [(NSNumber*)[previousCondition objectForKey:@"destination.z"] floatValue];
|
|
|
|
[previousCondition release];
|
|
previousCondition = nil;
|
|
frustration = 0.0;
|
|
|
|
proximity_alert = NO_TARGET;
|
|
|
|
//[shipAI message:@"RESTART_DOCKING"]; // if docking, start over, other AIs will ignore this message
|
|
}
|
|
|
|
- (double) message_time
|
|
{
|
|
return message_time;
|
|
}
|
|
|
|
- (void) setMessage_time:(double) value
|
|
{
|
|
message_time = value;
|
|
}
|
|
|
|
- (int) group_id
|
|
{
|
|
return group_id;
|
|
}
|
|
|
|
- (void) setGroup_id:(int) value
|
|
{
|
|
group_id = value;
|
|
}
|
|
|
|
- (int) n_escorts
|
|
{
|
|
return n_escorts;
|
|
}
|
|
|
|
- (void) setN_escorts:(int) value
|
|
{
|
|
n_escorts = value;
|
|
escortsAreSetUp = (n_escorts == 0);
|
|
}
|
|
|
|
- (ShipEntity*) proximity_alert
|
|
{
|
|
return (ShipEntity*)[universe entityForUniversalID:proximity_alert];
|
|
}
|
|
|
|
- (void) setProximity_alert:(ShipEntity*) other
|
|
{
|
|
if (!other)
|
|
{
|
|
proximity_alert = NO_TARGET;
|
|
return;
|
|
}
|
|
|
|
if (isStation||(other->isStation)) // don't be alarmed close to stations
|
|
return;
|
|
|
|
if ((scan_class == CLASS_CARGO)||(scan_class == CLASS_BUOY)||(scan_class == CLASS_MISSILE)||(scan_class == CLASS_ROCK)) // rocks and stuff don't get alarmed easily
|
|
return;
|
|
|
|
if (condition == CONDITION_AVOID_COLLISION) // already avoiding something
|
|
{
|
|
ShipEntity* prox = (ShipEntity*)[universe entityForUniversalID:proximity_alert];
|
|
if (other == prox)
|
|
{
|
|
// NSLog(@"DEBUG %@ already alerted to proximity of %@", self, other);
|
|
return;
|
|
}
|
|
if (prox)
|
|
{
|
|
GLfloat d_prox = distance2(position, prox->position) - prox->collision_radius * prox->collision_radius;
|
|
GLfloat d_other = distance2(position, other->position) - other->collision_radius * other->collision_radius;
|
|
if (d_prox < d_other)
|
|
{
|
|
// NSLog(@"DEBUG %@ is already avoiding %@", self, prox);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
proximity_alert = [other universal_id];
|
|
// NSLog(@"DEBUG PROXIMITY ALERT FOR %@ VS %@ == %d", self, other, proximity_alert);
|
|
}
|
|
|
|
- (NSString *) name
|
|
{
|
|
return name;
|
|
}
|
|
|
|
- (NSString *) identFromShip:(ShipEntity*) otherShip
|
|
{
|
|
if (has_military_jammer && military_jammer_active && (![otherShip hasMilitaryScannerFilter]))
|
|
return @"Unknown Target";
|
|
return name;
|
|
}
|
|
|
|
- (NSString *) roles
|
|
{
|
|
return roles;
|
|
}
|
|
|
|
- (void) setRoles:(NSString *) value
|
|
{
|
|
if (roles)
|
|
[roles release];
|
|
roles = [[NSString stringWithString:value] retain];
|
|
}
|
|
|
|
- (BOOL) hasHostileTarget
|
|
{
|
|
if (primaryTarget == NO_TARGET)
|
|
return NO;
|
|
if ((condition == CONDITION_AVOID_COLLISION)&&(previousCondition))
|
|
{
|
|
int old_condition = [(NSNumber*)[previousCondition objectForKey:@"condition"] intValue];
|
|
return IS_CONDITION_HOSTILE(old_condition);
|
|
}
|
|
return IS_CONDITION_HOSTILE(condition);
|
|
}
|
|
|
|
- (NSMutableArray *) launch_actions
|
|
{
|
|
return launch_actions;
|
|
}
|
|
|
|
- (NSMutableArray *) script_actions
|
|
{
|
|
return script_actions;
|
|
}
|
|
|
|
- (NSMutableArray *) death_actions
|
|
{
|
|
return death_actions;
|
|
}
|
|
|
|
- (double) weapon_range
|
|
{
|
|
return weapon_range;
|
|
}
|
|
|
|
- (void) setWeaponRange: (double) value
|
|
{
|
|
weapon_range = value;
|
|
}
|
|
|
|
- (void) set_weapon_data_from_type: (int) weapon_type
|
|
{
|
|
switch (weapon_type)
|
|
{
|
|
case WEAPON_PLASMA_CANNON :
|
|
weapon_energy = 6.0;
|
|
weapon_recharge_rate = 0.25;
|
|
weapon_range = 5000;
|
|
break;
|
|
case WEAPON_PULSE_LASER :
|
|
weapon_energy = 15.0;
|
|
weapon_recharge_rate = 0.33;
|
|
weapon_range = 12500;
|
|
break;
|
|
case WEAPON_BEAM_LASER :
|
|
weapon_energy = 15.0;
|
|
weapon_recharge_rate = 0.25;
|
|
weapon_range = 15000;
|
|
break;
|
|
case WEAPON_MINING_LASER :
|
|
weapon_energy = 50.0;
|
|
weapon_recharge_rate = 0.5;
|
|
weapon_range = 12500;
|
|
break;
|
|
case WEAPON_THARGOID_LASER : // omni directional lasers FRIGHTENING!
|
|
weapon_energy = 12.5;
|
|
weapon_recharge_rate = 0.5;
|
|
weapon_range = 17500;
|
|
break;
|
|
case WEAPON_MILITARY_LASER :
|
|
weapon_energy = 23.0;
|
|
weapon_recharge_rate = 0.20;
|
|
weapon_range = 30000;
|
|
break;
|
|
case WEAPON_NONE :
|
|
weapon_energy = 0.0; // indicating no weapon!
|
|
weapon_recharge_rate = 0.20; // maximum rate
|
|
weapon_range = 32000;
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (double) scanner_range
|
|
{
|
|
return scanner_range;
|
|
}
|
|
|
|
- (void) setScannerRange: (double) value
|
|
{
|
|
scanner_range = value;
|
|
}
|
|
|
|
- (Vector) reference
|
|
{
|
|
return reference;
|
|
}
|
|
|
|
- (void) setReference:(Vector) v
|
|
{
|
|
reference.x = v.x; reference.y = v.y; reference.z = v.z;
|
|
}
|
|
|
|
- (BOOL) reportAImessages
|
|
{
|
|
return reportAImessages;
|
|
}
|
|
|
|
- (void) setReportAImessages:(BOOL) yn
|
|
{
|
|
reportAImessages = yn;
|
|
}
|
|
|
|
- (int) checkForAegis
|
|
{
|
|
PlanetEntity* the_planet = [universe planet];
|
|
|
|
if (!the_planet)
|
|
{
|
|
if (aegis_status != AEGIS_NONE)
|
|
[shipAI message:@"AEGIS_NONE"];
|
|
return AEGIS_NONE;
|
|
}
|
|
|
|
// check planet
|
|
Vector p1 = the_planet->position;
|
|
double cr = the_planet->collision_radius;
|
|
int result = AEGIS_NONE;
|
|
p1.x -= position.x; p1.y -= position.y; p1.z -= position.z;
|
|
double d2 = p1.x*p1.x + p1.y*p1.y + p1.z*p1.z - cr * cr * 9.0; // 3x radius of planet
|
|
if (d2 < 0.0)
|
|
result = AEGIS_CLOSE_TO_PLANET;
|
|
// check station
|
|
StationEntity* the_station = [universe station];
|
|
if (!the_station)
|
|
{
|
|
if (aegis_status != AEGIS_NONE)
|
|
[shipAI message:@"AEGIS_NONE"];
|
|
return AEGIS_NONE;
|
|
}
|
|
p1 = the_station->position;
|
|
p1.x -= position.x; p1.y -= position.y; p1.z -= position.z;
|
|
d2 = p1.x*p1.x + p1.y*p1.y + p1.z*p1.z - SCANNER_MAX_RANGE2*4.0; // double scanner range
|
|
if (d2 < 0.0)
|
|
result = AEGIS_IN_DOCKING_RANGE;
|
|
within_station_aegis = (d2 < 0.0);
|
|
|
|
// ai messages on change in status
|
|
// approaching..
|
|
if ((aegis_status == AEGIS_NONE)&&(result == AEGIS_CLOSE_TO_PLANET))
|
|
[shipAI message:@"AEGIS_CLOSE_TO_PLANET"];
|
|
if (((aegis_status == AEGIS_CLOSE_TO_PLANET)||(aegis_status == AEGIS_NONE))&&(result == AEGIS_IN_DOCKING_RANGE))
|
|
[shipAI message:@"AEGIS_IN_DOCKING_RANGE"];
|
|
// leaving..
|
|
if ((aegis_status == AEGIS_IN_DOCKING_RANGE)&&(result == AEGIS_CLOSE_TO_PLANET))
|
|
[shipAI message:@"AEGIS_LEAVING_DOCKING_RANGE"];
|
|
if ((aegis_status != AEGIS_NONE)&&(result == AEGIS_NONE))
|
|
[shipAI message:@"AEGIS_NONE"];
|
|
|
|
aegis_status = result; // put this here
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BOOL) within_station_aegis
|
|
{
|
|
return within_station_aegis;
|
|
}
|
|
|
|
- (void) setStatus:(int) stat
|
|
{
|
|
status = stat;
|
|
if ((status == STATUS_LAUNCHING)&&(universe))
|
|
launch_time = [universe getTime];
|
|
}
|
|
|
|
- (void) setCrew: (NSArray*) crewArray
|
|
{
|
|
if (crew)
|
|
[crew autorelease];
|
|
if (crewArray)
|
|
crew = [[NSArray arrayWithArray:crewArray] retain];
|
|
else
|
|
crew = nil;
|
|
}
|
|
|
|
- (void) setStateMachine:(NSString *) ai_desc
|
|
{
|
|
[shipAI setStateMachine: ai_desc];
|
|
}
|
|
|
|
- (void) setAI:(AI *) ai
|
|
{
|
|
if (shipAI) [shipAI release];
|
|
shipAI = [ai retain];
|
|
}
|
|
|
|
- (AI *) getAI
|
|
{
|
|
return shipAI;
|
|
}
|
|
|
|
- (int) fuel
|
|
{
|
|
return fuel;
|
|
}
|
|
|
|
- (void) setFuel:(int) amount
|
|
{
|
|
fuel = amount;
|
|
if (fuel < 0)
|
|
fuel = 0;
|
|
if (fuel > 70)
|
|
fuel = 70;
|
|
}
|
|
|
|
- (void) setRoll:(double) amount
|
|
{
|
|
flight_roll = amount * PI / 2.0;
|
|
}
|
|
|
|
- (void) setPitch:(double) amount
|
|
{
|
|
flight_pitch = amount * PI / 2.0;
|
|
}
|
|
|
|
|
|
- (void) setThrust:(double) amount
|
|
{
|
|
thrust = amount;
|
|
}
|
|
|
|
|
|
- (void) setBounty:(int) amount
|
|
{
|
|
bounty = amount;
|
|
}
|
|
|
|
- (int) getBounty
|
|
{
|
|
return bounty;
|
|
}
|
|
|
|
- (int) legal_status
|
|
{
|
|
if (scan_class == CLASS_THARGOID)
|
|
return 5 * collision_radius;
|
|
if ([roles isEqual:@"asteroid"])
|
|
return 0;
|
|
if ([roles isEqual:@"boulder"])
|
|
return 0;
|
|
if ([roles isEqual:@"splinter"])
|
|
return 0;
|
|
return bounty;
|
|
}
|
|
|
|
- (void) setCommodity:(int) co_type andAmount:(int) co_amount;
|
|
{
|
|
commodity_type = co_type;
|
|
commodity_amount = co_amount;
|
|
}
|
|
- (int) getCommodityType
|
|
{
|
|
return commodity_type;
|
|
}
|
|
- (int) getCommodityAmount
|
|
{
|
|
return commodity_amount;
|
|
}
|
|
|
|
- (int) getMaxCargo
|
|
{
|
|
return max_cargo;
|
|
}
|
|
|
|
- (int) getCargoType
|
|
{
|
|
return cargo_type;
|
|
}
|
|
|
|
- (NSMutableArray*) cargo
|
|
{
|
|
return cargo;
|
|
}
|
|
|
|
- (void) setCargo:(NSArray *) some_cargo
|
|
{
|
|
[cargo removeAllObjects];
|
|
[cargo addObjectsFromArray:some_cargo];
|
|
}
|
|
|
|
- (int) cargoFlag
|
|
{
|
|
return cargo_flag;
|
|
}
|
|
|
|
- (void) setCargoFlag:(int) flag
|
|
{
|
|
cargo_flag = flag;
|
|
}
|
|
|
|
- (void) setSpeed:(double) amount
|
|
{
|
|
flight_speed = amount;
|
|
}
|
|
|
|
- (void) setDesiredSpeed:(double) amount
|
|
{
|
|
desired_speed = amount;
|
|
}
|
|
|
|
- (void) increase_flight_speed:(double) delta
|
|
{
|
|
double factor = ((desired_speed > max_flight_speed)&&(has_fuel_injection)&&(fuel > 0)) ? AFTERBURNER_FACTOR : 1.0;
|
|
|
|
if (flight_speed < max_flight_speed * factor)
|
|
flight_speed += delta * factor;
|
|
else
|
|
flight_speed = max_flight_speed * factor;
|
|
}
|
|
|
|
- (void) decrease_flight_speed:(double) delta
|
|
{
|
|
if (flight_speed > -max_flight_speed)
|
|
flight_speed -= delta;
|
|
else
|
|
flight_speed = -max_flight_speed;
|
|
}
|
|
|
|
|
|
- (void) increase_flight_roll:(double) delta
|
|
{
|
|
if (flight_roll < max_flight_roll)
|
|
flight_roll += delta;
|
|
if (flight_roll > max_flight_roll)
|
|
flight_roll = max_flight_roll;
|
|
}
|
|
|
|
- (void) decrease_flight_roll:(double) delta
|
|
{
|
|
if (flight_roll > -max_flight_roll)
|
|
flight_roll -= delta;
|
|
if (flight_roll < -max_flight_roll)
|
|
flight_roll = -max_flight_roll;
|
|
}
|
|
|
|
|
|
- (void) increase_flight_pitch:(double) delta
|
|
{
|
|
if (flight_pitch < max_flight_pitch)
|
|
flight_pitch += delta;
|
|
if (flight_pitch > max_flight_pitch)
|
|
flight_pitch = max_flight_pitch;
|
|
}
|
|
|
|
|
|
- (void) decrease_flight_pitch:(double) delta
|
|
{
|
|
if (flight_pitch > -max_flight_pitch)
|
|
flight_pitch -= delta;
|
|
if (flight_pitch < -max_flight_pitch)
|
|
flight_pitch = -max_flight_pitch;
|
|
}
|
|
|
|
- (double) flight_roll
|
|
{
|
|
return flight_roll;
|
|
}
|
|
|
|
- (double) flight_pitch
|
|
{
|
|
return flight_pitch;
|
|
}
|
|
|
|
- (double) flight_speed
|
|
{
|
|
return flight_speed;
|
|
}
|
|
|
|
- (double) max_flight_speed
|
|
{
|
|
return max_flight_speed;
|
|
}
|
|
|
|
- (double) speed_factor
|
|
{
|
|
if (max_flight_speed <= 0.0)
|
|
return 0.0;
|
|
return flight_speed / max_flight_speed;
|
|
}
|
|
|
|
- (int) damage
|
|
{
|
|
return (int)(100 - (100 * energy / max_energy));
|
|
}
|
|
|
|
|
|
- (void) dealEnergyDamageWithinDesiredRange
|
|
{
|
|
NSArray* targets = [universe getEntitiesWithinRange:desired_range ofEntity:self];
|
|
if ([targets count] > 0)
|
|
{
|
|
int i;
|
|
for (i = 0; i < [targets count]; i++)
|
|
{
|
|
Entity *e2 = [targets objectAtIndex:i];
|
|
Vector p2 = e2->position;
|
|
p2.x -= position.x; p2.y -= position.y; p2.z -= position.z;
|
|
double d2 = p2.x*p2.x + p2.y*p2.y + p2.z*p2.z;
|
|
double damage = weapon_energy*desired_range/d2;
|
|
[e2 takeEnergyDamage:damage from:self becauseOf:[self owner]];
|
|
//if ((e2)&&(e2->isShip))
|
|
// //NSLog(@"Doing %.1f damage to %@ %d",damage,[(ShipEntity *)e2 name],[(ShipEntity *)e2 universal_id]);
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) takeEnergyDamage:(double) amount from:(Entity *) ent becauseOf:(Entity *) other
|
|
{
|
|
if (status == STATUS_DEAD) // it's too late for this one!
|
|
return;
|
|
if (amount == 0.0)
|
|
return;
|
|
if ((ent)&&(ent->isParticle)&&(ent->scan_class == CLASS_MINE))
|
|
{
|
|
if (self == [universe station])
|
|
{
|
|
if ((other)&&(other->isShip))
|
|
{
|
|
[(ShipEntity*)other markAsOffender:96];
|
|
[self setPrimaryAggressor:other];
|
|
found_target = primaryAggressor;
|
|
}
|
|
[(StationEntity*)self increaseAlertLevel];
|
|
[shipAI reactToMessage:@"ATTACKED"]; // note use the reactToMessage: method NOT the think-delayed message: method
|
|
return; // Main stations are energy-bomb-proof!
|
|
}
|
|
|
|
// otherwise start a chain-reaction
|
|
//
|
|
if ((amount > energy)&&(energy > 10))
|
|
{
|
|
ParticleEntity* chain_reaction = [[ParticleEntity alloc] initEnergyMineFromShip:self];
|
|
[universe addEntity:chain_reaction];
|
|
[chain_reaction setOwner:[ent owner]];
|
|
[chain_reaction release];
|
|
// }
|
|
}
|
|
}
|
|
//
|
|
BOOL iAmTheLaw = (scan_class == CLASS_POLICE);
|
|
BOOL uAreTheLaw = ((other)&&(other->scan_class == CLASS_POLICE));
|
|
//
|
|
energy -= amount;
|
|
being_mined = NO;
|
|
//
|
|
// if the other entity is a ship note it as an aggressor
|
|
if ((other)&&(other->isShip))
|
|
{
|
|
ShipEntity* hunter = (ShipEntity *)other;
|
|
//
|
|
last_escort_target = NO_TARGET; // we're being attacked, escorts can scramble!
|
|
//
|
|
primaryAggressor = [hunter universal_id];
|
|
found_target = primaryAggressor;
|
|
|
|
// tell ourselves we've been attacked
|
|
if (energy > 0)
|
|
[shipAI reactToMessage:@"ATTACKED"]; // note use the reactToMessage: method NOT the think-delayed message: method
|
|
|
|
// tell our group we've been attacked
|
|
if (group_id != NO_TARGET)
|
|
{
|
|
if ([roles isEqual:@"escort"]||[roles isEqual:@"trader"])
|
|
{
|
|
ShipEntity *group_leader = (ShipEntity *)[universe entityForUniversalID:group_id];
|
|
if (group_leader)
|
|
{
|
|
//NSLog(@"DEBUG %@ %d informs group leader %@ %d of attack by %@ %d", name, universal_id, [group_leader name], [group_leader universal_id], [hunter name], [hunter universal_id]);
|
|
|
|
//[group_leader setReportAImessages:YES];
|
|
[group_leader setFound_target:hunter];
|
|
[group_leader setPrimaryAggressor:hunter];
|
|
[[group_leader getAI] reactToMessage:@"ATTACKED"];
|
|
}
|
|
}
|
|
if ([roles isEqual:@"pirate"])
|
|
{
|
|
NSArray *fellow_pirates = [self shipsInGroup:group_id];
|
|
int i;
|
|
for (i = 0; i < [fellow_pirates count]; i++)
|
|
{
|
|
ShipEntity *other_pirate = (ShipEntity *)[fellow_pirates objectAtIndex:i];
|
|
if (randf() < 0.5) // 50% chance they'll help
|
|
{
|
|
[other_pirate setFound_target:hunter];
|
|
[other_pirate setPrimaryAggressor:hunter];
|
|
[[other_pirate getAI] reactToMessage:@"ATTACKED"];
|
|
}
|
|
}
|
|
}
|
|
if (iAmTheLaw)
|
|
{
|
|
NSArray *fellow_police = [self shipsInGroup:group_id];
|
|
int i;
|
|
for (i = 0; i < [fellow_police count]; i++)
|
|
{
|
|
ShipEntity *other_police = (ShipEntity *)[fellow_police objectAtIndex:i];
|
|
[other_police setFound_target:hunter];
|
|
[other_police setPrimaryAggressor:hunter];
|
|
[[other_police getAI] reactToMessage:@"ATTACKED"];
|
|
}
|
|
}
|
|
}
|
|
|
|
// if I'm a copper and you're not, then mark the other as an offender!
|
|
if ((iAmTheLaw)&&(!uAreTheLaw))
|
|
[hunter markAsOffender:64];
|
|
|
|
// avoid shooting each other
|
|
if (([hunter group_id] == group_id)||(iAmTheLaw && uAreTheLaw))
|
|
{
|
|
if ([hunter condition] == CONDITION_ATTACK_FLY_TO_TARGET) // avoid me please!
|
|
{
|
|
[hunter setCondition:CONDITION_ATTACK_FLY_FROM_TARGET];
|
|
[hunter setDesiredSpeed:[hunter max_flight_speed]];
|
|
}
|
|
}
|
|
|
|
if ((other)&&(other->isShip))
|
|
being_mined = [(ShipEntity *)other isMining];
|
|
}
|
|
// die if I'm out of energy
|
|
if (energy <= 0.0)
|
|
{
|
|
if ((other)&&(other->isShip))
|
|
{
|
|
ShipEntity* hunter = (ShipEntity *)other;
|
|
[hunter collectBountyFor:self];
|
|
if ([hunter getPrimaryTarget] == (Entity *)self)
|
|
{
|
|
[hunter removeTarget:(Entity *)self];
|
|
[[hunter getAI] message:@"TARGET_DESTROYED"];
|
|
}
|
|
}
|
|
|
|
[self becomeExplosion];
|
|
}
|
|
else
|
|
{
|
|
// warn if I'm low on energy
|
|
if (energy < max_energy *0.25)
|
|
[shipAI reactToMessage:@"ENERGY_LOW"];
|
|
if ((energy < max_energy *0.125)&&(has_escape_pod)&&((ranrot_rand() & 3) == 0)) // 25% chance he gets to an escape pod
|
|
{
|
|
has_escape_pod = NO;
|
|
//NSLog(@"Escape Pod launched");
|
|
[shipAI setStateMachine:@"nullAI.plist"];
|
|
[shipAI setState:@"GLOBAL"];
|
|
condition = CONDITION_IDLE;
|
|
frustration = 0.0;
|
|
[self launchEscapeCapsule];
|
|
[self setScanClass: CLASS_NEUTRAL]; // we're unmanned now!
|
|
thrust = thrust * 0.5;
|
|
desired_speed = 0.0;
|
|
max_flight_speed = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) becomeExplosion
|
|
{
|
|
// check if we're destroying a subentity
|
|
ShipEntity* parent = (ShipEntity*)[self owner];
|
|
if ((parent)&&(parent != self)&&(parent->isShip)&&[parent->sub_entities containsObject:self])
|
|
{
|
|
ShipEntity* this_ship = [self retain];
|
|
Vector this_pos = [self absolutePositionForSubentity];
|
|
// remove this ship from its parent's subentity list
|
|
NSMutableArray *temp = [NSMutableArray arrayWithArray:parent->sub_entities];
|
|
[temp removeObject:this_ship];
|
|
[parent->sub_entities autorelease];
|
|
parent->sub_entities = [[NSArray arrayWithArray:temp] retain];
|
|
[universe addEntity:this_ship];
|
|
this_ship->position = this_pos;
|
|
[this_ship release];
|
|
}
|
|
|
|
Vector xposition = position;
|
|
ParticleEntity *fragment;
|
|
int i;
|
|
Vector v;
|
|
Quaternion q;
|
|
int speed_low = 200;
|
|
int n_alloys = floor((boundingBox.max_z - boundingBox.min_z) / 50.0);
|
|
|
|
if (status == STATUS_DEAD)
|
|
{
|
|
[universe removeEntity:self];
|
|
return;
|
|
}
|
|
status = STATUS_DEAD;
|
|
//scripting
|
|
if ([death_actions count])
|
|
{
|
|
int i;
|
|
PlayerEntity* player = (PlayerEntity *)[universe entityZero];
|
|
[player setScript_target:self];
|
|
for (i = 0; i < [death_actions count]; i++)
|
|
{
|
|
NSObject* action = [death_actions objectAtIndex:i];
|
|
if ([action isKindOfClass:[NSDictionary class]])
|
|
[player checkCouplet:(NSDictionary *)action onEntity:self];
|
|
if ([action isKindOfClass:[NSString class]])
|
|
[player scriptAction:(NSString *)action onEntity:self];
|
|
}
|
|
[death_actions removeAllObjects];
|
|
}
|
|
|
|
|
|
if ([roles isEqual:@"thargoid"])
|
|
[self broadcastThargoidDestroyed];
|
|
|
|
if (collision_radius > 49.9) // big!
|
|
{
|
|
// quick test of hyperring
|
|
ParticleEntity *ring = [[ParticleEntity alloc] initHyperringFromShip:self]; // retained
|
|
Vector ring_vel = [ring getVelocity];
|
|
ring_vel.x *= 0.25; ring_vel.y *= 0.25; ring_vel.z *= 0.25; // quarter velocity
|
|
[ring setVelocity:ring_vel];
|
|
[universe addEntity:ring];
|
|
[ring release];
|
|
}
|
|
|
|
// several parts to the explosion:
|
|
// 1. fast sparks
|
|
fragment = [[ParticleEntity alloc] initFragburstSize: collision_radius FromPosition: xposition];
|
|
[universe addEntity:fragment];
|
|
[fragment release];
|
|
// 2. slow clouds
|
|
fragment = [[ParticleEntity alloc] initBurst2Size: collision_radius FromPosition: xposition];
|
|
[universe addEntity:fragment];
|
|
[fragment release];
|
|
// 3. flash
|
|
fragment = [[ParticleEntity alloc] initFlashSize: collision_radius FromPosition: xposition];
|
|
[universe addEntity:fragment];
|
|
[fragment release];
|
|
|
|
// we need to throw out cargo at this point.
|
|
NSArray *jetsam = nil; // this will contain the stuff to get thrown out
|
|
int cargo_chance = 10;
|
|
if ([[name lowercaseString] rangeOfString:@"medical"].location != NSNotFound)
|
|
{
|
|
int cargo_to_go = max_cargo * cargo_chance / 100;
|
|
while (cargo_to_go > 15)
|
|
cargo_to_go = ranrot_rand() % cargo_to_go;
|
|
[self setCargo:[universe getContainersOfDrugs:cargo_to_go]];
|
|
cargo_chance = 100; // chance of any given piece of cargo surviving decompression
|
|
cargo_flag = CARGO_FLAG_CANISTERS;
|
|
}
|
|
|
|
int cargo_to_go = max_cargo * cargo_chance / 100;
|
|
while (cargo_to_go > 15)
|
|
cargo_to_go = ranrot_rand() % cargo_to_go;
|
|
cargo_chance = 100; // chance of any given piece of cargo surviving decompression
|
|
switch (cargo_flag)
|
|
{
|
|
case CARGO_FLAG_FULL_UNIFORM :
|
|
// NSLog(@"DEBUG dropping uniform cargo (CARGO_FLAG_FULL_UNIFORM)");
|
|
{
|
|
NSString* commodity_name = (NSString*)[shipinfoDictionary objectForKey:@"cargo_carried"];
|
|
jetsam = [universe getContainersOfCommodity:commodity_name :cargo_to_go];
|
|
}
|
|
break;
|
|
|
|
case CARGO_FLAG_FULL_PLENTIFUL :
|
|
// NSLog(@"DEBUG dropping plentiful cargo (CARGO_FLAG_FULL_PLENTIFUL)");
|
|
jetsam = [universe getContainersOfPlentifulGoods:cargo_to_go];
|
|
break;
|
|
|
|
case CARGO_FLAG_PIRATE :
|
|
// NSLog(@"DEBUG dropping pirated cargo (CARGO_FLAG_PIRATE)");
|
|
cargo_to_go = likely_cargo;
|
|
while (cargo_to_go > 15)
|
|
cargo_to_go = ranrot_rand() % cargo_to_go;
|
|
cargo_chance = 65; // 35% chance of spoilage
|
|
jetsam = [universe getContainersOfScarceGoods:cargo_to_go];
|
|
break;
|
|
|
|
case CARGO_FLAG_FULL_SCARCE :
|
|
// NSLog(@"DEBUG dropping scarce cargo (CARGO_FLAG_FULL_SCARCE)");
|
|
jetsam = [universe getContainersOfScarceGoods:cargo_to_go];
|
|
break;
|
|
|
|
case CARGO_FLAG_CANISTERS:
|
|
// NSLog(@"DEBUG dropping ship's scooped cargo (CARGO_FLAG_CANISTERS)");
|
|
jetsam = [NSArray arrayWithArray:cargo]; // what the ship is carrying
|
|
[cargo removeAllObjects]; // dispense with it!
|
|
break;
|
|
}
|
|
|
|
if (jetsam)
|
|
{
|
|
for (i = 0; i < [jetsam count]; i++)
|
|
{
|
|
if (ranrot_rand() % 100 < cargo_chance) // chance of any given piece of cargo surviving decompression
|
|
{
|
|
ShipEntity* container = [jetsam objectAtIndex:i];
|
|
Vector rpos = xposition;
|
|
Vector rrand = randomPositionInBoundingBox(boundingBox);
|
|
rpos.x += rrand.x; rpos.y += rrand.y; rpos.z += rrand.z;
|
|
rpos.x += (ranrot_rand() % 7) - 3;
|
|
rpos.y += (ranrot_rand() % 7) - 3;
|
|
rpos.z += (ranrot_rand() % 7) - 3;
|
|
[container setPosition:rpos];
|
|
v.x = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
|
|
v.y = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
|
|
v.z = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
|
|
[container setVelocity:v];
|
|
quaternion_set_random(&q);
|
|
[container setQRotation:q];
|
|
[container setStatus:STATUS_IN_FLIGHT];
|
|
[container setScanClass: CLASS_CARGO];
|
|
[universe addEntity:container];
|
|
[[container getAI] setState:@"GLOBAL"];
|
|
//NSLog(@"Launched %@ %d with %@",[container name], [container universal_id], [universe describeCommodity:[container getCommodityType] amount:[container getCommodityAmount]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Throw out rocks and alloys to be scooped up
|
|
//
|
|
if ([roles isEqual:@"asteroid"])
|
|
{
|
|
if ((being_mined)||(randf() < 0.20))
|
|
{
|
|
// if hit by a mining laser, break up into 2..6 boulders
|
|
// int n_rocks = 2 + (ranrot_rand() % 5);
|
|
int n_rocks = likely_cargo;
|
|
// NSLog(@"DEBUG %@ %d Throwing %d boulders", name, universal_id, n_rocks);
|
|
// NSLog(@"DEBUG At (%.1f, %.1f, %.1f)", xposition.x, xposition.y, xposition.z);
|
|
for (i = 0; i < n_rocks; i++)
|
|
{
|
|
ShipEntity* rock = [universe getShipWithRole:@"boulder"]; // retain count = 1
|
|
if (rock)
|
|
{
|
|
Vector rpos = xposition;
|
|
int r_speed = 20.0 * [rock max_flight_speed];
|
|
int cr = 3 * rock->collision_radius;
|
|
rpos.x += (ranrot_rand() % cr) - cr/2;
|
|
rpos.y += (ranrot_rand() % cr) - cr/2;
|
|
rpos.z += (ranrot_rand() % cr) - cr/2;
|
|
[rock setPosition:rpos];
|
|
// NSLog(@"DEBUG Spawned Boulder At (%.1f, %.1f, %.1f)", rpos.x, rpos.y, rpos.z);
|
|
v.x = 0.1 *((ranrot_rand() % r_speed) - r_speed / 2);
|
|
v.y = 0.1 *((ranrot_rand() % r_speed) - r_speed / 2);
|
|
v.z = 0.1 *((ranrot_rand() % r_speed) - r_speed / 2);
|
|
[rock setVelocity:v];
|
|
quaternion_set_random(&q);
|
|
[rock setQRotation:q];
|
|
[rock setStatus:STATUS_IN_FLIGHT];
|
|
[rock setScanClass: CLASS_ROCK];
|
|
[universe addEntity:rock];
|
|
[[rock getAI] setState:@"GLOBAL"];
|
|
[rock release];
|
|
}
|
|
}
|
|
}
|
|
[universe removeEntity:self];
|
|
return; // don't do anything more
|
|
}
|
|
|
|
if ([roles isEqual:@"boulder"])
|
|
{
|
|
if ((being_mined)||(ranrot_rand() % 100 < 20))
|
|
{
|
|
// if hit by a mining laser, break up into 2..6 splinters
|
|
int n_rocks = 2 + (ranrot_rand() % 5);
|
|
//NSLog(@"Throwing %d splinters", n_rocks);
|
|
for (i = 0; i < n_rocks; i++)
|
|
{
|
|
ShipEntity* rock = [universe getShipWithRole:@"splinter"]; // retain count = 1
|
|
if (rock)
|
|
{
|
|
Vector rpos = xposition;
|
|
int r_speed = 20.0 * [rock max_flight_speed];
|
|
int cr = 3 * rock->collision_radius;
|
|
rpos.x += (ranrot_rand() % cr) - cr/2;
|
|
rpos.y += (ranrot_rand() % cr) - cr/2;
|
|
rpos.z += (ranrot_rand() % cr) - cr/2;
|
|
[rock setPosition:rpos];
|
|
v.x = 0.1 *((ranrot_rand() % r_speed) - r_speed / 2);
|
|
v.y = 0.1 *((ranrot_rand() % r_speed) - r_speed / 2);
|
|
v.z = 0.1 *((ranrot_rand() % r_speed) - r_speed / 2);
|
|
[rock setVelocity:v];
|
|
quaternion_set_random(&q);
|
|
[rock setQRotation:q];
|
|
[rock setStatus:STATUS_IN_FLIGHT];
|
|
[rock setScanClass: CLASS_CARGO];
|
|
[universe addEntity:rock];
|
|
[[rock getAI] setState:@"GLOBAL"];
|
|
[rock release];
|
|
}
|
|
}
|
|
}
|
|
[universe removeEntity:self];
|
|
return; // don't do anything more
|
|
}
|
|
|
|
//NSLog(@"Throwing %d pieces of alloy", n_alloys);
|
|
for (i = 0; i < n_alloys; i++)
|
|
{
|
|
ShipEntity* plate = [universe getShipWithRole:@"alloy"]; // retain count = 1
|
|
if (plate)
|
|
{
|
|
Vector rpos = xposition;
|
|
Vector rrand = randomPositionInBoundingBox(boundingBox);
|
|
rpos.x += rrand.x; rpos.y += rrand.y; rpos.z += rrand.z;
|
|
rpos.x += (ranrot_rand() % 7) - 3;
|
|
rpos.y += (ranrot_rand() % 7) - 3;
|
|
rpos.z += (ranrot_rand() % 7) - 3;
|
|
[plate setPosition:rpos];
|
|
v.x = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
|
|
v.y = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
|
|
v.z = 0.1 *((ranrot_rand() % speed_low) - speed_low / 2);
|
|
[plate setVelocity:v];
|
|
quaternion_set_random(&q);
|
|
[plate setQRotation:q];
|
|
[plate setStatus:STATUS_TEST];
|
|
[plate setScanClass: CLASS_CARGO];
|
|
[plate setCommodity:9 andAmount:1];
|
|
[universe addEntity:plate];
|
|
[[plate getAI] setState:@"GLOBAL"];
|
|
[plate release];
|
|
}
|
|
}
|
|
//
|
|
if (sub_entities)
|
|
{
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
Entity* se = (Entity *)[sub_entities objectAtIndex:i];
|
|
if (se->isShip)
|
|
{
|
|
Vector origin = [(ShipEntity*)se absolutePositionForSubentity];
|
|
[se setPosition:origin]; // is this what's messing thing up??
|
|
[universe addEntity:se];
|
|
[(ShipEntity *)se becomeExplosion];
|
|
}
|
|
}
|
|
[sub_entities release]; // releases each subentity too!
|
|
sub_entities = nil;
|
|
}
|
|
|
|
//
|
|
if (!isPlayer)
|
|
[universe removeEntity:self];
|
|
}
|
|
|
|
- (void) becomeEnergyBlast
|
|
{
|
|
ParticleEntity* blast = [[ParticleEntity alloc] initEnergyMineFromShip:self];
|
|
[universe addEntity:blast];
|
|
[blast setOwner: [self owner]];
|
|
[blast release];
|
|
[universe removeEntity:self];
|
|
}
|
|
|
|
Vector randomPositionInBoundingBox(BoundingBox bb)
|
|
{
|
|
Vector result;
|
|
result.x = bb.min_x + randf() * (bb.max_x - bb.min_x);
|
|
result.y = bb.min_y + randf() * (bb.max_y - bb.min_y);
|
|
result.z = bb.min_z + randf() * (bb.max_z - bb.min_z);
|
|
return result;
|
|
}
|
|
|
|
- (void) becomeLargeExplosion:(double) factor
|
|
{
|
|
Vector xposition = position;
|
|
ParticleEntity *fragment;
|
|
int n_cargo = (ranrot_rand() % (likely_cargo + 1));
|
|
|
|
if (status == STATUS_DEAD)
|
|
return;
|
|
|
|
status = STATUS_DEAD;
|
|
//scripting
|
|
if ([death_actions count])
|
|
{
|
|
int i;
|
|
PlayerEntity* player = (PlayerEntity *)[universe entityZero];
|
|
[player setScript_target:self];
|
|
for (i = 0; i < [death_actions count]; i++)
|
|
{
|
|
NSObject* action = [death_actions objectAtIndex:i];
|
|
if ([action isKindOfClass:[NSDictionary class]])
|
|
[player checkCouplet:(NSDictionary *)action onEntity:self];
|
|
if ([action isKindOfClass:[NSString class]])
|
|
[player scriptAction:(NSString *)action onEntity:self];
|
|
}
|
|
[death_actions removeAllObjects];
|
|
}
|
|
|
|
// two parts to the explosion:
|
|
// 1. fast sparks
|
|
float how_many = factor;
|
|
while (how_many > 0.5f)
|
|
{
|
|
// fragment = [[ParticleEntity alloc] initFragburstFromPosition:xposition];
|
|
fragment = [[ParticleEntity alloc] initFragburstSize: collision_radius FromPosition:xposition];
|
|
[universe addEntity:fragment];
|
|
[fragment release];
|
|
how_many -= 1.0f;
|
|
}
|
|
// 2. slow clouds
|
|
how_many = factor;
|
|
while (how_many > 0.5f)
|
|
{
|
|
fragment = [[ParticleEntity alloc] initBurst2Size: collision_radius FromPosition:xposition];
|
|
[universe addEntity:fragment];
|
|
[fragment release];
|
|
how_many -= 1.0f;
|
|
}
|
|
|
|
|
|
// we need to throw out cargo at this point.
|
|
int cargo_chance = 10;
|
|
if ([[name lowercaseString] rangeOfString:@"medical"].location != NSNotFound)
|
|
{
|
|
int cargo_to_go = max_cargo * cargo_chance / 100;
|
|
while (cargo_to_go > 15)
|
|
cargo_to_go = ranrot_rand() % cargo_to_go;
|
|
[self setCargo:[universe getContainersOfDrugs:cargo_to_go]];
|
|
cargo_chance = 100; // chance of any given piece of cargo surviving decompression
|
|
cargo_flag = CARGO_FLAG_CANISTERS;
|
|
}
|
|
if (cargo_flag == CARGO_FLAG_FULL_PLENTIFUL)
|
|
{
|
|
int cargo_to_go = max_cargo / 10;
|
|
while (cargo_to_go > 15)
|
|
cargo_to_go = ranrot_rand() % cargo_to_go;
|
|
//NSLog(@"explosion in %@ %d will launch %d pieces of cargo (max_cargo = %d)", name, universal_id, cargo_to_go, max_cargo);
|
|
[self setCargo:[universe getContainersOfPlentifulGoods:cargo_to_go]];
|
|
cargo_chance = 100;
|
|
}
|
|
if (cargo_flag == CARGO_FLAG_FULL_SCARCE)
|
|
{
|
|
int cargo_to_go = max_cargo / 10;
|
|
while (cargo_to_go > 15)
|
|
cargo_to_go = ranrot_rand() % cargo_to_go;
|
|
//NSLog(@"explosion in %@ %d will launch %d pieces of cargo (max_cargo = %d)", name, universal_id, cargo_to_go, max_cargo);
|
|
[self setCargo:[universe getContainersOfScarceGoods:cargo_to_go]];
|
|
cargo_chance = 100;
|
|
}
|
|
while ([cargo count] > 0)
|
|
{
|
|
if (ranrot_rand() % 100 < cargo_chance) // 10% chance of any given piece of cargo surviving decompression
|
|
{
|
|
ShipEntity* container = [[cargo objectAtIndex:0] retain];
|
|
Vector rpos = xposition;
|
|
Vector rrand = randomPositionInBoundingBox(boundingBox);
|
|
rpos.x += rrand.x; rpos.y += rrand.y; rpos.z += rrand.z;
|
|
rpos.x += (ranrot_rand() % 7) - 3;
|
|
rpos.y += (ranrot_rand() % 7) - 3;
|
|
rpos.z += (ranrot_rand() % 7) - 3;
|
|
[container setStatus:STATUS_TEST];
|
|
[container setPosition:rpos];
|
|
[container setScanClass: CLASS_CARGO];
|
|
[universe addEntity:container];
|
|
[[container getAI] setState:@"GLOBAL"];
|
|
[container release];
|
|
if (n_cargo > 0)
|
|
n_cargo--; // count down extra cargo
|
|
}
|
|
[cargo removeObjectAtIndex:0];
|
|
}
|
|
//
|
|
|
|
if (!isPlayer)
|
|
[universe removeEntity:self];
|
|
}
|
|
|
|
- (void) collectBountyFor:(ShipEntity *)other
|
|
{
|
|
if ([roles isEqual:@"pirate"])
|
|
bounty += [other getBounty];
|
|
}
|
|
|
|
/*-----------------------------------------
|
|
|
|
AI piloting methods
|
|
|
|
-----------------------------------------*/
|
|
|
|
- (void) setFound_target:(Entity *) targetEntity
|
|
{
|
|
if (targetEntity)
|
|
found_target = [targetEntity universal_id];
|
|
}
|
|
|
|
- (void) setPrimaryAggressor:(Entity *) targetEntity
|
|
{
|
|
if (targetEntity)
|
|
primaryAggressor = [targetEntity universal_id];
|
|
}
|
|
|
|
- (void) addTarget:(Entity *) targetEntity
|
|
{
|
|
//if ((targetentity)&&(targetEntity->isShip))
|
|
// NSLog(@"DEBUG %@ now targetting %@", [self name], [(ShipEntity *)targetEntity name]);
|
|
if (targetEntity)
|
|
primaryTarget = [targetEntity universal_id];
|
|
if (sub_entities)
|
|
{
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
Entity* se = [sub_entities objectAtIndex:i];
|
|
if (se->isShip)
|
|
[(ShipEntity *)se addTarget:targetEntity];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) removeTarget:(Entity *) targetEntity
|
|
{
|
|
if (primaryTarget != NO_TARGET)
|
|
[shipAI reactToMessage:@"TARGET_LOST"];
|
|
primaryTarget = NO_TARGET;
|
|
if (sub_entities)
|
|
{
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
Entity* se = [sub_entities objectAtIndex:i];
|
|
if (se->isShip)
|
|
[(ShipEntity *)se removeTarget:targetEntity];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (Entity *) getPrimaryTarget
|
|
{
|
|
return [universe entityForUniversalID:primaryTarget];
|
|
}
|
|
|
|
- (int) getPrimaryTargetID
|
|
{
|
|
return primaryTarget;
|
|
}
|
|
|
|
- (int) condition
|
|
{
|
|
return condition;
|
|
}
|
|
|
|
- (void) setCondition:(int) cond
|
|
{
|
|
if (cond !=condition)
|
|
frustration = 0.0; // change is a GOOD thing
|
|
condition = cond;
|
|
}
|
|
|
|
- (Vector) destination
|
|
{
|
|
return destination;
|
|
}
|
|
|
|
- (Vector) one_km_six
|
|
{
|
|
Vector six = position;
|
|
six.x -= 1000 * v_forward.x; six.y -= 1000 * v_forward.y; six.z -= 1000 * v_forward.z;
|
|
return six;
|
|
}
|
|
|
|
- (Vector) distance_six: (GLfloat) dist
|
|
{
|
|
Vector six = position;
|
|
six.x -= dist * v_forward.x; six.y -= dist * v_forward.y; six.z -= dist * v_forward.z;
|
|
return six;
|
|
}
|
|
|
|
- (Vector) distance_twelve: (GLfloat) dist
|
|
{
|
|
Vector twelve = position;
|
|
twelve.x += dist * v_up.x; twelve.y += dist * v_up.y; twelve.z += dist * v_up.z;
|
|
return twelve;
|
|
}
|
|
|
|
- (double) ballTrackTarget:(double) delta_t
|
|
{
|
|
Vector vector_to_target;
|
|
Vector axis_to_track_by;
|
|
Vector my_position = position; // position relative to parent
|
|
Vector my_aim = vector_forward_from_quaternion(q_rotation);
|
|
Vector my_ref = reference;
|
|
double aim_cos, ref_cos;
|
|
//
|
|
Entity* targent = [self getPrimaryTarget];
|
|
//
|
|
//
|
|
//NSLog(@"DEBUG ball_tracking (before rotation) my_aim (%.2f,%.2f,%.2f) my_ref (%.2f,%.2f,%.2f)", my_aim.x, my_aim.y, my_aim.z, my_ref.x, my_ref.y, my_ref.z);
|
|
Entity* father = [self owner];
|
|
GLfloat* r_mat = [father rotationMatrix];
|
|
while (father)
|
|
{
|
|
mult_vector_gl_matrix(&my_position, r_mat);
|
|
mult_vector_gl_matrix(&my_ref, r_mat);
|
|
Vector pos = father->position;
|
|
my_position.x += pos.x; my_position.y += pos.y; my_position.z += pos.z;
|
|
|
|
father = [father owner];
|
|
r_mat = [father rotationMatrix];
|
|
}
|
|
|
|
if (targent)
|
|
{
|
|
vector_to_target = targent->position;
|
|
//
|
|
vector_to_target.x -= my_position.x; vector_to_target.y -= my_position.y; vector_to_target.z -= my_position.z;
|
|
if (vector_to_target.x||vector_to_target.y||vector_to_target.z)
|
|
vector_to_target = unit_vector(&vector_to_target);
|
|
else
|
|
vector_to_target.z = 1.0;
|
|
//
|
|
// do the tracking!
|
|
aim_cos = dot_product(vector_to_target, my_aim);
|
|
ref_cos = dot_product(vector_to_target, my_ref);
|
|
}
|
|
else
|
|
{
|
|
aim_cos = 0.0;
|
|
ref_cos = -1.0;
|
|
}
|
|
//
|
|
//NSLog(@"DEBUG ball_tracking vtt (%.2f,%.2f,%.2f)", vector_to_target.x, vector_to_target.y, vector_to_target.z);
|
|
//NSLog(@"DEBUG ball_tracking target %@ aim_cos = %.3f ref_cos = %.3f", [(ShipEntity *)targent name], aim_cos, ref_cos);
|
|
|
|
if (ref_cos > TURRET_MINIMUM_COS) // target is forward of self
|
|
{
|
|
axis_to_track_by = cross_product(vector_to_target, my_aim);
|
|
}
|
|
else
|
|
{
|
|
aim_cos = 0.0;
|
|
axis_to_track_by = cross_product(my_ref, my_aim); // return to center
|
|
}
|
|
|
|
quaternion_rotate_about_axis( &q_rotation, axis_to_track_by, thrust * delta_t);
|
|
|
|
quaternion_normalise(&q_rotation);
|
|
quaternion_into_gl_matrix(q_rotation, rotMatrix);
|
|
|
|
status = STATUS_ACTIVE;
|
|
|
|
return aim_cos;
|
|
}
|
|
|
|
- (void) trackOntoTarget:(double) delta_t withDForward: (GLfloat) dp
|
|
{
|
|
Vector vector_to_target;
|
|
Quaternion q_minarc;
|
|
//
|
|
Entity* targent = [self getPrimaryTarget];
|
|
//
|
|
if (!targent)
|
|
return;
|
|
|
|
vector_to_target = targent->position;
|
|
vector_to_target.x -= position.x; vector_to_target.y -= position.y; vector_to_target.z -= position.z;
|
|
//
|
|
GLfloat range2 = magnitude2( vector_to_target);
|
|
GLfloat targetRadius = 0.75 * targent->actual_radius;
|
|
GLfloat max_cos = sqrt(1 - targetRadius*targetRadius/range2);
|
|
//
|
|
if (dp > max_cos)
|
|
return; // ON TARGET!
|
|
//
|
|
if (vector_to_target.x||vector_to_target.y||vector_to_target.z)
|
|
vector_to_target = unit_vector(&vector_to_target);
|
|
else
|
|
vector_to_target.z = 1.0;
|
|
//
|
|
q_minarc = quaternion_rotation_between( v_forward, vector_to_target);
|
|
//
|
|
q_rotation = quaternion_multiply( q_minarc, q_rotation);
|
|
quaternion_normalise(&q_rotation);
|
|
quaternion_into_gl_matrix(q_rotation, rotMatrix);
|
|
//
|
|
flight_roll = 0.0;
|
|
flight_pitch = 0.0;
|
|
}
|
|
|
|
- (double) ballTrackLeadingTarget:(double) delta_t
|
|
{
|
|
Vector vector_to_target;
|
|
Vector axis_to_track_by;
|
|
Vector my_position = position; // position relative to parent
|
|
Vector my_aim = vector_forward_from_quaternion(q_rotation);
|
|
Vector my_ref = reference;
|
|
double aim_cos, ref_cos;
|
|
//
|
|
Entity* targent = [self getPrimaryTarget];
|
|
//
|
|
Vector leading = [targent getVelocity];
|
|
// leading.x *= lead_t; leading.y *= lead_t; leading.z *= lead_t;
|
|
//
|
|
//
|
|
//NSLog(@"DEBUG ball_tracking (before rotation) my_aim (%.2f,%.2f,%.2f) my_ref (%.2f,%.2f,%.2f)", my_aim.x, my_aim.y, my_aim.z, my_ref.x, my_ref.y, my_ref.z);
|
|
Entity* father = [self owner];
|
|
GLfloat* r_mat = [father rotationMatrix];
|
|
while (father)
|
|
{
|
|
mult_vector_gl_matrix(&my_position, r_mat);
|
|
mult_vector_gl_matrix(&my_ref, r_mat);
|
|
Vector pos = father->position;
|
|
my_position.x += pos.x; my_position.y += pos.y; my_position.z += pos.z;
|
|
|
|
father = [father owner];
|
|
r_mat = [father rotationMatrix];
|
|
}
|
|
|
|
if (targent)
|
|
{
|
|
vector_to_target = targent->position;
|
|
//
|
|
vector_to_target.x -= my_position.x; vector_to_target.y -= my_position.y; vector_to_target.z -= my_position.z;
|
|
//
|
|
float lead = sqrt(magnitude2(vector_to_target)) / TURRET_SHOT_SPEED;
|
|
//
|
|
vector_to_target.x += lead * leading.x; vector_to_target.y += lead * leading.y; vector_to_target.z += lead * leading.z;
|
|
if (vector_to_target.x||vector_to_target.y||vector_to_target.z)
|
|
vector_to_target = unit_vector(&vector_to_target);
|
|
else
|
|
vector_to_target.z = 1.0;
|
|
//
|
|
// do the tracking!
|
|
aim_cos = dot_product(vector_to_target, my_aim);
|
|
ref_cos = dot_product(vector_to_target, my_ref);
|
|
}
|
|
else
|
|
{
|
|
aim_cos = 0.0;
|
|
ref_cos = -1.0;
|
|
}
|
|
//
|
|
//NSLog(@"DEBUG ball_tracking vtt (%.2f,%.2f,%.2f)", vector_to_target.x, vector_to_target.y, vector_to_target.z);
|
|
//NSLog(@"DEBUG ball_tracking target %@ aim_cos = %.3f ref_cos = %.3f", [(ShipEntity *)targent name], aim_cos, ref_cos);
|
|
|
|
if (ref_cos > TURRET_MINIMUM_COS) // target is forward of self
|
|
{
|
|
axis_to_track_by = cross_product(vector_to_target, my_aim);
|
|
}
|
|
else
|
|
{
|
|
aim_cos = 0.0;
|
|
axis_to_track_by = cross_product(my_ref, my_aim); // return to center
|
|
}
|
|
|
|
quaternion_rotate_about_axis( &q_rotation, axis_to_track_by, thrust * delta_t);
|
|
|
|
quaternion_normalise(&q_rotation);
|
|
quaternion_into_gl_matrix(q_rotation, rotMatrix);
|
|
|
|
status = STATUS_ACTIVE;
|
|
|
|
return aim_cos;
|
|
}
|
|
|
|
|
|
- (double) trackPrimaryTarget:(double) delta_t :(BOOL) retreat
|
|
{
|
|
Entity* target = [self getPrimaryTarget];
|
|
|
|
if (!target) // leave now!
|
|
{
|
|
[shipAI message:@"TARGET_LOST"];
|
|
return 0.0;
|
|
}
|
|
|
|
if (scan_class == CLASS_MISSILE)
|
|
return [self missileTrackPrimaryTarget: delta_t];
|
|
|
|
GLfloat d_forward, d_up, d_right;
|
|
|
|
Vector relativePosition = target->position;
|
|
relativePosition.x -= position.x;
|
|
relativePosition.y -= position.y;
|
|
relativePosition.z -= position.z;
|
|
|
|
double range2 = magnitude2(relativePosition);
|
|
|
|
if (range2 > SCANNER_MAX_RANGE2)
|
|
{
|
|
[shipAI message:@"TARGET_LOST"];
|
|
return 0.0;
|
|
}
|
|
|
|
//jink if retreating
|
|
if (retreat && (range2 > 250000.0)) // don't jink if closer than 500m - just RUN
|
|
{
|
|
Vector vx, vy, vz;
|
|
if (target->isShip)
|
|
{
|
|
ShipEntity* targetShip = (ShipEntity*)target;
|
|
vx = targetShip->v_right;
|
|
vy = targetShip->v_up;
|
|
vz = targetShip->v_forward;
|
|
}
|
|
else
|
|
{
|
|
Quaternion q = target->q_rotation;
|
|
vx = vector_right_from_quaternion(q);
|
|
vy = vector_up_from_quaternion(q);
|
|
vz = vector_forward_from_quaternion(q);
|
|
}
|
|
relativePosition.x += jink.x * vx.x + jink.y * vy.x + jink.z * vz.x;
|
|
relativePosition.y += jink.x * vx.y + jink.y * vy.y + jink.z * vz.y;
|
|
relativePosition.z += jink.x * vx.z + jink.y * vy.z + jink.z * vz.z;
|
|
}
|
|
|
|
if (relativePosition.x||relativePosition.y||relativePosition.z)
|
|
relativePosition = unit_vector(&relativePosition);
|
|
else
|
|
relativePosition.z = 1.0;
|
|
|
|
double targetRadius = 0.75 * target->actual_radius;
|
|
|
|
double max_cos = sqrt(1 - targetRadius*targetRadius/range2);
|
|
|
|
double damping = 0.5 * delta_t;
|
|
double rate2 = 4.0 * delta_t;
|
|
double rate1 = 2.0 * delta_t;
|
|
|
|
double stick_roll = 0.0; //desired roll and pitch
|
|
double stick_pitch = 0.0;
|
|
|
|
double reverse = (retreat)? -1.0: 1.0;
|
|
|
|
double min_d = 0.004;
|
|
|
|
d_right = dot_product(relativePosition, v_right);
|
|
d_up = dot_product(relativePosition, v_up);
|
|
d_forward = dot_product(relativePosition, v_forward); // == cos of angle between v_forward and vector to target
|
|
|
|
if (d_forward * reverse > max_cos) // on_target!
|
|
return d_forward;
|
|
|
|
// begin rule-of-thumb manoeuvres
|
|
stick_pitch = 0.0;
|
|
stick_roll = 0.0;
|
|
|
|
|
|
if ((reverse * d_forward < -0.5) && !pitching_over) // we're going the wrong way!
|
|
pitching_over = YES;
|
|
|
|
if (pitching_over)
|
|
{
|
|
if (reverse * d_up > 0) // pitch up
|
|
stick_pitch = -max_flight_pitch;
|
|
else
|
|
stick_pitch = max_flight_pitch;
|
|
pitching_over = (reverse * d_forward < 0.707);
|
|
}
|
|
|
|
// treat missiles specially
|
|
if ((scan_class == CLASS_MISSILE) && (d_forward > cos( delta_t * max_flight_pitch)))
|
|
{
|
|
NSLog(@"missile %@ in tracking mode", self);
|
|
[self trackOntoTarget: delta_t withDForward: d_forward];
|
|
return d_forward;
|
|
}
|
|
|
|
// check if we are flying toward the destination..
|
|
if ((d_forward < max_cos)||(retreat)) // not on course so we must adjust controls..
|
|
{
|
|
if (d_forward < -max_cos) // hack to avoid just flying away from the destination
|
|
{
|
|
d_up = min_d * 2.0;
|
|
}
|
|
|
|
if (d_up > min_d)
|
|
{
|
|
int factor = sqrt( fabs(d_right) / fabs(min_d));
|
|
if (factor > 8)
|
|
factor = 8;
|
|
if (d_right > min_d)
|
|
stick_roll = - max_flight_roll * reverse * 0.125 * factor;
|
|
if (d_right < -min_d)
|
|
stick_roll = + max_flight_roll * reverse * 0.125 * factor;
|
|
}
|
|
if (d_up < -min_d)
|
|
{
|
|
int factor = sqrt( fabs(d_right) / fabs(min_d));
|
|
if (factor > 8)
|
|
factor = 8;
|
|
if (d_right > min_d)
|
|
stick_roll = + max_flight_roll * reverse * 0.125 * factor;
|
|
if (d_right < -min_d)
|
|
stick_roll = - max_flight_roll * reverse * 0.125 * factor;
|
|
}
|
|
|
|
if (stick_roll == 0.0)
|
|
{
|
|
int factor = sqrt( fabs(d_up) / fabs(min_d));
|
|
if (factor > 8)
|
|
factor = 8;
|
|
if (d_up > min_d)
|
|
stick_pitch = - max_flight_pitch * reverse * 0.125 * factor;
|
|
if (d_up < -min_d)
|
|
stick_pitch = + max_flight_pitch * reverse * 0.125 * factor;
|
|
}
|
|
}
|
|
|
|
// end rule-of-thumb manoeuvres
|
|
|
|
// apply stick movement limits
|
|
if (flight_roll < stick_roll - rate1)
|
|
stick_roll = flight_roll + rate1;
|
|
if (flight_roll > stick_roll + rate1)
|
|
stick_roll = flight_roll - rate1;
|
|
if (flight_pitch < stick_pitch - rate2)
|
|
stick_pitch = flight_pitch + rate2;
|
|
if (flight_pitch > stick_pitch + rate2)
|
|
stick_pitch = flight_pitch - rate2;
|
|
|
|
// apply damping
|
|
double damproll = (flight_speed > 2.0)? damping : 2.0 * damping; // double damping if we're going very slowly
|
|
if (flight_roll < 0)
|
|
flight_roll += (flight_roll < -damproll)? damproll : -flight_roll;
|
|
if (flight_roll > 0)
|
|
flight_roll -= (flight_roll > damproll)? damproll : flight_roll;
|
|
if (flight_pitch < 0)
|
|
flight_pitch += (flight_pitch < -damping) ? damping : -flight_pitch;
|
|
if (flight_pitch > 0)
|
|
flight_pitch -= (flight_pitch > damping) ? damping : flight_pitch;
|
|
|
|
// apply stick to attitude control
|
|
flight_roll = stick_roll;
|
|
flight_pitch = stick_pitch;
|
|
|
|
if (retreat)
|
|
d_forward *= d_forward; // make positive AND decrease granularity
|
|
|
|
if (d_forward < 0.0)
|
|
return 0.0;
|
|
|
|
if ((!flight_roll)&&(!flight_pitch)) // no correction
|
|
return 1.0;
|
|
|
|
return d_forward;
|
|
}
|
|
|
|
- (double) missileTrackPrimaryTarget:(double) delta_t
|
|
{
|
|
Vector relativePosition;
|
|
GLfloat d_forward, d_up, d_right, range2;
|
|
Entity *target = [self getPrimaryTarget];
|
|
|
|
if (!target) // leave now!
|
|
return 0.0;
|
|
|
|
double damping = 0.5 * delta_t;
|
|
double rate2 = 4.0 * delta_t;
|
|
double rate1 = 2.0 * delta_t;
|
|
|
|
double stick_roll = 0.0; //desired roll and pitch
|
|
double stick_pitch = 0.0;
|
|
|
|
double tolerance1 = pitch_tolerance;
|
|
|
|
relativePosition = target->position;
|
|
relativePosition.x -= position.x;
|
|
relativePosition.y -= position.y;
|
|
relativePosition.z -= position.z;
|
|
|
|
range2 = magnitude2(relativePosition);
|
|
|
|
if (relativePosition.x||relativePosition.y||relativePosition.z)
|
|
relativePosition = unit_vector(&relativePosition);
|
|
else
|
|
relativePosition.z = 1.0;
|
|
|
|
d_right = dot_product(relativePosition, v_right); // = cosine of angle between angle to target and v_right
|
|
d_up = dot_product(relativePosition, v_up); // = cosine of angle between angle to target and v_up
|
|
d_forward = dot_product(relativePosition, v_forward); // = cosine of angle between angle to target and v_forward
|
|
|
|
// begin rule-of-thumb manoeuvres
|
|
|
|
stick_roll = 0.0;
|
|
|
|
if (pitching_over)
|
|
pitching_over = (stick_pitch != 0.0);
|
|
|
|
if ((d_forward < -tolerance1) && (!pitching_over))
|
|
{
|
|
pitching_over = YES;
|
|
if (d_up >= 0)
|
|
stick_pitch = -max_flight_pitch;
|
|
if (d_up < 0)
|
|
stick_pitch = max_flight_pitch;
|
|
}
|
|
|
|
if (pitching_over)
|
|
{
|
|
pitching_over = (d_forward < 0.5);
|
|
}
|
|
else
|
|
{
|
|
stick_pitch = -max_flight_pitch * d_up;
|
|
stick_roll = -max_flight_roll * d_right;
|
|
}
|
|
|
|
// end rule-of-thumb manoeuvres
|
|
|
|
// apply damping
|
|
if (flight_roll < 0)
|
|
flight_roll += (flight_roll < -damping) ? damping : -flight_roll;
|
|
if (flight_roll > 0)
|
|
flight_roll -= (flight_roll > damping) ? damping : flight_roll;
|
|
if (flight_pitch < 0)
|
|
flight_pitch += (flight_pitch < -damping) ? damping : -flight_pitch;
|
|
if (flight_pitch > 0)
|
|
flight_pitch -= (flight_pitch > damping) ? damping : flight_pitch;
|
|
|
|
// apply stick movement limits
|
|
if (flight_roll + rate1 < stick_roll)
|
|
stick_roll = flight_roll + rate1;
|
|
if (flight_roll - rate1 > stick_roll)
|
|
stick_roll = flight_roll - rate1;
|
|
if (flight_pitch + rate2 < stick_pitch)
|
|
stick_pitch = flight_pitch + rate2;
|
|
if (flight_pitch - rate2 > stick_pitch)
|
|
stick_pitch = flight_pitch - rate2;
|
|
|
|
// apply stick to attitude
|
|
flight_roll = stick_roll;
|
|
flight_pitch = stick_pitch;
|
|
|
|
//
|
|
// return target confidence 0.0 .. 1.0
|
|
//
|
|
if (d_forward < 0.0)
|
|
return 0.0;
|
|
return d_forward;
|
|
}
|
|
|
|
- (double) trackDestination:(double) delta_t :(BOOL) retreat
|
|
{
|
|
Vector relativePosition;
|
|
GLfloat d_forward, d_up, d_right;
|
|
|
|
Entity* primeTarget = [self getPrimaryTarget];
|
|
BOOL we_are_docking = ((primeTarget)&&(primeTarget->isStation));
|
|
|
|
double damping = 0.5 * delta_t;
|
|
double rate2 = 4.0 * delta_t;
|
|
double rate1 = 2.0 * delta_t;
|
|
|
|
double stick_roll = 0.0; //desired roll and pitch
|
|
double stick_pitch = 0.0;
|
|
|
|
double reverse = 1.0;
|
|
|
|
double min_d = 0.004;
|
|
double max_cos = 0.85;
|
|
|
|
if (retreat)
|
|
reverse = -reverse;
|
|
|
|
if (isPlayer)
|
|
reverse = -reverse;
|
|
|
|
relativePosition = destination;
|
|
relativePosition.x -= position.x;
|
|
relativePosition.y -= position.y;
|
|
relativePosition.z -= position.z;
|
|
|
|
double range2 = magnitude2(relativePosition);
|
|
|
|
max_cos = sqrt(1 - desired_range*desired_range/range2);
|
|
|
|
if (relativePosition.x||relativePosition.y||relativePosition.z)
|
|
relativePosition = unit_vector(&relativePosition);
|
|
else
|
|
relativePosition.z = 1.0;
|
|
|
|
d_right = dot_product(relativePosition, v_right);
|
|
d_up = dot_product(relativePosition, v_up);
|
|
d_forward = dot_product(relativePosition, v_forward); // == cos of angle between v_forward and vector to target
|
|
|
|
// begin rule-of-thumb manoeuvres
|
|
stick_pitch = 0.0;
|
|
stick_roll = 0.0;
|
|
|
|
// if (isPlayer)
|
|
// NSLog(@"DEBUG trackDestination:: max_cos %.4f, d_forward %.4f, we_are_docking %@", max_cos, d_forward, (we_are_docking)? @":YES:" : @":NO:");
|
|
|
|
// check if we are flying toward the destination..
|
|
if ((d_forward < max_cos)||(retreat)) // not on course so we must adjust controls..
|
|
{
|
|
|
|
if (d_forward < -max_cos) // hack to avoid just flying away from the destination
|
|
{
|
|
d_up = min_d * 2.0;
|
|
}
|
|
|
|
if (d_up > min_d)
|
|
{
|
|
int factor = sqrt( fabs(d_right) / fabs(min_d));
|
|
if (factor > 8)
|
|
factor = 8;
|
|
if (d_right > min_d)
|
|
stick_roll = - max_flight_roll * reverse * 0.125 * factor; //roll_roll * reverse;
|
|
if (d_right < -min_d)
|
|
stick_roll = + max_flight_roll * reverse * 0.125 * factor; //roll_roll * reverse;
|
|
}
|
|
if (d_up < -min_d)
|
|
{
|
|
int factor = sqrt( fabs(d_right) / fabs(min_d));
|
|
if (factor > 8)
|
|
factor = 8;
|
|
if (d_right > min_d)
|
|
stick_roll = + max_flight_roll * reverse * 0.125 * factor; //roll_roll * reverse;
|
|
if (d_right < -min_d)
|
|
stick_roll = - max_flight_roll * reverse * 0.125 * factor; //roll_roll * reverse;
|
|
}
|
|
|
|
if (stick_roll == 0.0)
|
|
{
|
|
int factor = sqrt( fabs(d_up) / fabs(min_d));
|
|
if (factor > 8)
|
|
factor = 8;
|
|
if (d_up > min_d)
|
|
stick_pitch = - max_flight_pitch * reverse * 0.125 * factor; //pitch_pitch * reverse;
|
|
if (d_up < -min_d)
|
|
stick_pitch = + max_flight_pitch * reverse * 0.125 * factor; //pitch_pitch * reverse;
|
|
}
|
|
}
|
|
|
|
if (we_are_docking && docking_match_rotation && (d_forward > max_cos))
|
|
{
|
|
/* we are docking and need to consider the rotation/orientation of the docking port */
|
|
Vector up_vec = [(StationEntity *)[self getPrimaryTarget] portUpVector];
|
|
double cosTheta = dot_product(up_vec, v_up); // == cos of angle between up vectors
|
|
double sinTheta = dot_product(up_vec, v_right);
|
|
|
|
double station_roll = [(StationEntity *)[self getPrimaryTarget] flight_roll];
|
|
|
|
if (!isPlayer)
|
|
{
|
|
station_roll = -station_roll; // make necessary corrections for a different viewpoint
|
|
sinTheta = -sinTheta;
|
|
}
|
|
|
|
if (cosTheta < 0)
|
|
{
|
|
cosTheta = -cosTheta;
|
|
sinTheta = -sinTheta;
|
|
}
|
|
|
|
if (sinTheta > 0.0)
|
|
{
|
|
// increase roll rate
|
|
stick_roll = cosTheta * cosTheta * station_roll + sinTheta * sinTheta * max_flight_roll;
|
|
}
|
|
else
|
|
{
|
|
// decrease roll rate
|
|
stick_roll = cosTheta * cosTheta * station_roll - sinTheta * sinTheta * max_flight_roll;
|
|
}
|
|
|
|
// NSLog(@"DEBUG %@ matching rotation .. docking cosTheta %.3f sinTheta %.3f station_roll %.3f stick_roll %.3f",
|
|
// self, cosTheta, sinTheta, station_roll, stick_roll);
|
|
}
|
|
|
|
// end rule-of-thumb manoeuvres
|
|
|
|
// apply stick movement limits
|
|
if (flight_roll < stick_roll - rate1)
|
|
stick_roll = flight_roll + rate1;
|
|
if (flight_roll > stick_roll + rate1)
|
|
stick_roll = flight_roll - rate1;
|
|
if (flight_pitch < stick_pitch - rate2)
|
|
stick_pitch = flight_pitch + rate2;
|
|
if (flight_pitch > stick_pitch + rate2)
|
|
stick_pitch = flight_pitch - rate2;
|
|
|
|
// apply damping
|
|
if ((!docking_match_rotation)||(!we_are_docking))
|
|
{
|
|
double damproll = (flight_speed > 2.0)? damping : 2.0 * damping; // double damping if we're going very slowly
|
|
if (flight_roll < 0)
|
|
flight_roll += (flight_roll < -damproll)? damproll : -flight_roll;
|
|
if (flight_roll > 0)
|
|
flight_roll -= (flight_roll > damproll)? damproll : flight_roll;
|
|
}
|
|
if (flight_pitch < 0)
|
|
flight_pitch += (flight_pitch < -damping) ? damping : -flight_pitch;
|
|
if (flight_pitch > 0)
|
|
flight_pitch -= (flight_pitch > damping) ? damping : flight_pitch;
|
|
|
|
// apply stick to attitude control
|
|
flight_roll = stick_roll;
|
|
flight_pitch = stick_pitch;
|
|
|
|
if (retreat)
|
|
d_forward *= d_forward; // make positive AND decrease granularity
|
|
|
|
if (d_forward < 0.0)
|
|
return 0.0;
|
|
|
|
if ((!flight_roll)&&(!flight_pitch)) // no correction
|
|
return 1.0;
|
|
|
|
return d_forward;
|
|
}
|
|
|
|
- (double) rangeToDestination
|
|
{
|
|
double dist;
|
|
Vector delta = destination;
|
|
delta.x -= position.x;
|
|
delta.y -= position.y;
|
|
delta.z -= position.z;
|
|
dist = sqrt(delta.x*delta.x + delta.y*delta.y + delta.z*delta.z);
|
|
return dist;
|
|
}
|
|
|
|
- (double) rangeToPrimaryTarget
|
|
{
|
|
double dist;
|
|
Vector delta;
|
|
Entity *target = [self getPrimaryTarget];
|
|
if (target == nil) // leave now!
|
|
return 0.0;
|
|
delta = target->position;
|
|
delta.x -= position.x;
|
|
delta.y -= position.y;
|
|
delta.z -= position.z;
|
|
dist = sqrt(delta.x*delta.x + delta.y*delta.y + delta.z*delta.z);
|
|
dist -= target->collision_radius;
|
|
dist -= collision_radius;
|
|
return dist;
|
|
}
|
|
|
|
- (BOOL) onTarget:(BOOL) fwd_weapon
|
|
{
|
|
GLfloat d2, radius, dq, astq;
|
|
Vector rel_pos, urp;
|
|
int weapon_type = (fwd_weapon)? forward_weapon_type : aft_weapon_type;
|
|
if (weapon_type == WEAPON_THARGOID_LASER)
|
|
return (randf() < 0.05); // one in twenty shots on target
|
|
Entity *target = [self getPrimaryTarget];
|
|
if (target == nil) // leave now!
|
|
return NO;
|
|
if (target->status == STATUS_DEAD)
|
|
return NO;
|
|
if (isSunlit && (target->isSunlit == NO) && (randf() < 0.75))
|
|
return NO; // 3/4 of the time you can't see from a lit place into a darker place
|
|
radius = target->collision_radius;
|
|
rel_pos = target->position;
|
|
rel_pos.x -= position.x;
|
|
rel_pos.y -= position.y;
|
|
rel_pos.z -= position.z;
|
|
d2 = magnitude2(rel_pos);
|
|
if (d2)
|
|
urp = unit_vector(&rel_pos);
|
|
else
|
|
urp = make_vector( 0, 0, 1);
|
|
dq = dot_product(urp, v_forward); // cosine of angle between v_forward and unit relative position
|
|
if (((fwd_weapon)&&(dq < 0.0)) || ((!fwd_weapon)&&(dq > 0.0)))
|
|
return NO;
|
|
|
|
astq = sqrt(1.0 - radius * radius / d2); // cosine of half angle subtended by target
|
|
|
|
return (fabs(dq) >= astq);
|
|
}
|
|
|
|
- (BOOL) fireMainWeapon:(double) range
|
|
{
|
|
//
|
|
// set the values for the forward weapon
|
|
//
|
|
[self set_weapon_data_from_type:forward_weapon_type];
|
|
//
|
|
if (shot_time < weapon_recharge_rate)
|
|
return NO;
|
|
int accuracy = 1;
|
|
if ([shipinfoDictionary objectForKey:@"accuracy"])
|
|
accuracy = [(NSNumber *)[shipinfoDictionary objectForKey:@"accuracy"] intValue];
|
|
if (accuracy < 1)
|
|
accuracy = 1;
|
|
if (range > randf() * weapon_range * accuracy)
|
|
return NO;
|
|
if (range > weapon_range)
|
|
return NO;
|
|
if (![self onTarget:YES])
|
|
return NO;
|
|
//
|
|
BOOL fired = NO;
|
|
switch (forward_weapon_type)
|
|
{
|
|
case WEAPON_PLASMA_CANNON :
|
|
[self firePlasmaShot:weapon_offset_x:1500.0:[NSColor yellowColor]];
|
|
[self firePlasmaShot:weapon_offset_x:1500.0:[NSColor yellowColor]];
|
|
fired = YES;
|
|
break;
|
|
|
|
case WEAPON_PULSE_LASER :
|
|
case WEAPON_BEAM_LASER :
|
|
case WEAPON_MINING_LASER :
|
|
case WEAPON_MILITARY_LASER :
|
|
[self fireLaserShot];
|
|
fired = YES;
|
|
break;
|
|
|
|
case WEAPON_THARGOID_LASER :
|
|
[self fireDirectLaserShot];
|
|
fired = YES;
|
|
break;
|
|
|
|
}
|
|
|
|
//can we fire lasers from our subentities?
|
|
int n_subs = [sub_entities count];
|
|
if (n_subs)
|
|
{
|
|
int i = 0;
|
|
for (i = 0; i < n_subs; i++)
|
|
{
|
|
ShipEntity* subent = (ShipEntity*)[sub_entities objectAtIndex:i];
|
|
if ((subent)&&(subent->isShip))
|
|
fired |= [subent fireSubentityLaserShot: range];
|
|
}
|
|
}
|
|
|
|
return fired;
|
|
}
|
|
|
|
- (BOOL) fireAftWeapon:(double) range
|
|
{
|
|
BOOL result = YES;
|
|
//
|
|
// save the existing weapon values
|
|
//
|
|
double weapon_energy1 = weapon_energy;
|
|
double weapon_recharge_rate1 = weapon_recharge_rate;
|
|
double weapon_range1 = weapon_range;
|
|
//
|
|
// set new values from aft_weapon_type
|
|
//
|
|
[self set_weapon_data_from_type:aft_weapon_type];
|
|
//
|
|
//
|
|
|
|
//NSLog(@"DEBUG %@ should fire aft weapon",name);
|
|
|
|
if (shot_time < weapon_recharge_rate)
|
|
return NO;
|
|
if (![self onTarget:NO])
|
|
return NO;
|
|
if (range > randf() * weapon_range)
|
|
return NO;
|
|
|
|
//NSLog(@"DEBUG %@ firing aft weapon",name);
|
|
|
|
if (result)
|
|
{
|
|
switch (aft_weapon_type)
|
|
{
|
|
case WEAPON_PULSE_LASER :
|
|
case WEAPON_BEAM_LASER :
|
|
case WEAPON_MINING_LASER :
|
|
case WEAPON_MILITARY_LASER :
|
|
[self fireLaserShotInDirection:VIEW_AFT];
|
|
break;
|
|
case WEAPON_THARGOID_LASER :
|
|
[self fireDirectLaserShot];
|
|
return YES;
|
|
break;
|
|
|
|
}
|
|
}
|
|
//
|
|
// restore previous values
|
|
//
|
|
weapon_energy = weapon_energy1;
|
|
weapon_recharge_rate = weapon_recharge_rate1;
|
|
weapon_range = weapon_range1;
|
|
//
|
|
return result;
|
|
}
|
|
|
|
- (BOOL) fireTurretCannon:(double) range
|
|
{
|
|
if (shot_time < weapon_recharge_rate)
|
|
return NO;
|
|
if (range > 5000)
|
|
return NO;
|
|
|
|
ParticleEntity *shot;
|
|
Vector origin = position;
|
|
Entity* father = [self owner];
|
|
GLfloat* r_mat = [father rotationMatrix];
|
|
Vector vel = vector_forward_from_quaternion(q_rotation);
|
|
// Vector vel_father = [father getVelocity];
|
|
while (father)
|
|
{
|
|
mult_vector_gl_matrix(&origin, r_mat);
|
|
Vector pos = father->position;
|
|
origin.x += pos.x; origin.y += pos.y; origin.z += pos.z;
|
|
father = [father owner];
|
|
r_mat = [father rotationMatrix];
|
|
}
|
|
double start = collision_radius + 0.5;
|
|
double speed = TURRET_SHOT_SPEED;
|
|
NSColor* color = laser_color;
|
|
|
|
origin.x += vel.x * start;
|
|
origin.y += vel.y * start;
|
|
origin.z += vel.z * start;
|
|
|
|
vel.x *= speed;
|
|
vel.y *= speed;
|
|
vel.z *= speed;
|
|
|
|
// vel.x += vel_father.x;
|
|
// vel.y += vel_father.y;
|
|
// vel.z += vel_father.z;
|
|
//
|
|
shot = [[ParticleEntity alloc] init]; // alloc retains!
|
|
[shot setPosition:origin]; // directly ahead
|
|
[shot setScanClass: CLASS_NO_DRAW];
|
|
[shot setVelocity: vel];
|
|
[shot setDuration: 3.0];
|
|
[shot setCollisionRadius: 2.0];
|
|
[shot setEnergy: weapon_energy];
|
|
[shot setParticleType: PARTICLE_SHOT_PLASMA];
|
|
[shot setColor:color];
|
|
[shot setSize:NSMakeSize(12,12)];
|
|
[universe addEntity:shot];
|
|
|
|
[shot setOwner:[self owner]]; // has to be done AFTER adding shot to the universe
|
|
// NSLog(@"DEBUG Plasma cannon shot owner is %@", [shot owner]);
|
|
|
|
[shot release]; //release
|
|
|
|
shot_time = 0.0;
|
|
return YES;
|
|
}
|
|
|
|
- (void) setLaserColor:(NSColor *) color
|
|
{
|
|
if (color)
|
|
{
|
|
[laser_color release];
|
|
laser_color = [color retain];
|
|
}
|
|
}
|
|
|
|
- (BOOL) fireLaserShot
|
|
{
|
|
ParticleEntity *shot;
|
|
int direction = VIEW_FORWARD;
|
|
double range_limit2 = weapon_range*weapon_range;
|
|
target_laser_hit = NO_TARGET;
|
|
|
|
Vector laserPortOffset = forwardWeaponOffset;
|
|
Vector aimOffset = make_vector( 0, 0, 0);
|
|
|
|
if (isPlayer) // only the player has weapons on other facings
|
|
{
|
|
direction = [universe viewDir]; // set the weapon facing here
|
|
switch(direction)
|
|
{
|
|
case VIEW_AFT:
|
|
laserPortOffset = aftWeaponOffset;
|
|
break;
|
|
case VIEW_PORT:
|
|
laserPortOffset = portWeaponOffset;
|
|
break;
|
|
case VIEW_STARBOARD:
|
|
laserPortOffset = starboardWeaponOffset;
|
|
break;
|
|
default:
|
|
laserPortOffset = forwardWeaponOffset;
|
|
}
|
|
aimOffset = [(PlayerEntity*)self viewOffset];
|
|
}
|
|
|
|
target_laser_hit = [universe getFirstEntityHitByLaserFromEntity:self inView:direction offset:aimOffset];
|
|
|
|
shot = [[ParticleEntity alloc] initLaserFromShip:self view:direction offset:laserPortOffset]; // alloc retains!
|
|
|
|
[shot setColor:laser_color];
|
|
[shot setScanClass: CLASS_NO_DRAW];
|
|
if (target_laser_hit != NO_TARGET)
|
|
{
|
|
Entity *victim = [universe entityForUniversalID:target_laser_hit];
|
|
if (victim)
|
|
{
|
|
Vector p0 = shot->position;
|
|
Vector p1 = victim->position;
|
|
if (victim->isShip)
|
|
{
|
|
ShipEntity* ship_hit = ((ShipEntity*)victim);
|
|
ShipEntity* subent = ship_hit->subentity_taking_damage;
|
|
if ((subent) && [ship_hit->sub_entities containsObject:subent])
|
|
{
|
|
if (ship_hit->isFrangible)
|
|
{
|
|
p1 = [subent absolutePositionForSubentity];
|
|
victim = subent;
|
|
// do 1% bleed-through damage...
|
|
[ship_hit takeEnergyDamage: 0.01 * weapon_energy from:subent becauseOf:self];
|
|
}
|
|
}
|
|
}
|
|
//
|
|
double dist2 = distance2( p0, p1);
|
|
if ((victim->isShip)&&(dist2 < range_limit2))
|
|
{
|
|
[(ShipEntity *)victim takeEnergyDamage:weapon_energy from:self becauseOf:self]; // a very palpable hit
|
|
[shot setCollisionRadius:sqrt(dist2)]; // so it's drawn to the right size
|
|
}
|
|
}
|
|
}
|
|
[universe addEntity:shot];
|
|
[shot release]; //release
|
|
|
|
shot_time = 0.0;
|
|
|
|
// random laser over-heating for AI ships
|
|
if ((!isPlayer)&&((ranrot_rand() & 255) < weapon_energy)&&(![self isMining]))
|
|
shot_time -= (randf() * weapon_energy);
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) fireSubentityLaserShot: (double) range
|
|
{
|
|
ParticleEntity *shot;
|
|
int direction = VIEW_FORWARD;
|
|
target_laser_hit = NO_TARGET;
|
|
|
|
if (forward_weapon_type == WEAPON_NONE)
|
|
return NO;
|
|
[self set_weapon_data_from_type:forward_weapon_type];
|
|
|
|
ShipEntity* parent = (ShipEntity*)[self owner];
|
|
|
|
if (shot_time < weapon_recharge_rate)
|
|
return NO;
|
|
|
|
if (range > weapon_range)
|
|
return NO;
|
|
|
|
target_laser_hit = [universe getFirstEntityHitByLaserFromEntity:self inView:direction];
|
|
|
|
// NSLog(@"DEBUG target hit by SubEntityLaserShot: %d %@", target_laser_hit, [universe entityForUniversalID:target_laser_hit]);
|
|
|
|
shot = [[ParticleEntity alloc] initLaserFromSubentity:self view:direction]; // alloc retains!
|
|
[shot setColor:laser_color];
|
|
[shot setScanClass: CLASS_NO_DRAW];
|
|
if (target_laser_hit != NO_TARGET)
|
|
{
|
|
Entity *victim = [universe entityForUniversalID:target_laser_hit];
|
|
if (victim)
|
|
{
|
|
Vector p0 = shot->position;
|
|
Vector p1 = victim->position;
|
|
if (victim->isShip)
|
|
{
|
|
ShipEntity* ship_hit = ((ShipEntity*)victim);
|
|
ShipEntity* subent = ship_hit->subentity_taking_damage;
|
|
if ((subent) && [ship_hit->sub_entities containsObject:subent])
|
|
{
|
|
if (ship_hit->isFrangible)
|
|
{
|
|
p1 = [subent absolutePositionForSubentity];
|
|
victim = subent;
|
|
// do 1% bleed-through damage...
|
|
[ship_hit takeEnergyDamage: 0.01 * weapon_energy from:subent becauseOf:self];
|
|
}
|
|
}
|
|
}
|
|
//
|
|
double dist2 = distance2( p0, p1);
|
|
if ((victim->isShip)&&(dist2 < weapon_range*weapon_range))
|
|
{
|
|
[(ShipEntity *)victim takeEnergyDamage:weapon_energy from:self becauseOf:parent]; // a very palpable hit
|
|
[shot setCollisionRadius:sqrt(dist2)]; // so it's drawn to the right size
|
|
}
|
|
}
|
|
}
|
|
[universe addEntity:shot];
|
|
[shot release]; //release
|
|
|
|
shot_time = 0.0;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) fireDirectLaserShot
|
|
{
|
|
// NSLog(@"DEBUG %@ %d laser fired direct shot on %@ %d", name, universal_id, [(ShipEntity*)[self getPrimaryTarget] name], primaryTarget);
|
|
|
|
Entity* my_target = [self getPrimaryTarget];
|
|
if (!my_target)
|
|
return NO;
|
|
ParticleEntity* shot;
|
|
double range_limit2 = weapon_range*weapon_range;
|
|
Vector r_pos = my_target->position;
|
|
r_pos.x -= position.x; r_pos.y -= position.y; r_pos.z -= position.z;
|
|
if (r_pos.x||r_pos.y||r_pos.z)
|
|
r_pos = unit_vector(&r_pos);
|
|
else
|
|
r_pos.z = 1.0;
|
|
|
|
// target_laser_hit = primaryTarget;
|
|
|
|
Quaternion q_laser = quaternion_rotation_between(r_pos, make_vector(0.0f,0.0f,1.0f));
|
|
q_laser.x += 0.01 * (randf() - 0.5); // randomise aim a little (+/- 0.005)
|
|
q_laser.y += 0.01 * (randf() - 0.5);
|
|
q_laser.z += 0.01 * (randf() - 0.5);
|
|
quaternion_normalise(&q_laser);
|
|
|
|
Quaternion q_save = q_rotation; // save rotation
|
|
q_rotation = q_laser; // face in direction of laser
|
|
target_laser_hit = [universe getFirstEntityHitByLaserFromEntity:self inView:VIEW_FORWARD];
|
|
q_rotation = q_save; // restore rotation
|
|
|
|
Vector vel = make_vector( v_forward.x * flight_speed, v_forward.y * flight_speed, v_forward.z * flight_speed);
|
|
|
|
// do special effects laser line
|
|
shot = [[ParticleEntity alloc] initLaserFromShip:self view:VIEW_FORWARD]; // alloc retains!
|
|
[shot setColor:laser_color];
|
|
[shot setScanClass: CLASS_NO_DRAW];
|
|
[shot setPosition: position];
|
|
[shot setQRotation: q_laser];
|
|
[shot setVelocity: vel];
|
|
if (target_laser_hit != NO_TARGET)
|
|
{
|
|
Entity *victim = [universe entityForUniversalID:target_laser_hit];
|
|
|
|
if ((victim) && (victim->isShip))
|
|
{
|
|
ShipEntity* parent = (ShipEntity*)[victim owner];
|
|
if ((parent) && (parent != victim) && [parent->sub_entities containsObject:victim])
|
|
{
|
|
if (parent->isFrangible)
|
|
{
|
|
// NSLog(@"DEBUG Direct Laser hit on subentity %@ of frangible entity %@", victim, parent);
|
|
}
|
|
else
|
|
{
|
|
// NSLog(@"DEBUG Direct Laser hit on subentity %@ of NON-frangible entity %@", victim, parent);
|
|
victim = parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (victim)
|
|
{
|
|
Vector p0 = shot->position;
|
|
Vector p1 = victim->position;
|
|
p1.x -= p0.x; p1.y -= p0.y; p1.z -= p0.z;
|
|
double dist2 = magnitude2(p1);
|
|
if ((victim->isShip)&&(dist2 < range_limit2))
|
|
{
|
|
[(ShipEntity *)victim takeEnergyDamage:weapon_energy from:self becauseOf:self]; // a very palpable hit
|
|
[shot setCollisionRadius:sqrt(dist2)];
|
|
//
|
|
}
|
|
}
|
|
}
|
|
[universe addEntity:shot];
|
|
[shot release]; //release
|
|
|
|
shot_time = 0.0;
|
|
|
|
// random laser over-heating for AI ships
|
|
if ((!isPlayer)&&((ranrot_rand() & 255) < weapon_energy)&&(![self isMining]))
|
|
shot_time -= (randf() * weapon_energy);
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) fireLaserShotInDirection: (int) direction
|
|
{
|
|
ParticleEntity *shot;
|
|
double range_limit2 = weapon_range*weapon_range;
|
|
Vector vel;
|
|
target_laser_hit = NO_TARGET;
|
|
|
|
vel.x = v_forward.x * flight_speed;
|
|
vel.y = v_forward.y * flight_speed;
|
|
vel.z = v_forward.z * flight_speed;
|
|
|
|
target_laser_hit = [universe getFirstEntityHitByLaserFromEntity:self inView:direction];
|
|
|
|
shot = [[ParticleEntity alloc] initLaserFromShip:self view:direction]; // alloc retains!
|
|
[shot setColor:laser_color];
|
|
[shot setScanClass: CLASS_NO_DRAW];
|
|
[shot setVelocity: vel];
|
|
if (target_laser_hit != NO_TARGET)
|
|
{
|
|
Entity *victim = [universe entityForUniversalID:target_laser_hit];
|
|
|
|
if ((victim) && (victim->isShip))
|
|
{
|
|
ShipEntity* parent = (ShipEntity*)[victim owner];
|
|
if ((parent) && (parent != victim) && [parent->sub_entities containsObject:victim])
|
|
{
|
|
if (parent->isFrangible)
|
|
{
|
|
// NSLog(@"DEBUG Laser hit on subentity %@ of frangible entity %@", victim, parent);
|
|
}
|
|
else
|
|
{
|
|
// NSLog(@"DEBUG Laser hit on subentity %@ of NON-frangible entity %@", victim, parent);
|
|
victim = parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (victim)
|
|
{
|
|
Vector p0 = shot->position;
|
|
Vector p1 = victim->position;
|
|
p1.x -= p0.x; p1.y -= p0.y; p1.z -= p0.z;
|
|
double dist2 = magnitude2(p1);
|
|
if ((victim->isShip)&&(dist2 < range_limit2))
|
|
{
|
|
[(ShipEntity *)victim takeEnergyDamage:weapon_energy from:self becauseOf:self]; // a very palpable hit
|
|
[shot setCollisionRadius:sqrt(dist2)];
|
|
}
|
|
}
|
|
}
|
|
[universe addEntity:shot];
|
|
[shot release]; //release
|
|
|
|
shot_time = 0.0;
|
|
|
|
// random laser over-heating for AI ships
|
|
if ((!isPlayer)&&((ranrot_rand() & 255) < weapon_energy)&&(![self isMining]))
|
|
shot_time -= (randf() * weapon_energy);
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (void) throwSparks
|
|
{
|
|
ParticleEntity* spark;
|
|
Vector vel;
|
|
Vector origin = position;
|
|
|
|
GLfloat lr = randf() * (boundingBox.max_x - boundingBox.min_x) + boundingBox.min_x;
|
|
GLfloat ud = randf() * (boundingBox.max_y - boundingBox.min_y) + boundingBox.min_y;
|
|
GLfloat fb = randf() * boundingBox.max_z + boundingBox.min_z; // rear section only
|
|
|
|
origin.x += fb * v_forward.x;
|
|
origin.y += fb * v_forward.y;
|
|
origin.z += fb * v_forward.z;
|
|
|
|
origin.x += ud * v_up.x;
|
|
origin.y += ud * v_up.y;
|
|
origin.z += ud * v_up.z;
|
|
|
|
origin.x += lr * v_right.x;
|
|
origin.y += lr * v_right.y;
|
|
origin.z += lr * v_right.z;
|
|
|
|
float w = boundingBox.max_x - boundingBox.min_x;
|
|
float h = boundingBox.max_y - boundingBox.min_y;
|
|
float m = (w < h) ? 0.25 * w: 0.25 * h;
|
|
|
|
float sz = m * (1 + randf() + randf()); // half minimum dimension on average
|
|
|
|
vel = make_vector( 2.0 * (origin.x - position.x), 2.0 * (origin.y - position.y), 2.0 * (origin.z - position.z));
|
|
|
|
spark = [[ParticleEntity alloc] init]; // alloc retains!
|
|
[spark setPosition:origin]; // directly ahead
|
|
[spark setScanClass: CLASS_NO_DRAW];
|
|
[spark setVelocity: vel];
|
|
[spark setDuration: 2.0 + 3.0 * randf()];
|
|
[spark setCollisionRadius: 2.0];
|
|
[spark setSize:NSMakeSize( sz, sz)];
|
|
[spark setEnergy: 0.0];
|
|
[spark setParticleType: PARTICLE_SPARK];
|
|
[spark setColor:[NSColor colorWithCalibratedHue:0.08 + 0.17 * randf() saturation:1.0 brightness:1.0 alpha:1.0]];
|
|
[spark setOwner:self];
|
|
[universe addEntity:spark];
|
|
[spark release]; //release
|
|
|
|
next_spark_time = randf();
|
|
}
|
|
|
|
- (BOOL) firePlasmaShot:(double) offset :(double) speed :(NSColor *) color
|
|
{
|
|
ParticleEntity *shot;
|
|
Vector vel, rt;
|
|
Vector origin = position;
|
|
double start = collision_radius + 0.5;
|
|
|
|
speed += flight_speed;
|
|
|
|
if (++shot_counter % 2)
|
|
offset = -offset;
|
|
|
|
vel = v_forward;
|
|
rt = v_right;
|
|
|
|
if (isPlayer) // player can fire into multiple views!
|
|
{
|
|
switch ([universe viewDir])
|
|
{
|
|
case VIEW_AFT :
|
|
vel = v_forward;
|
|
vel.x = -vel.x; vel.y = -vel.y; vel.z = -vel.z; // reverse
|
|
rt = v_right;
|
|
rt.x = -rt.x; rt.y = -rt.y; rt.z = -rt.z; // reverse
|
|
break;
|
|
case VIEW_STARBOARD :
|
|
vel = v_right;
|
|
rt = v_forward;
|
|
rt.x = -rt.x; rt.y = -rt.y; rt.z = -rt.z; // reverse
|
|
break;
|
|
case VIEW_PORT :
|
|
vel = v_right;
|
|
vel.x = -vel.x; vel.y = -vel.y; vel.z = -vel.z; // reverse
|
|
rt = v_forward;
|
|
break;
|
|
}
|
|
}
|
|
|
|
origin.x += vel.x * start;
|
|
origin.y += vel.y * start;
|
|
origin.z += vel.z * start;
|
|
|
|
origin.x += rt.x * offset;
|
|
origin.y += rt.y * offset;
|
|
origin.z += rt.z * offset;
|
|
|
|
vel.x *= speed;
|
|
vel.y *= speed;
|
|
vel.z *= speed;
|
|
|
|
shot = [[ParticleEntity alloc] init]; // alloc retains!
|
|
[shot setPosition:origin]; // directly ahead
|
|
[shot setScanClass: CLASS_NO_DRAW];
|
|
[shot setVelocity: vel];
|
|
[shot setDuration: 5.0];
|
|
[shot setCollisionRadius: 2.0];
|
|
[shot setEnergy: weapon_energy];
|
|
[shot setParticleType: PARTICLE_SHOT_GREEN_PLASMA];
|
|
[shot setColor:color];
|
|
[shot setOwner:self];
|
|
[universe addEntity:shot];
|
|
[shot release]; //release
|
|
|
|
shot_time = 0.0;
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) fireMissile
|
|
{
|
|
ShipEntity *missile;
|
|
Vector vel;
|
|
Vector origin = position;
|
|
Vector start, v_eject;
|
|
|
|
// default launching position
|
|
start.x = 0.0; // in the middle
|
|
start.y = boundingBox.min_y - 4.0; // 4m below bounding box
|
|
start.z = boundingBox.max_z + 1.0; // 1m ahead of bounding box
|
|
// custom launching position
|
|
if ([shipinfoDictionary objectForKey:@"missile_launch_position"])
|
|
{
|
|
start = [Entity vectorFromString:(NSString *)[shipinfoDictionary objectForKey:@"missile_launch_position"]];
|
|
}
|
|
|
|
double throw_speed = 250.0;
|
|
Quaternion q1 = q_rotation;
|
|
Entity *target = [self getPrimaryTarget];
|
|
|
|
if ((missiles <= 0)||(target == nil)||(target->scan_class == CLASS_NO_DRAW)||
|
|
((target->isShip)&&(!has_military_scanner_filter)&&([(ShipEntity*)target isJammingScanning]))) // no missile lock!
|
|
return NO;
|
|
|
|
if ([roles isEqual:@"thargoid"])
|
|
return [self fireTharglet];
|
|
|
|
//vel.x *= throw_speed; vel.y *= throw_speed; vel.z *= throw_speed;
|
|
if (randf() < 0.90) // choose a standard missile 90% of the time
|
|
missile = [universe getShipWithRole:@"EQ_MISSILE"]; // retained
|
|
else // otherwise choose any with the role 'missile' - which may include alternative weapons
|
|
missile = [universe getShipWithRole:@"missile"]; // retained
|
|
|
|
if (!missile)
|
|
return NO;
|
|
|
|
missiles--;
|
|
|
|
double mcr = missile->collision_radius;
|
|
|
|
v_eject = unit_vector( &start);
|
|
|
|
// check if start is within bounding box...
|
|
while ( (start.x > boundingBox.min_x - mcr)&&(start.x < boundingBox.max_x + mcr)&&
|
|
(start.y > boundingBox.min_y - mcr)&&(start.y < boundingBox.max_y + mcr)&&
|
|
(start.z > boundingBox.min_z - mcr)&&(start.z < boundingBox.max_z + mcr))
|
|
{
|
|
start.x += mcr * v_eject.x; start.y += mcr * v_eject.y; start.z += mcr * v_eject.z;
|
|
}
|
|
|
|
if (isPlayer)
|
|
q1.w = -q1.w; // player view is reversed remember!
|
|
|
|
vel.x = (flight_speed + throw_speed) * v_forward.x;
|
|
vel.y = (flight_speed + throw_speed) * v_forward.y;
|
|
vel.z = (flight_speed + throw_speed) * v_forward.z;
|
|
|
|
origin.x = position.x + v_right.x * start.x + v_up.x * start.y + v_forward.x * start.z;
|
|
origin.y = position.y + v_right.y * start.x + v_up.y * start.y + v_forward.y * start.z;
|
|
origin.z = position.z + v_right.z * start.x + v_up.z * start.y + v_forward.z * start.z;
|
|
|
|
[missile setPosition:origin];
|
|
[missile setQRotation:q1];
|
|
[missile setScanClass: CLASS_NO_DRAW]; // make it invisible
|
|
[missile addTarget:target];
|
|
[missile setStatus: STATUS_IN_FLIGHT]; // necessary to get it going!
|
|
[missile setVelocity: vel];
|
|
[missile setSpeed:150.0];
|
|
[missile setOwner:self];
|
|
[missile setDistanceTravelled:0.0];
|
|
//debug
|
|
//[missile setReportAImessages:YES];
|
|
//
|
|
[universe addEntity:missile];
|
|
[missile setScanClass: CLASS_MISSILE]; // make it visible
|
|
//NSLog(@"Missile collision radius is %.1f",missile->collision_radius);
|
|
[missile release]; //release
|
|
|
|
[(ShipEntity *)target setPrimaryAggressor:self];
|
|
[[(ShipEntity *)target getAI] reactToMessage:@"INCOMING_MISSILE"];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) fireTharglet
|
|
{
|
|
ShipEntity *tharglet;
|
|
Vector vel;
|
|
Vector origin = position;
|
|
Vector start;
|
|
start.x = 0.0; // in the middle
|
|
start.y = boundingBox.min_y - 10.0; // 10m below bounding box
|
|
start.z = 1.0; // 1m ahead of bounding box
|
|
double throw_speed = 500.0;
|
|
Quaternion q1 = q_rotation;
|
|
Entity *target = [self getPrimaryTarget];
|
|
|
|
if ((missiles <= 0)||(target == nil))
|
|
return NO;
|
|
|
|
missiles--;
|
|
|
|
if (isPlayer)
|
|
q1.w = -q1.w; // player view is reversed remember!
|
|
|
|
vel.x = (flight_speed + throw_speed) * v_forward.x;
|
|
vel.y = (flight_speed + throw_speed) * v_forward.y;
|
|
vel.z = (flight_speed + throw_speed) * v_forward.z;
|
|
|
|
origin.x = position.x + v_right.x * start.x + v_up.x * start.y + v_forward.x * start.z;
|
|
origin.y = position.y + v_right.y * start.x + v_up.y * start.y + v_forward.y * start.z;
|
|
origin.z = position.z + v_right.z * start.x + v_up.z * start.y + v_forward.z * start.z;
|
|
|
|
tharglet = [universe getShipWithRole:@"tharglet"]; // retain count = 1
|
|
if (tharglet)
|
|
{
|
|
[tharglet setPosition:origin]; // directly below
|
|
[tharglet setScanClass: CLASS_THARGOID];
|
|
[tharglet addTarget:target];
|
|
[tharglet setQRotation:q1];
|
|
[tharglet setStatus: STATUS_IN_FLIGHT]; // necessary to get it going!
|
|
[tharglet setVelocity: vel];
|
|
[tharglet setSpeed:350.0];
|
|
[tharglet setOwner:self];
|
|
//[tharglet setReportAImessages:YES]; // debug
|
|
[universe addEntity:tharglet];
|
|
//NSLog(@"tharglet collision radius is %.1f",tharglet->collision_radius);
|
|
|
|
[tharglet setGroup_id:group_id];
|
|
|
|
[tharglet release]; //release
|
|
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL) fireECM
|
|
{
|
|
if (!has_ecm)
|
|
return NO;
|
|
else
|
|
{
|
|
ParticleEntity *ecmDevice = [[ParticleEntity alloc] initECMMineFromShip:self]; // retained
|
|
[universe addEntity:ecmDevice];
|
|
[ecmDevice release];
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL) activateCloakingDevice
|
|
{
|
|
if (!has_cloaking_device)
|
|
return NO;
|
|
if (!cloaking_device_active)
|
|
cloaking_device_active = (energy > CLOAKING_DEVICE_START_ENERGY * max_energy);
|
|
return cloaking_device_active;
|
|
}
|
|
|
|
- (void) deactivateCloakingDevice
|
|
{
|
|
cloaking_device_active = NO;
|
|
}
|
|
|
|
- (BOOL) launchEnergyBomb
|
|
{
|
|
if (!has_energy_bomb)
|
|
return NO;
|
|
[self setSpeed: max_flight_speed + 300];
|
|
ShipEntity* bomb = [universe getShipWithRole:@"energy-bomb"];
|
|
if (!bomb)
|
|
return NO;
|
|
double start = collision_radius + bomb->collision_radius;
|
|
double eject_speed = -800.0;
|
|
Quaternion random_direction;
|
|
Vector vel;
|
|
Vector rpos = position;
|
|
double random_roll = randf() - 0.5; // -0.5 to +0.5
|
|
double random_pitch = randf() - 0.5; // -0.5 to +0.5
|
|
quaternion_set_random(&random_direction);
|
|
rpos.x -= v_forward.x * start;
|
|
rpos.y -= v_forward.y * start;
|
|
rpos.z -= v_forward.z * start;
|
|
vel.x = v_forward.x * (flight_speed + eject_speed);
|
|
vel.y = v_forward.y * (flight_speed + eject_speed);
|
|
vel.z = v_forward.z * (flight_speed + eject_speed);
|
|
eject_speed *= 0.5 * (randf() - 0.5); // -0.25x .. +0.25x
|
|
vel.x += v_up.x * eject_speed;
|
|
vel.y += v_up.y * eject_speed;
|
|
vel.z += v_up.z * eject_speed;
|
|
eject_speed *= 0.5 * (randf() - 0.5); // -0.0625x .. +0.0625x
|
|
vel.x += v_right.x * eject_speed;
|
|
vel.y += v_right.y * eject_speed;
|
|
vel.z += v_right.z * eject_speed;
|
|
[bomb setPosition:rpos];
|
|
[bomb setQRotation:random_direction];
|
|
[bomb setRoll:random_roll];
|
|
[bomb setPitch:random_pitch];
|
|
[bomb setVelocity:vel];
|
|
[bomb setScanClass: CLASS_MINE]; // TODO should be CLASS_ENERGY_BOMB
|
|
[bomb setStatus: STATUS_IN_FLIGHT];
|
|
[bomb setEnergy: 5.0]; // 5 second countdown
|
|
[bomb setCondition: CONDITION_ENERGY_BOMB_COUNTDOWN];
|
|
[bomb setOwner: self];
|
|
[universe addEntity:bomb];
|
|
[[bomb getAI] setState:@"GLOBAL"];
|
|
[bomb release];
|
|
if (self != [universe entityZero]) // get the heck out of here
|
|
{
|
|
[self addTarget:bomb];
|
|
condition = CONDITION_FLEE_TARGET;
|
|
frustration = 0.0;
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (int) launchEscapeCapsule
|
|
{
|
|
ShipEntity *pod;
|
|
|
|
pod = [universe getShipWithRole:@"escape-capsule"]; // retain count = 1
|
|
if (pod)
|
|
{
|
|
[pod setOwner:self];
|
|
[pod setScanClass: CLASS_CARGO];
|
|
[pod setCommodity:[universe commodityForName:@"Slaves"] andAmount:1];
|
|
if (crew)
|
|
{
|
|
[pod setCrew: crew];
|
|
[crew autorelease];
|
|
crew = nil;
|
|
}
|
|
[[pod getAI] setStateMachine:@"homeAI.plist"];
|
|
[self dumpItem:pod];
|
|
[[pod getAI] setState:@"GLOBAL"];
|
|
[pod release]; //release
|
|
return [pod universal_id];
|
|
}
|
|
return NO_TARGET;
|
|
}
|
|
|
|
- (int) dumpCargo
|
|
{
|
|
if (status == STATUS_DEAD)
|
|
return 0;
|
|
|
|
int result = CARGO_NOT_CARGO;
|
|
if (([cargo count] > 0)&&([universe getTime] - cargo_dump_time > 0.5)) // space them 0.5s or 10m apart
|
|
{
|
|
ShipEntity* jetto = [cargo objectAtIndex:0];
|
|
if (!jetto)
|
|
return 0;
|
|
result = [jetto getCommodityType];
|
|
[self dumpItem:jetto];
|
|
[cargo removeObjectAtIndex:0];
|
|
cargo_dump_time = [universe getTime];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (int) dumpItem: (ShipEntity*) jetto
|
|
{
|
|
if (!jetto)
|
|
return 0;
|
|
int result = [jetto getCargoType];
|
|
Vector start;
|
|
|
|
double eject_speed = 20.0;
|
|
double eject_reaction = -eject_speed * [jetto mass] / [self mass];
|
|
double jcr = jetto->collision_radius;
|
|
|
|
Quaternion random_direction;
|
|
Vector vel, v_eject;
|
|
Vector rpos = position;
|
|
double random_roll = ((ranrot_rand() % 1024) - 512.0)/1024.0; // -0.5 to +0.5
|
|
double random_pitch = ((ranrot_rand() % 1024) - 512.0)/1024.0; // -0.5 to +0.5
|
|
quaternion_set_random(&random_direction);
|
|
|
|
// default launching position
|
|
start.x = 0.0; // in the middle
|
|
start.y = 0.0; //
|
|
start.z = boundingBox.min_z - jcr; // 1m behind of bounding box
|
|
|
|
// custom launching position
|
|
if ([shipinfoDictionary objectForKey:@"aft_eject_position"])
|
|
{
|
|
start = [Entity vectorFromString:(NSString *)[shipinfoDictionary objectForKey:@"aft_eject_position"]];
|
|
}
|
|
|
|
v_eject = unit_vector( &start);
|
|
|
|
// check if start is within bounding box...
|
|
while ( (start.x > boundingBox.min_x - jcr)&&(start.x < boundingBox.max_x + jcr)&&
|
|
(start.y > boundingBox.min_y - jcr)&&(start.y < boundingBox.max_y + jcr)&&
|
|
(start.z > boundingBox.min_z - jcr)&&(start.z < boundingBox.max_z + jcr))
|
|
{
|
|
start.x += jcr * v_eject.x; start.y += jcr * v_eject.y; start.z += jcr * v_eject.z;
|
|
}
|
|
|
|
v_eject = make_vector( v_right.x * start.x + v_up.x * start.y + v_forward.x * start.z,
|
|
v_right.y * start.x + v_up.y * start.y + v_forward.y * start.z,
|
|
v_right.z * start.x + v_up.z * start.y + v_forward.z * start.z);
|
|
|
|
rpos.x += v_eject.x;
|
|
rpos.y += v_eject.y;
|
|
rpos.z += v_eject.z;
|
|
|
|
v_eject = unit_vector( &v_eject);
|
|
|
|
v_eject.x += (randf() - randf())/eject_speed;
|
|
v_eject.y += (randf() - randf())/eject_speed;
|
|
v_eject.z += (randf() - randf())/eject_speed;
|
|
|
|
vel.x = v_forward.x * flight_speed + v_eject.x * eject_speed;
|
|
vel.y = v_forward.y * flight_speed + v_eject.y * eject_speed;
|
|
vel.z = v_forward.z * flight_speed + v_eject.z * eject_speed;
|
|
|
|
velocity.x += v_eject.x * eject_reaction;
|
|
velocity.y += v_eject.y * eject_reaction;
|
|
velocity.z += v_eject.z * eject_reaction;
|
|
|
|
[jetto setPosition:rpos];
|
|
[jetto setQRotation:random_direction];
|
|
[jetto setRoll:random_roll];
|
|
[jetto setPitch:random_pitch];
|
|
[jetto setVelocity:vel];
|
|
[jetto setScanClass: CLASS_CARGO];
|
|
[jetto setStatus: STATUS_IN_FLIGHT];
|
|
[universe addEntity:jetto];
|
|
[[jetto getAI] setState:@"GLOBAL"];
|
|
cargo_dump_time = [universe getTime];
|
|
return result;
|
|
}
|
|
|
|
- (void) manageCollisions
|
|
{
|
|
// deal with collisions
|
|
//
|
|
Entity* ent;
|
|
ShipEntity* other_ship;
|
|
|
|
while ([collidingEntities count] > 0)
|
|
{
|
|
ent = [(Entity *)[collidingEntities objectAtIndex:0] retain];
|
|
[collidingEntities removeObjectAtIndex:0];
|
|
if (ent->isShip)
|
|
{
|
|
other_ship = (ShipEntity *)ent;
|
|
[self collideWithShip:other_ship];
|
|
}
|
|
if (ent->isPlanet)
|
|
{
|
|
if (isPlayer)
|
|
{
|
|
[(PlayerEntity *)self getDestroyed];
|
|
return;
|
|
}
|
|
[self becomeExplosion];
|
|
}
|
|
if (ent->isWormhole)
|
|
{
|
|
WormholeEntity* whole = (WormholeEntity*)ent;
|
|
if (isPlayer)
|
|
{
|
|
[(PlayerEntity*)self enterWormhole: whole];
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
[whole suckInShip: self];
|
|
}
|
|
}
|
|
[ent release];
|
|
}
|
|
}
|
|
|
|
- (BOOL) collideWithShip:(ShipEntity *)other
|
|
{
|
|
Vector loc, opos, pos;
|
|
double inc1, dam1;
|
|
|
|
// NSLog(@"DEBUG %@ %d colliding with other %@ %d", name, universal_id, [other name], [other universal_id]);
|
|
if (!other)
|
|
return NO;
|
|
|
|
ShipEntity* parent = (ShipEntity*)[other owner];
|
|
BOOL isSubentity = ((parent)&&(parent != other)&&([parent->sub_entities containsObject:other]));
|
|
|
|
// calculate line of centers using centres
|
|
if (isSubentity)
|
|
opos = [other absolutePositionForSubentity];
|
|
else
|
|
opos = other->position;
|
|
loc = opos;
|
|
loc.x -= position.x; loc.y -= position.y; loc.z -= position.z;
|
|
double back_dist = 0.5 * (actual_radius + other->actual_radius - sqrt(magnitude2(loc)));
|
|
|
|
if (loc.x||loc.y||loc.z)
|
|
loc = unit_vector(&loc);
|
|
else
|
|
loc.z = 1.0;
|
|
|
|
Vector back = make_vector( back_dist * loc.x, back_dist * loc.y, back_dist * loc.z);
|
|
|
|
inc1 = (v_forward.x*loc.x)+(v_forward.y*loc.y)+(v_forward.z*loc.z);
|
|
|
|
if ([self canScoop:other])
|
|
{
|
|
[self scoopUp:other];
|
|
return NO;
|
|
}
|
|
if ([other canScoop:self])
|
|
{
|
|
[other scoopUp:self];
|
|
return NO;
|
|
}
|
|
|
|
// back-off minimum distance
|
|
if (isSubentity)
|
|
{
|
|
pos = position;
|
|
[self setPosition: pos.x - 2.0 * back.x :pos.y - 2.0 * back.y :pos.z - 2.0 * back.z];
|
|
}
|
|
else
|
|
{
|
|
pos = position;
|
|
[self setPosition: pos.x - back.x :pos.y - back.y :pos.z - back.z];
|
|
pos = other->position;
|
|
[other setPosition: pos.x + back.x :pos.y + back.y :pos.z + back.z];
|
|
}
|
|
|
|
// find velocity along line of centers
|
|
//
|
|
// momentum = mass x velocity
|
|
// ke = mass x velocity x velocity
|
|
//
|
|
GLfloat m1 = mass; // mass of self
|
|
GLfloat m2 = [other mass]; // mass of other
|
|
//
|
|
Vector vel1 = [self getVelocity];
|
|
Vector vel2 = [other getVelocity];
|
|
if (isSubentity)
|
|
{
|
|
if (parent)
|
|
vel2 = [parent getVelocity];
|
|
else
|
|
vel2 = make_vector( 0, 0, 0);
|
|
}
|
|
//
|
|
GLfloat v1 = dot_product( vel1, loc); // velocity of self in direction of line of centers
|
|
GLfloat v2 = dot_product( vel2, loc); // velocity of other in direction of line of centers
|
|
//
|
|
GLfloat v1a = (2 * m2 * v2 + (m1 - m2) * v1) / ( m1 + m2); // velocity of self along loc after elastic collision
|
|
GLfloat v2a = v1 - v2 + v1a; // velocity of other along loc after elastic collision
|
|
//
|
|
Vector vel1a = make_vector( vel1.x + (v1a - v1) * loc.x, vel1.y + (v1a - v1) * loc.y, vel1.z + (v1a - v1) * loc.z);
|
|
Vector vel2a = make_vector( vel2.x + (v2a - v2) * loc.x, vel2.y + (v2a - v2) * loc.y, vel2.z + (v2a - v2) * loc.z);
|
|
//
|
|
if (isSubentity)
|
|
{
|
|
[self setVelocity:vel1a];
|
|
if (parent)
|
|
[parent setVelocity:vel2a];
|
|
}
|
|
else
|
|
{
|
|
[self setVelocity:vel1a];
|
|
[other setVelocity:vel2a];
|
|
}
|
|
//
|
|
// convert some velocity into damage energy
|
|
//
|
|
dam1 = (m1 + m2) * (v1 - v2) * (v1 - v2) / 100000000;
|
|
//
|
|
[self takeScrapeDamage: dam1 from:other];
|
|
if ((isSubentity) && (parent) && !(parent->isFrangible))
|
|
[parent takeScrapeDamage: dam1 from:self];
|
|
else
|
|
[other takeScrapeDamage: dam1 from:self];
|
|
//
|
|
// remove self from other's collision list
|
|
//
|
|
[[other collisionArray] removeObject:self];
|
|
//
|
|
////
|
|
|
|
[shipAI reactToMessage:@"COLLISION"];
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (Vector) getVelocity // overrides Entity getVelocity
|
|
{
|
|
Vector v = velocity;
|
|
v.x += flight_speed * v_forward.x; v.y += flight_speed * v_forward.y; v.z += flight_speed * v_forward.z;
|
|
return v;
|
|
}
|
|
|
|
- (void) addImpactMoment:(Vector) moment fraction:(GLfloat) howmuch
|
|
{
|
|
momentum.x += howmuch * moment.x;
|
|
momentum.y += howmuch * moment.y;
|
|
momentum.z += howmuch * moment.z;
|
|
}
|
|
|
|
- (BOOL) canScoop:(ShipEntity*)other
|
|
{
|
|
// NSLog(@"DEBUG Checking if %@ %d can scoop %@ %d", name, universal_id, [other name], [other universal_id]);
|
|
if (!other) return NO;
|
|
//
|
|
if (!has_scoop) return NO;
|
|
// NSLog(@"DEBUG scoop okay");
|
|
if ([cargo count] >= max_cargo) return NO;
|
|
// NSLog(@"DEBUG cargo space okay");
|
|
if (other->scan_class != CLASS_CARGO) return NO;
|
|
// NSLog(@"DEBUG other scan class is CLASS_CARGO okay");
|
|
if ([other getCargoType] == CARGO_NOT_CARGO) return NO;
|
|
// NSLog(@"DEBUG other cargo type is not CARGO_NOT_CARGO okay");
|
|
Vector loc = other->position;
|
|
loc.x -= position.x; loc.y -= position.y; loc.z -= position.z;
|
|
|
|
if (loc.x||loc.y||loc.z)
|
|
loc = unit_vector(&loc);
|
|
else
|
|
loc.z = 1.0;
|
|
|
|
double inc1 = (v_forward.x*loc.x)+(v_forward.y*loc.y)+(v_forward.z*loc.z);
|
|
if (inc1 < 0) return NO;
|
|
// NSLog(@"DEBUG incidence 1 okay");
|
|
double inc2 = (v_up.x*loc.x)+(v_up.y*loc.y)+(v_up.z*loc.z);
|
|
if ((inc2 > 0)&&(scan_class == CLASS_PLAYER)) return NO;
|
|
// NSLog(@"DEBUG incidence 2 okey dokey --> SHOULD SCOOP");
|
|
return YES;
|
|
}
|
|
|
|
- (void) scoopUp:(ShipEntity *)other
|
|
{
|
|
int co_type,co_amount;
|
|
switch ([other getCargoType])
|
|
{
|
|
case CARGO_RANDOM :
|
|
co_type = [other getCommodityType];
|
|
co_amount = [other getCommodityAmount];
|
|
break;
|
|
case CARGO_SLAVES :
|
|
co_amount = 1;
|
|
co_type = [universe commodityForName:@"Slaves"];
|
|
if (co_type == NSNotFound) // No 'Slaves' in this game, get something else instead...
|
|
{
|
|
co_type = [universe getRandomCommodity];
|
|
co_amount = [universe getRandomAmountOfCommodity:co_type];
|
|
}
|
|
break;
|
|
case CARGO_ALLOY :
|
|
co_amount = 1;
|
|
co_type = [universe commodityForName:@"Alloys"];
|
|
break;
|
|
case CARGO_MINERALS :
|
|
co_amount = 1;
|
|
co_type = [universe commodityForName:@"Minerals"];
|
|
break;
|
|
case CARGO_THARGOID :
|
|
co_amount = 1;
|
|
co_type = [universe commodityForName:@"Alien Items"];
|
|
break;
|
|
case CARGO_SCRIPTED_ITEM :
|
|
{
|
|
NSArray* actions = [other script_actions];
|
|
//scripting
|
|
if ([actions count])
|
|
{
|
|
int i;
|
|
PlayerEntity* player = (PlayerEntity *)[universe entityZero];
|
|
[player setScript_target:self];
|
|
for (i = 0; i < [actions count]; i++)
|
|
{
|
|
if ([[actions objectAtIndex:i] isKindOfClass:[NSDictionary class]])
|
|
[player checkCouplet: (NSDictionary *)[actions objectAtIndex:i] onEntity:other];
|
|
if ([[actions objectAtIndex:i] isKindOfClass:[NSString class]])
|
|
[player scriptAction: (NSString *) [actions objectAtIndex:i] onEntity:other];
|
|
}
|
|
// [(PlayerEntity *)[universe entityZero] scriptAction:(NSString *)[actions objectAtIndex:i] onEntity:other];
|
|
}
|
|
// NSLog(@"DEBUG Scooped scripted item %@ %@ %d", other, [other name], [other universal_id]);
|
|
if (isPlayer)
|
|
{
|
|
Random_Seed s_seed;
|
|
NSString* scoopedMS = [NSString stringWithFormat:[universe expandDescription:@"[@-scooped]" forSystem:s_seed], [other name]];
|
|
[universe clearPreviousMessage];
|
|
[universe addMessage:scoopedMS forCount:4];
|
|
}
|
|
}
|
|
default :
|
|
co_amount = 0;
|
|
co_type = 0;
|
|
break;
|
|
}
|
|
if (co_amount > 0)
|
|
{
|
|
[other setCommodity:co_type andAmount:co_amount]; // belt and braces setting this!
|
|
if (cargo_flag !=CARGO_FLAG_CANISTERS)
|
|
cargo_flag = CARGO_FLAG_CANISTERS;
|
|
//NSLog(@"---> %@ %d scooped %@", name, universal_id, [universe describeCommodity:co_type amount:co_amount]);
|
|
if (isPlayer)
|
|
{
|
|
[universe clearPreviousMessage];
|
|
[universe addMessage:[universe describeCommodity:co_type amount:co_amount] forCount:4.5];
|
|
}
|
|
[cargo addObject:other];
|
|
[other setStatus:STATUS_IN_HOLD]; // prevents entity from being recycled!
|
|
// [[other collisionArray] removeObject:self]; // so it can't be scooped twice!
|
|
[shipAI message:@"CARGO_SCOOPED"];
|
|
if ([cargo count] == max_cargo)
|
|
[shipAI message:@"HOLD_FULL"];
|
|
}
|
|
[[other collisionArray] removeObject:self]; // so it can't be scooped twice!
|
|
[universe removeEntity:other];
|
|
}
|
|
|
|
- (void) takeScrapeDamage:(double) amount from:(Entity *) ent
|
|
{
|
|
if (status == STATUS_DEAD) // it's too late for this one!
|
|
return;
|
|
|
|
if ([universe station] == self) // main stations are indestructible
|
|
return;
|
|
|
|
if (status == STATUS_LAUNCHING) // no collisions during launches please
|
|
return;
|
|
if ((ent)&&(ent->status == STATUS_LAUNCHING)) // no collisions during launches please
|
|
return;
|
|
|
|
//
|
|
energy -= amount;
|
|
// oops we hit too hard!!!
|
|
if (energy <= 0.0)
|
|
{
|
|
being_mined = YES; // same as using a mining laser
|
|
if ((ent)&&(ent->isShip))
|
|
{
|
|
ShipEntity* hunter = (ShipEntity *)ent;
|
|
[hunter collectBountyFor:self];
|
|
if ([hunter getPrimaryTarget] == (Entity *)self)
|
|
{
|
|
[hunter removeTarget:(Entity *)self];
|
|
[[hunter getAI] message:@"TARGET_DESTROYED"];
|
|
}
|
|
}
|
|
[self becomeExplosion];
|
|
}
|
|
else
|
|
{
|
|
// warn if I'm low on energy
|
|
if (energy < max_energy *0.25)
|
|
[shipAI message:@"ENERGY_LOW"];
|
|
}
|
|
}
|
|
|
|
- (void) enterDock:(StationEntity *)station
|
|
{
|
|
[shipAI message:@"DOCKED"];
|
|
[station noteDockedShip:self];
|
|
[universe removeEntity:self];
|
|
}
|
|
|
|
- (void) leaveDock:(StationEntity *)station
|
|
{
|
|
if (station)
|
|
{
|
|
Vector launchPos = station->position;
|
|
Vector stat_f = vector_forward_from_quaternion(station->q_rotation);
|
|
launchPos.x += 500.0*stat_f.x;
|
|
launchPos.y += 500.0*stat_f.y;
|
|
launchPos.z += 500.0*stat_f.z;
|
|
position = launchPos;
|
|
q_rotation = station->q_rotation;
|
|
flight_roll = [station flight_roll];
|
|
}
|
|
flight_pitch = 0.0;
|
|
flight_speed = max_flight_speed * 0.5;
|
|
status = STATUS_LAUNCHING;
|
|
[shipAI message:@"LAUNCHED"];
|
|
[universe addEntity:self];
|
|
}
|
|
|
|
- (void) enterWormhole:(WormholeEntity *) w_hole
|
|
{
|
|
if (![[universe sun] willGoNova]) // if the sun's not going nova
|
|
[universe witchspaceShipWithRole:roles]; // then add a new ship like this one leaving!
|
|
|
|
[w_hole suckInShip: self]; // removes ship from universe
|
|
}
|
|
|
|
- (void) enterWitchspace
|
|
{
|
|
// witchspace entry effects here
|
|
ParticleEntity *ring1 = [[ParticleEntity alloc] initHyperringFromShip:self]; // retained
|
|
[universe addEntity:ring1];
|
|
[ring1 release];
|
|
ParticleEntity *ring2 = [[ParticleEntity alloc] initHyperringFromShip:self]; // retained
|
|
[ring2 setSize:NSMakeSize([ring2 size].width * -2.5 ,[ring2 size].height * -2.0 )]; // shrinking!
|
|
[universe addEntity:ring2];
|
|
[ring2 release];
|
|
|
|
[shipAI message:@"ENTERED_WITCHSPACE"];
|
|
|
|
if (![[universe sun] willGoNova]) // if the sun's not going nova
|
|
[universe witchspaceShipWithRole:roles]; // then add a new ship like this one leaving!
|
|
|
|
[universe removeEntity:self];
|
|
}
|
|
|
|
- (void) leaveWitchspace
|
|
{
|
|
Vector pos = [universe getWitchspaceExitPosition];
|
|
Quaternion q_rtn = [universe getWitchspaceExitRotation];
|
|
position = pos;
|
|
double d1 = SCANNER_MAX_RANGE*((ranrot_rand() % 256)/256.0 - 0.5);
|
|
if (abs(d1) < 500.0) // no closer than 500m
|
|
d1 += ((d1 > 0.0)? 500.0: -500.0);
|
|
Quaternion q1 = q_rtn;
|
|
quaternion_set_random(&q1);
|
|
Vector v1 = vector_forward_from_quaternion(q1);
|
|
|
|
position.x += v1.x * d1; // randomise exit position
|
|
position.y += v1.y * d1;
|
|
position.z += v1.z * d1;
|
|
q_rotation = q_rtn;
|
|
flight_roll = 0.0;
|
|
flight_pitch = 0.0;
|
|
flight_speed = max_flight_speed * 0.25;
|
|
status = STATUS_LAUNCHING;
|
|
[shipAI message:@"EXITED_WITCHSPACE"];
|
|
[universe addEntity:self];
|
|
|
|
//NSLog(@"DEBUG Ship: %@ %d exiting witchspace now!", name, universal_id);
|
|
|
|
// witchspace exit effects here
|
|
ParticleEntity *ring1 = [[ParticleEntity alloc] initHyperringFromShip:self]; // retained
|
|
[universe addEntity:ring1];
|
|
[ring1 release];
|
|
ParticleEntity *ring2 = [[ParticleEntity alloc] initHyperringFromShip:self]; // retained
|
|
[ring2 setSize:NSMakeSize([ring2 size].width * -2.5 ,[ring2 size].height * -2.0 )]; // shrinking!
|
|
[universe addEntity:ring2];
|
|
[ring2 release];
|
|
}
|
|
|
|
- (void) markAsOffender:(int)offence_value
|
|
{
|
|
if (![roles isEqual:@"police"])
|
|
bounty |= offence_value;
|
|
}
|
|
|
|
- (void) switchLightsOn
|
|
{
|
|
if (!sub_entities) return;
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
Entity* subent = (Entity*)[sub_entities objectAtIndex:i];
|
|
if (subent->isParticle)
|
|
{
|
|
if ([(ParticleEntity*)subent particleType] == PARTICLE_FLASHER)
|
|
[subent setStatus:STATUS_EFFECT];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) switchLightsOff
|
|
{
|
|
if (!sub_entities) return;
|
|
int i;
|
|
for (i = 0; i < [sub_entities count]; i++)
|
|
{
|
|
Entity* subent = (Entity*)[sub_entities objectAtIndex:i];
|
|
if (subent->isParticle)
|
|
{
|
|
if ([(ParticleEntity*)subent particleType] == PARTICLE_FLASHER)
|
|
[subent setStatus:STATUS_INACTIVE];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void) setDestination:(Vector) dest
|
|
{
|
|
destination = dest;
|
|
frustration = 0.0; // new destination => no frustration!
|
|
}
|
|
|
|
inline BOOL pairOK(NSString* my_role, NSString* their_role)
|
|
{
|
|
BOOL pairing_okay = NO;
|
|
|
|
pairing_okay |= (![my_role isEqual:@"escort"] && ![my_role isEqual:@"wingman"] && [their_role isEqual:@"escort"]);
|
|
pairing_okay |= (([my_role isEqual:@"police"]||[my_role isEqual:@"interceptor"]) && [their_role isEqual:@"wingman"]);
|
|
|
|
// NSLog(@"checking if pairOK for ( %@, %@) >> %@", my_role, their_role, (pairing_okay)? @"YES":@"NO");
|
|
|
|
return pairing_okay;
|
|
}
|
|
|
|
- (BOOL) acceptAsEscort:(ShipEntity *) other_ship
|
|
{
|
|
// can't pair with self
|
|
if (self == other_ship)
|
|
return NO;
|
|
|
|
// NSLog(@"DEBUG %@ %d asked to accept %@ %d as escort when ai_stack_depth is %d", name, universal_id, [other_ship name], [other_ship universal_id], [shipAI ai_stack_depth]);
|
|
|
|
// if not in standard ai mode reject approach
|
|
if ([shipAI ai_stack_depth] > 1)
|
|
return NO;
|
|
|
|
// NSLog(@"DEBUG pairOK( %@, %@) = %@", roles, [other_ship roles], (pairOK( roles, [other_ship roles]))? @"YES":@"NO");
|
|
|
|
if (pairOK( roles, [other_ship roles]))
|
|
{
|
|
// check total number acceptable
|
|
int max_escorts = [(NSNumber *)[shipinfoDictionary objectForKey:@"escorts"] intValue];
|
|
|
|
// check it's not already been accepted
|
|
int i;
|
|
for (i = 0; i < n_escorts; i++)
|
|
{
|
|
if (escort_ids[i] == [other_ship universal_id])
|
|
{
|
|
[other_ship setGroup_id:universal_id];
|
|
[self setGroup_id:universal_id]; // make self part of same group
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
if ((n_escorts < MAX_ESCORTS)&&(n_escorts < max_escorts))
|
|
{
|
|
escort_ids[n_escorts] = [other_ship universal_id];
|
|
[other_ship setGroup_id:universal_id];
|
|
[self setGroup_id:universal_id]; // make self part of same group
|
|
n_escorts++;
|
|
|
|
//debug
|
|
// NSLog(@"DEBUG ::YES:: %@ accepts escort %@", self, other_ship);
|
|
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (Vector) getCoordinatesForEscortPosition:(int) f_pos
|
|
{
|
|
int f_hi = 1 + (f_pos >> 2);
|
|
int f_lo = f_pos & 3;
|
|
|
|
int fp = f_lo * 3;
|
|
int escort_positions[12] = { -2,0,-1, 2,0,-1, -3,0,-3, 3,0,-3 };
|
|
Vector pos = position;
|
|
double spacing = collision_radius * ESCORT_SPACING_FACTOR;
|
|
double xx = f_hi * spacing * escort_positions[fp++];
|
|
double yy = f_hi * spacing * escort_positions[fp++];
|
|
double zz = f_hi * spacing * escort_positions[fp];
|
|
pos.x += v_right.x * xx; pos.y += v_right.y * xx; pos.z += v_right.z * xx;
|
|
pos.x += v_up.x * yy; pos.y += v_up.y * yy; pos.z += v_up.z * yy;
|
|
pos.x += v_forward.x * zz; pos.y += v_forward.y * zz; pos.z += v_forward.z * zz;
|
|
|
|
return pos;
|
|
}
|
|
|
|
- (void) deployEscorts
|
|
{
|
|
if (n_escorts < 1)
|
|
return;
|
|
|
|
if (![self getPrimaryTarget])
|
|
return;
|
|
|
|
if (primaryTarget == last_escort_target)
|
|
{
|
|
// already deployed escorts onto this target!
|
|
// NSLog(@"DEBUG attempting to deploy more escorts onto same target - denied");
|
|
return;
|
|
}
|
|
|
|
last_escort_target = primaryTarget;
|
|
|
|
int n_deploy = ranrot_rand() % n_escorts;
|
|
if (n_deploy == 0)
|
|
n_deploy = 1;
|
|
|
|
//NSLog(@"DEBUG %@ %d deploying %d escorts", name, universal_id, n_deploy);
|
|
|
|
int i_deploy = n_escorts - 1;
|
|
while ((n_deploy > 0)&&(n_escorts > 0))
|
|
{
|
|
int escort_id = escort_ids[i_deploy];
|
|
ShipEntity *escorter = (ShipEntity *)[universe entityForUniversalID:escort_id];
|
|
// check it's still an escort ship
|
|
BOOL escorter_okay = YES;
|
|
if (!escorter)
|
|
escorter_okay = NO;
|
|
else
|
|
escorter_okay = escorter->isShip;
|
|
if (escorter_okay)
|
|
{
|
|
[escorter setGroup_id:NO_TARGET]; // act individually now!
|
|
[escorter addTarget:[self getPrimaryTarget]];
|
|
[[escorter getAI] setStateMachine:@"interceptAI.plist"];
|
|
[[escorter getAI] setState:@"GLOBAL"];
|
|
|
|
escort_ids[i_deploy] = NO_TARGET;
|
|
i_deploy--;
|
|
n_deploy--;
|
|
n_escorts--;
|
|
//debug
|
|
//NSLog(@"DEBUG trader %@ %d deploys escort %@ %d", name, universal_id, [escorter name], [escorter universal_id]);
|
|
//[escorter setReportAImessages:YES];
|
|
}
|
|
else
|
|
{
|
|
escort_ids[i_deploy--] = escort_ids[--n_escorts]; // remove the escort
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
- (void) dockEscorts
|
|
{
|
|
if (n_escorts < 1)
|
|
return;
|
|
|
|
int i;
|
|
for (i = 0; i < n_escorts; i++)
|
|
{
|
|
int escort_id = escort_ids[i];
|
|
ShipEntity *escorter = (ShipEntity *)[universe entityForUniversalID:escort_id];
|
|
// check it's still an escort ship
|
|
BOOL escorter_okay = YES;
|
|
if (!escorter)
|
|
escorter_okay = NO;
|
|
else
|
|
escorter_okay = escorter->isShip;
|
|
if (escorter_okay)
|
|
{
|
|
SEL _setSM = @selector(setStateMachine:);
|
|
SEL _setSt = @selector(setState:);
|
|
float delay = i * 3.0 + 1.5; // send them off at three second intervals
|
|
[escorter setGroup_id:NO_TARGET]; // act individually now!
|
|
[[escorter getAI] performSelector:_setSM withObject:@"dockingAI.plist" afterDelay:delay];
|
|
[[escorter getAI] performSelector:_setSt withObject:@"ABORT" afterDelay:delay + 0.25];
|
|
}
|
|
escort_ids[i] = NO_TARGET;
|
|
}
|
|
n_escorts = 0;
|
|
|
|
}
|
|
|
|
- (void) setTargetToStation
|
|
{
|
|
// check if the group_id (parent ship) points to a station...
|
|
Entity* mother = [universe entityForUniversalID:group_id];
|
|
if ((mother)&&(mother->isStation))
|
|
{
|
|
primaryTarget = group_id;
|
|
targetStation = primaryTarget;
|
|
return; // head for mother!
|
|
}
|
|
|
|
/*- selects the nearest station it can find -*/
|
|
if (!universe)
|
|
return;
|
|
int ent_count = universe->n_entities;
|
|
Entity** uni_entities = universe->sortedEntities; // grab the public sorted list
|
|
Entity* my_entities[ent_count];
|
|
int i;
|
|
int station_count = 0;
|
|
for (i = 0; i < ent_count; i++)
|
|
if (uni_entities[i]->isStation)
|
|
my_entities[station_count++] = [uni_entities[i] retain]; // retained
|
|
//
|
|
StationEntity* station = nil;
|
|
double nearest2 = SCANNER_MAX_RANGE2 * 1000000.0; // 1000x scanner range (25600 km), squared.
|
|
for (i = 0; i < station_count; i++)
|
|
{
|
|
StationEntity* thing = (StationEntity*)my_entities[i];
|
|
double range2 = distance2( position, thing->position);
|
|
if (range2 < nearest2)
|
|
{
|
|
station = (StationEntity *)thing;
|
|
nearest2 = range2;
|
|
}
|
|
}
|
|
for (i = 0; i < station_count; i++)
|
|
[my_entities[i] release]; // released
|
|
//
|
|
if (station)
|
|
{
|
|
primaryTarget = [station universal_id];
|
|
targetStation = primaryTarget;
|
|
}
|
|
}
|
|
|
|
- (PlanetEntity *) findNearestLargeBody
|
|
{
|
|
/*- selects the nearest planet it can find -*/
|
|
if (!universe)
|
|
return nil;
|
|
int ent_count = universe->n_entities;
|
|
Entity** uni_entities = universe->sortedEntities; // grab the public sorted list
|
|
Entity* my_entities[ent_count];
|
|
int i;
|
|
int planet_count = 0;
|
|
for (i = 0; i < ent_count; i++)
|
|
if (uni_entities[i]->isPlanet)
|
|
my_entities[planet_count++] = [uni_entities[i] retain]; // retained
|
|
//
|
|
PlanetEntity *the_planet = nil;
|
|
double nearest2 = SCANNER_MAX_RANGE2 * 10000000000.0; // 100 000x scanner range (2 560 000 km), squared.
|
|
for (i = 0; i < planet_count; i++)
|
|
{
|
|
PlanetEntity *thing = (PlanetEntity*)my_entities[i];
|
|
double range2 = distance2( position, thing->position);
|
|
if ((!the_planet)||(range2 < nearest2))
|
|
{
|
|
the_planet = (PlanetEntity *)thing;
|
|
nearest2 = range2;
|
|
}
|
|
}
|
|
for (i = 0; i < planet_count; i++)
|
|
[my_entities[i] release]; // released
|
|
//
|
|
return the_planet;
|
|
}
|
|
|
|
- (void) abortDocking
|
|
{
|
|
if (!universe)
|
|
return;
|
|
int ent_count = universe->n_entities;
|
|
Entity** uni_entities = universe->sortedEntities; // grab the public sorted list
|
|
int i;
|
|
for (i = 0; i < ent_count; i++)
|
|
if (uni_entities[i]->isStation)
|
|
[(StationEntity *)uni_entities[i] abortDockingForShip:self]; // action
|
|
}
|
|
|
|
- (void) broadcastThargoidDestroyed
|
|
{
|
|
/*-- Locates all tharglets in range and tells them you've gone --*/
|
|
if (!universe)
|
|
return;
|
|
int ent_count = universe->n_entities;
|
|
Entity** uni_entities = universe->sortedEntities; // grab the public sorted list
|
|
Entity* my_entities[ent_count];
|
|
int i;
|
|
int ship_count = 0;
|
|
for (i = 0; i < ent_count; i++)
|
|
if (uni_entities[i]->isShip)
|
|
my_entities[ship_count++] = [uni_entities[i] retain]; // retained
|
|
//
|
|
double d2;
|
|
double found_d2 = SCANNER_MAX_RANGE2;
|
|
for (i = 0; i < ship_count ; i++)
|
|
{
|
|
ShipEntity* ship = (ShipEntity *)my_entities[i];
|
|
d2 = distance2( position, ship->position);
|
|
if ((d2 < found_d2)&&([[ship roles] isEqual:@"tharglet"]))
|
|
[[ship getAI] message:@"THARGOID_DESTROYED"];
|
|
}
|
|
for (i = 0; i < ship_count; i++)
|
|
[my_entities[i] release]; // released
|
|
}
|
|
|
|
- (NSArray *) shipsInGroup:(int) ship_group_id
|
|
{
|
|
//-- Locates all the ships with this particular group id --//
|
|
NSMutableArray* result = [NSMutableArray arrayWithCapacity:20]; // is autoreleased
|
|
if (!universe)
|
|
return (NSArray *)result;
|
|
int ent_count = universe->n_entities;
|
|
Entity** uni_entities = universe->sortedEntities; // grab the public sorted list
|
|
Entity* my_entities[ent_count];
|
|
int i;
|
|
int ship_count = 0;
|
|
for (i = 0; i < ent_count; i++)
|
|
if (uni_entities[i]->isShip)
|
|
my_entities[ship_count++] = [uni_entities[i] retain]; // retained
|
|
//
|
|
for (i = 0; i < ship_count ; i++)
|
|
{
|
|
ShipEntity* ship = (ShipEntity *)my_entities[i];
|
|
if ([ship group_id] == ship_group_id)
|
|
[result addObject: ship];
|
|
}
|
|
for (i = 0; i < ship_count; i++)
|
|
[my_entities[i] release]; // released
|
|
return (NSArray *)result;
|
|
}
|
|
|
|
- (void) sendExpandedMessage:(NSString *) message_text toShip:(ShipEntity*) other_ship
|
|
{
|
|
if (!other_ship)
|
|
return;
|
|
Vector delta = other_ship->position;
|
|
delta.x -= position.x; delta.y -= position.y; delta.z -= position.z;
|
|
double d2 = delta.x*delta.x + delta.y*delta.y + delta.z*delta.z;
|
|
if (d2 > scanner_range * scanner_range)
|
|
return; // out of comms range
|
|
if (!other_ship)
|
|
return;
|
|
NSMutableString* localExpandedMessage = [NSMutableString stringWithString:message_text];
|
|
[localExpandedMessage replaceOccurrencesOfString:@"[self:name]"
|
|
withString:name
|
|
options:NSLiteralSearch range:NSMakeRange( 0, [localExpandedMessage length])];
|
|
[localExpandedMessage replaceOccurrencesOfString:@"[target:name]"
|
|
withString:[other_ship identFromShip: self]
|
|
options:NSLiteralSearch range:NSMakeRange( 0, [localExpandedMessage length])];
|
|
|
|
Random_Seed very_random_seed;
|
|
very_random_seed.a = rand() & 255;
|
|
very_random_seed.b = rand() & 255;
|
|
very_random_seed.c = rand() & 255;
|
|
very_random_seed.d = rand() & 255;
|
|
very_random_seed.e = rand() & 255;
|
|
very_random_seed.f = rand() & 255;
|
|
seed_RNG_only_for_planet_description(very_random_seed);
|
|
NSString* expandedMessage = [universe expandDescription:localExpandedMessage forSystem:[universe systemSeed]];
|
|
|
|
[self setCommsMessageColor];
|
|
[other_ship receiveCommsMessage:[NSString stringWithFormat:@"%@:\n %@", name, expandedMessage]];
|
|
if (other_ship->isPlayer)
|
|
message_time = 6.0;
|
|
[universe resetCommsLogColor];
|
|
}
|
|
|
|
- (void) broadcastMessage:(NSString *) message_text
|
|
{
|
|
/*-- Locates all the stations, bounty hunters and police ships in range and tells them your message --*/
|
|
NSString* expandedMessage = [NSString stringWithFormat:@"%@:\n %@", name, [universe expandDescription:message_text forSystem:[universe systemSeed]]];
|
|
|
|
if (!universe)
|
|
return;
|
|
int ent_count = universe->n_entities;
|
|
Entity** uni_entities = universe->sortedEntities; // grab the public sorted list
|
|
Entity* my_entities[ent_count];
|
|
int i;
|
|
int ship_count = 0;
|
|
for (i = 0; i < ent_count; i++)
|
|
if (uni_entities[i]->isShip)
|
|
my_entities[ship_count++] = [uni_entities[i] retain]; // retained
|
|
//
|
|
double d2;
|
|
double found_d2 = scanner_range * scanner_range;
|
|
found_target = NO_TARGET;
|
|
[self setCommsMessageColor];
|
|
for (i = 0; i < ship_count ; i++)
|
|
{
|
|
ShipEntity* ship = (ShipEntity *)my_entities[i];
|
|
d2 = distance2( position, ship->position);
|
|
if (d2 < found_d2)
|
|
{
|
|
[ship receiveCommsMessage: expandedMessage];
|
|
if (ship->isPlayer)
|
|
message_time = 6.0;
|
|
}
|
|
}
|
|
for (i = 0; i < ship_count; i++)
|
|
[my_entities[i] release]; // released
|
|
[universe resetCommsLogColor];
|
|
}
|
|
|
|
- (void) setCommsMessageColor
|
|
{
|
|
float hue = 0.0625 * (universal_id & 15);
|
|
[[universe comm_log_gui] setTextColor:[NSColor colorWithCalibratedHue:hue saturation:0.375 brightness:1.0 alpha:1.0]];
|
|
if (scan_class == CLASS_THARGOID)
|
|
[[universe comm_log_gui] setTextColor:[NSColor greenColor]];
|
|
if (scan_class == CLASS_POLICE)
|
|
[[universe comm_log_gui] setTextColor:[NSColor cyanColor]];
|
|
}
|
|
|
|
- (void) receiveCommsMessage:(NSString *) message_text
|
|
{
|
|
// ignore messages for now
|
|
}
|
|
|
|
- (BOOL) markForFines
|
|
{
|
|
if (being_fined)
|
|
return NO; // can't mark twice
|
|
being_fined = ([self legal_status] > 0);
|
|
return being_fined;
|
|
}
|
|
|
|
- (BOOL) isMining
|
|
{
|
|
return ((condition == CONDITION_ATTACK_MINING_TARGET)&&(forward_weapon_type == WEAPON_MINING_LASER));
|
|
}
|
|
|
|
- (void) setNumberOfMinedRocks:(int) value
|
|
{
|
|
if (![roles isEqual:@"asteroid"])
|
|
return;
|
|
likely_cargo = value;
|
|
}
|
|
|
|
- (void) interpretAIMessage:(NSString *)ms
|
|
{
|
|
if ([ms hasPrefix:AIMS_AGGRESSOR_SWITCHED_TARGET])
|
|
{
|
|
// if I'm under attack send a thank-you message to the rescuer
|
|
//
|
|
// NSArray* tokens = [ms componentsSeparatedByString:@" "];
|
|
NSArray* tokens = [Entity scanTokensFromString:ms];
|
|
int switcher_id = [(NSString*)[tokens objectAtIndex:1] intValue];
|
|
Entity* switcher = [universe entityForUniversalID:switcher_id];
|
|
int rescuer_id = [(NSString*)[tokens objectAtIndex:2] intValue];
|
|
Entity* rescuer = [universe entityForUniversalID:rescuer_id];
|
|
if ((switcher_id == primaryAggressor)&&(switcher_id == primaryTarget)&&(switcher)&&(rescuer)&&(rescuer->isShip)&&(thanked_ship_id != rescuer_id)&&(scan_class != CLASS_THARGOID))
|
|
{
|
|
if (scan_class == CLASS_POLICE)
|
|
[self sendExpandedMessage:@"[police-thanks-for-assist]" toShip:(ShipEntity*)rescuer];
|
|
else
|
|
[self sendExpandedMessage:@"[thanks-for-assist]" toShip:(ShipEntity*)rescuer];
|
|
thanked_ship_id = rescuer_id;
|
|
[(ShipEntity*)switcher setBounty:[(ShipEntity*)switcher getBounty] + 5 + (ranrot_rand() & 15)]; // reward
|
|
}
|
|
}
|
|
}
|
|
|
|
- (BoundingBox) findBoundingBoxRelativeTo:(Entity *)other InVectors:(Vector) _i :(Vector) _j :(Vector) _k
|
|
{
|
|
Vector pv, rv;
|
|
Vector opv = (other)? other->position : position;
|
|
Vector rpos = position;
|
|
rpos.x -= opv.x; rpos.y -= opv.y; rpos.z -= opv.z;
|
|
pv.x = rpos.x + v_right.x * vertices[0].x + v_up.x * vertices[0].y + v_forward.x * vertices[0].z;
|
|
pv.y = rpos.y + v_right.y * vertices[0].x + v_up.y * vertices[0].y + v_forward.y * vertices[0].z;
|
|
pv.z = rpos.z + v_right.z * vertices[0].x + v_up.z * vertices[0].y + v_forward.z * vertices[0].z;
|
|
rv.x = dot_product(_i,pv);
|
|
rv.y = dot_product(_j,pv);
|
|
rv.z = dot_product(_k,pv);
|
|
BoundingBox result;
|
|
bounding_box_reset_to_vector(&result,rv);
|
|
int i;
|
|
for (i = 1; i < n_vertices; i++)
|
|
{
|
|
pv.x = rpos.x + v_right.x * vertices[i].x + v_up.x * vertices[i].y + v_forward.x * vertices[i].z;
|
|
pv.y = rpos.y + v_right.y * vertices[i].x + v_up.y * vertices[i].y + v_forward.y * vertices[i].z;
|
|
pv.z = rpos.z + v_right.z * vertices[i].x + v_up.z * vertices[i].y + v_forward.z * vertices[i].z;
|
|
rv.x = dot_product(_i,pv);
|
|
rv.y = dot_product(_j,pv);
|
|
rv.z = dot_product(_k,pv);
|
|
bounding_box_add_vector(&result,rv);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (BoundingBox) findBoundingBoxRelativeToPosition:(Vector)opv InVectors:(Vector) _i :(Vector) _j :(Vector) _k
|
|
{
|
|
Vector pv, rv;
|
|
Vector rpos = position;
|
|
rpos.x -= opv.x; rpos.y -= opv.y; rpos.z -= opv.z;
|
|
rv.x = dot_product(_i,rpos);
|
|
rv.y = dot_product(_j,rpos);
|
|
rv.z = dot_product(_k,rpos);
|
|
pv.x = rpos.x + v_right.x * vertices[0].x + v_up.x * vertices[0].y + v_forward.x * vertices[0].z;
|
|
pv.y = rpos.y + v_right.y * vertices[0].x + v_up.y * vertices[0].y + v_forward.y * vertices[0].z;
|
|
pv.z = rpos.z + v_right.z * vertices[0].x + v_up.z * vertices[0].y + v_forward.z * vertices[0].z;
|
|
BoundingBox result;
|
|
bounding_box_reset_to_vector(&result,rv);
|
|
int i;
|
|
for (i = 1; i < n_vertices; i++)
|
|
{
|
|
pv.x = rpos.x + v_right.x * vertices[i].x + v_up.x * vertices[i].y + v_forward.x * vertices[i].z;
|
|
pv.y = rpos.y + v_right.y * vertices[i].x + v_up.y * vertices[i].y + v_forward.y * vertices[i].z;
|
|
pv.z = rpos.z + v_right.z * vertices[i].x + v_up.z * vertices[i].y + v_forward.z * vertices[i].z;
|
|
rv.x = dot_product(_i,pv);
|
|
rv.y = dot_product(_j,pv);
|
|
rv.z = dot_product(_k,pv);
|
|
bounding_box_add_vector(&result,rv);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void) spawn:(NSString *)roles_number
|
|
{
|
|
NSArray* tokens = [Entity scanTokensFromString:roles_number];
|
|
NSString* roleString = nil;
|
|
NSString* numberString = nil;
|
|
|
|
if ([tokens count] != 2)
|
|
{
|
|
NSLog(@"***** CANNOT SPAWN: '%@'",roles_number);
|
|
return;
|
|
}
|
|
|
|
roleString = (NSString *)[tokens objectAtIndex:0];
|
|
numberString = (NSString *)[tokens objectAtIndex:1];
|
|
|
|
int number = [numberString intValue];
|
|
|
|
if (debug)
|
|
NSLog(@"DEBUG ..... Going to spawn %d x '%@' near %@ %d", number, roleString, name, universal_id);
|
|
|
|
while (number--)
|
|
[universe spawnShipWithRole:roleString near:self];
|
|
}
|
|
|
|
- (int) checkShipsInVicinityForWitchJumpExit
|
|
{
|
|
// checks if there are any large masses close by
|
|
// since we want to place the space station at least 10km away
|
|
// the formula we'll use is K x m / d2 < 1.0
|
|
// (m = mass, d2 = distance squared)
|
|
// coriolis station is mass 1,000,000,000
|
|
// 10km is 10,000m,
|
|
// 10km squared is 100,000,000
|
|
// therefore K is 0.10
|
|
|
|
int result = NO_TARGET;
|
|
|
|
GLfloat k = 0.1;
|
|
|
|
int ent_count = universe->n_entities;
|
|
Entity** uni_entities = universe->sortedEntities; // grab the public sorted list
|
|
ShipEntity* my_entities[ent_count];
|
|
int i;
|
|
|
|
int ship_count = 0;
|
|
for (i = 0; i < ent_count; i++)
|
|
if ((uni_entities[i]->isShip)&&(uni_entities[i] != self))
|
|
my_entities[ship_count++] = (ShipEntity*)[uni_entities[i] retain]; // retained
|
|
//
|
|
for (i = 0; (i < ship_count)&&(result == NO_TARGET) ; i++)
|
|
{
|
|
ShipEntity* ship = my_entities[i];
|
|
Vector delta = ship->position;
|
|
delta.x -= position.x; delta.y -= position.y; delta.z -= position.z;
|
|
if ((delta.x < 10000.0)&&(delta.y < 10000.0)&&(delta.z < 10000.0)&&( k * [ship mass] / magnitude2(delta) > 1.0))
|
|
result = [ship universal_id];
|
|
}
|
|
for (i = 0; i < ship_count; i++)
|
|
[my_entities[i] release]; // released
|
|
|
|
return result;
|
|
}
|
|
|
|
@end
|