// // 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:@"", 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