/* Danger from the Deep - Open source submarine simulation Copyright (C) 2003-2006 Thorsten Jordan, Luis Barrancos and others. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // torpedoes // subsim (C)+(W) Thorsten Jordan. SEE LICENSE #include "torpedo.h" #include "global_data.h" #include "model.h" #include "system.h" #include "game.h" #include "sensors.h" #include "submarine.h" using std::string; using std::vector; /** * This is just a default fuse constructor so that the * code in the fuse(s) section of the torpedo constructor * can actually work. Quick and dirty hack. */ torpedo::fuse::fuse() { model = Pi1; failure_probability = 0.3f; type = IMPACT; } /** Tokenize a string. * This function will accept a string and add tokens from it to * your vector (which needn't even be empty) separated by * arbitrary delimiters. While any character in the delimiters * string will be treated as a delimiter, this function will not * treat different delimiters differently. The tokens vector * ends up as a simple, sequential list of tokens regardless of * how exactly the delimiters occur. * * Usage:
* string sentence("All work and no play makes Jack a dull boy."); //
* vector words; //
* tokenize(sentence, words); //
* string typelist("TZ1,Pi1|TZ3,Pi3"); //
* vector types; //
* tokenize(typelist, types, ",|"); * * @param str arbitrary string which is to be parsed for tokens. * @param tokens vector which is to contain the tokens. Look for your result here. * @param delimiters characters which this function should consider separate tokens. */ void tokenize( const string& str, vector& tokens, const string& delimiters = " ") { // Skip any delimiters at the beginning string::size_type last_position = str.find_first_not_of(delimiters, 0); // Find the beginning of our first token string::size_type position = str.find_first_of(delimiters, last_position); while (string::npos != position || string::npos != last_position) { // Hey! A token! tokens.push_back(str.substr(last_position, position - last_position)); // Skip all delimiters last_position = str.find_first_not_of(delimiters, position); // Find the beginning of our next token position = str.find_first_of(delimiters, last_position); } } torpedo::fuse::fuse(const xml_elem& parent, date equipdate) { string modelstr = parent.attr("type"); // fixme: TI_FaTI.xml uses "TZ3,Pi3" as the fuse type. Implement random fuse choice? if (modelstr.find(",") != string::npos) { vector types; tokenize(modelstr, types, ","); unsigned r = rnd(types.size()); modelstr = types.at(r); } if (modelstr == "Pi1") model = Pi1; else if (modelstr == "Pi2") model = Pi2; else if (modelstr == "Pi3") model = Pi3; else if (modelstr == "Pi4a") model = Pi4a; else if (modelstr == "Pi4b") model = Pi4b; else if (modelstr == "Pi4c") model = Pi4c; else if (modelstr == "Pi6") model = Pi6; else if (modelstr == "TZ3") model = TZ3; else if (modelstr == "TZ5") model = TZ5; else if (modelstr == "TZ6") model = TZ6; else throw xml_error(string("illegal fuse model given: ") + modelstr, parent.doc_name()); // fixme: check here if that is correct!!! Pi4 intertial? switch (model) { case Pi1: failure_probability = 0.3f; // fixme depends on date! type = IMPACT; break; case Pi2: failure_probability = 0.2f; // fixme depends on date! type = IMPACT; break; case Pi3: failure_probability = 0.1f; // fixme depends on date! type = IMPACT; break; case Pi4a: failure_probability = 0.1f; // fixme depends on date! type = IMPACT; break; case Pi4b: failure_probability = 0.1f; // fixme depends on date! type = IMPACT; break; case Pi4c: failure_probability = 0.1f; // fixme depends on date! type = IMPACT; break; case Pi6: failure_probability = 0.02f; // fixme depends on date! type = INERTIAL; break; case TZ3: failure_probability = 0.5f; // fixme depends on date! type = INFLUENCE; break; case TZ5: failure_probability = 0.2f; // fixme depends on date! type = INFLUENCE; break; case TZ6: failure_probability = 0.1f; // fixme depends on date! type = INFLUENCE; break; } } bool torpedo::fuse::handle_impact(angle impactangle) const { // compute failure depending on angle, type and probability if (rnd() < failure_probability) return false; return true; } torpedo::torpedo(game& gm, const xml_elem& parent) : ship(gm, parent), primaryrange(1600), secondaryrange(800), initialturn_left(true), turnangle(180), torpspeed(0), // 0-2 slow-fast, only for G7a torps rundepth(3), temperature(15), // degrees C probability_of_rundepth_failure(0.2), // basically high before mid 1942, fixme run_length(0) { date dt = gm.get_equipment_date(); // ------------ availability, check this first xml_elem eavailability = parent.child("availability"); date availdt = date(eavailability.attr("date")); if (dt < availdt) throw xml_error("torpedo type not available at this date!", parent.doc_name()); set_skin_layout(model::default_layout); mass = parent.child("weight").attrf(); untertrieb = parent.child("untertrieb").attrf(); xml_elem ewarhead = parent.child("warhead"); warhead_weight = ewarhead.attrf("weight"); string charge = ewarhead.attr("charge"); if (charge == "Ka") { warhead_type = Ka; } else if (charge == "Kb") { warhead_type = Kb; } else if (charge == "Kc") { warhead_type = Kc; } else if (charge == "Kd") { warhead_type = Kd; } else if (charge == "Ke") { warhead_type = Ke; } else if (charge == "Kf") { warhead_type = Kf; } else { // fixme: charges are atm numbers, should be replaced later... warhead_type = Ka; //throw xml_error(string("unknown charge type ")+charge, parent.doc_name()); } // ------------- arming xml_elem earming = parent.child("arming"); arming_distance = -1; double latest_arming_distance = -1; // just in case today's date is after date latest = date("1/1/1"); // the latest available period specified for (xml_elem::iterator it = earming.iterate("period"); !it.end(); it.next()) { date from = it.elem().attr("from"); date until = it.elem().attr("until"); if (until >= latest) { latest = until; latest_arming_distance = it.elem().attrf("runlength"); } if (from <= dt && dt <= until) { arming_distance = it.elem().attrf("runlength"); break; } } if (arming_distance < 0) { if (dt >= latest) arming_distance = latest_arming_distance; else throw xml_error("no period subtags of arming that match current equipment date!", parent.doc_name()); } // ---------- fuse(s) xml_elem efuse = parent.child("fuse"); fuse latest_fuse; latest = date("1/1/1"); for (xml_elem::iterator it = efuse.iterate("period"); !it.end(); it.next()) { date from = it.elem().attr("from"); date until = it.elem().attr("until"); if (until >= latest) { latest = until; latest_fuse = fuse(it.elem(), dt); } if (from <= dt && dt <= until) { fuses.push_back(fuse(it.elem(), dt)); } } if (fuses.empty()) { if (dt >= latest) fuses.push_back(latest_fuse); else throw xml_error("no period subtags of fuse that match current equipment date!", parent.doc_name()); } // ----------- motion / steering device xml_elem emotion = parent.child("motion"); unsigned hasfat = emotion.attru("FAT"); unsigned haslut = emotion.attru("LUT"); if (hasfat > 0) { if (haslut > 0) throw xml_error("steering device must be EITHER LuT OR FaT!", parent.doc_name()); steering_device = FaT; } else if (haslut > 0) { steering_device = (haslut == 1) ? LuTI : LuTII; } else { steering_device = STRAIGHT; } // ------------ sensors, fixme // ------------ ranges xml_elem eranges = parent.child("ranges"); for (xml_elem::iterator it = eranges.iterate("range"); !it.end(); it.next()) { if (it.elem().attrb("preheated")) { range_preheated = it.elem().attrf("distance"); speed_preheated = kts2ms(it.elem().attrf("speed")); } else { range_normal = it.elem().attrf("distance"); speed_normal = kts2ms(it.elem().attrf("speed")); } } // ------------ power, fixme - not needed yet xml_elem epower = parent.child("power"); // ------------ set ship turning values, fixme: read from files, more a hack... max_rudder_angle = 40; max_rudder_turn_speed = 200;//20; // with smaller values torpedo course oscillates. damping too high?! steering to crude?! fixme // set turn rate here. With 0.6 a torpedo takes roughly 10 seconds to turn 90 degrees. // With that value the torpedo turn radius is ~98m. Maybe a bit too much. turn_rate = 0.6; } #if 0 // fixme: to fire a torp, let the TDC set the values while torpedo is in tube (stored!) //and then spawn it in game, so that torp::simulate() is called... torpedo::torpedo(game& gm_, sea_object* parent, torpedo::types type_, bool usebowtubes, angle headto_, const tubesetup& stp) : ship(gm_), temperature(20.0 /* fixme*/ ) { type = type_; primaryrange = (stp.primaryrange <= 16) ? 1600+stp.primaryrange*100 : 1600; secondaryrange = (stp.secondaryrange & 1) ? 1600 : 800; initialturn = stp.initialturn; turnangle = stp.turnangle; torpspeed = stp.torpspeed; rundepth = stp.rundepth; position = parent->get_pos(); heading = parent->get_heading(); if (!usebowtubes) heading += angle(180); vector2 dp = heading.direction() * (parent->get_length()/2 + 3.5); position.x += dp.x; position.y += dp.y; head_to_ang(headto_, !heading.is_cw_nearer(headto_)); // fixme: simulate variable speeds of T1? // fixme: simulate effect of magnetic influence fuse (much greater damage) turn_rate = 0.6f; // most submarine simulations seem to ignore this // launching a torpedo will cause it to run in target direction // immidiately instead of turning there from the sub's heading // fixme historic values?? size3d = vector3f(0.533, 7, 0.533); // diameter 53.3cm (21inch), length ~ 7m //fixme: retrieve from model file referenced in xml file // preheated torpedoes hat longer ranges... this needs to be simulated, too // look at: http://www.uboat.net/technical/torpedoes.htm // and: http://www.uboat.net/history/torpedo_crisis.htm // and: http://www.u-boot-greywolf.de/utorpedo.htm run_length = 0; max_speed_forward = velocity.y = get_speed_by_type(type_); max_speed_reverse = 0; switch (type_) { case T1: max_run_length = 14000; influencefuse = false; break; case T2: max_run_length = 5000; influencefuse = false; break; case T3: max_run_length = 5000; influencefuse = true; break; case T3a: max_run_length = 7500; influencefuse = true; break; case T4: max_run_length = 7500; // fixme: Falke sensor set_sensor ( passive_sonar_system, new passive_sonar_sensor ( passive_sonar_sensor::passive_sonar_type_tt_t5 ) ); influencefuse = true; break; case T5: max_run_length = 5700; set_sensor ( passive_sonar_system, new passive_sonar_sensor ( passive_sonar_sensor::passive_sonar_type_tt_t5 ) ); influencefuse = true; break; case T11: max_run_length = 5700; set_sensor ( passive_sonar_system, new passive_sonar_sensor ( passive_sonar_sensor::passive_sonar_type_tt_t11 ) ); influencefuse = true; break; case T1FAT: max_run_length = 14000; influencefuse = false; break; case T3FAT: max_run_length = 7500; influencefuse = true; break; case T6LUT: max_run_length = 7500; influencefuse = true; break; }; throttle = aheadflank; // set ship turning values max_rudder_angle = 40; max_rudder_turn_speed = 200;//20; // with smaller values torpedo course oscillates. damping too high?! steering to crude?! fixme max_angular_velocity = 18; // ~ 5 seconds for 90 degree turn (50m radius circle with 30 knots) turn_rate = 1; // ? is this needed somewhere?! max_accel_forward = 1; sys().add_console("torpedo created"); } #endif void torpedo::load(const xml_elem& parent) { sea_object::load(parent); xml_elem stg = parent.child("settings"); primaryrange = stg.attru("primaryrange"); secondaryrange = stg.attru("secondaryrange"); initialturn_left = stg.attrb("initialturn_left"); turnangle = angle(stg.attrf("turnangle")); torpspeed = stg.attru("torpspeed"); rundepth = stg.attrf("rundepth"); temperature = parent.child("temperature").attrf(); probability_of_rundepth_failure = parent.child("probability_of_rundepth_failure").attrf(); run_length = parent.child("run_length").attrf(); } void torpedo::save(xml_elem& parent) const { sea_object::save(parent); xml_elem stg = parent.add_child("settings"); stg.set_attr(primaryrange, "primaryrange"); stg.set_attr(secondaryrange, "secondaryrange"); stg.set_attr(initialturn_left, "initialturn_left"); stg.set_attr(turnangle.value(), "turnangle"); stg.set_attr(torpspeed, "torpspeed"); stg.set_attr(rundepth, "rundepth"); parent.add_child("temperature").set_attr(temperature); parent.add_child("probability_of_rundepth_failure").set_attr(probability_of_rundepth_failure); parent.add_child("run_length").set_attr(run_length); } void torpedo::set_steering_values(unsigned primrg, unsigned secrg, bool initurnleft, angle turnang, unsigned tspeedsel, double rdepth) { primaryrange = primrg; // fixme: multiply? ... secondaryrange = secrg; initialturn_left = initurnleft; turnangle = turnang; torpspeed = tspeedsel; rundepth = rdepth; } void torpedo::simulate(double delta_time) { /* cout << "torpedo " << this << " heading " << heading.value() << " should head to " << head_to.value() << " turn speed " << turn_velocity << "\n"; cout << " position " << position << " orientation " << orientation << " run_length " << run_length << "\n"; cout << " velo " << velocity << " turnvelo " << turn_velocity << " global vel " << global_velocity << "\n"; cout << " acceleration " << acceleration << " delta t "<< delta_time << "\n"; */ redetect_time = 1.0; ship::simulate(delta_time); double old_run_length = run_length; run_length += get_speed() * delta_time; if (run_length > get_range()) { // later: simulate slow sinking to the ground... kill(); return; } // Torpedo starts to search for a target when the minimum save // distance for the warhead is passed. if (!sensors.empty() && run_length >= sensor_activation_distance) { ship* target = gm.sonar_acoustical_torpedo_target ( this ); if (target) { angle targetang(target->get_engine_noise_source() - get_pos().xy()); bool turnright = get_heading().is_cw_nearer(targetang); head_to_ang(targetang, !turnright); } } if (steering_device != STRAIGHT) { unsigned old_phase = unsigned(floor((old_run_length < primaryrange) ? old_run_length/primaryrange : 1.0+(old_run_length - primaryrange)/secondaryrange)); unsigned phase = unsigned(floor((run_length < primaryrange) ? run_length/primaryrange : 1.0+(run_length - primaryrange)/secondaryrange)); if (phase > old_phase) { // phase change. if (phase == 1) { // first turn. Differences between LuT and FaT, // FaT always 180 degrees, LuT variable. Angle is stored, use that angle turn = initialturn_left ? -turnangle : turnangle; head_to_ang(get_heading() + turn, initialturn_left); } else { // further turns, always 180 degrees. bool turn_is_left = initialturn_left ? ((phase & 1) != 0) : ((phase & 1) == 0); angle turn = turn_is_left ? -180 : 180; head_to_ang(get_heading() + turn, initialturn_left); } } } // check for collisions with other subs or ships if (run_length > 10) { // avoid collision with parent after initial creation bool runlengthfailure = (run_length < arming_distance); bool failure = false; // calculate additional probability of torpedo failure //fixme: depends on fuse. compute this once and not for every test... if (gm.check_torpedo_hit(this, runlengthfailure, failure)) kill(); } } void torpedo::launch(const vector3& launchpos, angle parenthdg) { position = launchpos; orientation = quaternion::rot(-parenthdg.value(), 0, 0, 1); heading = parenthdg; max_speed_forward = get_speed(); max_angular_velocity = max_speed_forward * turn_rate; // velocity is local, object moves only forward. velocity = vector3(0, max_speed_forward, 0); run_length = 0; } #if 0 //fixme beim laden mitbenutzen void torpedo::create_sensor_array ( types t ) { switch ( t ) { case T4: // fixme set_sensor ( passive_sonar_system, new passive_sonar_sensor ( passive_sonar_sensor::passive_sonar_type_tt_t5 ) ); break; case T5: set_sensor ( passive_sonar_system, new passive_sonar_sensor ( passive_sonar_sensor::passive_sonar_type_tt_t5 ) ); break; case T11: set_sensor ( passive_sonar_system, new passive_sonar_sensor ( passive_sonar_sensor::passive_sonar_type_tt_t11 ) ); break; } } #endif unsigned torpedo::get_hit_points () const // awful, useless, replace, fixme { return 100;//G7A_HITPOINTS;//fixme } double torpedo::get_range() const { // fixme: TI independent on temperature, but depends on torpspeed selector! double s = 0; if (temperature > 15) { if (temperature > 30) s = 1; else s = (temperature - 15)/15; } return range_normal * (1-s) + range_preheated * s; } double torpedo::get_speed() const { // fixme: TI independent on temperature, but depends on torpspeed selector! double s = 0; if (temperature > 15) { if (temperature > 30) s = 1; else s = (temperature - 15)/15; } return speed_normal * (1-s) + speed_preheated * s; }