PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/src/Ship-AI.cpp

https://github.com/GizmoR13/pioneer
C++ | 430 lines | 289 code | 69 blank | 72 comment | 41 complexity | 62eb121a65fef347359474901eb348c1 MD5 | raw file
  1. // Copyright © 2008-2012 Pioneer Developers. See AUTHORS.txt for details
  2. // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
  3. #include "libs.h"
  4. #include "Ship.h"
  5. #include "ShipAICmd.h"
  6. #include "Pi.h"
  7. #include "Player.h"
  8. #include "perlin.h"
  9. #include "Frame.h"
  10. #include "Planet.h"
  11. #include "SpaceStation.h"
  12. #include "Space.h"
  13. #include "LuaConstants.h"
  14. #include "LuaEvent.h"
  15. #include "KeyBindings.h"
  16. void Ship::AIModelCoordsMatchAngVel(vector3d desiredAngVel, double softness)
  17. {
  18. const ShipType &stype = GetShipType();
  19. double angAccel = stype.angThrust / GetAngularInertia();
  20. const double softTimeStep = Pi::game->GetTimeStep() * softness;
  21. matrix4x4d rot;
  22. GetRotMatrix(rot);
  23. vector3d angVel = desiredAngVel - rot.InverseOf() * GetAngVelocity();
  24. vector3d thrust;
  25. for (int axis=0; axis<3; axis++) {
  26. if (angAccel * softTimeStep >= fabs(angVel[axis])) {
  27. thrust[axis] = angVel[axis] / (softTimeStep * angAccel);
  28. } else {
  29. thrust[axis] = (angVel[axis] > 0.0 ? 1.0 : -1.0);
  30. }
  31. }
  32. SetAngThrusterState(thrust);
  33. }
  34. void Ship::AIModelCoordsMatchSpeedRelTo(const vector3d v, const Ship *other)
  35. {
  36. matrix4x4d m; GetRotMatrix(m);
  37. vector3d relToVel = m.InverseOf() * other->GetVelocity() + v;
  38. AIAccelToModelRelativeVelocity(relToVel);
  39. }
  40. // Try to reach this model-relative velocity.
  41. // (0,0,-100) would mean going 100m/s forward.
  42. void Ship::AIAccelToModelRelativeVelocity(const vector3d v)
  43. {
  44. // OK. For rotating frames linked to space stations we want to set
  45. // speed relative to non-rotating frame (so we apply Frame::GetStasisVelocityAtPosition.
  46. // For rotating frames linked to planets we want to set velocity relative to
  47. // surface, so we do not apply Frame::GetStasisVelocityAtPosition
  48. vector3d relVel = GetVelocity();
  49. if (GetFrame()->IsStationRotFrame()) {
  50. relVel -= GetFrame()->GetStasisVelocityAtPosition(GetPosition());
  51. }
  52. matrix4x4d m; GetRotMatrix(m);
  53. vector3d difVel = v - (relVel * m); // required change in velocity
  54. vector3d maxThrust = GetMaxThrust(difVel);
  55. vector3d maxFrameAccel = maxThrust * Pi::game->GetTimeStep() / GetMass();
  56. SetThrusterState(0, difVel.x / maxFrameAccel.x);
  57. SetThrusterState(1, difVel.y / maxFrameAccel.y);
  58. SetThrusterState(2, difVel.z / maxFrameAccel.z); // use clamping
  59. }
  60. // returns true if command is complete
  61. bool Ship::AITimeStep(float timeStep)
  62. {
  63. // allow the launch thruster thing to happen
  64. if (m_launchLockTimeout > 0.0) return false;
  65. if (!m_curAICmd) {
  66. if (this == Pi::player) return true;
  67. // just in case the AI left it on
  68. ClearThrusterState();
  69. for (int i=0; i<ShipType::GUNMOUNT_MAX; i++)
  70. SetGunState(i,0);
  71. return true;
  72. }
  73. if (m_curAICmd->TimeStepUpdate()) {
  74. AIClearInstructions();
  75. // ClearThrusterState(); // otherwise it does one timestep at 10k and gravity is fatal
  76. LuaEvent::Queue("onAICompleted", this, LuaConstants::GetConstantString(Lua::manager->GetLuaState(), "ShipAIError", AIMessage()));
  77. return true;
  78. }
  79. else return false;
  80. }
  81. void Ship::AIClearInstructions()
  82. {
  83. if (!m_curAICmd) return;
  84. delete m_curAICmd; // rely on destructor to kill children
  85. m_curAICmd = 0;
  86. }
  87. void Ship::AIGetStatusText(char *str)
  88. {
  89. if (!m_curAICmd) strcpy(str, "AI inactive");
  90. else m_curAICmd->GetStatusText(str);
  91. }
  92. Frame *Ship::AIGetRiskFrame()
  93. {
  94. if (!m_curAICmd) return 0;
  95. else return m_curAICmd->GetRiskFrame();
  96. }
  97. void Ship::AIKamikaze(Body *target)
  98. {
  99. AIClearInstructions();
  100. m_curAICmd = new AICmdKamikaze(this, target);
  101. }
  102. void Ship::AIKill(Ship *target)
  103. {
  104. AIClearInstructions();
  105. m_curAICmd = new AICmdKill(this, target);
  106. }
  107. /*
  108. void Ship::AIJourney(SystemBodyPath &dest)
  109. {
  110. AIClearInstructions();
  111. // m_curAICmd = new AICmdJourney(this, dest);
  112. }
  113. */
  114. void Ship::AIFlyTo(Body *target, float hungriness)
  115. {
  116. AIClearInstructions();
  117. m_curAICmd = new AICmdFlyTo(this, target, hungriness);
  118. }
  119. void Ship::AIDock(SpaceStation *target, float hungriness)
  120. {
  121. AIClearInstructions();
  122. m_curAICmd = new AICmdDock(this, target, hungriness);
  123. }
  124. void Ship::AIOrbit(Body *target, double alt, float hungriness)
  125. {
  126. AIClearInstructions();
  127. m_curAICmd = new AICmdFlyAround(this, target, alt, hungriness);
  128. }
  129. void Ship::AIHoldPosition()
  130. {
  131. AIClearInstructions();
  132. m_curAICmd = new AICmdHoldPosition(this);
  133. }
  134. // Because of issues when reducing timestep, must do parts of this as if 1x accel
  135. // final frame has too high velocity to correct if timestep is reduced
  136. // fix is too slow in the terminal stages:
  137. // if (endvel <= vel) { endvel = vel; ivel = dist / Pi::game->GetTimeStep(); } // last frame discrete correction
  138. // ivel = std::min(ivel, endvel + 0.5*acc/PHYSICS_HZ); // unknown next timestep discrete overshoot correction
  139. // yeah ok, this doesn't work
  140. // sometimes endvel is too low to catch moving objects
  141. // worked around with half-accel hack in dynamicbody & pi.cpp
  142. double calc_ivel(double dist, double vel, double acc)
  143. {
  144. bool inv = false;
  145. if (dist < 0) { dist = -dist; vel = -vel; inv = true; }
  146. double ivel = 0.9 * sqrt(vel*vel + 2.0 * acc * dist); // fudge hardly necessary
  147. double endvel = ivel - (acc * Pi::game->GetTimeStep());
  148. if (endvel <= 0.0) ivel = dist / Pi::game->GetTimeStep(); // last frame discrete correction
  149. else ivel = (ivel + endvel) * 0.5; // discrete overshoot correction
  150. // else ivel = endvel + 0.5*acc/PHYSICS_HZ; // unknown next timestep discrete overshoot correction
  151. return (inv) ? -ivel : ivel;
  152. }
  153. // vel is desired velocity in ship's frame
  154. // returns true if this can be attained in a single timestep
  155. // todo: check space station rotating frame case
  156. bool Ship::AIMatchVel(const vector3d &vel)
  157. {
  158. matrix4x4d rot; GetRotMatrix(rot);
  159. vector3d diffvel = (vel - GetVelocityRelTo(GetFrame())) * rot; // convert to object space
  160. return AIChangeVelBy(diffvel);
  161. }
  162. // diffvel is required change in velocity in object space
  163. // returns true if this can be done in a single timestep
  164. bool Ship::AIChangeVelBy(const vector3d &diffvel)
  165. {
  166. // counter external forces unless we're in an orbital station rotating frame
  167. matrix4x4d rot; GetRotMatrix(rot);
  168. vector3d diffvel2 = GetExternalForce() * Pi::game->GetTimeStep() / GetMass();
  169. if (GetFrame()->IsStationRotFrame()) diffvel2 = diffvel;
  170. else diffvel2 = diffvel - diffvel2 * rot;
  171. vector3d maxThrust = GetMaxThrust(diffvel2);
  172. vector3d maxFrameAccel = maxThrust * Pi::game->GetTimeStep() / GetMass();
  173. vector3d thrust(diffvel2.x / maxFrameAccel.x,
  174. diffvel2.y / maxFrameAccel.y,
  175. diffvel2.z / maxFrameAccel.z);
  176. SetThrusterState(thrust); // use clamping
  177. if (thrust.x*thrust.x > 1.0 || thrust.y*thrust.y > 1.0 || thrust.z*thrust.z > 1.0) return false;
  178. return true;
  179. }
  180. // relpos and relvel are position and velocity of ship relative to target in ship's frame
  181. // targspeed is in direction of motion, must be positive
  182. // returns difference in closing speed from ideal, or zero if it thinks it's at the target
  183. double Ship::AIMatchPosVel(const vector3d &relpos, const vector3d &relvel, double targspeed, const vector3d &maxthrust)
  184. {
  185. matrix4x4d rot; GetRotMatrix(rot);
  186. vector3d objpos = relpos * rot;
  187. vector3d reldir = objpos.NormalizedSafe();
  188. vector3d endvel = targspeed * reldir;
  189. double invmass = 1.0 / GetMass();
  190. // find ideal velocities at current time given reverse thrust level
  191. vector3d ivel;
  192. ivel.x = calc_ivel(objpos.x, endvel.x, maxthrust.x * invmass);
  193. ivel.y = calc_ivel(objpos.y, endvel.y, maxthrust.y * invmass);
  194. ivel.z = calc_ivel(objpos.z, endvel.z, maxthrust.z * invmass);
  195. vector3d objvel = relvel * rot;
  196. vector3d diffvel = ivel - objvel; // required change in velocity
  197. AIChangeVelBy(diffvel);
  198. return diffvel.Dot(reldir);
  199. }
  200. // reldir*targdist and relvel are pos and vel of ship relative to target in ship's frame
  201. // endspeed is in direction of motion, must be positive
  202. // maxdecel is maximum deceleration thrust
  203. bool Ship::AIMatchPosVel2(const vector3d &reldir, double targdist, const vector3d &relvel, double endspeed, double maxdecel)
  204. {
  205. matrix4x4d rot; GetRotMatrix(rot);
  206. double parspeed = relvel.Dot(reldir);
  207. double ivel = calc_ivel(targdist, endspeed, maxdecel);
  208. double diffspeed = ivel - parspeed;
  209. vector3d diffvel = diffspeed * reldir * rot;
  210. bool rval = false;
  211. // crunch diffvel by relative thruster power to get acceleration in right direction
  212. if (diffspeed > 0.0) {
  213. vector3d maxFA = GetMaxThrust(diffvel) * Pi::game->GetTimeStep() / GetMass();
  214. if (abs(diffvel.x) > maxFA.x) diffvel *= maxFA.x / abs(diffvel.x);
  215. if (abs(diffvel.y) > maxFA.y) diffvel *= maxFA.y / abs(diffvel.y);
  216. // if (abs(diffvel.z) > maxFA.z) diffvel *= maxFA.z / abs(diffvel.z);
  217. if (maxFA.z < diffspeed) rval = true;
  218. }
  219. // add perpendicular velocity
  220. vector3d perpvel = relvel - parspeed*reldir;
  221. diffvel -= perpvel * rot;
  222. AIChangeVelBy(diffvel);
  223. return rval; // true if acceleration was capped
  224. }
  225. // Input in object space
  226. void Ship::AIMatchAngVelObjSpace(const vector3d &angvel)
  227. {
  228. double maxAccel = GetShipType().angThrust / GetAngularInertia();
  229. double invFrameAccel = 1.0 / (maxAccel * Pi::game->GetTimeStep());
  230. matrix4x4d rot; GetRotMatrix(rot);
  231. vector3d diff = angvel - GetAngVelocity() * rot; // find diff between current & desired angvel
  232. SetAngThrusterState(diff * invFrameAccel);
  233. }
  234. // just forces the orientation
  235. void Ship::AIFaceDirectionImmediate(const vector3d &dir)
  236. {
  237. vector3d zaxis = -dir;
  238. vector3d xaxis = vector3d(0.0,1.0,0.0).Cross(zaxis).Normalized();
  239. vector3d yaxis = zaxis.Cross(xaxis).Normalized();
  240. matrix4x4d wantOrient = matrix4x4d::MakeRotMatrix(xaxis, yaxis, zaxis).InverseOf();
  241. SetRotMatrix(wantOrient);
  242. }
  243. // position in ship's frame
  244. vector3d Ship::AIGetNextFramePos()
  245. {
  246. vector3d thrusters = GetThrusterState();
  247. vector3d maxThrust = GetMaxThrust(thrusters);
  248. vector3d thrust = vector3d(maxThrust.x*thrusters.x, maxThrust.y*thrusters.y,
  249. maxThrust.z*thrusters.z);
  250. matrix4x4d rot; GetRotMatrix(rot);
  251. vector3d vel = GetVelocity() + rot * thrust * Pi::game->GetTimeStep() / GetMass();
  252. vector3d pos = GetPosition() + vel * Pi::game->GetTimeStep();
  253. return pos;
  254. }
  255. // returns true if ship will be aligned at end of frame
  256. bool Ship::AIFaceOrient(const vector3d &dir, const vector3d &updir)
  257. {
  258. double timeStep = Pi::game->GetTimeStep();
  259. double maxAccel = GetShipType().angThrust / GetAngularInertia(); // should probably be in stats anyway
  260. if (maxAccel <= 0.0) return 0.0;
  261. double frameAccel = maxAccel * timeStep;
  262. matrix4x4d rot; GetRotMatrix(rot);
  263. if (dir.Dot(vector3d(rot[8], rot[9], rot[10])) > -0.999999)
  264. { AIFaceDirection(dir); return false; }
  265. vector3d uphead = (updir * rot).Normalized(); // create desired object-space updir
  266. vector3d dav(0.0, 0.0, 0.0); // desired angular velocity
  267. double ang = 0.0;
  268. if (uphead.y < 0.999999)
  269. {
  270. ang = acos(Clamp(uphead.y, -1.0, 1.0)); // scalar angle from head to curhead
  271. double iangvel = sqrt(2.0 * maxAccel * ang); // ideal angvel at current time
  272. double frameEndAV = iangvel - frameAccel;
  273. if (frameEndAV <= 0.0) iangvel = ang / timeStep; // last frame discrete correction
  274. else iangvel = (iangvel + frameEndAV) * 0.5; // discrete overshoot correction
  275. dav.z = uphead.x > 0 ? -iangvel : iangvel;
  276. }
  277. vector3d cav = (GetAngVelocity() - GetFrame()->GetAngVelocity()) * rot; // current obj-rel angvel
  278. // vector3d cav = GetAngVelocity() * rot; // current obj-rel angvel
  279. vector3d diff = (dav - cav) / frameAccel; // find diff between current & desired angvel
  280. SetAngThrusterState(diff);
  281. return (diff.z*diff.z < 1.0);
  282. }
  283. // Input: direction in ship's frame, doesn't need to be normalized
  284. // Approximate positive angular velocity at match point
  285. // Applies thrust directly
  286. // old: returns whether it can reach that direction in this frame
  287. // returns angle to target
  288. double Ship::AIFaceDirection(const vector3d &dir, double av)
  289. {
  290. double timeStep = Pi::game->GetTimeStep();
  291. double maxAccel = GetShipType().angThrust / GetAngularInertia(); // should probably be in stats anyway
  292. if (maxAccel <= 0.0) return 0.0;
  293. double frameAccel = maxAccel * timeStep;
  294. matrix4x4d rot; GetRotMatrix(rot);
  295. vector3d head = (dir * rot).Normalized(); // create desired object-space heading
  296. vector3d dav(0.0, 0.0, 0.0); // desired angular velocity
  297. double ang = 0.0;
  298. if (head.z > -0.99999999)
  299. {
  300. ang = acos (Clamp(-head.z, -1.0, 1.0)); // scalar angle from head to curhead
  301. double iangvel = av + sqrt (2.0 * maxAccel * ang); // ideal angvel at current time
  302. double frameEndAV = iangvel - frameAccel;
  303. if (frameEndAV <= 0.0) iangvel = ang / timeStep; // last frame discrete correction
  304. else iangvel = (iangvel + frameEndAV) * 0.5; // discrete overshoot correction
  305. // Normalize (head.x, head.y) to give desired angvel direction
  306. if (head.z > 0.999999) head.x = 1.0;
  307. double head2dnorm = 1.0 / sqrt(head.x*head.x + head.y*head.y); // NAN fix shouldn't be necessary if inputs are normalized
  308. dav.x = head.y * head2dnorm * iangvel;
  309. dav.y = -head.x * head2dnorm * iangvel;
  310. }
  311. vector3d cav = (GetAngVelocity() - GetFrame()->GetAngVelocity()) * rot; // current obj-rel angvel
  312. // vector3d cav = GetAngVelocity() * rot; // current obj-rel angvel
  313. vector3d diff = (dav - cav) / frameAccel; // find diff between current & desired angvel
  314. // If the player is pressing a roll key, don't override roll.
  315. // XXX this really shouldn't be here. a better way would be to have a
  316. // field in Ship describing the wanted angvel adjustment from input. the
  317. // baseclass version in Ship would always be 0. the version in Player
  318. // would be constructed from user input. that adjustment could then be
  319. // considered by this method when computing the required change
  320. if (IsType(Object::PLAYER) && (KeyBindings::rollLeft.IsActive() || KeyBindings::rollRight.IsActive()))
  321. diff.z = m_angThrusters.z;
  322. SetAngThrusterState(diff);
  323. return ang;
  324. }
  325. // returns direction in ship's frame from this ship to target lead position
  326. vector3d Ship::AIGetLeadDir(const Body *target, const vector3d& targaccel, int gunindex)
  327. {
  328. vector3d targpos = target->GetPositionRelTo(this);
  329. vector3d targvel = target->GetVelocityRelTo(this);
  330. // todo: should adjust targpos for gunmount offset
  331. int laser = Equip::types[m_equipment.Get(Equip::SLOT_LASER, gunindex)].tableIndex;
  332. double projspeed = Equip::lasers[laser].speed;
  333. // first attempt
  334. double projtime = targpos.Length() / projspeed;
  335. vector3d leadpos = targpos + targvel*projtime + 0.5*targaccel*projtime*projtime;
  336. // second pass
  337. projtime = leadpos.Length() / projspeed;
  338. leadpos = targpos + targvel*projtime + 0.5*targaccel*projtime*projtime;
  339. return leadpos.Normalized();
  340. }
  341. // same inputs as matchposvel, returns approximate travel time instead
  342. // underestimates if targspeed isn't reachable
  343. double Ship::AITravelTime(const vector3d &reldir, double targdist, const vector3d &relvel, double targspeed, bool flip)
  344. {
  345. double speed = relvel.Dot(reldir); // speed >0 is towards
  346. double dist = targdist;
  347. double faccel = GetAccelFwd();
  348. double raccel = flip ? faccel : GetAccelRev();
  349. double time1, time2, time3;
  350. // time to reduce speed to zero:
  351. time1 = -speed / faccel;
  352. dist += 0.5 * time1 * -speed;
  353. // time to reduce speed to zero after target reached:
  354. time3 = -targspeed / raccel;
  355. dist += 0.5 * time3 * -targspeed;
  356. // now time to cover distance between zero-vel points
  357. // midpoint = intercept of two gradients
  358. double m = dist*raccel / (faccel+raccel);
  359. time2 = sqrt(2*m/faccel) + sqrt(2*(dist-m)/raccel);
  360. return time1+time2+time3;
  361. }