diff --git a/sources.cmake b/sources.cmake index 494082265..7a190a8f8 100644 --- a/sources.cmake +++ b/sources.cmake @@ -101,8 +101,8 @@ src/karts/controller/ai_base_controller.cpp src/karts/controller/controller.cpp src/karts/controller/default_ai_controller.cpp src/karts/controller/end_controller.cpp -src/karts/controller/new_ai_controller.cpp src/karts/controller/player_controller.cpp +src/karts/controller/skidding_ai.cpp src/karts/explosion_animation.cpp src/karts/ghost_kart.cpp src/karts/kart.cpp @@ -346,8 +346,8 @@ src/karts/controller/controller.hpp src/karts/controller/default_ai_controller.hpp src/karts/controller/end_controller.hpp src/karts/controller/kart_control.hpp -src/karts/controller/new_ai_controller.hpp src/karts/controller/player_controller.hpp +src/karts/controller/skidding_ai.hpp src/karts/explosion_animation.hpp src/karts/ghost_kart.hpp src/karts/kart.hpp diff --git a/src/ide/vc9/supertuxkart.vcproj b/src/ide/vc9/supertuxkart.vcproj index 0097ef9d2..8deaa8660 100644 --- a/src/ide/vc9/supertuxkart.vcproj +++ b/src/ide/vc9/supertuxkart.vcproj @@ -916,11 +916,11 @@ > @@ -2102,11 +2102,11 @@ > diff --git a/src/karts/controller/new_ai_controller.cpp b/src/karts/controller/skidding_ai.cpp similarity index 62% rename from src/karts/controller/new_ai_controller.cpp rename to src/karts/controller/skidding_ai.cpp index 6c1bb7a1e..a25088952 100644 --- a/src/karts/controller/new_ai_controller.cpp +++ b/src/karts/controller/skidding_ai.cpp @@ -1,8 +1,8 @@ // // SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2004-2010 Steve Baker -// Copyright (C) 2006-2010 Eduardo Hernandez Munoz -// Copyright (C) 2008-2010 Joerg Henrichs +// Copyright (C) 2004-2005 Steve Baker +// Copyright (C) 2006-2007 Eduardo Hernandez Munoz +// Copyright (C) 2008-2012 Joerg Henrichs // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -21,9 +21,9 @@ //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. -#define AI_DEBUG +#undef AI_DEBUG -#include "karts/controller/new_ai_controller.hpp" +#include "karts/controller/skidding_ai.hpp" #ifdef AI_DEBUG # include "irrlicht.h" @@ -36,15 +36,16 @@ #include #ifdef AI_DEBUG -#include "graphics/irr_driver.hpp" +# include "graphics/irr_driver.hpp" #endif - -#include "items/attachment.hpp" -#include "items/powerup.hpp" +#include "graphics/slip_stream.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/kart_control.hpp" -#include "karts/rescue_animation.hpp" +#include "karts/kart_properties.hpp" #include "karts/max_speed.hpp" +#include "karts/rescue_animation.hpp" +#include "items/attachment.hpp" +#include "items/powerup.hpp" #include "modes/linear_world.hpp" #include "network/network_manager.hpp" #include "race/race_manager.hpp" @@ -52,71 +53,116 @@ #include "tracks/track.hpp" #include "utils/constants.hpp" -NewAIController::NewAIController(AbstractKart *kart) : AIBaseController(kart) +SkiddingAI::SkiddingAI(AbstractKart *kart) + : AIBaseController(kart) { - // Reset must be called after QuadGraph::get() etc. is set up reset(); - + switch( race_manager->getDifficulty()) { case RaceManager::RD_EASY: - m_wait_for_players = true; - m_max_handicap_accel = 0.9f; - m_item_tactic = IT_TEN_SECONDS; - m_max_start_delay = 0.5f; - m_min_steps = 0; - m_nitro_level = NITRO_NONE; - m_handle_bomb = false; + m_wait_for_players = true; + m_make_use_of_slipstream = false; + m_max_handicap_speed = 0.9f; + m_item_tactic = IT_TEN_SECONDS; + m_false_start_probability = 0.08f; + m_min_start_delay = 0.3f; + m_max_start_delay = 0.5f; + m_min_steps = 1; + m_nitro_level = NITRO_NONE; + m_handle_bomb = false; setSkiddingFraction(4.0f); break; case RaceManager::RD_MEDIUM: - m_wait_for_players = true; - m_max_handicap_accel = 0.95f; - m_item_tactic = IT_CALCULATE; - m_max_start_delay = 0.4f; - m_min_steps = 1; - m_nitro_level = NITRO_SOME; - m_handle_bomb = true; + m_wait_for_players = true; + m_make_use_of_slipstream = false; + m_max_handicap_speed = 0.95f; + m_item_tactic = IT_CALCULATE; + m_false_start_probability = 0.04f; + m_min_start_delay = 0.25f; + m_max_start_delay = 0.4f; + m_min_steps = 1; + m_nitro_level = NITRO_SOME; + m_handle_bomb = true; setSkiddingFraction(3.0f); break; case RaceManager::RD_HARD: - m_wait_for_players = false; - m_max_handicap_accel = 1.0f; - m_item_tactic = IT_CALCULATE; - m_max_start_delay = 0.1f; - m_min_steps = 2; - m_nitro_level = NITRO_ALL; - m_handle_bomb = true; + m_wait_for_players = false; + m_make_use_of_slipstream = true; + m_max_handicap_speed = 1.0f; + m_item_tactic = IT_CALCULATE; + m_false_start_probability = 0.01f; + // See http://www.humanbenchmark.com/tests/reactiontime/stats.php + // Average reaction time is around 0.215 s, so using .15 as minimum + // gives an AI average slightly above the human average + m_min_start_delay = 0.15f; + m_max_start_delay = 0.28f; + m_min_steps = 2; + m_nitro_level = NITRO_ALL; + m_handle_bomb = true; setSkiddingFraction(2.0f); break; } #ifdef AI_DEBUG m_debug_sphere = irr_driver->getSceneManager()->addSphereSceneNode(1); - m_debug_left = irr_driver->getSceneManager()->addSphereSceneNode(1); - m_debug_right = irr_driver->getSceneManager()->addSphereSceneNode(1); #endif -} // NewAIController +} // SkiddingAI //----------------------------------------------------------------------------- -/** The destructor deletes the shared TrackInfo objects if no more NewAIController +/** The destructor deletes the shared TrackInfo objects if no more SkiddingAI * instances are around. */ -NewAIController::~NewAIController() +SkiddingAI::~SkiddingAI() { #ifdef AI_DEBUG irr_driver->removeNode(m_debug_sphere); - irr_driver->removeNode(m_debug_left ); - irr_driver->removeNode(m_debug_right ); #endif -} // ~NewAIController +} // ~SkiddingAI + +//----------------------------------------------------------------------------- +void SkiddingAI::reset() +{ + m_time_since_last_shot = 0.0f; + m_start_kart_crash_direction = 0; + m_curve_target_speed = m_kart->getCurrentMaxSpeed(); + m_curve_angle = 0.0; + m_start_delay = -1.0f; + m_time_since_stuck = 0.0f; + m_kart_ahead = NULL; + m_distance_ahead = 0.0f; + m_kart_behind = NULL; + m_distance_behind = 0.0f; + + AIBaseController::reset(); + m_track_node = QuadGraph::UNKNOWN_SECTOR; + QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node); + if(m_track_node==QuadGraph::UNKNOWN_SECTOR) + { + fprintf(stderr, + "Invalid starting position for '%s' - not on track" + " - can be ignored.\n", + m_kart->getIdent().c_str()); + m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ()); + } + + AIBaseController::reset(); +} // reset + +//----------------------------------------------------------------------------- +const irr::core::stringw& SkiddingAI::getNamePostfix() const +{ + // Static to avoid returning the address of a temporary stringq + static irr::core::stringw name="(default)"; + return name; +} // getNamePostfix //----------------------------------------------------------------------------- /** Returns the pre-computed successor of a graph node. * \parameter index The index of the graph node for which the successor * is searched. */ -unsigned int NewAIController::getNextSector(unsigned int index) +unsigned int SkiddingAI::getNextSector(unsigned int index) { return m_successor_index[index]; } // getNextSector @@ -124,20 +170,44 @@ unsigned int NewAIController::getNextSector(unsigned int index) //----------------------------------------------------------------------------- //TODO: if the AI is crashing constantly, make it move backwards in a straight //line, then move forward while turning. -void NewAIController::update(float dt) +void SkiddingAI::update(float dt) { - AIBaseController::update(dt); // This is used to enable firing an item backwards. m_controls->m_look_back = false; m_controls->m_nitro = false; + // Don't do anything if there is currently a kart animations shown. + if(m_kart->getKartAnimation()) + return; + + // Having a non-moving AI can be useful for debugging, e.g. aiming + // or slipstreaming. +#undef AI_DOES_NOT_MOVE_FOR_DEBUGGING +#ifdef AI_DOES_NOT_MOVE_FOR_DEBUGGING + m_controls->m_accel = 0; + m_controls->m_steer = 0; + return; +#endif + // The client does not do any AI computations. if(network_manager->getMode()==NetworkManager::NW_CLIENT) + { + AIBaseController::update(dt); return; + } + + // If the kart needs to be rescued, do it now (and nothing else) + if(isStuck() && !m_kart->getKartAnimation()) + { + new RescueAnimation(m_kart); + AIBaseController::update(dt); + return; + } if( m_world->isStartPhase() ) { handleRaceStart(); + AIBaseController::update(dt); return; } @@ -160,7 +230,7 @@ void NewAIController::update(float dt) { // Use nitro if the kart is far ahead, or faster than this kart m_controls->m_nitro = m_distance_ahead>10.0f || - m_kart_ahead->getSpeed() > m_kart->getSpeed(); + m_kart_ahead->getSpeed() > m_kart->getSpeed(); // If we are close enough, try to hit this kart if(m_distance_ahead<=10) { @@ -185,7 +255,7 @@ void NewAIController::update(float dt) /*Response handling functions*/ handleAcceleration(dt); handleSteering(dt); - handleItems(dt, steps); + handleItems(dt); handleRescue(dt); handleBraking(); // If a bomb is attached, nitro might already be set. @@ -209,16 +279,19 @@ void NewAIController::update(float dt) m_controls->m_fire = true; } } + + /*And obviously general kart stuff*/ + AIBaseController::update(dt); } // update //----------------------------------------------------------------------------- -void NewAIController::handleBraking() +void SkiddingAI::handleBraking() { // In follow the leader mode, the kart should brake if they are ahead of // the leader (and not the leader, i.e. don't have initial position 1) if(race_manager->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER && m_kart->getPosition() < m_world->getKart(0)->getPosition() && - m_kart->getInitialPosition()>1 ) + m_kart->getInitialPosition()>1 ) { m_controls->m_brake = true; return; @@ -238,7 +311,10 @@ void NewAIController::handleBraking() kart_ang_diff = normalizeAngle(kart_ang_diff); kart_ang_diff = fabsf(kart_ang_diff); - const float MIN_TRACK_ANGLE = DEGREE_TO_RAD*190.0f; + // FIXME: The original min_track_angle value of 20 degrees + // resulted in way too much braking. Is this test + // actually necessary at all??? + const float MIN_TRACK_ANGLE = DEGREE_TO_RAD*60.0f; const float CURVE_INSIDE_PERC = 0.25f; //Brake only if the road does not goes somewhat straight. @@ -250,7 +326,8 @@ void NewAIController::handleBraking() //out of the curve. if(!(m_world->getDistanceToCenterForKart(m_kart->getWorldKartId()) > QuadGraph::get()->getNode(m_track_node).getPathWidth() * - -CURVE_INSIDE_PERC || m_curve_angle > RAD_TO_DEGREE*m_kart->getMaxSteerAngle())) + -CURVE_INSIDE_PERC || + m_curve_angle > RAD_TO_DEGREE*m_kart->getMaxSteerAngle()) ) { m_controls->m_brake = false; return; @@ -260,7 +337,8 @@ void NewAIController::handleBraking() { if(!(m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() ) < QuadGraph::get()->getNode(m_track_node).getPathWidth() * - CURVE_INSIDE_PERC || m_curve_angle < -RAD_TO_DEGREE*m_kart->getMaxSteerAngle())) + CURVE_INSIDE_PERC || + m_curve_angle < -RAD_TO_DEGREE*m_kart->getMaxSteerAngle())) { m_controls->m_brake = false; return; @@ -271,7 +349,7 @@ void NewAIController::handleBraking() //to go through the curve at the widest angle, or if the kart //is not going straight in relation to the road. if(m_kart->getVelocityLC().getZ() > m_curve_target_speed || - kart_ang_diff > MIN_TRACK_ANGLE ) + kart_ang_diff > MIN_TRACK_ANGLE ) { #ifdef AI_DEBUG std::cout << "BRAKING" << std::endl; @@ -286,7 +364,7 @@ void NewAIController::handleBraking() } // handleBraking //----------------------------------------------------------------------------- -void NewAIController::handleSteering(float dt) +void SkiddingAI::handleSteering(float dt) { const int next = m_next_node_index[m_track_node]; @@ -296,14 +374,31 @@ void NewAIController::handleSteering(float dt) *finite state machine. */ //Reaction to being outside of the road - if( fabsf(m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() )) > - 0.5f* QuadGraph::get()->getNode(m_track_node).getPathWidth()+1.0f ) + float side_dist = + m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() ); + if( fabsf(side_dist) > + 0.5f* QuadGraph::get()->getNode(m_track_node).getPathWidth()+0.5f ) { - steer_angle = steerToPoint(m_last_target_point); + // If the speed is negative, the kart is most likely being pushed + // away from a collision with the terrain, and this most likely means + // that the kart is off track. In this case, steer so that the kart + // will rotate towards the center of the track. E.g. if the kart is + // to the right, steer towards the right. +#ifdef XX + if(m_kart->getSpeed()<0) + { + steer_angle = side_dist > 0 ? -m_kart->getMaxSteerAngle() + : m_kart->getMaxSteerAngle(); + } + else +#endif + steer_angle = steerToPoint(QuadGraph::get()->getQuadOfNode(next) + .getCenter()); #ifdef AI_DEBUG - m_debug_sphere->setPosition(m_last_target_point.toIrrVector()); - std::cout << "- Outside of road: steer to last aimed at point." << + m_debug_sphere->setPosition(QuadGraph::get()->getQuadOfNode(next) + .getCenter().toIrrVector()); + std::cout << "- Outside of road: steer to center point." << std::endl; #endif } @@ -348,21 +443,30 @@ void NewAIController::handleSteering(float dt) { m_start_kart_crash_direction = 0; Vec3 straight_point; - steer_angle = findNonCrashingAngle(); + findNonCrashingPoint(&straight_point); +#ifdef AI_DEBUG + m_debug_sphere->setPosition(straight_point.toIrrVector()); +#endif + steer_angle = steerToPoint(straight_point); } setSteering(steer_angle, dt); } // handleSteering //----------------------------------------------------------------------------- -void NewAIController::handleItems( const float DELTA, const int STEPS ) +/** Handle all items depending on the chosen strategy: Either (low level AI) + * just use an item after 10 seconds, or do a much better job on higher level + * AI - e.g. aiming at karts ahead/behind, wait an appropriate time before + * using multiple items etc. + */ +void SkiddingAI::handleItems(const float dt) { m_controls->m_fire = false; - if(m_kart->getKartAnimation() || + if(m_kart->getKartAnimation() || m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING ) return; - m_time_since_last_shot += DELTA; + m_time_since_last_shot += dt; // Tactic 1: wait ten seconds, then use item // ----------------------------------------- @@ -378,14 +482,12 @@ void NewAIController::handleItems( const float DELTA, const int STEPS ) // Tactic 2: calculate // ------------------- - switch(m_kart->getPowerup()->getType() ) + switch( m_kart->getPowerup()->getType() ) { - case PowerupManager::POWERUP_ZIPPER: - // Do nothing. Further up a zipper is used if nitro should be selected, - // saving the (potential more valuable nitro) for later - break; - case PowerupManager::POWERUP_BUBBLEGUM: + // Avoid dropping all bubble gums one after another + if( m_time_since_last_shot <3.0f) break; + // Either use the bubble gum after 10 seconds, or if the next kart // behind is 'close' but not too close (too close likely means that the // kart is not behind but more to the side of this kart and so won't @@ -394,21 +496,36 @@ void NewAIController::handleItems( const float DELTA, const int STEPS ) // this approach helps preventing an overtaken kart to overtake us // again. m_controls->m_fire = (m_distance_behind < 15.0f && - m_distance_behind > 3.0f ) || - m_time_since_last_shot>10.0f; - if(m_distance_behind < 10.0f && m_distance_behind > 2.0f ) - m_distance_behind *= 1.0f; - break; + m_distance_behind > 3.0f ); + break; // POWERUP_BUBBLEGUM + // All the thrown/fired items might be improved by considering the angle - // towards m_kart_ahead. And some of them can fire backwards, too - which - // isn't yet supported for AI karts. + // towards m_kart_ahead. case PowerupManager::POWERUP_CAKE: - m_controls->m_fire = (m_kart_ahead && m_distance_ahead < 20.0f) || - m_time_since_last_shot > 10.0f; - break; + { + // Leave some time between shots + if(m_time_since_last_shot<3.0f) break; + // Since cakes can be fired all around, just use a sane distance + // with a bit of extra for backwards, as enemy will go towards cake + bool fire_backwards = (m_kart_behind && m_kart_ahead && + m_distance_behind < m_distance_ahead) || + !m_kart_ahead; + float distance = fire_backwards ? m_distance_behind + : m_distance_ahead; + m_controls->m_fire = (fire_backwards && distance < 25.0f) || + (!fire_backwards && distance < 20.0f); + if(m_controls->m_fire) + m_controls->m_look_back = fire_backwards; + break; + } // POWERUP_CAKE + case PowerupManager::POWERUP_BOWLING: { - // Bowling balls slower, so only fire on closer karts - but when + // Leave more time between bowling balls, since they are + // slower, so it should take longer to hit something which + // can result in changing our target. + if(m_time_since_last_shot < 5.0f) break; + // Bowling balls are slower, so only fire on closer karts - but when // firing backwards, the kart can be further away, since the ball // acts a bit like a mine (and the kart is racing towards it, too) bool fire_backwards = (m_kart_behind && m_kart_ahead && @@ -416,51 +533,99 @@ void NewAIController::handleItems( const float DELTA, const int STEPS ) !m_kart_ahead; float distance = fire_backwards ? m_distance_behind : m_distance_ahead; - m_controls->m_fire = (fire_backwards && distance < 30.0f) || - (!fire_backwards && distance <10.0f) || - m_time_since_last_shot > 10.0f; + m_controls->m_fire = ( (fire_backwards && distance < 30.0f) || + (!fire_backwards && distance <10.0f) ) && + m_time_since_last_shot > 3.0f; if(m_controls->m_fire) m_controls->m_look_back = fire_backwards; break; - } + } // POWERUP_BOWLING + + case PowerupManager::POWERUP_ZIPPER: + // Do nothing. Further up a zipper is used if nitro should be selected, + // saving the (potential more valuable nitro) for later + break; // POWERUP_ZIPPER + case PowerupManager::POWERUP_PLUNGER: { + // Leave more time after a plunger, since it will take some + // time before a plunger effect becomes obvious. + if(m_time_since_last_shot < 5.0f) break; + // Plungers can be fired backwards and are faster, // so allow more distance for shooting. bool fire_backwards = (m_kart_behind && m_kart_ahead && m_distance_behind < m_distance_ahead) || !m_kart_ahead; - float distance = fire_backwards ? m_distance_behind - : m_distance_ahead; - m_controls->m_fire = distance < 30.0f || - m_time_since_last_shot > 10.0f; + float distance = fire_backwards ? m_distance_behind + : m_distance_ahead; + m_controls->m_fire = distance < 30.0f || + m_time_since_last_shot > 10.0f; if(m_controls->m_fire) m_controls->m_look_back = fire_backwards; break; - } + } // POWERUP_PLUNGER + + case PowerupManager::POWERUP_SWITCH: + // For now don't use a switch if this kart is first (since it's more + // likely that this kart then gets a good iteam), otherwise use it + // after a waiting an appropriate time + if(m_kart->getPosition()>1 && + m_time_since_last_shot > stk_config->m_item_switch_time+2.0f) + m_controls->m_fire = true; + break; // POWERUP_SWITCH + + case PowerupManager::POWERUP_PARACHUTE: + // Wait one second more than a previous parachute + if(m_time_since_last_shot > stk_config->m_parachute_time_other+1.0f) + m_controls->m_fire = true; + break; // POWERUP_PARACHUTE + case PowerupManager::POWERUP_ANVIL: + // Wait one second more than a previous anvil + if(m_time_since_last_shot < stk_config->m_anvil_time+1.0f) break; + if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER) { - m_controls->m_fire = m_world->getTime()<1.0f && m_kart->getPosition()>2; + m_controls->m_fire = m_world->getTime()<1.0f && + m_kart->getPosition()>2; } else { m_controls->m_fire = m_time_since_last_shot > 3.0f && m_kart->getPosition()>1; } + break; // POWERUP_ANVIL + + case PowerupManager::POWERUP_SWATTER: + { + // Squared distance for which the swatter works + float d2 = m_kart->getKartProperties()->getSwatterDistance2(); + // Fire if the closest kart ahead or to the back is not already + // squashed and close enough. + // FIXME: this can be improved on, since more than one kart might + // be hit, and a kart ahead might not be at an angle at + // which the glove can be used. + if( ( m_kart_ahead && !m_kart_ahead->isSquashed() && + (m_kart_ahead->getXYZ()-m_kart->getXYZ()).length2()getSpeed() < m_kart->getSpeed() ) || + ( m_kart_behind && !m_kart_behind->isSquashed() && + (m_kart_behind->getXYZ()-m_kart->getXYZ()).length2()m_fire = true; + break; + } case PowerupManager::POWERUP_RUBBERBALL: // Perhaps some more sophisticated algorithm might be useful. // For now: fire if there is a kart ahead (which means that // this kart is certainly not the first kart) m_controls->m_fire = m_kart_ahead != NULL; break; - - case PowerupManager::POWERUP_SWATTER: // fallthrough default: - m_controls->m_fire = true; + printf("Invalid or unhandled powerup '%d' in default AI.\n", + m_kart->getPowerup()->getType()); + assert(false); } if(m_controls->m_fire) m_time_since_last_shot = 0.0f; - return; } // handleItems //----------------------------------------------------------------------------- @@ -468,7 +633,7 @@ void NewAIController::handleItems( const float DELTA, const int STEPS ) * 'closeness' is for now simply based on the position, i.e. if a kart is * more than one lap behind or ahead, it is not considered to be closest. */ -void NewAIController::computeNearestKarts() +void SkiddingAI::computeNearestKarts() { bool need_to_check = false; int my_position = m_kart->getPosition(); @@ -488,7 +653,7 @@ void NewAIController::computeNearestKarts() for(unsigned int i=0; igetNumKarts(); i++) { AbstractKart *k = m_world->getKart(i); - if(k->isEliminated() || k==m_kart) continue; + if(k->isEliminated() || k->hasFinishedRace() || k==m_kart) continue; if(k->getPosition()==my_position+1) { m_kart_behind = k; @@ -508,12 +673,12 @@ void NewAIController::computeNearestKarts() } // computeNearestKarts //----------------------------------------------------------------------------- -void NewAIController::handleAcceleration( const float DELTA ) +void SkiddingAI::handleAcceleration( const float dt) { //Do not accelerate until we have delayed the start enough - if( m_time_till_start > 0.0f ) + if( m_start_delay > 0.0f ) { - m_time_till_start -= DELTA; + m_start_delay -= dt; m_controls->m_accel = 0.0f; return; } @@ -533,6 +698,11 @@ void NewAIController::handleAcceleration( const float DELTA ) return; } + + // FIXME: this needs to be rewritten, it doesn't make any sense: + // wait for players triggers the opposite (if a player is ahead + // of this AI, go full speed). Besides, it's going to use full + // speed anyway. if( m_wait_for_players ) { //Find if any player is ahead of this kart @@ -546,7 +716,7 @@ void NewAIController::handleAcceleration( const float DELTA ) if( player_winning ) { - m_controls->m_accel = m_max_handicap_accel; + m_controls->m_accel = m_max_handicap_speed; return; } } @@ -555,27 +725,32 @@ void NewAIController::handleAcceleration( const float DELTA ) } // handleAcceleration //----------------------------------------------------------------------------- -void NewAIController::handleRaceStart() +void SkiddingAI::handleRaceStart() { - //FIXME: make karts able to get a penalty for accelerating too soon - //like players, should happen to about 20% of the karts in easy, - //5% in medium and less than 1% of the karts in hard. - if( m_time_till_start < 0.0f ) + if( m_start_delay < 0.0f ) { - //Each kart starts at a different, random time, and the time is - //smaller depending on the difficulty. - m_time_till_start = ( float ) rand() / RAND_MAX * m_max_start_delay; + // Each kart starts at a different, random time, and the time is + // smaller depending on the difficulty. + m_start_delay = m_min_start_delay + + (float) rand() / RAND_MAX * (m_max_start_delay-m_min_start_delay); + + // Now check for a false start. If so, add 1 second penalty time. + if(rand() < RAND_MAX * m_false_start_probability) + { + m_start_delay+=stk_config->m_penalty_time; + return; + } } } // handleRaceStart //----------------------------------------------------------------------------- -void NewAIController::handleRescue(const float DELTA) +void SkiddingAI::handleRescue(const float dt) { // check if kart is stuck - if(m_kart->getSpeed()<2.0f && !m_kart->getKartAnimation() && + if(m_kart->getSpeed()<2.0f && !m_kart->getKartAnimation() && !m_world->isStartPhase()) { - m_time_since_stuck += DELTA; + m_time_since_stuck += dt; if(m_time_since_stuck > 2.0f) { new RescueAnimation(m_kart); @@ -591,7 +766,7 @@ void NewAIController::handleRescue(const float DELTA) //----------------------------------------------------------------------------- /** Decides wether to use nitro or not. */ -void NewAIController::handleNitroAndZipper() +void SkiddingAI::handleNitroAndZipper() { m_controls->m_nitro = false; // If we are already very fast, save nitro. @@ -607,8 +782,8 @@ void NewAIController::handleNitroAndZipper() // to use it, and we don't have a zipper or are not supposed to use // it (calculated). if( (m_kart->getEnergy()==0 || m_nitro_level==NITRO_NONE) && - (m_kart->getPowerup()->getType()!=PowerupManager::POWERUP_ZIPPER || - m_item_tactic==IT_TEN_SECONDS ) ) + (m_kart->getPowerup()->getType()!=PowerupManager::POWERUP_ZIPPER || + m_item_tactic==IT_TEN_SECONDS ) ) return; // If a parachute or anvil is attached, the nitro doesn't give much @@ -657,16 +832,16 @@ void NewAIController::handleNitroAndZipper() // Try to overtake a kart that is close ahead, except // when we are already much faster than that kart // -------------------------------------------------- - if(m_kart_ahead && - m_distance_ahead < overtake_distance && + if(m_kart_ahead && + m_distance_ahead < overtake_distance && m_kart_ahead->getSpeed()+5.0f > m_kart->getSpeed() ) { m_controls->m_nitro = true; return; } - if(m_kart_behind && - m_distance_behind < overtake_distance && + if(m_kart_behind && + m_distance_behind < overtake_distance && m_kart_behind->getSpeed() > m_kart->getSpeed() ) { // Only prevent overtaking on highest level @@ -677,7 +852,7 @@ void NewAIController::handleNitroAndZipper() } // handleNitroAndZipper //----------------------------------------------------------------------------- -void NewAIController::checkCrashes( const int STEPS, const Vec3& pos ) +void SkiddingAI::checkCrashes(int steps, const Vec3& pos ) { //Right now there are 2 kind of 'crashes': with other karts and another //with the track. The sight line is used to find if the karts crash with @@ -686,6 +861,20 @@ void NewAIController::checkCrashes( const int STEPS, const Vec3& pos ) //tell when a kart is going to get out of the track so it steers. m_crashes.clear(); + // If slipstream should be handled actively, trigger overtaking the + // kart which gives us slipstream if slipstream is ready + const SlipStream *slip=m_kart->getSlipstream(); + if(m_make_use_of_slipstream && slip->isSlipstreamReady() && + slip->getSlipstreamTarget()) + { + //printf("%s overtaking %s\n", m_kart->getIdent().c_str(), + // m_kart->getSlipstreamKart()->getIdent().c_str()); + // FIXME: we might define a minimum distance, and if the target kart + // is too close break first - otherwise the AI hits the kart when + // trying to overtake it, actually speeding the other kart up. + m_crashes.m_kart = slip->getSlipstreamTarget()->getWorldKartId(); + } + const size_t NUM_KARTS = m_world->getNumKarts(); //Protection against having vel_normal with nan values @@ -700,7 +889,13 @@ void NewAIController::checkCrashes( const int STEPS, const Vec3& pos ) vel_normal/=speed; int current_node = m_track_node; - for(int i = 1; STEPS > i; ++i) + if(steps<1 || steps>1000) + { + printf("Warning, incorrect STEPS=%d. kart_length %f velocity %f\n", + steps, m_kart_length, m_kart->getVelocityLC().getZ()); + steps=1000; + } + for(int i = 1; steps > i; ++i) { Vec3 step_coord = pos + vel_normal* m_kart_length * float(i); @@ -741,111 +936,76 @@ void NewAIController::checkCrashes( const int STEPS, const Vec3& pos ) } // checkCrashes //----------------------------------------------------------------------------- -float NewAIController::findNonCrashingAngle() +/** 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 SkiddingAI::findNonCrashingPoint(Vec3 *result) { - unsigned int current_sector = m_next_node_index[m_track_node]; - const Vec3 &xyz = m_kart->getXYZ(); - const Quad &q = QuadGraph::get()->getQuadOfNode(current_sector); - const Vec3 &right = q[2]; - const Vec3 &left = q[3]; - Vec3 final_right = q[2]; - Vec3 final_left = q[3]; + unsigned int sector = m_next_node_index[m_track_node]; + int target_sector; - float sign = 1; - float very_right = -atan2(right.getX()-xyz.getX(), - right.getZ()-xyz.getZ()); - float very_left = -atan2(left.getX()-xyz.getX(), - left.getZ()-xyz.getZ()); - if(very_left < very_right || very_right>M_PI*0.5f || - very_left < -M_PI*0.5f) + Vec3 direction; + Vec3 step_track_coord; + + // The original while(1) loop is replaced with a for loop to avoid + // infinite loops (which we had once or twice). Usually the number + // of iterations in the while loop is less than 7. + for(unsigned int i=0; i<100; i++) { - sign = -1; - very_right = -atan2(-right.getX()+xyz.getX(), - -right.getZ()+xyz.getZ()); - very_left = -atan2(-left.getX()+xyz.getX(), - -left.getZ()+xyz.getZ()); - } - float dist = 0; + //target_sector is the sector at the longest distance that we can drive + //to without crashing with the track. + target_sector = m_next_node_index[sector]; - while(dist<40.0f) - { - const Quad &q = QuadGraph::get()->getQuadOfNode(current_sector); - const Vec3 &right = q[2]; - const Vec3 &left = q[3]; + //direction is a vector from our kart to the sectors we are testing + direction = QuadGraph::get()->getQuadOfNode(target_sector).getCenter() + - m_kart->getXYZ(); - float angle_right = -atan2(sign*(right.getX()-xyz.getX()), - sign*(right.getZ()-xyz.getZ())); - float angle_left = -atan2(sign*(left.getX()-xyz.getX()), - sign*(left.getZ()-xyz.getZ())); -#ifdef DO_PRINTS - printf("angle %f %f %f %f\n", - very_left, angle_left, angle_right, very_right); -#endif - // Stop if the left and the right beam overlap. - if(angle_leftvery_left) - { - if(dist<0.1) - break; - break; + float len=direction.length_2d(); + unsigned int steps = (unsigned int)( len / m_kart_length ); + if( steps < 3 ) steps = 3; + + // That shouldn't happen, but since we had one instance of + // STK hanging, add an upper limit here (usually it's at most + // 20 steps) + if( steps>1000) steps = 1000; + + //Protection against having vel_normal with nan values + if(len>0.0f) { + direction*= 1.0f/len; } - if(angle_left very_right) - { - very_right = angle_right; - final_right = right; - } - dist += QuadGraph::get()->getDistanceToNext(current_sector, - m_successor_index[current_sector]); - current_sector = m_next_node_index[current_sector]; - } - m_last_target_point = (final_left+final_right)*0.5f; - float steer_angle = steerToPoint(m_last_target_point); -#ifdef AI_DEBUG - m_debug_left->setPosition(final_left.toIrrVector()); - m_debug_right->setPosition(final_right.toIrrVector()); - m_debug_sphere->setPosition(m_last_target_point.toIrrVector()); -#endif - return steer_angle; -} // findNonCrashingAngle + step_coord = m_kart->getXYZ()+direction*m_kart_length * float(i); -//----------------------------------------------------------------------------- -void NewAIController::reset() -{ - m_time_since_last_shot = 0.0f; - m_start_kart_crash_direction = 0; - m_curve_target_speed = m_kart->getCurrentMaxSpeed(); - m_curve_angle = 0.0; - m_time_till_start = -1.0f; - m_crash_time = 0.0f; - m_time_since_stuck = 0.0f; - m_kart_ahead = NULL; - m_distance_ahead = 0.0f; - m_kart_behind = NULL; - m_distance_behind = 0.0f; - m_track_node = QuadGraph::UNKNOWN_SECTOR; - AIBaseController::reset(); - QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node); - if(m_track_node==QuadGraph::UNKNOWN_SECTOR) - { - fprintf(stderr, "Invalid starting position for '%s' - not on track - can be ignored.\n", - m_kart->getIdent().c_str()); - m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ()); - } + QuadGraph::get()->spatialToTrack(&step_track_coord, step_coord, + sector ); + + float distance = fabsf(step_track_coord[0]); -} // reset + //If we are outside, the previous sector is what we are looking for + if ( distance + m_kart_width * 0.5f + > QuadGraph::get()->getNode(sector).getPathWidth() ) + { + *result = QuadGraph::get()->getQuadOfNode(sector).getCenter(); + return; + } + } + sector = target_sector; + } // for i<100 + *result = QuadGraph::get()->getQuadOfNode(sector).getCenter(); +} // 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 NewAIController::calcSteps() +int SkiddingAI::calcSteps() { int steps = int( m_kart->getVelocityLC().getZ() / m_kart_length ); if( steps < m_min_steps ) steps = m_min_steps; @@ -866,7 +1026,9 @@ int NewAIController::calcSteps() steps += WIDTH_STEPS; } #endif - return steps; + // The AI is driving significantly better with more steps, so for now + // add 5 additional steps. + return steps+5; } // calcSteps //----------------------------------------------------------------------------- @@ -876,21 +1038,23 @@ int NewAIController::calcSteps() * * The number of sectors that form the curve is dependant on the kart's speed. */ -void NewAIController::findCurve() +void SkiddingAI::findCurve() { float total_dist = 0.0f; int i; for(i = m_track_node; total_dist < m_kart->getVelocityLC().getZ(); i = m_next_node_index[i]) { - total_dist += QuadGraph::get()->getDistanceToNext(i, m_successor_index[i]); + total_dist += QuadGraph::get()->getDistanceToNext(i, + m_successor_index[i]); } m_curve_angle = - normalizeAngle(QuadGraph::get()->getAngleToNext(i, m_successor_index[i]) + normalizeAngle(QuadGraph::get()->getAngleToNext(i, + m_successor_index[i]) -QuadGraph::get()->getAngleToNext(m_track_node, - m_successor_index[m_track_node]) ); + m_successor_index[m_track_node]) ); m_curve_target_speed = m_kart->getCurrentMaxSpeed(); } // findCurve diff --git a/src/karts/controller/new_ai_controller.hpp b/src/karts/controller/skidding_ai.hpp similarity index 61% rename from src/karts/controller/new_ai_controller.hpp rename to src/karts/controller/skidding_ai.hpp index 471d30f79..1cc41beae 100644 --- a/src/karts/controller/new_ai_controller.hpp +++ b/src/karts/controller/skidding_ai.hpp @@ -2,6 +2,7 @@ // SuperTuxKart - a fun racing game with go-kart // Copyright (C) 2004-2005 Steve Baker // Copyright (C) 2006-2007 Eduardo Hernandez Munoz +// Copyright (C) 2010 Joerg Henrichs // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License @@ -17,17 +18,14 @@ // 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_NEW_AI_CONTROLLER_HPP -#define HEADER_NEW_AI_CONTROLLER_HPP +#ifndef HEADER_SKIDDING_AI_HPP +#define HEADER_SKIDDING_AI__HPP #include "karts/controller/ai_base_controller.hpp" -#include "utils/vec3.hpp" - -/* third coord won't be used */ +class Track; class LinearWorld; class QuadGraph; -class Track; namespace irr { @@ -40,7 +38,7 @@ namespace irr /** * \ingroup controller */ -class NewAIController : public AIBaseController +class SkiddingAI : public AIBaseController { private: /** How the AI uses nitro. */ @@ -64,54 +62,58 @@ private: } m_crashes; /*Difficulty handling variables*/ - float m_max_start_delay; //Delay before accelerating at the start of each - //race - int m_min_steps; //Minimum number of steps to check. If 0, the AI doesn't - //even has check around the kart, if 1, it checks around - //the kart always, and more than that will check the - //remaining number of steps in front of the kart, always - bool m_wait_for_players; //If true, the acceleration is decreased when - //the AI is in a better position than all the - //human players. - float m_max_handicap_accel; //The allowed maximum speed, in percentage, - //from 0.0 to 1.0. Used only when - //m_wait_for_players == true. - ItemTactic m_item_tactic; //How are items going to be used? + /** Chance of a false start. */ + float m_false_start_probability; + /** The minimum delay time before a AI kart starts. */ + float m_min_start_delay; + /** The maximum delay time before an AI kart starts. */ + float m_max_start_delay; + /** The actual start delay used. */ + float m_start_delay; + + /** Minimum number of steps to check. If 0, the AI doesn't even has check + * around the kart, if 1, it checks around the kart always, and more + * than that will check the remaining number of steps in front of the + * kart, always. */ + int m_min_steps; + /** If true, the acceleration is decreased when the AI is in a better + * position than all the human players. */ + bool m_wait_for_players; + + /** The allowed maximum speed in percent of the kart's maximum speed. */ + float m_max_handicap_speed; + + /** How are items going to be used? */ + ItemTactic m_item_tactic; /** True if the kart should try to pass on a bomb to another kart. */ - bool m_handle_bomb; + + /** True if the AI should avtively try to make use of slipstream. */ + bool m_make_use_of_slipstream; + /*General purpose variables*/ - //The crash percentage is how much of the time the AI has been crashing, - //if the AI has been crashing for some time, use the rescue. - float m_crash_time; /** 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; /** Time an item has been collected and not used. */ float m_time_since_last_shot; - - float m_time_till_start; //Used to simulate a delay at the start of the - //race, since human players don't accelerate - //at the same time and rarely repeat the a - //previous timing. - + float m_curve_target_speed; float m_curve_angle; - /** The point the kart was aiming at when it was on track last. */ - Vec3 m_last_target_point; - float m_time_since_stuck; int m_start_kart_crash_direction; //-1 = left, 1 = right, 0 = no crash. @@ -119,7 +121,6 @@ private: /** For debugging purpose: a sphere indicating where the AI * is targeting at. */ irr::scene::ISceneNode *m_debug_sphere; - irr::scene::ISceneNode *m_debug_left, *m_debug_right; /*Functions called directly from update(). They all represent an action *that can be done, and end up setting their respective m_controls @@ -127,15 +128,16 @@ private: *specific action (more like, associated with inaction). */ void handleRaceStart(); - void handleAcceleration(const float DELTA); + void handleAcceleration(const float dt); void handleSteering(float dt); - void handleItems(const float DELTA, const int STEPS); - void handleRescue(const float DELTA); + void handleItems(const float dt); + void handleRescue(const float dt); void handleBraking(); void handleNitroAndZipper(); void computeNearestKarts(); - void checkCrashes(const int STEPS, const Vec3& pos); - float findNonCrashingAngle(); + + void checkCrashes(int steps, const Vec3& pos); + void findNonCrashingPoint(Vec3 *result); int calcSteps(); void findCurve(); @@ -143,15 +145,11 @@ protected: virtual unsigned int getNextSector(unsigned int index); public: - NewAIController(AbstractKart *kart); - virtual ~NewAIController(); + SkiddingAI(AbstractKart *kart); + ~SkiddingAI(); virtual void update (float delta) ; virtual void reset (); - virtual const irr::core::stringw& getN() const - { - static irr::core::stringw name("(NewAI)"); - return name; - } // getName + virtual const irr::core::stringw& getNamePostfix() const; }; #endif diff --git a/src/modes/world.cpp b/src/modes/world.cpp index 57043ba01..957523f99 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -34,9 +34,9 @@ #include "io/file_manager.hpp" #include "items/projectile_manager.hpp" #include "karts/controller/default_ai_controller.hpp" -#include "karts/controller/new_ai_controller.hpp" #include "karts/controller/player_controller.hpp" #include "karts/controller/end_controller.hpp" +#include "karts/controller/skidding_ai.hpp" #include "karts/kart.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/profile_world.hpp" @@ -251,7 +251,7 @@ Controller* World::loadAIController(AbstractKart *kart) controller = new DefaultAIController(kart); break; case 1: - controller = new NewAIController(kart); + controller = new SkiddingAI(kart); break; default: fprintf(stderr, "Warning: Unknown robot, using default.\n");