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:
parent
409862dbe2
commit
d14a39e0f7
@ -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/*")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
790
src/karts/controller/arena_ai.cpp
Normal file
790
src/karts/controller/arena_ai.cpp
Normal 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
|
143
src/karts/controller/arena_ai.hpp
Normal file
143
src/karts/controller/arena_ai.hpp
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
195
src/karts/controller/soccer_ai.cpp
Normal file
195
src/karts/controller/soccer_ai.cpp
Normal 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
|
53
src/karts/controller/soccer_ai.hpp
Normal file
53
src/karts/controller/soccer_ai.hpp
Normal 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
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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 )
|
||||
|
@ -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 ;
|
||||
|
||||
|
@ -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 &&
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user