diff --git a/sources.cmake b/sources.cmake index b5c2d8c50..ddc029d4f 100644 --- a/sources.cmake +++ b/sources.cmake @@ -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/*") diff --git a/src/karts/controller/ai_base_controller.cpp b/src/karts/controller/ai_base_controller.cpp index 5afdbd9f0..e94274565 100644 --- a/src/karts/controller/ai_base_controller.cpp +++ b/src/karts/controller/ai_base_controller.cpp @@ -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 diff --git a/src/karts/controller/ai_base_controller.hpp b/src/karts/controller/ai_base_controller.hpp index ed0acca6c..5949078fb 100644 --- a/src/karts/controller/ai_base_controller.hpp +++ b/src/karts/controller/ai_base_controller.hpp @@ -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 diff --git a/src/karts/controller/ai_base_lap_controller.hpp b/src/karts/controller/ai_base_lap_controller.hpp index 58749ac02..8fe165ce7 100644 --- a/src/karts/controller/ai_base_lap_controller.hpp +++ b/src/karts/controller/ai_base_lap_controller.hpp @@ -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 diff --git a/src/karts/controller/ai_properties.hpp b/src/karts/controller/ai_properties.hpp index 8ab895928..562276ae9 100644 --- a/src/karts/controller/ai_properties.hpp +++ b/src/karts/controller/ai_properties.hpp @@ -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; diff --git a/src/karts/controller/arena_ai.cpp b/src/karts/controller/arena_ai.cpp new file mode 100644 index 000000000..f4bbe04d0 --- /dev/null +++ b/src/karts/controller/arena_ai.cpp @@ -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 >& 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 this_node_verts = + NavMesh::get()->getNavPoly(this_node).getVerticesIndex(); + std::vector 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 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& points ) +{ + // Declaring variables + float a, b; + irr::core::CMatrix4 A; + irr::core::CMatrix4 X; + irr::core::CMatrix4 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 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 >& 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 diff --git a/src/karts/controller/arena_ai.hpp b/src/karts/controller/arena_ai.hpp new file mode 100644 index 000000000..1c55a12ac --- /dev/null +++ b/src/karts/controller/arena_ai.hpp @@ -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 +#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 m_on_node; + + /** Holds the corner points computed using the funnel algorithm that the AI + * will eventaully move through. See stringPull(). */ + std::vector m_path_corners; + + /** Holds the set of portals that the kart will cross when moving through + * polygon channel. See findPortals(). */ + std::vector > 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& 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 diff --git a/src/karts/controller/battle_ai.cpp b/src/karts/controller/battle_ai.cpp index cc189186e..aab498cd6 100644 --- a/src/karts/controller/battle_ai.cpp +++ b/src/karts/controller/battle_ai.cpp @@ -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 -#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 >& 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 this_node_verts = - NavMesh::get()->getNavPoly(this_node).getVerticesIndex(); - std::vector 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 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& points ) -{ - // Declaring variables - float a, b; - irr::core::CMatrix4 A; - irr::core::CMatrix4 X; - irr::core::CMatrix4 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 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 >& 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 diff --git a/src/karts/controller/battle_ai.hpp b/src/karts/controller/battle_ai.hpp index aeb0d3e90..1d79ea6a0 100644 --- a/src/karts/controller/battle_ai.hpp +++ b/src/karts/controller/battle_ai.hpp @@ -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 m_on_node; - - /** Holds the corner points computed using the funnel algorithm that the AI - * will eventaully move through. See stringPull(). */ - std::vector m_path_corners; - - /** Holds the set of portals that the kart will cross when moving through - * polygon channel. See findPortals(). */ - std::vector > 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& 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 diff --git a/src/karts/controller/player_controller.hpp b/src/karts/controller/player_controller.hpp index bbb63e6ea..e5cfc4e07 100644 --- a/src/karts/controller/player_controller.hpp +++ b/src/karts/controller/player_controller.hpp @@ -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; } diff --git a/src/karts/controller/soccer_ai.cpp b/src/karts/controller/soccer_ai.cpp new file mode 100644 index 000000000..14dd49f7e --- /dev/null +++ b/src/karts/controller/soccer_ai.cpp @@ -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 +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(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 diff --git a/src/karts/controller/soccer_ai.hpp b/src/karts/controller/soccer_ai.hpp new file mode 100644 index 000000000..c6a9f9a64 --- /dev/null +++ b/src/karts/controller/soccer_ai.hpp @@ -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 diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index 14e1cb718..9223e7a3a 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -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(World::getWorld()); + m_race_result = sw->getKartSoccerResult(this->getWorldKartId()); } else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG) { diff --git a/src/modes/soccer_world.cpp b/src/modes/soccer_world.cpp index f0710b453..af70c74f6 100644 --- a/src/modes/soccer_world.cpp +++ b/src/modes/soccer_world.cpp @@ -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 - #include - //----------------------------------------------------------------------------- /** 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& objects = tom->getObjects(); for(unsigned int i=0; ihasNavMesh()) + 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 *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(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; ngetKartInfo(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& 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() + ->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(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; ngetNumberOfKarts() - 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(m_karts[kart_id]->getController()); + if (controller) + return controller->getAITeam(); + } + // Fallback + Log::warn("SoccerWorld", "Unknown team, using blue default."); + return true; +} diff --git a/src/modes/soccer_world.hpp b/src/modes/soccer_world.hpp index 5a47f992f..fd2ea6b50 100644 --- a/src/modes/soccer_world.hpp +++ b/src/modes/soccer_world.hpp @@ -23,40 +23,64 @@ #include "states_screens/race_gui_base.hpp" #include "karts/abstract_kart.hpp" - #include - #include -#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 m_redScorers; - std::vector m_redScoreTimes; - std::vector m_blueScorers; - std::vector m_blueScoreTimes; + int m_last_kart_to_hit_ball; + + /** Number of goals each team scored */ + std::vector m_team_goals; + std::vector m_red_scorers; + std::vector m_red_score_times; + std::vector m_blue_scorers; + std::vector m_blue_score_times; + + /** Data generated from navmesh */ + std::vector 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 *info); - int getScore(unsigned int i); + std::vector *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 getScorers(unsigned int team) - { - if(team == 0) - return m_redScorers; - else - return m_blueScorers; - } - std::vector 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& getScorers(bool team) const + { return (team ? m_blue_scorers : m_red_scorers); } + // ------------------------------------------------------------------------ + const std::vector& 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 diff --git a/src/modes/three_strikes_battle.cpp b/src/modes/three_strikes_battle.cpp index c0a0ff434..a94270eeb 100644 --- a/src/modes/three_strikes_battle.cpp +++ b/src/modes/three_strikes_battle.cpp @@ -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& 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*/); } } diff --git a/src/modes/three_strikes_battle.hpp b/src/modes/three_strikes_battle.hpp index 04e6ced8e..19fd5addb 100644 --- a/src/modes/three_strikes_battle.hpp +++ b/src/modes/three_strikes_battle.hpp @@ -71,7 +71,7 @@ private: PtrVector 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: diff --git a/src/modes/world.cpp b/src/modes/world.cpp index d3d0a3e38..e2984debc 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -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); diff --git a/src/network/remote_kart_info.hpp b/src/network/remote_kart_info.hpp index 1d9e108c5..a78db09e6 100644 --- a/src/network/remote_kart_info.hpp +++ b/src/network/remote_kart_info.hpp @@ -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. */ diff --git a/src/race/race_manager.hpp b/src/race/race_manager.hpp index 1cc99abee..6e090c63c 100644 --- a/src/race/race_manager.hpp +++ b/src/race/race_manager.hpp @@ -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 diff --git a/src/states_screens/arenas_screen.cpp b/src/states_screens/arenas_screen.cpp index 227128040..decb7a982 100644 --- a/src/states_screens/arenas_screen.cpp +++ b/src/states_screens/arenas_screen.cpp @@ -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 { diff --git a/src/states_screens/race_gui.cpp b/src/states_screens/race_gui.cpp index e40ea35fe..2f6d77b70 100644 --- a/src/states_screens/race_gui.cpp +++ b/src/states_screens/race_gui.cpp @@ -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; igetNumKarts(); 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 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 indicatorPos(offsetX, offsetY, - offsetX + (int)(m_minimap_player_size/1.25f), - offsetY + (int)(m_minimap_player_size/1.25f)); - core::rect sourceRect(core::position2d(0,0), + core::rect indicator_pos(offset_x, offset_y, + offset_x + (int)(m_minimap_player_size*2), + offset_y + (int)(m_minimap_player_size*2)); + core::rect source_rect(core::position2d(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(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 source(core::position2di(0, 0), icon->getSize()); + core::rect 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; igetNumKarts(); 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 im_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 pos(currX, currY, currX, currY); - font->draw(resultText.c_str(), pos, color, true, true); + core::rect 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(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(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(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(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(centerX, currY, centerX, currY); + int center_x = UserConfigParams::m_width/2; + pos = core::rect(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 scorers = soccerWorld->getScorers(0); - std::vector scoreTimes = soccerWorld->getScoreTimes(0); - irr::video::ITexture* scorerIcon; + std::vector scorers = sw->getScorers(false); + std::vector score_times = sw->getScoreTimes(false); + irr::video::ITexture* scorer_icon; - int prevY = currY; + int prev_y = current_y; for(unsigned int i=0; igetKart(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(currX,currY,currX,currY); - font->draw(resultText,pos, color, true, false); - scorerIcon = soccerWorld->getKart(scorers.at(i)) + pos = core::rect(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; igetKart(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(currX,currY,currX,currY); - font->draw(resultText,pos, color, true, false); - scorerIcon = soccerWorld->getKart(scorers.at(i))-> + pos = core::rect(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); } } diff --git a/src/states_screens/race_setup_screen.cpp b/src/states_screens/race_setup_screen.cpp index 3a380e14f..e4842ad9d 100644 --- a/src/states_screens/race_setup_screen.cpp +++ b/src/states_screens/race_setup_screen.cpp @@ -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(); diff --git a/src/states_screens/track_info_screen.cpp b/src/states_screens/track_info_screen.cpp index 109f53f5e..34877139b 100644 --- a/src/states_screens/track_info_screen.cpp +++ b/src/states_screens/track_info_screen.cpp @@ -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()); diff --git a/src/tracks/battle_graph.cpp b/src/tracks/battle_graph.cpp index 8172e903a..5ecca6832 100644 --- a/src/tracks/battle_graph.cpp +++ b/src/tracks/battle_graph.cpp @@ -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& 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 diff --git a/src/tracks/battle_graph.hpp b/src/tracks/battle_graph.hpp index 286a08a06..6b872f1f2 100644 --- a/src/tracks/battle_graph.hpp +++ b/src/tracks/battle_graph.hpp @@ -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 diff --git a/src/tracks/check_goal.cpp b/src/tracks/check_goal.cpp index 3f578eced..046a35d24 100644 --- a/src/tracks/check_goal.cpp +++ b/src/tracks/check_goal.cpp @@ -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(World::getWorld()); - Track* track = world->getTrack(); - assert(track); - - TrackObjectManager* tom = track->getTrackObjectManager(); - assert(tom); - - PtrVector& objects = tom->getObjects(); - unsigned int ball_index = 0; - for(unsigned int i=0; iisSoccerBall()) - continue; - - const Vec3 &xyz = obj->getPresentation()->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()->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(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& objects = tom->getObjects(); - for(unsigned int i=0; i(World::getWorld()); + + if (world) { - const TrackObject* obj = objects.get(i); - if(!obj->isSoccerBall()) - continue; - - const Vec3 &xyz = obj->getPresentation()->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); +} diff --git a/src/tracks/check_goal.hpp b/src/tracks/check_goal.hpp index 3368924d4..9b2d6b526 100644 --- a/src/tracks/check_goal.hpp +++ b/src/tracks/check_goal.hpp @@ -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 diff --git a/src/tracks/check_structure.cpp b/src/tracks/check_structure.cpp index 9f2c81788..2ca199e6a 100644 --- a/src/tracks/check_structure.cpp +++ b/src/tracks/check_structure.cpp @@ -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()); diff --git a/src/tracks/nav_poly.cpp b/src/tracks/nav_poly.cpp index 69d726a82..0a2f644f0 100644 --- a/src/tracks/nav_poly.cpp +++ b/src/tracks/nav_poly.cpp @@ -52,7 +52,7 @@ const std::vector NavPoly::getVertices() //----------------------------------------------------------------------------- -bool NavPoly::pointInPoly(const Vec3& p) const +bool NavPoly::pointInPoly(const Vec3& p, bool ignore_vertical) const { std::vector points; for(unsigned int i=0; i 1.0f ) diff --git a/src/tracks/nav_poly.hpp b/src/tracks/nav_poly.hpp index 175e6e826..123b85a35 100644 --- a/src/tracks/nav_poly.hpp +++ b/src/tracks/nav_poly.hpp @@ -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 ; diff --git a/src/tracks/track.cpp b/src/tracks/track.cpp index 5acb20e36..7178c2a9d 100644 --- a/src/tracks/track.cpp +++ b/src/tracks/track.cpp @@ -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 && diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index ff12464c0..6468db322 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -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);