Autopilot nudge

Need help testing contributed art or code or having trouble getting your newest additions into game compatible format? Confused by changes to data formats? Reading through source and wondering what the developers were thinking when they wrote something? Need "how-to" style guidance for messing with VS internals? This is probably the right forum.
Post Reply
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Autopilot nudge

Post by chuck_starchaser »

Need a little nudge to get started. I have an autopilot for people to try, already coded, but I don't know how to plug it into the engine. The way it will work is like this: You use T to go through possible destinations. Once you have the one you want targeted, you press Ctrl-A to transfer it to the autopilot's memory. Then you press A to toggle the autopilot on or off. Yes, won't need A for Spec, as the autopilot will take control of spec.

The autopilot will accelerate continuously towards the target, and about mid-trip it will smoothly turn around to apply full thrust in the opposite direction, to slow down. Towards the end of the trip it will again change orientation as needed to arrive within 1000 kilometers of the target, say, with matched or slightly higher velocity, then will turn itself off.
Because of the long acceleration --half the trip--, it will reach *very* high velocities, so I need to also find out where the non-combat speed cap is applied, so as to disable it.
Spec multiplier will be controlled by the autopilot, and it will be a function of basic (pre-spec) speed, but subject to gravitaty and stuff. I will compute gravity myself, so, I need to access the system planets, their positions and masses, and to output a value directly to the spec multiplier.
The autopilot will allow you to intercept other ships, not just bases. On approaching another ship, spec multiplier will be synchronized first, then spec lowered for both ships or groups of ships, so I also need to know how to get the speed AND spec of any ship I target.
Conversely, a pirate who has the autopilot can use it to intercept you, and as he/she approaches you, spec has to come down, so I need some kind of callback, or list of ships targeting the player, and to be able to get their position, speed and spec, and to be able to control their spec. Unless I add the autopilot to their AI or something along the lines.

I have most of the code written already; physics AND spec control. I just need to know how to hook it up and test it. For instance, all of the code changes should be bypassable by an environmental flag, presumably in vegastrike.config. Or, what I would like even better is to have a new item you can buy: Autopilot I. Before you buy it, everything works the way it does presently. But if you have Autopilot I in your ship, it overrides the A binding for spec and interprets A as autopilot on/off toggle. If you don't like it, you can sell it and all goes back to normal.

Here's a list of inputs and outputs I need to tap into; if someone could be so gracious to tell me where the variables, are or what functions to call to access them:

* The A key: I need to disconnect it from spec activation toggling, and bind it to autopilot on/off toggle.
* Ctrl-A: For transfer of current target to autopilot target memory.
* Shift-Ctrl-A: For transfer of autopilot target to current target (In case you forgot where you were going ;))
* The player's hitting A or Tab or any keyboard navigation controls should suspend autopilot if currently on. A should turn it back on if it's off.
* Hitting A when no target has been saved to autopilot memory should display a message: "Autopilot: NO TARGET SELECTED. Use Ctrl-A to assign current target to autopilot memory."
* The [`] key; I need to disable it in auto mode, and to set the mode to non-compensated, as the autopilot will control the ship itself and needs no interference from the nav computer.

INPUTS (other than keyboard):
* Current target
* The ship's current position relative to the star.
* The ship's current speed vector relative to the star.
* The ship's current rotational orientation.
* The ship's current rotational speed.
* The autopilot target's current position relative to the star.
* The autopilot target's current speed vector relative to the star.
* The autopilot target's current rotational orientation.
* The autopilot target's current rotational speed.
* The current spec multiplier of any ship I target.
* The current position, speed and spec of any ship targeting the player.

OUTPUTS:
* Current target (Shft-Ctrl-A writes to it)
* The player's ship's thrusters and spec multiplier.
* The ship's current position relative to the star. (Yeah, I think I might do my own physics during autopilot travel).
* The ship's current speed vector relative to the star.
* The ship's current rotational orientation.
* The ship's current rotational speed.
* Set speed and combat-speed-cap mode, to leave them in a friendly state when autopilot turns itself off.

And I'm not sure how I give autopilot to npc's, and how to mod their AI to use it... And I need to change their spawning also: I want to spawn them well in advance, perhaps as soon as you launch from a base, and if they see you and want to intercept you, they'll use their autopilots. But that comes after. First is to get the player's autopilot to work.

TIA

Here's my present code, haven't tried to compile yet; take as poetry...

Code: Select all

//assume these are globals, for simplicity:

//positions and velocities: They will function equally regardless of the
//frame of reference chosen, as long as it is non-accelerating and non-
//-rotating. The standard, local star coord system is a perfect choice.
vector3d     my_pos;
vector3d     dest_pos;
vector3d     my_velocity;
vector3d     dest_velocity;

//Our auto-pilot routine outputs a desired ship orientation, or "nose
//vector". This is sent to the maneuvering computer, which will operate
//the maneuvering jets to achieve that orientation. However this may
//take a short time to achieve, so the "actual nose vector" follows the
//(desired) nose vector, but isn't necessarily the same vector.
//Thus "nose_vector" is the autopilot's main output. Other autopilot
//output variables are "main_thruster-on" and "autopilot_on". Actually,
//autopilot_on is i/o, set by the client; reset by autopilot().
vector3d     nose_vector;
vector3d     actual_nose_vector;

//The ship will use the "main thrusters" (back-facing thrusters) both
//for accelerating and deccelerating. This implies 2 things: Ship will
//turn-around at about mid-flight and face backwards to deccelerate.
//And the power of the other thrusters can be made much less, for all
//ships, which will better match most models' larger main thrusters.
//NOTE: Another choice would be to modify the ships to add retro rockets
//of similar size to the main thrusters, but I think the first solution
//makes more sense, after all, as making equal thrusters pointing in all
//directions would amount to a duplication, or a multiplication, of
//these (probably complex and expensive) devices.
const double max_accel; //maximum *forward* acceleration for one's ship
boolean      in_autopilot;  //set/reset by client; reset by autopilot()
boolean      main_thruster_on; //another autopilot() output variable

void autopilot(float time_delta) //to be called periodically
{
  if( ! in_autopilot ) return;
  vector3d linear_path = dest_pos - my_pos;
  double distance = linear_path.length();
  if( distance  < 90.0 /*kilometers*/ )
  {
    issue_a_final_ASAP();
    in_autopilot = false;
    return;
  }
  vector3d normalized_path = linear_path/distance;
  if( distance < 100.0 /*kilometers*/ )
  {
    //almost done, just turn the engines off and point at dest
    main_thruster_on = false;
    //if pointing away from destination, point at it
    if( dot( actual_nose_vector, normalized_path ) < 0.9 )
    {
        nose_vector = normalized_path;
    }
    else //otherwise return to manual mode
    {
      issue_a_final_ASAP();
      in_autopilot = false;
    }
    return;
  }
  vector3d approach_vector = my_velocity - dest_velocity;
  //Ideal velocity:
  //The ideal velocity is the maximum velocity we can barely brake
  //on time not to hit our destination; which is equivalent to the
  //speed we'd acquire if accelerating back from the destination.
  //From a previous post: we deduced accel = 0.5 * v^2 / d
  //If that's right, then v = sqrt( 2 * accel * d )
  double ideal_velocity =  sqrt( 2.0 * max_accel * distance );
  vector3d ideal_approach_vector =
    normalized_path * ideal_velocity;
  //but we want our final speed vector to match the target, so...
  ideal_approach_vector += dest_velocity;
  //now we compute velocity vector correction needed
  vector3d correction_vector = ideal_approach_vector - my_velocity;
  //99% of the time we need the main thruster on, but check, just
  //in case.. and use a bit of hystheresis..
  double correction_magnitude = correction_vector.length();
  if( main_truster_on )
    if( correction_magnitude < 0.2 * max_accel * time_delta )
      main_thruster_on = false;
  if( ! main_truster_on )
    if( correction_magnitude > 0.8 * max_accel * time_delta )
      main_thruster_on = true;
  //maybe control the throttle too?
  if( main_thruster_on &&
    correction_magnitude < max_accel * time_delta )
      throttle_0_to_1 =
        correction_magnitude / ( max_accel * time_delta );
  //finally, indicate to the ship which direction it should point
  if( main_thruster_on ) //important! avoids div by zero ;-)
    nose_vector = correction_vector / correction_magnitude;
}

//The way SPEC works is we have a linearly varying quantity, which goes
//up or down trapezoidally with time. This is not the spec multiplier
//itself, but rather the exponent we raise a base to, to obtain the
//final spec multiplier. And it is only a cap: There are other factors
//that may lower spec. Its main purpose is to limit the rate of growth
//and shrinkage of spec numbers after the ship leaves one place, and
//before it arrives to another.
//The first idea was to make it a function of time or distance from
//departure/arrival or source/destination; but this proved complex to
//to compute. The current implementation simply makes it a function of
//pre-spec speed (my_velocity).
//The other factors affecting spec cap are gravity, proximity of other
//ships targeting you, and an absolute max_linear_spec cap.
//For smoothness, rather than picking the lowest of these caps, we
//use the formula for resistance of resistors in parallel, which yields
//lower resistance than the resistor with the lowest resistance. The
//formula is R = 1 / ( 1/R1 + 1/R2 + 1/R3 + ... + 1/Rn )
//The SPEC of ships flying together as a wing or convoy is synchronized
//by their computers. If a hostile ship targeting you comes within
//radar range the specs are synchronized to the lower of the two, and
//lowered down to non-spec as the ships approach some fraction of radar
//range, to be determined.
boolean      spec_on;          //probably under control of autopilot()
const double specmax_abs_cap = TBD; //=log(max_spec_multiplier)
double       specmax_gravity;
double       specmax_speed;
double       linear_spec_var;
double       specmul_bogie;
double       spec_mul;
const double spec_speed_ratio = TBD; //max_linear_spec/1000km/sec?
const double spec_grav_influence = TBD; //tweak experimentally
const double FORRBDSZ = TBD; //"Fraction Of Radar Range Bogie (locking
//us) Downs SPEC to Zero"; --a number between 0 and 1, try 0.25
//Actually, at the above fraction of radar range, linear spec comes
//down to half (mul spec down to square root), but it falls to zero
//very fast as the range gets shorter (by 4th power of the distance)
std::vector<planet> planets;

void update_spec
(
  float time_delta,
  bogie *nearest_bogie = NULL
)
{
  if( ! spec_on )
  {
    linear_spec_var = 0.0;
    spec_mul = 1.0;
  }
  else
  {
    //evaluate net gravity's magnitude
    vector3d gravity = vector3d(0.0,0.0,0.0);
    planets.iterator_t i;
    for( i = planets.begin(); i != planets.end(); ++i )
    {
      vector pull = i->pos() - my_pos;
      double distance = pull.length();
      pull /= distance;
      pull /= distance;
      pull /= distance;
      pull *= i->mass;
      gravity += pull;
    }
    double net_gravity_magnitude = gravity.length();
    if( net_gravity_magnitude < 0.0000000000001 )
      net_gravity_magnitude = 0.0000000000001;
    //max spec as per gravity
    specmax_gravity = spec_grav_influence / net_gravity_magnitude;
    //max spec as per velocity
    vector3d rel_dest_velocity = my_velocity - dest_velocity;
    double rel_dest_speed = rel_dest_velocity.length();
    rel_dest_speed -= min_speed_for_spec;
    if( rel_dest_speed < 0.0 )
    {
      linear_spec_var = 0.0;
    }
    else
    {
      specmax_speed = spec_speed_ratio * rel_dest_velocity.length();
      //overall max spec (use parallel resistance formula as a substitute
      //for lowest() that yields a continuous function...
      double temp = 1.0 /
      ( (1.0/specmax_abs_cap) + (1.0/specmax_gravity) + (1.0/specmax_speed) );
      //add just a slight bit of low-pass filtering...
      linear_spec_var -= ( time_delta * linear_spec_var );
      linear_spec_var += ( time_delta * temp );
    }
    //finally, a hostile locking us within radar range lowers spec directly
    if( nearest_bogie && nearest_bogie.distance() < radar_range )
    {
      //get a factor that maps from 1 to 0 as range moves from max radar-
      //-range to zero
      double factor = nearest_bogie.distance()/radar_range;
      //turn this factor into a sigmoid, for smoothness. Note that at
      //FORRBDSZ * radar_range factor will be one after next line
      factor /= FORRBDSZ;
      //raise factor to the 4th power.
      factor *= factor;
      factor *= factor;
      //now smooth-out the long range part of the curve, quasi asymptotic to 1
      factor = 1.0 / ( 1.0 + (1.0/factor) );
      //and scale the spec multiplier by this factor
      linear_spec_var *= factor;
      synchronize_spec_with( nearest_bogie ); //lower of the two.
    }
    //finally, convert linear spec to exponential multiplier of speed
    spec_mul = exp( linear_spec_var );
  }
}

void update_physics(float time_delta)
{
  //As spec numbers increase, mass decreases. The decrease in mass
  //multiplies our speed to conserve momentum, and also multiplies our
  //acceleration, for a given thrust; the net effect being quivalent to
  //time-compression...
  //So, first we compute physics normally, ignoring SPEC
  vector3d accel;
  if( main_thrusters_on )
  {
    accel = max_accel * actual_nose_vector;
  }
  else
  {
    accel = vector3d(0.0,0.0,0.0);
  }
  my_velocity += ( accel * time_delta );
  //and now we SPEC the velocity and update position
  vector3d post_spec_velocity = spec_mul * my_velocity;
  my_pos += ( post_spec_velocity * time_delta );
}

void ship_update(long current_time) //time since boot, milliseconds
{
  //compute time delta
  static long previous_time = system_time(); //ms since boot
  //compute time-delta in seconds (time since last call)
  float time_delta = 0.001f * (current_time - previous_time);
  previous_time = current_time;
  if( time_delta <= 0.002f ) return;
  if( time_delta > 0.5f ) time_delta = 0.5f;
  //toggle autopilot state
  if( autopilot_button_pressed() )
    autopilot ^= true;
  //execute updates
  if( in_autopilot ) autopilot( time_delta );
  else manual_pilot( time_delta );
  update_spec( time_delta, &nearest_bogie );
  update_physics( time_delta );
}
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

I wrote:* Set speed and combat-speed-cap mode, to leave them in a friendly state when autopilot turns itself off.
Looks like the function is

Code: Select all

ShipCommands::setkps(const char *in);
or...

Code: Select all

UniverseUtil::getPlayer()->GetComputerData().set_speed = float(newspeed);
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

SUCCESS! I think I've found some answers... In file unit_generic.h...

Code: Select all

/***************************************************************************************/
/**** PHYSICS STUFF                                                                 ****/
/***************************************************************************************/

private:
  void RechargeEnergy();
protected:
  virtual float ExplosionRadius();
public:
  virtual void SetPlanetHackTransformation (Transformation *&ct,Matrix *&ctm) {}
  bool AutoPilotTo(Unit * un, bool automaticenergyrealloc,int recursive_level=2);
  ///The owner of this unit. This may not collide with owner or units owned by owner. Do not dereference (may be dead pointer)
  void *owner; //void ensures that it won't be referenced by accident
  ///The number of frames ahead this was put in the simulation queue
  unsigned int sim_atom_multiplier;
  ///The previous state in last physics frame to interpolate within
  Transformation prev_physical_state;
  ///The state of the current physics frame to interpolate within
  Transformation curr_physical_state;
  ///number of meshes (each with separate texture) this unit has
  ///The cumulative (incl subunits parents' transformation)
  Matrix cumulative_transformation_matrix;
  ///The cumulative (incl subunits parents' transformation)
  Transformation cumulative_transformation;
  ///The velocity this unit has in World Space
  Vector cumulative_velocity;
  ///The force applied from outside accrued over the whole physics frame
  Vector NetForce;
  ///The force applied by internal objects (thrusters)
  Vector NetLocalForce;
  ///The torque applied from outside objects
  Vector NetTorque;
  ///The torque applied from internal objects
  Vector NetLocalTorque;
  ///the current velocities in LOCAL space (not world space)
  Vector AngularVelocity;  Vector Velocity;  ///The image that will appear on those screens of units targetting this unit
  UnitImages *image;
  ///positive for the multiplier applied to nearby spec starships (1 = planetary/inert effects) 0 is default (no effect), -X means 0 but able to be enabled
  float specInterdiction;
  ///mass of this unit (may change with cargo)
  float Mass;
  float HeatSink;
protected:
  ///are shields tight to the hull.  zero means bubble
  float shieldtight;
  ///fuel of this unit
  float fuel;
  float afterburnenergy;  //short fix
  int   afterburntype; // 0--energy, 1--fuel
  ///-1 means it is off. -2 means it doesn't exist. otherwise it's engaged to destination (positive number)
 ///Moment of intertia of this unit
  float Momentofinertia;
  Vector SavedAccel;
klauss
Elite
Elite
Posts: 7243
Joined: Mon Apr 18, 2005 2:40 pm
Location: LS87, Buenos Aires, República Argentina

Post by klauss »

Chuch, try to use the facilities of FlyByWire. That way, the autopilot won't do anything you're not supposed to be doing. Also, instead of setting the SPEC multiplier directly, set a SPEC cap. So that the physics compute the SPEC multiplier, and then if that multiplier is above what the AP wants, you lower it down. I'm not sure where all that is done, but you want to code the autopilot in a similar fashion as the FlyTo or FireAt AI objects... basically, you just handle an Execute() which is called every physics frame.

Your thing should be layered on top of FlyByWire (ie: be a descendant of), and use its facilities to direct the ship's movements.

I'm sorry I can't explain any clearer. I myself don't know it well. Perhaps spirit can enlighten you.
Oíd mortales, el grito sagrado...
Call me "Menes, lord of Cats"
Wing Commander Universe
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Thanks, klauss; so I should derive from FlyByWire just like FlyByKeyboard?
I guess I'll call it "FlyByAutopilot"?
By the way, I was looking at these classes, and noticed that FlyByKeyboard adds a bunch of variables of its own, on top of the ones it inherits from FlyByWire, so it's a different size; but FlyByWire's destructor is non-virtual. Could result in a memory leak if a FlyByKeyboard object is deleted through a FlyByWire ptr. In fact could get worse: FlyByKeyboard's destructor is not trivial; got code in it. Shall I just add 'virtual' to ~FlyByWire() and commit it?
(Funny, FlyByKeyboard's dtor is virtual...)

Okay, in FlyByKeyboard there's these functions...

Code: Select all

void FlyByKeyboard::AutoKey (const KBData&,KBSTATE k) {
  if (g().dirty)  g().UnDirty();
  if (k==PRESS) {
    g().realauto=true;
  }
}

void FlyByKeyboard::EngageSpecAuto (const KBData&,KBSTATE k) {
  if (g().dirty)  g().UnDirty();
  if (k==PRESS) {
    g().ASAP=true;
  }
}
...and I suppose I could replace ASAP with my autopilot? So I'd copy these functions to FlyByAutopilot, copy Execute(), and then in execute, replace...

Code: Select all

  if (SSCK.ASAP) {
    SSCK.ASAP=false;
	if(FlyByKeyboard::inauto){
		this->eraseType(FACING|MOVEMENT);
		FlyByKeyboard::inauto=false;
	} else {
		Orders::AutoLongHaul* temp = new Orders::AutoLongHaul();
		temp->SetParent(parent);
		Order::EnqueueOrderFirst(temp);
		FlyByKeyboard::inauto=true;
    }
  }
... with my code? Or maybe write a function Orders::ChucksREALautoLongHaul()?

What decides which descendant of FlyByWire is used? I suppose my class should be instanced instead of FlyByKeyboard?

EDIT:
Actually, klauss; being that ASAP is part of FlyByKeyboard, how about I just modify it, so that if you have an Autopilot I or better installed, instead of the normal ASAP function it executes the functionality of whichever autopilot model you have in your ship. Does this sound like a good idea?
klauss
Elite
Elite
Posts: 7243
Joined: Mon Apr 18, 2005 2:40 pm
Location: LS87, Buenos Aires, República Argentina

Post by klauss »

chuck_starchaser wrote: entire post, plus
Actually, klauss; being that ASAP is part of FlyByKeyboard, how about I just modify it, so that if you have an Autopilot I or better installed, instead of the normal ASAP function it executes the functionality of whichever autopilot model you have in your ship. Does this sound like a good idea?
Not a bad idea.
But be careful. Others are working on ASAP, making it less jerky and stuff.

Also, you'll have to ask spirit or some other AI expert. I'm not entirely sure how things are handled here.

I now you can have several AI orders stacked up... not sure how... that each handle an aspect of the AI. For instance, you can have an order that handles comms (I think its the CommunicatingAI), and another that handles flight/guns (FireAt), both set as current orders. Each has a type bitfield which specifies which aspects are being controlled.

Anyway, I think ASAP is handled by making you use the FlyTo AI order. But I'm not sure. If that's the case, you should do your stuff in FlyTo. The good thing that would come out of that is that NPC AI also uses FlyTo, so they would benefit from the autopilot too... groovy.

Anyway. Ask an expert. I guess spirit or hellcat would be the ones to go to. I've spent many hours trying to figure out that stuff, and I'm doing it at baby steps. Spirit, however, figured it out in no time. So I'd ask her.
Oíd mortales, el grito sagrado...
Call me "Menes, lord of Cats"
Wing Commander Universe
Post Reply