Merge remote-tracking branch 'origin/advanced_soccer_ai' into arena_ai_profiling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# SuperTuxKart Installation Instructions
|
||||
|
||||
Note: If you obtained this source code from Github, you also need to download the game assets from Sourceforge using SVN.
|
||||
Note: If you obtained this source code from Github, you also need to download
|
||||
the game assets from Sourceforge using SVN.
|
||||
|
||||
`svn checkout https://svn.code.sf.net/p/supertuxkart/code/stk-assets stk-assets`
|
||||
|
||||
@@ -26,8 +27,10 @@ First, make sure that you have the following packages installed:
|
||||
Ubuntu command:
|
||||
|
||||
```
|
||||
sudo apt-get install autoconf automake build-essential cmake libogg-dev libvorbis-dev libopenal-dev libxxf86vm-dev \
|
||||
libgl1-mesa-dev libglu1-mesa-dev libcurl4-openssl-dev libfribidi-dev libbluetooth-dev libxrandr-dev libfreetype6-dev
|
||||
sudo apt-get install build-essential cmake libbluetooth-dev \
|
||||
libcurl4-gnutls-dev libfreetype6-dev libfribidi-dev libgl1-mesa-dev \
|
||||
libjpeg-dev libogg-dev libopenal-dev libpng-dev libvorbis-dev libxrandr-dev \
|
||||
mesa-common-dev pkg-config zlib1g-dev
|
||||
```
|
||||
|
||||
Unpack the files from the tarball like this:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 23 KiB |
@@ -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;
|
||||
|
||||
@@ -8,28 +8,28 @@
|
||||
<summary>A racing game</summary>
|
||||
<description>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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!
|
||||
</p>
|
||||
</description>
|
||||
<url type="homepage">http://supertuxkart.sourceforge.net/</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">http://supertuxkart.sourceforge.net/persistent/images/4/4d/SuperTuxKart_0.8_screenshot.jpg</screenshot>
|
||||
<screenshot>http://supertuxkart.sourceforge.net/persistent/images/1/1f/SuperTuxKart_0.8_screenshot4.jpg</screenshot>
|
||||
<screenshot type="default">http://supertuxkart.sourceforge.net/persistent/images/4/4e/Supertuxkart-0.9-screenshot-2.jpg</screenshot>
|
||||
<screenshot>http://supertuxkart.sourceforge.net/persistent/images/a/a9/Supertuxkart-0.9-screenshot-1.jpg</screenshot>
|
||||
<screenshot>http://supertuxkart.sourceforge.net/persistent/images/6/63/Supertuxkart-0.9-screenshot-3.jpg</screenshot>
|
||||
</screenshots>
|
||||
<updatecontact>supertuxkart-devel@lists.sourceforge.net</updatecontact>
|
||||
</application>
|
||||
|
||||
@@ -16,3 +16,4 @@ add_library(glew STATIC
|
||||
)
|
||||
|
||||
add_definitions(-DGLEW_STATIC)
|
||||
add_definitions(-DGLEW_NO_GLU)
|
||||
@@ -283,23 +283,24 @@ void AIBaseController::crashed(const Material *m)
|
||||
} // crashed(Material)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void AIBaseController::checkPosition(const Vec3 &point,
|
||||
posData *pos_data,
|
||||
Vec3 *lc) const
|
||||
void AIBaseController::checkPosition(const Vec3 &point, posData *pos_data,
|
||||
Vec3 *lc, bool use_front_xyz) 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 p = point -
|
||||
(use_front_xyz ? m_kart->getFrontXYZ() : 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)
|
||||
|
||||
@@ -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);
|
||||
@@ -77,7 +77,9 @@ 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;
|
||||
void checkPosition(const Vec3&, posData*,
|
||||
Vec3* lc = NULL,
|
||||
bool use_front_xyz = false) const;
|
||||
|
||||
public:
|
||||
AIBaseController(AbstractKart *kart);
|
||||
|
||||
@@ -33,6 +33,7 @@ ArenaAI::ArenaAI(AbstractKart *kart)
|
||||
: AIBaseController(kart)
|
||||
{
|
||||
m_debug_sphere = NULL;
|
||||
m_debug_sphere_next = NULL;
|
||||
} // ArenaAI
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -49,6 +50,7 @@ void ArenaAI::reset()
|
||||
m_cur_kart_pos_data = {0};
|
||||
m_is_stuck = false;
|
||||
m_is_uturn = false;
|
||||
m_avoid_eating_banana = false;
|
||||
m_target_point = Vec3(0, 0, 0);
|
||||
m_time_since_last_shot = 0.0f;
|
||||
m_time_since_driving = 0.0f;
|
||||
@@ -75,7 +77,10 @@ void ArenaAI::update(float dt)
|
||||
|
||||
// Don't do anything if there is currently a kart animations shown.
|
||||
if (m_kart->getKartAnimation())
|
||||
{
|
||||
resetAfterStop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWaiting())
|
||||
{
|
||||
@@ -100,6 +105,7 @@ void ArenaAI::update(float dt)
|
||||
|
||||
if (m_is_uturn)
|
||||
{
|
||||
resetAfterStop();
|
||||
handleArenaUTurn(dt);
|
||||
}
|
||||
else
|
||||
@@ -154,6 +160,7 @@ void ArenaAI::checkIfStuck(const float dt)
|
||||
*/
|
||||
void ArenaAI::handleArenaAcceleration(const float dt)
|
||||
{
|
||||
|
||||
if (m_controls->m_brake)
|
||||
{
|
||||
m_controls->m_accel = 0.0f;
|
||||
@@ -201,6 +208,7 @@ bool ArenaAI::handleArenaUnstuck(const float dt)
|
||||
{
|
||||
if (!m_is_stuck || m_is_uturn) return false;
|
||||
|
||||
resetAfterStop();
|
||||
setSteering(0.0f, dt);
|
||||
|
||||
if (fabsf(m_kart->getSpeed()) >
|
||||
@@ -233,16 +241,17 @@ void ArenaAI::handleArenaSteering(const float dt)
|
||||
if (current_node == BattleGraph::UNKNOWN_POLY ||
|
||||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
|
||||
|
||||
if (m_target_node == current_node)
|
||||
if (m_target_node == current_node || directDrive())
|
||||
{
|
||||
// Very close to the item, steer directly
|
||||
m_path_corners.clear();
|
||||
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_adjusting_side = m_cur_kart_pos_data.lhs;
|
||||
m_is_uturn = true;
|
||||
}
|
||||
else
|
||||
@@ -262,11 +271,23 @@ 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());
|
||||
m_debug_sphere->setPosition(m_path_corners[0].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
|
||||
@@ -288,6 +309,8 @@ void ArenaAI::handleArenaSteering(const float dt)
|
||||
//-----------------------------------------------------------------------------
|
||||
void ArenaAI::handleArenaBanana()
|
||||
{
|
||||
m_avoid_eating_banana = false;
|
||||
|
||||
if (m_is_uturn) return;
|
||||
|
||||
const std::vector< std::pair<const Item*, int> >& item_list =
|
||||
@@ -306,13 +329,10 @@ 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()
|
||||
->pointToNode(getCurrentNode(), m_target_point,
|
||||
false/*ignore_vertical*/);
|
||||
|
||||
m_avoid_eating_banana = true;
|
||||
// Handle one banana only
|
||||
break;
|
||||
}
|
||||
@@ -478,28 +498,28 @@ 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<Vec3> points;
|
||||
if (forceBraking() && m_kart->getSpeed() > MIN_SPEED)
|
||||
{
|
||||
// Brake now
|
||||
m_controls->m_brake = true;
|
||||
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();
|
||||
if (m_path_corners.empty()) return;
|
||||
|
||||
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 +536,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<Vec3>& points )
|
||||
float ArenaAI::determineTurnRadius(const Vec3& p1, const Vec3& p2,
|
||||
const Vec3& p3)
|
||||
{
|
||||
// Declaring variables
|
||||
float a, b;
|
||||
irr::core::CMatrix4<float> A;
|
||||
irr::core::CMatrix4<float> X;
|
||||
irr::core::CMatrix4<float> 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<float> 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 +672,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;
|
||||
|
||||
@@ -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;
|
||||
@@ -63,6 +64,9 @@ protected:
|
||||
/** The target point. */
|
||||
Vec3 m_target_point;
|
||||
|
||||
/** For directDrive() to work */
|
||||
bool m_avoid_eating_banana;
|
||||
|
||||
void collectItemInArena(Vec3*, int*) const;
|
||||
private:
|
||||
/** Used by handleArenaUTurn, it tells whether to do left or right
|
||||
@@ -104,7 +108,8 @@ private:
|
||||
float m_time_since_uturn;
|
||||
|
||||
void checkIfStuck(const float dt);
|
||||
float determineTurnRadius(std::vector<Vec3>& 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();
|
||||
@@ -116,8 +121,11 @@ private:
|
||||
void stringPull(const Vec3&, const Vec3&);
|
||||
virtual int getCurrentNode() const = 0;
|
||||
virtual bool isWaiting() const = 0;
|
||||
virtual void resetAfterStop() {};
|
||||
virtual void findClosestKart(bool use_difficulty) = 0;
|
||||
virtual void findTarget() = 0;
|
||||
virtual bool forceBraking() { return false; }
|
||||
virtual bool directDrive() { return m_avoid_eating_banana; }
|
||||
public:
|
||||
ArenaAI(AbstractKart *kart);
|
||||
virtual ~ArenaAI() {};
|
||||
|
||||
@@ -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<ThreeStrikesBattle*>(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();
|
||||
|
||||
@@ -140,8 +140,7 @@ SkiddingAI::SkiddingAI(AbstractKart *kart)
|
||||
} // SkiddingAI
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** The destructor deletes the shared TrackInfo objects if no more SkiddingAI
|
||||
* instances are around.
|
||||
/** Destructor, mostly to clean up debug data structures.
|
||||
*/
|
||||
SkiddingAI::~SkiddingAI()
|
||||
{
|
||||
@@ -183,10 +182,10 @@ void SkiddingAI::reset()
|
||||
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
|
||||
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
|
||||
{
|
||||
Log::error("SkiddingAI",
|
||||
"Invalid starting position for '%s' - not on track"
|
||||
" - can be ignored.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::error(getControllerName().c_str(),
|
||||
"Invalid starting position for '%s' - not on track"
|
||||
" - can be ignored.",
|
||||
m_kart->getIdent().c_str());
|
||||
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
|
||||
}
|
||||
|
||||
@@ -400,8 +399,8 @@ void SkiddingAI::handleBraking()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI", "braking: %s ahead of leader.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(), "braking: %s ahead of leader.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
|
||||
m_controls->m_brake = true;
|
||||
@@ -420,8 +419,9 @@ void SkiddingAI::handleBraking()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI", "%s not aligned with track.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s not aligned with track.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
m_controls->m_brake = true;
|
||||
return;
|
||||
@@ -439,11 +439,11 @@ void SkiddingAI::handleBraking()
|
||||
m_controls->m_brake = true;
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI",
|
||||
"speed %f too tight curve: radius %f ",
|
||||
m_kart->getSpeed(),
|
||||
m_kart->getIdent().c_str(),
|
||||
m_current_curve_radius);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"speed %f too tight curve: radius %f ",
|
||||
m_kart->getSpeed(),
|
||||
m_kart->getIdent().c_str(),
|
||||
m_current_curve_radius);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -484,7 +484,8 @@ void SkiddingAI::handleSteering(float dt)
|
||||
#ifdef AI_DEBUG
|
||||
m_debug_sphere[0]->setPosition(QuadGraph::get()->getQuadOfNode(next)
|
||||
.getCenter().toIrrVector());
|
||||
Log::debug("skidding_ai","-Outside of road: steer to center point.");
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"Outside of road: steer to center point.");
|
||||
#endif
|
||||
}
|
||||
//If we are going to crash against a kart, avoid it if it doesn't
|
||||
@@ -494,12 +495,12 @@ void SkiddingAI::handleSteering(float dt)
|
||||
//-1 = left, 1 = right, 0 = no crash.
|
||||
if( m_start_kart_crash_direction == 1 )
|
||||
{
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f );
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f );
|
||||
m_start_kart_crash_direction = 0;
|
||||
}
|
||||
else if(m_start_kart_crash_direction == -1)
|
||||
{
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f);
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f);
|
||||
m_start_kart_crash_direction = 0;
|
||||
}
|
||||
else
|
||||
@@ -507,18 +508,19 @@ void SkiddingAI::handleSteering(float dt)
|
||||
if(m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() ) >
|
||||
m_world->getDistanceToCenterForKart( m_crashes.m_kart ))
|
||||
{
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f );
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f );
|
||||
m_start_kart_crash_direction = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f );
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f );
|
||||
m_start_kart_crash_direction = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef AI_DEBUG
|
||||
Log::debug("skidding_ai", "- Velocity vector crashes with kart "
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"Velocity vector crashes with kart "
|
||||
"and doesn't crashes with road : steer 90 "
|
||||
"degrees away from kart.");
|
||||
#endif
|
||||
@@ -700,8 +702,8 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
}
|
||||
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI", "%s unselects item.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(), "%s unselects item.",
|
||||
m_kart->getIdent().c_str());
|
||||
// Otherwise remove the pre-selected item (and start
|
||||
// looking for a new item).
|
||||
m_item_to_collect = NULL;
|
||||
@@ -770,9 +772,10 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
.toIrrVector());
|
||||
#endif
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI", "%s selects item type '%d'.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s selects item type '%d'.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType());
|
||||
m_item_to_collect = item_to_collect;
|
||||
}
|
||||
else
|
||||
@@ -793,18 +796,18 @@ void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
.toIrrVector());
|
||||
#endif
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI",
|
||||
"%s adjusts to hit type %d angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s adjusts to hit type %d angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI",
|
||||
"%s won't hit '%d', angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s won't hit '%d', angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
}
|
||||
} // kart will not hit item
|
||||
} // does hit hit bad item
|
||||
@@ -1421,9 +1424,9 @@ void SkiddingAI::handleItems(const float dt)
|
||||
m_controls->m_fire = m_kart_ahead != NULL;
|
||||
break;
|
||||
default:
|
||||
Log::error("SkiddingAI",
|
||||
"Invalid or unhandled powerup '%d' in default AI.",
|
||||
m_kart->getPowerup()->getType());
|
||||
Log::error(getControllerName().c_str(),
|
||||
"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;
|
||||
@@ -1717,7 +1720,7 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
|
||||
slip->isSlipstreamReady() &&
|
||||
slip->getSlipstreamTarget())
|
||||
{
|
||||
//Log::debug("skidding_ai", "%s overtaking %s\n",
|
||||
//Log::debug(getControllerName().c_str(), "%s overtaking %s",
|
||||
// m_kart->getIdent().c_str(),
|
||||
// m_kart->getSlipstreamKart()->getIdent().c_str());
|
||||
// FIXME: we might define a minimum distance, and if the target kart
|
||||
@@ -1742,9 +1745,9 @@ void SkiddingAI::checkCrashes(const Vec3& pos )
|
||||
int current_node = m_track_node;
|
||||
if(steps<1 || steps>1000)
|
||||
{
|
||||
Log::warn("SkiddingAI",
|
||||
"Incorrect STEPS=%d. kart_length %f velocity %f",
|
||||
steps, m_kart_length, m_kart->getVelocityLC().getZ());
|
||||
Log::warn(getControllerName().c_str(),
|
||||
"Incorrect STEPS=%d. kart_length %f velocity %f",
|
||||
steps, m_kart_length, m_kart->getVelocityLC().getZ());
|
||||
steps=1000;
|
||||
}
|
||||
for(int i = 1; steps > i; ++i)
|
||||
@@ -2226,8 +2229,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
|
||||
if(m_ai_debug)
|
||||
{
|
||||
if(fabsf(steer_fraction)>=2.5f)
|
||||
Log::debug("SkiddingAI", "%s stops skidding (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s stops skidding (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
}
|
||||
#endif
|
||||
// If the current turn is not sharp enough, delay releasing
|
||||
@@ -2246,8 +2250,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
|
||||
#ifdef DEBUG
|
||||
if(m_controls->m_skid && m_ai_debug)
|
||||
{
|
||||
Log::debug("SkiddingAI", "%s stops skidding on straight.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s stops skidding on straight.",
|
||||
m_kart->getIdent().c_str());
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
@@ -2282,8 +2287,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
|
||||
if(m_controls->m_skid && duration < 1.0f)
|
||||
{
|
||||
if(m_ai_debug)
|
||||
Log::debug("SkiddingAI", "'%s' too short, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"'%s' too short, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
return false;
|
||||
}
|
||||
// Test if the AI is trying to skid against track direction. This
|
||||
@@ -2297,9 +2303,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_controls->m_skid && m_ai_debug)
|
||||
Log::debug("SkiddingAI",
|
||||
"%s skidding against track direction.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s skidding against track direction.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -2309,8 +2315,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(!m_controls->m_skid && m_ai_debug)
|
||||
Log::debug("SkiddingAI", "%s start skid, duration %f.",
|
||||
m_kart->getIdent().c_str(), duration);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s start skid, duration %f.",
|
||||
m_kart->getIdent().c_str(), duration);
|
||||
#endif
|
||||
return true;
|
||||
|
||||
@@ -2318,8 +2325,9 @@ bool SkiddingAI::canSkid(float steer_fraction)
|
||||
|
||||
#ifdef DEBUG
|
||||
if(m_controls->m_skid && m_ai_debug)
|
||||
Log::debug("SkiddingAI", "%s has no reasons to skid anymore.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s has no reasons to skid anymore.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
return false;
|
||||
} // canSkid
|
||||
@@ -2364,9 +2372,10 @@ void SkiddingAI::setSteering(float angle, float dt)
|
||||
: SKID_PROBAB_NO_SKID;
|
||||
#undef PRINT_SKID_STATS
|
||||
#ifdef PRINT_SKID_STATS
|
||||
Log::info("SkiddingAI", "%s distance %f prob %d skidding %s\n",
|
||||
m_kart->getIdent().c_str(), distance, prob,
|
||||
sc= ? "no" : sc==KartControl::SC_LEFT ? "left" : "right");
|
||||
Log::info(getControllerName().c_str(),
|
||||
"%s distance %f prob %d skidding %s",
|
||||
m_kart->getIdent().c_str(), distance, prob,
|
||||
sc= ? "no" : sc==KartControl::SC_LEFT ? "left" : "right");
|
||||
#endif
|
||||
}
|
||||
m_controls->m_skid = m_skid_probability_state == SKID_PROBAB_SKID
|
||||
@@ -2397,8 +2406,9 @@ void SkiddingAI::setSteering(float angle, float dt)
|
||||
m_controls->m_skid = KartControl::SC_NONE;
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::info("SkiddingAI", "'%s' wrong steering, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::info(getControllerName().c_str(),
|
||||
"'%s' wrong steering, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2412,8 +2422,9 @@ void SkiddingAI::setSteering(float angle, float dt)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::info("SkiddingAI", "%s steering too much (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
Log::info(getControllerName().c_str(),
|
||||
"%s steering too much (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
#endif
|
||||
m_controls->m_skid = KartControl::SC_NONE;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ using namespace irr;
|
||||
using namespace std;
|
||||
#endif
|
||||
|
||||
#ifdef BALL_AIM_DEBUG
|
||||
#include "graphics/camera.hpp"
|
||||
#endif
|
||||
|
||||
SoccerAI::SoccerAI(AbstractKart *kart)
|
||||
: ArenaAI(kart)
|
||||
{
|
||||
@@ -41,11 +45,27 @@ 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
|
||||
|
||||
#ifdef BALL_AIM_DEBUG
|
||||
video::SColor red(128, 128, 0, 0);
|
||||
video::SColor blue(128, 0, 0, 128);
|
||||
m_red_sphere = irr_driver->addSphere(1.0f, red);
|
||||
m_red_sphere->setVisible(false);
|
||||
m_blue_sphere = irr_driver->addSphere(1.0f, blue);
|
||||
m_blue_sphere->setVisible(false);
|
||||
#endif
|
||||
|
||||
m_world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
m_track = m_world->getTrack();
|
||||
m_cur_team = m_world->getKartTeam(m_kart->getWorldKartId());
|
||||
m_opp_team = (m_cur_team == SOCCER_TEAM_BLUE ?
|
||||
SOCCER_TEAM_RED : SOCCER_TEAM_BLUE);
|
||||
|
||||
// Don't call our own setControllerName, since this will add a
|
||||
// billboard showing 'AIBaseController' to the kart.
|
||||
@@ -59,7 +79,14 @@ SoccerAI::~SoccerAI()
|
||||
{
|
||||
#ifdef AI_DEBUG
|
||||
irr_driver->removeNode(m_debug_sphere);
|
||||
irr_driver->removeNode(m_debug_sphere_next);
|
||||
#endif
|
||||
|
||||
#ifdef BALL_AIM_DEBUG
|
||||
irr_driver->removeNode(m_red_sphere);
|
||||
irr_driver->removeNode(m_blue_sphere);
|
||||
#endif
|
||||
|
||||
} // ~SoccerAI
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -70,34 +97,27 @@ void SoccerAI::reset()
|
||||
ArenaAI::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_overtake_ball = false;
|
||||
m_force_brake = false;
|
||||
m_steer_with_ball = false;
|
||||
|
||||
} // reset
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerAI::update(float dt)
|
||||
{
|
||||
m_saving_ball = false;
|
||||
#ifdef BALL_AIM_DEBUG
|
||||
Vec3 red = m_world->getBallAimPosition(SOCCER_TEAM_RED);
|
||||
Vec3 blue = m_world->getBallAimPosition(SOCCER_TEAM_BLUE);
|
||||
m_red_sphere->setPosition(red.toIrrVector());
|
||||
m_blue_sphere->setPosition(blue.toIrrVector());
|
||||
#endif
|
||||
m_force_brake = false;
|
||||
m_steer_with_ball = false;
|
||||
|
||||
if (World::getWorld()->getPhase() == World::GOAL_PHASE)
|
||||
{
|
||||
resetAfterStop();
|
||||
m_controls->m_brake = false;
|
||||
m_controls->m_accel = 0.0f;
|
||||
AIBaseController::update(dt);
|
||||
@@ -146,114 +166,225 @@ 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 &&
|
||||
m_world->getDefender(m_cur_team) == (signed)m_kart->getWorldKartId())
|
||||
// Check if this AI kart is the one who will chase the ball
|
||||
if (m_world->getBallChaser(m_cur_team) == (signed)m_kart->getWorldKartId())
|
||||
{
|
||||
m_target_node = m_world->getBallNode();
|
||||
m_target_point = correctBallPosition(m_world->getBallPosition());
|
||||
m_target_point = determineBallAimingPosition();
|
||||
m_target_node = m_world->getBallNode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a suitable target to drive to, either ball or powerup
|
||||
if ((m_world->getBallPosition() - m_kart->getXYZ()).length_2d() > 20.0f &&
|
||||
(m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
|
||||
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER))
|
||||
// Always reset this flag,
|
||||
// in case the ball chaser lost the ball somehow
|
||||
m_overtake_ball = false;
|
||||
|
||||
if (m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
|
||||
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER)
|
||||
{
|
||||
collectItemInArena(&m_target_point , &m_target_node);
|
||||
}
|
||||
else if (m_world->getAttacker(m_cur_team) == (signed)m_kart
|
||||
->getWorldKartId())
|
||||
{
|
||||
// This AI will attack the other team ball chaser
|
||||
int id = m_world->getBallChaser(m_cur_team == SOCCER_TEAM_BLUE ?
|
||||
SOCCER_TEAM_RED : SOCCER_TEAM_BLUE);
|
||||
m_target_point = m_world->getKart(id)->getXYZ();
|
||||
m_target_node = m_world->getKartNode(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_target_node = m_world->getBallNode();
|
||||
m_target_point = correctBallPosition(m_world->getBallPosition());
|
||||
m_target_point = m_closest_kart_point;
|
||||
m_target_node = m_closest_kart_node;
|
||||
}
|
||||
|
||||
} // findTarget
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
Vec3 SoccerAI::correctBallPosition(const Vec3& orig_pos)
|
||||
Vec3 SoccerAI::determineBallAimingPosition()
|
||||
{
|
||||
// Notice: Build with AI_DEBUG and change camera target to an AI kart,
|
||||
// to debug or see how AI steer with the ball
|
||||
#ifdef BALL_AIM_DEBUG
|
||||
// Choose your favourite team to watch
|
||||
if (m_world->getKartTeam(m_kart->getWorldKartId()) == SOCCER_TEAM_BLUE)
|
||||
{
|
||||
Camera *cam = Camera::getActiveCamera();
|
||||
cam->setMode(Camera::CM_NORMAL);
|
||||
cam->setKart(m_kart);
|
||||
}
|
||||
#endif
|
||||
|
||||
const Vec3& ball_aim_pos = m_world->getBallAimPosition(m_opp_team);
|
||||
const Vec3& orig_pos = m_world->getBallPosition();
|
||||
const float ball_diameter = m_world->getBallDiameter();
|
||||
posData ball_pos = {0};
|
||||
posData goal_pos = {0};
|
||||
Vec3 ball_lc(0, 0, 0);
|
||||
checkPosition(orig_pos, &ball_pos, &ball_lc);
|
||||
posData aim_pos = {0};
|
||||
Vec3 ball_lc;
|
||||
Vec3 aim_lc;
|
||||
checkPosition(orig_pos, &ball_pos, &ball_lc, true/*use_front_xyz*/);
|
||||
checkPosition(ball_aim_pos, &aim_pos, &aim_lc, true/*use_front_xyz*/);
|
||||
|
||||
// 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 from the ball,
|
||||
// use path finding from arena ai to get close
|
||||
// ie no extra braking is needed
|
||||
if (aim_pos.distance > 10.0f) return ball_aim_pos;
|
||||
|
||||
if (goal_pos.behind)
|
||||
if (m_overtake_ball)
|
||||
{
|
||||
if (goal_pos.angle > 0.3f && ball_pos.distance < 3.0f &&
|
||||
!ball_pos.behind)
|
||||
Vec3 overtake_wc;
|
||||
const bool can_overtake = determineOvertakePosition(ball_lc, ball_pos,
|
||||
&overtake_wc);
|
||||
if (!can_overtake)
|
||||
{
|
||||
// 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;
|
||||
m_overtake_ball = false;
|
||||
return ball_aim_pos;
|
||||
}
|
||||
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);
|
||||
}
|
||||
return overtake_wc;
|
||||
}
|
||||
|
||||
if (ball_pos.distance < 3.0f &&
|
||||
!ball_pos.behind && !goal_pos.behind)
|
||||
else
|
||||
{
|
||||
if (goal_pos.angle < 0.5f)
|
||||
return orig_pos;
|
||||
else
|
||||
// Check whether the aim point is non-reachable
|
||||
// ie the ball is in front of the kart, which the aim position
|
||||
// is behind the ball , if so m_overtake_ball is true
|
||||
if (aim_lc.z() > 0 && aim_lc.z() > ball_lc.z())
|
||||
{
|
||||
// Same with above
|
||||
if (ball_pos.on_side && goal_pos.on_side)
|
||||
const bool can_overtake = determineOvertakePosition(ball_lc,
|
||||
ball_pos, NULL);
|
||||
if (can_overtake)
|
||||
{
|
||||
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);
|
||||
m_overtake_ball = true;
|
||||
}
|
||||
else
|
||||
m_controls->m_brake = true;
|
||||
{
|
||||
// Stop a while to wait for overtaking, prevent own goal too
|
||||
// Only do that if the ball is moving
|
||||
if (!m_world->ballNotMoving())
|
||||
m_force_brake = true;
|
||||
return ball_aim_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
return orig_pos;
|
||||
} // correctBallPosition
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Otherwise use the aim position calculated by soccer world
|
||||
// Prevent lost control when steering with ball
|
||||
m_force_brake = ball_pos.angle > 0.15f &&
|
||||
m_kart->getSpeed() > 9.0f && ball_pos.distance < ball_diameter;
|
||||
|
||||
if (aim_pos.behind && aim_pos.distance < (ball_diameter / 2))
|
||||
{
|
||||
// Reached aim point, aim forward
|
||||
m_steer_with_ball = true;
|
||||
return m_world->getBallAimPosition(m_opp_team, true/*reverse*/);
|
||||
}
|
||||
return ball_aim_pos;
|
||||
}
|
||||
|
||||
// Make compiler happy
|
||||
return ball_aim_pos;
|
||||
|
||||
} // determineBallAimingPosition
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool SoccerAI::determineOvertakePosition(const Vec3& ball_lc,
|
||||
const posData& ball_pos,
|
||||
Vec3* overtake_wc)
|
||||
{
|
||||
// This done by drawing a circle using the center of ball local coordinates
|
||||
// and the distance / 2 from kart to ball center as radius (which allows more
|
||||
// offset for overtaking), then find tangent line from kart (0, 0, 0) to the
|
||||
// circle. The intercept point will be used as overtake position
|
||||
|
||||
// No overtake if ball is behind
|
||||
if (ball_lc.z() < 0.0f) return false;
|
||||
|
||||
// Circle equation: (x-a)2 + (y-b)2 = r2
|
||||
const float r2 = (ball_pos.distance / 2) * (ball_pos.distance / 2);
|
||||
|
||||
// No overtake if sqrtf(r2) / 2 < ball radius,
|
||||
// which will likely push to ball forward
|
||||
if ((sqrtf(r2) / 2) < (m_world->getBallDiameter() / 2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const float a = ball_lc.x();
|
||||
const float b = ball_lc.z();
|
||||
|
||||
// Check first if the kart is lies inside the circle, if so no tangent
|
||||
// can be drawn ( so can't overtake), minus 0.1 as epslion
|
||||
const float test_radius_2 = ((a * a) + (b * b)) - 0.1f;
|
||||
if (test_radius_2 < r2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise calculate the tangent
|
||||
// As all are local coordinates, so center is 0,0 which is y = mx for the
|
||||
// tangent equation, and the m (slope) can be calculated by puting y = mx
|
||||
// into the general form of circle equation x2 + y2 + Dx + Ey + F = 0
|
||||
// This means: x2 + m2x2 + Dx + Emx + F = 0
|
||||
// (1+m2)x2 + (D+Em)x +F = 0
|
||||
// As only one root for it, so discriminant b2 - 4ac = 0
|
||||
// So: (D+Em)2 - 4(1+m2)(F) = 0
|
||||
// D2 + 2DEm +E2m2 - 4F - 4m2F = 0
|
||||
// (E2 - 4F)m2 + (2DE)m + (D2 - 4F) = 0
|
||||
// Now solve the above quadratic equation using
|
||||
// x = -b (+/-) sqrt(b2 - 4ac) / 2a
|
||||
const float d = -2 * a;
|
||||
const float e = -2 * b;
|
||||
const float f = (d * d / 4) + (e * e / 4) - r2;
|
||||
const float discriminant = (2 * 2 * d * d * e * e) -
|
||||
(4 * ((e * e) - (4 * f)) * ((d * d) - (4 * f)));
|
||||
|
||||
assert(discriminant > 0.0f);
|
||||
const float slope_1 = (-(2 * d * e) + sqrtf(discriminant)) /
|
||||
(2 * ((e * e) - (4 * f)));
|
||||
const float slope_2 = (-(2 * d * e) - sqrtf(discriminant)) /
|
||||
(2 * ((e * e) - (4 * f)));
|
||||
|
||||
assert(!std::isnan(slope_1));
|
||||
assert(!std::isnan(slope_2));
|
||||
|
||||
// Calculate two intercept points, as we already put y=mx into circle
|
||||
// equation and know that only one root for each slope, so x can be
|
||||
// calculated easily with -b / 2a
|
||||
// From (1+m2)x2 + (D+Em)x +F = 0:
|
||||
const float x1 = -(d + (e * slope_1)) / (2 * (1 + (slope_1 * slope_1)));
|
||||
const float x2 = -(d + (e * slope_2)) / (2 * (1 + (slope_2 * slope_2)));
|
||||
|
||||
// Use the closest point to aim
|
||||
float x = std::min(fabsf(x1), fabsf(x2));
|
||||
float y = 0.0f;
|
||||
if (-x == x1)
|
||||
{
|
||||
// x was negative
|
||||
x = -x;
|
||||
y = slope_1 * x;
|
||||
}
|
||||
else if (x == x1)
|
||||
{
|
||||
y = slope_1 * x;
|
||||
}
|
||||
else if (-x == x2)
|
||||
{
|
||||
x = -x;
|
||||
y = slope_2 * x;
|
||||
}
|
||||
else
|
||||
{
|
||||
y = slope_2 * x;
|
||||
}
|
||||
|
||||
if (overtake_wc)
|
||||
*overtake_wc = m_kart->getTrans()(Vec3(x, 0, y));
|
||||
return true;
|
||||
} // determineOvertakePosition
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
int SoccerAI::getCurrentNode() const
|
||||
{
|
||||
return m_world->getKartNode(m_kart->getWorldKartId());
|
||||
} // getCurrentNode
|
||||
// ------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
bool SoccerAI::isWaiting() const
|
||||
{
|
||||
return m_world->isStartPhase();
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
|
||||
#include "karts/controller/arena_ai.hpp"
|
||||
|
||||
#undef BALL_AIM_DEBUG
|
||||
#ifdef BALL_AIM_DEBUG
|
||||
#include "graphics/irr_driver.hpp"
|
||||
#endif
|
||||
|
||||
class SoccerWorld;
|
||||
class Vec3;
|
||||
class Item;
|
||||
|
||||
/** The actual soccer AI.
|
||||
* \ingroup controller
|
||||
@@ -31,19 +35,41 @@ class Item;
|
||||
class SoccerAI : public ArenaAI
|
||||
{
|
||||
private:
|
||||
|
||||
#ifdef BALL_AIM_DEBUG
|
||||
irr::scene::ISceneNode *m_red_sphere;
|
||||
irr::scene::ISceneNode *m_blue_sphere;
|
||||
#endif
|
||||
|
||||
/** Keep a pointer to world. */
|
||||
SoccerWorld *m_world;
|
||||
|
||||
SoccerTeam m_cur_team;
|
||||
bool m_saving_ball;
|
||||
SoccerTeam m_opp_team;
|
||||
|
||||
Vec3 correctBallPosition(const Vec3&);
|
||||
/** Define which way to handle to ball, either steer with it,
|
||||
* or overtake it (Denfense).
|
||||
*/
|
||||
bool m_overtake_ball;
|
||||
bool m_force_brake;
|
||||
bool m_steer_with_ball;
|
||||
|
||||
Vec3 determineBallAimingPosition();
|
||||
bool determineOvertakePosition(const Vec3& ball_lc,
|
||||
const posData& ball_pos, Vec3* overtake_wc);
|
||||
|
||||
virtual void findClosestKart(bool use_difficulty);
|
||||
virtual void findTarget();
|
||||
virtual void resetAfterStop() OVERRIDE { m_overtake_ball = false; }
|
||||
virtual int getCurrentNode() const;
|
||||
virtual bool isWaiting() const;
|
||||
virtual bool canSkid(float steer_fraction) { return m_saving_ball; }
|
||||
virtual bool canSkid(float steer_fraction) { return false; }
|
||||
virtual bool forceBraking() OVERRIDE { return m_force_brake; }
|
||||
virtual bool directDrive() OVERRIDE
|
||||
{
|
||||
return m_avoid_eating_banana || m_overtake_ball || m_steer_with_ball;
|
||||
}
|
||||
|
||||
public:
|
||||
SoccerAI(AbstractKart *kart);
|
||||
~SoccerAI();
|
||||
|
||||
@@ -58,7 +58,13 @@
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
TestAI::TestAI(AbstractKart *kart)
|
||||
// This define is only used to make the TestAI as similar to the
|
||||
// SkiddingAI. This way the two AIs can be compared without too many
|
||||
// false positives.
|
||||
|
||||
#define SkiddingAI TestAI
|
||||
|
||||
SkiddingAI::SkiddingAI(AbstractKart *kart)
|
||||
: AIBaseLapController(kart)
|
||||
{
|
||||
reset();
|
||||
@@ -137,13 +143,12 @@ TestAI::TestAI(AbstractKart *kart)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
} // TestAI
|
||||
} // SkiddingAI
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** The destructor deletes the shared TrackInfo objects if no more TestAI
|
||||
* instances are around.
|
||||
/** Destructor, mostly to clean up debug data structures.
|
||||
*/
|
||||
TestAI::~TestAI()
|
||||
SkiddingAI::~SkiddingAI()
|
||||
{
|
||||
#ifdef AI_DEBUG
|
||||
for(unsigned int i=0; i<3; i++)
|
||||
@@ -155,12 +160,12 @@ TestAI::~TestAI()
|
||||
}
|
||||
delete [] m_curve;
|
||||
#endif
|
||||
} // ~TestAI
|
||||
} // ~SkiddingAI
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Resets the AI when a race is restarted.
|
||||
*/
|
||||
void TestAI::reset()
|
||||
void SkiddingAI::reset()
|
||||
{
|
||||
m_time_since_last_shot = 0.0f;
|
||||
m_start_kart_crash_direction = 0;
|
||||
@@ -183,10 +188,10 @@ void TestAI::reset()
|
||||
QuadGraph::get()->findRoadSector(m_kart->getXYZ(), &m_track_node);
|
||||
if(m_track_node==QuadGraph::UNKNOWN_SECTOR)
|
||||
{
|
||||
Log::error("TestAI",
|
||||
"Invalid starting position for '%s' - not on track"
|
||||
" - can be ignored.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::error(getControllerName().c_str(),
|
||||
"Invalid starting position for '%s' - not on track"
|
||||
" - can be ignored.",
|
||||
m_kart->getIdent().c_str());
|
||||
m_track_node = QuadGraph::get()->findOutOfRoadSector(m_kart->getXYZ());
|
||||
}
|
||||
|
||||
@@ -198,7 +203,7 @@ void TestAI::reset()
|
||||
* This is used in profile mode when comparing different AI implementations
|
||||
* to be able to distinguish them from each other.
|
||||
*/
|
||||
const irr::core::stringw& TestAI::getNamePostfix() const
|
||||
const irr::core::stringw& SkiddingAI::getNamePostfix() const
|
||||
{
|
||||
// Static to avoid returning the address of a temporary string.
|
||||
static irr::core::stringw name="(default)";
|
||||
@@ -210,7 +215,7 @@ const irr::core::stringw& TestAI::getNamePostfix() const
|
||||
* \param index The index of the graph node for which the successor
|
||||
* is searched.
|
||||
*/
|
||||
unsigned int TestAI::getNextSector(unsigned int index)
|
||||
unsigned int SkiddingAI::getNextSector(unsigned int index)
|
||||
{
|
||||
return m_successor_index[index];
|
||||
} // getNextSector
|
||||
@@ -220,7 +225,7 @@ unsigned int TestAI::getNextSector(unsigned int index)
|
||||
* It is called once per frame for each AI and determines the behaviour of
|
||||
* the AI, e.g. steering, accelerating/braking, firing.
|
||||
*/
|
||||
void TestAI::update(float dt)
|
||||
void SkiddingAI::update(float dt)
|
||||
{
|
||||
// This is used to enable firing an item backwards.
|
||||
m_controls->m_look_back = false;
|
||||
@@ -389,7 +394,7 @@ void TestAI::update(float dt)
|
||||
* it will brake in order to make it easier to re-align itself), and
|
||||
* estimated curve radius (brake to avoid being pushed out of a curve).
|
||||
*/
|
||||
void TestAI::handleBraking()
|
||||
void SkiddingAI::handleBraking()
|
||||
{
|
||||
m_controls->m_brake = false;
|
||||
// In follow the leader mode, the kart should brake if they are ahead of
|
||||
@@ -400,8 +405,8 @@ void TestAI::handleBraking()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI", "braking: %s ahead of leader.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(), "braking: %s ahead of leader.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
|
||||
m_controls->m_brake = true;
|
||||
@@ -420,8 +425,9 @@ void TestAI::handleBraking()
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI", "%s not aligned with track.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s not aligned with track.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
m_controls->m_brake = true;
|
||||
return;
|
||||
@@ -439,11 +445,11 @@ void TestAI::handleBraking()
|
||||
m_controls->m_brake = true;
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI",
|
||||
"speed %f too tight curve: radius %f ",
|
||||
m_kart->getSpeed(),
|
||||
m_kart->getIdent().c_str(),
|
||||
m_current_curve_radius);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"speed %f too tight curve: radius %f ",
|
||||
m_kart->getSpeed(),
|
||||
m_kart->getIdent().c_str(),
|
||||
m_current_curve_radius);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -462,7 +468,7 @@ void TestAI::handleBraking()
|
||||
* steer direction to arrive at the currently aim-at point.
|
||||
* \param dt Time step size.
|
||||
*/
|
||||
void TestAI::handleSteering(float dt)
|
||||
void SkiddingAI::handleSteering(float dt)
|
||||
{
|
||||
const int next = m_next_node_index[m_track_node];
|
||||
|
||||
@@ -484,7 +490,8 @@ void TestAI::handleSteering(float dt)
|
||||
#ifdef AI_DEBUG
|
||||
m_debug_sphere[0]->setPosition(QuadGraph::get()->getQuadOfNode(next)
|
||||
.getCenter().toIrrVector());
|
||||
Log::debug("skidding_ai","-Outside of road: steer to center point.");
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"Outside of road: steer to center point.");
|
||||
#endif
|
||||
}
|
||||
//If we are going to crash against a kart, avoid it if it doesn't
|
||||
@@ -494,12 +501,12 @@ void TestAI::handleSteering(float dt)
|
||||
//-1 = left, 1 = right, 0 = no crash.
|
||||
if( m_start_kart_crash_direction == 1 )
|
||||
{
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f );
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f );
|
||||
m_start_kart_crash_direction = 0;
|
||||
}
|
||||
else if(m_start_kart_crash_direction == -1)
|
||||
{
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f);
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f);
|
||||
m_start_kart_crash_direction = 0;
|
||||
}
|
||||
else
|
||||
@@ -507,18 +514,19 @@ void TestAI::handleSteering(float dt)
|
||||
if(m_world->getDistanceToCenterForKart( m_kart->getWorldKartId() ) >
|
||||
m_world->getDistanceToCenterForKart( m_crashes.m_kart ))
|
||||
{
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f );
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f );
|
||||
m_start_kart_crash_direction = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
steer_angle = steerToAngle(next, M_PI*0.5f );
|
||||
steer_angle = steerToAngle(next, -M_PI*0.5f );
|
||||
m_start_kart_crash_direction = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef AI_DEBUG
|
||||
Log::debug("skidding_ai", "- Velocity vector crashes with kart "
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"Velocity vector crashes with kart "
|
||||
"and doesn't crashes with road : steer 90 "
|
||||
"degrees away from kart.");
|
||||
#endif
|
||||
@@ -611,7 +619,7 @@ void TestAI::handleSteering(float dt)
|
||||
* try to collect an item, this value will be changed.
|
||||
* \param last_node Index of the graph node on which the aim_point is.
|
||||
*/
|
||||
void TestAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
void SkiddingAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
int last_node)
|
||||
{
|
||||
#ifdef AI_DEBUG
|
||||
@@ -700,8 +708,8 @@ void TestAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
}
|
||||
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI", "%s unselects item.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(), "%s unselects item.",
|
||||
m_kart->getIdent().c_str());
|
||||
// Otherwise remove the pre-selected item (and start
|
||||
// looking for a new item).
|
||||
m_item_to_collect = NULL;
|
||||
@@ -770,9 +778,10 @@ void TestAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
.toIrrVector());
|
||||
#endif
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI", "%s selects item type '%d'.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s selects item type '%d'.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType());
|
||||
m_item_to_collect = item_to_collect;
|
||||
}
|
||||
else
|
||||
@@ -793,18 +802,18 @@ void TestAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
.toIrrVector());
|
||||
#endif
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI",
|
||||
"%s adjusts to hit type %d angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s adjusts to hit type %d angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI",
|
||||
"%s won't hit '%d', angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s won't hit '%d', angle %f.",
|
||||
m_kart->getIdent().c_str(),
|
||||
item_to_collect->getType(), angle);
|
||||
}
|
||||
} // kart will not hit item
|
||||
} // does hit hit bad item
|
||||
@@ -820,7 +829,7 @@ void TestAI::handleItemCollectionAndAvoidance(Vec3 *aim_point,
|
||||
* to be avoided.
|
||||
* \return True if it would hit any of the bad items.
|
||||
*/
|
||||
bool TestAI::hitBadItemWhenAimAt(const Item *item,
|
||||
bool SkiddingAI::hitBadItemWhenAimAt(const Item *item,
|
||||
const std::vector<const Item *> &items_to_avoid)
|
||||
{
|
||||
core::line2df to_item(m_kart->getXYZ().getX(), m_kart->getXYZ().getZ(),
|
||||
@@ -845,7 +854,7 @@ bool TestAI::hitBadItemWhenAimAt(const Item *item,
|
||||
* \param last_node
|
||||
* \return True if th AI should still aim for the pre-selected item.
|
||||
*/
|
||||
bool TestAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
|
||||
bool SkiddingAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
|
||||
{
|
||||
// If the item is unavailable keep on testing. It is not necessary
|
||||
// to test if an item has turned bad, this was tested before this
|
||||
@@ -881,7 +890,7 @@ bool TestAI::handleSelectedItem(float kart_aim_angle, Vec3 *aim_point)
|
||||
* into account).
|
||||
* \return True if steering is necessary to avoid an item.
|
||||
*/
|
||||
bool TestAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
|
||||
bool SkiddingAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
|
||||
const core::line2df &line_to_target,
|
||||
Vec3 *aim_point)
|
||||
{
|
||||
@@ -1024,7 +1033,7 @@ bool TestAI::steerToAvoid(const std::vector<const Item *> &items_to_avoid,
|
||||
* (NULL if no item was avoided so far).
|
||||
* \param item_to_collect A pointer to a previously selected item to collect.
|
||||
*/
|
||||
void TestAI::evaluateItems(const Item *item, float kart_aim_angle,
|
||||
void SkiddingAI::evaluateItems(const Item *item, float kart_aim_angle,
|
||||
std::vector<const Item *> *items_to_avoid,
|
||||
std::vector<const Item *> *items_to_collect)
|
||||
{
|
||||
@@ -1136,7 +1145,7 @@ void TestAI::evaluateItems(const Item *item, float kart_aim_angle,
|
||||
* STATE: shield on -> avoid usage of offensive items (with certain tolerance)
|
||||
* STATE: swatter on -> avoid usage of shield
|
||||
*/
|
||||
void TestAI::handleItems(const float dt)
|
||||
void SkiddingAI::handleItems(const float dt)
|
||||
{
|
||||
m_controls->m_fire = false;
|
||||
if(m_kart->getKartAnimation() ||
|
||||
@@ -1421,9 +1430,9 @@ void TestAI::handleItems(const float dt)
|
||||
m_controls->m_fire = m_kart_ahead != NULL;
|
||||
break;
|
||||
default:
|
||||
Log::error("TestAI",
|
||||
"Invalid or unhandled powerup '%d' in default AI.",
|
||||
m_kart->getPowerup()->getType());
|
||||
Log::error(getControllerName().c_str(),
|
||||
"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;
|
||||
@@ -1434,7 +1443,7 @@ void TestAI::handleItems(const float dt)
|
||||
* 'closeness' is for now simply based on the position, i.e. if a kart is
|
||||
* more than one lap behind or ahead, it is not considered to be closest.
|
||||
*/
|
||||
void TestAI::computeNearestKarts()
|
||||
void SkiddingAI::computeNearestKarts()
|
||||
{
|
||||
int my_position = m_kart->getPosition();
|
||||
|
||||
@@ -1496,7 +1505,7 @@ void TestAI::computeNearestKarts()
|
||||
/** Determines if the AI should accelerate or not.
|
||||
* \param dt Time step size.
|
||||
*/
|
||||
void TestAI::handleAcceleration( const float dt)
|
||||
void SkiddingAI::handleAcceleration( const float dt)
|
||||
{
|
||||
//Do not accelerate until we have delayed the start enough
|
||||
if( m_start_delay > 0.0f )
|
||||
@@ -1506,13 +1515,15 @@ void TestAI::handleAcceleration( const float dt)
|
||||
return;
|
||||
}
|
||||
|
||||
// This test appears to be incorrect, since at this stage
|
||||
// m_brake has not been reset from the previous frame
|
||||
//if( m_controls->m_brake )
|
||||
//{
|
||||
// m_controls->m_accel = 0.0f;
|
||||
// return;
|
||||
//}
|
||||
// FIXME: This test appears to be incorrect, since at this stage
|
||||
// m_brake has not been reset from the previous frame, which can
|
||||
// cause too long slow downs. On the other hand removing it appears
|
||||
// to decrease performance in some narrower tracks
|
||||
if( m_controls->m_brake )
|
||||
{
|
||||
m_controls->m_accel = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_kart->getBlockedByPlungerTime()>0)
|
||||
{
|
||||
@@ -1528,7 +1539,7 @@ void TestAI::handleAcceleration( const float dt)
|
||||
} // handleAcceleration
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void TestAI::handleRaceStart()
|
||||
void SkiddingAI::handleRaceStart()
|
||||
{
|
||||
if( m_start_delay < 0.0f )
|
||||
{
|
||||
@@ -1556,7 +1567,7 @@ void TestAI::handleRaceStart()
|
||||
//TODO: if the AI is crashing constantly, make it move backwards in a straight
|
||||
//line, then move forward while turning.
|
||||
|
||||
void TestAI::handleRescue(const float dt)
|
||||
void SkiddingAI::handleRescue(const float dt)
|
||||
{
|
||||
// check if kart is stuck
|
||||
if(m_kart->getSpeed()<2.0f && !m_kart->getKartAnimation() &&
|
||||
@@ -1578,7 +1589,7 @@ void TestAI::handleRescue(const float dt)
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Decides wether to use nitro or not.
|
||||
*/
|
||||
void TestAI::handleNitroAndZipper()
|
||||
void SkiddingAI::handleNitroAndZipper()
|
||||
{
|
||||
m_controls->m_nitro = false;
|
||||
|
||||
@@ -1586,9 +1597,10 @@ void TestAI::handleNitroAndZipper()
|
||||
// is already fast.
|
||||
|
||||
// If we are already very fast, save nitro.
|
||||
//if(m_kart->getSpeed() > 0.95f*m_kart->getCurrentMaxSpeed())
|
||||
// return;
|
||||
|
||||
if(m_kart->getSpeed() > 0.95f*m_kart->getCurrentMaxSpeed())
|
||||
return;
|
||||
// About the above: removing this line might enable the AI to better use
|
||||
// nitro and zippers .
|
||||
// This works well for some tracks (math, lighthouse
|
||||
// sandtrack), but worse in others (zen, xr591). The good result
|
||||
// is caused by long straights in which the AI will now use zippers,
|
||||
@@ -1734,7 +1746,7 @@ void TestAI::handleNitroAndZipper()
|
||||
} // handleNitroAndZipper
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void TestAI::checkCrashes(const Vec3& pos )
|
||||
void SkiddingAI::checkCrashes(const Vec3& pos )
|
||||
{
|
||||
int steps = int( m_kart->getVelocityLC().getZ() / m_kart_length );
|
||||
if( steps < 2 ) steps = 2;
|
||||
@@ -1757,7 +1769,7 @@ void TestAI::checkCrashes(const Vec3& pos )
|
||||
slip->isSlipstreamReady() &&
|
||||
slip->getSlipstreamTarget())
|
||||
{
|
||||
//Log::debug("skidding_ai", "%s overtaking %s\n",
|
||||
//Log::debug(getControllerName().c_str(), "%s overtaking %s",
|
||||
// m_kart->getIdent().c_str(),
|
||||
// m_kart->getSlipstreamKart()->getIdent().c_str());
|
||||
// FIXME: we might define a minimum distance, and if the target kart
|
||||
@@ -1782,9 +1794,9 @@ void TestAI::checkCrashes(const Vec3& pos )
|
||||
int current_node = m_track_node;
|
||||
if(steps<1 || steps>1000)
|
||||
{
|
||||
Log::warn("TestAI",
|
||||
"Incorrect STEPS=%d. kart_length %f velocity %f",
|
||||
steps, m_kart_length, m_kart->getVelocityLC().getZ());
|
||||
Log::warn(getControllerName().c_str(),
|
||||
"Incorrect STEPS=%d. kart_length %f velocity %f",
|
||||
steps, m_kart_length, m_kart->getVelocityLC().getZ());
|
||||
steps=1000;
|
||||
}
|
||||
for(int i = 1; steps > i; ++i)
|
||||
@@ -1863,7 +1875,7 @@ void TestAI::checkCrashes(const Vec3& pos )
|
||||
* driven to in a straight line.
|
||||
* \param last_node The graph node index in which the aim_position is.
|
||||
*/
|
||||
void TestAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
|
||||
void SkiddingAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
|
||||
{
|
||||
*last_node = m_next_node_index[m_track_node];
|
||||
const core::vector2df xz = m_kart->getXYZ().toIrrVector2d();
|
||||
@@ -1956,7 +1968,7 @@ void TestAI::findNonCrashingPointNew(Vec3 *result, int *last_node)
|
||||
* driven to in a straight line.
|
||||
* \param last_node The graph node index in which the aim_position is.
|
||||
*/
|
||||
void TestAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
|
||||
void SkiddingAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
|
||||
{
|
||||
#ifdef AI_DEBUG_KART_HEADING
|
||||
const Vec3 eps(0,0.5f,0);
|
||||
@@ -2050,7 +2062,7 @@ void TestAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
|
||||
* \param aim_position On exit contains the point the AI should aim at.
|
||||
* \param last_node On exit contais the graph node the AI is aiming at.
|
||||
*/
|
||||
void TestAI::findNonCrashingPoint(Vec3 *aim_position, int *last_node)
|
||||
void SkiddingAI::findNonCrashingPoint(Vec3 *aim_position, int *last_node)
|
||||
{
|
||||
#ifdef AI_DEBUG_KART_HEADING
|
||||
const Vec3 eps(0,0.5f,0);
|
||||
@@ -2137,7 +2149,7 @@ void TestAI::findNonCrashingPointFixed(Vec3 *aim_position, int *last_node)
|
||||
/** Determines the direction of the track ahead of the kart: 0 indicates
|
||||
* straight, +1 right turn, -1 left turn.
|
||||
*/
|
||||
void TestAI::determineTrackDirection()
|
||||
void SkiddingAI::determineTrackDirection()
|
||||
{
|
||||
const QuadGraph *qg = QuadGraph::get();
|
||||
unsigned int succ = m_successor_index[m_track_node];
|
||||
@@ -2189,7 +2201,7 @@ void TestAI::determineTrackDirection()
|
||||
// ----------------------------------------------------------------------------
|
||||
/** If the kart is at/in a curve, determine the turn radius.
|
||||
*/
|
||||
void TestAI::handleCurve()
|
||||
void SkiddingAI::handleCurve()
|
||||
{
|
||||
// Ideally we would like to have a circle that:
|
||||
// 1) goes through the kart position
|
||||
@@ -2253,7 +2265,7 @@ void TestAI::handleCurve()
|
||||
* AIBaseLapController.
|
||||
* \return True if the kart should skid.
|
||||
*/
|
||||
bool TestAI::canSkid(float steer_fraction)
|
||||
bool SkiddingAI::canSkid(float steer_fraction)
|
||||
{
|
||||
if(fabsf(steer_fraction)>1.5f)
|
||||
{
|
||||
@@ -2266,8 +2278,9 @@ bool TestAI::canSkid(float steer_fraction)
|
||||
if(m_ai_debug)
|
||||
{
|
||||
if(fabsf(steer_fraction)>=2.5f)
|
||||
Log::debug("TestAI", "%s stops skidding (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s stops skidding (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
}
|
||||
#endif
|
||||
// If the current turn is not sharp enough, delay releasing
|
||||
@@ -2286,8 +2299,9 @@ bool TestAI::canSkid(float steer_fraction)
|
||||
#ifdef DEBUG
|
||||
if(m_controls->m_skid && m_ai_debug)
|
||||
{
|
||||
Log::debug("TestAI", "%s stops skidding on straight.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s stops skidding on straight.",
|
||||
m_kart->getIdent().c_str());
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
@@ -2322,8 +2336,9 @@ bool TestAI::canSkid(float steer_fraction)
|
||||
if(m_controls->m_skid && duration < 1.0f)
|
||||
{
|
||||
if(m_ai_debug)
|
||||
Log::debug("TestAI", "'%s' too short, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"'%s' too short, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
return false;
|
||||
}
|
||||
// Test if the AI is trying to skid against track direction. This
|
||||
@@ -2337,9 +2352,9 @@ bool TestAI::canSkid(float steer_fraction)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_controls->m_skid && m_ai_debug)
|
||||
Log::debug("TestAI",
|
||||
"%s skidding against track direction.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s skidding against track direction.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -2349,8 +2364,9 @@ bool TestAI::canSkid(float steer_fraction)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(!m_controls->m_skid && m_ai_debug)
|
||||
Log::debug("TestAI", "%s start skid, duration %f.",
|
||||
m_kart->getIdent().c_str(), duration);
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s start skid, duration %f.",
|
||||
m_kart->getIdent().c_str(), duration);
|
||||
#endif
|
||||
return true;
|
||||
|
||||
@@ -2358,8 +2374,9 @@ bool TestAI::canSkid(float steer_fraction)
|
||||
|
||||
#ifdef DEBUG
|
||||
if(m_controls->m_skid && m_ai_debug)
|
||||
Log::debug("TestAI", "%s has no reasons to skid anymore.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::debug(getControllerName().c_str(),
|
||||
"%s has no reasons to skid anymore.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
return false;
|
||||
} // canSkid
|
||||
@@ -2377,7 +2394,7 @@ bool TestAI::canSkid(float steer_fraction)
|
||||
* \param angle Steering angle.
|
||||
* \param dt Time step.
|
||||
*/
|
||||
void TestAI::setSteering(float angle, float dt)
|
||||
void SkiddingAI::setSteering(float angle, float dt)
|
||||
{
|
||||
float steer_fraction = angle / m_kart->getMaxSteerAngle();
|
||||
|
||||
@@ -2404,9 +2421,10 @@ void TestAI::setSteering(float angle, float dt)
|
||||
: SKID_PROBAB_NO_SKID;
|
||||
#undef PRINT_SKID_STATS
|
||||
#ifdef PRINT_SKID_STATS
|
||||
Log::info("TestAI", "%s distance %f prob %d skidding %s\n",
|
||||
m_kart->getIdent().c_str(), distance, prob,
|
||||
sc= ? "no" : sc==KartControl::SC_LEFT ? "left" : "right");
|
||||
Log::info(getControllerName().c_str(),
|
||||
"%s distance %f prob %d skidding %s",
|
||||
m_kart->getIdent().c_str(), distance, prob,
|
||||
sc= ? "no" : sc==KartControl::SC_LEFT ? "left" : "right");
|
||||
#endif
|
||||
}
|
||||
m_controls->m_skid = m_skid_probability_state == SKID_PROBAB_SKID
|
||||
@@ -2437,8 +2455,9 @@ void TestAI::setSteering(float angle, float dt)
|
||||
m_controls->m_skid = KartControl::SC_NONE;
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::info("TestAI", "'%s' wrong steering, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
Log::info(getControllerName().c_str(),
|
||||
"'%s' wrong steering, stop skid.",
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -2452,8 +2471,9 @@ void TestAI::setSteering(float angle, float dt)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if(m_ai_debug)
|
||||
Log::info("TestAI", "%s steering too much (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
Log::info(getControllerName().c_str(),
|
||||
"%s steering too much (%f).",
|
||||
m_kart->getIdent().c_str(), steer_fraction);
|
||||
#endif
|
||||
m_controls->m_skid = KartControl::SC_NONE;
|
||||
}
|
||||
@@ -2496,7 +2516,7 @@ void TestAI::setSteering(float angle, float dt)
|
||||
* \param[out] center Center point of the circle.
|
||||
* \param[out] radius Radius of the circle.
|
||||
*/
|
||||
void TestAI::determineTurnRadius(const Vec3 &start,
|
||||
void SkiddingAI::determineTurnRadius(const Vec3 &start,
|
||||
const Vec3 &tangent,
|
||||
const Vec3 &end,
|
||||
Vec3 *center,
|
||||
|
||||
@@ -552,6 +552,8 @@ void cmdLineHelp()
|
||||
" --root=DIR Path to add to the list of STK root directories.\n"
|
||||
" You can specify more than one by separating them\n"
|
||||
" with colons (:).\n"
|
||||
" --cutscene=NAME Launch the specified track as a cutscene.\n"
|
||||
" This is for internal debugging use only.\n"
|
||||
"\n"
|
||||
"You can visit SuperTuxKart's homepage at "
|
||||
"http://supertuxkart.sourceforge.net\n\n",
|
||||
|
||||
@@ -301,6 +301,11 @@ void CutsceneWorld::update(float dt)
|
||||
rot2.setPitch(rot2.getPitch() + 90.0f);
|
||||
m_camera->setRotation(rot2.toIrrVector());
|
||||
|
||||
irr::core::vector3df up(0.0f, 0.0f, 1.0f);
|
||||
irr::core::matrix4 matrix = anchorNode->getAbsoluteTransformation();
|
||||
matrix.rotateVect(up);
|
||||
m_camera->setUpVector(up);
|
||||
|
||||
SFXManager::get()->positionListener(m_camera->getAbsolutePosition(),
|
||||
m_camera->getTarget() -
|
||||
m_camera->getAbsolutePosition(),
|
||||
|
||||
@@ -32,8 +32,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"
|
||||
#include "utils/constants.hpp"
|
||||
@@ -45,10 +43,10 @@
|
||||
*/
|
||||
SoccerWorld::SoccerWorld() : WorldWithRank()
|
||||
{
|
||||
if(race_manager->hasTimeTarget())
|
||||
if (race_manager->hasTimeTarget())
|
||||
{
|
||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
||||
m_count_down_reached_zero = false;
|
||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN,
|
||||
race_manager->getTimeTarget());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -58,10 +56,12 @@ SoccerWorld::SoccerWorld() : WorldWithRank()
|
||||
m_frame_count = 0;
|
||||
m_start_time = irr_driver->getRealTime();
|
||||
m_use_highscores = false;
|
||||
m_red_ai = 0;
|
||||
m_blue_ai = 0;
|
||||
} // SoccerWorld
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** The destructor frees al data structures.
|
||||
/** The destructor frees all data structures.
|
||||
*/
|
||||
SoccerWorld::~SoccerWorld()
|
||||
{
|
||||
@@ -80,37 +80,11 @@ void SoccerWorld::init()
|
||||
m_display_rank = false;
|
||||
m_goal_timer = 0.0f;
|
||||
m_ball_hitter = -1;
|
||||
m_ball = NULL;
|
||||
m_ball_body = NULL;
|
||||
m_goal_target = race_manager->getMaxGoal();
|
||||
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
|
||||
|
||||
} // init
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Called when a soccer game is restarted.
|
||||
*/
|
||||
void SoccerWorld::reset()
|
||||
{
|
||||
WorldWithRank::reset();
|
||||
if(race_manager->hasTimeTarget())
|
||||
{
|
||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
||||
m_count_down_reached_zero = false;
|
||||
}
|
||||
else WorldStatus::setClockMode(CLOCK_CHRONO);
|
||||
|
||||
m_can_score_points = true;
|
||||
m_red_goal = 0;
|
||||
m_blue_goal = 0;
|
||||
m_red_scorers.clear();
|
||||
m_red_score_times.clear();
|
||||
m_blue_scorers.clear();
|
||||
m_blue_score_times.clear();
|
||||
m_ball_hitter = -1;
|
||||
m_ball = NULL;
|
||||
m_red_defender = -1;
|
||||
m_blue_defender = -1;
|
||||
m_ball_invalid_timer = 0.0f;
|
||||
|
||||
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
|
||||
assert(tom);
|
||||
PtrVector<TrackObject>& objects = tom->getObjects();
|
||||
@@ -120,12 +94,44 @@ void SoccerWorld::reset()
|
||||
if(!obj->isSoccerBall())
|
||||
continue;
|
||||
m_ball = obj;
|
||||
m_ball_body = m_ball->getPhysicalObject()->getBody();
|
||||
// Handle one ball only
|
||||
break;
|
||||
}
|
||||
if (!m_ball)
|
||||
Log::fatal("SoccerWorld","Ball is missing in soccer field, abort.");
|
||||
|
||||
m_bgd.init(m_ball->getPhysicalObject()->getRadius());
|
||||
|
||||
} // init
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Called when a soccer game is restarted.
|
||||
*/
|
||||
void SoccerWorld::reset()
|
||||
{
|
||||
WorldWithRank::reset();
|
||||
if (race_manager->hasTimeTarget())
|
||||
{
|
||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN,
|
||||
race_manager->getTimeTarget());
|
||||
}
|
||||
else
|
||||
{
|
||||
WorldStatus::setClockMode(CLOCK_CHRONO);
|
||||
}
|
||||
|
||||
m_count_down_reached_zero = false;
|
||||
m_red_scorers.clear();
|
||||
m_red_score_times.clear();
|
||||
m_blue_scorers.clear();
|
||||
m_blue_score_times.clear();
|
||||
m_ball_hitter = -1;
|
||||
m_red_kdm.clear();
|
||||
m_blue_kdm.clear();
|
||||
m_ball_heading = 0.0f;
|
||||
m_ball_invalid_timer = 0.0f;
|
||||
|
||||
if (m_goal_sound != NULL &&
|
||||
m_goal_sound->getStatus() == SFXBase::SFX_PLAYING)
|
||||
{
|
||||
@@ -133,9 +139,9 @@ void SoccerWorld::reset()
|
||||
}
|
||||
|
||||
initKartList();
|
||||
resetAllNodes();
|
||||
initGoalNodes();
|
||||
resetBall();
|
||||
resetAllPosition();
|
||||
m_ball->reset();
|
||||
m_bgd.reset();
|
||||
|
||||
} // reset
|
||||
|
||||
@@ -153,19 +159,17 @@ const std::string& SoccerWorld::getIdent() const
|
||||
*/
|
||||
void SoccerWorld::update(float dt)
|
||||
{
|
||||
World *world = World::getWorld();
|
||||
|
||||
WorldWithRank::update(dt);
|
||||
WorldWithRank::updateTrack(dt);
|
||||
|
||||
updateBallPosition(dt);
|
||||
if (m_track->hasNavMesh())
|
||||
{
|
||||
updateKartNodes();
|
||||
updateDefenders();
|
||||
updateAIData();
|
||||
}
|
||||
|
||||
if (world->getPhase() == World::GOAL_PHASE)
|
||||
WorldWithRank::update(dt);
|
||||
WorldWithRank::updateTrack(dt);
|
||||
|
||||
if (getPhase() == World::GOAL_PHASE)
|
||||
{
|
||||
if (m_goal_timer == 0.0f)
|
||||
{
|
||||
@@ -177,7 +181,7 @@ void SoccerWorld::update(float dt)
|
||||
|
||||
if (m_goal_timer > 3.0f)
|
||||
{
|
||||
world->setPhase(WorldStatus::RACE_PHASE);
|
||||
setPhase(WorldStatus::RACE_PHASE);
|
||||
m_goal_timer = 0.0f;
|
||||
if (!isRaceOver())
|
||||
{
|
||||
@@ -198,62 +202,52 @@ void SoccerWorld::onCheckGoalTriggered(bool first_goal)
|
||||
if (isRaceOver() || isStartPhase())
|
||||
return;
|
||||
|
||||
if (m_can_score_points)
|
||||
setPhase(WorldStatus::GOAL_PHASE);
|
||||
m_goal_sound->play();
|
||||
if (m_ball_hitter != -1)
|
||||
{
|
||||
(first_goal ? m_red_goal++ : m_blue_goal++);
|
||||
ScorerData sd;
|
||||
sd.m_id = m_ball_hitter;
|
||||
sd.m_correct_goal = isCorrectGoal(m_ball_hitter, first_goal);
|
||||
|
||||
World *world = World::getWorld();
|
||||
world->setPhase(WorldStatus::GOAL_PHASE);
|
||||
m_goal_sound->play();
|
||||
if (m_ball_hitter != -1)
|
||||
if (sd.m_correct_goal)
|
||||
{
|
||||
ScorerData sd;
|
||||
sd.m_id = m_ball_hitter;
|
||||
sd.m_correct_goal = isCorrectGoal(m_ball_hitter, first_goal);
|
||||
m_karts[m_ball_hitter]->getKartModel()
|
||||
->setAnimation(KartModel::AF_WIN_START, true/* play_non_loop*/);
|
||||
}
|
||||
|
||||
if (sd.m_correct_goal)
|
||||
{
|
||||
m_karts[m_ball_hitter]->getKartModel()
|
||||
->setAnimation(KartModel::AF_WIN_START, true/* play_non_loop*/);
|
||||
}
|
||||
else if (!sd.m_correct_goal)
|
||||
{
|
||||
m_karts[m_ball_hitter]->getKartModel()
|
||||
->setAnimation(KartModel::AF_LOSE_START, true/* play_non_loop*/);
|
||||
}
|
||||
|
||||
else if (!sd.m_correct_goal)
|
||||
if (first_goal)
|
||||
{
|
||||
// Notice: true first_goal means it's blue goal being shoot,
|
||||
// so red team can score
|
||||
m_red_scorers.push_back(sd);
|
||||
if (race_manager->hasTimeTarget())
|
||||
{
|
||||
m_karts[m_ball_hitter]->getKartModel()
|
||||
->setAnimation(KartModel::AF_LOSE_START, true/* play_non_loop*/);
|
||||
}
|
||||
|
||||
if (first_goal)
|
||||
{
|
||||
// Notice: true first_goal means it's blue goal being shoot,
|
||||
// so red team can score
|
||||
m_red_scorers.push_back(sd);
|
||||
if(race_manager->hasTimeTarget())
|
||||
{
|
||||
m_red_score_times.push_back(race_manager
|
||||
->getTimeTarget() - world->getTime());
|
||||
}
|
||||
else
|
||||
m_red_score_times.push_back(world->getTime());
|
||||
m_red_score_times.push_back(race_manager->getTimeTarget()
|
||||
- getTime());
|
||||
}
|
||||
else
|
||||
m_red_score_times.push_back(getTime());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_blue_scorers.push_back(sd);
|
||||
if (race_manager->hasTimeTarget())
|
||||
{
|
||||
m_blue_scorers.push_back(sd);
|
||||
if (race_manager->hasTimeTarget())
|
||||
{
|
||||
m_blue_score_times.push_back(race_manager
|
||||
->getTimeTarget() - world->getTime());
|
||||
}
|
||||
else
|
||||
m_blue_score_times.push_back(world->getTime());
|
||||
m_blue_score_times.push_back(race_manager->getTimeTarget()
|
||||
- getTime());
|
||||
}
|
||||
else
|
||||
m_blue_score_times.push_back(getTime());
|
||||
}
|
||||
}
|
||||
|
||||
resetBall();
|
||||
//Resetting the ball triggers the goal check line one more time.
|
||||
//This ensures that only one goal is counted, and the second is ignored.
|
||||
m_can_score_points = !m_can_score_points;
|
||||
m_ball->reset();
|
||||
|
||||
} // onCheckGoalTriggered
|
||||
|
||||
@@ -285,17 +279,6 @@ bool SoccerWorld::isRaceOver()
|
||||
|
||||
} // isRaceOver
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Called when the race finishes, i.e. after playing (if necessary) an
|
||||
* end of race animation. It updates the time for all karts still racing,
|
||||
* and then updates the ranks.
|
||||
*/
|
||||
void SoccerWorld::terminateRace()
|
||||
{
|
||||
m_can_score_points = false;
|
||||
WorldWithRank::terminateRace();
|
||||
} // terminateRace
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Called when the match time ends.
|
||||
*/
|
||||
@@ -361,22 +344,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
|
||||
@@ -462,23 +433,27 @@ void SoccerWorld::updateBallPosition(float dt)
|
||||
{
|
||||
if (isRaceOver()) return;
|
||||
|
||||
m_ball_position = m_ball->getPresentation<TrackObjectPresentationMesh>()
|
||||
->getNode()->getPosition();
|
||||
if (!ballNotMoving())
|
||||
{
|
||||
// Only update heading if the ball is moving
|
||||
m_ball_heading = atan2f(m_ball_body->getLinearVelocity().getX(),
|
||||
m_ball_body->getLinearVelocity().getZ());
|
||||
}
|
||||
|
||||
if (m_track->hasNavMesh())
|
||||
{
|
||||
m_ball_on_node = BattleGraph::get()->pointToNode(m_ball_on_node,
|
||||
m_ball_position, true/*ignore_vertical*/);
|
||||
getBallPosition(), true/*ignore_vertical*/);
|
||||
|
||||
if (m_ball_on_node == BattleGraph::UNKNOWN_POLY &&
|
||||
World::getWorld()->getPhase() == RACE_PHASE)
|
||||
getPhase() == RACE_PHASE)
|
||||
{
|
||||
m_ball_invalid_timer += dt;
|
||||
// Reset the ball and karts if out of navmesh after 2 seconds
|
||||
if (m_ball_invalid_timer >= 2.0f)
|
||||
{
|
||||
m_ball_invalid_timer = 0.0f;
|
||||
resetBall();
|
||||
m_ball->reset();
|
||||
for (unsigned int i = 0; i < m_karts.size(); i++)
|
||||
moveKartAfterRescue(m_karts[i]);
|
||||
}
|
||||
@@ -490,59 +465,20 @@ void SoccerWorld::updateBallPosition(float dt)
|
||||
} // updateBallPosition
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Localize two goals on the navigation mesh.
|
||||
*/
|
||||
void SoccerWorld::initGoalNodes()
|
||||
{
|
||||
if (!m_track->hasNavMesh()) return;
|
||||
|
||||
unsigned int n = CheckManager::get()->getCheckStructureCount();
|
||||
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
{
|
||||
CheckGoal* goal =
|
||||
dynamic_cast<CheckGoal*>(CheckManager::get()->getCheckStructure(i));
|
||||
if (goal)
|
||||
{
|
||||
if (goal->getTeam())
|
||||
{
|
||||
m_blue_goal_node = BattleGraph::get()->pointToNode(m_blue_goal_node,
|
||||
goal->convertTo3DCenter(), true/*ignore_vertical*/);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_red_goal_node = BattleGraph::get()->pointToNode(m_red_goal_node,
|
||||
goal->convertTo3DCenter(), true/*ignore_vertical*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // initGoalNodes
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::resetAllNodes()
|
||||
void SoccerWorld::resetAllPosition()
|
||||
{
|
||||
m_kart_on_node.clear();
|
||||
m_kart_on_node.resize(m_karts.size());
|
||||
for(unsigned int n=0; n<m_karts.size(); n++)
|
||||
m_kart_on_node[n] = BattleGraph::UNKNOWN_POLY;
|
||||
m_kart_on_node.resize(m_karts.size(), 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
|
||||
{
|
||||
std::map<int, SoccerTeam>::const_iterator n = m_kart_team_map.find(kart_id);
|
||||
if (n != m_kart_team_map.end())
|
||||
{
|
||||
return n->second;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
Log::warn("SoccerWorld", "Unknown team, using blue default.");
|
||||
return SOCCER_TEAM_BLUE;
|
||||
std::map<int, SoccerTeam>::const_iterator n =
|
||||
m_kart_team_map.find(kart_id);
|
||||
|
||||
assert(n != m_kart_team_map.end());
|
||||
return n->second;
|
||||
} // getKartTeam
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -563,55 +499,64 @@ bool SoccerWorld::isCorrectGoal(unsigned int kart_id, bool first_goal) const
|
||||
} // isCorrectGoal
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::updateDefenders()
|
||||
void SoccerWorld::updateAIData()
|
||||
{
|
||||
if (isRaceOver()) return;
|
||||
|
||||
float distance = 99999.9f;
|
||||
int defender = -1;
|
||||
// Fill the kart distance map
|
||||
m_red_kdm.clear();
|
||||
m_blue_kdm.clear();
|
||||
|
||||
// Check for red team
|
||||
for (unsigned int i = 0; i < (unsigned)m_karts.size(); ++i)
|
||||
for (unsigned int i = 0; i < m_karts.size(); ++i)
|
||||
{
|
||||
if (m_karts[i]->getController()->isPlayerController() ||
|
||||
getKartTeam(m_karts[i]->getWorldKartId()) != SOCCER_TEAM_RED)
|
||||
continue;
|
||||
|
||||
Vec3 d = NavMesh::get()->getNavPoly(this
|
||||
->getGoalNode(SOCCER_TEAM_RED)).getCenter()
|
||||
- m_karts[i]->getXYZ();
|
||||
|
||||
if (d.length_2d() <= distance)
|
||||
if (getKartTeam(m_karts[i]->getWorldKartId()) == SOCCER_TEAM_RED)
|
||||
{
|
||||
defender = i;
|
||||
distance = d.length_2d();
|
||||
Vec3 rd = m_karts[i]->getXYZ() - getBallPosition();
|
||||
m_red_kdm.push_back(KartDistanceMap(i, rd.length_2d()));
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec3 bd = m_karts[i]->getXYZ() - getBallPosition();
|
||||
m_blue_kdm.push_back(KartDistanceMap(i, bd.length_2d()));
|
||||
}
|
||||
}
|
||||
if (defender != -1) m_red_defender = defender;
|
||||
// Sort the vectors, so first vector will have the min distance
|
||||
std::sort(m_red_kdm.begin(), m_red_kdm.end());
|
||||
std::sort(m_blue_kdm.begin(), m_blue_kdm.end());
|
||||
|
||||
distance = 99999.9f;
|
||||
defender = -1;
|
||||
// Fill Ball and goals data
|
||||
m_bgd.updateBallAndGoal(getBallPosition(), getBallHeading());
|
||||
|
||||
// Check for blue team
|
||||
for (unsigned int i = 0; i < (unsigned)m_karts.size(); ++i)
|
||||
} // updateAIData
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
int SoccerWorld::getAttacker(SoccerTeam team) const
|
||||
{
|
||||
if (team == SOCCER_TEAM_BLUE && m_blue_kdm.size() > 1)
|
||||
{
|
||||
if (m_karts[i]->getController()->isPlayerController() ||
|
||||
getKartTeam(m_karts[i]->getWorldKartId()) != SOCCER_TEAM_BLUE)
|
||||
continue;
|
||||
|
||||
Vec3 d = NavMesh::get()->getNavPoly(this
|
||||
->getGoalNode(SOCCER_TEAM_BLUE)).getCenter()
|
||||
- m_karts[i]->getXYZ();
|
||||
|
||||
if (d.length_2d() <= distance)
|
||||
for (unsigned int i = 1; i < m_blue_kdm.size(); i++)
|
||||
{
|
||||
defender = i;
|
||||
distance = d.length_2d();
|
||||
// Only AI will do the attack job
|
||||
if (getKart(m_blue_kdm[i].m_kart_id)
|
||||
->getController()->isPlayerController())
|
||||
continue;
|
||||
return m_blue_kdm[i].m_kart_id;
|
||||
}
|
||||
}
|
||||
else if (team == SOCCER_TEAM_RED && m_red_kdm.size() > 1)
|
||||
{
|
||||
for (unsigned int i = 1; i < m_red_kdm.size(); i++)
|
||||
{
|
||||
if (getKart(m_red_kdm[i].m_kart_id)
|
||||
->getController()->isPlayerController())
|
||||
continue;
|
||||
return m_red_kdm[i].m_kart_id;
|
||||
}
|
||||
}
|
||||
if (defender != -1) m_blue_defender = defender;
|
||||
|
||||
} // updateDefenders
|
||||
// No attacker
|
||||
return -1;
|
||||
} // getAttacker
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
int SoccerWorld::getTeamNum(SoccerTeam team) const
|
||||
@@ -632,23 +577,11 @@ unsigned int SoccerWorld::getRescuePositionIndex(AbstractKart *kart)
|
||||
{
|
||||
std::map<int, unsigned int>::const_iterator n =
|
||||
m_kart_position_map.find(kart->getWorldKartId());
|
||||
if (n != m_kart_position_map.end())
|
||||
{
|
||||
return n->second;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
Log::warn("SoccerWorld", "Unknown kart, using default starting position.");
|
||||
return 0;
|
||||
assert (n != m_kart_position_map.end());
|
||||
return n->second;
|
||||
} // getRescuePositionIndex
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::resetBall()
|
||||
{
|
||||
m_ball->reset();
|
||||
m_ball->getPhysicalObject()->reset();
|
||||
} // resetBall
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::enterRaceOverState()
|
||||
{
|
||||
@@ -683,3 +616,51 @@ void SoccerWorld::enterRaceOverState()
|
||||
else
|
||||
WorldStatus::enterRaceOverState();
|
||||
} // enterRaceOverState
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
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();
|
||||
|
||||
// Happen in profiling mode
|
||||
if (team == SOCCER_TEAM_NONE)
|
||||
{
|
||||
race_manager->setKartSoccerTeam(i, SOCCER_TEAM_BLUE);
|
||||
team = SOCCER_TEAM_BLUE;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "modes/world_with_rank.hpp"
|
||||
#include "states_screens/race_gui_base.hpp"
|
||||
#include "karts/abstract_kart.hpp"
|
||||
#include "tracks/check_goal.hpp"
|
||||
#include "tracks/check_manager.hpp"
|
||||
|
||||
#include <IMesh.h>
|
||||
#include <string>
|
||||
@@ -53,16 +55,213 @@ protected:
|
||||
PerPlayerDifficulty difficulty) OVERRIDE;
|
||||
|
||||
private:
|
||||
class KartDistanceMap
|
||||
{
|
||||
public:
|
||||
/** World ID of kart. */
|
||||
unsigned int m_kart_id;
|
||||
/** Distance to ball from kart */
|
||||
float m_distance;
|
||||
|
||||
bool operator < (const KartDistanceMap& r) const
|
||||
{
|
||||
return m_distance < r.m_distance;
|
||||
}
|
||||
KartDistanceMap(unsigned int kart_id = 0, float distance = 0.0f)
|
||||
{
|
||||
m_kart_id = kart_id;
|
||||
m_distance = distance;
|
||||
}
|
||||
}; // KartDistanceMap
|
||||
|
||||
class BallGoalData
|
||||
{
|
||||
// These data are used by AI to determine ball aiming angle
|
||||
private:
|
||||
// Radius of the ball
|
||||
float m_radius;
|
||||
|
||||
// Slope of the line from ball to the center point of goals
|
||||
float m_red_goal_slope;
|
||||
float m_blue_goal_slope;
|
||||
|
||||
// The transform only takes the ball heading into account,
|
||||
// ie no hpr of ball which allowing setting aim point easier
|
||||
btTransform m_trans;
|
||||
|
||||
// Two goals
|
||||
CheckGoal* m_blue_check_goal;
|
||||
CheckGoal* m_red_check_goal;
|
||||
|
||||
// Location to red/blue goal points from the ball heading point of view
|
||||
Vec3 m_red_goal_1;
|
||||
Vec3 m_red_goal_2;
|
||||
Vec3 m_red_goal_3;
|
||||
Vec3 m_blue_goal_1;
|
||||
Vec3 m_blue_goal_2;
|
||||
Vec3 m_blue_goal_3;
|
||||
public:
|
||||
void reset()
|
||||
{
|
||||
m_red_goal_1 = Vec3(0, 0, 0);
|
||||
m_red_goal_2 = Vec3(0, 0, 0);
|
||||
m_red_goal_3 = Vec3(0, 0, 0);
|
||||
m_blue_goal_1 = Vec3(0, 0, 0);
|
||||
m_blue_goal_2 = Vec3(0, 0, 0);
|
||||
m_blue_goal_3 = Vec3(0, 0, 0);
|
||||
m_red_goal_slope = 1.0f;
|
||||
m_blue_goal_slope = 1.0f;
|
||||
m_trans = btTransform(btQuaternion(0, 0, 0, 1), Vec3(0, 0, 0));
|
||||
} // reset
|
||||
|
||||
float getDiameter() const
|
||||
{
|
||||
return m_radius * 2;
|
||||
} // getDiameter
|
||||
|
||||
void init(float ball_radius)
|
||||
{
|
||||
m_radius = ball_radius;
|
||||
assert(m_radius > 0.0f);
|
||||
|
||||
// Save two goals
|
||||
unsigned int n = CheckManager::get()->getCheckStructureCount();
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
{
|
||||
CheckGoal* goal = dynamic_cast<CheckGoal*>
|
||||
(CheckManager::get()->getCheckStructure(i));
|
||||
if (goal)
|
||||
{
|
||||
if (goal->getTeam())
|
||||
m_blue_check_goal = goal;
|
||||
else
|
||||
m_red_check_goal = goal;
|
||||
}
|
||||
}
|
||||
if (m_blue_check_goal == NULL || m_red_check_goal == NULL)
|
||||
{
|
||||
Log::error("SoccerWorld", "Goal(s) is missing!");
|
||||
}
|
||||
} // init
|
||||
|
||||
void updateBallAndGoal(const Vec3& ball_pos, float heading)
|
||||
{
|
||||
btQuaternion quat(Vec3(0, 1, 0), -heading);
|
||||
m_trans = btTransform(btQuaternion(Vec3(0, 1, 0), heading),
|
||||
ball_pos);
|
||||
|
||||
// Red goal
|
||||
m_red_goal_1 = quatRotate(quat, m_red_check_goal
|
||||
->getPoint(CheckGoal::POINT_FIRST) - ball_pos);
|
||||
m_red_goal_2 = quatRotate(quat, m_red_check_goal
|
||||
->getPoint(CheckGoal::POINT_CENTER) - ball_pos);
|
||||
m_red_goal_3 = quatRotate(quat, m_red_check_goal
|
||||
->getPoint(CheckGoal::POINT_LAST) - ball_pos);
|
||||
|
||||
// Blue goal
|
||||
m_blue_goal_1 = quatRotate(quat, m_blue_check_goal
|
||||
->getPoint(CheckGoal::POINT_FIRST) - ball_pos);
|
||||
m_blue_goal_2 = quatRotate(quat, m_blue_check_goal
|
||||
->getPoint(CheckGoal::POINT_CENTER) - ball_pos);
|
||||
m_blue_goal_3 = quatRotate(quat, m_blue_check_goal
|
||||
->getPoint(CheckGoal::POINT_LAST) - ball_pos);
|
||||
|
||||
// Update the slope:
|
||||
// Use y = mx + c as an equation from goal center to ball
|
||||
// As the line always intercept in (0,0) which is the ball location,
|
||||
// so y(z)/x is the slope , it is used for determine aiming position
|
||||
// of ball later
|
||||
m_red_goal_slope = m_red_goal_2.z() / m_red_goal_2.x();
|
||||
m_blue_goal_slope = m_blue_goal_2.z() / m_blue_goal_2.x();
|
||||
} // updateBallAndGoal
|
||||
|
||||
bool isApproachingGoal(SoccerTeam team) const
|
||||
{
|
||||
// If the ball lies between the first and last pos, and faces
|
||||
// in front of either of them, (inside angular size of goal)
|
||||
// than it's likely to goal
|
||||
if (team == SOCCER_TEAM_BLUE)
|
||||
{
|
||||
if ((m_blue_goal_1.z() > 0.0f || m_blue_goal_3.z() > 0.0f) &&
|
||||
((m_blue_goal_1.x() < 0.0f && m_blue_goal_3.x() > 0.0f) ||
|
||||
(m_blue_goal_3.x() < 0.0f && m_blue_goal_1.x() > 0.0f)))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_red_goal_1.z() > 0.0f || m_red_goal_3.z() > 0.0f) &&
|
||||
((m_red_goal_1.x() < 0.0f && m_red_goal_3.x() > 0.0f) ||
|
||||
(m_red_goal_3.x() < 0.0f && m_red_goal_1.x() > 0.0f)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // isApproachingGoal
|
||||
|
||||
Vec3 getAimPosition(SoccerTeam team, bool reverse) const
|
||||
{
|
||||
// If it's likely to goal already, aim the ball straight behind
|
||||
// should do the job
|
||||
if (isApproachingGoal(team))
|
||||
return m_trans(Vec3(0, 0, -1));
|
||||
|
||||
// Otherwise do the below:
|
||||
// This is done by using Pythagorean Theorem and solving the
|
||||
// equation from ball to goal center (y = (m_***_goal_slope) x)
|
||||
|
||||
// We aim behind the ball from the center of the ball to its
|
||||
// diameter, so 2*m_radius = sqrt (x2 + y2),
|
||||
// which is next x = sqrt (2*m_radius - y2)
|
||||
// And than we have x = y / m(m_***_goal_slope)
|
||||
// After put that in the slope equation, we have
|
||||
// y = sqrt(2*m_radius*m2 / (1+m2))
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
if (team == SOCCER_TEAM_BLUE)
|
||||
{
|
||||
y = sqrt((m_blue_goal_slope * m_blue_goal_slope * m_radius*2) /
|
||||
(1 + (m_blue_goal_slope * m_blue_goal_slope)));
|
||||
if (m_blue_goal_2.x() == 0.0f ||
|
||||
(m_blue_goal_2.x() > 0.0f && m_blue_goal_2.z() > 0.0f) ||
|
||||
(m_blue_goal_2.x() < 0.0f && m_blue_goal_2.z() > 0.0f))
|
||||
{
|
||||
// Determine when y should be negative
|
||||
y = -y;
|
||||
}
|
||||
x = y / m_blue_goal_slope;
|
||||
}
|
||||
else
|
||||
{
|
||||
y = sqrt((m_red_goal_slope * m_red_goal_slope * m_radius*2) /
|
||||
(1 + (m_red_goal_slope * m_red_goal_slope)));
|
||||
if (m_red_goal_2.x() == 0.0f ||
|
||||
(m_red_goal_2.x() > 0.0f && m_red_goal_2.z() > 0.0f) ||
|
||||
(m_red_goal_2.x() < 0.0f && m_red_goal_2.z() > 0.0f))
|
||||
{
|
||||
y = -y;
|
||||
}
|
||||
x = y / m_red_goal_slope;
|
||||
}
|
||||
assert (!std::isnan(x));
|
||||
assert (!std::isnan(y));
|
||||
// Return the world coordinates
|
||||
return (reverse ? m_trans(Vec3(-x, 0, -y)) :
|
||||
m_trans(Vec3(x, 0, y)));
|
||||
} // getAimPosition
|
||||
|
||||
}; // BallGoalData
|
||||
|
||||
std::vector<KartDistanceMap> m_red_kdm;
|
||||
std::vector<KartDistanceMap> m_blue_kdm;
|
||||
BallGoalData m_bgd;
|
||||
|
||||
/** Keep a pointer to the track object of soccer ball */
|
||||
TrackObject* m_ball;
|
||||
btRigidBody* m_ball_body;
|
||||
|
||||
/** Number of goals needed to win */
|
||||
int m_goal_target;
|
||||
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*/
|
||||
@@ -71,8 +270,6 @@ private:
|
||||
int m_ball_hitter;
|
||||
|
||||
/** Goals data of each team scored */
|
||||
int m_red_goal;
|
||||
int m_blue_goal;
|
||||
std::vector<ScorerData> m_red_scorers;
|
||||
std::vector<float> m_red_score_times;
|
||||
std::vector<ScorerData> m_blue_scorers;
|
||||
@@ -84,27 +281,22 @@ private:
|
||||
/** Data generated from navmesh */
|
||||
std::vector<int> m_kart_on_node;
|
||||
int m_ball_on_node;
|
||||
Vec3 m_ball_position;
|
||||
int m_red_goal_node;
|
||||
int m_blue_goal_node;
|
||||
|
||||
int m_red_defender;
|
||||
int m_blue_defender;
|
||||
int m_red_ai;
|
||||
int m_blue_ai;
|
||||
|
||||
float m_ball_heading;
|
||||
|
||||
/** 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(float dt);
|
||||
/** Clean up */
|
||||
void resetAllNodes();
|
||||
/** Reset the ball to original starting position. */
|
||||
void resetBall();
|
||||
/** Function to update the AI which is the closest to its goal to defend. */
|
||||
void updateDefenders();
|
||||
void resetAllPosition();
|
||||
/** Function to update data for AI usage. */
|
||||
void updateAIData();
|
||||
/** Get number of teammates in a team, used by starting position assign. */
|
||||
int getTeamNum(SoccerTeam team) const;
|
||||
|
||||
@@ -121,7 +313,6 @@ public:
|
||||
|
||||
// clock events
|
||||
virtual bool isRaceOver() OVERRIDE;
|
||||
virtual void terminateRace() OVERRIDE;
|
||||
virtual void countdownReachedZero() OVERRIDE;
|
||||
|
||||
// overriding World methods
|
||||
@@ -131,7 +322,7 @@ public:
|
||||
|
||||
virtual bool useFastMusicNearEnd() const OVERRIDE { return false; }
|
||||
virtual void getKartsDisplayInfo(
|
||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) OVERRIDE {}
|
||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) OVERRIDE {}
|
||||
|
||||
virtual bool raceHasLaps() OVERRIDE { return false; }
|
||||
|
||||
@@ -151,8 +342,11 @@ public:
|
||||
/** Get the team of kart in soccer world (including AIs) */
|
||||
SoccerTeam getKartTeam(unsigned int kart_id) const;
|
||||
// ------------------------------------------------------------------------
|
||||
const int getScore(SoccerTeam team) const
|
||||
{ return (team == SOCCER_TEAM_BLUE ? m_blue_goal : m_red_goal); }
|
||||
int getScore(SoccerTeam team) const
|
||||
{
|
||||
return (team == SOCCER_TEAM_BLUE ? m_blue_scorers.size() :
|
||||
m_red_scorers.size());
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
const std::vector<ScorerData>& getScorers(SoccerTeam team) const
|
||||
{ return (team == SOCCER_TEAM_BLUE ? m_blue_scorers : m_red_scorers); }
|
||||
@@ -163,26 +357,48 @@ public:
|
||||
m_blue_score_times : m_red_score_times);
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
const int getKartNode(unsigned int kart_id) const
|
||||
{ return m_kart_on_node[kart_id]; }
|
||||
int getKartNode(unsigned int kart_id) const
|
||||
{ return m_kart_on_node[kart_id]; }
|
||||
// ------------------------------------------------------------------------
|
||||
const int getBallNode() const
|
||||
{ return m_ball_on_node; }
|
||||
int getBallNode() const
|
||||
{ return m_ball_on_node; }
|
||||
// ------------------------------------------------------------------------
|
||||
const Vec3& getBallPosition() const
|
||||
{ return m_ball_position; }
|
||||
{ return (Vec3&)m_ball_body->getCenterOfMassTransform().getOrigin(); }
|
||||
// ------------------------------------------------------------------------
|
||||
const int getGoalNode(SoccerTeam team) const
|
||||
bool ballNotMoving() const
|
||||
{
|
||||
return (team == SOCCER_TEAM_BLUE ? m_blue_goal_node : m_red_goal_node);
|
||||
return (m_ball_body->getLinearVelocity().x() == 0.0f ||
|
||||
m_ball_body->getLinearVelocity().z() == 0.0f);
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
float getBallHeading() const
|
||||
{ return m_ball_heading; }
|
||||
// ------------------------------------------------------------------------
|
||||
float getBallDiameter() const
|
||||
{ return m_bgd.getDiameter(); }
|
||||
// ------------------------------------------------------------------------
|
||||
bool ballApproachingGoal(SoccerTeam team) const
|
||||
{ return m_bgd.isApproachingGoal(team); }
|
||||
// ------------------------------------------------------------------------
|
||||
Vec3 getBallAimPosition(SoccerTeam team, bool reverse = false) const
|
||||
{ return m_bgd.getAimPosition(team, reverse); }
|
||||
// ------------------------------------------------------------------------
|
||||
bool isCorrectGoal(unsigned int kart_id, bool first_goal) const;
|
||||
// ------------------------------------------------------------------------
|
||||
const int getDefender(SoccerTeam team) const
|
||||
int getBallChaser(SoccerTeam team) const
|
||||
{
|
||||
return (team == SOCCER_TEAM_BLUE ? m_blue_defender : m_red_defender);
|
||||
// Only AI call this function, so each team should have at least a kart
|
||||
assert(m_blue_kdm.size() > 0 && m_red_kdm.size() > 0);
|
||||
return (team == SOCCER_TEAM_BLUE ? m_blue_kdm[0].m_kart_id :
|
||||
m_red_kdm[0].m_kart_id);
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
/** Get the AI who will attack the other team ball chaser. */
|
||||
int getAttacker(SoccerTeam team) const;
|
||||
// ------------------------------------------------------------------------
|
||||
void setAITeam();
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
}; // SoccerWorld
|
||||
|
||||
|
||||
@@ -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<SoccerWorld*>(this);
|
||||
if (sw)
|
||||
sw->setAITeam();
|
||||
|
||||
for(unsigned int i=0; i<num_karts; i++)
|
||||
{
|
||||
if (race_manager->getKartType(i) == RaceManager::KT_GHOST) continue;
|
||||
|
||||
@@ -211,6 +211,8 @@ public:
|
||||
/** Add body to dynamic world */
|
||||
void addBody();
|
||||
// ------------------------------------------------------------------------
|
||||
float getRadius() const { return m_radius; }
|
||||
// ------------------------------------------------------------------------
|
||||
const std::string& getOnKartCollisionFunction() const { return m_on_kart_collision; }
|
||||
// ------------------------------------------------------------------------
|
||||
const std::string& getOnItemCollisionFunction() const { return m_on_item_collision; }
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -75,6 +75,8 @@ RaceManager::RaceManager()
|
||||
m_coin_target = 0;
|
||||
m_started_from_overworld = false;
|
||||
m_have_kart_last_position_on_overworld = false;
|
||||
setMaxGoal(0);
|
||||
setTimeTarget(0.0f);
|
||||
setReverseTrack(false);
|
||||
setRecordRace(false);
|
||||
setRaceGhostKarts(false);
|
||||
|
||||
@@ -332,7 +332,6 @@ private:
|
||||
unsigned int m_num_finished_karts;
|
||||
unsigned int m_num_finished_players;
|
||||
int m_coin_target;
|
||||
bool m_has_time_target;
|
||||
float m_time_target;
|
||||
int m_goal_target;
|
||||
|
||||
@@ -417,9 +416,13 @@ public:
|
||||
void computeRandomKartList();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
bool hasTimeTarget() const { return m_has_time_target; }
|
||||
bool hasTimeTarget() const { return m_time_target > 0.0f; }
|
||||
// ------------------------------------------------------------------------
|
||||
void setMaxGoal(int maxGoal){ m_goal_target = maxGoal; }
|
||||
void setMaxGoal(int max_goal)
|
||||
{
|
||||
m_time_target = 0.0f;
|
||||
m_goal_target = max_goal;
|
||||
} // setMaxGoal
|
||||
// ------------------------------------------------------------------------
|
||||
int getMaxGoal(){ return m_goal_target; }
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -458,9 +461,8 @@ public:
|
||||
void setMajorMode(MajorRaceModeType mode) { m_major_mode = mode; }
|
||||
// ------------------------------------------------------------------------
|
||||
void setMinorMode(MinorRaceModeType mode)
|
||||
{
|
||||
{
|
||||
m_minor_mode = mode;
|
||||
m_has_time_target = false;
|
||||
} // setMinorMode
|
||||
// ------------------------------------------------------------------------
|
||||
void setNumKarts(int num)
|
||||
@@ -470,10 +472,10 @@ public:
|
||||
m_ai_superpower = SUPERPOWER_NONE;
|
||||
} // setNumKarts
|
||||
// ------------------------------------------------------------------------
|
||||
void setTimeTarget(float num)
|
||||
void setTimeTarget(float time)
|
||||
{
|
||||
m_has_time_target = true;
|
||||
m_time_target = num;
|
||||
m_goal_target = 0;
|
||||
m_time_target = time;
|
||||
} // setTimeTarget
|
||||
// ------------------------------------------------------------------------
|
||||
const RemoteKartInfo& getKartInfo(unsigned int n) const
|
||||
@@ -495,7 +497,7 @@ public:
|
||||
MinorRaceModeType getMinorMode() const { return m_minor_mode; }
|
||||
// ------------------------------------------------------------------------
|
||||
unsigned int getNumPlayers() const
|
||||
{
|
||||
{
|
||||
return (unsigned int) m_player_karts.size();
|
||||
} // getNumPlayers
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@@ -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<DynamicRibbonWidget>("karts");
|
||||
if (w == NULL)
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -44,7 +44,6 @@ DEFINE_SCREEN_SINGLETON( NetworkKartSelectionScreen );
|
||||
NetworkKartSelectionScreen::NetworkKartSelectionScreen()
|
||||
: KartSelectionScreen("karts_online.stkgui")
|
||||
{
|
||||
KartSelectionScreen::m_instance_ptr = this;
|
||||
} // NetworkKartSelectionScreen
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -21,7 +21,6 @@ DEFINE_SCREEN_SINGLETON( OfflineKartSelectionScreen );
|
||||
|
||||
OfflineKartSelectionScreen::OfflineKartSelectionScreen() : KartSelectionScreen("karts.stkgui")
|
||||
{
|
||||
KartSelectionScreen::m_instance_ptr = this;
|
||||
}
|
||||
|
||||
OfflineKartSelectionScreen::~OfflineKartSelectionScreen()
|
||||
|
||||
@@ -397,10 +397,10 @@ void RaceGUI::drawGlobalMiniMap()
|
||||
irr_driver->getTexture(FileManager::GUI, "soccer_ball_normal.png");
|
||||
|
||||
core::rect<s32> source(core::position2di(0, 0), icon->getSize());
|
||||
core::rect<s32> position(m_map_left+(int)(draw_at.getX()-(m_minimap_ai_size>>2)),
|
||||
lower_y -(int)(draw_at.getY()+(m_minimap_ai_size>>2)),
|
||||
m_map_left+(int)(draw_at.getX()+(m_minimap_ai_size>>2)),
|
||||
lower_y -(int)(draw_at.getY()-(m_minimap_ai_size>>2)));
|
||||
core::rect<s32> position(m_map_left+(int)(draw_at.getX()-(m_minimap_player_size/2.5f)),
|
||||
lower_y -(int)(draw_at.getY()+(m_minimap_player_size/2.5f)),
|
||||
m_map_left+(int)(draw_at.getX()+(m_minimap_player_size/2.5f)),
|
||||
lower_y -(int)(draw_at.getY()-(m_minimap_player_size/2.5f)));
|
||||
draw2DImage(icon, position, source, NULL, NULL, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<const EndController*>(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<const PlayerController*>(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)"));
|
||||
}
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ private:
|
||||
void displayHighScores();
|
||||
void displaySoccerResults();
|
||||
void displayScreenShots();
|
||||
irr::core::stringw getKartDisplayName(const AbstractKart *kart) const;
|
||||
|
||||
int getFontHeight () const;
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
#include <IMeshSceneNode.h>
|
||||
|
||||
#include "config/user_config.hpp"
|
||||
#include "io/xml_node.hpp"
|
||||
#include "items/item_manager.hpp"
|
||||
#include "race/race_manager.hpp"
|
||||
#include "tracks/navmesh.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
@@ -33,7 +35,8 @@ BattleGraph * BattleGraph::m_battle_graph = NULL;
|
||||
/** Constructor, Creates a navmesh, builds a graph from the navmesh and
|
||||
* then runs shortest path algorithm to find and store paths to be used
|
||||
* by the AI. */
|
||||
BattleGraph::BattleGraph(const std::string &navmesh_file_name)
|
||||
BattleGraph::BattleGraph(const std::string &navmesh_file_name,
|
||||
const XMLNode& node)
|
||||
{
|
||||
m_items_on_graph.clear();
|
||||
|
||||
@@ -41,6 +44,9 @@ BattleGraph::BattleGraph(const std::string &navmesh_file_name)
|
||||
m_navmesh_file = navmesh_file_name;
|
||||
buildGraph(NavMesh::get());
|
||||
computeFloydWarshall();
|
||||
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
||||
loadGoalNodes(node);
|
||||
|
||||
} // BattleGraph
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -122,7 +128,6 @@ void BattleGraph::computeFloydWarshall()
|
||||
} // computeFloydWarshall
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
/** Maps items on battle graph */
|
||||
void BattleGraph::findItemsOnGraphNodes()
|
||||
{
|
||||
@@ -152,7 +157,6 @@ void BattleGraph::findItemsOnGraphNodes()
|
||||
} // findItemsOnGraphNodes
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
int BattleGraph::pointToNode(const int cur_node,
|
||||
const Vec3& cur_point,
|
||||
bool ignore_vertical) const
|
||||
@@ -215,10 +219,64 @@ int BattleGraph::pointToNode(const int cur_node,
|
||||
} // pointToNode
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
const bool BattleGraph::differentNodeColor(int n, NodeColor* c) const
|
||||
{
|
||||
std::set<int>::iterator it;
|
||||
it = m_red_node.find(n);
|
||||
if (it != m_red_node.end())
|
||||
{
|
||||
*c = COLOR_RED;
|
||||
return true;
|
||||
}
|
||||
|
||||
it = m_blue_node.find(n);
|
||||
if (it != m_blue_node.end())
|
||||
{
|
||||
*c = COLOR_BLUE;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // differentNodeColor
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
void BattleGraph::loadGoalNodes(const XMLNode& node)
|
||||
{
|
||||
m_red_node.clear();
|
||||
m_blue_node.clear();
|
||||
|
||||
const XMLNode *check_node = node.getNode("checks");
|
||||
for (unsigned int i = 0; i < check_node->getNumNodes(); i++)
|
||||
{
|
||||
const XMLNode *goal = check_node->getNode(i);
|
||||
if (goal->getName() =="goal")
|
||||
{
|
||||
Vec3 p1, p2;
|
||||
bool first_goal = false;
|
||||
goal->get("first_goal", &first_goal);
|
||||
goal->get("p1", &p1);
|
||||
goal->get("p2", &p2);
|
||||
|
||||
int first = pointToNode(/*cur_node*/-1, p1, true);
|
||||
int last = pointToNode(/*cur_node*/-1, p2, true);
|
||||
|
||||
first_goal ? m_blue_node.insert(first) : m_red_node.insert(first);
|
||||
first_goal ? m_blue_node.insert(last) : m_red_node.insert(last);
|
||||
while (first != last)
|
||||
{
|
||||
// Find all the nodes which connect the two points of
|
||||
// goal, notice: only work if it's a straight line
|
||||
first = getNextShortestPathPoly(first, last);
|
||||
first_goal ? m_blue_node.insert(first) :
|
||||
m_red_node.insert(first);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // loadGoalNodes
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
#ifndef HEADER_BATTLE_GRAPH_HPP
|
||||
#define HEADER_BATTLE_GRAPH_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "tracks/graph_structure.hpp"
|
||||
#include "tracks/navmesh.hpp"
|
||||
@@ -30,6 +30,7 @@ class GraphStructure;
|
||||
class Item;
|
||||
class ItemManager;
|
||||
class Navmesh;
|
||||
class XMLNode;
|
||||
|
||||
/**
|
||||
* \ingroup tracks
|
||||
@@ -57,10 +58,14 @@ private:
|
||||
|
||||
std::vector< std::pair<const Item*, int> > m_items_on_graph;
|
||||
|
||||
std::set<int> m_red_node;
|
||||
std::set<int> m_blue_node;
|
||||
|
||||
void buildGraph(NavMesh*);
|
||||
void computeFloydWarshall();
|
||||
void loadGoalNodes(const XMLNode& node);
|
||||
|
||||
BattleGraph(const std::string &navmesh_file_name);
|
||||
BattleGraph(const std::string &navmesh_file_name, const XMLNode& node);
|
||||
~BattleGraph(void);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -79,6 +84,8 @@ private:
|
||||
// ------------------------------------------------------------------------
|
||||
virtual const bool hasLapLine() const
|
||||
{ return false; }
|
||||
// ------------------------------------------------------------------------
|
||||
virtual const bool differentNodeColor(int n, NodeColor* c) const;
|
||||
|
||||
public:
|
||||
static const int UNKNOWN_POLY;
|
||||
@@ -88,10 +95,11 @@ public:
|
||||
// ----------------------------------------------------------------------
|
||||
/** Asserts that no BattleGraph instance exists. Then
|
||||
* creates a BattleGraph instance. */
|
||||
static void create(const std::string &navmesh_file_name)
|
||||
static void create(const std::string &navmesh_file_name,
|
||||
const XMLNode& node)
|
||||
{
|
||||
assert(m_battle_graph==NULL);
|
||||
m_battle_graph = new BattleGraph(navmesh_file_name);
|
||||
m_battle_graph = new BattleGraph(navmesh_file_name, node);
|
||||
|
||||
} // create
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -55,8 +55,7 @@ void CheckGoal::update(float dt)
|
||||
|
||||
if (world)
|
||||
{
|
||||
const Vec3 &xyz = world->getBallPosition();
|
||||
if (isTriggered(m_previous_ball_position, xyz, -1))
|
||||
if (isTriggered(m_previous_ball_position, world->getBallPosition(), -1))
|
||||
{
|
||||
if (UserConfigParams::m_check_debug)
|
||||
{
|
||||
@@ -65,7 +64,7 @@ void CheckGoal::update(float dt)
|
||||
}
|
||||
trigger(0);
|
||||
}
|
||||
m_previous_ball_position = xyz;
|
||||
m_previous_ball_position = world->getBallPosition();
|
||||
}
|
||||
} // update
|
||||
|
||||
@@ -108,16 +107,7 @@ void CheckGoal::reset(const Track &track)
|
||||
|
||||
if (world)
|
||||
{
|
||||
const Vec3 &xyz = world->getBallPosition();
|
||||
m_previous_ball_position = xyz;
|
||||
m_previous_ball_position = world->getBallPosition();
|
||||
}
|
||||
|
||||
} // reset
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
Vec3 CheckGoal::convertTo3DCenter() const
|
||||
{
|
||||
float x = m_line.getMiddle().X;
|
||||
float y = m_line.getMiddle().Y;
|
||||
return Vec3(x, 0, y);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include <line2d.h>
|
||||
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
|
||||
|
||||
@@ -152,8 +152,12 @@ void GraphStructure::createMesh(bool show_invisible,
|
||||
c.setBlue((i%2) ? 0 : 255);
|
||||
}
|
||||
|
||||
NodeColor nc = COLOR_RED;
|
||||
const bool different_color = differentNodeColor(count, &nc);
|
||||
// Transfer the 4 points of the current quad to the list of vertices
|
||||
set3DVerticesOfGraph(count, new_v+4*i, c);
|
||||
set3DVerticesOfGraph(count, new_v+4*i, (different_color ?
|
||||
(nc == COLOR_RED ? video::SColor(255, 255, 0, 0) :
|
||||
video::SColor(255, 0, 0, 255)) : c));
|
||||
|
||||
// Set up the indices for the triangles
|
||||
// (note, afaik with opengl we could use quads directly, but the code
|
||||
|
||||
@@ -46,6 +46,13 @@ class GraphStructure : public NoCopy
|
||||
{
|
||||
protected:
|
||||
|
||||
/** Used by soccer field with navmesh to draw goal line. */
|
||||
enum NodeColor
|
||||
{
|
||||
COLOR_BLUE,
|
||||
COLOR_RED
|
||||
};
|
||||
|
||||
void cleanupDebugMesh();
|
||||
void destroyRTT();
|
||||
|
||||
@@ -77,6 +84,7 @@ private:
|
||||
virtual const bool isNodeInvisible(int n) const = 0;
|
||||
virtual const bool isNodeInvalid(int n) const = 0;
|
||||
virtual const bool hasLapLine() const = 0;
|
||||
virtual const bool differentNodeColor(int n, NodeColor* c) const = 0;
|
||||
|
||||
public:
|
||||
GraphStructure();
|
||||
|
||||
@@ -90,6 +90,9 @@ private:
|
||||
// ------------------------------------------------------------------------
|
||||
virtual const bool hasLapLine() const
|
||||
{ return true; }
|
||||
// ------------------------------------------------------------------------
|
||||
virtual const bool differentNodeColor(int n, NodeColor* c) const
|
||||
{ return false; }
|
||||
|
||||
public:
|
||||
static const int UNKNOWN_SECTOR;
|
||||
|
||||
@@ -670,9 +670,9 @@ void Track::startMusic() const
|
||||
/** Loads the polygon graph for battle, i.e. the definition of all polys, and the way
|
||||
* they are connected to each other. Input file name is hardcoded for now
|
||||
*/
|
||||
void Track::loadBattleGraph()
|
||||
void Track::loadBattleGraph(const XMLNode &node)
|
||||
{
|
||||
BattleGraph::create(m_root+"navmesh.xml");
|
||||
BattleGraph::create(m_root+"navmesh.xml", node);
|
||||
|
||||
if(BattleGraph::get()->getNumNodes()==0)
|
||||
{
|
||||
@@ -1635,25 +1635,9 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
|
||||
(void)e;
|
||||
}
|
||||
|
||||
// Load the graph only now: this function is called from world, after
|
||||
// the race gui was created. The race gui is needed since it stores
|
||||
// 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)
|
||||
loadBattleGraph();
|
||||
|
||||
ItemManager::create();
|
||||
|
||||
// Set the default start positions. Node that later the default
|
||||
// positions can still be overwritten.
|
||||
float forwards_distance = 1.5f;
|
||||
float sidewards_distance = 3.0f;
|
||||
float upwards_distance = 0.1f;
|
||||
int karts_per_row = 2;
|
||||
|
||||
|
||||
// Start building the scene graph
|
||||
// Soccer field with navmesh requires it
|
||||
// for two goal line to be drawn them in minimap
|
||||
std::string path = m_root + m_all_modes[mode_id].m_scene;
|
||||
XMLNode *root = file_manager->createXMLTree(path);
|
||||
|
||||
@@ -1667,6 +1651,23 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
|
||||
throw std::runtime_error(msg.str());
|
||||
}
|
||||
|
||||
// Load the graph only now: this function is called from world, after
|
||||
// the race gui was created. The race gui is needed since it stores
|
||||
// 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)
|
||||
loadBattleGraph(*root);
|
||||
|
||||
ItemManager::create();
|
||||
|
||||
// Set the default start positions. Node that later the default
|
||||
// positions can still be overwritten.
|
||||
float forwards_distance = 1.5f;
|
||||
float sidewards_distance = 3.0f;
|
||||
float upwards_distance = 0.1f;
|
||||
int karts_per_row = 2;
|
||||
|
||||
const XMLNode *default_start = root->getNode("default-start");
|
||||
if (default_start)
|
||||
{
|
||||
|
||||
@@ -381,7 +381,7 @@ private:
|
||||
|
||||
void loadTrackInfo();
|
||||
void loadQuadGraph(unsigned int mode_id, const bool reverse);
|
||||
void loadBattleGraph();
|
||||
void loadBattleGraph(const XMLNode &node);
|
||||
void convertTrackToBullet(scene::ISceneNode *node);
|
||||
bool loadMainTrack(const XMLNode &node);
|
||||
void loadMinimap();
|
||||
|
||||
Reference in New Issue
Block a user