AIController.as 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. const float initialAggression = 0.0020;
  2. const float initialPrediction = 30;
  3. const float initialAimSpeed = 10;
  4. const float deltaAggression = 0.000025;
  5. const float deltaPrediction = -0.15;
  6. const float deltaAimSpeed = 0.30;
  7. const float maxAggression = 0.01;
  8. const float maxPrediction = 20;
  9. const float maxAimSpeed = 40;
  10. float aiAggression = initialAggression;
  11. float aiPrediction = initialPrediction;
  12. float aiAimSpeed = initialAimSpeed;
  13. void ResetAI()
  14. {
  15. aiAggression = initialAggression;
  16. aiPrediction = initialPrediction;
  17. aiAimSpeed = initialAimSpeed;
  18. }
  19. void MakeAIHarder()
  20. {
  21. aiAggression += deltaAggression;
  22. if (aiAggression > maxAggression)
  23. aiAggression = maxAggression;
  24. aiPrediction += deltaPrediction;
  25. if (aiPrediction < maxPrediction)
  26. aiPrediction = maxPrediction;
  27. aiAimSpeed += deltaAimSpeed;
  28. if (aiAimSpeed > maxAimSpeed)
  29. aiAimSpeed = maxAimSpeed;
  30. }
  31. class AIController
  32. {
  33. // Use a weak handle instead of a normal handle to point to the current target
  34. // so that we don't mistakenly keep it alive.
  35. WeakHandle currentTarget;
  36. float newTargetTimer = 0;
  37. void Control(Ninja@ ownNinja, Node@ ownNode, float timeStep)
  38. {
  39. // Get new target if none. Do not constantly scan for new targets to conserve CPU time
  40. if (currentTarget.Get() is null)
  41. {
  42. newTargetTimer += timeStep;
  43. if (newTargetTimer > 0.5)
  44. GetNewTarget(ownNode);
  45. }
  46. Node@ targetNode = currentTarget.Get();
  47. if (targetNode !is null)
  48. {
  49. // Check that current target is still alive. Otherwise choose new
  50. Ninja@ targetNinja = cast<Ninja>(targetNode.scriptObject);
  51. if (targetNinja is null || targetNinja.health <= 0)
  52. {
  53. currentTarget = null;
  54. return;
  55. }
  56. RigidBody@ targetBody = targetNode.GetComponent("RigidBody");
  57. ownNinja.controls.Set(CTRL_FIRE, false);
  58. ownNinja.controls.Set(CTRL_JUMP, false);
  59. float deltaX = 0.0f;
  60. float deltaY = 0.0f;
  61. // Aim from own head to target's feet
  62. Vector3 ownPos(ownNode.position + Vector3(0, 0.9, 0));
  63. Vector3 targetPos(targetNode.position + Vector3(0, -0.9, 0));
  64. float distance = (targetPos - ownPos).length;
  65. // Use prediction according to target distance & estimated snowball speed
  66. Vector3 currentAim(ownNinja.GetAim() * Vector3(0, 0, 1));
  67. float predictDistance = distance;
  68. if (predictDistance > 50) predictDistance = 50;
  69. Vector3 predictedPos = targetPos + targetBody.linearVelocity * predictDistance / aiPrediction;
  70. Vector3 targetAim = (predictedPos - ownPos);
  71. // Add distance/height compensation
  72. float compensation = Max(targetAim.length - 15, 0.0);
  73. targetAim += Vector3(0, 0.6, 0) * compensation;
  74. // X-aiming
  75. targetAim.Normalize();
  76. Vector3 currentYaw(currentAim.x, 0, currentAim.z);
  77. Vector3 targetYaw(targetAim.x, 0, targetAim.z);
  78. currentYaw.Normalize();
  79. targetYaw.Normalize();
  80. deltaX = Clamp(Quaternion(currentYaw, targetYaw).yaw, -aiAimSpeed, aiAimSpeed);
  81. // Y-aiming
  82. Vector3 currentPitch(0, currentAim.y, 1);
  83. Vector3 targetPitch(0, targetAim.y, 1);
  84. currentPitch.Normalize();
  85. targetPitch.Normalize();
  86. deltaY = Clamp(Quaternion(currentPitch, targetPitch).pitch, -aiAimSpeed, aiAimSpeed);
  87. ownNinja.controls.yaw += 0.1 * deltaX;
  88. ownNinja.controls.pitch += 0.1 * deltaY;
  89. // Firing? if close enough and relatively correct aim
  90. if ((distance < 25) && (currentAim.DotProduct(targetAim) > 0.75))
  91. {
  92. if (Random(1.0) < aiAggression)
  93. ownNinja.controls.Set(CTRL_FIRE, true);
  94. }
  95. // Movement
  96. ownNinja.dirChangeTime -= timeStep;
  97. if (ownNinja.dirChangeTime <= 0)
  98. {
  99. ownNinja.dirChangeTime = 0.5 + Random(1.0);
  100. ownNinja.controls.Set(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT, false);
  101. // Far distance: go forward
  102. if (distance > 30)
  103. ownNinja.controls.Set(CTRL_UP, true);
  104. else if (distance > 6)
  105. {
  106. // Medium distance: random strafing, predominantly forward
  107. float v = Random(1.0);
  108. if (v < 0.8)
  109. ownNinja.controls.Set(CTRL_UP, true);
  110. float h = Random(1.0);
  111. if (h < 0.3)
  112. ownNinja.controls.Set(CTRL_LEFT, true);
  113. if (h > 0.7)
  114. ownNinja.controls.Set(CTRL_RIGHT, true);
  115. }
  116. else
  117. {
  118. // Close distance: random strafing backwards
  119. float v = Random(1.0);
  120. if (v < 0.8)
  121. ownNinja.controls.Set(CTRL_DOWN, true);
  122. float h = Random(1.0);
  123. if (h < 0.4)
  124. ownNinja.controls.Set(CTRL_LEFT, true);
  125. if (h > 0.6)
  126. ownNinja.controls.Set(CTRL_RIGHT, true);
  127. }
  128. }
  129. // Random jump, if going forward
  130. if ((ownNinja.controls.IsDown(CTRL_UP)) && (distance < 1000))
  131. {
  132. if (Random(1.0) < (aiAggression / 5.0))
  133. ownNinja.controls.Set(CTRL_JUMP, true);
  134. }
  135. }
  136. else
  137. {
  138. // If no target, walk idly
  139. ownNinja.controls.Set(CTRL_ALL, false);
  140. ownNinja.controls.Set(CTRL_UP, true);
  141. ownNinja.dirChangeTime -= timeStep;
  142. if (ownNinja.dirChangeTime <= 0)
  143. {
  144. ownNinja.dirChangeTime = 1 + Random(2);
  145. ownNinja.controls.yaw += 0.1 * (Random(600) - 300);
  146. }
  147. if (ownNinja.isSliding)
  148. ownNinja.controls.yaw += 0.2;
  149. }
  150. }
  151. void GetNewTarget(Node@ ownNode)
  152. {
  153. newTargetTimer = 0;
  154. Array<Node@> nodes = ownNode.scene.GetChildrenWithScript("Ninja", true);
  155. float closestDistance = M_INFINITY;
  156. for (uint i = 0; i < nodes.length; ++i)
  157. {
  158. Node@ otherNode = nodes[i];
  159. Ninja@ otherNinja = cast<Ninja>(otherNode.scriptObject);
  160. if (otherNinja.side == SIDE_PLAYER && otherNinja.health > 0)
  161. {
  162. float distance = (ownNode.position - otherNode.position).lengthSquared;
  163. if (distance < closestDistance)
  164. {
  165. currentTarget = otherNode;
  166. closestDistance = distance;
  167. }
  168. }
  169. }
  170. }
  171. }