stk-code_catmod/src/modes/soccer_world.cpp

1105 lines
37 KiB
C++

// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2006-2015 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "modes/soccer_world.hpp"
#include "main_loop.hpp"
#include "audio/music_manager.hpp"
#include "audio/sfx_base.hpp"
#include "config/user_config.hpp"
#include "io/file_manager.hpp"
#include "graphics/irr_driver.hpp"
#include "karts/abstract_kart_animation.hpp"
#include "karts/kart_model.hpp"
#include "karts/kart_properties.hpp"
#include "karts/controller/local_player_controller.hpp"
#include "karts/controller/network_player_controller.hpp"
#include "network/network_config.hpp"
#include "network/network_string.hpp"
#include "network/protocols/game_events_protocol.hpp"
#include "network/stk_host.hpp"
#include "network/stk_peer.hpp"
#include "physics/physics.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/check_goal.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/graph.hpp"
#include "tracks/quad.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "tracks/track_sector.hpp"
#include "utils/constants.hpp"
#include "utils/string_utils.hpp"
#include <IMeshSceneNode.h>
#include <numeric>
#include <string>
//=============================================================================
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
CheckManager* cm = Track::getCurrentTrack()->getCheckManager();
unsigned int n = cm->getCheckStructureCount();
for (unsigned int i = 0; i < n; i++)
{
CheckGoal* goal = dynamic_cast<CheckGoal*>
(cm->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(KartTeam 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 == KART_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(KartTeam 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, reverse ? m_radius*2 : -m_radius*2));
// 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 == KART_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
void resetCheckGoal(const Track* t)
{
m_red_check_goal->reset(*t);
m_blue_check_goal->reset(*t);
}
}; // BallGoalData
//-----------------------------------------------------------------------------
/** Constructor. Sets up the clock mode etc.
*/
SoccerWorld::SoccerWorld() : WorldWithRank()
{
if (RaceManager::get()->hasTimeTarget())
{
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN,
RaceManager::get()->getTimeTarget());
}
else
{
WorldStatus::setClockMode(CLOCK_CHRONO);
}
m_frame_count = 0;
m_use_highscores = false;
m_red_ai = 0;
m_blue_ai = 0;
m_ball_track_sector = NULL;
m_bgd.reset(new BallGoalData());
} // SoccerWorld
//-----------------------------------------------------------------------------
/** The destructor frees all data structures.
*/
SoccerWorld::~SoccerWorld()
{
m_goal_sound->deleteSFX();
delete m_ball_track_sector;
m_ball_track_sector = NULL;
} // ~SoccerWorld
//-----------------------------------------------------------------------------
/** Initializes the soccer world. It sets up the data structure
* to keep track of points etc. for each kart.
*/
void SoccerWorld::init()
{
m_kart_team_map.clear();
m_kart_position_map.clear();
WorldWithRank::init();
m_display_rank = false;
m_ball_hitter = -1;
m_ball = NULL;
m_ball_body = NULL;
m_goal_target = RaceManager::get()->getMaxGoal();
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
Track *track = Track::getCurrentTrack();
if (track->hasNavMesh())
{
// Init track sector for ball if navmesh is found
m_ball_track_sector = new TrackSector();
}
TrackObjectManager* tom = track->getTrackObjectManager();
assert(tom);
PtrVector<TrackObject>& objects = tom->getObjects();
for (unsigned int i = 0; i < objects.size(); i++)
{
TrackObject* obj = objects.get(i);
if(!obj->isSoccerBall())
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(bool restart)
{
WorldWithRank::reset(restart);
if (RaceManager::get()->hasTimeTarget())
{
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN,
RaceManager::get()->getTimeTarget());
}
else
{
WorldStatus::setClockMode(CLOCK_CHRONO);
}
m_count_down_reached_zero = false;
m_red_scorers.clear();
m_blue_scorers.clear();
m_ball_hitter = -1;
m_red_kdm.clear();
m_blue_kdm.clear();
m_ball_heading = 0.0f;
m_ball_invalid_timer = 0;
m_goal_transforms.clear();
m_goal_transforms.resize(m_karts.size());
if (m_goal_sound != NULL &&
m_goal_sound->getStatus() == SFXBase::SFX_PLAYING)
{
m_goal_sound->stop();
}
if (Track::getCurrentTrack()->hasNavMesh())
{
m_ball_track_sector->reset();
}
m_reset_ball_ticks = -1;
m_ball->reset();
m_bgd->reset();
m_ticks_back_to_own_goal = -1;
m_ball->setEnabled(false);
// Make the player kart in profiling mode up
// ie make this kart less likely to affect gaming result
if (UserConfigParams::m_arena_ai_stats)
getKart(8)->flyUp();
} // reset
//-----------------------------------------------------------------------------
void SoccerWorld::onGo()
{
m_ball->setEnabled(true);
m_ball->reset();
WorldWithRank::onGo();
} // onGo
//-----------------------------------------------------------------------------
void SoccerWorld::terminateRace()
{
const unsigned int kart_amount = getNumKarts();
for (unsigned int i = 0; i < kart_amount ; i++)
{
// Soccer mode use goal for race result, and each goal time is
// handled by handlePlayerGoalFromServer already
m_karts[i]->finishedRace(0.0f, true/*from_server*/);
} // i<kart_amount
WorldWithRank::terminateRace();
} // terminateRace
//-----------------------------------------------------------------------------
/** Returns the internal identifier for this race.
*/
const std::string& SoccerWorld::getIdent() const
{
return IDENT_SOCCER;
} // getIdent
//-----------------------------------------------------------------------------
/** Update the world and the track.
* \param ticks Physics time steps - should be 1.
*/
void SoccerWorld::update(int ticks)
{
updateBallPosition(ticks);
if (Track::getCurrentTrack()->hasNavMesh())
{
updateSectorForKarts();
if (!NetworkConfig::get()->isNetworking())
updateAIData();
}
WorldWithRank::update(ticks);
WorldWithRank::updateTrack(ticks);
std::vector<int> red_id, blue_id;
for (unsigned int i = 0; i < m_karts.size(); i++)
{
if (getKartTeam(i) == KART_TEAM_RED)
red_id.push_back(i);
else
blue_id.push_back(i);
if (isGoalPhase())
{
auto& kart = m_karts[i];
if (kart->isEliminated())
continue;
if (kart->getKartAnimation())
{
AbstractKartAnimation* ka = kart->getKartAnimation();
kart->setKartAnimation(NULL);
delete ka;
}
kart->getBody()->setLinearVelocity(Vec3(0.0f));
kart->getBody()->setAngularVelocity(Vec3(0.0f));
kart->getBody()->proceedToTransform(m_goal_transforms[i]);
kart->setTrans(m_goal_transforms[i]);
}
}
if (isGoalPhase() &&
m_ticks_back_to_own_goal - getTicksSinceStart() == 1 && !isRaceOver())
{
// Reset all karts and ball
resetKartsToSelfGoals();
if (UserConfigParams::m_arena_ai_stats)
getKart(8)->flyUp();
}
if (UserConfigParams::m_arena_ai_stats)
m_frame_count++;
// We only use kart position for spliting team in race gui drawing
beginSetKartPositions();
int pos = 1;
if (!Track::getCurrentTrack()->getMinimapInvert())
std::swap(red_id, blue_id);
for (int id : red_id)
setKartPosition(id, pos++);
for (int id : blue_id)
setKartPosition(id, pos++);
endSetKartPositions();
} // update
//-----------------------------------------------------------------------------
void SoccerWorld::onCheckGoalTriggered(bool first_goal)
{
if (isRaceOver() || isStartPhase() ||
(NetworkConfig::get()->isNetworking() &&
NetworkConfig::get()->isClient()))
return;
m_ticks_back_to_own_goal = getTicksSinceStart() +
stk_config->time2Ticks(3.0f);
m_goal_sound->play();
m_ball->reset();
m_ball->setEnabled(false);
if (m_ball_hitter != -1)
{
if (UserConfigParams::m_arena_ai_stats)
{
const int elapsed_frame = m_goal_frame.empty() ? 0 :
std::accumulate(m_goal_frame.begin(), m_goal_frame.end(), 0);
m_goal_frame.push_back(m_frame_count - elapsed_frame);
}
ScorerData sd = {};
sd.m_id = m_ball_hitter;
sd.m_correct_goal = isCorrectGoal(m_ball_hitter, first_goal);
sd.m_kart = getKart(m_ball_hitter)->getIdent();
sd.m_player = getKart(m_ball_hitter)->getController()
->getName(false/*include_handicap_string*/);
sd.m_handicap_level = getKart(m_ball_hitter)->getHandicap();
if (RaceManager::get()->getKartGlobalPlayerId(m_ball_hitter) > -1)
{
sd.m_country_code =
RaceManager::get()->getKartInfo(m_ball_hitter).getCountryCode();
}
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*/);
}
if (first_goal)
{
if (RaceManager::get()->hasTimeTarget())
{
sd.m_time = RaceManager::get()->getTimeTarget() - getTime();
}
else
sd.m_time = getTime();
// Notice: true first_goal means it's blue goal being shoot,
// so red team can score
m_red_scorers.push_back(sd);
}
else
{
if (RaceManager::get()->hasTimeTarget())
{
sd.m_time = RaceManager::get()->getTimeTarget() - getTime();
}
else
sd.m_time = getTime();
m_blue_scorers.push_back(sd);
}
if (NetworkConfig::get()->isNetworking() &&
NetworkConfig::get()->isServer())
{
NetworkString p(PROTOCOL_GAME_EVENTS);
p.setSynchronous(true);
p.addUInt8(GameEventsProtocol::GE_PLAYER_GOAL)
.addUInt8((uint8_t)sd.m_id).addUInt8(sd.m_correct_goal)
.addUInt8(first_goal).addFloat(sd.m_time)
.addTime(m_ticks_back_to_own_goal)
.encodeString(sd.m_kart).encodeString(sd.m_player);
// Added in 1.1, add missing handicap info and country code
NetworkString p_1_1 = p;
p_1_1.encodeString(sd.m_country_code)
.addUInt8(sd.m_handicap_level);
auto peers = STKHost::get()->getPeers();
for (auto& peer : peers)
{
if (peer->isValidated() && !peer->isWaitingForGame())
{
if (peer->getClientCapabilities().find("soccer_fixes") !=
peer->getClientCapabilities().end())
{
peer->sendPacket(&p_1_1, true/*reliable*/);
}
else
{
peer->sendPacket(&p, true/*reliable*/);
}
}
}
}
}
for (unsigned i = 0; i < m_karts.size(); i++)
{
auto& kart = m_karts[i];
kart->getBody()->setLinearVelocity(Vec3(0.0f));
kart->getBody()->setAngularVelocity(Vec3(0.0f));
m_goal_transforms[i] = kart->getBody()->getWorldTransform();
}
} // onCheckGoalTriggered
//-----------------------------------------------------------------------------
void SoccerWorld::handleResetBallFromServer(const NetworkString& ns)
{
int ticks_now = World::getWorld()->getTicksSinceStart();
int ticks_back_to_own_goal = ns.getTime();
if (ticks_now >= ticks_back_to_own_goal)
{
Log::warn("SoccerWorld", "Server ticks %d is too close to client ticks "
"%d when reset player", ticks_back_to_own_goal, ticks_now);
return;
}
m_reset_ball_ticks = ticks_back_to_own_goal;
} // handleResetBallFromServer
//-----------------------------------------------------------------------------
void SoccerWorld::handlePlayerGoalFromServer(const NetworkString& ns)
{
ScorerData sd = {};
sd.m_id = ns.getUInt8();
sd.m_correct_goal = ns.getUInt8() == 1;
bool first_goal = ns.getUInt8() == 1;
sd.m_time = ns.getFloat();
int ticks_now = World::getWorld()->getTicksSinceStart();
int ticks_back_to_own_goal = ns.getTime();
ns.decodeString(&sd.m_kart);
ns.decodeStringW(&sd.m_player);
// Added in 1.1, add missing handicap info and country code
if (NetworkConfig::get()->getServerCapabilities().find("soccer_fixes")
!= NetworkConfig::get()->getServerCapabilities().end())
{
ns.decodeString(&sd.m_country_code);
sd.m_handicap_level = (HandicapLevel)ns.getUInt8();
}
if (first_goal)
{
m_red_scorers.push_back(sd);
}
else
{
m_blue_scorers.push_back(sd);
}
if (ticks_now >= ticks_back_to_own_goal && !isStartPhase())
{
Log::warn("SoccerWorld", "Server ticks %d is too close to client ticks "
"%d when goal", ticks_back_to_own_goal, ticks_now);
return;
}
m_ticks_back_to_own_goal = ticks_back_to_own_goal;
for (unsigned i = 0; i < m_karts.size(); i++)
{
auto& kart = m_karts[i];
btTransform transform_now = kart->getBody()->getWorldTransform();
kart->getBody()->setLinearVelocity(Vec3(0.0f));
kart->getBody()->setAngularVelocity(Vec3(0.0f));
kart->getBody()->proceedToTransform(transform_now);
kart->setTrans(transform_now);
m_goal_transforms[i] = transform_now;
}
m_ball->reset();
m_ball->setEnabled(false);
// Ignore the rest in live join
if (isStartPhase())
return;
if (sd.m_correct_goal)
{
m_karts[sd.m_id]->getKartModel()
->setAnimation(KartModel::AF_WIN_START, true/* play_non_loop*/);
}
else if (!sd.m_correct_goal)
{
m_karts[sd.m_id]->getKartModel()
->setAnimation(KartModel::AF_LOSE_START, true/* play_non_loop*/);
}
m_goal_sound->play();
} // handlePlayerGoalFromServer
//-----------------------------------------------------------------------------
void SoccerWorld::resetKartsToSelfGoals()
{
m_ball->setEnabled(true);
m_ball->reset();
m_bgd->resetCheckGoal(Track::getCurrentTrack());
for (unsigned i = 0; i < m_karts.size(); i++)
{
auto& kart = m_karts[i];
if (kart->isEliminated())
continue;
kart->getBody()->setLinearVelocity(Vec3(0.0f));
kart->getBody()->setAngularVelocity(Vec3(0.0f));
unsigned index = m_kart_position_map.at(kart->getWorldKartId());
btTransform t = Track::getCurrentTrack()->getStartTransform(index);
moveKartTo(kart.get(), t);
}
} // resetKartsToSelfGoals
//-----------------------------------------------------------------------------
/** Sets the last kart that hit the ball, to be able to
* identify the scorer later.
*/
void SoccerWorld::setBallHitter(unsigned int kart_id)
{
m_ball_hitter = kart_id;
} // setBallHitter
//-----------------------------------------------------------------------------
/** The soccer game is over if time up or either team wins.
*/
bool SoccerWorld::isRaceOver()
{
if (m_unfair_team)
return true;
if (RaceManager::get()->hasTimeTarget())
{
return m_count_down_reached_zero;
}
// One team scored the target goals ...
else
{
return (getScore(KART_TEAM_BLUE) >= m_goal_target ||
getScore(KART_TEAM_RED) >= m_goal_target);
}
} // isRaceOver
//-----------------------------------------------------------------------------
/** Called when the match time ends.
*/
void SoccerWorld::countdownReachedZero()
{
// Prevent negative time in network soccer when finishing
m_time_ticks = 0;
m_time = 0.0f;
m_count_down_reached_zero = true;
} // countdownReachedZero
//-----------------------------------------------------------------------------
bool SoccerWorld::getKartSoccerResult(unsigned int kart_id) const
{
if (m_red_scorers.size() == m_blue_scorers.size()) return true;
bool red_win = m_red_scorers.size() > m_blue_scorers.size();
KartTeam team = getKartTeam(kart_id);
if ((red_win && team == KART_TEAM_RED) ||
(!red_win && team == KART_TEAM_BLUE))
return true;
else
return false;
} // getKartSoccerResult
//-----------------------------------------------------------------------------
/** Localize the ball on the navigation mesh.
*/
void SoccerWorld::updateBallPosition(int ticks)
{
if (isRaceOver()) return;
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 (Track::getCurrentTrack()->hasNavMesh())
{
m_ball_track_sector
->update(getBallPosition(), true/*ignore_vertical*/);
bool is_client = NetworkConfig::get()->isNetworking() &&
NetworkConfig::get()->isClient();
bool is_server = NetworkConfig::get()->isNetworking() &&
NetworkConfig::get()->isServer();
if (!is_client && getTicksSinceStart() > m_reset_ball_ticks &&
!m_ball_track_sector->isOnRoad())
{
m_ball_invalid_timer += ticks;
// Reset the ball and karts if out of navmesh after 2 seconds
if (m_ball_invalid_timer >= stk_config->time2Ticks(2.0f))
{
if (is_server)
{
// Reset the ball 2 seconds in the future to make sure it's
// after all clients time
m_reset_ball_ticks = getTicksSinceStart() +
stk_config->time2Ticks(2.0f);
NetworkString p(PROTOCOL_GAME_EVENTS);
p.setSynchronous(true);
p.addUInt8(GameEventsProtocol::GE_RESET_BALL)
.addTime(m_reset_ball_ticks);
STKHost::get()->sendPacketToAllPeers(&p, true);
}
else if (!NetworkConfig::get()->isNetworking())
{
m_ball_invalid_timer = 0;
resetKartsToSelfGoals();
if (UserConfigParams::m_arena_ai_stats)
getKart(8)->flyUp();
}
}
}
else
m_ball_invalid_timer = 0;
if (m_reset_ball_ticks == World::getWorld()->getTicksSinceStart())
{
resetKartsToSelfGoals();
}
}
} // updateBallPosition
//-----------------------------------------------------------------------------
int SoccerWorld::getBallNode() const
{
assert(m_ball_track_sector != NULL);
return m_ball_track_sector->getCurrentGraphNode();
} // getBallNode
//-----------------------------------------------------------------------------
bool SoccerWorld::isCorrectGoal(unsigned int kart_id, bool first_goal) const
{
KartTeam team = getKartTeam(kart_id);
if (first_goal)
{
if (team == KART_TEAM_RED)
return true;
}
else if (!first_goal)
{
if (team == KART_TEAM_BLUE)
return true;
}
return false;
} // isCorrectGoal
//-----------------------------------------------------------------------------
void SoccerWorld::updateAIData()
{
if (isRaceOver()) return;
// Fill the kart distance map
m_red_kdm.clear();
m_blue_kdm.clear();
for (unsigned int i = 0; i < m_karts.size(); ++i)
{
if (UserConfigParams::m_arena_ai_stats &&
m_karts[i]->getController()->isPlayerController())
continue;
if (getKartTeam(m_karts[i]->getWorldKartId()) == KART_TEAM_RED)
{
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()));
}
}
// 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());
// Fill Ball and goals data
m_bgd->updateBallAndGoal(getBallPosition(), getBallHeading());
} // updateAIData
//-----------------------------------------------------------------------------
int SoccerWorld::getAttacker(KartTeam team) const
{
if (team == KART_TEAM_BLUE && m_blue_kdm.size() > 1)
{
for (unsigned int i = 1; i < m_blue_kdm.size(); i++)
{
// 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 == KART_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;
}
}
// No attacker
return -1;
} // getAttacker
//-----------------------------------------------------------------------------
unsigned int SoccerWorld::getRescuePositionIndex(AbstractKart *kart)
{
if (!Track::getCurrentTrack()->hasNavMesh())
return m_kart_position_map.at(kart->getWorldKartId());
int last_valid_node =
getTrackSector(kart->getWorldKartId())->getLastValidGraphNode();
if (last_valid_node >= 0)
return last_valid_node;
Log::warn("SoccerWorld", "Missing last valid node for rescuing");
return 0;
} // getRescuePositionIndex
//-----------------------------------------------------------------------------
btTransform SoccerWorld::getRescueTransform(unsigned int rescue_pos) const
{
if (!Track::getCurrentTrack()->hasNavMesh())
return WorldWithRank::getRescueTransform(rescue_pos);
const Vec3 &xyz = Graph::get()->getQuad(rescue_pos)->getCenter();
const Vec3 &normal = Graph::get()->getQuad(rescue_pos)->getNormal();
btTransform pos;
pos.setOrigin(xyz);
btQuaternion q1 = shortestArcQuat(Vec3(0.0f, 1.0f, 0.0f), normal);
pos.setRotation(q1);
return pos;
} // getRescueTransform
//-----------------------------------------------------------------------------
void SoccerWorld::enterRaceOverState()
{
WorldWithRank::enterRaceOverState();
if (UserConfigParams::m_arena_ai_stats)
{
Log::verbose("Soccer AI profiling", "Total frames elapsed for a team"
" to win with 30 goals: %d", m_frame_count);
// Goal time statistics
std::sort(m_goal_frame.begin(), m_goal_frame.end());
const int mean = std::accumulate(m_goal_frame.begin(),
m_goal_frame.end(), 0) / (int)m_goal_frame.size();
// Prevent overflow if there is a large frame in vector
double squared_sum = 0;
for (const int &i : m_goal_frame)
squared_sum = squared_sum + (double(i - mean) * double(i - mean));
// Use sample st. deviation (n-1) as the profiling can't be run forever
const int stdev = int(sqrt(squared_sum / (m_goal_frame.size() - 1)));
int median = 0;
if (m_goal_frame.size() % 2 == 0)
{
median = (m_goal_frame[m_goal_frame.size() / 2 - 1] +
m_goal_frame[m_goal_frame.size() / 2]) / 2;
}
else
{
median = m_goal_frame[m_goal_frame.size() / 2];
}
Log::verbose("Soccer AI profiling", "Frames elapsed for each goal:"
" min: %d max: %d mean: %d median: %d standard deviation: %d",
m_goal_frame.front(), m_goal_frame.back(), mean, median, stdev);
// Goal calculation
int red_own_goal = 0;
int blue_own_goal = 0;
for (unsigned i = 0; i < m_red_scorers.size(); i++)
{
// Notice: if a team has own goal, the score will end up in the
// opposite team
if (!m_red_scorers[i].m_correct_goal)
blue_own_goal++;
}
for (unsigned i = 0; i < m_blue_scorers.size(); i++)
{
if (!m_blue_scorers[i].m_correct_goal)
red_own_goal++;
}
int red_goal = ((int(m_red_scorers.size()) - blue_own_goal) >= 0 ?
(int)m_red_scorers.size() - blue_own_goal : 0);
int blue_goal = ((int(m_blue_scorers.size()) - red_own_goal) >= 0 ?
(int)m_blue_scorers.size() - red_own_goal : 0);
Log::verbose("Soccer AI profiling", "Red goal: %d, Red own goal: %d,"
"Blue goal: %d, Blue own goal: %d", red_goal, red_own_goal,
blue_goal, blue_own_goal);
if (getScore(KART_TEAM_BLUE) >= m_goal_target)
Log::verbose("Soccer AI profiling", "Blue team wins");
else
Log::verbose("Soccer AI profiling", "Red team wins");
delete this;
main_loop->abort();
}
} // enterRaceOverState
// ----------------------------------------------------------------------------
void SoccerWorld::saveCompleteState(BareNetworkString* bns, STKPeer* peer)
{
const unsigned red_scorers = (unsigned)m_red_scorers.size();
bns->addUInt32(red_scorers);
for (unsigned i = 0; i < red_scorers; i++)
{
bns->addUInt8((uint8_t)m_red_scorers[i].m_id)
.addUInt8(m_red_scorers[i].m_correct_goal)
.addFloat(m_red_scorers[i].m_time)
.encodeString(m_red_scorers[i].m_kart)
.encodeString(m_red_scorers[i].m_player);
if (peer->getClientCapabilities().find("soccer_fixes") !=
peer->getClientCapabilities().end())
{
bns->encodeString(m_red_scorers[i].m_country_code)
.addUInt8(m_red_scorers[i].m_handicap_level);
}
}
const unsigned blue_scorers = (unsigned)m_blue_scorers.size();
bns->addUInt32(blue_scorers);
for (unsigned i = 0; i < blue_scorers; i++)
{
bns->addUInt8((uint8_t)m_blue_scorers[i].m_id)
.addUInt8(m_blue_scorers[i].m_correct_goal)
.addFloat(m_blue_scorers[i].m_time)
.encodeString(m_blue_scorers[i].m_kart)
.encodeString(m_blue_scorers[i].m_player);
if (peer->getClientCapabilities().find("soccer_fixes") !=
peer->getClientCapabilities().end())
{
bns->encodeString(m_blue_scorers[i].m_country_code)
.addUInt8(m_blue_scorers[i].m_handicap_level);
}
}
bns->addTime(m_reset_ball_ticks).addTime(m_ticks_back_to_own_goal);
} // saveCompleteState
// ----------------------------------------------------------------------------
void SoccerWorld::restoreCompleteState(const BareNetworkString& b)
{
m_red_scorers.clear();
m_blue_scorers.clear();
const unsigned red_size = b.getUInt32();
for (unsigned i = 0; i < red_size; i++)
{
ScorerData sd;
sd.m_id = b.getUInt8();
sd.m_correct_goal = b.getUInt8() == 1;
sd.m_time = b.getFloat();
b.decodeString(&sd.m_kart);
b.decodeStringW(&sd.m_player);
if (NetworkConfig::get()->getServerCapabilities().find("soccer_fixes")
!= NetworkConfig::get()->getServerCapabilities().end())
{
b.decodeString(&sd.m_country_code);
sd.m_handicap_level = (HandicapLevel)b.getUInt8();
}
m_red_scorers.push_back(sd);
}
const unsigned blue_size = b.getUInt32();
for (unsigned i = 0; i < blue_size; i++)
{
ScorerData sd;
sd.m_id = b.getUInt8();
sd.m_correct_goal = b.getUInt8() == 1;
sd.m_time = b.getFloat();
b.decodeString(&sd.m_kart);
b.decodeStringW(&sd.m_player);
if (NetworkConfig::get()->getServerCapabilities().find("soccer_fixes")
!= NetworkConfig::get()->getServerCapabilities().end())
{
b.decodeString(&sd.m_country_code);
sd.m_handicap_level = (HandicapLevel)b.getUInt8();
}
m_blue_scorers.push_back(sd);
}
m_reset_ball_ticks = b.getTime();
m_ticks_back_to_own_goal = b.getTime();
} // restoreCompleteState
// ----------------------------------------------------------------------------
float SoccerWorld::getBallDiameter() const
{
return m_bgd->getDiameter();
} // getBallDiameter
// ----------------------------------------------------------------------------
bool SoccerWorld::ballApproachingGoal(KartTeam team) const
{
return m_bgd->isApproachingGoal(team);
} // ballApproachingGoal
// ----------------------------------------------------------------------------
Vec3 SoccerWorld::getBallAimPosition(KartTeam team, bool reverse) const
{
return m_bgd->getAimPosition(team, reverse);
} // getBallAimPosition
// ----------------------------------------------------------------------------
/** Returns the data to display in the race gui.
*/
void SoccerWorld::getKartsDisplayInfo(
std::vector<RaceGUIBase::KartIconDisplayInfo> *info)
{
const unsigned int kart_amount = getNumKarts();
for (unsigned int i = 0; i < kart_amount ; i++)
{
RaceGUIBase::KartIconDisplayInfo& rank_info = (*info)[i];
rank_info.lap = -1;
rank_info.m_outlined_font = true;
rank_info.m_color = getKartTeam(i) == KART_TEAM_RED ?
video::SColor(255, 255, 0, 0) : video::SColor(255, 0, 0, 255);
rank_info.m_text = getKart(i)->getController()->getName();
if (RaceManager::get()->getKartGlobalPlayerId(i) > -1)
{
const core::stringw& flag = StringUtils::getCountryFlag(
RaceManager::get()->getKartInfo(i).getCountryCode());
if (!flag.empty())
{
rank_info.m_text += L" ";
rank_info.m_text += flag;
}
}
}
} // getKartsDisplayInfo