2017-02-21 12:09:22 +08:00

288 lines
11 KiB
C++

//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2004-2015 Steve Baker <sjbaker1@airmail.net>
// Copyright (C) 2006-2015 Eduardo Hernandez Munoz
// Copyright (C) 2010-2015 Joerg Henrichs
//
// 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 3
// 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.
#ifndef HEADER_SKIDDING_AI_HPP
#define HEADER_SKIDDING_AI_HPP
// Some debugging features for the AI. For example you can visualise the
// point the AI is aiming at, or visualise the curve the AI is predicting.
// It works best with just 1 AI kart, so set the number of karts
// to 2 in main.cpp with quickstart and run supertuxkart with the arg -N.
// Or use --profile-laps=99 and run just one AI. Using the debug camera
// (top view) is useful, too
#ifdef DEBUG
// Enable AI graphical debugging
# undef AI_DEBUG
// Shows left and right lines when using new findNonCrashing function
# undef AI_DEBUG_NEW_FIND_NON_CRASHING
// Show the predicted turn circles
# undef AI_DEBUG_CIRCLES
// Show the heading of the kart
# undef AI_DEBUG_KART_HEADING
// Shows line from kart to its aim point
# undef AI_DEBUG_KART_AIM
#endif
#include "karts/controller/ai_base_lap_controller.hpp"
#include "race/race_manager.hpp"
#include "tracks/drive_node.hpp"
#include "utils/random_generator.hpp"
#include <line3d.h>
class LinearWorld;
class DriveGraph;
class ShowCurve;
class Track;
namespace irr
{
namespace scene
{
class ISceneNode;
}
}
/**
\brief This is the actual racing AI.
The main entry point, called once per frame for each AI, is update().
After handling some standard cases (race start, AI being rescued)
the AI does the following steps:
- compute nearest karts (one ahead and one behind)
- check if the kart is about to crash with another kart or the
track. This is done by simply testing a certain number of timesteps
ahead and estimating the future position of any kart by using
current_position + velocity * time
(so turns are not taken into account). It also checks if the kart
would be outside the quad graph, which indicates a 'collision with
track' (though technically this only means the kart would be off track).
This information is stored in the m_crashes data structure.
- Determine track direction (straight, left, or right), which the
function determineTrackDirection stores in m_current_track_direction
- If the kart has a bomb attached, it will try to hit the kart ahead,
using nitro if necessary. The kart will aim at the target kart,
nothing els is done (idea: if the kart has a plunger, fire it).
- Otherwise, the AI will:
- set acceleration (handleAcceleration)
- decide where to steer to (handleSteering()): it will first check if it
is outside of the track, and if so, steer towards the center of the
next quad. If it was detected that the AI is going to hit another
kart, it will try to avoid this. Otherwise it will aim at the center
of the quad furthest away so that a straight line from the current
position to this center is on track (see findNonCrashingPoint).
There are actually three different implementations of this, but the
(somewhat buggy) default one results in reality with the best AI
behaviour.
The function handleSteering() then calls setSteering() to set the
actually steering amount. The latter function also decides if skidding
should be done or not (by calling canSkid()).
- decide if to try to collect or avoid items (handeItems).
It considers all items on quads between the current quad of the kart
and the quad the AI is aiming at (see handleSteering). If it finds
an item to collect, it pre-selects this items, which means it will
not select another item for collection until this pre-selected item
is clearly uncollectable (gone or too far out of the way). This avoids
problems that the AI is between two items it can collects, and keeps
switching between both items, at the end missing both.
- detects if the AI is stuck and needs rescue (handleRescue)
- decides if it needs to brake (handlebraking)
- decides if nitro or zipper should be used
- Finally, it checks if it has a zipper but selected to use nitro, and
under certain circumstances will use zipper instead of nitro.
\ingroup controller
*/
class SkiddingAI : public AIBaseLapController
{
private:
class CrashTypes
{
public:
bool m_road; //true if we are going to 'crash' with the bounds of the road
int m_kart; //-1 if no crash, pos numbers are the kart it crashes with
CrashTypes() : m_road(false), m_kart(-1) {};
void clear() {m_road = false; m_kart = -1;}
} m_crashes;
RaceManager::AISuperPower m_superpower;
/*General purpose variables*/
/** Pointer to the closest kart ahead of this kart. NULL if this
* kart is first. */
AbstractKart *m_kart_ahead;
/** Distance to the kart ahead. */
float m_distance_ahead;
/** Pointer to the closest kart behind this kart. NULL if this kart
* is last. */
AbstractKart *m_kart_behind;
/** Distance to the kard behind. */
float m_distance_behind;
/** The actual start delay used. */
float m_start_delay;
/** Time an item has been collected and not used. */
float m_time_since_last_shot;
float m_time_since_stuck;
/** Direction of crash: -1 = left, 1 = right, 0 = no crash. */
int m_start_kart_crash_direction;
/** The direction of the track where the kart is on atm. */
DriveNode::DirectionType m_current_track_direction;
/** The radius of the curve the kart is currently driving. Undefined
* when being on a straigt section. */
float m_current_curve_radius;
/** Stores the center of the curve (if the kart is in a curve,
* otherwise undefined). */
Vec3 m_curve_center;
/** The index of the last node with the same direction as the current
* node the kart is on. If kart is in a left turn, this will be
* the last node that is still turning left etc. */
unsigned int m_last_direction_node;
/** If set an item that the AI should aim for. */
const Item *m_item_to_collect;
/** True if items to avoid are close by. Used to avoid using zippers
* (which would make it more difficult to avoid items). */
bool m_avoid_item_close;
/** Distance to the player, used for rubber-banding. */
float m_distance_to_player;
/** A random number generator to decide if the AI should skid or not. */
RandomGenerator m_random_skid;
/** This implements a simple finite state machine: it starts in
* NOT_YET. The first time the AI decides to skid, the state is changed
* randomly (depending on skid probability) to NO_SKID or SKID.
* As long as the AI keeps on deciding to skid, the state remains
* unchanged (so no new random decision is made) till it decides
* not to skid. In which case the state is set to NOT_YET again.
* This guarantees that for each 'skidable' section of the track
* the random decision is only done once. */
enum {SKID_PROBAB_NOT_YET, SKID_PROBAB_NO_SKID, SKID_PROBAB_SKID}
m_skid_probability_state;
/** The last item selected for collection, for which a probability
* was determined. */
const Item *m_last_item_random;
/** True if m_last_item_random was randomly selected to be collected. */
bool m_really_collect_item;
/** A random number generator for collecting items. */
RandomGenerator m_random_collect_item;
/** \brief Determines the algorithm to use to select the point-to-aim-for
* There are three different Point Selection Algorithms:
* 1. findNonCrashingPoint() is the default (which is actually slightly
* buggy, but so far best one after handling of 90 degree turns was
* added).
* 2. findNonCrashingPointFixed() which fixes the bugs of the default
* algorithm.
* 3. findNonCrashingPointNew() A newly designed algorithm, which is
* faster than the standard one, but does not give as good results
* as the 'buggy' one.
*
* So far the default one has by far the best performance, even though
* it has bugs. */
enum {PSA_DEFAULT, PSA_FIXED, PSA_NEW}
m_point_selection_algorithm;
#ifdef AI_DEBUG
/** For skidding debugging: shows the estimated turn shape. */
ShowCurve **m_curve;
/** For debugging purpose: a sphere indicating where the AI
* is targeting at. */
irr::scene::ISceneNode *m_debug_sphere[4];
/** For item debugging: set to the item that is selected to
* be collected. */
irr::scene::ISceneNode *m_item_sphere;
#endif
/*Functions called directly from update(). They all represent an action
*that can be done, and end up setting their respective m_controls
*variable, except handle_race_start() that isn't associated with any
*specific action (more like, associated with inaction).
*/
void handleRaceStart();
void handleAcceleration(const float dt);
void handleSteering(float dt);
void handleItems(const float dt);
void handleRescue(const float dt);
void handleBraking();
void handleNitroAndZipper();
void computeNearestKarts();
void handleItemCollectionAndAvoidance(Vec3 *aim_point,
int last_node);
bool handleSelectedItem(Vec3 kart_aim_direction, Vec3 *aim_point);
bool steerToAvoid(const std::vector<const Item *> &items_to_avoid,
const core::line3df &line_to_target,
Vec3 *aim_point);
bool hitBadItemWhenAimAt(const Item *item,
const std::vector<const Item *> &items_to_avoid);
void evaluateItems(const Item *item, Vec3 kart_aim_direction,
std::vector<const Item *> *items_to_avoid,
std::vector<const Item *> *items_to_collect);
void checkCrashes(const Vec3& pos);
void findNonCrashingPointFixed(Vec3 *result, int *last_node);
void findNonCrashingPointNew(Vec3 *result, int *last_node);
void findNonCrashingPoint(Vec3 *result, int *last_node);
void determineTrackDirection();
virtual bool canSkid(float steer_fraction);
virtual void setSteering(float angle, float dt);
void handleCurve();
protected:
virtual unsigned int getNextSector(unsigned int index);
public:
SkiddingAI(AbstractKart *kart);
~SkiddingAI();
virtual void update (float delta) ;
virtual void reset ();
virtual const irr::core::stringw& getNamePostfix() const;
};
#endif
/* EOF */