Initial work on soccer AI, plus lots of code re-factor/clean up.

To test, you need to update stk-assets with the dummy soccer field.
In single player, you are limited to be in red team for now, so just
shoot the ball into the blue goal. Also max-goal is limited to 4.

Regarding AIs, the steering with ball logic is handled in
SoccerAI::correctBallPosition, max AI supported in single player
is limited to 5 by now.

The AI is not as good as Zidane or Ronaldo, but steering with ball
for human is not as easy as not normal driving anyway, so don't
laugh at the AIs. :)

TODO: Friendly-team firing, polish the soccer field.
This commit is contained in:
Benau 2016-01-15 18:25:52 +08:00
parent 409862dbe2
commit d14a39e0f7
34 changed files with 1808 additions and 1322 deletions

View File

@ -1,5 +1,5 @@
# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")

View File

@ -306,3 +306,34 @@ void AIBaseController::crashed(const Material *m)
}
} // crashed(Material)
//-----------------------------------------------------------------------------
void AIBaseController::checkPosition(const Vec3 &point,
posData *pos_data,
Vec3 *lc) const
{
// Convert to local coordinates from the point of view of current kart
btQuaternion q(btVector3(0, 1, 0), -m_kart->getHeading());
Vec3 p = point - m_kart->getXYZ();
Vec3 local_coordinates = quatRotate(q, p);
// Save local coordinates for later use if needed
if (lc) *lc = local_coordinates;
// on_side: tell whether it's left or right hand side
if (local_coordinates.getX() < 0)
pos_data->on_side = true;
else
pos_data->on_side = false;
// behind: tell whether it's behind or not
if (local_coordinates.getZ() < 0)
pos_data->behind = true;
else
pos_data->behind = false;
pos_data->angle = atan2(fabsf(local_coordinates.getX()),
fabsf(local_coordinates.getZ()));
pos_data->distance = p.length_2d();
} // checkPosition

View File

@ -43,6 +43,7 @@ private:
bool m_stuck;
protected:
/** Length of the kart, storing it here saves many function calls. */
float m_kart_length;
@ -57,6 +58,9 @@ protected:
static bool m_ai_debug;
/** Position info structure of targets. */
struct posData {bool behind; bool on_side; float angle; float distance;};
virtual void update (float delta) ;
virtual void setSteering (float angle, float dt);
void setControllerName(const std::string &name);
@ -67,6 +71,7 @@ protected:
/** This can be called to detect if the kart is stuck (i.e. repeatedly
* hitting part of the track). */
bool isStuck() const { return m_stuck; }
void checkPosition(const Vec3&, posData*, Vec3* lc = NULL) const;
public:
AIBaseController(AbstractKart *kart,
@ -85,7 +90,8 @@ public:
virtual bool isPlayerController() const { return false; }
virtual bool isLocalPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
virtual void skidBonusTriggered() {};
}; // AIBaseController
#endif

View File

@ -72,16 +72,6 @@ public:
StateManager::ActivePlayer *player=NULL);
virtual ~AIBaseLapController() {};
virtual void reset();
virtual void crashed(const AbstractKart *k) {};
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isNetworkController() const { return false; }
virtual bool isPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
}; // AIBaseLapController
#endif

View File

@ -45,7 +45,7 @@ protected:
friend class AIBaseController;
friend class AIBaseLapController;
friend class SkiddingAI;
friend class BattleAI;
friend class ArenaAI;
/** Used to check that all values are defined in the xml file. */
static float UNDEFINED;

View File

@ -0,0 +1,790 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "karts/controller/arena_ai.hpp"
#include "items/attachment.hpp"
#include "items/item_manager.hpp"
#include "items/powerup.hpp"
#include "items/projectile_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/ai_properties.hpp"
#include "karts/kart_properties.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/log.hpp"
ArenaAI::ArenaAI(AbstractKart *kart,
StateManager::ActivePlayer *player)
: AIBaseController(kart, player)
{
m_debug_sphere = NULL;
} // ArenaAI
//-----------------------------------------------------------------------------
/** Resets the AI when a race is restarted.
*/
void ArenaAI::reset()
{
m_target_node = BattleGraph::UNKNOWN_POLY;
m_adjusting_side = false;
m_closest_kart = NULL;
m_closest_kart_node = BattleGraph::UNKNOWN_POLY;
m_closest_kart_point = Vec3(0, 0, 0);
m_closest_kart_pos_data = {0};
m_cur_kart_pos_data = {0};
m_is_steering_overridden = false;
m_is_stuck = false;
m_is_uturn = false;
m_target_point = Vec3(0, 0, 0);
m_time_since_last_shot = 0.0f;
m_time_since_driving = 0.0f;
m_time_since_reversing = 0.0f;
m_time_since_steering_overridden = 0.0f;
m_time_since_uturn = 0.0f;
m_on_node.clear();
m_path_corners.clear();
m_portals.clear();
m_cur_difficulty = race_manager->getDifficulty();
AIBaseController::reset();
} // reset
//-----------------------------------------------------------------------------
/** This is the main entry point for the AI.
* It is called once per frame for each AI and determines the behaviour of
* the AI, e.g. steering, accelerating/braking, firing.
*/
void ArenaAI::update(float 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;
if (isWaiting())
{
AIBaseController::update(dt);
return;
}
checkIfStuck(dt);
if (handleArenaUnstuck(dt))
return;
findClosestKart(true);
findTarget();
handleArenaItems(dt);
handleArenaBanana();
if (m_kart->getSpeed() > 15.0f && m_cur_kart_pos_data.angle < 0.2f)
{
// Only use nitro when target is straight
m_controls->m_nitro = true;
}
if (m_is_uturn)
{
handleArenaUTurn(dt);
}
else
{
handleArenaAcceleration(dt);
handleArenaSteering(dt);
handleArenaBraking();
}
AIBaseController::update(dt);
} // update
//-----------------------------------------------------------------------------
void ArenaAI::checkIfStuck(const float dt)
{
if (m_is_stuck) return;
if (m_kart->getKartAnimation() || isWaiting())
{
m_on_node.clear();
m_time_since_driving = 0.0f;
}
m_on_node.insert(getCurrentNode());
m_time_since_driving += dt;
if ((m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f)
&& m_on_node.size() < 2 && !m_is_uturn &&
fabsf(m_kart->getSpeed()) < 3.0f) || isStuck() == true)
{
// Check whether a kart stay on the same node for a period of time
// Or crashed 3 times
m_on_node.clear();
m_time_since_driving = 0.0f;
AIBaseController::reset();
m_is_stuck = true;
}
else if (m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
{
m_on_node.clear(); // Reset for any correct movement
m_time_since_driving = 0.0f;
}
} // checkIfStuck
//-----------------------------------------------------------------------------
/** Handles acceleration.
* \param dt Time step size.
*/
void ArenaAI::handleArenaAcceleration(const float dt)
{
if (m_controls->m_brake)
{
m_controls->m_accel = 0.0f;
return;
}
const float handicap =
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 0.7f : 1.0f);
m_controls->m_accel = stk_config->m_ai_acceleration * handicap;
} // handleArenaAcceleration
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaUTurn(const float dt)
{
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0) // Try to emulate reverse like human players
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -5.0f;
if (m_time_since_uturn >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
setSteering(-(turn_side), dt); // Preventing keep going around circle
else
setSteering(turn_side, dt);
m_time_since_uturn += dt;
checkPosition(m_target_point, &m_cur_kart_pos_data);
if (!m_cur_kart_pos_data.behind || m_time_since_uturn >
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 3.5f : 3.0f))
{
m_is_uturn = false;
m_time_since_uturn = 0.0f;
}
else
m_is_uturn = true;
} // handleArenaUTurn
//-----------------------------------------------------------------------------
bool ArenaAI::handleArenaUnstuck(const float dt)
{
if (!m_is_stuck || m_is_uturn) return false;
setSteering(0.0f, dt);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0)
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -4.0f;
m_time_since_reversing += dt;
if (m_time_since_reversing >= 1.0f)
{
m_is_stuck = false;
m_time_since_reversing = 0.0f;
}
AIBaseController::update(dt);
return true;
} // handleArenaUnstuck
//-----------------------------------------------------------------------------
/** This function sets the steering.
* \param dt Time step size.
*/
void ArenaAI::handleArenaSteering(const float dt)
{
const int current_node = getCurrentNode();
if (current_node == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
if (m_is_steering_overridden)
{
// Steering is overridden to avoid eating banana
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
m_time_since_steering_overridden += dt;
if (m_time_since_steering_overridden > 0.35f)
setSteering(-(turn_side), dt);
else
setSteering(turn_side, dt);
if (m_time_since_steering_overridden > 0.7f)
{
m_is_steering_overridden = false;
m_time_since_steering_overridden = 0.0f;
}
return;
}
if (m_target_node == current_node)
{
// Very close to the item, steer directly
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else if (m_target_node != current_node)
{
findPortals(current_node, m_target_node);
stringPull(m_kart->getXYZ(), m_target_point);
if (m_path_corners.size() > 0)
m_target_point = m_path_corners[0];
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else
{
// Do nothing (go straight) if no targets found
setSteering(0.0f, dt);
return;
}
} // handleSteering
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaBanana()
{
if (m_is_steering_overridden || m_is_uturn) return;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->getType() == Item::ITEM_BANANA && !item->wasCollected())
{
posData banana_pos = {0};
checkPosition(item->getXYZ(), &banana_pos);
if (banana_pos.angle < 0.2f && banana_pos.distance < 7.5f &&
!banana_pos.behind)
{
// Check whether it's straight ahead towards a banana
// If so, try to do hard turn to avoid
m_adjusting_side = banana_pos.on_side;
m_is_steering_overridden = true;
}
}
}
} // handleArenaBanana
//-----------------------------------------------------------------------------
/** This function finds the polyon edges(portals) that the AI will cross before
* reaching its destination. We start from the current polygon and call
* BattleGraph::getNextShortestPathPoly() to find the next polygon on the shortest
* path to the destination. Then find the common edge between the current
* poly and the next poly, store it and step through the channel.
*
* 1----2----3 In this case, the portals are:
* |strt| | (2,5) (4,5) (10,7) (10,9) (11,12)
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start The start node(polygon) of the channel.
* \param end The end node(polygon) of the channel.
*/
void ArenaAI::findPortals(int start, int end)
{
int this_node = start;
// We can't use NULL because NULL==0 which is a valid node, so we initialize
// with a value that is always invalid.
int next_node = -999;
m_portals.clear();
while (next_node != end && this_node != -1 && next_node != -1 && this_node != end)
{
next_node = BattleGraph::get()->getNextShortestPathPoly(this_node, end);
if (next_node == BattleGraph::UNKNOWN_POLY || next_node == -999) return;
std::vector<int> this_node_verts =
NavMesh::get()->getNavPoly(this_node).getVerticesIndex();
std::vector<int> next_node_verts=
NavMesh::get()->getNavPoly(next_node).getVerticesIndex();
// this_node_verts and next_node_verts hold vertices of polygons in CCW order
// We reverse next_node_verts so it becomes easy to compare edges in the next step
std::reverse(next_node_verts.begin(),next_node_verts.end());
Vec3 portalLeft, portalRight;
//bool flag = 0;
for (unsigned int n_i = 0; n_i < next_node_verts.size(); n_i++)
{
for (unsigned int t_i = 0; t_i < this_node_verts.size(); t_i++)
{
if ((next_node_verts[n_i] == this_node_verts[t_i]) &&
(next_node_verts[(n_i+1)%next_node_verts.size()] ==
this_node_verts[(t_i+1)%this_node_verts.size()]))
{
portalLeft = NavMesh::get()->
getVertex(this_node_verts[(t_i+1)%this_node_verts.size()]);
portalRight = NavMesh::get()->getVertex(this_node_verts[t_i]);
}
}
}
m_portals.push_back(std::make_pair(portalLeft, portalRight));
// for debugging:
//m_debug_sphere->setPosition((portalLeft).toIrrVector());
this_node = next_node;
}
} // findPortals
//-----------------------------------------------------------------------------
/** This function implements the funnel algorithm for finding shortest paths
* through a polygon channel. This means that we should move from corner to
* corner to move on the most straight and shortest path to the destination.
* This can be visualized as pulling a string from the end point to the start.
* The string will bend at the corners, and this algorithm will find those
* corners using portals from findPortals(). The AI will aim at the first
* corner and the rest can be used for estimating the curve (braking).
*
* 1----2----3 In this case, the corners are:
* |strt| | <5,10,end>
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start_pos The start position (usually the AI's current position).
* \param end_pos The end position (m_target_point).
*/
void ArenaAI::stringPull(const Vec3& start_pos, const Vec3& end_pos)
{
Vec3 funnel_apex = start_pos;
Vec3 funnel_left = m_portals[0].first;
Vec3 funnel_right = m_portals[0].second;
unsigned int apex_index=0, fun_left_index=0, fun_right_index=0;
m_portals.push_back(std::make_pair(end_pos, end_pos));
m_path_corners.clear();
const float eps=0.0001f;
for (unsigned int i = 0; i < m_portals.size(); i++)
{
Vec3 portal_left = m_portals[i].first;
Vec3 portal_right = m_portals[i].second;
//Compute for left edge
if ((funnel_left == funnel_apex) ||
portal_left.sideOfLine2D(funnel_apex, funnel_left) <= -eps)
{
funnel_left = 0.98f*portal_left + 0.02f*portal_right;
//funnel_left = portal_left;
fun_left_index = i;
if (portal_left.sideOfLine2D(funnel_apex, funnel_right) < -eps)
{
funnel_apex = funnel_right;
apex_index = fun_right_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
//Compute for right edge
if ((funnel_right == funnel_apex) ||
portal_right.sideOfLine2D(funnel_apex, funnel_right) >= eps)
{
funnel_right = 0.98f*portal_right + 0.02f*portal_left;
//funnel_right = portal_right;
fun_right_index = i;
if (portal_right.sideOfLine2D(funnel_apex, funnel_left) > eps)
{
funnel_apex = funnel_left;
apex_index = fun_left_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
}
//Push end_pos to m_path_corners so if no corners, we aim at target
m_path_corners.push_back(end_pos);
} // stringPull
//-----------------------------------------------------------------------------
/** This function handles braking. It calls determineTurnRadius() to find out
* the curve radius. Depending on the turn radius, it finds out the maximum
* speed. If the current speed is greater than the max speed and a set minimum
* speed, brakes are applied.
*/
void ArenaAI::handleArenaBraking()
{
m_controls->m_brake = false;
if (getCurrentNode() == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY ||
m_is_steering_overridden) return;
// A kart will not brake when the speed is already slower than this
// value. This prevents a kart from going too slow (or even backwards)
// in tight curves.
const float MIN_SPEED = 5.0f;
std::vector<Vec3> points;
points.push_back(m_kart->getXYZ());
points.push_back(m_path_corners[0]);
points.push_back((m_path_corners.size()>=2) ? m_path_corners[1] : m_path_corners[0]);
float current_curve_radius = determineTurnRadius(points);
Vec3 d1 = m_kart->getXYZ() - m_target_point;
Vec3 d2 = m_kart->getXYZ() - m_path_corners[0];
if (d1.length2_2d() < d2.length2_2d())
current_curve_radius = d1.length_2d();
float max_turn_speed = m_kart->getSpeedForTurnRadius(current_curve_radius);
if (m_kart->getSpeed() > max_turn_speed &&
m_kart->getSpeed() > MIN_SPEED)
{
m_controls->m_brake = true;
}
} // handleArenaBraking
//-----------------------------------------------------------------------------
/** The turn radius is determined by fitting a parabola to 3 points: current
* location of AI, first corner and the second corner. Once the constants are
* computed, a formula is used to find the radius of curvature at the kart's
* current location.
* NOTE: This method does not apply enough braking, should think of something
* else.
*/
float ArenaAI::determineTurnRadius( std::vector<Vec3>& points )
{
// Declaring variables
float a, b;
irr::core::CMatrix4<float> A;
irr::core::CMatrix4<float> X;
irr::core::CMatrix4<float> B;
//Populating matrices
for (unsigned int i = 0; i < 3; i++)
{
A(i, 0) = points[i].x()*points[i].x();
A(i, 1) = points[i].x();
A(i, 2) = 1.0f;
A(i, 3) = 0.0f;
}
A(3, 0) = A(3, 1) = A(3, 2) = 0.0f;
A(3, 3) = 1.0f;
for (unsigned int i = 0; i < 3; i++)
{
B(i, 0) = points[i].z();
B(i, 1) = 0.0f;
B(i, 2) = 0.0f;
B(i, 3) = 0.0f;
}
B(3, 0) = B(3, 1) = B(3, 2) = B(3, 3) = 0.0f;
//Computing inverse : X = inv(A)*B
irr::core::CMatrix4<float> invA;
if (!A.getInverse(invA))
return -1;
X = invA*B;
a = X(0, 0);
b = X(0, 1);
//c = X(0, 2);
float x = points.front().x();
//float z = a*pow(x, 2) + b*x + c;
float dx_by_dz = 2*a*x + b;
float d2x_by_dz = 2*a;
float radius = pow(abs(1 + pow(dx_by_dz, 2)), 1.5f)/ abs(d2x_by_dz);
return radius;
} // determineTurnRadius
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaItems(const float dt)
{
m_controls->m_fire = false;
if (m_kart->getKartAnimation() ||
m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING)
return;
// Find a closest kart again, this time we ignore difficulty
findClosestKart(false);
if (!m_closest_kart) return;
m_time_since_last_shot += dt;
float min_bubble_time = 2.0f;
const bool difficulty = m_cur_difficulty == RaceManager::DIFFICULTY_EASY ||
m_cur_difficulty == RaceManager::DIFFICULTY_MEDIUM;
const bool fire_behind = m_closest_kart_pos_data.behind && !difficulty;
const bool perfect_aim = m_closest_kart_pos_data.angle < 0.2f;
switch(m_kart->getPowerup()->getType())
{
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 ||
type == Attachment::ATTACH_NOLOKS_SWATTER)
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->m_fire = true;
m_controls->m_look_back = false;
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,
// or can't find a close kart for too long time
if ((m_closest_kart_pos_data.distance < 15.0f &&
m_closest_kart_pos_data.distance > 3.0f) ||
m_time_since_last_shot > 15.0f)
{
m_controls->m_fire = true;
m_controls->m_look_back = true;
break;
}
break; // POWERUP_BUBBLEGUM
}
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)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 25.0f &&
!m_closest_kart->isInvulnerable())
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_CAKE
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)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 6.0f &&
(difficulty || perfect_aim))
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_BOWLING
case PowerupManager::POWERUP_SWATTER:
{
// 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.
if (m_kart->getShieldTime() > min_bubble_time)
break;
if (!m_closest_kart->isSquashed() &&
m_closest_kart_pos_data.distance < d2 &&
m_closest_kart->getSpeed() < m_kart->getSpeed())
{
m_controls->m_fire = true;
m_controls->m_look_back = false;
break;
}
break;
}
// Below powerups won't appear in arena, so skip them
case PowerupManager::POWERUP_ZIPPER:
break; // POWERUP_ZIPPER
case PowerupManager::POWERUP_PLUNGER:
break; // POWERUP_PLUNGER
case PowerupManager::POWERUP_SWITCH: // Don't handle switch
m_controls->m_fire = true; // (use it no matter what) for now
break; // POWERUP_SWITCH
case PowerupManager::POWERUP_PARACHUTE:
break; // POWERUP_PARACHUTE
case PowerupManager::POWERUP_ANVIL:
break; // POWERUP_ANVIL
case PowerupManager::POWERUP_RUBBERBALL:
break;
default:
Log::error("ArenaAI",
"Invalid or unhandled powerup '%d' in default AI.",
m_kart->getPowerup()->getType());
assert(false);
}
if (m_controls->m_fire)
m_time_since_last_shot = 0.0f;
} // handleArenaItems
//-----------------------------------------------------------------------------
void ArenaAI::collectItemInArena(Vec3* aim_point, int* target_node) const
{
float distance = 99999.9f;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
if (item_list.empty())
{
// Notice: this should not happen, as it makes no sense
// for an arean without items, if so how can attack happen?
Log::fatal ("ArenaAI",
"AI can't find any items in the arena, "
"maybe there is something wrong with the navmesh, "
"make sure it lies closely to the ground.");
return;
}
unsigned int closest_item_num = 0;
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->wasCollected()) continue;
if ((item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL) &&
(m_kart->getEnergy() >
m_kart->getKartProperties()->getNitroSmallContainer()))
continue; // Ignore nitro when already has some
Vec3 d = item->getXYZ() - m_kart->getXYZ();
if (d.length_2d() <= distance &&
(item->getType() == Item::ITEM_BONUS_BOX ||
item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL))
{
closest_item_num = i;
distance = d.length_2d();
}
}
const Item *item_selected = item_list[closest_item_num].first;
if (item_selected->getType() == Item::ITEM_BONUS_BOX ||
item_selected->getType() == Item::ITEM_NITRO_BIG ||
item_selected->getType() == Item::ITEM_NITRO_SMALL)
{
*aim_point = item_selected->getXYZ();
*target_node = item_list[closest_item_num].second;
}
else
{
// Handle when all targets are swapped, which make AIs follow karts
*aim_point = m_closest_kart_point;
*target_node = m_closest_kart_node;
}
} // collectItemInArena

View File

@ -0,0 +1,143 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_ARENA_AI_HPP
#define HEADER_ARENA_AI_HPP
#include "karts/controller/ai_base_controller.hpp"
#include "race/race_manager.hpp"
#include "utils/random_generator.hpp"
#undef AI_DEBUG
#ifdef AI_DEBUG
#include "graphics/irr_driver.hpp"
#endif
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
#define isnan _isnan
#else
#include <math.h>
#endif
class Vec3;
namespace irr
{
namespace scene { class ISceneNode; }
namespace video { class ITexture; }
}
/** A base class for AI that use navmesh to work.
* \ingroup controller
*/
class ArenaAI : public AIBaseController
{
protected:
/** Pointer to the closest kart around this kart. */
AbstractKart *m_closest_kart;
int m_closest_kart_node;
Vec3 m_closest_kart_point;
posData m_closest_kart_pos_data;
/** Holds the current difficulty. */
RaceManager::Difficulty m_cur_difficulty;
/** For debugging purpose: a sphere indicating where the AI
* is targeting at. */
irr::scene::ISceneNode *m_debug_sphere;
/** The node(poly) at which the target point lies in. */
int m_target_node;
/** The target point. */
Vec3 m_target_point;
void collectItemInArena(Vec3*, int*) const;
private:
/** Used by handleBanana and UTurn, it tells whether to do left or right
* turning when steering is overridden. */
bool m_adjusting_side;
posData m_cur_kart_pos_data;
/** Indicates that the steering of kart is overridden, and
* m_time_since_steering_overridden is counting down. */
bool m_is_steering_overridden;
/** Indicates that the kart is currently stuck, and m_time_since_reversing is
* counting down. */
bool m_is_stuck;
/** Indicates that the kart need a uturn to reach a node behind, and
* m_time_since_uturn is counting down. */
bool m_is_uturn;
/** Holds the unique node ai has driven through, useful to tell if AI is
* stuck by determine the size of this set. */
std::set <int> m_on_node;
/** Holds the corner points computed using the funnel algorithm that the AI
* will eventaully move through. See stringPull(). */
std::vector<Vec3> m_path_corners;
/** Holds the set of portals that the kart will cross when moving through
* polygon channel. See findPortals(). */
std::vector<std::pair<Vec3,Vec3> > m_portals;
/** Time an item has been collected and not used. */
float m_time_since_last_shot;
/** This is a timer that counts down when the kart is reversing to get unstuck. */
float m_time_since_reversing;
/** This is a timer that counts down when the kart is starting to drive. */
float m_time_since_driving;
/** This is a timer that counts down when the steering of kart is overridden. */
float m_time_since_steering_overridden;
/** This is a timer that counts down when the kart is doing u-turn. */
float m_time_since_uturn;
void checkIfStuck(const float dt);
float determineTurnRadius(std::vector<Vec3>& points);
void findPortals(int start, int end);
void handleArenaAcceleration(const float dt);
void handleArenaBanana();
void handleArenaBraking();
void handleArenaItems(const float dt);
void handleArenaSteering(const float dt);
void handleArenaUTurn(const float dt);
bool handleArenaUnstuck(const float dt);
void stringPull(const Vec3&, const Vec3&);
virtual int getCurrentNode() const = 0;
virtual bool isWaiting() const = 0;
virtual void findClosestKart(bool use_difficulty) = 0;
virtual void findTarget() = 0;
public:
ArenaAI(AbstractKart *kart,
StateManager::ActivePlayer *player = NULL);
virtual ~ArenaAI() {};
virtual void update (float delta);
virtual void reset ();
virtual void newLap(int lap) {};
};
#endif

View File

@ -21,20 +21,10 @@
#include "karts/controller/battle_ai.hpp"
#include "items/attachment.hpp"
#include "items/item_manager.hpp"
#include "items/powerup.hpp"
#include "items/projectile_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/kart_control.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/ai_properties.hpp"
#include "karts/kart_properties.hpp"
#include "karts/max_speed.hpp"
#include "karts/rescue_animation.hpp"
#include "karts/skidding.hpp"
#include "modes/three_strikes_battle.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/log.hpp"
#ifdef AI_DEBUG
#include "irrlicht.h"
@ -43,15 +33,9 @@ using namespace irr;
using namespace std;
#endif
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
#define isnan _isnan
#else
#include <math.h>
#endif
BattleAI::BattleAI(AbstractKart *kart,
StateManager::ActivePlayer *player)
: AIBaseController(kart, player)
: ArenaAI(kart, player)
{
reset();
@ -95,162 +79,18 @@ BattleAI::~BattleAI()
*/
void BattleAI::reset()
{
m_target_node = BattleGraph::UNKNOWN_POLY;
m_adjusting_side = false;
m_closest_kart = NULL;
m_closest_kart_node = BattleGraph::UNKNOWN_POLY;
m_closest_kart_point = Vec3(0, 0, 0);
m_closest_kart_pos_data = {0};
m_cur_kart_pos_data = {0};
m_is_steering_overridden = false;
m_is_stuck = false;
m_is_uturn = false;
m_target_point = Vec3(0, 0, 0);
m_time_since_last_shot = 0.0f;
m_time_since_driving = 0.0f;
m_time_since_reversing = 0.0f;
m_time_since_steering_overridden = 0.0f;
m_time_since_uturn = 0.0f;
m_on_node.clear();
m_path_corners.clear();
m_portals.clear();
m_cur_difficulty = race_manager->getDifficulty();
ArenaAI::reset();
AIBaseController::reset();
} // reset
//-----------------------------------------------------------------------------
/** This is the main entry point for the AI.
* It is called once per frame for each AI and determines the behaviour of
* the AI, e.g. steering, accelerating/braking, firing.
*/
void BattleAI::update(float 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;
if (m_world->isStartPhase())
{
AIBaseController::update(dt);
return;
}
checkIfStuck(dt);
if (m_is_stuck && !m_is_uturn)
{
setSteering(0.0f, dt);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0)
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -4.0f;
m_time_since_reversing += dt;
if (m_time_since_reversing >= 1.0f)
{
m_is_stuck = false;
m_time_since_reversing = 0.0f;
}
AIBaseController::update(dt);
return;
}
findClosestKart(true);
findTarget();
handleItems(dt);
handleBanana();
if (m_kart->getSpeed() > 15.0f && m_cur_kart_pos_data.angle < 0.2f)
{
// Only use nitro when target is straight
m_controls->m_nitro = true;
}
if (m_is_uturn)
{
handleUTurn(dt);
}
else
{
handleAcceleration(dt);
handleSteering(dt);
handleBraking();
}
AIBaseController::update(dt);
ArenaAI::update(dt);
} // update
//-----------------------------------------------------------------------------
void BattleAI::checkIfStuck(const float dt)
{
if (m_is_stuck) return;
if (m_kart->getKartAnimation() || m_world->isStartPhase())
{
m_on_node.clear();
m_time_since_driving = 0.0f;
}
m_on_node.insert(m_world->getKartNode(m_kart->getWorldKartId()));
m_time_since_driving += dt;
if ((m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f)
&& m_on_node.size() < 2 && !m_is_uturn &&
fabsf(m_kart->getSpeed()) < 3.0f) || isStuck() == true)
{
// Check whether a kart stay on the same node for a period of time
// Or crashed 3 times
m_on_node.clear();
m_time_since_driving = 0.0f;
AIBaseController::reset();
m_is_stuck = true;
}
else if (m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
{
m_on_node.clear(); // Reset for any correct movement
m_time_since_driving = 0.0f;
}
} // checkIfStuck
//-----------------------------------------------------------------------------
void BattleAI::checkPosition(const Vec3 &point, posData *pos_data)
{
// Convert to local coordinates from the point of view of current kart
btQuaternion q(btVector3(0, 1, 0), -m_kart->getHeading());
Vec3 p = point - m_kart->getXYZ();
Vec3 local_coordinates = quatRotate(q, p);
// on_side: tell whether it's left or right hand side
if (local_coordinates.getX() < 0)
pos_data->on_side = true;
else
pos_data->on_side = false;
// behind: tell whether it's behind or not
if (local_coordinates.getZ() < 0)
pos_data->behind = true;
else
pos_data->behind = false;
pos_data->angle = atan2(fabsf(local_coordinates.getX()),
fabsf(local_coordinates.getZ()));
pos_data->distance = p.length_2d();
} // checkPosition
//-----------------------------------------------------------------------------
void BattleAI::findClosestKart(bool difficulty)
void BattleAI::findClosestKart(bool use_difficulty)
{
float distance = 99999.9f;
const unsigned int n = m_world->getNumKarts();
@ -266,8 +106,8 @@ void BattleAI::findClosestKart(bool difficulty)
// Test whether takes current difficulty into account for closest kart
// Notice: it don't affect aiming, this function will be called once
// more in handleItems, which ignore difficulty.
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && difficulty)
// more in handleArenaItems, which ignore difficulty.
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && use_difficulty)
{
// Skip human players for novice mode unless only human players left
const AbstractKart* temp = m_world->getKart(i);
@ -276,7 +116,7 @@ void BattleAI::findClosestKart(bool difficulty)
m_world->getCurrentNumPlayers()) > 1)
continue;
}
else if (m_cur_difficulty == RaceManager::DIFFICULTY_BEST && difficulty)
else if (m_cur_difficulty == RaceManager::DIFFICULTY_BEST && use_difficulty)
{
// Skip AI players for supertux mode
const AbstractKart* temp = m_world->getKart(i);
@ -296,7 +136,7 @@ void BattleAI::findClosestKart(bool difficulty)
m_closest_kart_node = m_world->getKartNode(closest_kart_num);
m_closest_kart_point = closest_kart->getXYZ();
if (!difficulty)
if (!use_difficulty)
{
m_closest_kart = m_world->getKart(closest_kart_num);
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
@ -309,7 +149,7 @@ void BattleAI::findTarget()
// Find a suitable target to drive to, either powerup or kart
if (m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER)
handleItemCollection(&m_target_point , &m_target_node);
collectItemInArena(&m_target_point , &m_target_node);
else
{
m_target_point = m_closest_kart_point;
@ -317,615 +157,13 @@ void BattleAI::findTarget()
}
} // findTarget
//-----------------------------------------------------------------------------
/** Handles acceleration.
* \param dt Time step size.
*/
void BattleAI::handleAcceleration(const float dt)
// ------------------------------------------------------------------------
int BattleAI::getCurrentNode() const
{
if (m_controls->m_brake)
{
m_controls->m_accel = 0.0f;
return;
}
const float handicap =
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 0.7f : 1.0f);
m_controls->m_accel = stk_config->m_ai_acceleration * handicap;
} // handleAcceleration
//-----------------------------------------------------------------------------
void BattleAI::handleUTurn(const float dt)
return m_world->getKartNode(m_kart->getWorldKartId());
} // getCurrentNode
// ------------------------------------------------------------------------
bool BattleAI::isWaiting() const
{
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0) // Try to emulate reverse like human players
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -5.0f;
if (m_time_since_uturn >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
setSteering(-(turn_side), dt); // Preventing keep going around circle
else
setSteering(turn_side, dt);
m_time_since_uturn += dt;
checkPosition(m_target_point, &m_cur_kart_pos_data);
if (!m_cur_kart_pos_data.behind || m_time_since_uturn >
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 3.5f : 3.0f))
{
m_is_uturn = false;
m_time_since_uturn = 0.0f;
}
else
m_is_uturn = true;
} // handleUTurn
//-----------------------------------------------------------------------------
/** This function sets the steering.
* \param dt Time step size.
*/
void BattleAI::handleSteering(const float dt)
{
const int current_node = m_world->getKartNode(m_kart->getWorldKartId());
if (current_node == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
if (m_is_steering_overridden)
{
// Steering is overridden to avoid eating banana
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
m_time_since_steering_overridden += dt;
if (m_time_since_steering_overridden > 0.35f)
setSteering(-(turn_side), dt);
else
setSteering(turn_side, dt);
if (m_time_since_steering_overridden > 0.7f)
{
m_is_steering_overridden = false;
m_time_since_steering_overridden = 0.0f;
}
return;
}
if (m_target_node == current_node)
{
// Very close to the item, steer directly
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else if (m_target_node != current_node)
{
findPortals(current_node, m_target_node);
stringPull(m_kart->getXYZ(), m_target_point);
if (m_path_corners.size() > 0)
m_target_point = m_path_corners[0];
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else
{
// Do nothing (go straight) if no targets found
setSteering(0.0f, dt);
return;
}
} // handleSteering
//-----------------------------------------------------------------------------
void BattleAI::handleBanana()
{
if (m_is_steering_overridden || m_is_uturn) return;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->getType() == Item::ITEM_BANANA && !item->wasCollected())
{
posData banana_pos = {0};
checkPosition(item->getXYZ(), &banana_pos);
if (banana_pos.angle < 0.2f && banana_pos.distance < 7.5f &&
!banana_pos.behind)
{
// Check whether it's straight ahead towards a banana
// If so, try to do hard turn to avoid
m_adjusting_side = banana_pos.on_side;
m_is_steering_overridden = true;
}
}
}
} // handleBanana
//-----------------------------------------------------------------------------
/** This function finds the polyon edges(portals) that the AI will cross before
* reaching its destination. We start from the current polygon and call
* BattleGraph::getNextShortestPathPoly() to find the next polygon on the shortest
* path to the destination. Then find the common edge between the current
* poly and the next poly, store it and step through the channel.
*
* 1----2----3 In this case, the portals are:
* |strt| | (2,5) (4,5) (10,7) (10,9) (11,12)
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start The start node(polygon) of the channel.
* \param end The end node(polygon) of the channel.
*/
void BattleAI::findPortals(int start, int end)
{
int this_node = start;
// We can't use NULL because NULL==0 which is a valid node, so we initialize
// with a value that is always invalid.
int next_node = -999;
m_portals.clear();
while (next_node != end && this_node != -1 && next_node != -1 && this_node != end)
{
next_node = BattleGraph::get()->getNextShortestPathPoly(this_node, end);
if (next_node == BattleGraph::UNKNOWN_POLY || next_node == -999) return;
std::vector<int> this_node_verts =
NavMesh::get()->getNavPoly(this_node).getVerticesIndex();
std::vector<int> next_node_verts=
NavMesh::get()->getNavPoly(next_node).getVerticesIndex();
// this_node_verts and next_node_verts hold vertices of polygons in CCW order
// We reverse next_node_verts so it becomes easy to compare edges in the next step
std::reverse(next_node_verts.begin(),next_node_verts.end());
Vec3 portalLeft, portalRight;
//bool flag = 0;
for (unsigned int n_i = 0; n_i < next_node_verts.size(); n_i++)
{
for (unsigned int t_i = 0; t_i < this_node_verts.size(); t_i++)
{
if ((next_node_verts[n_i] == this_node_verts[t_i]) &&
(next_node_verts[(n_i+1)%next_node_verts.size()] ==
this_node_verts[(t_i+1)%this_node_verts.size()]))
{
portalLeft = NavMesh::get()->
getVertex(this_node_verts[(t_i+1)%this_node_verts.size()]);
portalRight = NavMesh::get()->getVertex(this_node_verts[t_i]);
}
}
}
m_portals.push_back(std::make_pair(portalLeft, portalRight));
// for debugging:
//m_debug_sphere->setPosition((portalLeft).toIrrVector());
this_node = next_node;
}
} // findPortals
//-----------------------------------------------------------------------------
/** This function implements the funnel algorithm for finding shortest paths
* through a polygon channel. This means that we should move from corner to
* corner to move on the most straight and shortest path to the destination.
* This can be visualized as pulling a string from the end point to the start.
* The string will bend at the corners, and this algorithm will find those
* corners using portals from findPortals(). The AI will aim at the first
* corner and the rest can be used for estimating the curve (braking).
*
* 1----2----3 In this case, the corners are:
* |strt| | <5,10,end>
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start_pos The start position (usually the AI's current position).
* \param end_pos The end position (m_target_point).
*/
void BattleAI::stringPull(const Vec3& start_pos, const Vec3& end_pos)
{
Vec3 funnel_apex = start_pos;
Vec3 funnel_left = m_portals[0].first;
Vec3 funnel_right = m_portals[0].second;
unsigned int apex_index=0, fun_left_index=0, fun_right_index=0;
m_portals.push_back(std::make_pair(end_pos, end_pos));
m_path_corners.clear();
const float eps=0.0001f;
for (unsigned int i = 0; i < m_portals.size(); i++)
{
Vec3 portal_left = m_portals[i].first;
Vec3 portal_right = m_portals[i].second;
//Compute for left edge
if ((funnel_left == funnel_apex) ||
portal_left.sideOfLine2D(funnel_apex, funnel_left) <= -eps)
{
funnel_left = 0.98f*portal_left + 0.02f*portal_right;
//funnel_left = portal_left;
fun_left_index = i;
if (portal_left.sideOfLine2D(funnel_apex, funnel_right) < -eps)
{
funnel_apex = funnel_right;
apex_index = fun_right_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
//Compute for right edge
if ((funnel_right == funnel_apex) ||
portal_right.sideOfLine2D(funnel_apex, funnel_right) >= eps)
{
funnel_right = 0.98f*portal_right + 0.02f*portal_left;
//funnel_right = portal_right;
fun_right_index = i;
if (portal_right.sideOfLine2D(funnel_apex, funnel_left) > eps)
{
funnel_apex = funnel_left;
apex_index = fun_left_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
}
//Push end_pos to m_path_corners so if no corners, we aim at target
m_path_corners.push_back(end_pos);
} // stringPull
//-----------------------------------------------------------------------------
/** This function handles braking. It calls determineTurnRadius() to find out
* the curve radius. Depending on the turn radius, it finds out the maximum
* speed. If the current speed is greater than the max speed and a set minimum
* speed, brakes are applied.
*/
void BattleAI::handleBraking()
{
m_controls->m_brake = false;
if (m_world->getKartNode(m_kart->getWorldKartId())
== BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY ||
m_is_steering_overridden) return;
// A kart will not brake when the speed is already slower than this
// value. This prevents a kart from going too slow (or even backwards)
// in tight curves.
const float MIN_SPEED = 5.0f;
std::vector<Vec3> points;
points.push_back(m_kart->getXYZ());
points.push_back(m_path_corners[0]);
points.push_back((m_path_corners.size()>=2) ? m_path_corners[1] : m_path_corners[0]);
float current_curve_radius = BattleAI::determineTurnRadius(points);
Vec3 d1 = m_kart->getXYZ() - m_target_point;
Vec3 d2 = m_kart->getXYZ() - m_path_corners[0];
if (d1.length2_2d() < d2.length2_2d())
current_curve_radius = d1.length_2d();
float max_turn_speed = m_kart->getSpeedForTurnRadius(current_curve_radius);
if (m_kart->getSpeed() > max_turn_speed &&
m_kart->getSpeed() > MIN_SPEED)
{
m_controls->m_brake = true;
}
} // handleBraking
//-----------------------------------------------------------------------------
/** The turn radius is determined by fitting a parabola to 3 points: current
* location of AI, first corner and the second corner. Once the constants are
* computed, a formula is used to find the radius of curvature at the kart's
* current location.
* NOTE: This method does not apply enough braking, should think of something
* else.
*/
float BattleAI::determineTurnRadius( std::vector<Vec3>& points )
{
// Declaring variables
float a, b;
irr::core::CMatrix4<float> A;
irr::core::CMatrix4<float> X;
irr::core::CMatrix4<float> B;
//Populating matrices
for (unsigned int i = 0; i < 3; i++)
{
A(i, 0) = points[i].x()*points[i].x();
A(i, 1) = points[i].x();
A(i, 2) = 1.0f;
A(i, 3) = 0.0f;
}
A(3, 0) = A(3, 1) = A(3, 2) = 0.0f;
A(3, 3) = 1.0f;
for (unsigned int i = 0; i < 3; i++)
{
B(i, 0) = points[i].z();
B(i, 1) = 0.0f;
B(i, 2) = 0.0f;
B(i, 3) = 0.0f;
}
B(3, 0) = B(3, 1) = B(3, 2) = B(3, 3) = 0.0f;
//Computing inverse : X = inv(A)*B
irr::core::CMatrix4<float> invA;
if (!A.getInverse(invA))
return -1;
X = invA*B;
a = X(0, 0);
b = X(0, 1);
//c = X(0, 2);
float x = points.front().x();
//float z = a*pow(x, 2) + b*x + c;
float dx_by_dz = 2*a*x + b;
float d2x_by_dz = 2*a;
float radius = pow(abs(1 + pow(dx_by_dz, 2)), 1.5f)/ abs(d2x_by_dz);
return radius;
} // determineTurnRadius
//-----------------------------------------------------------------------------
void BattleAI::handleItems(const float dt)
{
m_controls->m_fire = false;
if (m_kart->getKartAnimation() ||
m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING)
return;
// Find a closest kart again, this time we ignore difficulty
findClosestKart(false);
if (!m_closest_kart) return;
m_time_since_last_shot += dt;
float min_bubble_time = 2.0f;
const bool difficulty = m_cur_difficulty == RaceManager::DIFFICULTY_EASY ||
m_cur_difficulty == RaceManager::DIFFICULTY_MEDIUM;
const bool fire_behind = m_closest_kart_pos_data.behind && !difficulty;
const bool perfect_aim = m_closest_kart_pos_data.angle < 0.2f;
switch(m_kart->getPowerup()->getType())
{
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 ||
type == Attachment::ATTACH_NOLOKS_SWATTER)
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->m_fire = true;
m_controls->m_look_back = false;
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,
// or can't find a close kart for too long time
if ((m_closest_kart_pos_data.distance < 15.0f &&
m_closest_kart_pos_data.distance > 3.0f) ||
m_time_since_last_shot > 15.0f)
{
m_controls->m_fire = true;
m_controls->m_look_back = true;
break;
}
break; // POWERUP_BUBBLEGUM
}
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)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 25.0f &&
!m_closest_kart->isInvulnerable())
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_CAKE
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)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 6.0f &&
(difficulty || perfect_aim))
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_BOWLING
case PowerupManager::POWERUP_SWATTER:
{
// 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.
if (m_kart->getShieldTime() > min_bubble_time)
break;
if (!m_closest_kart->isSquashed() &&
m_closest_kart_pos_data.distance < d2 &&
m_closest_kart->getSpeed() < m_kart->getSpeed())
{
m_controls->m_fire = true;
m_controls->m_look_back = false;
break;
}
break;
}
// Below powerups won't appear in battle mode, so skip them
case PowerupManager::POWERUP_ZIPPER:
break; // POWERUP_ZIPPER
case PowerupManager::POWERUP_PLUNGER:
break; // POWERUP_PLUNGER
case PowerupManager::POWERUP_SWITCH: // Don't handle switch
m_controls->m_fire = true; // (use it no matter what) for now
break; // POWERUP_SWITCH
case PowerupManager::POWERUP_PARACHUTE:
break; // POWERUP_PARACHUTE
case PowerupManager::POWERUP_ANVIL:
break; // POWERUP_ANVIL
case PowerupManager::POWERUP_RUBBERBALL:
break;
default:
Log::error("BattleAI",
"Invalid or unhandled powerup '%d' in default AI.",
m_kart->getPowerup()->getType());
assert(false);
}
if (m_controls->m_fire)
m_time_since_last_shot = 0.0f;
} // handleItems
//-----------------------------------------------------------------------------
void BattleAI::handleItemCollection(Vec3* aim_point, int* target_node)
{
float distance = 99999.9f;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
if (item_list.empty())
{
// Notice: this should not happen, as it makes no sense
// for an arean without items, if so how can attack happen?
Log::fatal ("BattleAI",
"AI can't find any items in the arena, "
"maybe there is something wrong with the navmesh, "
"make sure it lies closely to the ground.");
return;
}
unsigned int closest_item_num = 0;
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->wasCollected()) continue;
if ((item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL) &&
(m_kart->getEnergy() >
m_kart->getKartProperties()->getNitroSmallContainer()))
continue; // Ignore nitro when already has some
Vec3 d = item->getXYZ() - m_kart->getXYZ();
if (d.length_2d() <= distance &&
(item->getType() == Item::ITEM_BONUS_BOX ||
item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL))
{
closest_item_num = i;
distance = d.length_2d();
}
}
const Item *item_selected = item_list[closest_item_num].first;
if (item_selected->getType() == Item::ITEM_BONUS_BOX ||
item_selected->getType() == Item::ITEM_NITRO_BIG ||
item_selected->getType() == Item::ITEM_NITRO_SMALL)
{
*aim_point = item_selected->getXYZ();
*target_node = item_list[closest_item_num].second;
}
else
{
// Handle when all targets are swapped, which make AIs follow karts
*aim_point = m_closest_kart_point;
*target_node = m_closest_kart_node;
}
} // handleItemCollection
return m_world->isStartPhase();
} // isWaiting

View File

@ -21,140 +21,31 @@
#ifndef HEADER_BATTLE_AI_HPP
#define HEADER_BATTLE_AI_HPP
#undef AI_DEBUG
#ifdef AI_DEBUG
#include "graphics/irr_driver.hpp"
#endif
#include "karts/controller/ai_base_controller.hpp"
#include "race/race_manager.hpp"
#include "utils/random_generator.hpp"
#include "karts/controller/arena_ai.hpp"
class ThreeStrikesBattle;
class Vec3;
class Item;
namespace irr
/** The actual battle AI.
* \ingroup controller
*/
class BattleAI : public ArenaAI
{
namespace scene { class ISceneNode; }
namespace video { class ITexture; }
}
class BattleAI : public AIBaseController
{
private:
/** Holds the position info of targets. */
struct posData {bool behind; bool on_side; float angle; float distance;};
/** Used by handleBanana and UTurn, it tells whether to do left or right
* turning when steering is overridden. */
bool m_adjusting_side;
int m_closest_kart_node;
Vec3 m_closest_kart_point;
/** Pointer to the closest kart around this kart. */
AbstractKart *m_closest_kart;
posData m_closest_kart_pos_data;
posData m_cur_kart_pos_data;
/** Holds the current difficulty. */
RaceManager::Difficulty m_cur_difficulty;
/** Indicates that the steering of kart is overridden, and
* m_time_since_steering_overridden is counting down. */
bool m_is_steering_overridden;
/** Indicates that the kart is currently stuck, and m_time_since_reversing is
* counting down. */
bool m_is_stuck;
/** Indicates that the kart need a uturn to reach a node behind, and
* m_time_since_uturn is counting down. */
bool m_is_uturn;
/** Holds the unique node ai has driven through, useful to tell if AI is
* stuck by determine the size of this set. */
std::set <int> m_on_node;
/** Holds the corner points computed using the funnel algorithm that the AI
* will eventaully move through. See stringPull(). */
std::vector<Vec3> m_path_corners;
/** Holds the set of portals that the kart will cross when moving through
* polygon channel. See findPortals(). */
std::vector<std::pair<Vec3,Vec3> > m_portals;
/** The node(poly) at which the target point lies in. */
int m_target_node;
/** The target point. */
Vec3 m_target_point;
/** Time an item has been collected and not used. */
float m_time_since_last_shot;
/** This is a timer that counts down when the kart is reversing to get unstuck. */
float m_time_since_reversing;
/** This is a timer that counts down when the kart is starting to drive. */
float m_time_since_driving;
/** This is a timer that counts down when the steering of kart is overridden. */
float m_time_since_steering_overridden;
/** This is a timer that counts down when the kart is doing u-turn. */
float m_time_since_uturn;
void checkIfStuck(const float dt);
void checkPosition(const Vec3 &, posData*);
float determineTurnRadius(std::vector<Vec3>& points);
void findClosestKart(bool difficulty);
void findPortals(int start, int end);
void findTarget();
void handleAcceleration(const float dt);
void handleBanana();
void handleBraking();
void handleItems(const float dt);
void handleItemCollection(Vec3*, int*);
void handleSteering(const float dt);
void handleSwatter();
void handleUTurn(const float dt);
void stringPull(const Vec3&, const Vec3&);
protected:
/** Keep a pointer to world. */
ThreeStrikesBattle *m_world;
#ifdef AI_DEBUG
/** For debugging purpose: a sphere indicating where the AI
* is targeting at. */
irr::scene::ISceneNode *m_debug_sphere;
#endif
virtual void findClosestKart(bool use_difficulty);
virtual void findTarget();
virtual int getCurrentNode() const;
virtual bool isWaiting() const;
public:
BattleAI(AbstractKart *kart,
StateManager::ActivePlayer *player=NULL);
StateManager::ActivePlayer *player = NULL);
~BattleAI();
virtual void update (float delta);
virtual void reset ();
virtual void crashed(const AbstractKart *k) {};
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isNetworkController() const { return false; }
virtual bool isPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
virtual bool disableSlipstreamBonus() const {return 0; }
virtual void newLap(int lap) {};
};
#endif

View File

@ -34,9 +34,6 @@ protected:
float m_penalty_time;
/** This variable is required for battle mode **/
int m_current_node;
virtual void steer(float, int);
// ------------------------------------------------------------------------
/** Called when this kart started too early and got a start penalty. */
@ -59,10 +56,6 @@ public:
{
};
// ------------------------------------------------------------------------
unsigned int getCurrentNode() const { return m_current_node; }
// ------------------------------------------------------------------------
void setCurrentNode(int i) { m_current_node = i; }
// ------------------------------------------------------------------------
virtual bool isPlayerController() const OVERRIDE { return true; }
// ------------------------------------------------------------------------
virtual bool isLocalPlayerController() const OVERRIDE { return true; }

View File

@ -0,0 +1,195 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "karts/controller/soccer_ai.hpp"
#include "items/attachment.hpp"
#include "items/powerup.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/kart_control.hpp"
#include "karts/kart_properties.hpp"
#include "modes/soccer_world.hpp"
#include "tracks/battle_graph.hpp"
#ifdef AI_DEBUG
#include "irrlicht.h"
#include <iostream>
using namespace irr;
using namespace std;
#endif
SoccerAI::SoccerAI(AbstractKart *kart,
StateManager::ActivePlayer *player)
: ArenaAI(kart, player)
{
reset();
#ifdef AI_DEBUG
video::SColor col_debug(128, 128, 0, 0);
m_debug_sphere = irr_driver->addSphere(1.0f, col_debug);
m_debug_sphere->setVisible(true);
#endif
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
{
m_world = dynamic_cast<SoccerWorld*>(World::getWorld());
m_track = m_world->getTrack();
}
else
{
// Those variables are not defined in a battle mode (m_world is
// a linear world, since it assumes the existance of drivelines)
m_world = NULL;
m_track = NULL;
}
// Don't call our own setControllerName, since this will add a
// billboard showing 'AIBaseController' to the kart.
Controller::setControllerName("SoccerAI");
} // SoccerAI
//-----------------------------------------------------------------------------
SoccerAI::~SoccerAI()
{
#ifdef AI_DEBUG
irr_driver->removeNode(m_debug_sphere);
#endif
} // ~SoccerAI
//-----------------------------------------------------------------------------
/** Resets the AI when a race is restarted.
*/
void SoccerAI::reset()
{
ArenaAI::reset();
AIBaseController::reset();
m_cur_team = (m_kart->getWorldKartId() % 2 == 0 ? true : false);
} // reset
//-----------------------------------------------------------------------------
void SoccerAI::update(float dt)
{
ArenaAI::update(dt);
} // update
//-----------------------------------------------------------------------------
void SoccerAI::findClosestKart(bool use_difficulty)
{
float distance = 99999.9f;
const unsigned int n = m_world->getNumKarts();
int closest_kart_num = 0;
for (unsigned int i = 0; i < n; i++)
{
const AbstractKart* kart = m_world->getKart(i);
if (kart->isEliminated()) continue;
if (kart->getWorldKartId() == m_kart->getWorldKartId())
continue; // Skip the same kart
if (m_world->getKartTeam(kart
->getWorldKartId()) == m_world->getKartTeam(m_kart
->getWorldKartId()))
continue; // Skip the kart with the same team
Vec3 d = kart->getXYZ() - m_kart->getXYZ();
if (d.length_2d() <= distance)
{
distance = d.length_2d();
closest_kart_num = i;
}
}
const AbstractKart* closest_kart = m_world->getKart(closest_kart_num);
m_closest_kart_node = m_world->getKartNode(closest_kart_num);
m_closest_kart_point = closest_kart->getXYZ();
m_closest_kart = m_world->getKart(closest_kart_num);
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
} // findClosestKart
//-----------------------------------------------------------------------------
void SoccerAI::findTarget()
{
// Find a suitable target to drive to, either ball or powerup
if ((m_world->getBallPosition() - m_kart->getXYZ()).length_2d() > 10.0f &&
(m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER))
collectItemInArena(&m_target_point , &m_target_node);
else
{
m_target_node = m_world->getBallNode();
m_target_point = correctBallPosition(m_world->getBallPosition());
}
} // findTarget
//-----------------------------------------------------------------------------
Vec3 SoccerAI::correctBallPosition(const Vec3& orig_pos)
{
// Notice: Build with AI_DEBUG and change camera target to an AI kart,
// to debug or see how AI steer with the ball
posData ball_pos = {0};
posData goal_pos = {0};
Vec3 ball_lc(0, 0, 0);
checkPosition(orig_pos, &ball_pos, &ball_lc);
// !m_cur_team for opposite team goal
checkPosition(NavMesh::get()->getNavPoly(m_world
->getGoalNode(!m_cur_team)).getCenter(), &goal_pos);
if (goal_pos.behind)
{
// If facing the wrong goal, apply more offset to the ball
// to prevent shooting into its own team goal
ball_lc = (goal_pos.on_side ? ball_lc + Vec3 (2, 0, 2) :
ball_lc - Vec3 (2, 0, 0) + Vec3 (0, 0, 2));
return m_kart->getTrans()(ball_lc);
}
if (ball_pos.distance < 3.0f &&
!ball_pos.behind && !goal_pos.behind)
{
if (goal_pos.angle < 0.5f)
return orig_pos;
else
{
ball_lc = (goal_pos.on_side ? ball_lc + Vec3 (1, 0, 1) :
ball_lc - Vec3 (1, 0, 0) + Vec3 (0, 0, 1));
return m_kart->getTrans()(ball_lc);
}
}
return orig_pos;
} // correctBallPosition
// ------------------------------------------------------------------------
int SoccerAI::getCurrentNode() const
{
return m_world->getKartNode(m_kart->getWorldKartId());
} // getCurrentNode
// ------------------------------------------------------------------------
bool SoccerAI::isWaiting() const
{
return m_world->isStartPhase();
} // isWaiting

View File

@ -0,0 +1,53 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_SOCCER_AI_HPP
#define HEADER_SOCCER_AI_HPP
#include "karts/controller/arena_ai.hpp"
class SoccerWorld;
class Vec3;
class Item;
/** The actual soccer AI.
* \ingroup controller
*/
class SoccerAI : public ArenaAI
{
private:
/** Keep a pointer to world. */
SoccerWorld *m_world;
bool m_cur_team;
Vec3 correctBallPosition(const Vec3&);
virtual void findClosestKart(bool use_difficulty);
virtual void findTarget();
virtual int getCurrentNode() const;
virtual bool isWaiting() const;
public:
SoccerAI(AbstractKart *kart,
StateManager::ActivePlayer *player = NULL);
~SoccerAI();
virtual void update (float delta);
virtual void reset ();
bool getAITeam() const { return m_cur_team; }
};
#endif

View File

@ -44,6 +44,7 @@
#include "karts/kart_gfx.hpp"
#include "karts/rescue_animation.hpp"
#include "modes/overworld.hpp"
#include "modes/soccer_world.hpp"
#include "modes/world.hpp"
#include "io/file_manager.hpp"
#include "items/attachment.hpp"
@ -884,7 +885,6 @@ void Kart::setRaceResult()
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL)
{
// TODO NetworkController?
if (m_controller->isLocalPlayerController()) // if player is on this computer
{
PlayerProfile *player = PlayerManager::getCurrentPlayer();
@ -921,8 +921,8 @@ void Kart::setRaceResult()
}
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
{
// TODO complete together with soccer ai!
m_race_result = true;
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
m_race_result = sw->getKartSoccerResult(this->getWorldKartId());
}
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG)
{

View File

@ -27,16 +27,17 @@
#include "karts/kart_properties.hpp"
#include "karts/rescue_animation.hpp"
#include "karts/controller/local_player_controller.hpp"
#include "karts/controller/soccer_ai.hpp"
#include "physics/physics.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/check_goal.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "utils/constants.hpp"
#include <IMeshSceneNode.h>
#include <string>
//-----------------------------------------------------------------------------
/** Constructor. Sets up the clock mode etc.
*/
@ -45,7 +46,7 @@ SoccerWorld::SoccerWorld() : WorldWithRank()
if(race_manager->hasTimeTarget())
{
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
countDownReachedZero = false;
m_count_down_reached_zero = false;
}
else
{
@ -72,14 +73,7 @@ void SoccerWorld::init()
WorldWithRank::init();
m_display_rank = false;
m_goal_timer = 0.f;
m_lastKartToHitBall = -1;
// check for possible problems if AI karts were incorrectly added
if(getNumKarts() > race_manager->getNumPlayers())
{
Log::error("[SoccerWorld]", "No AI exists for this game mode");
exit(1);
}
m_last_kart_to_hit_ball = -1;
m_goal_target = race_manager->getMaxGoal();
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
@ -94,21 +88,24 @@ void SoccerWorld::reset()
if(race_manager->hasTimeTarget())
{
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
countDownReachedZero = false;
m_count_down_reached_zero = false;
}
else WorldStatus::setClockMode(CLOCK_CHRONO);
m_can_score_points = true;
memset(m_team_goals, 0, sizeof(m_team_goals));
m_team_goals.clear();
m_team_goals.resize(2);
m_team_goals[0] = 0;
m_team_goals[1] = 0;
// Reset original positions for the soccer balls
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
assert(tom);
m_redScorers.clear();
m_redScoreTimes.clear();
m_blueScorers.clear();
m_blueScoreTimes.clear();
m_lastKartToHitBall = -1;
m_red_scorers.clear();
m_red_score_times.clear();
m_blue_scorers.clear();
m_blue_score_times.clear();
m_last_kart_to_hit_ball = -1;
PtrVector<TrackObject>& objects = tom->getObjects();
for(unsigned int i=0; i<objects.size(); i++)
{
@ -127,6 +124,9 @@ void SoccerWorld::reset()
}
initKartList();
resetAllNodes();
initGoalNodes();
} // reset
//-----------------------------------------------------------------------------
@ -148,6 +148,10 @@ void SoccerWorld::update(float dt)
WorldWithRank::update(dt);
WorldWithRank::updateTrack(dt);
updateBallPosition();
if (m_track->hasNavMesh())
updateKartNodes();
if (world->getPhase() == World::GOAL_PHASE)
{
m_goal_timer += dt;
@ -163,33 +167,35 @@ void SoccerWorld::update(float dt)
//-----------------------------------------------------------------------------
void SoccerWorld::onCheckGoalTriggered(bool first_goal)
{
if (isRaceOver())
if (isRaceOver() || isStartPhase())
return;
if (m_can_score_points)
{
m_team_goals[first_goal ? 0 : 1]++;
m_team_goals[first_goal ? 1 : 0]++;
World *world = World::getWorld();
world->setPhase(WorldStatus::GOAL_PHASE);
m_goal_sound->play();
if(m_lastKartToHitBall != -1)
if(m_last_kart_to_hit_ball != -1)
{
if(first_goal)
{
m_redScorers.push_back(m_lastKartToHitBall);
// Notice: true first_goal means it's blue goal being shoot,
// so red team can score
m_red_scorers.push_back(m_last_kart_to_hit_ball);
if(race_manager->hasTimeTarget())
m_redScoreTimes.push_back(race_manager->getTimeTarget() - world->getTime());
m_red_score_times.push_back(race_manager->getTimeTarget() - world->getTime());
else
m_redScoreTimes.push_back(world->getTime());
m_red_score_times.push_back(world->getTime());
}
else
{
m_blueScorers.push_back(m_lastKartToHitBall);
m_blue_scorers.push_back(m_last_kart_to_hit_ball);
if(race_manager->hasTimeTarget())
m_blueScoreTimes.push_back(race_manager->getTimeTarget() - world->getTime());
m_blue_score_times.push_back(race_manager->getTimeTarget() - world->getTime());
else
m_blueScoreTimes.push_back(world->getTime());
m_blue_score_times.push_back(world->getTime());
}
}
}
@ -213,25 +219,15 @@ void SoccerWorld::onCheckGoalTriggered(bool first_goal)
//This ensures that only one goal is counted, and the second is ignored.
m_can_score_points = !m_can_score_points;
//for(int i=0 ; i < getNumKarts() ; i++
/*if(World::getWorld()->getTrack()->isAutoRescueEnabled() &&
!getKartAnimation() && fabs(getRoll())>60*DEGREE_TO_RAD &&
fabs(getSpeed())<3.0f )
{
new RescueAnimation(this, true);
}*/
// TODO: rescue the karts
} // onCheckGoalTriggered
//-----------------------------------------------------------------------------
/** Sets the last kart that hit the ball, to be able to
* identify the scorer later.
*/
void SoccerWorld::setLastKartTohitBall(unsigned int kartId)
void SoccerWorld::setLastKartTohitBall(unsigned int kart_id)
{
m_lastKartToHitBall = kartId;
m_last_kart_to_hit_ball = kart_id;
} // setLastKartTohitBall
//-----------------------------------------------------------------------------
@ -239,20 +235,15 @@ void SoccerWorld::setLastKartTohitBall(unsigned int kartId)
*/
bool SoccerWorld::isRaceOver()
{
// for tests : never over when we have a single player there :)
if (race_manager->getNumPlayers() < 2)
{
return false;
}
if(race_manager->hasTimeTarget())
{
return countDownReachedZero;
return m_count_down_reached_zero;
}
// One team scored the target goals ...
else
{
return (getScore(0) >= m_goal_target || getScore(1) >= m_goal_target);
return (getScore(true) >= m_goal_target || getScore(false) >= m_goal_target);
}
} // isRaceOver
@ -273,61 +264,18 @@ void SoccerWorld::terminateRace()
*/
void SoccerWorld::countdownReachedZero()
{
countDownReachedZero = true;
m_count_down_reached_zero = true;
} // countdownReachedZero
//-----------------------------------------------------------------------------
/** Returns the data to display in the race gui.
*/
void SoccerWorld::getKartsDisplayInfo(
std::vector<RaceGUIBase::KartIconDisplayInfo> *info)
{
// TODO!!
/*
const unsigned int kart_amount = getNumKarts();
for(unsigned int i = 0; i < kart_amount ; i++)
{
RaceGUIBase::KartIconDisplayInfo& rank_info = (*info)[i];
// reset color
rank_info.lap = -1;
AbstractKart* kart = getKart(i);
switch(kart->getSoccerTeam())
{
case SOCCER_TEAM_BLUE:
rank_info.r = 0.0f;
rank_info.g = 0.0f;
rank_info.b = 0.7f;
break;
case SOCCER_TEAM_RED:
rank_info.r = 0.9f;
rank_info.g = 0.0f;
rank_info.b = 0.0f;
break;
default:
assert(false && "Soccer team not set to blue or red");
rank_info.r = 0.0f;
rank_info.g = 0.0f;
rank_info.b = 0.0f;
}
}
*/
} // getKartsDisplayInfo
//-----------------------------------------------------------------------------
/** Set position and team for the karts */
void SoccerWorld::initKartList()
{
const unsigned int kart_amount = (unsigned int)m_karts.size();
int team_karts_amount[NB_SOCCER_TEAMS];
memset(team_karts_amount, 0, sizeof(team_karts_amount));
//Loading the indicator textures
irr::video::ITexture *redTeamTexture =
irr::video::ITexture *red =
irr_driver->getTexture(FileManager::GUI, "soccer_player_red.png");
irr::video::ITexture *blueTeamTexture =
irr::video::ITexture *blue =
irr_driver->getTexture(FileManager::GUI, "soccer_player_blue.png");
//Assigning indicators
@ -335,51 +283,30 @@ void SoccerWorld::initKartList()
{
scene::ISceneNode *arrowNode;
float arrow_pos_height = m_karts[i]->getKartModel()->getHeight()+0.5f;
SoccerTeam team = race_manager->getKartInfo(i).getSoccerTeam();
bool team = getKartTeam(i);
arrowNode = irr_driver->addBillboard(core::dimension2d<irr::f32>(0.3f,0.3f),
team==SOCCER_TEAM_RED ? redTeamTexture : blueTeamTexture,
m_karts[i]->getNode(), true);
team ? blue : red, m_karts[i]->getNode(), true);
arrowNode->setPosition(core::vector3df(0, arrow_pos_height, 0));
}
// Compute start positions for each team
int team_cur_position[NB_SOCCER_TEAMS];
team_cur_position[0] = 1;
for(int i=1 ; i < (int)NB_SOCCER_TEAMS ; i++)
team_cur_position[i] = team_karts_amount[i-1] + team_cur_position[i-1];
// Set kart positions, ordering them by team
for(unsigned int n=0; n<kart_amount; n++)
{
SoccerTeam team = race_manager->getKartInfo(n).getSoccerTeam();
#ifdef DEBUG
// In debug mode it's possible to play soccer with a single player
// (in artist debug mode). Avoid overwriting memory in this case.
if(team==SOCCER_TEAM_NONE) team=SOCCER_TEAM_RED;
#endif
m_karts[n]->setPosition(team_cur_position[team]);
team_cur_position[team]++;
} // next kart
}
//-----------------------------------------------------------------------------
int SoccerWorld::getScore(unsigned int i)
bool SoccerWorld::getKartSoccerResult(unsigned int kart_id) const
{
return m_team_goals[i];
} // getScore
if (m_red_scorers.size() == m_blue_scorers.size()) return true;
//-----------------------------------------------------------------------------
int SoccerWorld::getTeamLeader(unsigned int team)
{
for(unsigned int i = 0; i< m_karts.size(); i++)
{
if(race_manager->getKartInfo(i).getSoccerTeam() == (SoccerTeam) team)
return i;
}
return -1;
} // getTeamLeader
bool red_win = m_red_scorers.size() > m_blue_scorers.size();
bool team_win = getKartTeam(kart_id);
if ((red_win && !team_win) || (!red_win && team_win))
return true;
else
return false;
} // getKartSoccerResult
//-----------------------------------------------------------------------------
AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
@ -389,8 +316,14 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
{
int posIndex = index;
int position = index+1;
bool team = true;
if(race_manager->getKartInfo(index).getSoccerTeam() == SOCCER_TEAM_RED)
if (kart_type == RaceManager::KT_AI)
team = (index % 2 == 0 ? true : false);
else
team = getKartTeam(index);
if(!team)
{
if(index % 2 != 1) posIndex += 1;
}
@ -434,4 +367,115 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
} // createKart
//-----------------------------------------------------------------------------
/** Updates the m_kart_on_node value of each kart to localize it
* on the navigation mesh.
*/
void SoccerWorld::updateKartNodes()
{
if (isRaceOver()) return;
const unsigned int n = getNumKarts();
for (unsigned int i = 0; i < n; i++)
{
if (m_karts[i]->isEliminated()) continue;
m_kart_on_node[i] = BattleGraph::get()->pointToNode(m_kart_on_node[i],
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
}
}
//-----------------------------------------------------------------------------
/** Localize the ball on the navigation mesh.
*/
void SoccerWorld::updateBallPosition()
{
if (isRaceOver()) return;
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
assert(tom);
PtrVector<TrackObject>& objects = tom->getObjects();
for (unsigned int i = 0; i < objects.size(); i++)
{
TrackObject* obj = objects.get(i);
if (obj->isSoccerBall())
{
m_ball_position = obj->getPresentation<TrackObjectPresentationMesh>()
->getNode()->getPosition();
break;
}
}
if (m_track->hasNavMesh())
{
m_ball_on_node = BattleGraph::get()->pointToNode(m_ball_on_node,
m_ball_position, true/*ignore_vertical*/);
}
}
//-----------------------------------------------------------------------------
/** Localize two goals on the navigation mesh.
*/
void SoccerWorld::initGoalNodes()
{
if (!m_track->hasNavMesh()) return;
unsigned int n = CheckManager::get()->getCheckStructureCount();
for (unsigned int i = 0; i < n; i++)
{
CheckGoal* goal =
dynamic_cast<CheckGoal*>(CheckManager::get()->getCheckStructure(i));
if (goal)
{
if (goal->getTeam())
{
m_blue_goal_node = BattleGraph::get()->pointToNode(m_blue_goal_node,
goal->convertTo3DCenter(), true/*ignore_vertical*/);
}
else
{
m_red_goal_node = BattleGraph::get()->pointToNode(m_red_goal_node,
goal->convertTo3DCenter(), true/*ignore_vertical*/);
}
}
}
}
//-----------------------------------------------------------------------------
void SoccerWorld::resetAllNodes()
{
m_kart_on_node.clear();
m_kart_on_node.resize(m_karts.size());
for(unsigned int n=0; n<m_karts.size(); n++)
m_kart_on_node[n] = BattleGraph::UNKNOWN_POLY;
m_ball_on_node = BattleGraph::UNKNOWN_POLY;
m_ball_position = Vec3(0, 0, 0);
m_red_goal_node = BattleGraph::UNKNOWN_POLY;
m_blue_goal_node = BattleGraph::UNKNOWN_POLY;
}
//-----------------------------------------------------------------------------
bool SoccerWorld::getKartTeam(unsigned int kart_id) const
{
// Test for human player first
int rm_id = kart_id -
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
if (rm_id >= 0)
{
return (race_manager
->getKartInfo(rm_id).getSoccerTeam() == SOCCER_TEAM_BLUE ?
true : false);
}
else if (m_karts.size() > 0)
{
SoccerAI* controller =
dynamic_cast<SoccerAI*>(m_karts[kart_id]->getController());
if (controller)
return controller->getAITeam();
}
// Fallback
Log::warn("SoccerWorld", "Unknown team, using blue default.");
return true;
}

View File

@ -23,40 +23,64 @@
#include "states_screens/race_gui_base.hpp"
#include "karts/abstract_kart.hpp"
#include <IMesh.h>
#include <string>
#define CLEAR_SPAWN_RANGE 5
class PhysicalObject;
class AbstractKart;
class Controller;
/**
* \brief An implementation of World, to provide the soccer game mode
/** An implementation of World, to provide the soccer game mode
* Notice: In soccer world, true team means blue, false means red.
* \ingroup modes
*/
class SoccerWorld : public WorldWithRank
{
protected:
virtual AbstractKart *createKart(const std::string &kart_ident, int index,
int local_player_id, int global_player_id,
RaceManager::KartType type,
PerPlayerDifficulty difficulty);
private:
/** Number of goals each team scored */
int m_team_goals[NB_SOCCER_TEAMS];
/** Number of goals needed to win */
int m_goal_target;
bool countDownReachedZero;
bool m_count_down_reached_zero;
/** Whether or not goals can be scored (they are disabled when a point is scored
and re-enabled when the next game can be played)*/
bool m_can_score_points;
SFXBase *m_goal_sound;
/** Timer for displaying goal text*/
float m_goal_timer;
int m_lastKartToHitBall;
std::vector<int> m_redScorers;
std::vector<float> m_redScoreTimes;
std::vector<int> m_blueScorers;
std::vector<float> m_blueScoreTimes;
int m_last_kart_to_hit_ball;
/** Number of goals each team scored */
std::vector<int> m_team_goals;
std::vector<int> m_red_scorers;
std::vector<float> m_red_score_times;
std::vector<int> m_blue_scorers;
std::vector<float> m_blue_score_times;
/** Data generated from navmesh */
std::vector<int> m_kart_on_node;
int m_ball_on_node;
Vec3 m_ball_position;
int m_red_goal_node;
int m_blue_goal_node;
/** Set the team for the karts */
void initKartList();
/** Function to init the locations of two goals on the polygon map */
void initGoalNodes();
/** Function to update the locations of all karts on the polygon map */
void updateKartNodes();
/** Function to update the location the ball on the polygon map */
void updateBallPosition();
/** Clean up */
void resetAllNodes();
public:
SoccerWorld();
@ -74,39 +98,45 @@ public:
virtual bool useFastMusicNearEnd() const { return false; }
virtual void getKartsDisplayInfo(
std::vector<RaceGUIBase::KartIconDisplayInfo> *info);
int getScore(unsigned int i);
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) {}
virtual bool raceHasLaps() { return false; }
virtual const std::string& getIdent() const;
virtual void update(float dt);
// ------------------------------------------------------------------------
void onCheckGoalTriggered(bool first_goal);
int getTeamLeader(unsigned int i);
void setLastKartTohitBall(unsigned int kartId);
std::vector<int> getScorers(unsigned int team)
{
if(team == 0)
return m_redScorers;
else
return m_blueScorers;
}
std::vector<float> getScoreTimes(unsigned int team)
{
if(team == 0)
return m_redScoreTimes;
else
return m_blueScoreTimes;
}
// ------------------------------------------------------------------------
void setLastKartTohitBall(unsigned int kart_id);
// ------------------------------------------------------------------------
/** Get the soccer result of kart in soccer world (including AIs) */
bool getKartSoccerResult(unsigned int kart_id) const;
// ------------------------------------------------------------------------
/** Get the team of kart in soccer world (including AIs) */
bool getKartTeam(unsigned int kart_id) const;
// ------------------------------------------------------------------------
const int getScore(bool team) const
{ return (team ? m_team_goals[0] : m_team_goals[1]); }
// ------------------------------------------------------------------------
const std::vector<int>& getScorers(bool team) const
{ return (team ? m_blue_scorers : m_red_scorers); }
// ------------------------------------------------------------------------
const std::vector<float>& getScoreTimes(bool team) const
{ return (team ? m_blue_score_times : m_red_score_times); }
// ------------------------------------------------------------------------
const int& getKartNode(unsigned int kart_id) const
{ return m_kart_on_node[kart_id]; }
// ------------------------------------------------------------------------
const int& getBallNode() const
{ return m_ball_on_node; }
// ------------------------------------------------------------------------
const Vec3& getBallPosition() const
{ return m_ball_position; }
// ------------------------------------------------------------------------
const int& getGoalNode(bool team) const
{ return (team ? m_blue_goal_node : m_red_goal_node); }
private:
void initKartList();
protected:
virtual AbstractKart *createKart(const std::string &kart_ident, int index,
int local_player_id, int global_player_id,
RaceManager::KartType type,
PerPlayerDifficulty difficulty);
}; // SoccerWorld

View File

@ -453,69 +453,9 @@ void ThreeStrikesBattle::updateKartNodes()
{
if (m_karts[i]->isEliminated()) continue;
const int saved_current_node = m_kart_info[i].m_on_node;
if (saved_current_node == BattleGraph::UNKNOWN_POLY)
{
// Try all nodes in the battle graph
bool found = false;
unsigned int node = 0;
while (!found && node < BattleGraph::get()->getNumNodes())
{
const NavPoly& p_all = BattleGraph::get()->getPolyOfNode(node);
if ((p_all.pointInPoly(m_karts[i]->getXYZ())))
{
m_kart_info[i].m_on_node = node;
found = true;
}
node++;
}
}
else
{
// Check if the kart is still on the same node
const NavPoly& p_cur = BattleGraph::get()
->getPolyOfNode(saved_current_node);
if (p_cur.pointInPoly(m_karts[i]->getXYZ())) continue;
// If not then check all adjacent polys
const std::vector<int>& adjacents = NavMesh::get()
->getAdjacentPolys(saved_current_node);
// Set current node to unknown so that if no adjacent polygons,
// we look everywhere the next time updateKartNodes is called.
// This is useful in cases when you are "teleported"
// to some other polygons, ex. rescue
m_kart_info[i].m_on_node = BattleGraph::UNKNOWN_POLY;
bool found = false;
unsigned int num = 0;
while (!found && num < adjacents.size())
{
const NavPoly& p_temp =
BattleGraph::get()->getPolyOfNode(adjacents[num]);
if (p_temp.pointInPoly(m_karts[i]->getXYZ()))
{
m_kart_info[i].m_on_node = adjacents[num];
found = true;
}
num++;
}
// Current node is still unkown
if (m_kart_info[i].m_on_node == BattleGraph::UNKNOWN_POLY)
{
// Calculated distance from saved node to current position,
// if it's close enough than use the saved node anyway, it
// may happen when the kart stays on the edge of obstacles
const NavPoly& p = BattleGraph::get()
->getPolyOfNode(saved_current_node);
const float dist = (p.getCenter() - m_karts[i]->getXYZ()).length_2d();
if (dist < 3.0f)
m_kart_info[i].m_on_node = saved_current_node;
}
}
m_kart_info[i].m_on_node = BattleGraph::get()
->pointToNode(m_kart_info[i].m_on_node,
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
}
}

View File

@ -71,7 +71,7 @@ private:
PtrVector<TrackObject, REF> m_tires;
/** Function to udpate the locations of all karts on the polygon map */
/** Function to update the locations of all karts on the polygon map */
void updateKartNodes();
public:

View File

@ -32,6 +32,7 @@
#include "input/keyboard_device.hpp"
#include "items/projectile_manager.hpp"
#include "karts/controller/battle_ai.hpp"
#include "karts/controller/soccer_ai.hpp"
#include "karts/controller/end_controller.hpp"
#include "karts/controller/local_player_controller.hpp"
#include "karts/controller/skidding_ai.hpp"
@ -352,6 +353,8 @@ Controller* World::loadAIController(AbstractKart *kart)
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
turn=1;
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
turn=2;
// If different AIs should be used, adjust turn (or switch randomly
// or dependent on difficulty)
switch(turn)
@ -362,6 +365,9 @@ Controller* World::loadAIController(AbstractKart *kart)
case 1:
controller = new BattleAI(kart);
break;
case 2:
controller = new SoccerAI(kart);
break;
default:
Log::warn("[World]", "Unknown AI, using default.");
controller = new SkiddingAI(kart);

View File

@ -31,7 +31,6 @@ enum SoccerTeam
SOCCER_TEAM_NONE=-1,
SOCCER_TEAM_RED=0,
SOCCER_TEAM_BLUE=1,
NB_SOCCER_TEAMS
};
/** Game difficulty per player. */

View File

@ -197,7 +197,7 @@ public:
case MINOR_MODE_FOLLOW_LEADER: return true;
case MINOR_MODE_3_STRIKES: return true;
case MINOR_MODE_EASTER_EGG: return false;
case MINOR_MODE_SOCCER: return false;
case MINOR_MODE_SOCCER: return true;
default: assert(false); return false;
}
} // hasAI

View File

@ -95,7 +95,9 @@ void ArenasScreen::beforeAddingWidget()
Track* temp = track_manager->getTrack(n);
if (soccer_mode)
{
if(temp->isSoccer())
if(temp->isSoccer() && (temp->hasNavMesh() ||
race_manager->getNumLocalPlayers() > 1 ||
UserConfigParams::m_artist_debug_mode))
num_of_arenas++;
}
else
@ -196,6 +198,14 @@ void ArenasScreen::eventCallback(Widget* widget, const std::string& name, const
Track* clicked_track = track_manager->getTrack(selection);
if (clicked_track != NULL)
{
//HACK hardcode for single player soccer
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER &&
race_manager->getNumPlayers() == 1)
{
race_manager->setMaxGoal(4);
race_manager->setKartSoccerTeam(0, SOCCER_TEAM_RED);
}
TrackInfoScreen::getInstance()->setTrack(clicked_track);
TrackInfoScreen::getInstance()->push();
} // clickedTrack != NULL
@ -239,7 +249,18 @@ void ArenasScreen::buildTrackList()
Track* curr = track_manager->getTrack(n);
if (soccer_mode)
{
if(!curr->isSoccer()) continue;
if(curr->isSoccer() && curr->hasNavMesh() && !arenas_have_navmesh)
arenas_have_navmesh = true;
if(!curr->isSoccer() ||
(!(curr->hasNavMesh() ||
race_manager->getNumLocalPlayers() > 1 ||
UserConfigParams::m_artist_debug_mode)))
{
if (curr->isSoccer())
m_unsupported_arena.insert(n);
continue;
}
}
else
{
@ -280,7 +301,18 @@ void ArenasScreen::buildTrackList()
Track* curr = track_manager->getTrack(currArenas[n]);
if (soccer_mode)
{
if(!curr->isSoccer()) continue;
if(curr->isSoccer() && curr->hasNavMesh() && !arenas_have_navmesh)
arenas_have_navmesh = true;
if(!curr->isSoccer() ||
(!(curr->hasNavMesh() ||
race_manager->getNumLocalPlayers() > 1 ||
UserConfigParams::m_artist_debug_mode)))
{
if (curr->isSoccer())
m_unsupported_arena.insert(currArenas[n]);
continue;
}
}
else
{

View File

@ -48,6 +48,7 @@ using namespace irr;
#include "modes/soccer_world.hpp"
#include "race/race_manager.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "utils/constants.hpp"
#include "utils/string_utils.hpp"
#include "utils/translation.hpp"
@ -225,54 +226,45 @@ void RaceGUI::renderPlayerView(const Camera *camera, float dt)
void RaceGUI::drawScores()
{
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
int offsetY = 5;
int offsetX = 5;
gui::ScalableFont* font = GUIEngine::getFont();
int offset_y = 5;
int offset_x = 5;
gui::ScalableFont* font = GUIEngine::getTitleFont();
static video::SColor color = video::SColor(255,255,255,255);
//Draw kart icons above score(denoting teams)
//Draw two teams score
irr::video::ITexture *red_team = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_red.png");
irr::video::ITexture *blue_team = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_blue.png");
irr::video::ITexture *team_icon = red_team;
int numLeader = 1;
for(unsigned int i=0; i<soccerWorld->getNumKarts(); i++)
for(unsigned int i=0; i<2; i++)
{
int j = soccerWorld->getTeamLeader(i);
if(j < 0) break;
core::recti position(offset_x, offset_y,
offset_x + 2*m_minimap_player_size, offset_y + 2*m_minimap_player_size);
AbstractKart* kart = soccerWorld->getKart(i);
video::ITexture* icon = kart->getKartProperties()->getMinimapIcon();
core::rect<s32> source(core::position2di(0, 0), icon->getSize());
core::recti position(offsetX, offsetY,
offsetX + 2*m_minimap_player_size, offsetY + 2*m_minimap_player_size);
draw2DImage(icon, position, source,
NULL, NULL, true);
core::stringw score = StringUtils::toWString(soccerWorld->getScore(i));
int string_height =
GUIEngine::getFont()->getDimension(score.c_str()).Height;
core::recti pos(position.UpperLeftCorner.X + 5,
position.LowerRightCorner.Y + offsetY,
position.LowerRightCorner.Y + offset_y,
position.LowerRightCorner.X,
position.LowerRightCorner.Y + string_height);
font->draw(score.c_str(),pos,color);
if (numLeader == 2)
if (i == 1)
{
team_icon = blue_team;
}
core::rect<s32> indicatorPos(offsetX, offsetY,
offsetX + (int)(m_minimap_player_size/1.25f),
offsetY + (int)(m_minimap_player_size/1.25f));
core::rect<s32> sourceRect(core::position2d<s32>(0,0),
core::rect<s32> indicator_pos(offset_x, offset_y,
offset_x + (int)(m_minimap_player_size*2),
offset_y + (int)(m_minimap_player_size*2));
core::rect<s32> source_rect(core::position2d<s32>(0,0),
team_icon->getSize());
draw2DImage(team_icon,indicatorPos,sourceRect,
draw2DImage(team_icon,indicator_pos,source_rect,
NULL,NULL,true);
numLeader++;
offsetX += position.LowerRightCorner.X;
offset_x += position.LowerRightCorner.X;
}
} // drawScores
@ -343,8 +335,8 @@ void RaceGUI::drawGlobalMiniMap()
{
World *world = World::getWorld();
// draw a map when arena has a navigation mesh.
if ((world->getTrack()->isArena() && !(world->getTrack()->hasNavMesh())) ||
world->getTrack()->isSoccer())
if ((world->getTrack()->isArena() || world->getTrack()->isSoccer()) &&
!(world->getTrack()->hasNavMesh()))
return;
const video::ITexture *old_rtt_mini_map = world->getTrack()->getOldRttMiniMap();
@ -372,6 +364,22 @@ void RaceGUI::drawGlobalMiniMap()
dest, source, NULL, video::SColor(127, 255, 255, 255), true);
}
SoccerWorld *sw = dynamic_cast<SoccerWorld*>(World::getWorld());
if (sw)
{
Vec3 draw_at;
world->getTrack()->mapPoint2MiniMap(sw->getBallPosition(), &draw_at);
video::ITexture* icon =
irr_driver->getTexture(FileManager::GUI, "soccer_ball_blue.png");
core::rect<s32> source(core::position2di(0, 0), icon->getSize());
core::rect<s32> position(m_map_left+(int)(draw_at.getX()-(m_minimap_ai_size>>2)),
lower_y -(int)(draw_at.getY()+(m_minimap_ai_size>>2)),
m_map_left+(int)(draw_at.getX()+(m_minimap_ai_size>>2)),
lower_y -(int)(draw_at.getY()-(m_minimap_ai_size>>2)));
draw2DImage(icon, position, source, NULL, NULL, true);
}
for(unsigned int i=0; i<world->getNumKarts(); i++)
{
const AbstractKart *kart = world->getKart(i);
@ -393,6 +401,7 @@ void RaceGUI::drawGlobalMiniMap()
lower_y -(int)(draw_at.getY()-marker_half_size));
draw2DImage(icon, position, source, NULL, NULL, true);
} // for i<getNumKarts
} // drawGlobalMiniMap
//-----------------------------------------------------------------------------

View File

@ -981,136 +981,137 @@ void RaceResultGUI::displaySoccerResults()
{
//Draw win text
core::stringw resultText;
core::stringw result_text;
static video::SColor color = video::SColor(255, 255, 255, 255);
gui::IGUIFont* font = GUIEngine::getTitleFont();
int currX = UserConfigParams::m_width/2;
int current_x = UserConfigParams::m_width/2;
RowInfo *ri = &(m_all_row_infos[0]);
int currY = (int)ri->m_y_pos;
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
int teamScore[2] = {soccerWorld->getScore(0), soccerWorld->getScore(1)};
int current_y = (int)ri->m_y_pos;
SoccerWorld* sw = (SoccerWorld*)World::getWorld();
const int red_score = sw->getScore(false);
const int blue_score = sw->getScore(true);
GUIEngine::Widget *table_area = getWidget("result-table");
int height = table_area->m_h + table_area->m_y;
if(teamScore[0] > teamScore[1])
if(red_score > blue_score)
{
resultText = _("Red Team Wins");
result_text = _("Red Team Wins");
}
else if(teamScore[1] > teamScore[0])
else if(blue_score > red_score)
{
resultText = _("Blue Team Wins");
result_text = _("Blue Team Wins");
}
else
{
//Cannot really happen now. Only in time limited matches.
resultText = _("It's a draw");
result_text = _("It's a draw");
}
core::rect<s32> pos(currX, currY, currX, currY);
font->draw(resultText.c_str(), pos, color, true, true);
core::rect<s32> pos(current_x, current_y, current_x, current_y);
font->draw(result_text.c_str(), pos, color, true, true);
core::dimension2du rect = m_font->getDimension(resultText.c_str());
core::dimension2du rect = m_font->getDimension(result_text.c_str());
//Draw team scores:
currY += rect.Height;
currX /= 2;
irr::video::ITexture* redTeamIcon = irr_driver->getTexture(FileManager::GUI,
current_y += rect.Height;
current_x /= 2;
irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_red.png");
irr::video::ITexture* blueTeamIcon = irr_driver->getTexture(FileManager::GUI,
irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_blue.png");
core::recti sourceRect(core::vector2di(0,0), redTeamIcon->getSize());
core::recti destRect(currX, currY, currX+redTeamIcon->getSize().Width/2,
currY+redTeamIcon->getSize().Height/2);
draw2DImage(redTeamIcon, destRect,sourceRect,
core::recti source_rect(core::vector2di(0,0), red_icon->getSize());
core::recti dest_rect(current_x, current_y, current_x+red_icon->getSize().Width/2,
current_y+red_icon->getSize().Height/2);
draw2DImage(red_icon, dest_rect,source_rect,
NULL,NULL, true);
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
destRect = core::recti(currX, currY, currX+redTeamIcon->getSize().Width/2,
currY+redTeamIcon->getSize().Height/2);
draw2DImage(blueTeamIcon,destRect,sourceRect,
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
dest_rect = core::recti(current_x, current_y, current_x+red_icon->getSize().Width/2,
current_y+red_icon->getSize().Height/2);
draw2DImage(blue_icon,dest_rect,source_rect,
NULL, NULL, true);
resultText = StringUtils::toWString(teamScore[1]);
rect = m_font->getDimension(resultText.c_str());
currX += redTeamIcon->getSize().Width/4;
currY += redTeamIcon->getSize().Height/2 + rect.Height/4;
pos = core::rect<s32>(currX, currY, currX, currY);
result_text = StringUtils::toWString(blue_score);
rect = m_font->getDimension(result_text.c_str());
current_x += red_icon->getSize().Width/4;
current_y += red_icon->getSize().Height/2 + rect.Height/4;
pos = core::rect<s32>(current_x, current_y, current_x, current_y);
color = video::SColor(255,255,255,255);
font->draw(resultText.c_str(), pos, color, true, false);
font->draw(result_text.c_str(), pos, color, true, false);
currX -= UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
resultText = StringUtils::toWString(teamScore[0]);
pos = core::rect<s32>(currX,currY,currX,currY);
font->draw(resultText.c_str(), pos, color, true, false);
current_x -= UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
result_text = StringUtils::toWString(red_score);
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
font->draw(result_text.c_str(), pos, color, true, false);
int centerX = UserConfigParams::m_width/2;
pos = core::rect<s32>(centerX, currY, centerX, currY);
int center_x = UserConfigParams::m_width/2;
pos = core::rect<s32>(center_x, current_y, center_x, current_y);
font->draw("-", pos, color, true, false);
//Draw goal scorers:
//The red scorers:
currY += rect.Height/2 + rect.Height/4;
current_y += rect.Height/2 + rect.Height/4;
font = GUIEngine::getSmallFont();
std::vector<int> scorers = soccerWorld->getScorers(0);
std::vector<float> scoreTimes = soccerWorld->getScoreTimes(0);
irr::video::ITexture* scorerIcon;
std::vector<int> scorers = sw->getScorers(false);
std::vector<float> score_times = sw->getScoreTimes(false);
irr::video::ITexture* scorer_icon;
int prevY = currY;
int prev_y = current_y;
for(unsigned int i=0; i<scorers.size(); i++)
{
resultText = soccerWorld->getKart(scorers.at(i))->
result_text = sw->getKart(scorers.at(i))->
getKartProperties()->getName();
resultText.append(" ");
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
rect = m_font->getDimension(resultText.c_str());
result_text.append(" ");
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
rect = m_font->getDimension(result_text.c_str());
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
currY += (height-prevY)/((short)scorers.size()+1);
if(height-prev_y < ((short)scorers.size()+1)*(short)rect.Height)
current_y += (height-prev_y)/((short)scorers.size()+1);
else
currY += rect.Height;
current_y += rect.Height;
if(currY > height) break;
if(current_y > height) break;
pos = core::rect<s32>(currX,currY,currX,currY);
font->draw(resultText,pos, color, true, false);
scorerIcon = soccerWorld->getKart(scorers.at(i))
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
font->draw(result_text,pos, color, true, false);
scorer_icon = sw->getKart(scorers.at(i))
->getKartProperties()->getIconMaterial()->getTexture();
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
draw2DImage(scorerIcon, destRect, sourceRect,
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
draw2DImage(scorer_icon, dest_rect, source_rect,
NULL, NULL, true);
}
//The blue scorers:
currY = prevY;
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
scorers = soccerWorld->getScorers(1);
scoreTimes = soccerWorld->getScoreTimes(1);
current_y = prev_y;
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
scorers = sw->getScorers(true);
score_times = sw->getScoreTimes(true);
for(unsigned int i=0; i<scorers.size(); i++)
{
resultText = soccerWorld->getKart(scorers.at(i))->
result_text = sw->getKart(scorers.at(i))->
getKartProperties()->getName();
resultText.append(" ");
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
rect = m_font->getDimension(resultText.c_str());
result_text.append(" ");
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
rect = m_font->getDimension(result_text.c_str());
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
currY += (height-prevY)/((short)scorers.size()+1);
if(height-prev_y < ((short)scorers.size()+1)*(short)rect.Height)
current_y += (height-prev_y)/((short)scorers.size()+1);
else
currY += rect.Height;
current_y += rect.Height;
if(currY > height) break;
if(current_y > height) break;
pos = core::rect<s32>(currX,currY,currX,currY);
font->draw(resultText,pos, color, true, false);
scorerIcon = soccerWorld->getKart(scorers.at(i))->
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
font->draw(result_text,pos, color, true, false);
scorer_icon = sw->getKart(scorers.at(i))->
getKartProperties()->getIconMaterial()->getTexture();
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
draw2DImage(scorerIcon, destRect, sourceRect,
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
draw2DImage(scorer_icon, dest_rect, source_rect,
NULL, NULL, true);
}
}

View File

@ -115,13 +115,10 @@ void RaceSetupScreen::init()
w2->addItem( name4, IDENT_STRIKES, RaceManager::getIconOf(RaceManager::MINOR_MODE_3_STRIKES));
#ifdef ENABLE_SOCCER_MODE
if (race_manager->getNumLocalPlayers() > 1 || UserConfigParams::m_artist_debug_mode)
{
irr::core::stringw name5 = irr::core::stringw(
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
}
irr::core::stringw name5 = irr::core::stringw(
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
#endif
#define ENABLE_EASTER_EGG_MODE
@ -237,7 +234,6 @@ void RaceSetupScreen::eventCallback(Widget* widget, const std::string& name,
{
race_manager->setMinorMode(RaceManager::MINOR_MODE_SOCCER);
UserConfigParams::m_game_mode = CONFIG_CODE_SOCCER;
race_manager->setNumKarts( race_manager->getNumLocalPlayers() ); // no AI karts;
// 1 player -> no need to choose a team or determine when the match ends
if(race_manager->getNumLocalPlayers() <= 1)
ArenasScreen::getInstance()->push();

View File

@ -154,7 +154,8 @@ void TrackInfoScreen::init()
// -------------
const int local_players = race_manager->getNumLocalPlayers();
const bool has_AI =
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ?
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER ?
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
race_manager->hasAI());
m_ai_kart_spinner->setVisible(has_AI);
@ -170,7 +171,8 @@ void TrackInfoScreen::init()
m_ai_kart_spinner->setValue(num_ai);
race_manager->setNumKarts(num_ai + local_players);
// Set the max karts supported based on the battle arena selected
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
{
m_ai_kart_spinner->setMax(max_arena_players - local_players);
}
@ -181,8 +183,9 @@ void TrackInfoScreen::init()
{
m_ai_kart_spinner->setMin(3 - local_players);
}
// Make sure in battle mode at least 1 ai for single player
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES &&
// Make sure in battle and soccer mode at least 1 ai for single player
else if((race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER) &&
local_players == 1 &&
!UserConfigParams::m_artist_debug_mode)
m_ai_kart_spinner->setMin(1);
@ -310,7 +313,8 @@ void TrackInfoScreen::onEnterPressedInternal()
const int max_arena_players = m_track->getMaxArenaPlayers();
const int local_players = race_manager->getNumLocalPlayers();
const bool has_AI =
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ?
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER ?
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
race_manager->hasAI());

View File

@ -137,7 +137,7 @@ void BattleGraph::findItemsOnGraphNodes()
for (unsigned int j = 0; j < this->getNumNodes(); ++j)
{
if (NavMesh::get()->getNavPoly(j).pointInPoly(xyz))
if (NavMesh::get()->getNavPoly(j).pointInPoly(xyz, false))
polygon = j;
}
@ -149,7 +149,70 @@ void BattleGraph::findItemsOnGraphNodes()
else
Log::debug("BattleGraph","Can't map item number %d with a suitable polygon", i);
}
}
} // findItemsOnGraphNodes
// -----------------------------------------------------------------------------
int BattleGraph::pointToNode(const int cur_node,
const Vec3& cur_point,
bool ignore_vertical) const
{
int final_node = BattleGraph::UNKNOWN_POLY;
if (cur_node == BattleGraph::UNKNOWN_POLY)
{
// Try all nodes in the battle graph
bool found = false;
unsigned int node = 0;
while (!found && node < this->getNumNodes())
{
const NavPoly& p_all = this->getPolyOfNode(node);
if (p_all.pointInPoly(cur_point, ignore_vertical))
{
final_node = node;
found = true;
}
node++;
}
}
else
{
// Check if the point is still on the same node
const NavPoly& p_cur = this->getPolyOfNode(cur_node);
if (p_cur.pointInPoly(cur_point, ignore_vertical)) return cur_node;
// If not then check all adjacent polys
const std::vector<int>& adjacents = NavMesh::get()
->getAdjacentPolys(cur_node);
bool found = false;
unsigned int num = 0;
while (!found && num < adjacents.size())
{
const NavPoly& p_temp = this->getPolyOfNode(adjacents[num]);
if (p_temp.pointInPoly(cur_point, ignore_vertical))
{
final_node = adjacents[num];
found = true;
}
num++;
}
// Current node is still unkown
if (final_node == BattleGraph::UNKNOWN_POLY)
{
// Calculated distance from saved node to current position,
// if it's close enough than use the saved node anyway, it
// may happen when the kart stays on the edge of obstacles
const NavPoly& p = this->getPolyOfNode(cur_node);
const float dist = (p.getCenter() - cur_point).length_2d();
if (dist < 3.0f)
final_node = cur_node;
}
}
return final_node;
} // pointToNode
// -----------------------------------------------------------------------------
@ -158,4 +221,4 @@ const int & BattleGraph::getNextShortestPathPoly(int i, int j) const
if (i == BattleGraph::UNKNOWN_POLY || j == BattleGraph::UNKNOWN_POLY)
return BattleGraph::UNKNOWN_POLY;
return m_parent_poly[j][i];
}
} // getNextShortestPathPoly

View File

@ -126,6 +126,10 @@ public:
{ return m_items_on_graph; }
void findItemsOnGraphNodes();
// ----------------------------------------------------------------------
int pointToNode(const int cur_node,
const Vec3& cur_point,
bool ignore_vertical) const;
}; //BattleGraph
#endif

View File

@ -51,42 +51,28 @@ CheckGoal::CheckGoal(const XMLNode &node, unsigned int index)
*/
void CheckGoal::update(float dt)
{
World *world = World::getWorld();
assert(world);
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
Track* track = world->getTrack();
assert(track);
TrackObjectManager* tom = track->getTrackObjectManager();
assert(tom);
PtrVector<TrackObject>& objects = tom->getObjects();
unsigned int ball_index = 0;
for(unsigned int i=0; i<objects.size(); i++)
if (world)
{
TrackObject* obj = objects.get(i);
if(!obj->isSoccerBall())
continue;
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
if(isTriggered(m_previous_position[ball_index], xyz, -1))
const Vec3 &xyz = world->getBallPosition();
if (isTriggered(m_previous_position[0], xyz, -1))
{
if(UserConfigParams::m_check_debug)
Log::info("CheckGoal", "Goal check structure %d triggered for object %s.",
m_index, obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getDebugName());
trigger(ball_index);
if (UserConfigParams::m_check_debug)
{
Log::info("CheckGoal", "Goal check structure"
"%d triggered for ball.", m_index);
}
trigger(0);
}
m_previous_position[ball_index] = xyz;
ball_index++;
m_previous_position[0] = xyz;
}
} // update
// ----------------------------------------------------------------------------
/** Called when the check line is triggered. This function creates a cannon
* animation object and attaches it to the kart.
* \param kart_index The index of the kart that triggered the check line.
/** Called when the goal line is triggered. Input any integer for i to use
*/
void CheckGoal::trigger(unsigned int kart_index)
void CheckGoal::trigger(unsigned int i)
{
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
if(!world)
@ -116,21 +102,23 @@ bool CheckGoal::isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
void CheckGoal::reset(const Track &track)
{
CheckStructure::reset(track);
const TrackObjectManager* tom = track.getTrackObjectManager();
assert(tom);
// Clean again as kart info is stored in CheckStructure::reset
m_previous_position.clear();
const PtrVector<TrackObject>& objects = tom->getObjects();
for(unsigned int i=0; i<objects.size(); i++)
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
if (world)
{
const TrackObject* obj = objects.get(i);
if(!obj->isSoccerBall())
continue;
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
m_previous_position.push_back(xyz);
const Vec3 &xyz = world->getBallPosition();
m_previous_position[0] = xyz;
}
} // reset
// ----------------------------------------------------------------------------
Vec3 CheckGoal::convertTo3DCenter() const
{
float x = m_line.getMiddle().X;
float y = m_line.getMiddle().Y;
return Vec3(x, 0, y);
}

View File

@ -27,6 +27,7 @@ using namespace irr;
class CheckManager;
class XMLNode;
class Track;
class Vec3;
/**
* \brief Implements a simple checkline that will score a point when the
@ -51,6 +52,11 @@ public:
virtual bool isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
unsigned int indx) OVERRIDE;
virtual void reset(const Track &track) OVERRIDE;
// ------------------------------------------------------------------------
bool getTeam() const { return m_first_goal; }
// ------------------------------------------------------------------------
Vec3 convertTo3DCenter() const;
}; // CheckLine
#endif

View File

@ -53,6 +53,8 @@ CheckStructure::CheckStructure(const XMLNode &node, unsigned int index)
// Cannons don't have a kind specified, so test for the name in this case
else if(node.getName()=="cannon")
m_check_type = CT_CANNON;
else if(node.getName()=="goal")
m_check_type = CT_GOAL;
else
Log::warn("CheckStructure", "Unknown check structure '%s' - ignored.", kind.c_str());

View File

@ -52,7 +52,7 @@ const std::vector<Vec3> NavPoly::getVertices()
//-----------------------------------------------------------------------------
bool NavPoly::pointInPoly(const Vec3& p) const
bool NavPoly::pointInPoly(const Vec3& p, bool ignore_vertical) const
{
std::vector<Vec3> points;
for(unsigned int i=0; i<m_vertices.size(); i++)
@ -70,6 +70,8 @@ bool NavPoly::pointInPoly(const Vec3& p) const
return false;
}
if (ignore_vertical) return true;
// Check for vertical distance too
const float dist = p.getY() - m_center.getY();
if (fabsf(dist) > 1.0f )

View File

@ -61,7 +61,8 @@ public:
// ------------------------------------------------------------------------
/** Returns true if a given point lies in this polygon. */
bool pointInPoly(const Vec3& p) const;
bool pointInPoly(const Vec3& p,
bool ignore_vertical) const;
const Vec3& operator[](int i) const ;

View File

@ -589,11 +589,16 @@ void Track::loadTrackInfo()
if(file_manager->fileExists(m_root+"navmesh.xml"))
m_has_navmesh = true;
else if(m_is_arena)
else if(m_is_arena || m_is_soccer)
{
Log::warn("Track", "NavMesh is not found for arena %s, "
"disable AI for it.\n", m_name.c_str());
}
if (m_is_soccer)
{
// Currently only max six players in soccer mode
m_max_arena_players = 6;
}
} // loadTrackInfo
@ -716,7 +721,7 @@ void Track::loadQuadGraph(unsigned int mode_id, const bool reverse)
void Track::mapPoint2MiniMap(const Vec3 &xyz, Vec3 *draw_at) const
{
if (m_is_arena && m_has_navmesh)
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
BattleGraph::get()->mapPoint2MiniMap(xyz, draw_at);
else
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
@ -1032,7 +1037,7 @@ void Track::loadMinimap()
core::dimension2du size = m_mini_map_size
.getOptimalSize(!nonpower,!nonsquare);
if (m_is_arena && m_has_navmesh)
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
{
BattleGraph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255),
&m_old_rtt_mini_map, &m_new_rtt_mini_map);
@ -1645,7 +1650,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
// the information about the size of the texture to render the mini
// map to.
if (!m_is_arena && !m_is_soccer && !m_is_cutscene) loadQuadGraph(mode_id, reverse_track);
else if (m_is_arena && !m_is_soccer && !m_is_cutscene && m_has_navmesh)
else if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh)
loadBattleGraph();
ItemManager::create();
@ -1871,7 +1876,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
delete root;
if (m_is_arena && !m_is_soccer && !m_is_cutscene && m_has_navmesh)
if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh)
BattleGraph::get()->findItemsOnGraphNodes();
if (UserConfigParams::m_track_debug &&

View File

@ -107,6 +107,10 @@ enum DebugMenuCommand
DEBUG_VIEW_KART_TWO,
DEBUG_VIEW_KART_THREE,
DEBUG_VIEW_KART_FOUR,
DEBUG_VIEW_KART_FIVE,
DEBUG_VIEW_KART_SIX,
DEBUG_VIEW_KART_SEVEN,
DEBUG_VIEW_KART_EIGHT,
DEBUG_HIDE_KARTS,
DEBUG_THROTTLE_FPS,
DEBUG_VISUAL_VALUES,
@ -494,6 +498,22 @@ bool handleContextMenuAction(s32 cmdID)
{
changeCameraTarget(4);
}
else if (cmdID == DEBUG_VIEW_KART_FIVE)
{
changeCameraTarget(5);
}
else if (cmdID == DEBUG_VIEW_KART_SIX)
{
changeCameraTarget(6);
}
else if (cmdID == DEBUG_VIEW_KART_SEVEN)
{
changeCameraTarget(7);
}
else if (cmdID == DEBUG_VIEW_KART_EIGHT)
{
changeCameraTarget(8);
}
else if (cmdID == DEBUG_PRINT_START_POS)
{
if (!world) return false;
@ -691,6 +711,10 @@ bool onEvent(const SEvent &event)
sub->addItem(L"To kart two", DEBUG_VIEW_KART_TWO);
sub->addItem(L"To kart three", DEBUG_VIEW_KART_THREE);
sub->addItem(L"To kart four", DEBUG_VIEW_KART_FOUR);
sub->addItem(L"To kart five", DEBUG_VIEW_KART_FIVE);
sub->addItem(L"To kart six", DEBUG_VIEW_KART_SIX);
sub->addItem(L"To kart seven", DEBUG_VIEW_KART_SEVEN);
sub->addItem(L"To kart eight", DEBUG_VIEW_KART_EIGHT);
mnu->addItem(L"Adjust values", DEBUG_VISUAL_VALUES);