diff --git a/data/shaders/sky.frag b/data/shaders/sky.frag index eb339c8a2..4fe5e76b1 100644 --- a/data/shaders/sky.frag +++ b/data/shaders/sky.frag @@ -4,7 +4,7 @@ out vec4 FragColor; void main(void) { - vec3 eyedir = vec3(gl_FragCoord.xy / screen, 1.); + vec3 eyedir = vec3(mod(gl_FragCoord.xy, screen) / screen, 1.); eyedir = 2.0 * eyedir - 1.0; vec4 tmp = (InverseProjectionMatrix * vec4(eyedir, 1.)); tmp /= tmp.w; diff --git a/data/supertuxkart.appdata.xml b/data/supertuxkart.appdata.xml index c4ab14416..bed5375cb 100644 --- a/data/supertuxkart.appdata.xml +++ b/data/supertuxkart.appdata.xml @@ -8,28 +8,28 @@ A racing game

- SuperTuxKart is a fun 3D kart racing game. - You can play with up to 4 friends on one PC, racing against each other or - just try to beat the computer. + Supertuxkart is a free 3D kart racing game, with a focus on having fun over + realism. You can play with up to 4 friends on one PC, racing against each + other, or try to beat the computer in single-player mode.

See the great lighthouse or drive through the sand and visit the pyramids. - Race underground or in space, watching the stars passing by. - Have some rest under the palms on the beach (watching the other karts - overtaking you :) ). - But don't eat the bananas! Watch for bowling balls, plungers, bubble gum - and cakes thrown by opponents. + Race underground or in space, watching the stars pass by. Or rest under the + palm trees on the beach, watching the other karts overtake you. But don't eat + the bananas! Watch for bowling balls, plungers, bubble gum and cakes thrown by + your opponents.

- You can do a single race against other karts, compete in one of several - Grand Prix, try to beat the high score in time trials on your own, play - battle mode against your friends, and more! + You can do a single race against other karts, compete in one of several Grand + Prix, try to beat the high score in time trials on your own, play battle mode + against the computer or your friends, and more!

http://supertuxkart.sourceforge.net/ - http://supertuxkart.sourceforge.net/persistent/images/4/4d/SuperTuxKart_0.8_screenshot.jpg - http://supertuxkart.sourceforge.net/persistent/images/1/1f/SuperTuxKart_0.8_screenshot4.jpg + http://supertuxkart.sourceforge.net/persistent/images/4/4e/Supertuxkart-0.9-screenshot-2.jpg + http://supertuxkart.sourceforge.net/persistent/images/a/a9/Supertuxkart-0.9-screenshot-1.jpg + http://supertuxkart.sourceforge.net/persistent/images/6/63/Supertuxkart-0.9-screenshot-3.jpg supertuxkart-devel@lists.sourceforge.net diff --git a/src/karts/controller/ai_base_controller.cpp b/src/karts/controller/ai_base_controller.cpp index 88bb92f56..1600df748 100644 --- a/src/karts/controller/ai_base_controller.cpp +++ b/src/karts/controller/ai_base_controller.cpp @@ -289,17 +289,18 @@ void AIBaseController::checkPosition(const Vec3 &point, { // 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 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 (pos_data == NULL) return; + // lhs: tell whether it's left or right hand side if (local_coordinates.getX() < 0) - pos_data->on_side = true; + pos_data->lhs = true; else - pos_data->on_side = false; + pos_data->lhs = false; // behind: tell whether it's behind or not if (local_coordinates.getZ() < 0) diff --git a/src/karts/controller/ai_base_controller.hpp b/src/karts/controller/ai_base_controller.hpp index dad50604c..ffa8e3fef 100644 --- a/src/karts/controller/ai_base_controller.hpp +++ b/src/karts/controller/ai_base_controller.hpp @@ -65,7 +65,7 @@ protected: static int m_test_ai; /** Position info structure of targets. */ - struct posData {bool behind; bool on_side; float angle; float distance;}; + struct posData {bool behind; bool lhs; float angle; float distance;}; void setControllerName(const std::string &name); float steerToPoint(const Vec3 &point); diff --git a/src/karts/controller/arena_ai.cpp b/src/karts/controller/arena_ai.cpp index 1ee41316f..d58937080 100644 --- a/src/karts/controller/arena_ai.cpp +++ b/src/karts/controller/arena_ai.cpp @@ -33,6 +33,7 @@ ArenaAI::ArenaAI(AbstractKart *kart) : AIBaseController(kart) { m_debug_sphere = NULL; + m_debug_sphere_next = NULL; } // ArenaAI //----------------------------------------------------------------------------- @@ -242,7 +243,7 @@ void ArenaAI::handleArenaSteering(const float dt) #endif if (m_cur_kart_pos_data.behind) { - m_adjusting_side = m_cur_kart_pos_data.on_side; + m_adjusting_side = m_cur_kart_pos_data.lhs; m_is_uturn = true; } else @@ -262,11 +263,22 @@ void ArenaAI::handleArenaSteering(const float dt) checkPosition(m_target_point, &m_cur_kart_pos_data); #ifdef AI_DEBUG - m_debug_sphere->setPosition(m_target_point.toIrrVector()); + if (m_path_corners.size() > 2) + { + m_debug_sphere->setVisible(true); + m_debug_sphere_next->setVisible(true); + m_debug_sphere->setPosition(m_path_corners[1].toIrrVector()); + m_debug_sphere_next->setPosition(m_path_corners[2].toIrrVector()); + } + else + { + m_debug_sphere->setVisible(false); + m_debug_sphere_next->setVisible(false); + } #endif if (m_cur_kart_pos_data.behind) { - m_adjusting_side = m_cur_kart_pos_data.on_side; + m_adjusting_side = m_cur_kart_pos_data.lhs; m_is_uturn = true; } else @@ -306,7 +318,7 @@ void ArenaAI::handleArenaBanana() { // Check whether it's straight ahead towards a banana // If so, adjust target point - banana_lc = (banana_pos.on_side ? banana_lc + Vec3 (2, 0, 0) : + banana_lc = (banana_pos.lhs ? banana_lc + Vec3 (2, 0, 0) : banana_lc - Vec3 (2, 0, 0)); m_target_point = m_kart->getTrans()(banana_lc); m_target_node = BattleGraph::get() @@ -478,28 +490,25 @@ void ArenaAI::stringPull(const Vec3& start_pos, const Vec3& end_pos) */ void ArenaAI::handleArenaBraking() { - m_controls->m_brake = false; - - if (getCurrentNode() == BattleGraph::UNKNOWN_POLY || - m_target_node == BattleGraph::UNKNOWN_POLY) 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; + if (forceBraking() && m_kart->getSpeed() > MIN_SPEED) + { + // Brake now + return; + } - 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]); + m_controls->m_brake = false; - float current_curve_radius = determineTurnRadius(points); + if (getCurrentNode() == BattleGraph::UNKNOWN_POLY || + m_target_node == BattleGraph::UNKNOWN_POLY) return; - 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 current_curve_radius = determineTurnRadius(m_kart->getXYZ(), + m_path_corners[0], (m_path_corners.size() >= 2 ? m_path_corners[1] : + m_path_corners[0])); float max_turn_speed = m_kart->getSpeedForTurnRadius(current_curve_radius); @@ -516,55 +525,44 @@ void ArenaAI::handleArenaBraking() * 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 ) +float ArenaAI::determineTurnRadius(const Vec3& p1, const Vec3& p2, + const Vec3& p3) { - // Declaring variables - float a, b; - irr::core::CMatrix4 A; - irr::core::CMatrix4 X; - irr::core::CMatrix4 B; + // The parabola function is as following: y=ax2+bx+c + // No need to calculate c as after differentiating c will be zero + const float eps = 0.01f; + const float denominator = (p1.x() - p2.x()) * (p1.x() - p3.x()) * + (p2.x() - p3.x()); - //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; + // Avoid nan, this will happen if three values of coordinates x are too + // close together, ie a straight line, return a large radius + // so no braking is needed + if (fabsf(denominator) < eps) return 25.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; + const float a = (p3.x() * (p2.z() - p1.z()) + + p2.x() * (p1.z() - p3.z()) + + p1.x() * (p3.z() - p2.z())) / denominator; - //Computing inverse : X = inv(A)*B - irr::core::CMatrix4 invA; - if (!A.getInverse(invA)) - return -1; + // Should not happen, otherwise y=c which is a straight line + if (fabsf(a) < eps) return 25.0f; - X = invA*B; - a = X(0, 0); - b = X(0, 1); - //c = X(0, 2); + const float b = (p3.x() * p3.x() * (p1.z() - p2.z()) + + p2.x() * p2.x() * (p3.z() - p1.z()) + + p1.x() * p1.x() * (p2.z() - p3.z())) / denominator; - 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; + // Differentiate the function, so y=ax2+bx+c will become y=2ax+b for dy_dx, + // y=2a for d2y_dx2 + // Use the p1 (current location of AI) as x + const float dy_dx = 2 * a * p1.x() + b; + const float d2y_dx2 = 2 * a; - float radius = pow(abs(1 + pow(dx_by_dz, 2)), 1.5f)/ abs(d2x_by_dz); + // Calculate the radius of curvature at current location of AI + const float radius = pow(1 + pow(dy_dx, 2), 1.5f) / fabsf(d2y_dx2); + assert(!std::isnan(radius)); - return radius; + // Avoid returning too large radius + return (radius > 25.0f ? 25.0f : radius); } // determineTurnRadius //----------------------------------------------------------------------------- @@ -663,7 +661,8 @@ void ArenaAI::handleArenaItems(const float dt) if (m_time_since_last_shot < 1.0f) break; if (m_closest_kart_pos_data.distance < 6.0f && - (difficulty || perfect_aim)) + (difficulty || perfect_aim) && + !m_closest_kart->isInvulnerable()) { m_controls->m_fire = true; m_controls->m_look_back = fire_behind; diff --git a/src/karts/controller/arena_ai.hpp b/src/karts/controller/arena_ai.hpp index 3caf152ba..9ebe46a0d 100644 --- a/src/karts/controller/arena_ai.hpp +++ b/src/karts/controller/arena_ai.hpp @@ -56,6 +56,7 @@ protected: /** For debugging purpose: a sphere indicating where the AI * is targeting at. */ irr::scene::ISceneNode *m_debug_sphere; + irr::scene::ISceneNode *m_debug_sphere_next; /** The node(poly) at which the target point lies in. */ int m_target_node; @@ -104,7 +105,8 @@ private: float m_time_since_uturn; void checkIfStuck(const float dt); - float determineTurnRadius(std::vector& points); + float determineTurnRadius(const Vec3& p1, const Vec3& p2, + const Vec3& p3); void findPortals(int start, int end); void handleArenaAcceleration(const float dt); void handleArenaBanana(); @@ -118,6 +120,7 @@ private: virtual bool isWaiting() const = 0; virtual void findClosestKart(bool use_difficulty) = 0; virtual void findTarget() = 0; + virtual bool forceBraking() { return false; } public: ArenaAI(AbstractKart *kart); virtual ~ArenaAI() {}; diff --git a/src/karts/controller/battle_ai.cpp b/src/karts/controller/battle_ai.cpp index 1b2cdc57b..d94ce4e99 100644 --- a/src/karts/controller/battle_ai.cpp +++ b/src/karts/controller/battle_ai.cpp @@ -41,8 +41,11 @@ BattleAI::BattleAI(AbstractKart *kart) #ifdef AI_DEBUG video::SColor col_debug(128, 128, 0, 0); + video::SColor col_debug_next(128, 0, 128, 128); m_debug_sphere = irr_driver->addSphere(1.0f, col_debug); m_debug_sphere->setVisible(true); + m_debug_sphere_next = irr_driver->addSphere(1.0f, col_debug_next); + m_debug_sphere_next->setVisible(true); #endif m_world = dynamic_cast(World::getWorld()); m_track = m_world->getTrack(); @@ -59,6 +62,7 @@ BattleAI::~BattleAI() { #ifdef AI_DEBUG irr_driver->removeNode(m_debug_sphere); + irr_driver->removeNode(m_debug_sphere_next); #endif } // ~BattleAI @@ -157,12 +161,12 @@ void BattleAI::findTarget() } } // findTarget -// ------------------------------------------------------------------------ +//----------------------------------------------------------------------------- int BattleAI::getCurrentNode() const { return m_world->getKartNode(m_kart->getWorldKartId()); } // getCurrentNode -// ------------------------------------------------------------------------ +//----------------------------------------------------------------------------- bool BattleAI::isWaiting() const { return m_world->isStartPhase(); diff --git a/src/karts/controller/soccer_ai.cpp b/src/karts/controller/soccer_ai.cpp index 8c2c45f31..91b7b2174 100644 --- a/src/karts/controller/soccer_ai.cpp +++ b/src/karts/controller/soccer_ai.cpp @@ -41,11 +41,15 @@ SoccerAI::SoccerAI(AbstractKart *kart) #ifdef AI_DEBUG video::SColor col_debug(128, 128, 0, 0); + video::SColor col_debug_next(128, 0, 128, 128); m_debug_sphere = irr_driver->addSphere(1.0f, col_debug); m_debug_sphere->setVisible(true); + m_debug_sphere_next = irr_driver->addSphere(1.0f, col_debug_next); + m_debug_sphere_next->setVisible(true); #endif m_world = dynamic_cast(World::getWorld()); m_track = m_world->getTrack(); + m_cur_team = m_world->getKartTeam(m_kart->getWorldKartId()); // Don't call our own setControllerName, since this will add a // billboard showing 'AIBaseController' to the kart. @@ -59,6 +63,7 @@ SoccerAI::~SoccerAI() { #ifdef AI_DEBUG irr_driver->removeNode(m_debug_sphere); + irr_driver->removeNode(m_debug_sphere_next); #endif } // ~SoccerAI @@ -71,31 +76,15 @@ void SoccerAI::reset() AIBaseController::reset(); m_saving_ball = false; - if (race_manager->getNumPlayers() == 1) - { - // Same handle in SoccerWorld::createKart - if (race_manager->getKartInfo(0).getSoccerTeam() == SOCCER_TEAM_RED) - { - m_cur_team = (m_kart->getWorldKartId() % 2 == 0 ? - SOCCER_TEAM_BLUE : SOCCER_TEAM_RED); - } - else - { - m_cur_team = (m_kart->getWorldKartId() % 2 == 0 ? - SOCCER_TEAM_RED : SOCCER_TEAM_BLUE); - } - } - else - { - m_cur_team = (m_kart->getWorldKartId() % 2 == 0 ? - SOCCER_TEAM_BLUE : SOCCER_TEAM_RED); - } + m_force_brake = false; } // reset //----------------------------------------------------------------------------- void SoccerAI::update(float dt) { m_saving_ball = false; + m_force_brake = false; + if (World::getWorld()->getPhase() == World::GOAL_PHASE) { m_controls->m_brake = false; @@ -147,8 +136,8 @@ void SoccerAI::findClosestKart(bool use_difficulty) void SoccerAI::findTarget() { // Check whether any defense is needed - if ((m_world->getBallPosition() - NavMesh::get()->getNavPoly(m_world - ->getGoalNode(m_cur_team)).getCenter()).length_2d() < 50.0f && + if ((m_world->getBallPosition() - m_world->getGoalLocation(m_cur_team, + CheckGoal::POINT_CENTER)).length_2d() < 50.0f && m_world->getDefender(m_cur_team) == (signed)m_kart->getWorldKartId()) { m_target_node = m_world->getBallNode(); @@ -174,86 +163,110 @@ 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); + Vec3 ball_lc; checkPosition(orig_pos, &ball_pos, &ball_lc); - // opposite team goal - checkPosition(NavMesh::get()->getNavPoly(m_world - ->getGoalNode(m_cur_team == SOCCER_TEAM_BLUE ? - SOCCER_TEAM_RED : SOCCER_TEAM_BLUE)).getCenter(), &goal_pos); + // Too far / behind from the ball, + // use path finding from arena ai to get close + if (!(ball_pos.distance < 3.0f) || ball_pos.behind) return orig_pos; - if (goal_pos.behind) + // Save the opposite team + SoccerTeam opp_team = (m_cur_team == SOCCER_TEAM_BLUE ? + SOCCER_TEAM_RED : SOCCER_TEAM_BLUE); + + // Prevent lost control when steering with ball + const bool need_braking = ball_pos.angle > 0.1f && + m_kart->getSpeed() > 9.0f && ball_pos.distance <2.5f; + + // Goal / own goal detection first, as different aiming method will be used + const bool likely_to_goal = + ball_pos.angle < 0.2f && isLikelyToGoal(opp_team); + if (likely_to_goal) { - if (goal_pos.angle > 0.3f && ball_pos.distance < 3.0f && - !ball_pos.behind) + if (need_braking) { - // Only steer with ball if same sides for ball and goal - if (ball_pos.on_side && goal_pos.on_side) - { - ball_lc = ball_lc + Vec3 (1, 0, 1); - return m_kart->getTrans()(ball_lc); - } - else if (!ball_pos.on_side && !goal_pos.on_side) - { - ball_lc = ball_lc - Vec3 (1, 0, 0) + Vec3 (0, 0, 1); - return m_kart->getTrans()(ball_lc); - } - else - m_controls->m_brake = true; - } - else - { - // This case is facing straight ahead opposite goal - // (which is straight behind itself), apply more - // offset for skidding, to save the ball from behind - // scored. - // Notice: this assume map maker make soccer field - // with two goals facing each other straight - ball_lc = (goal_pos.on_side ? ball_lc - Vec3 (2, 0, 0) + - Vec3 (0, 0, 2) : ball_lc + Vec3 (2, 0, 2)); - - if (ball_pos.distance < 3.0f && - (m_cur_difficulty == RaceManager::DIFFICULTY_HARD || - m_cur_difficulty == RaceManager::DIFFICULTY_BEST)) - m_saving_ball = true; - return m_kart->getTrans()(ball_lc); + m_controls->m_brake = true; + m_force_brake = true; + // Prevent keep pushing by aiming a nearer point + return m_kart->getTrans()(ball_lc - Vec3(0, 0, 1)); } + // Keep pushing the ball straight to the goal + return orig_pos; } - if (ball_pos.distance < 3.0f && - !ball_pos.behind && !goal_pos.behind) + const bool likely_to_own_goal = + ball_pos.angle < 0.2f && isLikelyToGoal(m_cur_team); + if (likely_to_own_goal) { - if (goal_pos.angle < 0.5f) - return orig_pos; - else - { - // Same with above - if (ball_pos.on_side && goal_pos.on_side) - { - ball_lc = ball_lc + Vec3 (1, 0, 1); - return m_kart->getTrans()(ball_lc); - } - else if (!ball_pos.on_side && !goal_pos.on_side) - { - ball_lc = ball_lc - Vec3 (1, 0, 0) + Vec3 (0, 0, 1); - return m_kart->getTrans()(ball_lc); - } - else - m_controls->m_brake = true; - } + // It's getting likely to own goal, apply more + // offset for skidding, to save the ball from behind + // scored. + if (m_cur_difficulty == RaceManager::DIFFICULTY_HARD || + m_cur_difficulty == RaceManager::DIFFICULTY_BEST) + m_saving_ball = true; + return m_kart->getTrans()(ball_pos.lhs ? + ball_lc - Vec3(2, 0, 0) + Vec3(0, 0, 2) : + ball_lc + Vec3(2, 0, 2)); } - return orig_pos; + + // Now try to make the ball face towards the goal + // Aim at upper/lower left/right corner of ball depends on location + posData goal_pos = {0}; + checkPosition(m_world->getGoalLocation(opp_team, CheckGoal::POINT_CENTER), + &goal_pos); + Vec3 corrected_pos; + if (ball_pos.lhs && goal_pos.lhs) + { + corrected_pos = ball_lc + Vec3(1, 0, 1); + } + else if (!ball_pos.lhs && !goal_pos.lhs) + { + corrected_pos = ball_lc - Vec3(1, 0, 0) + Vec3(0, 0, 1); + } + else if (!ball_pos.lhs && goal_pos.lhs) + { + corrected_pos = ball_lc + Vec3(1, 0, 0) - Vec3(0, 0, 1); + } + else + { + corrected_pos = ball_lc - Vec3(1, 0, 1); + } + if (need_braking) + { + m_controls->m_brake = true; + m_force_brake = true; + } + return m_kart->getTrans()(corrected_pos); + } // correctBallPosition -// ------------------------------------------------------------------------ +//----------------------------------------------------------------------------- +bool SoccerAI::isLikelyToGoal(SoccerTeam team) const +{ + // Use local coordinate for easy compare + Vec3 first_pos; + Vec3 last_pos; + checkPosition(m_world->getGoalLocation(team, CheckGoal::POINT_FIRST), + NULL, &first_pos); + checkPosition(m_world->getGoalLocation(team, CheckGoal::POINT_LAST), + NULL, &last_pos); + + // If the kart lies between the first and last pos, and faces + // in front of them, than it's likely to goal + if ((first_pos.z() > 0.0f && last_pos.z() > 0.0f) && + ((first_pos.x() < 0.0f && last_pos.x() > 0.0f) || + (last_pos.x() < 0.0f && first_pos.x() > 0.0f))) + return true; + + return false; +} // isLikelyToGoal +//----------------------------------------------------------------------------- int SoccerAI::getCurrentNode() const { return m_world->getKartNode(m_kart->getWorldKartId()); } // getCurrentNode -// ------------------------------------------------------------------------ +//----------------------------------------------------------------------------- bool SoccerAI::isWaiting() const { return m_world->isStartPhase(); diff --git a/src/karts/controller/soccer_ai.hpp b/src/karts/controller/soccer_ai.hpp index 4a44babd4..b970d471a 100644 --- a/src/karts/controller/soccer_ai.hpp +++ b/src/karts/controller/soccer_ai.hpp @@ -36,14 +36,17 @@ private: SoccerTeam m_cur_team; bool m_saving_ball; + bool m_force_brake; Vec3 correctBallPosition(const Vec3&); + bool isLikelyToGoal(SoccerTeam team) const; virtual void findClosestKart(bool use_difficulty); virtual void findTarget(); virtual int getCurrentNode() const; virtual bool isWaiting() const; virtual bool canSkid(float steer_fraction) { return m_saving_ball; } + virtual bool forceBraking() OVERRIDE { return m_force_brake; } public: SoccerAI(AbstractKart *kart); ~SoccerAI(); diff --git a/src/modes/soccer_world.cpp b/src/modes/soccer_world.cpp index fe2114b69..645aa5244 100644 --- a/src/modes/soccer_world.cpp +++ b/src/modes/soccer_world.cpp @@ -30,7 +30,6 @@ #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" @@ -54,6 +53,8 @@ SoccerWorld::SoccerWorld() : WorldWithRank() } m_use_highscores = false; + m_red_ai = 0; + m_blue_ai = 0; } // SoccerWorld //----------------------------------------------------------------------------- @@ -76,8 +77,11 @@ void SoccerWorld::init() m_display_rank = false; m_goal_timer = 0.0f; m_ball_hitter = -1; + m_red_check_goal = NULL; + m_blue_check_goal = NULL; m_goal_target = race_manager->getMaxGoal(); m_goal_sound = SFXManager::get()->createSoundSource("goal_scored"); + initGoal(); } // init @@ -129,8 +133,7 @@ void SoccerWorld::reset() } initKartList(); - resetAllNodes(); - initGoalNodes(); + resetAllPosition(); resetBall(); } // reset @@ -355,22 +358,10 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index, if (kart_type == RaceManager::KT_AI) { - if (race_manager->getNumPlayers() == 1) - { - // Make AI even when single player choose a different team - if (race_manager->getKartInfo(0).getSoccerTeam() == SOCCER_TEAM_RED) - { - team = (index % 2 == 0 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED); - } - else - { - team = (index % 2 == 0 ? SOCCER_TEAM_RED : SOCCER_TEAM_BLUE); - } - } + if (index < m_red_ai) + team = SOCCER_TEAM_RED; else - { - team = (index % 2 == 0 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED); - } + team = SOCCER_TEAM_BLUE; m_kart_team_map[index] = team; } else @@ -484,9 +475,7 @@ void SoccerWorld::updateBallPosition(float dt) } // updateBallPosition //----------------------------------------------------------------------------- -/** Localize two goals on the navigation mesh. - */ -void SoccerWorld::initGoalNodes() +void SoccerWorld::initGoal() { if (!m_track->hasNavMesh()) return; @@ -500,20 +489,18 @@ void SoccerWorld::initGoalNodes() { if (goal->getTeam()) { - m_blue_goal_node = BattleGraph::get()->pointToNode(m_blue_goal_node, - goal->convertTo3DCenter(), true/*ignore_vertical*/); + m_blue_check_goal = goal; } else { - m_red_goal_node = BattleGraph::get()->pointToNode(m_red_goal_node, - goal->convertTo3DCenter(), true/*ignore_vertical*/); + m_red_check_goal = goal; } } } -} // initGoalNodes +} // initGoal //----------------------------------------------------------------------------- -void SoccerWorld::resetAllNodes() +void SoccerWorld::resetAllPosition() { m_kart_on_node.clear(); m_kart_on_node.resize(m_karts.size()); @@ -521,9 +508,7 @@ void SoccerWorld::resetAllNodes() m_kart_on_node[n] = BattleGraph::UNKNOWN_POLY; m_ball_on_node = BattleGraph::UNKNOWN_POLY; m_ball_position = Vec3(0, 0, 0); - m_red_goal_node = BattleGraph::UNKNOWN_POLY; - m_blue_goal_node = BattleGraph::UNKNOWN_POLY; -} // resetAllNodes +} // resetAllPosition //----------------------------------------------------------------------------- SoccerTeam SoccerWorld::getKartTeam(unsigned int kart_id) const { @@ -571,9 +556,8 @@ void SoccerWorld::updateDefenders() getKartTeam(m_karts[i]->getWorldKartId()) != SOCCER_TEAM_RED) continue; - Vec3 d = NavMesh::get()->getNavPoly(this - ->getGoalNode(SOCCER_TEAM_RED)).getCenter() - - m_karts[i]->getXYZ(); + Vec3 d = this->getGoalLocation(SOCCER_TEAM_RED, + CheckGoal::POINT_CENTER) - m_karts[i]->getXYZ(); if (d.length_2d() <= distance) { @@ -593,9 +577,8 @@ void SoccerWorld::updateDefenders() getKartTeam(m_karts[i]->getWorldKartId()) != SOCCER_TEAM_BLUE) continue; - Vec3 d = NavMesh::get()->getNavPoly(this - ->getGoalNode(SOCCER_TEAM_BLUE)).getCenter() - - m_karts[i]->getXYZ(); + Vec3 d = this->getGoalLocation(SOCCER_TEAM_BLUE, + CheckGoal::POINT_CENTER) - m_karts[i]->getXYZ(); if (d.length_2d() <= distance) { @@ -642,3 +625,52 @@ void SoccerWorld::resetBall() m_ball->reset(); m_ball->getPhysicalObject()->reset(); } // resetBall + +//----------------------------------------------------------------------------- +void SoccerWorld::setAITeam() +{ + const int total_player = race_manager->getNumPlayers(); + const int total_karts = race_manager->getNumberOfKarts(); + + // No AI + if ((total_karts - total_player) == 0) return; + + int red_player = 0; + int blue_player = 0; + for (int i = 0; i < total_player; i++) + { + SoccerTeam team = race_manager->getKartInfo(i).getSoccerTeam(); + assert(team != SOCCER_TEAM_NONE); + team == SOCCER_TEAM_BLUE ? blue_player++ : red_player++; + } + + int available_ai = total_karts - red_player - blue_player; + while (available_ai > 0) + { + if ((m_red_ai + red_player) > (m_blue_ai + blue_player)) + { + m_blue_ai++; + available_ai--; + } + else if ((m_blue_ai + blue_player) > (m_red_ai + red_player)) + { + m_red_ai++; + available_ai--; + } + else if ((m_blue_ai + blue_player) == (m_red_ai + red_player)) + { + blue_player > red_player ? m_red_ai++ : m_blue_ai++; + available_ai--; + } + } + Log::debug("SoccerWorld","blue AI: %d red AI: %d", m_blue_ai, m_red_ai); + +} // setAITeam + +//----------------------------------------------------------------------------- +const Vec3& SoccerWorld::getGoalLocation(SoccerTeam team, + CheckGoal::PointLocation point) const +{ + return (team == SOCCER_TEAM_BLUE ? m_blue_check_goal->getPoint(point) : + m_red_check_goal->getPoint(point)); +} // getGoalLocation diff --git a/src/modes/soccer_world.hpp b/src/modes/soccer_world.hpp index 0e87a3bc8..e1667212f 100644 --- a/src/modes/soccer_world.hpp +++ b/src/modes/soccer_world.hpp @@ -22,6 +22,7 @@ #include "modes/world_with_rank.hpp" #include "states_screens/race_gui_base.hpp" #include "karts/abstract_kart.hpp" +#include "tracks/check_goal.hpp" #include #include @@ -85,22 +86,26 @@ private: 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; + + CheckGoal* m_red_check_goal; + CheckGoal* m_blue_check_goal; int m_red_defender; int m_blue_defender; + int m_red_ai; + int m_blue_ai; + /** Set the team for the karts */ void initKartList(); - /** Function to init the locations of two goals on the polygon map */ - void initGoalNodes(); + /** Function to save check goal for the getting of coordinate */ + void initGoal(); /** 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(float dt); /** Clean up */ - void resetAllNodes(); + void resetAllPosition(); /** Reset the ball to original starting position. */ void resetBall(); /** Function to update the AI which is the closest to its goal to defend. */ @@ -166,17 +171,18 @@ public: const Vec3& getBallPosition() const { return m_ball_position; } // ------------------------------------------------------------------------ - const int getGoalNode(SoccerTeam team) const - { - return (team == SOCCER_TEAM_BLUE ? m_blue_goal_node : m_red_goal_node); - } - // ------------------------------------------------------------------------ bool isCorrectGoal(unsigned int kart_id, bool first_goal) const; // ------------------------------------------------------------------------ const int getDefender(SoccerTeam team) const { return (team == SOCCER_TEAM_BLUE ? m_blue_defender : m_red_defender); } + // ------------------------------------------------------------------------ + void setAITeam(); + // ------------------------------------------------------------------------ + const Vec3& getGoalLocation(SoccerTeam team, + CheckGoal::PointLocation point) const; + // ------------------------------------------------------------------------ }; // SoccerWorld diff --git a/src/modes/world.cpp b/src/modes/world.cpp index 04e43e1c9..33a9c603b 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -42,6 +42,7 @@ #include "karts/kart_properties_manager.hpp" #include "modes/overworld.hpp" #include "modes/profile_world.hpp" +#include "modes/soccer_world.hpp" #include "network/network_config.hpp" #include "physics/btKart.hpp" #include "physics/physics.hpp" @@ -191,6 +192,11 @@ void World::init() m_karts.push_back(ReplayPlay::get()->getGhostKart(k)); } + // Assign team of AIs for soccer mode before createKart + SoccerWorld* sw = dynamic_cast(this); + if (sw) + sw->setAITeam(); + for(unsigned int i=0; igetKartType(i) == RaceManager::KT_GHOST) continue; diff --git a/src/race/grand_prix_data.cpp b/src/race/grand_prix_data.cpp index 5788f85ad..7890406f9 100644 --- a/src/race/grand_prix_data.cpp +++ b/src/race/grand_prix_data.cpp @@ -329,7 +329,7 @@ void GrandPrixData::reload() throw std::runtime_error("Missing track id"); } - if (number_of_laps < 1) + if (number_of_laps < 1 && !UserConfigParams::m_artist_debug_mode) { Log::error("GrandPrixData", "Track '%s' in the Grand Prix file '%s' should be raced " diff --git a/src/states_screens/kart_selection.cpp b/src/states_screens/kart_selection.cpp index 4ff233240..b93d5c98b 100644 --- a/src/states_screens/kart_selection.cpp +++ b/src/states_screens/kart_selection.cpp @@ -148,8 +148,6 @@ EventPropagation FocusDispatcher::focused(const int player_id) return GUIEngine::EVENT_LET; } // focused -static FocusDispatcher *g_dispatcher = NULL; - #if 0 #pragma mark - #pragma mark KartHoverListener @@ -229,6 +227,7 @@ bool sameKart(const PlayerKartWidget& player1, const PlayerKartWidget& player2) KartSelectionScreen::KartSelectionScreen(const char* filename) : Screen(filename) { + m_dispatcher = NULL; m_removed_widget = NULL; m_multiplayer_message = NULL; m_from_overworld = false; @@ -246,8 +245,8 @@ KartSelectionScreen* KartSelectionScreen::getRunningInstance() void KartSelectionScreen::loadedFromFile() { - g_dispatcher = new FocusDispatcher(this); - m_first_widget = g_dispatcher; + m_dispatcher = new FocusDispatcher(this); + m_first_widget = m_dispatcher; m_game_master_confirmed = false; m_multiplayer_message = NULL; // Dynamically add tabs @@ -305,6 +304,7 @@ void KartSelectionScreen::beforeAddingWidget() void KartSelectionScreen::init() { + m_instance_ptr = this; Screen::init(); m_must_delete_on_back = false; @@ -318,17 +318,17 @@ void KartSelectionScreen::init() // FIXME : The reserved id value is -1 when we switch from KSS to NKSS and vice-versa - g_dispatcher->setRootID(placeholder->m_reserved_id); + m_dispatcher->setRootID(placeholder->m_reserved_id); g_root_id = placeholder->m_reserved_id; - if (!m_widgets.contains(g_dispatcher)) + if (!m_widgets.contains(m_dispatcher)) { - m_widgets.push_back(g_dispatcher); + m_widgets.push_back(m_dispatcher); // this is only needed if the dispatcher wasn't already in // the list of widgets. If it already was, it was added along // other widgets. - g_dispatcher->add(); + m_dispatcher->add(); } m_game_master_confirmed = false; @@ -426,7 +426,7 @@ void KartSelectionScreen::tearDown() void KartSelectionScreen::unloaded() { // these pointer is no more valid (have been deleted along other widgets) - g_dispatcher = NULL; + m_dispatcher = NULL; } // ---------------------------------------------------------------------------- @@ -439,7 +439,7 @@ bool KartSelectionScreen::joinPlayer(InputDevice* device) Log::info("KartSelectionScreen", "joinPlayer() invoked"); if (!m_multiplayer && !first_player) return false; - assert (g_dispatcher != NULL); + assert (m_dispatcher != NULL); DynamicRibbonWidget* w = getWidget("karts"); if (w == NULL) diff --git a/src/states_screens/kart_selection.hpp b/src/states_screens/kart_selection.hpp index dfa05186d..a9e31f8c4 100644 --- a/src/states_screens/kart_selection.hpp +++ b/src/states_screens/kart_selection.hpp @@ -36,6 +36,7 @@ namespace Online class OnlineProfile; } +class FocusDispatcher; class InputDevice; class KartHoverListener; @@ -79,6 +80,8 @@ protected: /** Message shown in multiplayer mode */ GUIEngine::BubbleWidget* m_multiplayer_message; + FocusDispatcher *m_dispatcher; + KartSelectionScreen(const char* filename); /** Called when all players selected their kart */ diff --git a/src/states_screens/main_menu_screen.cpp b/src/states_screens/main_menu_screen.cpp index d2c27920a..c3c2da955 100644 --- a/src/states_screens/main_menu_screen.cpp +++ b/src/states_screens/main_menu_screen.cpp @@ -37,6 +37,7 @@ #include "modes/cutscene_world.hpp" #include "modes/overworld.hpp" #include "modes/demo_world.hpp" +#include "network/network_config.hpp" #include "online/request_manager.hpp" #include "states_screens/addons_screen.hpp" #include "states_screens/credits.hpp" @@ -385,7 +386,8 @@ void MainMenuScreen::eventCallback(Widget* widget, const std::string& name, #endif if (selection == "new") { - KartSelectionScreen* s = OfflineKartSelectionScreen::getInstance(); //FIXME : that was for tests + NetworkConfig::get()->unsetNetworking(); + KartSelectionScreen* s = OfflineKartSelectionScreen::getInstance(); s->setMultiplayer(false); s->setFromOverworld(false); s->push(); @@ -393,6 +395,7 @@ void MainMenuScreen::eventCallback(Widget* widget, const std::string& name, else if (selection == "multiplayer") { KartSelectionScreen* s = OfflineKartSelectionScreen::getInstance(); + NetworkConfig::get()->unsetNetworking(); s->setMultiplayer(true); s->setFromOverworld(false); s->push(); diff --git a/src/states_screens/network_kart_selection.cpp b/src/states_screens/network_kart_selection.cpp index bfd608cf8..071d40ae2 100644 --- a/src/states_screens/network_kart_selection.cpp +++ b/src/states_screens/network_kart_selection.cpp @@ -44,7 +44,6 @@ DEFINE_SCREEN_SINGLETON( NetworkKartSelectionScreen ); NetworkKartSelectionScreen::NetworkKartSelectionScreen() : KartSelectionScreen("karts_online.stkgui") { - KartSelectionScreen::m_instance_ptr = this; } // NetworkKartSelectionScreen // ---------------------------------------------------------------------------- diff --git a/src/states_screens/offline_kart_selection.cpp b/src/states_screens/offline_kart_selection.cpp index 9bba1771e..fe443de3c 100644 --- a/src/states_screens/offline_kart_selection.cpp +++ b/src/states_screens/offline_kart_selection.cpp @@ -21,7 +21,6 @@ DEFINE_SCREEN_SINGLETON( OfflineKartSelectionScreen ); OfflineKartSelectionScreen::OfflineKartSelectionScreen() : KartSelectionScreen("karts.stkgui") { - KartSelectionScreen::m_instance_ptr = this; } OfflineKartSelectionScreen::~OfflineKartSelectionScreen() diff --git a/src/states_screens/race_result_gui.cpp b/src/states_screens/race_result_gui.cpp index 9bf444c57..014157c4f 100644 --- a/src/states_screens/race_result_gui.cpp +++ b/src/states_screens/race_result_gui.cpp @@ -35,6 +35,8 @@ #include "io/file_manager.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/controller.hpp" +#include "karts/controller/end_controller.hpp" +#include "karts/controller/local_player_controller.hpp" #include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/cutscene_world.hpp" @@ -384,10 +386,6 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, { race_manager->exitRace(); race_manager->setAIKartOverride(""); - // FIXME: why is this call necessary here? tearDown should be - // automatically called when the screen is left. Note that the - // NetworkKartSelectionScreen::getInstance()->tearDown(); caused #1347 - KartSelectionScreen::getRunningInstance()->tearDown(); Screen* newStack[] = { MainMenuScreen::getInstance(), RaceSetupScreen::getInstance(), NULL }; @@ -401,11 +399,6 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, { race_manager->exitRace(); race_manager->setAIKartOverride(""); - // FIXME: why is this call necessary here? tearDown should be - // automatically called when the screen is left. Note that the - // NetworkKartSelectionScreen::getInstance()->tearDown(); caused #1347 - //if (KartSelectionScreen::getRunningInstance() != NULL) - // KartSelectionScreen::getRunningInstance()->tearDown(); StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); if (race_manager->raceWasStartedFromOverworld()) @@ -492,15 +485,7 @@ void RaceResultGUI::backToLobby() // Save a pointer to the current row_info entry RowInfo *ri = &(m_all_row_infos[position - first_position]); ri->m_is_player_kart = kart->getController()->isLocalPlayerController(); - - // Identify Human player, if so display real name other than kart name - const int rm_id = kart->getWorldKartId() - - (race_manager->getNumberOfKarts() - race_manager->getNumPlayers()); - - if (rm_id >= 0 && !race_manager->isWatchingReplay()) - ri->m_kart_name = race_manager->getKartInfo(rm_id).getPlayerName(); - else - ri->m_kart_name = translations->fribidize(kart->getName()); + ri->m_kart_name = getKartDisplayName(kart); video::ITexture *icon = kart->getKartProperties()->getIconMaterial()->getTexture(); @@ -865,16 +850,8 @@ void RaceResultGUI::backToLobby() RowInfo *ri = &(m_all_row_infos[rank]); ri->m_kart_icon = kart->getKartProperties()->getIconMaterial()->getTexture(); - - const int rm_id = kart_id - - (race_manager->getNumberOfKarts() - race_manager->getNumPlayers()); - - if (rm_id >= 0 && !race_manager->isWatchingReplay()) - ri->m_kart_name = race_manager->getKartInfo(rm_id).getPlayerName(); - else - ri->m_kart_name = translations->fribidize(kart->getName()); - ri->m_is_player_kart = kart->getController()->isLocalPlayerController(); + ri->m_kart_name = getKartDisplayName(kart); // In FTL karts do have a time, which is shown even when the kart // is eliminated @@ -925,6 +902,29 @@ void RaceResultGUI::backToLobby() } // i < num_karts } // determineGPLayout + //----------------------------------------------------------------------------- + /** Returns a string to display next to a kart. For a player that's the name + * of the player, for an AI kart it's the name of the driver. + */ + core::stringw RaceResultGUI::getKartDisplayName(const AbstractKart *kart) const + { + const EndController *ec = + dynamic_cast(kart->getController()); + // If the race was given up, there is no end controller for the + // players, so this case needs to be handled separately + if(ec && ec->isLocalPlayerController()) + return ec->getName(); + else + { + // No end controller, check explicitely for a player controller + const PlayerController *pc = + dynamic_cast(kart->getController()); + // Check if the kart is a player controller to get the real name + if(pc) return pc->getName(); + } + return translations->fribidize(kart->getName()); + } // getKartDisplayName + //----------------------------------------------------------------------------- /** Displays the race results for a single kart. * \param n Index of the kart to be displayed. @@ -1102,6 +1102,7 @@ void RaceResultGUI::backToLobby() if (own_goal) { result_text.append(" "); + //I18N: indicates a player that scored in their own goal in result screen result_text.append(_("(Own Goal)")); } diff --git a/src/states_screens/race_result_gui.hpp b/src/states_screens/race_result_gui.hpp index b261620d6..db6557acf 100644 --- a/src/states_screens/race_result_gui.hpp +++ b/src/states_screens/race_result_gui.hpp @@ -194,6 +194,7 @@ private: void displayHighScores(); void displaySoccerResults(); void displayScreenShots(); + irr::core::stringw getKartDisplayName(const AbstractKart *kart) const; int getFontHeight () const; diff --git a/src/tracks/check_goal.cpp b/src/tracks/check_goal.cpp index 4aca5ea1a..d7e733583 100644 --- a/src/tracks/check_goal.cpp +++ b/src/tracks/check_goal.cpp @@ -37,12 +37,12 @@ CheckGoal::CheckGoal(const XMLNode &node, unsigned int index) m_first_goal = false; node.get("first_goal", &m_first_goal); - Vec3 p1, p2; - node.get("p1", &p1); - node.get("p2", &p2); + node.get("p1", &m_p1); + node.get("p2", &m_p3); - m_line.setLine( core::vector2df(p1.getX(), p1.getZ()), - core::vector2df(p2.getX(), p2.getZ()) ); + m_line.setLine( core::vector2df(m_p1.getX(), m_p1.getZ()), + core::vector2df(m_p3.getX(), m_p3.getZ()) ); + m_p2 = (m_p1 + m_p3) / 2; } // CheckGoal // ---------------------------------------------------------------------------- @@ -113,11 +113,3 @@ void CheckGoal::reset(const Track &track) } } // 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 85f3d2307..d1c36cb7c 100644 --- a/src/tracks/check_goal.hpp +++ b/src/tracks/check_goal.hpp @@ -24,7 +24,6 @@ #include using namespace irr; -class CheckManager; class XMLNode; class Track; class Vec3; @@ -37,6 +36,14 @@ class Vec3; */ class CheckGoal : public CheckStructure { +public: + /** Used by AIs to test whether the ball is likely to goal. */ + enum PointLocation + { + POINT_FIRST, + POINT_CENTER, + POINT_LAST + }; private: /** Previois ball position. */ Vec3 m_previous_ball_position; @@ -47,6 +54,11 @@ private: /** The line that is tested for being crossed. */ core::line2df m_line; + /** Used by AIs to test whether the ball is likely to goal. */ + Vec3 m_p1; + Vec3 m_p2; + Vec3 m_p3; + public: CheckGoal(const XMLNode &node, unsigned int index); virtual ~CheckGoal() {} @@ -59,7 +71,11 @@ public: // ------------------------------------------------------------------------ bool getTeam() const { return m_first_goal; } // ------------------------------------------------------------------------ - Vec3 convertTo3DCenter() const; -}; // CheckLine + const Vec3& getPoint(PointLocation point) const + { + return (point == POINT_LAST ? m_p3 : + (point == POINT_CENTER ? m_p2 : m_p1)); + } +}; // CheckGoal #endif