325 lines
11 KiB
C++
325 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) 2008-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.
|
|
|
|
|
|
//The AI debugging 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.
|
|
#undef AI_DEBUG
|
|
|
|
#include "karts/controller/end_controller.hpp"
|
|
|
|
#ifdef AI_DEBUG
|
|
# include "irrlicht.h"
|
|
using namespace irr;
|
|
#endif
|
|
|
|
#include <cstdlib>
|
|
#include <ctime>
|
|
#include <cstdio>
|
|
#include <iostream>
|
|
|
|
#ifdef AI_DEBUG
|
|
#include "graphics/irr_driver.hpp"
|
|
#endif
|
|
|
|
#include "karts/abstract_kart.hpp"
|
|
#include "karts/max_speed.hpp"
|
|
#include "karts/rescue_animation.hpp"
|
|
#include "modes/linear_world.hpp"
|
|
#include "race/race_manager.hpp"
|
|
#include "states_screens/race_result_gui.hpp"
|
|
#include "tracks/drive_graph.hpp"
|
|
#include "tracks/drive_node.hpp"
|
|
#include "tracks/track.hpp"
|
|
#include "utils/constants.hpp"
|
|
#include "utils/log.hpp"
|
|
|
|
EndController::EndController(AbstractKart *kart,
|
|
Controller *prev_controller)
|
|
: AIBaseLapController(kart)
|
|
{
|
|
m_previous_controller = prev_controller;
|
|
if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
|
|
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_SOCCER)
|
|
{
|
|
// Overwrite the random selected default path from AIBaseLapController
|
|
// with a path that always picks the first branch (i.e. it follows
|
|
// the main driveline).
|
|
std::vector<unsigned int> next;
|
|
for(unsigned int i=0; i<DriveGraph::get()->getNumNodes(); i++)
|
|
{
|
|
// 0 is always a valid successor - so even if the kart should end
|
|
// up by accident on a non-selected path, it will keep on working.
|
|
m_successor_index[i] = 0;
|
|
|
|
next.clear();
|
|
DriveGraph::get()->getSuccessors(i, next);
|
|
m_next_node_index[i] = next[0];
|
|
}
|
|
|
|
const unsigned int look_ahead=10;
|
|
// Now compute for each node in the graph the list of the next 'look_ahead'
|
|
// graph nodes. This is the list of node that is tested in checkCrashes.
|
|
// If the look_ahead is too big, the AI can skip loops (see
|
|
// DriveGraph::findRoadSector for details), if it's too short the AI won't
|
|
// find too good a driveline. Note that in general this list should
|
|
// be computed recursively, but since the AI for now is using only
|
|
// (randomly picked) path this is fine
|
|
for(unsigned int i=0; i<DriveGraph::get()->getNumNodes(); i++)
|
|
{
|
|
std::vector<int> l;
|
|
int current = i;
|
|
for(unsigned int j=0; j<look_ahead; j++)
|
|
{
|
|
l.push_back(m_next_node_index[current]);
|
|
current = m_next_node_index[current];
|
|
} // for j<look_ahead
|
|
m_all_look_aheads[i] = l;
|
|
}
|
|
} // if not battle mode
|
|
|
|
// Reset must be called after DriveGraph::get() etc. is set up
|
|
reset();
|
|
|
|
m_max_handicap_accel = 1.0f;
|
|
m_min_steps = 2;
|
|
|
|
#ifdef AI_DEBUG
|
|
m_debug_sphere = irr_driver->getSceneManager()->addSphereSceneNode(1);
|
|
#endif
|
|
m_kart->setSlowdown(MaxSpeed::MS_DECREASE_AI, 0.3f, 2);
|
|
|
|
// Set the name of the previous controller as this controller name, otherwise
|
|
// we get the incorrect name when printing statistics in profile mode.
|
|
setControllerName(prev_controller->getControllerName());
|
|
} // EndController
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** The destructor deletes the shared TrackInfo objects if no more EndController
|
|
* instances are around.
|
|
*/
|
|
EndController::~EndController()
|
|
{
|
|
#ifdef AI_DEBUG
|
|
irr_driver->removeNode(m_debug_sphere);
|
|
#endif
|
|
} // ~EndController
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void EndController::reset()
|
|
{
|
|
AIBaseLapController::reset();
|
|
|
|
m_crash_time = 0.0f;
|
|
m_time_since_stuck = 0.0f;
|
|
|
|
m_track_node = Graph::UNKNOWN_SECTOR;
|
|
// In battle mode there is no quad graph, so nothing to do in this case
|
|
if(race_manager->getMinorMode()!=RaceManager::MINOR_MODE_3_STRIKES &&
|
|
race_manager->getMinorMode()!=RaceManager::MINOR_MODE_SOCCER)
|
|
{
|
|
DriveGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
|
|
|
|
// Node that this can happen quite easily, e.g. an AI kart is
|
|
// taken over by the end controller while it is off track.
|
|
if(m_track_node==Graph::UNKNOWN_SECTOR)
|
|
{
|
|
m_track_node = DriveGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
|
|
}
|
|
}
|
|
} // reset
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Callback when a new lap is triggered. It is used to switch to the first
|
|
* end camera (which is esp. useful in fixing up end cameras in reverse mode,
|
|
* since otherwise the switch to the first end camera usually happens way too
|
|
* late)
|
|
*/
|
|
void EndController::newLap(int lap)
|
|
{
|
|
// Forward the call to the original controller. This will implicitely
|
|
// trigger setting the first end camera to be active if the controller
|
|
// is a player controller.
|
|
m_previous_controller->newLap(lap);
|
|
} // newLap
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** The end controller must forward 'fire' presses to the race gui.
|
|
*/
|
|
void EndController::action(PlayerAction action, int value)
|
|
{
|
|
if(action!=PA_FIRE) return;
|
|
RaceResultGUI *race_result_gui = dynamic_cast<RaceResultGUI*>(World::getWorld()->getRaceGUI());
|
|
if(!race_result_gui) return;
|
|
race_result_gui->nextPhase();
|
|
} // action
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void EndController::update(int ticks)
|
|
{
|
|
// This is used to enable firing an item backwards.
|
|
m_controls->setLookBack(false);
|
|
m_controls->setNitro(false);
|
|
m_controls->setBrake(false);
|
|
m_controls->setAccel(1.0f);
|
|
|
|
AIBaseLapController::update(ticks);
|
|
|
|
// In case of battle mode: don't do anything
|
|
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
|
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER ||
|
|
race_manager->getMinorMode()==RaceManager::MINOR_MODE_EASTER_EGG)
|
|
{
|
|
m_controls->setAccel(0.0f);
|
|
// Brake while we are still driving forwards (if we keep
|
|
// on braking, the kart will reverse otherwise)
|
|
m_controls->setBrake(m_kart->getSpeed()>0);
|
|
return;
|
|
}
|
|
/*Get information that is needed by more than 1 of the handling funcs*/
|
|
//Detect if we are going to crash with the track and/or kart
|
|
calcSteps();
|
|
|
|
/*Response handling functions*/
|
|
float dt = stk_config->ticks2Time(ticks);
|
|
handleSteering(dt);
|
|
handleRescue(dt);
|
|
} // update
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void EndController::handleSteering(float dt)
|
|
{
|
|
Vec3 target_point;
|
|
|
|
/*The AI responds based on the information we just gathered, using a
|
|
*finite state machine.
|
|
*/
|
|
//Reaction to being outside of the road
|
|
if( fabsf(m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() )) >
|
|
0.5f* DriveGraph::get()->getNode(m_track_node)->getPathWidth()+0.5f )
|
|
{
|
|
const int next = m_next_node_index[m_track_node];
|
|
target_point = DriveGraph::get()->getNode(next)->getCenter();
|
|
#ifdef AI_DEBUG
|
|
Log::debug("end_controller.cpp", "- Outside of road: steer to center point.");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
findNonCrashingPoint(&target_point);
|
|
}
|
|
#ifdef AI_DEBUG
|
|
m_debug_sphere->setPosition(target_point.toIrrVector());
|
|
#endif
|
|
|
|
setSteering(steerToPoint(target_point), dt);
|
|
} // handleSteering
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void EndController::handleRescue(const float DELTA)
|
|
{
|
|
// check if kart is stuck
|
|
if(m_kart->getSpeed()<2.0f && !m_kart->getKartAnimation() &&
|
|
!m_world->isStartPhase())
|
|
{
|
|
m_time_since_stuck += DELTA;
|
|
if(m_time_since_stuck > 2.0f)
|
|
{
|
|
new RescueAnimation(m_kart);
|
|
m_time_since_stuck=0.0f;
|
|
} // m_time_since_stuck > 2.0f
|
|
}
|
|
else
|
|
{
|
|
m_time_since_stuck = 0.0f;
|
|
}
|
|
} // handleRescue
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Find the sector that at the longest distance from the kart, that can be
|
|
* driven to without crashing with the track, then find towards which of
|
|
* the two edges of the track is closest to the next curve after wards,
|
|
* and return the position of that edge.
|
|
*/
|
|
void EndController::findNonCrashingPoint(Vec3 *result)
|
|
{
|
|
unsigned int sector = m_next_node_index[m_track_node];
|
|
|
|
Vec3 direction;
|
|
Vec3 step_track_coord;
|
|
float distance;
|
|
|
|
//We exit from the function when we have found a solution
|
|
while( 1 )
|
|
{
|
|
//target_sector is the sector at the longest distance that we can drive
|
|
//to without crashing with the track.
|
|
int target_sector = m_next_node_index[sector];
|
|
|
|
//direction is a vector from our kart to the sectors we are testing
|
|
direction = DriveGraph::get()->getNode(target_sector)->getCenter()
|
|
- m_kart->getXYZ();
|
|
|
|
float len=direction.length();
|
|
int steps = int( len / m_kart_length );
|
|
if( steps < 3 ) steps = 3;
|
|
|
|
//Protection against having vel_normal with nan values
|
|
if(len>0.0f) {
|
|
direction*= 1.0f/len;
|
|
}
|
|
|
|
Vec3 step_coord;
|
|
//Test if we crash if we drive towards the target sector
|
|
for( int i = 2; i < steps; ++i )
|
|
{
|
|
step_coord = m_kart->getXYZ()+direction*m_kart_length * float(i);
|
|
|
|
DriveGraph::get()->spatialToTrack(&step_track_coord, step_coord,
|
|
sector );
|
|
|
|
distance = fabsf(step_track_coord[0]);
|
|
|
|
//If we are outside, the previous sector is what we are looking for
|
|
if ( distance + m_kart_width * 0.5f
|
|
> DriveGraph::get()->getNode(sector)->getPathWidth()*0.5f )
|
|
{
|
|
*result = DriveGraph::get()->getNode(sector)->getCenter();
|
|
return;
|
|
}
|
|
}
|
|
sector = target_sector;
|
|
}
|
|
} // findNonCrashingPoint
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** calc_steps() divides the velocity vector by the lenght of the kart,
|
|
* and gets the number of steps to use for the sight line of the kart.
|
|
* The calling sequence guarantees that m_future_sector is not UNKNOWN.
|
|
*/
|
|
int EndController::calcSteps()
|
|
{
|
|
int steps = int( m_kart->getVelocityLC().getZ() / m_kart_length );
|
|
if( steps < m_min_steps ) steps = m_min_steps;
|
|
|
|
return steps;
|
|
} // calcSteps
|
|
|