AI item handling improvements (#3143)

* Prepares for advanced item and nitro usage strategy

Replace the bool determining if usage is random or not by an int allowing several levels of AI item usage.

Add NITRO_ADVANCED mode for nitro usage.

* Adapt for more item and nitro levels

The XML files have to be changed. The logic for the item_skill still has to be done.

* Preparation for the preferred kart in GP

* Preparation for the preferred kart in GP

* Preparation for the preferred kart in GP

* Prepares for advanced item and nitro usage strategy

*  Prepares for advanced item usage strategy

* Preparation for the preferred kart in GP

* Parametrized AI_skill level

* Fix typo

* Fix typo

* Fix typo

* Fire items with a shield on in unrestricted mode

* New function allowing AI to know what flyable is incoming

* New function allowing AI to know what flyable is incoming

* Fix typo

* Update bubble gum AI to new system

In addition of making some functionality depends on AI levels, there are a few overall improvements for the better AIs :
1)Remove the "drop the gum in the last lap" logic. Bad strategy since ever the shield has been added to the gum
2)The protection against flyable is not used if the user holds a swatter and the flyable is a plunger 
3)Holding a swatter no longer blocks the AI from using the shield against a flyable nor from using the gum behind
4)The shield is used to remove bad attachments (bomb, parachute, anvil)
5)Reduce the distance with the kart behind when dropping the gum to reduce misses

* Update swatter AI to new system

The swatter could benefit from several kind of usage improvement. This patch introduce only one, but probably the more important : the better AIs now use it to remove bad attachments

* Fixing #3139

* Fix compile error

* Fix

* Fix compilation

* Fix compilation

* Fix compilation

* Update variable

* Update variable

* Fix non-breaking space issue

* Fix typos

* Revert for compilation

* Revert for compilation

* Update cake AI to new system

* Redefine HandleItems to be position aware

* Redefine HandleItems to be position aware

Also changes bowling ball for the new system.

* Activate shield before hitting box or bad item

Meant for higher AIs.

* Increase lookup distance

Some distance will be needed for the switch

* Temporary fix to avoid a nitro regression

* Fix typo

* Fix typo

* Bubble gum improvements ; explicitely calculate distances to items

* Update Switch to the new system ; use item-distance awareness

Also fix a missing parenthesis

* Change nitro-usage to a numerical parameter

* Change nitro-usage to a numerical parameter

Also update comments to explain the new item-skill

* Change nitro-usage to a numerical parameter

* Change nitro-usage to a numerical parameter

* Change nitro-usage to a numerical parameter

* Update handleNitroAndZipper to the new system

Also fix a naming error, remove the 95% speed limitation on nitro usage

* Adds a small engine boost to nitro

Currently, nitro is very counter-intuitive as it only affects max_speed. This change corrects it. The effect remains small to not upset balance. It is mostly felt when below max_speed, so the can of nitro potential power doesn't change much.

* Improve AI nitro handling

Summary :
1)Makes the AI use nitro by bursts, greatly improving its efficiency
2)Makes the AI use nitro when close to max speed (to unlock the max speed increase)
3)Remove the overtaking logic as the AI will now use its nitro anyway
4)The AI tries to keep a reserve of nitro for use towards the end of the race.

* Allows estimated finish time to be checked at any lap, fix a crash

* Makes the AI use its nitro reserves

* Better tuning for nitro reserve usage to reduce the probability of unused nitro

* Remember last used powerup for AI

* Remember last used powerup for AI

* Remember last used powerup for AI

* Remember last used powerup for AI

* Remember last used powerup for AI

* Remember last used powerup for AI

* Fix compilation

* AI don't wait between usage of items of different types

* Use floats for Parachute duration

* Revert

* Nitro use when trying to pass a bomb improved

* Fix broken time check

* Take into account the kart-specific fadeout to calculate time between bursts

* Improvements to bursts and to use of the reserve at the end

* Improve comments explaining item_usage_skill and nitro_usage

* Make LastUsedPowerup a PowerupType

* Make LastUsedPowerup a PowerupType

* Make LastUsedPowerup a PowerupType

* Make LastUsedPowerup a PowerupType

* Make last_used_powerup a PowerupType

* Make last_used_powerup a PowerupType

* Fix compilation

* Fix compilation

* Revert

* Revert

* Finally fix compilation

* Finally fix compilation

* Change shield radius per difficulty

A reduced non-null shield radius reduces false positives as long as it is big enough to get a few frames during which the object is inside the radius.

* Improve Projectile closeness management

* Improve projectileCloseType

* Improve projectileCloseType

* Fixes indentation

* Fix lastUsedPowerup initialization

* Fix the last used powerup type

* Rename projectileCloseType

* Rename projectilCloseType

* Clarify comment and update projectileCloseType to new name

* Reuse a member RandomGenerator

* Fixes getLastUsedPowerup type

* Fixes comparison

* Fixes type
This commit is contained in:
Alayan-stk-2 2018-04-01 03:09:57 +02:00 committed by auriamg
parent a384866e6a
commit 8fae521a94
16 changed files with 772 additions and 227 deletions

View File

@ -225,10 +225,12 @@
min/max-start-delay: Minimum and maximum start delay.
See http://www.humanbenchmark.com/tests/reactiontime/stats.php
Average reaction time is around 0.215 s.
nitro-usage: "none", "some", "all": if nitro should be used, and
how much the AI should try to use it good.
non-random-item-usage: If true, use items in a sophisticated way,
otherwise use items randomly.
nitro-usage: Integer determining how well the AI uses nitro, from 0 to 4
0 corresponds to no use ; while 1 to 4 corresponds to various degrees
of quality use (using it immediately for 1 to more context-aware strategies)
item-skill: Integer determining how well the AI use items, from 0 to 5
0 corresponds to no use ; 1 to use after a random time ; while 2 to 5 use
more advanced tactics
collect-avoid-items: if the AI should collect and avoid items,
or just ignore them.
handle-bomb: If the AI should actively try to pass on a bomb.
@ -287,8 +289,8 @@
shield-incoming-radius="0"
false-start-probability="0.08"
min-start-delay="0.3" max-start-delay="0.5"
nitro-usage="none"
non-random-item-usage="false"
nitro-usage="0"
item-skill="1"
collect-avoid-items="false"
handle-bomb="false"
speed-cap="-10:1.0 -5:0.9 5:0.8 20:0.7 50:0.6"
@ -305,8 +307,8 @@
shield-incoming-radius="10"
false-start-probability="0.04"
min-start-delay="0.25" max-start-delay="0.4"
nitro-usage="some"
non-random-item-usage="true"
nitro-usage="1"
item-skill="2"
collect-avoid-items="true"
handle-bomb="false"
speed-cap="10:1.0 50:0.8"
@ -320,11 +322,11 @@
straight-length-for-zipper="35"
use-slipstream="true"
disable-slipstream-usage="false"
shield-incoming-radius="10"
shield-incoming-radius="8"
false-start-probability="0.01"
min-start-delay="0.15" max-start-delay="0.28"
nitro-usage="all"
non-random-item-usage="true"
nitro-usage="2"
item-skill="3"
collect-avoid-items="true"
handle-bomb="true"
speed-cap="20:1.0 50:0.8"
@ -338,11 +340,11 @@
straight-length-for-zipper="35"
use-slipstream="true"
disable-slipstream-usage="false"
shield-incoming-radius="10"
shield-incoming-radius="6"
false-start-probability="0.0"
min-start-delay="0.15" max-start-delay="0.2"
nitro-usage="all"
non-random-item-usage="true"
nitro-usage="3"
item-skill="4"
collect-avoid-items="true"
handle-bomb="true"
speed-cap="0:1.0"

View File

@ -163,7 +163,7 @@ void STKConfig::init_defaults()
UNDEFINED;
m_physics_fps = -100;
m_bubblegum_counter = -100;
m_shield_restrict_weapos = false;
m_shield_restrict_weapons = false;
m_max_karts = -100;
m_max_skidmarks = -100;
m_min_kart_version = -100;
@ -342,8 +342,8 @@ void STKConfig::getAllData(const XMLNode * root)
if(const XMLNode *bubblegum_node= root->getNode("bubblegum"))
{
bubblegum_node->get("disappear-counter", &m_bubblegum_counter );
bubblegum_node->get("restrict-weapons", &m_shield_restrict_weapos);
bubblegum_node->get("disappear-counter", &m_bubblegum_counter );
bubblegum_node->get("restrict-weapons", &m_shield_restrict_weapons);
}
if(const XMLNode *explosion_node= root->getNode("explosion"))

View File

@ -72,7 +72,7 @@ public:
float m_item_switch_time; /**< Time items will be switched. */
int m_bubblegum_counter; /**< How many times bubble gums must be
driven over before they disappear. */
bool m_shield_restrict_weapos; /**<Wether weapon usage is punished. */
bool m_shield_restrict_weapons; /**<Wether weapon usage is punished. */
float m_explosion_impulse_objects; /**<Impulse of explosion on moving
objects, e.g. road cones, ... */
float m_penalty_time; /**< Penalty time when starting too

View File

@ -257,7 +257,7 @@ void Powerup::use()
case PowerupManager::POWERUP_RUBBERBALL:
case PowerupManager::POWERUP_BOWLING:
case PowerupManager::POWERUP_PLUNGER:
if(stk_config->m_shield_restrict_weapos)
if(stk_config->m_shield_restrict_weapons)
m_kart->setShieldTime(0.0f); // make weapon usage destroy the shield
Powerup::adjustSound();
m_sound_use->play();

View File

@ -153,3 +153,32 @@ bool ProjectileManager::projectileIsClose(const AbstractKart * const kart,
}
return false;
} // projectileIsClose
// -----------------------------------------------------------------------------
/** Returns an int containing the numbers of a given flyable in a given radius
* around the kart
* \param kart The kart for which the test is done.
* \param radius Distance within which the projectile must be.
* \param type The type of projectile checked
*/
int ProjectileManager::getNearbyProjectileCount(const AbstractKart * const kart,
float radius, PowerupManager::PowerupType type)
{
float r2 = radius*radius;
int projectileCount = 0;
for(Projectiles::iterator i = m_active_projectiles.begin();
i != m_active_projectiles.end(); i++)
{
if ((*i)->getType() == type)
{
float dist2 = (*i)->getXYZ().distance2(kart->getXYZ());
if(dist2<r2)
{
projectileCount++;
}
}
}
return projectileCount;
} // getNearbyProjectileCount

View File

@ -65,6 +65,9 @@ public:
void removeTextures ();
bool projectileIsClose(const AbstractKart * const kart,
float radius);
int getNearbyProjectileCount(const AbstractKart * const kart,
float radius, PowerupManager::PowerupType type);
// ------------------------------------------------------------------------
/** Adds a special hit effect to be shown.
* \param hit_effect The hit effect to be added. */

View File

@ -286,7 +286,20 @@ public:
* e.g. in Ghost.
* \param category Which category to report on. */
virtual float getSpeedIncreaseTimeLeft(unsigned int category) const = 0;
// ------------------------------------------------------------------------
/** Sets the kart AI boost state.
* Not pure abstract, since there is no need to implement this e.g. in Ghost.
* \param boosted True if a boost should be applied. */
virtual void setBoostAI(bool boosted) = 0;
// ------------------------------------------------------------------------
/** Returns the kart AI boost state.
* Not pure abstract, since there is no need to implement this e.g. in Ghost. */
virtual bool getBoostAI() const = 0;
// ------------------------------------------------------------------------
/** Sets an increased maximum speed for a category.
* \param category The category for which to set the higher maximum speed.
* \param add_speed How much speed (in m/s) is added to the maximum speed.
@ -379,6 +392,9 @@ public:
/** Returns the current powerup. */
virtual Powerup *getPowerup() = 0;
// ------------------------------------------------------------------------
/** Returns the last used powerup type. */
virtual PowerupManager::PowerupType getLastUsedPowerup() = 0;
// ------------------------------------------------------------------------
/** Returns a points to this kart's graphical effects. */
virtual KartGFX* getKartGFX() = 0;
// ------------------------------------------------------------------------

View File

@ -43,9 +43,9 @@ AIProperties::AIProperties(RaceManager::Difficulty difficulty)
m_make_use_of_slipstream = false;
m_collect_avoid_items = false;
m_handle_bomb = false;
m_item_usage_non_random = false;
m_item_usage_skill = 0;
m_disable_slipstream_usage = false;
m_nitro_usage = NITRO_NONE;
m_nitro_usage = 0;
} // AIProperties
@ -65,7 +65,7 @@ void AIProperties::load(const XMLNode *ai_node)
ai_node->get("straight-length-for-zipper",&m_straight_length_for_zipper);
ai_node->get("rb-skid-probability", &m_skid_probability );
ai_node->get("speed-cap", &m_speed_cap );
ai_node->get("non-random-item-usage", &m_item_usage_non_random );
ai_node->get("item-skill", &m_item_usage_skill );
ai_node->get("collect-avoid-items", &m_collect_avoid_items );
ai_node->get("handle-bomb", &m_handle_bomb );
ai_node->get("skidding-threshold", &m_skidding_threshold );
@ -73,21 +73,8 @@ void AIProperties::load(const XMLNode *ai_node)
ai_node->get("false-start-probability", &m_false_start_probability );
ai_node->get("min-start-delay", &m_min_start_delay );
ai_node->get("max-start-delay", &m_max_start_delay );
ai_node->get("nitro-usage", &m_nitro_usage );
std::string s;
ai_node->get("nitro-usage", &s);
if(s=="none")
m_nitro_usage = NITRO_NONE;
else if(s=="some")
m_nitro_usage = NITRO_SOME;
else if(s=="all")
m_nitro_usage = NITRO_ALL;
else
{
Log::fatal("AIProperties",
"Incorrect nitro-usage '%s' in AI '%s'.",s.c_str(),
m_ident.c_str());
}
// We actually need the square of the distance later
m_bad_item_closeness_2 *= m_bad_item_closeness_2;

View File

@ -111,11 +111,15 @@ protected:
/** If the AI should actively try to pass on a bomb. */
bool m_handle_bomb;
/** True if items should be used better (i.e. non random). */
bool m_item_usage_non_random;
/** Determines the strategies used by the AI for items. 0 is no use,
1 is random use ; 2 to 5 use varying tactics, with 2 having the worst
and 5 the best. */
int m_item_usage_skill;
/** How the AI uses nitro. */
enum {NITRO_NONE, NITRO_SOME, NITRO_ALL} m_nitro_usage;
/** How the AI uses nitro. 0 correspond to no use ; 1 to immediate use
2 to 4 to various levels of mastery (the AI tries to accumulate a reserve
and to use bursts whose size/spacing varies according to the level). */
int m_nitro_usage;
/** TODO: ONLY USE FOR OLD SKIDDING! CAN BE REMOVED once the new skidding
* works as expected.

View File

@ -318,38 +318,41 @@ void SkiddingAI::update(float dt)
// of us.
bool commands_set = false;
if(m_ai_properties->m_handle_bomb &&
m_kart->getAttachment()->getType()==Attachment::ATTACH_BOMB &&
m_kart_ahead )
m_kart->getAttachment()->getType()==Attachment::ATTACH_BOMB)
{
// Use nitro if the kart is far ahead, or faster than this kart
m_controls->setNitro(m_distance_ahead>10.0f ||
m_kart_ahead->getSpeed() > m_kart->getSpeed());
// If we are close enough, try to hit this kart
if(m_distance_ahead<=10)
{
Vec3 target = m_kart_ahead->getXYZ();
//TODO : add logic to allow an AI kart to pass the bomb to a kart
// close behind by slowing/steering slightly
if ( m_kart_ahead != m_kart->getAttachment()->getPreviousOwner())
{
// Use nitro if the kart is far ahead, or faster than this kart
handleNitroAndZipper();
// If we are close enough, try to hit this kart
if(m_distance_ahead<=10)
{
Vec3 target = m_kart_ahead->getXYZ();
// If we are faster, try to predict the point where we will hit
// the other kart
if((m_kart_ahead->getSpeed() < m_kart->getSpeed()) &&
!m_kart_ahead->isGhostKart())
{
float time_till_hit = m_distance_ahead
/ (m_kart->getSpeed()-m_kart_ahead->getSpeed());
target += m_kart_ahead->getVelocity()*time_till_hit;
}
float steer_angle = steerToPoint(target);
setSteering(steer_angle, dt);
commands_set = true;
}
handleRescue(dt);
// If we are faster, try to predict the point where we will hit
// the other kart
if((m_kart_ahead->getSpeed() < m_kart->getSpeed()) &&
!m_kart_ahead->isGhostKart())
{
float time_till_hit = m_distance_ahead
/ (m_kart->getSpeed()-m_kart_ahead->getSpeed());
target += m_kart_ahead->getVelocity()*time_till_hit;
}
float steer_angle = steerToPoint(target);
setSteering(steer_angle, dt);
commands_set = true;
}
handleRescue(dt);
}
}
if(!commands_set)
{
/*Response handling functions*/
handleAcceleration(dt);
handleSteering(dt);
handleItems(dt);
handleSteering(dt); //Item handling relocated there to be direction aware
handleRescue(dt);
handleBraking();
// If a bomb is attached, nitro might already be set.
@ -489,6 +492,8 @@ void SkiddingAI::handleSteering(float dt)
}
//If we are going to crash against a kart, avoid it if it doesn't
//drives the kart out of the road
//TODO : adds item handling to use a cake if available to
//open the road
else if( m_crashes.m_kart != -1 && !m_crashes.m_road )
{
//-1 = left, 1 = right, 0 = no crash.
@ -550,6 +555,9 @@ void SkiddingAI::handleSteering(float dt)
m_curve[CURVE_AIM]->addPoint(aim_point);
#endif
//Manage item utilisation
handleItems(dt, &aim_point, last_node);
// Potentially adjust the point to aim for in order to either
// aim to collect item, or steer to avoid a bad item.
if(m_ai_properties->m_collect_avoid_items)
@ -1132,16 +1140,19 @@ void SkiddingAI::evaluateItems(const Item *item, Vec3 kart_aim_direction,
//-----------------------------------------------------------------------------
/** 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.
* Level 0 "AI" : do nothing (not used by default)
* Level 1 "AI" : use items after a random time
* Level 2 to 5 AI : strategy detailed before each item
* Each successive level is overall stronger (5 the strongest, 2 the weakest of
* non-random strategies), but two levels may share a strategy for a given item.
* (level 5 is not yet used ; meant for SuperTux GP preferred karts or boss races)
* \param dt Time step size.
* TODO: Implications of Bubble-Shield for AI's powerup-handling
* STATE: shield on -> avoid usage of offensive items (with certain tolerance)
* STATE: swatter on -> avoid usage of shield
*/
void SkiddingAI::handleItems(const float dt)
void SkiddingAI::handleItems(const float dt, const Vec3 *aim_point, int last_node)
{
m_controls->setFire(false);
if(m_kart->getKartAnimation() ||
@ -1149,7 +1160,15 @@ void SkiddingAI::handleItems(const float dt)
return;
m_time_since_last_shot += dt;
//time since last shot is meant to avoid using the same item
//several times in rapid succession ; not to wait to use a useful
//collected item
if ( m_kart->getPowerup()->getType() != m_kart->getLastUsedPowerup() )
{
m_time_since_last_shot = 50.0f; //The AI may wait if the value is low, so set a high value
}
if (m_superpower == RaceManager::SUPERPOWER_NOLOK_BOSS)
{
m_controls->setLookBack(m_kart->getPowerup()->getType() ==
@ -1172,12 +1191,41 @@ void SkiddingAI::handleItems(const float dt)
}
return;
}
int ai_skill = 0;
if (m_ai_properties->m_item_usage_skill > 0)
{
if (m_ai_properties->m_item_usage_skill > 5)
{
ai_skill = 5;
}
else
{
ai_skill = m_ai_properties->m_item_usage_skill;
}
}
if (m_kart->getBoostAI() == true && ai_skill < 5)
{
ai_skill++; //possible improvement : make the boost amplitude pulled from config
}
// Tactic 1: wait ten seconds, then use item
// Tactic 0: don't use item
// -----------------------------------------
if(!m_ai_properties->m_item_usage_non_random)
if(ai_skill == 0)
{
if( m_time_since_last_shot > 10.0f )
return;
}
// Tactic 1: wait between 5 and 10 seconds, then use item
// ------------------------------------------------------
if(ai_skill == 1)
{
int random_t = 0;
random_t = m_random_skid.get(6); //Reuse the random skid generator
random_t = random_t + 5;
if( m_time_since_last_shot > random_t )
{
m_controls->setFire(true);
m_time_since_last_shot = 0.0f;
@ -1185,67 +1233,206 @@ void SkiddingAI::handleItems(const float dt)
return;
}
// Tactic 2: calculate
// -------------------
// Tactics 2 to 5: calculate
// -----------------------------------------
float min_bubble_time = 2.0f;
int projectile_types[4]; //[3] basket, [2] cakes, [1] plunger, [0] bowling
projectile_types[0] = projectile_manager->getNearbyProjectileCount(m_kart,
m_ai_properties->m_shield_incoming_radius, PowerupManager::POWERUP_BOWLING);
projectile_types[1] = projectile_manager->getNearbyProjectileCount(m_kart,
m_ai_properties->m_shield_incoming_radius, PowerupManager::POWERUP_PLUNGER);
projectile_types[2] = projectile_manager->getNearbyProjectileCount(m_kart,
m_ai_properties->m_shield_incoming_radius, PowerupManager::POWERUP_CAKE);
projectile_types[3] = projectile_manager->getNearbyProjectileCount(m_kart,
m_ai_properties->m_shield_incoming_radius, PowerupManager::POWERUP_RUBBERBALL);
bool projectile_is_close = false;
float shield_radius = m_ai_properties->m_shield_incoming_radius;
if (m_kart->getBoostAI() == true)
{
if (shield_radius == 0)
{
shield_radius = 15;
}
else if (shield_radius >= 3)
{
shield_radius = shield_radius - 2;
}
}
projectile_is_close = projectile_manager->projectileIsClose(m_kart, shield_radius);
// Preparing item list for item aware actions
// Angle to aim_point
Vec3 kart_aim_direction = *aim_point - m_kart->getXYZ();
// Make sure we have a valid last_node
if(last_node==Graph::UNKNOWN_SECTOR)
last_node = m_next_node_index[m_track_node];
int node = m_track_node;
float distance = 0;
std::vector<const Item *> items_to_collect;
std::vector<const Item *> items_to_avoid;
// 1) Filter and sort all items close by
// -------------------------------------
const float max_item_lookahead_distance = 20.0f;
while(distance < max_item_lookahead_distance)
{
int n_index= DriveGraph::get()->getNode(node)->getIndex();
const std::vector<Item *> &items_ahead =
ItemManager::get()->getItemsInQuads(n_index);
for(unsigned int i=0; i<items_ahead.size(); i++)
{
evaluateItems(items_ahead[i], kart_aim_direction,
&items_to_avoid, &items_to_collect);
} // for i<items_ahead;
distance += DriveGraph::get()->getDistanceToNext(node,
m_successor_index[node]);
node = m_next_node_index[node];
// Stop when we have reached the last quad
if(node==last_node) break;
} // while (distance < max_item_lookahead_distance)
//items_to_avoid and items_to_collect now contain the closest item information needed after
//What matters is (a) if the lists are void ; (b) if they are not, what kind of item it is
switch( m_kart->getPowerup()->getType() )
{
// Level 2 : Use the shield immediately after a wait time
// Level 3 : Use the shield against flyables except cakes. Use the shield against bad attachments.
// Use the bubble gum against an enemy close behind, except if holding a swatter.
// Level 4 : Level 3, and protect against cakes too, and use before hitting gum/banana
// Level 5 : Level 4, and use before hitting item box, and let plunger hit
// (can use the shield after)
case PowerupManager::POWERUP_BUBBLEGUM:
{
Attachment::AttachmentType type = m_kart->getAttachment()->getType();
// Don't use shield when we have a swatter.
if( type == Attachment::ATTACH_SWATTER)
break;
Attachment::AttachmentType type = m_kart->getAttachment()->getType();
if((ai_skill == 2) && (m_time_since_last_shot > 2.0f))
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
// Check if a flyable (cake, ...) is close. If so, use bubblegum
// as shield
if(ai_skill == 3) //don't protect against cakes
{
if( !m_kart->isShielded() && projectile_is_close
&& projectile_types[2] == 0)
{
//don't discard swatter against plunger
if( projectile_types[1] == 0
|| (projectile_types[1] >= 1 && type != Attachment::ATTACH_SWATTER))
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
}
}
else if(ai_skill == 4)
{
if( !m_kart->isShielded() && projectile_is_close)
{
//don't discard swatter against plunger
if( projectile_types[1] == 0
|| (projectile_types[1] >= 1 && type != Attachment::ATTACH_SWATTER))
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
}
}
else if (ai_skill == 5) //don't protect against plungers alone TODO : activate after plunger hit
{
if( !m_kart->isShielded() && projectile_is_close)
{
if (projectile_types[0] >=1 || projectile_types[2] >=1 || projectile_types[3] >=1 )
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
}
}
// Check if a flyable (cake, ...) is close. If so, use bubblegum
// as shield
if( !m_kart->isShielded() &&
projectile_manager->projectileIsClose(m_kart,
m_ai_properties->m_shield_incoming_radius) )
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
// Use shield to remove bad attachments
if( type == Attachment::ATTACH_BOMB
|| type == Attachment::ATTACH_PARACHUTE
|| type == Attachment::ATTACH_ANVIL )
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
// Use shield if kart is going to hit a bad item (banana or bubblegum)
if((ai_skill == 4) || (ai_skill == 5))
{
if( !m_kart->isShielded() && items_to_avoid.size()>0)
{
float d = (items_to_avoid[0]->getXYZ() - m_kart->getXYZ()).length2();
if ((ai_skill == 4 && d < 1.5f) || (ai_skill == 5 && d < 0.7f))
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
}
}
// Use shield if kart is going to hit an item box
if (ai_skill == 5)
{
if( !m_kart->isShielded() && items_to_collect.size()>0)
{
float d = (items_to_collect[0]->getXYZ() - m_kart->getXYZ()).length2();
if ((items_to_collect[0]->getType() == Item::ITEM_BONUS_BOX) && (d < 0.7f))
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
}
}
// Avoid dropping all bubble gums one after another
if( m_time_since_last_shot < 2.0f) break;
// Avoid dropping all bubble gums one after another
if( m_time_since_last_shot < 3.0f) break;
// Use bubblegum 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 be hit by the bubble gum anyway).
// Should we check the speed of the kart as well? I.e. only drop if
// the kart behind is faster? Otoh this approach helps preventing an
// overtaken kart to overtake us again.
if(m_distance_behind < 15.0f && m_distance_behind > 3.0f )
{
m_controls->setFire(true);
m_controls->setLookBack(true);
break;
}
// If this kart is in its last lap, drop bubble gums at every
// opportunity, since this kart won't envounter them anymore.
LinearWorld *lin_world = dynamic_cast<LinearWorld*>(World::getWorld());
if(m_time_since_last_shot > 3.0f &&
lin_world &&
lin_world->getKartLaps(m_kart->getWorldKartId())
== race_manager->getNumLaps()-1)
{
m_controls->setFire(true);
m_controls->setLookBack(true);
break;
}
break; // POWERUP_BUBBLEGUM
}
// Use bubblegum 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 be hit by the bubble gum anyway).
// Should we check the speed of the kart as well? I.e. only drop if
// the kart behind is faster? Otoh this approach helps preventing an
// overtaken kart to overtake us again.
if(m_distance_behind < 10.0f && m_distance_behind > 3.0f )
{
m_controls->setFire(true);
m_controls->setLookBack(true);
break;
}
break; // POWERUP_BUBBLEGUM
}
// Level 2 : Use the cake against any close enemy, with priority to those ahead
// Level 3 : Don't fire on slower karts
// Level 4 : Same as level 3 TODO : Fire if the kart has a swatter which may hit us
// Level 5 : Same as level 4 TODO : Don't fire on a shielded kart if we're just behind (gum)
case PowerupManager::POWERUP_CAKE:
{
// if the kart has a shield, do not break it by using a cake.
if(m_kart->getShieldTime() > min_bubble_time)
if((m_kart->getShieldTime() > min_bubble_time) && (stk_config->m_shield_restrict_weapons == true))
break;
// Leave some time between shots
if(m_time_since_last_shot<3.0f) break;
if(m_time_since_last_shot<2.0f) break;
// Do not fire if the kart is driving too slow
bool kart_behind_is_slow =
@ -1267,9 +1454,12 @@ void SkiddingAI::handleItems(const float dt)
// the kart anyway, or that this might force the kart ahead to
// use its nitro/zipper (and then we will shoot since then the
// kart is faster).
if ((fire_backwards && kart_behind_is_slow) ||
(!fire_backwards && kart_ahead_is_slow) )
break;
if(ai_skill >= 3)
{
if ((fire_backwards && kart_behind_is_slow) ||
(!fire_backwards && kart_ahead_is_slow) )
break;
}
// Don't fire if the kart we are aiming at is invulnerable.
if ((fire_backwards && m_kart_behind && m_kart_behind->isInvulnerable()) ||
@ -1287,15 +1477,20 @@ void SkiddingAI::handleItems(const float dt)
break;
} // POWERUP_CAKE
// Level 2 : Use the bowling ball against enemies with a 5 second delay
// Level 3 : Only 3 seconds of delay
// Level 4 : Same as level 3
// Level 5 : Same as level 4
case PowerupManager::POWERUP_BOWLING:
{
// if the kart has a shield, do not break it by using a bowling ball.
if(m_kart->getShieldTime() > min_bubble_time)
if((m_kart->getShieldTime() > min_bubble_time) && (stk_config->m_shield_restrict_weapons == true))
break;
// 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;
if(ai_skill == 2 && m_time_since_last_shot < 5.0f) break;
if(ai_skill >= 3 && m_time_since_last_shot < 3.0f) break;
// Consider angle towards karts
bool straight_behind = false;
bool straight_ahead = false;
@ -1347,7 +1542,7 @@ void SkiddingAI::handleItems(const float dt)
case PowerupManager::POWERUP_PLUNGER:
{
// if the kart has a shield, do not break it by using a plunger.
if(m_kart->getShieldTime() > min_bubble_time)
if((m_kart->getShieldTime() > min_bubble_time) && (stk_config->m_shield_restrict_weapons == true))
break;
// Leave more time after a plunger, since it will take some
@ -1368,14 +1563,131 @@ void SkiddingAI::handleItems(const float dt)
break;
} // POWERUP_PLUNGER
// Level 2 : Use the switch after a wait time
// Level 3 : Same as level 2 but don't fire if close to a good item
// Level 4 : Same as level 3 and fire if very close to a bad item
// Level 5 : Use if it makes a better item available, or if very close
// to a bad item. Don't use it if too close of a good item.
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->setFire(true);
break; // POWERUP_SWITCH
{
// It's extremely unlikely two switches are used close one after another
if(ai_skill == 2)
{
if (m_time_since_last_shot > 2.0f)
{
m_controls->setFire(true);
break;
}
}
else if((ai_skill == 3) || (ai_skill == 4))
{
if( (ai_skill == 4) && items_to_avoid.size() > 0)
{
float d = (items_to_avoid[0]->getXYZ() - m_kart->getXYZ()).length2();
if (d < 2.0f)
{
m_controls->setFire(true);
break;
}
}
else if (items_to_collect.size() > 0)
{
float d = (items_to_collect[0]->getXYZ() - m_kart->getXYZ()).length2();
if (d > 10.0f)
{
m_controls->setFire(true);
break;
}
}
else if (m_time_since_last_shot > 2.0f)
{
m_controls->setFire(true);
break;
}
}
//TODO : retrieve ranking powerup class and use it to evaluate the best item
// available depending of if the switch is used or not
// In the mean time big nitro > item box > small nitro
//TODO : make steering switch-aware so that the kart goes towards a bad item
// and use the switch at the last moment
//It would also be possible but complicated to check if using the switch will
//cause another kart not far from taking a bad item instead of a good one
//It should also be possible but complicated to discard items when a good
//and a bad one are two close from one another
else if(ai_skill == 5)
{
//First step : identify the best available item
int i;
int bad = 0;
int good = 0;
//Good will store 1 for nitro, big or small, 2 for item box
//Big nitro are usually hard to take for the AI
for(i=items_to_collect.size()-1; i>=0; i--)
{
if (items_to_collect[i]->getType() == Item::ITEM_BONUS_BOX)
{
good = 2;
i = -1;
}
else if ( (items_to_collect[i]->getType() == Item::ITEM_NITRO_BIG) ||
(items_to_collect[i]->getType() == Item::ITEM_NITRO_SMALL) )
{
good = 1;
}
}
//Bad will store 2 for bananas, 3 for bubble gum
for(i=items_to_avoid.size()-1; i>=0; i--)
{
if (items_to_avoid[i]->getType() == Item::ITEM_BUBBLEGUM)
{
bad = 3;
i = -1;
}
else if ( items_to_avoid[i]->getType() == Item::ITEM_BANANA )
{
bad = 2;
}
}
//Second step : make sure a close item don't make the choice pointless
if( items_to_avoid.size()>0)
{
float d = (items_to_avoid[0]->getXYZ() - m_kart->getXYZ()).length2();
//fire if very close to a bad item
if (d < 2.0f)
{
m_controls->setFire(true);
break;
}
}
if( items_to_collect.size()>0)
{
float d = (items_to_collect[0]->getXYZ() - m_kart->getXYZ()).length2();
//don't fire if close to a good item
if (d < 5.0f)
{
break;
}
}
//Third step : Use or don't use to get the best available item
if( bad > good)
{
m_controls->setFire(true);
break;
}
} //ai_skill == 5
break;
} // POWERUP_SWITCH
case PowerupManager::POWERUP_PARACHUTE:
// Wait one second more than a previous parachute
@ -1399,8 +1711,33 @@ void SkiddingAI::handleItems(const float dt)
}
break; // POWERUP_ANVIL
// Level 2 : Use the swatter immediately after a wait time
// Level 3 : Use the swatter when enemies are close
// Level 4 : Level 3 and use the swatter to remove bad attachments
// Level 5 : Same as level 4.
case PowerupManager::POWERUP_SWATTER:
{
Attachment::AttachmentType type = m_kart->getAttachment()->getType();
if((ai_skill == 2) && (m_time_since_last_shot > 2.0f))
{
m_controls->setFire(true);
break;
}
// Use swatter to remove bad attachments
if((ai_skill == 4) || (ai_skill == 5))
{
if( type == Attachment::ATTACH_BOMB
|| type == Attachment::ATTACH_PARACHUTE
|| type == Attachment::ATTACH_ANVIL )
{
m_controls->setFire(true);
m_controls->setLookBack(false);
break;
}
}
// Squared distance for which the swatter works
float d2 = m_kart->getKartProperties()->getSwatterDistance();
// if the kart has a shield, do not break it by using a swatter.
@ -1421,7 +1758,7 @@ void SkiddingAI::handleItems(const float dt)
}
case PowerupManager::POWERUP_RUBBERBALL:
// if the kart has a shield, do not break it by using a swatter.
if(m_kart->getShieldTime() > min_bubble_time)
if((m_kart->getShieldTime() > min_bubble_time) && (stk_config->m_shield_restrict_weapons == true))
break;
// Perhaps some more sophisticated algorithm might be useful.
// For now: fire if there is a kart ahead (which means that
@ -1582,107 +1919,235 @@ void SkiddingAI::handleRescue(const float dt)
} // handleRescue
//-----------------------------------------------------------------------------
/** Decides wether to use nitro or not.
/** Decides wether to use nitro and zipper or not.
*/
void SkiddingAI::handleNitroAndZipper()
{
//Calculated here and not passed as parameter to not redo all the calls to the function
//May be better to change it
int ai_skill = 0;
if (m_ai_properties->m_item_usage_skill > 0)
{
if (m_ai_properties->m_item_usage_skill > 5)
{
ai_skill = 5;
}
else
{
ai_skill = m_ai_properties->m_item_usage_skill;
}
}
int nitro_skill = 0;
if (m_ai_properties->m_nitro_usage > 0)
{
if (m_ai_properties->m_nitro_usage > 4)
{
nitro_skill = 4;
}
else
{
nitro_skill = m_ai_properties->m_nitro_usage;
}
}
if (m_kart->getBoostAI() == true)
{
if (ai_skill < 5)
{
ai_skill = ai_skill + 1; //possible improvement : make the boost amplitude pulled from config
}
if (nitro_skill < 4)
{
nitro_skill = nitro_skill + 1; //possible improvement : make the boost amplitude pulled from config
}
}
if (m_kart->getPowerup()->getType()!=PowerupManager::POWERUP_ZIPPER)
{
ai_skill = 0; //don't try to use the zipper if there is none
}
//Nitro continue to be advantageous during the fadeout
float nitro_time = ( m_kart->getSpeedIncreaseTimeLeft(MaxSpeed::MS_INCREASE_NITRO)
+ m_kart->getKartProperties()->getNitroFadeOutTime() );
float nitro_max_time = m_kart->getKartProperties()->getNitroDuration()
+ m_kart->getKartProperties()->getNitroFadeOutTime();
//Nitro skill 0 : don't use
//Nitro skill 1 : don't use if the kart is braking, on the ground, has finished the race, has no nitro,
// has a parachute or an anvil attached, or has a plunger in the face.
// Otherwise, use it immediately
//Nitro skill 2 : Don't use nitro if there is more than 1,2 seconds of effect/fadeout left. Use it when at
// max speed or under 5 of speed (after rescue, etc.). Use it to pass bombs.
// Tries to builds a reserve of 4 energy to use towards the end
//Nitro skill 3 : Same as level 2, but don't use until 0,5 seconds of effect/fadeout left, and don't use close
// to bad items, and has a target reserve of 8 energy
//Nitro skill 4 : Same as level 3, but don't use until 0,05 seconds of effect/fadeout left and ignore the plunger
// and has a target reserve of 12 energy
m_controls->setNitro(false);
// If we are already very fast, save nitro.
if(m_kart->getSpeed() > 0.95f*m_kart->getCurrentMaxSpeed())
return;
// Don't use nitro when the AI has a plunger in the face!
if(m_kart->getBlockedByPlungerTime()>0) return;
// Don't use nitro if we are braking
float energy_reserve = 0;
if (nitro_skill == 2)
{
energy_reserve = 4;
}
if (nitro_skill == 3)
{
energy_reserve = 8;
}
if (nitro_skill == 4)
{
energy_reserve = 12;
}
// Don't use nitro or zipper if we are braking
if(m_controls->getBrake()) return;
// Don't use nitro if the kart is not on ground or has finished the race
// Don't use nitro or zipper if the kart is not on ground or has finished the race
if(!m_kart->isOnGround() || m_kart->hasFinishedRace()) return;
// Don't use nitro or zipper when the AI has a plunger in the face!
if(m_kart->getBlockedByPlungerTime()>0)
{
if ((nitro_skill < 4) && (ai_skill < 5))
{
return;
}
else if (nitro_skill < 4)
{
nitro_skill = 0;
}
else if (ai_skill < 5)
{
ai_skill = 0;
}
}
// Don't compute nitro usage if we don't have nitro or are not supposed
// to use it, and we don't have a zipper or are not supposed to use
// it (calculated).
if( (m_kart->getEnergy()==0 ||
m_ai_properties->m_nitro_usage==AIProperties::NITRO_NONE) &&
(m_kart->getPowerup()->getType()!=PowerupManager::POWERUP_ZIPPER ||
!m_ai_properties->m_item_usage_non_random ) )
return;
// If there are items to avoid close, and we only have zippers, don't
// use them (since this make it harder to avoid items).
if(m_avoid_item_close &&
(m_kart->getEnergy()==0 ||
m_ai_properties->m_nitro_usage==AIProperties::NITRO_NONE) )
return;
// If a parachute or anvil is attached, the nitro doesn't give much
// If a parachute or anvil is attached, the nitro and zipper don't give much
// benefit. Better wait till later.
const bool has_slowdown_attachment =
m_kart->getAttachment()->getType()==Attachment::ATTACH_PARACHUTE ||
m_kart->getAttachment()->getType()==Attachment::ATTACH_ANVIL;
if(has_slowdown_attachment) return;
// If the kart is very slow (e.g. after rescue), use nitro
if(m_kart->getSpeed()<5)
// Don't compute nitro usage if we don't have nitro
if( m_kart->getEnergy()==0 )
{
nitro_skill = 0;
}
// Don't use nitro if there is already a nitro boost active
// Nitro effect and fadeout varies between karts type from 2 to 4 seconds
// So vary time according to kart properties
if ((nitro_skill == 4) && nitro_time >= (nitro_max_time*0.01f) )
{
nitro_skill = 0;
}
else if ((nitro_skill == 3) && nitro_time >= (nitro_max_time*0.35f) )
{
nitro_skill = 0;
}
else if ((nitro_skill == 2) && nitro_time >= (nitro_max_time*0.5f) )
{
nitro_skill = 0;
}
// If there are items to avoid close
// Don't use zippers
// Dont use nitro if nitro_skill is 3 or 4
// (since going faster makes it harder to avoid items).
if(m_avoid_item_close && (m_kart->getEnergy()==0 || nitro_skill == 0 || nitro_skill >= 3) )
return;
// If basic AI, use nitro immediately
if (nitro_skill == 1)
{
m_controls->setNitro(true);
return;
}
// If this kart is the last kart, and we have enough
// (i.e. more than 2) nitro, use it.
// -------------------------------------------------
const unsigned int num_karts = m_world->getCurrentNumKarts();
if(m_kart->getPosition()== (int)num_karts &&
num_karts>1 && m_kart->getEnergy()>2.0f)
// Estimate time towards the end of the race.
// Decreases the reserve size when there is an estimate of time remaining
// to the end of less than 2,5 times the maximum nitro effect duration.
// This vary depending on kart characteristic.
// There is a margin because the kart will go a bit faster than predicted
// by the estimate, because the kart may collect more nitro and because
// there may be moments when it's not useful to use nitro (parachutes, etc).
if(nitro_skill >= 2 && energy_reserve > 0.0f)
{
m_controls->setNitro(true);
return;
}
// On the last track shortly before the finishing line, use nitro
// anyway. Since the kart is faster with nitro, estimate a 50% time
// decrease (additionally some nitro will be saved when top speed
// is reached).
if(m_world->getLapForKart(m_kart->getWorldKartId())
==race_manager->getNumLaps()-1 &&
m_ai_properties->m_nitro_usage == AIProperties::NITRO_ALL)
{
float finish =
m_world->getEstimatedFinishTime(m_kart->getWorldKartId());
if( 1.5f*m_kart->getEnergy() >= finish - m_world->getTime() )
float finish = m_world->getEstimatedFinishTime(m_kart->getWorldKartId()) - m_world->getTime();
float max_time_effect = nitro_max_time / m_kart->getKartProperties()->getNitroConsumption()
* m_kart->getEnergy()*2; //the minimum burst consumes around 0,5 energy
// The burster forces the AI to consume its reserve by series of 2 bursts
// Otherwise the bursting differences of the various nitro skill wouldn't matter here
// In short races, most AI nitro usage may be at the end with the reserve
float burster;
if ( nitro_time > 0)
{
m_controls->setNitro(true);
return;
burster = 2*nitro_max_time;
}
else
{
burster = 0;
}
if( (2.5f*max_time_effect) >= (finish - burster) )
{
// Absolute reduction to avoid small amount of unburned nitro at the end
energy_reserve = (finish - burster)/(2.5f*max_time_effect/m_kart->getEnergy()) - 0.5f ;
}
}
// A kart within this distance is considered to be overtaking (or to be
// overtaken).
const float overtake_distance = 10.0f;
// 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 &&
m_kart_ahead->getSpeed()+5.0f > m_kart->getSpeed() )
// If trying to pass a bomb to a kart faster or far ahead, use nitro reserve
if(m_kart->getAttachment()->getType() == Attachment::ATTACH_BOMB
&& nitro_skill >= 2 && energy_reserve > 0.0f)
{
m_controls->setNitro(true);
return;
if (m_distance_ahead>10.0f || m_kart_ahead->getSpeed() > m_kart->getSpeed() )
{
energy_reserve = 0 ;
}
}
// Don't use nitro if building an energy reserve
if (m_kart->getEnergy() <= energy_reserve)
{
nitro_skill = 0;
}
if(m_kart_behind &&
m_distance_behind < overtake_distance &&
m_kart_behind->getSpeed() > m_kart->getSpeed() )
// If the kart is very slow (e.g. after rescue), use nitro
if(nitro_skill > 0 && m_kart->getSpeed()<5 && m_kart->getSpeed()>2)
{
// Only prevent overtaking on highest level
m_controls->setNitro(m_ai_properties->m_nitro_usage
== AIProperties::NITRO_ALL);
m_controls->setNitro(true);
return;
}
// If kart is at max speed, use nitro
// This is to profit from the max speed increase
// And because it means there should be no slowing down from, e.g. plungers
if (nitro_skill > 0 && m_kart->getSpeed() > 0.99f*m_kart->getCurrentMaxSpeed() )
{
m_controls->setNitro(true);
return;
}
if(m_kart->getPowerup()->getType()==PowerupManager::POWERUP_ZIPPER &&
m_kart->getSpeed()>1.0f &&
// If this kart is the last kart, and we have nitro, use it.
// -------------------------------------------------
const unsigned int num_karts = m_world->getCurrentNumKarts();
if(nitro_skill > 0 && m_kart->getPosition()== (int)num_karts &&
num_karts > 1 )
{
m_controls->setNitro(true);
return;
}
// Use zipper
if(ai_skill >= 2 && m_kart->getSpeed()>1.0f &&
m_kart->getSpeedIncreaseTimeLeft(MaxSpeed::MS_INCREASE_ZIPPER)<=0)
{
DriveNode::DirectionType dir;

View File

@ -244,7 +244,8 @@ private:
void handleRaceStart();
void handleAcceleration(const float dt);
void handleSteering(float dt);
void handleItems(const float dt);
void handleItems(const float dt, const Vec3 *aim_point,
int last_node);
void handleRescue(const float dt);
void handleBraking();
void handleNitroAndZipper();

View File

@ -1181,7 +1181,7 @@ void SkiddingAI::handleItems(const float dt)
// Tactic 1: wait ten seconds, then use item
// -----------------------------------------
if(!m_ai_properties->m_item_usage_non_random)
if(m_ai_properties->m_item_usage_skill <= 1)
{
if( m_time_since_last_shot > 10.0f )
{
@ -1652,16 +1652,16 @@ void SkiddingAI::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_ai_properties->m_nitro_usage==AIProperties::NITRO_NONE) &&
m_ai_properties->m_nitro_usage == 0) &&
(m_kart->getPowerup()->getType()!=PowerupManager::POWERUP_ZIPPER ||
!m_ai_properties->m_item_usage_non_random ) )
m_ai_properties->m_item_usage_skill <= 1 ) )
return;
// If there are items to avoid close, and we only have zippers, don't
// use them (since this make it harder to avoid items).
if(m_avoid_item_close &&
(m_kart->getEnergy()==0 ||
m_ai_properties->m_nitro_usage==AIProperties::NITRO_NONE) )
m_ai_properties->m_nitro_usage == 0) )
return;
// If a parachute or anvil is attached, the nitro doesn't give much
// benefit. Better wait till later.
@ -1694,7 +1694,7 @@ void SkiddingAI::handleNitroAndZipper()
// is reached).
if(m_world->getLapForKart(m_kart->getWorldKartId())
==race_manager->getNumLaps()-1 &&
m_ai_properties->m_nitro_usage == AIProperties::NITRO_ALL)
m_ai_properties->m_nitro_usage >= 2)
{
float finish =
m_world->getEstimatedFinishTime(m_kart->getWorldKartId());
@ -1725,8 +1725,7 @@ void SkiddingAI::handleNitroAndZipper()
m_kart_behind->getSpeed() > m_kart->getSpeed() )
{
// Only prevent overtaking on highest level
m_controls->setNitro(m_ai_properties->m_nitro_usage
== AIProperties::NITRO_ALL );
m_controls->setNitro(m_ai_properties->m_nitro_usage >= 2 );
return;
}

View File

@ -77,6 +77,6 @@ public:
// ------------------------------------------------------------------------
virtual void kartIsInRestNow() {};
// ------------------------------------------------------------------------
}; // GhostKart
#endif

View File

@ -118,6 +118,7 @@ Kart::Kart (const std::string& ident, unsigned int world_kart_id,
m_max_speed = new MaxSpeed(this);
m_terrain_info = new TerrainInfo();
m_powerup = new Powerup(this);
m_last_used_powerup = PowerupManager::POWERUP_NOTHING;
m_vehicle = NULL;
m_initial_position = position;
m_race_position = position;
@ -143,6 +144,7 @@ Kart::Kart (const std::string& ident, unsigned int world_kart_id,
m_min_nitro_time = 0.0f;
m_fire_clicked = 0;
m_wrongway_counter = 0;
m_boosted_ai = false;
m_type = RaceManager::KT_AI;
m_view_blocked_by_plunger = 0;
@ -470,6 +472,17 @@ float Kart::getSpeedIncreaseTimeLeft(unsigned int category) const
return m_max_speed->getSpeedIncreaseTimeLeft(category);
} // getSpeedIncreaseTimeLeft
// -----------------------------------------------------------------------------
void Kart::setBoostAI(bool boosted)
{
m_boosted_ai = boosted;
} // setBoostAI
// -----------------------------------------------------------------------------
bool Kart::getBoostAI() const
{
return m_boosted_ai;
} // getBoostAI
// -----------------------------------------------------------------------------
/** Returns the current material the kart is on. */
const Material *Kart::getMaterial() const
@ -508,6 +521,15 @@ void Kart::setPowerup(PowerupManager::PowerupType t, int n)
m_powerup->set(t, n);
} // setPowerup
// ----------------------------------------------------------------------------
/** Sets the powerup this kart has last used. Number is always 1.
* \param t Type of the powerup.
*/
void Kart::setLastUsedPowerup(PowerupManager::PowerupType t)
{
m_last_used_powerup = t;
} // setLastUsedPowerup
// -----------------------------------------------------------------------------
int Kart::getNumPowerup() const
{
@ -1356,6 +1378,10 @@ void Kart::update(float dt)
if(m_controls.getFire() && !m_fire_clicked && !m_kart_animation)
{
if (m_powerup->getType() != PowerupManager::POWERUP_NOTHING)
{
setLastUsedPowerup(m_powerup->getType());
}
// use() needs to be called even if there currently is no collecteable
// since use() can test if something needs to be switched on/off.
m_powerup->use() ;
@ -2434,6 +2460,9 @@ void Kart::updateEnginePowerAndBrakes(float dt)
updateNitro(dt);
float engine_power = getActualWheelForce();
// apply nitro boost if relevant
if(getSpeedIncreaseTimeLeft(MaxSpeed::MS_INCREASE_NITRO))
engine_power*=1.2f;
// apply parachute physics if relevant
if(m_attachment->getType()==Attachment::ATTACH_PARACHUTE)
engine_power*=0.2f;

View File

@ -86,6 +86,9 @@ protected:
/** Handles the powerup of a kart. */
Powerup *m_powerup;
/** Remember the last **used** powerup type of a kart for AI purposes. */
PowerupManager::PowerupType m_last_used_powerup;
/** True if kart is flying (for debug purposes only). */
bool m_flying;
@ -160,6 +163,9 @@ protected:
/** Counter which is used for displaying wrong way message after a delay */
float m_wrongway_counter;
/** True if the kart has been selected to have a boosted ai */
bool m_boosted_ai;
// Bullet physics parameters
@ -270,6 +276,8 @@ public:
virtual void setSlowdown(unsigned int category, float max_speed_fraction,
float fade_in_time);
virtual float getSpeedIncreaseTimeLeft(unsigned int category) const;
virtual void setBoostAI (bool boosted);
virtual bool getBoostAI () const;
virtual void collectedItem(Item *item, int random_attachment);
virtual float getStartupBoost() const;
@ -303,12 +311,18 @@ public:
/** Sets a new powerup. */
virtual void setPowerup (PowerupManager::PowerupType t, int n);
// ------------------------------------------------------------------------
/** Sets the last used powerup. */
virtual void setLastUsedPowerup (PowerupManager::PowerupType t);
// ------------------------------------------------------------------------
/** Returns the current powerup. */
virtual const Powerup* getPowerup() const { return m_powerup; }
// ------------------------------------------------------------------------
/** Returns the current powerup. */
virtual Powerup* getPowerup() { return m_powerup; }
// ------------------------------------------------------------------------
/** Returns the last used powerup. */
virtual PowerupManager::PowerupType getLastUsedPowerup() { return m_last_used_powerup; }
// ------------------------------------------------------------------------
/** Returns the number of powerups. */
virtual int getNumPowerup() const;
// ------------------------------------------------------------------------
@ -486,4 +500,3 @@ public:
#endif
/* EOF */

View File

@ -206,13 +206,11 @@ void LinearWorld::update(float dt)
if (m_karts[i]->hasFinishedRace() ||
m_karts[i]->isEliminated() ) continue;
// During the last lap update the estimated finish time.
// This is used to play the faster music, and by the AI
if (m_kart_info[i].m_race_lap == race_manager->getNumLaps()-1)
{
m_kart_info[i].m_estimated_finish =
// Update the estimated finish time.
// This is used by the AI
m_kart_info[i].m_estimated_finish =
estimateFinishTimeForKart(m_karts[i]);
}
checkForWrongDirection(i, dt);
}
@ -413,13 +411,12 @@ int LinearWorld::getLapForKart(const int kart_id) const
} // getLapForKart
//-----------------------------------------------------------------------------
/** Returns the estimated finishing time. Only valid during the last lap!
/** Returns the estimated finishing time.
* \param kart_id Id of the kart.
*/
float LinearWorld::getEstimatedFinishTime(const int kart_id) const
{
assert(kart_id < (int)m_kart_info.size());
assert(m_kart_info[kart_id].m_race_lap == race_manager->getNumLaps()-1);
return m_kart_info[kart_id].m_estimated_finish;
} // getEstimatedFinishTime