Merge remote-tracking branch 'origin/master' into support_nw_splitscreen

This commit is contained in:
hiker 2016-02-09 17:40:59 +11:00
commit ee320588d5
61 changed files with 2423 additions and 1567 deletions

View File

@ -3,44 +3,45 @@
# Configuration manual:
# http://docs.travis-ci.com/user/build-configuration/
#
sudo: false
language: cpp
compiler:
- gcc
# - clang
#branches:
# only:
# - master
before_install:
# Update repos
- sudo apt-get update -qq
# Install dependencies
- sudo apt-get install build-essential libogg-dev libvorbis-dev libopenal-dev libxxf86vm-dev libcurl4-openssl-dev libfribidi-dev libbluetooth-dev
# Install mesa from an other repo (a newer version is required). Quantal is not supported anymore, saucy is only supported till July 2014,
# so we try to use trusty (precise which is what traiv uses a too old mesa version which doesn't link)
- sudo apt-add-repository "deb http://archive.ubuntu.com/ubuntu trusty main restricted"
- sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 3B4FE6ACC0B21F32
- sudo apt-get update -qq
- sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev libglew-dev cmake
- clang
matrix:
fast_finish: true
env:
matrix:
- BUILD_TYPE="Debug"
- BUILD_TYPE="Release"
addons:
apt:
packages:
- build-essential
- libogg-dev
- libvorbis-dev
- libopenal-dev
- libxxf86vm-dev
- libcurl4-openssl-dev
- libfribidi-dev
- libbluetooth-dev
- libgl1-mesa-dev
- libglu1-mesa-dev
- libglew-dev
- cmake
before_script:
- export THREADS=`nproc`
# Unfortunately using all threads crashes g++: "g++: internal compiler error: Killed (program cc1plus)"
- export THREADS=4
- export THREADS=$((`nproc` + 1))
- echo "THREADS = $THREADS"
- free -mt
script:
# First a debug build:
- mkdir build-debug
- cd build-debug
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DCHECK_ASSETS=off
- make VERBOSE=1 -j $THREADS
# Then a release build:
- cd ..
- mkdir build-release
- cd build-release
- cmake .. -DCMAKE_BUILD_TYPE=Release -DCHECK_ASSETS=off
- mkdir "build"
- cd "build"
- cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCHECK_ASSETS=off
- make VERBOSE=1 -j $THREADS
notifications:

View File

@ -215,7 +215,7 @@ endif()
# Set some compiler options
if(UNIX OR MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unused-function")
endif()
if(MINGW AND CMAKE_BUILD_TYPE MATCHES Release)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -6,38 +6,39 @@
<header width="80%" text="Race Setup" align="center" text_align="center" />
<spacer height="25" width="25"/>
<spacer height="10" width="25"/>
<div layout="horizontal-row" width="fit" height="35" align="left">
<bright proportion="1" height="100%"
I18N="In soccer setup screen" text="Number of goals to win" text_align="left" />
<spacer width="50" height="25"/>
<spacer width="10" height="25"/>
<spinner id="goalamount" width="200" height="90%" min_value="1" max_value="10"/>
</div>
<div layout="horizontal-row" width="fit" height="35" align="left">
<bright proportion="1" height="100%"
I18N="In soccer setup screen" text="Maximum time (min.)" text_align="left" />
<spacer width="50" height ="25"/>
<spacer width="10" height ="25"/>
<spinner id="timeamount" width="200" height="90%" min_value="1" max_value="15"/>
</div>
<div layout="horizontal-row" width="fit" height="35" align="left">
<bright proportion="1" height="100%"
I18N="In soccer setup screen" text="Game type (Goals limit / Time limit)" text_align="left" />
<spacer width="50" height="25"/>
<spacer width="10" height="25"/>
<checkbox id="time_enabled"/>
</div>
<spacer height="40" width="25"/>
<spacer height="10" width="25"/>
<bright height="15" width="25" I18N="In soccer setup screen" text="Use left/right to choose your team and press fire" text_align="center" align="center" />
<bubble height="fit" width="100%" id="lblLeftRight" I18N="In soccer setup screen" text="Use left/right to choose your team and press fire" word_wrap="true" text_align="center"/>
<div id="central_div" layout="horizontal-row" width="100%" proportion="1" align="center">
<roundedbox y="5%" width="100%" layout="horizontal-row" height="100%">
<roundedbox y="5%" width="100%" layout="horizontal-row" height="95%">
<!-- Content is added programmatically -->
</roundedbox>
<header id="vs" text="VS"/> <!-- Layout is done programmatically -->
<icon id="red_team" I18N="In soccer setup screen" text="Red Team" icon="gui/soccer_ball_red.png" width="font" height="font"/> <!-- Layout is done programmatically -->
<icon id="blue_team" I18N="In soccer setup screen" text="Blue Team" icon="gui/soccer_ball_blue.png" width="font" height="font"/>
</div>
<button id="continue" I18N="In soccer setup screen" text="Continue" align="center" width="60%"/>

View File

@ -101,6 +101,8 @@
w-multi=" 0 30 30 100 60 0 0 0 0 0" />
<battle w="10 30 60 0 0 10 30 0 0 0"
w-multi=" 0 0 5 0 0 0 0 0 0 0" />
<soccer w=" 0 30 60 0 0 10 30 0 0 0"
w-multi=" 0 0 5 0 0 0 0 0 0 0" />
<tuto w=" 0 0 0 0 0 0 0 0 0 0"
w-multi=" 0 0 100 0 0 0 0 0 0 0" />

View File

@ -1,5 +1,5 @@
# Modify this file to change the last-modified date when you add/remove a file.
# This will then trigger a new cmake run automatically.
# This will then trigger a new cmake run automatically.
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")

View File

@ -335,9 +335,15 @@ namespace UserConfigParams
PARAM_PREFIX IntUserConfigParam m_num_goals
PARAM_DEFAULT( IntUserConfigParam(3, "numgoals",
&m_race_setup_group, "Default number of goals in soccer mode.") );
PARAM_PREFIX IntUserConfigParam m_soccer_default_team
PARAM_DEFAULT( IntUserConfigParam(0, "soccer-default-team",
&m_race_setup_group, "Default team in soccer mode for single player.") );
PARAM_PREFIX IntUserConfigParam m_soccer_time_limit
PARAM_DEFAULT( IntUserConfigParam(3, "soccer-time-limit",
&m_race_setup_group, "Limit in soccer time mode.") );
&m_race_setup_group, "Time limit in soccer mode.") );
PARAM_PREFIX BoolUserConfigParam m_soccer_use_time_limit
PARAM_DEFAULT( BoolUserConfigParam(false, "soccer-use-time-limit",
&m_race_setup_group, "Enable time limit in soccer mode.") );
PARAM_PREFIX IntUserConfigParam m_difficulty
PARAM_DEFAULT( IntUserConfigParam(0, "difficulty",
&m_race_setup_group,

View File

@ -65,10 +65,18 @@ MaterialManager::~MaterialManager()
//-----------------------------------------------------------------------------
Material* MaterialManager::getMaterialFor(video::ITexture* t,
scene::IMeshBuffer *mb)
scene::IMeshBuffer *mb)
{
return getMaterialFor(t, mb->getMaterial().MaterialType);
}
//-----------------------------------------------------------------------------
Material* MaterialManager::getMaterialFor(video::ITexture* t,
video::E_MATERIAL_TYPE material_type)
{
if (t == NULL)
return getDefaultMaterial(mb->getMaterial().MaterialType);
return getDefaultMaterial(material_type);
core::stringc img_path = core::stringc(t->getName());
const std::string image = StringUtils::getBasename(img_path.c_str());
@ -94,7 +102,7 @@ Material* MaterialManager::getMaterialFor(video::ITexture* t,
} // for i
}
return getDefaultMaterial(mb->getMaterial().MaterialType);
return getDefaultMaterial(material_type);
}
//-----------------------------------------------------------------------------

View File

@ -59,6 +59,8 @@ public:
void loadMaterial ();
Material* getMaterialFor(video::ITexture* t,
scene::IMeshBuffer *mb);
Material* getMaterialFor(video::ITexture* t,
video::E_MATERIAL_TYPE material_type);
void setAllMaterialFlags(video::ITexture* t,
scene::IMeshBuffer *mb);
void adjustForFog(video::ITexture* t,

View File

@ -19,6 +19,8 @@
#include "graphics/glwrap.hpp"
#include "graphics/irr_driver.hpp"
#include "graphics/material.hpp"
#include "graphics/material_manager.hpp"
#include "graphics/shaders.hpp"
#include "graphics/shared_gpu_objects.hpp"
@ -92,12 +94,20 @@ void STKBillboard::render()
{
if (irr_driver->getPhase() != TRANSPARENT_PASS)
return;
core::vector3df pos = getAbsolutePosition();
glBindVertexArray(billboardvao);
video::ITexture *tex = Material.getTexture(0);
if (!tex )
return;
::Material* material = material_manager->getMaterialFor(tex,
video::E_MATERIAL_TYPE::EMT_ONETEXTURE_BLEND);
if (material->getShaderType() == Material::SHADERTYPE_ADDITIVE)
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
else
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
compressTexture(tex, true, true);
GLuint texid = getTextureGLuint(tex);
BillboardShader::getInstance()->use();

View File

@ -483,6 +483,7 @@ std::set<wchar_t> ScalableFont::getPreloadCharacters(const GUIEngine::TTFLoading
for (u32 i = 47; i < 59; ++i)
preload_char.insert((wchar_t)i); //Include chars used by timer and laps count only
preload_char.insert((wchar_t)120); //Used when displaying multiple items, e.g. 6x
break;
case T_BOLD:
preload_char = translations->getCurrentAllChar(); //Loading unique characters

View File

@ -69,7 +69,7 @@ namespace GUIEngine
void setText(const irr::core::stringw &s);
virtual int getHeightNeededAroundLabel() const { return 10; }
};
}

View File

@ -35,6 +35,7 @@
#include "karts/abstract_kart.hpp"
#include "karts/explosion_animation.hpp"
#include "modes/world.hpp"
#include "modes/soccer_world.hpp"
#include "physics/physics.hpp"
#include "tracks/track.hpp"
#include "utils/constants.hpp"
@ -226,6 +227,16 @@ void Flyable::getClosestKart(const AbstractKart **minKart,
if(kart->isEliminated() || kart == m_owner ||
kart->isInvulnerable() ||
kart->getKartAnimation() ) continue;
const SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
if (sw)
{
// Don't hit teammates in soccer world
if (sw->getKartTeam(kart->getWorldKartId()) == sw
->getKartTeam(m_owner->getWorldKartId()))
continue;
}
btTransform t=kart->getTrans();
Vec3 delta = t.getOrigin()-trans_projectile.getOrigin();
@ -461,6 +472,15 @@ void Flyable::explode(AbstractKart *kart_hit, PhysicalObject *object,
{
AbstractKart *kart = world->getKart(i);
const SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
if (sw)
{
// Don't explode teammates in soccer world
if (sw->getKartTeam(kart->getWorldKartId()) == sw
->getKartTeam(m_owner->getWorldKartId()))
continue;
}
// If no secondary hits should be done, only hit the
// direct hit kart.
if(!secondary_hits && kart!=kart_hit)

View File

@ -136,6 +136,7 @@ void PowerupManager::loadAllPowerups()
loadWeights(*root, "end33", POSITION_END33 );
loadWeights(*root, "last" , POSITION_LAST );
loadWeights(*root, "battle" , POSITION_BATTLE_MODE);
loadWeights(*root, "soccer" , POSITION_SOCCER_MODE);
loadWeights(*root, "tuto", POSITION_TUTORIAL_MODE);
delete root;
@ -273,7 +274,8 @@ void PowerupManager::updateWeightsForRace(unsigned int num_karts)
{
m_position_to_class.clear();
// In battle mode no positions exist, so use only position 1
unsigned int end_position = (race_manager->isBattleMode()) ? 1 : num_karts;
unsigned int end_position = (race_manager->isBattleMode() ||
race_manager->isSoccerMode()) ? 1 : num_karts;
for(unsigned int position =1; position <= end_position; position++)
{
// Set up the mapping of position to position class:
@ -322,6 +324,7 @@ PowerupManager::PositionClass
unsigned int position)
{
if(race_manager->isBattleMode()) return POSITION_BATTLE_MODE;
if(race_manager->isSoccerMode()) return POSITION_SOCCER_MODE;
if(race_manager->isTutorialMode()) return POSITION_TUTORIAL_MODE;
if(position==1) return POSITION_FIRST;
if(position==num_karts) return POSITION_LAST;
@ -354,6 +357,7 @@ PowerupManager::PowerupType PowerupManager::getRandomPowerup(unsigned int pos,
// Positions start with 1, while the index starts with 0 - so subtract 1
PositionClass pos_class =
(race_manager->isBattleMode() ? POSITION_BATTLE_MODE :
race_manager->isSoccerMode() ? POSITION_SOCCER_MODE :
(race_manager->isTutorialMode() ? POSITION_TUTORIAL_MODE :
m_position_to_class[pos-1]));

View File

@ -96,6 +96,7 @@ public:
POSITION_END33,
POSITION_LAST,
POSITION_BATTLE_MODE,
POSITION_SOCCER_MODE,
POSITION_TUTORIAL_MODE,
POSITION_COUNT};

View File

@ -38,6 +38,7 @@
#include "karts/explosion_animation.hpp"
#include "karts/kart_properties.hpp"
#include "modes/world.hpp"
#include "modes/soccer_world.hpp"
#include "karts/abstract_kart.hpp"
#define SWAT_POS_OFFSET core::vector3df(0.0, 0.2f, -0.4f)
@ -196,10 +197,12 @@ bool Swatter::updateAndTestFinished(float dt)
// change the current phase
squashThingsAround();
m_animation_phase = SWATTER_FROM_TARGET;
if (race_manager->getMinorMode()==
RaceManager::MINOR_MODE_3_STRIKES)
if (race_manager
->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
race_manager
->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
{
// Remove swatter from kart in 3 strikes battle
// Remove swatter from kart in arena gameplay
// after one successful hit
m_discard_now = true;
}
@ -246,6 +249,15 @@ void Swatter::chooseTarget()
if (kart->isInvulnerable() || kart->isSquashed())
continue;
const SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
if (sw)
{
// Don't hit teammates in soccer world
if (sw->getKartTeam(kart->getWorldKartId()) == sw
->getKartTeam(m_kart->getWorldKartId()))
continue;
}
float dist2 = (kart->getXYZ()-m_kart->getXYZ()).length2();
if(dist2<min_dist2)
{

View File

@ -183,7 +183,7 @@ float AIBaseController::normalizeAngle(float angle)
void AIBaseController::setSteering(float angle, float dt)
{
float steer_fraction = angle / m_kart->getMaxSteerAngle();
if(!doSkid(steer_fraction))
if(!canSkid(steer_fraction))
m_controls->m_skid = KartControl::SC_NONE;
else
m_controls->m_skid = steer_fraction > 0 ? KartControl::SC_RIGHT
@ -213,15 +213,6 @@ void AIBaseController::setSteering(float angle, float dt)
}
} // setSteering
// ----------------------------------------------------------------------------
/** Determines if the kart should skid. The base implementation enables
* skidding if a sharp turn is needed (which is for the old skidding
* implementation).
* \param steer_fraction The steering fraction as computed by the
* AIBaseController.
* \return True if the kart should skid.
*/
// ------------------------------------------------------------------------
/** Certain AI levels will not receive a slipstream bonus in order to
* be not as hard.
@ -231,23 +222,6 @@ bool AIBaseController::disableSlipstreamBonus() const
return m_ai_properties->disableSlipstreamUsage();
} // disableSlipstreamBonus
bool AIBaseController::doSkid(float steer_fraction)
{
// Disable skidding when a plunger is in the face
if(m_kart->getBlockedByPlungerTime()>0) return false;
// FIXME: Disable skidding for now if the new skidding
// code is activated, since the AI can not handle this
// properly.
if(m_kart->getKartProperties()->getSkidVisualTime() > 0)
return false;
// Otherwise return if we need a sharp turn (which is
// for the old skidding implementation).
return fabsf(steer_fraction)>=m_ai_properties->m_skidding_threshold;
} // doSkid
//-----------------------------------------------------------------------------
/** This is called when the kart crashed with the terrain. This subroutine
* tries to detect if the AI is stuck by determining if a certain number
@ -305,3 +279,34 @@ void AIBaseController::crashed(const Material *m)
}
} // crashed(Material)
//-----------------------------------------------------------------------------
void AIBaseController::checkPosition(const Vec3 &point,
posData *pos_data,
Vec3 *lc) const
{
// Convert to local coordinates from the point of view of current kart
btQuaternion q(btVector3(0, 1, 0), -m_kart->getHeading());
Vec3 p = point - m_kart->getXYZ();
Vec3 local_coordinates = quatRotate(q, p);
// Save local coordinates for later use if needed
if (lc) *lc = local_coordinates;
// on_side: tell whether it's left or right hand side
if (local_coordinates.getX() < 0)
pos_data->on_side = true;
else
pos_data->on_side = false;
// behind: tell whether it's behind or not
if (local_coordinates.getZ() < 0)
pos_data->behind = true;
else
pos_data->behind = false;
pos_data->angle = atan2(fabsf(local_coordinates.getX()),
fabsf(local_coordinates.getZ()));
pos_data->distance = p.length_2d();
} // checkPosition

View File

@ -42,6 +42,7 @@ private:
bool m_stuck;
protected:
/** Length of the kart, storing it here saves many function calls. */
float m_kart_length;
@ -49,23 +50,27 @@ protected:
float m_kart_width;
/** Keep a pointer to the track to reduce calls */
Track *m_track;
Track *m_track;
/** A pointer to the AI properties for this kart. */
const AIProperties *m_ai_properties;
static bool m_ai_debug;
/** Position info structure of targets. */
struct posData {bool behind; bool on_side; float angle; float distance;};
void setControllerName(const std::string &name);
float steerToPoint(const Vec3 &point);
float normalizeAngle(float angle);
virtual void update (float delta) ;
virtual void setSteering (float angle, float dt);
void setControllerName(const std::string &name);
float steerToPoint(const Vec3 &point);
float normalizeAngle(float angle);
virtual bool doSkid(float steer_fraction);
virtual bool canSkid(float steer_fraction) = 0;
// ------------------------------------------------------------------------
/** 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; }
bool isStuck() const { return m_stuck; }
void checkPosition(const Vec3&, posData*, Vec3* lc = NULL) const;
public:
AIBaseController(AbstractKart *kart);
@ -83,7 +88,8 @@ public:
virtual bool isPlayerController() const { return false; }
virtual bool isLocalPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
virtual void skidBonusTriggered() {};
}; // AIBaseController
#endif

View File

@ -70,16 +70,6 @@ public:
AIBaseLapController(AbstractKart *kart);
virtual ~AIBaseLapController() {};
virtual void reset();
virtual void crashed(const AbstractKart *k) {};
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isNetworkController() const { return false; }
virtual bool isPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
}; // AIBaseLapController
#endif

View File

@ -45,7 +45,7 @@ protected:
friend class AIBaseController;
friend class AIBaseLapController;
friend class SkiddingAI;
friend class BattleAI;
friend class ArenaAI;
/** Used to check that all values are defined in the xml file. */
static float UNDEFINED;

View File

@ -0,0 +1,777 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "karts/controller/arena_ai.hpp"
#include "items/attachment.hpp"
#include "items/item_manager.hpp"
#include "items/powerup.hpp"
#include "items/projectile_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/ai_properties.hpp"
#include "karts/kart_properties.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/log.hpp"
ArenaAI::ArenaAI(AbstractKart *kart)
: AIBaseController(kart)
{
m_debug_sphere = NULL;
} // ArenaAI
//-----------------------------------------------------------------------------
/** Resets the AI when a race is restarted.
*/
void ArenaAI::reset()
{
m_target_node = BattleGraph::UNKNOWN_POLY;
m_adjusting_side = false;
m_closest_kart = NULL;
m_closest_kart_node = BattleGraph::UNKNOWN_POLY;
m_closest_kart_point = Vec3(0, 0, 0);
m_closest_kart_pos_data = {0};
m_cur_kart_pos_data = {0};
m_is_stuck = false;
m_is_uturn = false;
m_target_point = Vec3(0, 0, 0);
m_time_since_last_shot = 0.0f;
m_time_since_driving = 0.0f;
m_time_since_reversing = 0.0f;
m_time_since_uturn = 0.0f;
m_on_node.clear();
m_path_corners.clear();
m_portals.clear();
m_cur_difficulty = race_manager->getDifficulty();
AIBaseController::reset();
} // reset
//-----------------------------------------------------------------------------
/** This is the main entry point for the AI.
* It is called once per frame for each AI and determines the behaviour of
* the AI, e.g. steering, accelerating/braking, firing.
*/
void ArenaAI::update(float dt)
{
// This is used to enable firing an item backwards.
m_controls->m_look_back = false;
m_controls->m_nitro = false;
// Don't do anything if there is currently a kart animations shown.
if (m_kart->getKartAnimation())
return;
if (isWaiting())
{
AIBaseController::update(dt);
return;
}
checkIfStuck(dt);
if (handleArenaUnstuck(dt))
return;
findClosestKart(true);
findTarget();
handleArenaItems(dt);
handleArenaBanana();
if (m_kart->getSpeed() > 15.0f && m_cur_kart_pos_data.angle < 0.2f)
{
// Only use nitro when target is straight
m_controls->m_nitro = true;
}
if (m_is_uturn)
{
handleArenaUTurn(dt);
}
else
{
handleArenaAcceleration(dt);
handleArenaSteering(dt);
handleArenaBraking();
}
AIBaseController::update(dt);
} // update
//-----------------------------------------------------------------------------
void ArenaAI::checkIfStuck(const float dt)
{
if (m_is_stuck) return;
if (m_kart->getKartAnimation() || isWaiting())
{
m_on_node.clear();
m_time_since_driving = 0.0f;
}
m_on_node.insert(getCurrentNode());
m_time_since_driving += dt;
if ((m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f)
&& m_on_node.size() < 2 && !m_is_uturn &&
fabsf(m_kart->getSpeed()) < 3.0f) || isStuck() == true)
{
// Check whether a kart stay on the same node for a period of time
// Or crashed 3 times
m_on_node.clear();
m_time_since_driving = 0.0f;
AIBaseController::reset();
m_is_stuck = true;
}
else if (m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
{
m_on_node.clear(); // Reset for any correct movement
m_time_since_driving = 0.0f;
}
} // checkIfStuck
//-----------------------------------------------------------------------------
/** Handles acceleration.
* \param dt Time step size.
*/
void ArenaAI::handleArenaAcceleration(const float dt)
{
if (m_controls->m_brake)
{
m_controls->m_accel = 0.0f;
return;
}
const float handicap =
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 0.7f : 1.0f);
m_controls->m_accel = stk_config->m_ai_acceleration * handicap;
} // handleArenaAcceleration
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaUTurn(const float dt)
{
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0) // Try to emulate reverse like human players
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -5.0f;
if (m_time_since_uturn >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
setSteering(-(turn_side), dt); // Preventing keep going around circle
else
setSteering(turn_side, dt);
m_time_since_uturn += dt;
checkPosition(m_target_point, &m_cur_kart_pos_data);
if (!m_cur_kart_pos_data.behind || m_time_since_uturn >
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 3.5f : 3.0f))
{
m_is_uturn = false;
m_time_since_uturn = 0.0f;
}
else
m_is_uturn = true;
} // handleArenaUTurn
//-----------------------------------------------------------------------------
bool ArenaAI::handleArenaUnstuck(const float dt)
{
if (!m_is_stuck || m_is_uturn) return false;
setSteering(0.0f, dt);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0)
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -4.0f;
m_time_since_reversing += dt;
if (m_time_since_reversing >= 1.0f)
{
m_is_stuck = false;
m_time_since_reversing = 0.0f;
}
AIBaseController::update(dt);
return true;
} // handleArenaUnstuck
//-----------------------------------------------------------------------------
/** This function sets the steering.
* \param dt Time step size.
*/
void ArenaAI::handleArenaSteering(const float dt)
{
const int current_node = getCurrentNode();
if (current_node == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
if (m_target_node == current_node)
{
// Very close to the item, steer directly
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else if (m_target_node != current_node)
{
findPortals(current_node, m_target_node);
stringPull(m_kart->getXYZ(), m_target_point);
if (m_path_corners.size() > 0)
m_target_point = m_path_corners[0];
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else
{
// Do nothing (go straight) if no targets found
setSteering(0.0f, dt);
return;
}
} // handleSteering
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaBanana()
{
if (m_is_uturn) return;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->getType() == Item::ITEM_BANANA && !item->wasCollected())
{
posData banana_pos = {0};
Vec3 banana_lc(0, 0, 0);
checkPosition(item->getXYZ(), &banana_pos, &banana_lc);
if (banana_pos.angle < 0.2f && banana_pos.distance < 7.5f &&
!banana_pos.behind)
{
// Check whether it's straight ahead towards a banana
// If so, adjust target point
banana_lc = (banana_pos.on_side ? 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*/);
// Handle one banana only
break;
}
}
}
} // handleArenaBanana
//-----------------------------------------------------------------------------
/** This function finds the polyon edges(portals) that the AI will cross before
* reaching its destination. We start from the current polygon and call
* BattleGraph::getNextShortestPathPoly() to find the next polygon on the shortest
* path to the destination. Then find the common edge between the current
* poly and the next poly, store it and step through the channel.
*
* 1----2----3 In this case, the portals are:
* |strt| | (2,5) (4,5) (10,7) (10,9) (11,12)
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start The start node(polygon) of the channel.
* \param end The end node(polygon) of the channel.
*/
void ArenaAI::findPortals(int start, int end)
{
int this_node = start;
// We can't use NULL because NULL==0 which is a valid node, so we initialize
// with a value that is always invalid.
int next_node = -999;
m_portals.clear();
while (next_node != end && this_node != -1 && next_node != -1 && this_node != end)
{
next_node = BattleGraph::get()->getNextShortestPathPoly(this_node, end);
if (next_node == BattleGraph::UNKNOWN_POLY || next_node == -999) return;
std::vector<int> this_node_verts =
NavMesh::get()->getNavPoly(this_node).getVerticesIndex();
std::vector<int> next_node_verts=
NavMesh::get()->getNavPoly(next_node).getVerticesIndex();
// this_node_verts and next_node_verts hold vertices of polygons in CCW order
// We reverse next_node_verts so it becomes easy to compare edges in the next step
std::reverse(next_node_verts.begin(),next_node_verts.end());
Vec3 portalLeft, portalRight;
//bool flag = 0;
for (unsigned int n_i = 0; n_i < next_node_verts.size(); n_i++)
{
for (unsigned int t_i = 0; t_i < this_node_verts.size(); t_i++)
{
if ((next_node_verts[n_i] == this_node_verts[t_i]) &&
(next_node_verts[(n_i+1)%next_node_verts.size()] ==
this_node_verts[(t_i+1)%this_node_verts.size()]))
{
portalLeft = NavMesh::get()->
getVertex(this_node_verts[(t_i+1)%this_node_verts.size()]);
portalRight = NavMesh::get()->getVertex(this_node_verts[t_i]);
}
}
}
m_portals.push_back(std::make_pair(portalLeft, portalRight));
// for debugging:
//m_debug_sphere->setPosition((portalLeft).toIrrVector());
this_node = next_node;
}
} // findPortals
//-----------------------------------------------------------------------------
/** This function implements the funnel algorithm for finding shortest paths
* through a polygon channel. This means that we should move from corner to
* corner to move on the most straight and shortest path to the destination.
* This can be visualized as pulling a string from the end point to the start.
* The string will bend at the corners, and this algorithm will find those
* corners using portals from findPortals(). The AI will aim at the first
* corner and the rest can be used for estimating the curve (braking).
*
* 1----2----3 In this case, the corners are:
* |strt| | <5,10,end>
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start_pos The start position (usually the AI's current position).
* \param end_pos The end position (m_target_point).
*/
void ArenaAI::stringPull(const Vec3& start_pos, const Vec3& end_pos)
{
Vec3 funnel_apex = start_pos;
Vec3 funnel_left = m_portals[0].first;
Vec3 funnel_right = m_portals[0].second;
unsigned int apex_index=0, fun_left_index=0, fun_right_index=0;
m_portals.push_back(std::make_pair(end_pos, end_pos));
m_path_corners.clear();
const float eps=0.0001f;
for (unsigned int i = 0; i < m_portals.size(); i++)
{
Vec3 portal_left = m_portals[i].first;
Vec3 portal_right = m_portals[i].second;
//Compute for left edge
if ((funnel_left == funnel_apex) ||
portal_left.sideOfLine2D(funnel_apex, funnel_left) <= -eps)
{
funnel_left = 0.98f*portal_left + 0.02f*portal_right;
//funnel_left = portal_left;
fun_left_index = i;
if (portal_left.sideOfLine2D(funnel_apex, funnel_right) < -eps)
{
funnel_apex = funnel_right;
apex_index = fun_right_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
//Compute for right edge
if ((funnel_right == funnel_apex) ||
portal_right.sideOfLine2D(funnel_apex, funnel_right) >= eps)
{
funnel_right = 0.98f*portal_right + 0.02f*portal_left;
//funnel_right = portal_right;
fun_right_index = i;
if (portal_right.sideOfLine2D(funnel_apex, funnel_left) > eps)
{
funnel_apex = funnel_left;
apex_index = fun_left_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
}
//Push end_pos to m_path_corners so if no corners, we aim at target
m_path_corners.push_back(end_pos);
} // stringPull
//-----------------------------------------------------------------------------
/** This function handles braking. It calls determineTurnRadius() to find out
* the curve radius. Depending on the turn radius, it finds out the maximum
* speed. If the current speed is greater than the max speed and a set minimum
* speed, brakes are applied.
*/
void ArenaAI::handleArenaBraking()
{
m_controls->m_brake = false;
if (getCurrentNode() == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
// A kart will not brake when the speed is already slower than this
// value. This prevents a kart from going too slow (or even backwards)
// in tight curves.
const float MIN_SPEED = 5.0f;
std::vector<Vec3> points;
points.push_back(m_kart->getXYZ());
points.push_back(m_path_corners[0]);
points.push_back((m_path_corners.size()>=2) ? m_path_corners[1] : m_path_corners[0]);
float current_curve_radius = determineTurnRadius(points);
Vec3 d1 = m_kart->getXYZ() - m_target_point;
Vec3 d2 = m_kart->getXYZ() - m_path_corners[0];
if (d1.length2_2d() < d2.length2_2d())
current_curve_radius = d1.length_2d();
float max_turn_speed = m_kart->getSpeedForTurnRadius(current_curve_radius);
if (m_kart->getSpeed() > max_turn_speed &&
m_kart->getSpeed() > MIN_SPEED)
{
m_controls->m_brake = true;
}
} // handleArenaBraking
//-----------------------------------------------------------------------------
/** The turn radius is determined by fitting a parabola to 3 points: current
* location of AI, first corner and the second corner. Once the constants are
* computed, a formula is used to find the radius of curvature at the kart's
* current location.
* NOTE: This method does not apply enough braking, should think of something
* else.
*/
float ArenaAI::determineTurnRadius( std::vector<Vec3>& points )
{
// Declaring variables
float a, b;
irr::core::CMatrix4<float> A;
irr::core::CMatrix4<float> X;
irr::core::CMatrix4<float> B;
//Populating matrices
for (unsigned int i = 0; i < 3; i++)
{
A(i, 0) = points[i].x()*points[i].x();
A(i, 1) = points[i].x();
A(i, 2) = 1.0f;
A(i, 3) = 0.0f;
}
A(3, 0) = A(3, 1) = A(3, 2) = 0.0f;
A(3, 3) = 1.0f;
for (unsigned int i = 0; i < 3; i++)
{
B(i, 0) = points[i].z();
B(i, 1) = 0.0f;
B(i, 2) = 0.0f;
B(i, 3) = 0.0f;
}
B(3, 0) = B(3, 1) = B(3, 2) = B(3, 3) = 0.0f;
//Computing inverse : X = inv(A)*B
irr::core::CMatrix4<float> invA;
if (!A.getInverse(invA))
return -1;
X = invA*B;
a = X(0, 0);
b = X(0, 1);
//c = X(0, 2);
float x = points.front().x();
//float z = a*pow(x, 2) + b*x + c;
float dx_by_dz = 2*a*x + b;
float d2x_by_dz = 2*a;
float radius = pow(abs(1 + pow(dx_by_dz, 2)), 1.5f)/ abs(d2x_by_dz);
return radius;
} // determineTurnRadius
//-----------------------------------------------------------------------------
void ArenaAI::handleArenaItems(const float dt)
{
m_controls->m_fire = false;
if (m_kart->getKartAnimation() ||
m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING)
return;
// Find a closest kart again, this time we ignore difficulty
findClosestKart(false);
if (!m_closest_kart) return;
m_time_since_last_shot += dt;
float min_bubble_time = 2.0f;
const bool difficulty = m_cur_difficulty == RaceManager::DIFFICULTY_EASY ||
m_cur_difficulty == RaceManager::DIFFICULTY_MEDIUM;
const bool fire_behind = m_closest_kart_pos_data.behind && !difficulty;
const bool perfect_aim = m_closest_kart_pos_data.angle < 0.2f;
switch(m_kart->getPowerup()->getType())
{
case PowerupManager::POWERUP_BUBBLEGUM:
{
Attachment::AttachmentType type = m_kart->getAttachment()->getType();
// Don't use shield when we have a swatter.
if (type == Attachment::ATTACH_SWATTER ||
type == Attachment::ATTACH_NOLOKS_SWATTER)
break;
// Check if a flyable (cake, ...) is close. If so, use bubblegum
// as shield
if (!m_kart->isShielded() &&
projectile_manager->projectileIsClose(m_kart,
m_ai_properties->m_shield_incoming_radius))
{
m_controls->m_fire = true;
m_controls->m_look_back = false;
break;
}
// Avoid dropping all bubble gums one after another
if (m_time_since_last_shot < 3.0f) break;
// Use bubblegum if the next kart behind is 'close' but not too close,
// or can't find a close kart for too long time
if ((m_closest_kart_pos_data.distance < 15.0f &&
m_closest_kart_pos_data.distance > 3.0f) ||
m_time_since_last_shot > 15.0f)
{
m_controls->m_fire = true;
m_controls->m_look_back = true;
break;
}
break; // POWERUP_BUBBLEGUM
}
case PowerupManager::POWERUP_CAKE:
{
// if the kart has a shield, do not break it by using a cake.
if (m_kart->getShieldTime() > min_bubble_time)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 25.0f &&
!m_closest_kart->isInvulnerable())
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_CAKE
case PowerupManager::POWERUP_BOWLING:
{
// if the kart has a shield, do not break it by using a bowling ball.
if (m_kart->getShieldTime() > min_bubble_time)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 6.0f &&
(difficulty || perfect_aim))
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_BOWLING
case PowerupManager::POWERUP_SWATTER:
{
// Squared distance for which the swatter works
float d2 = m_kart->getKartProperties()->getSwatterDistance();
// if the kart has a shield, do not break it by using a swatter.
if (m_kart->getShieldTime() > min_bubble_time)
break;
if (!m_closest_kart->isSquashed() &&
m_closest_kart_pos_data.distance < d2 &&
m_closest_kart->getSpeed() < m_kart->getSpeed())
{
m_controls->m_fire = true;
m_controls->m_look_back = false;
break;
}
break;
}
// Below powerups won't appear in arena, so skip them
case PowerupManager::POWERUP_ZIPPER:
break; // POWERUP_ZIPPER
case PowerupManager::POWERUP_PLUNGER:
break; // POWERUP_PLUNGER
case PowerupManager::POWERUP_SWITCH: // Don't handle switch
m_controls->m_fire = true; // (use it no matter what) for now
break; // POWERUP_SWITCH
case PowerupManager::POWERUP_PARACHUTE:
break; // POWERUP_PARACHUTE
case PowerupManager::POWERUP_ANVIL:
break; // POWERUP_ANVIL
case PowerupManager::POWERUP_RUBBERBALL:
break;
default:
Log::error("ArenaAI",
"Invalid or unhandled powerup '%d' in default AI.",
m_kart->getPowerup()->getType());
assert(false);
}
if (m_controls->m_fire)
m_time_since_last_shot = 0.0f;
} // handleArenaItems
//-----------------------------------------------------------------------------
void ArenaAI::collectItemInArena(Vec3* aim_point, int* target_node) const
{
float distance = 99999.9f;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
if (item_list.empty())
{
// Notice: this should not happen, as it makes no sense
// for an arean without items, if so how can attack happen?
Log::fatal ("ArenaAI",
"AI can't find any items in the arena, "
"maybe there is something wrong with the navmesh, "
"make sure it lies closely to the ground.");
return;
}
unsigned int closest_item_num = 0;
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->wasCollected()) continue;
if ((item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL) &&
(m_kart->getEnergy() >
m_kart->getKartProperties()->getNitroSmallContainer()))
continue; // Ignore nitro when already has some
Vec3 d = item->getXYZ() - m_kart->getXYZ();
if (d.length_2d() <= distance &&
(item->getType() == Item::ITEM_BONUS_BOX ||
item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL))
{
closest_item_num = i;
distance = d.length_2d();
}
}
const Item *item_selected = item_list[closest_item_num].first;
if (item_selected->getType() == Item::ITEM_BONUS_BOX ||
item_selected->getType() == Item::ITEM_NITRO_BIG ||
item_selected->getType() == Item::ITEM_NITRO_SMALL)
{
*aim_point = item_selected->getXYZ();
*target_node = item_list[closest_item_num].second;
}
else
{
// Handle when all targets are swapped, which make AIs follow karts
*aim_point = m_closest_kart_point;
*target_node = m_closest_kart_node;
}
} // collectItemInArena

View File

@ -0,0 +1,135 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_ARENA_AI_HPP
#define HEADER_ARENA_AI_HPP
#include "karts/controller/ai_base_controller.hpp"
#include "race/race_manager.hpp"
#include "utils/random_generator.hpp"
#undef AI_DEBUG
#ifdef AI_DEBUG
#include "graphics/irr_driver.hpp"
#endif
#if defined(WIN32) && !defined(__CYGWIN__) && !defined(__MINGW32__)
#define isnan _isnan
#else
#include <math.h>
#endif
class Vec3;
namespace irr
{
namespace scene { class ISceneNode; }
namespace video { class ITexture; }
}
/** A base class for AI that use navmesh to work.
* \ingroup controller
*/
class ArenaAI : public AIBaseController
{
protected:
/** Pointer to the closest kart around this kart. */
AbstractKart *m_closest_kart;
int m_closest_kart_node;
Vec3 m_closest_kart_point;
posData m_closest_kart_pos_data;
/** Holds the current difficulty. */
RaceManager::Difficulty m_cur_difficulty;
/** For debugging purpose: a sphere indicating where the AI
* is targeting at. */
irr::scene::ISceneNode *m_debug_sphere;
/** The node(poly) at which the target point lies in. */
int m_target_node;
/** The target point. */
Vec3 m_target_point;
void collectItemInArena(Vec3*, int*) const;
private:
/** Used by handleArenaUTurn, it tells whether to do left or right
* turning when steering is overridden. */
bool m_adjusting_side;
posData m_cur_kart_pos_data;
/** Indicates that the kart is currently stuck, and m_time_since_reversing is
* counting down. */
bool m_is_stuck;
/** Indicates that the kart need a uturn to reach a node behind, and
* m_time_since_uturn is counting down. */
bool m_is_uturn;
/** Holds the unique node ai has driven through, useful to tell if AI is
* stuck by determine the size of this set. */
std::set <int> m_on_node;
/** Holds the corner points computed using the funnel algorithm that the AI
* will eventaully move through. See stringPull(). */
std::vector<Vec3> m_path_corners;
/** Holds the set of portals that the kart will cross when moving through
* polygon channel. See findPortals(). */
std::vector<std::pair<Vec3,Vec3> > m_portals;
/** Time an item has been collected and not used. */
float m_time_since_last_shot;
/** This is a timer that counts down when the kart is reversing to get unstuck. */
float m_time_since_reversing;
/** This is a timer that counts down when the kart is starting to drive. */
float m_time_since_driving;
/** This is a timer that counts down when the kart is doing u-turn. */
float m_time_since_uturn;
void checkIfStuck(const float dt);
float determineTurnRadius(std::vector<Vec3>& points);
void findPortals(int start, int end);
void handleArenaAcceleration(const float dt);
void handleArenaBanana();
void handleArenaBraking();
void handleArenaItems(const float dt);
void handleArenaSteering(const float dt);
void handleArenaUTurn(const float dt);
bool handleArenaUnstuck(const float dt);
void stringPull(const Vec3&, const Vec3&);
virtual int getCurrentNode() const = 0;
virtual bool isWaiting() const = 0;
virtual void findClosestKart(bool use_difficulty) = 0;
virtual void findTarget() = 0;
public:
ArenaAI(AbstractKart *kart);
virtual ~ArenaAI() {};
virtual void update (float delta);
virtual void reset ();
virtual void newLap(int lap) {};
};
#endif

View File

@ -21,20 +21,10 @@
#include "karts/controller/battle_ai.hpp"
#include "items/attachment.hpp"
#include "items/item_manager.hpp"
#include "items/powerup.hpp"
#include "items/projectile_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/kart_control.hpp"
#include "karts/controller/player_controller.hpp"
#include "karts/controller/ai_properties.hpp"
#include "karts/kart_properties.hpp"
#include "karts/max_speed.hpp"
#include "karts/rescue_animation.hpp"
#include "karts/skidding.hpp"
#include "modes/three_strikes_battle.hpp"
#include "tracks/battle_graph.hpp"
#include "utils/log.hpp"
#ifdef AI_DEBUG
#include "irrlicht.h"
@ -50,7 +40,7 @@ using namespace std;
#endif
BattleAI::BattleAI(AbstractKart *kart)
: AIBaseController(kart)
: ArenaAI(kart)
{
reset();
@ -60,19 +50,8 @@ BattleAI::BattleAI(AbstractKart *kart)
m_debug_sphere = irr_driver->addSphere(1.0f, col_debug);
m_debug_sphere->setVisible(true);
#endif
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES)
{
m_world = dynamic_cast<ThreeStrikesBattle*>(World::getWorld());
m_track = m_world->getTrack();
}
else
{
// Those variables are not defined in a battle mode (m_world is
// a linear world, since it assumes the existance of drivelines)
m_world = NULL;
m_track = NULL;
}
m_world = dynamic_cast<ThreeStrikesBattle*>(World::getWorld());
m_track = m_world->getTrack();
// Don't call our own setControllerName, since this will add a
// billboard showing 'AIBaseController' to the kart.
@ -94,162 +73,20 @@ BattleAI::~BattleAI()
*/
void BattleAI::reset()
{
m_target_node = BattleGraph::UNKNOWN_POLY;
m_adjusting_side = false;
m_closest_kart = NULL;
m_closest_kart_node = BattleGraph::UNKNOWN_POLY;
m_closest_kart_point = Vec3(0, 0, 0);
m_closest_kart_pos_data = {0};
m_cur_kart_pos_data = {0};
m_is_steering_overridden = false;
m_is_stuck = false;
m_is_uturn = false;
m_target_point = Vec3(0, 0, 0);
m_time_since_last_shot = 0.0f;
m_time_since_driving = 0.0f;
m_time_since_reversing = 0.0f;
m_time_since_steering_overridden = 0.0f;
m_time_since_uturn = 0.0f;
m_on_node.clear();
m_path_corners.clear();
m_portals.clear();
m_cur_difficulty = race_manager->getDifficulty();
ArenaAI::reset();
AIBaseController::reset();
m_mini_skid = false;
} // reset
//-----------------------------------------------------------------------------
/** This is the main entry point for the AI.
* It is called once per frame for each AI and determines the behaviour of
* the AI, e.g. steering, accelerating/braking, firing.
*/
void BattleAI::update(float dt)
{
// This is used to enable firing an item backwards.
m_controls->m_look_back = false;
m_controls->m_nitro = false;
// Don't do anything if there is currently a kart animations shown.
if (m_kart->getKartAnimation())
return;
if (m_world->isStartPhase())
{
AIBaseController::update(dt);
return;
}
checkIfStuck(dt);
if (m_is_stuck && !m_is_uturn)
{
setSteering(0.0f, dt);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0)
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -4.0f;
m_time_since_reversing += dt;
if (m_time_since_reversing >= 1.0f)
{
m_is_stuck = false;
m_time_since_reversing = 0.0f;
}
AIBaseController::update(dt);
return;
}
findClosestKart(true);
findTarget();
handleItems(dt);
handleBanana();
if (m_kart->getSpeed() > 15.0f && m_cur_kart_pos_data.angle < 0.2f)
{
// Only use nitro when target is straight
m_controls->m_nitro = true;
}
if (m_is_uturn)
{
handleUTurn(dt);
}
else
{
handleAcceleration(dt);
handleSteering(dt);
handleBraking();
}
AIBaseController::update(dt);
m_mini_skid = false;
ArenaAI::update(dt);
} // update
//-----------------------------------------------------------------------------
void BattleAI::checkIfStuck(const float dt)
{
if (m_is_stuck) return;
if (m_kart->getKartAnimation() || m_world->isStartPhase())
{
m_on_node.clear();
m_time_since_driving = 0.0f;
}
m_on_node.insert(m_world->getKartNode(m_kart->getWorldKartId()));
m_time_since_driving += dt;
if ((m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f)
&& m_on_node.size() < 2 && !m_is_uturn &&
fabsf(m_kart->getSpeed()) < 3.0f) || isStuck() == true)
{
// Check whether a kart stay on the same node for a period of time
// Or crashed 3 times
m_on_node.clear();
m_time_since_driving = 0.0f;
AIBaseController::reset();
m_is_stuck = true;
}
else if (m_time_since_driving >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
{
m_on_node.clear(); // Reset for any correct movement
m_time_since_driving = 0.0f;
}
} // checkIfStuck
//-----------------------------------------------------------------------------
void BattleAI::checkPosition(const Vec3 &point, posData *pos_data)
{
// Convert to local coordinates from the point of view of current kart
btQuaternion q(btVector3(0, 1, 0), -m_kart->getHeading());
Vec3 p = point - m_kart->getXYZ();
Vec3 local_coordinates = quatRotate(q, p);
// on_side: tell whether it's left or right hand side
if (local_coordinates.getX() < 0)
pos_data->on_side = true;
else
pos_data->on_side = false;
// behind: tell whether it's behind or not
if (local_coordinates.getZ() < 0)
pos_data->behind = true;
else
pos_data->behind = false;
pos_data->angle = atan2(fabsf(local_coordinates.getX()),
fabsf(local_coordinates.getZ()));
pos_data->distance = p.length_2d();
} // checkPosition
//-----------------------------------------------------------------------------
void BattleAI::findClosestKart(bool difficulty)
void BattleAI::findClosestKart(bool use_difficulty)
{
float distance = 99999.9f;
const unsigned int n = m_world->getNumKarts();
@ -265,8 +102,8 @@ void BattleAI::findClosestKart(bool difficulty)
// Test whether takes current difficulty into account for closest kart
// Notice: it don't affect aiming, this function will be called once
// more in handleItems, which ignore difficulty.
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && difficulty)
// more in handleArenaItems, which ignore difficulty.
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && use_difficulty)
{
// Skip human players for novice mode unless only human players left
const AbstractKart* temp = m_world->getKart(i);
@ -275,7 +112,7 @@ void BattleAI::findClosestKart(bool difficulty)
m_world->getCurrentNumPlayers()) > 1)
continue;
}
else if (m_cur_difficulty == RaceManager::DIFFICULTY_BEST && difficulty)
else if (m_cur_difficulty == RaceManager::DIFFICULTY_BEST && use_difficulty)
{
// Skip AI players for supertux mode
const AbstractKart* temp = m_world->getKart(i);
@ -295,10 +132,20 @@ void BattleAI::findClosestKart(bool difficulty)
m_closest_kart_node = m_world->getKartNode(closest_kart_num);
m_closest_kart_point = closest_kart->getXYZ();
if (!difficulty)
if (!use_difficulty)
{
m_closest_kart = m_world->getKart(closest_kart_num);
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
// Do a mini-skid to closest kart only when firing target,
// not straight ahead, not too far, in front of it
// and with suitable difficulties.
if (m_closest_kart_pos_data.angle > 0.2f &&
m_closest_kart_pos_data.distance < 20.0f &&
!m_closest_kart_pos_data.behind &&
(m_cur_difficulty == RaceManager::DIFFICULTY_HARD ||
m_cur_difficulty == RaceManager::DIFFICULTY_BEST))
m_mini_skid = true;
}
} // findClosestKart
@ -308,7 +155,7 @@ void BattleAI::findTarget()
// Find a suitable target to drive to, either powerup or kart
if (m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER)
handleItemCollection(&m_target_point , &m_target_node);
collectItemInArena(&m_target_point , &m_target_node);
else
{
m_target_point = m_closest_kart_point;
@ -316,615 +163,13 @@ void BattleAI::findTarget()
}
} // findTarget
//-----------------------------------------------------------------------------
/** Handles acceleration.
* \param dt Time step size.
*/
void BattleAI::handleAcceleration(const float dt)
// ------------------------------------------------------------------------
int BattleAI::getCurrentNode() const
{
if (m_controls->m_brake)
{
m_controls->m_accel = 0.0f;
return;
}
const float handicap =
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 0.7f : 1.0f);
m_controls->m_accel = stk_config->m_ai_acceleration * handicap;
} // handleAcceleration
//-----------------------------------------------------------------------------
void BattleAI::handleUTurn(const float dt)
return m_world->getKartNode(m_kart->getWorldKartId());
} // getCurrentNode
// ------------------------------------------------------------------------
bool BattleAI::isWaiting() const
{
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
if (fabsf(m_kart->getSpeed()) >
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
&& m_kart->getSpeed() < 0) // Try to emulate reverse like human players
m_controls->m_accel = -0.06f;
else
m_controls->m_accel = -5.0f;
if (m_time_since_uturn >=
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
setSteering(-(turn_side), dt); // Preventing keep going around circle
else
setSteering(turn_side, dt);
m_time_since_uturn += dt;
checkPosition(m_target_point, &m_cur_kart_pos_data);
if (!m_cur_kart_pos_data.behind || m_time_since_uturn >
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 3.5f : 3.0f))
{
m_is_uturn = false;
m_time_since_uturn = 0.0f;
}
else
m_is_uturn = true;
} // handleUTurn
//-----------------------------------------------------------------------------
/** This function sets the steering.
* \param dt Time step size.
*/
void BattleAI::handleSteering(const float dt)
{
const int current_node = m_world->getKartNode(m_kart->getWorldKartId());
if (current_node == BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
if (m_is_steering_overridden)
{
// Steering is overridden to avoid eating banana
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
m_time_since_steering_overridden += dt;
if (m_time_since_steering_overridden > 0.35f)
setSteering(-(turn_side), dt);
else
setSteering(turn_side, dt);
if (m_time_since_steering_overridden > 0.7f)
{
m_is_steering_overridden = false;
m_time_since_steering_overridden = 0.0f;
}
return;
}
if (m_target_node == current_node)
{
// Very close to the item, steer directly
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else if (m_target_node != current_node)
{
findPortals(current_node, m_target_node);
stringPull(m_kart->getXYZ(), m_target_point);
if (m_path_corners.size() > 0)
m_target_point = m_path_corners[0];
checkPosition(m_target_point, &m_cur_kart_pos_data);
#ifdef AI_DEBUG
m_debug_sphere->setPosition(m_target_point.toIrrVector());
#endif
if (m_cur_kart_pos_data.behind)
{
m_adjusting_side = m_cur_kart_pos_data.on_side;
m_is_uturn = true;
}
else
{
float target_angle = steerToPoint(m_target_point);
setSteering(target_angle, dt);
}
return;
}
else
{
// Do nothing (go straight) if no targets found
setSteering(0.0f, dt);
return;
}
} // handleSteering
//-----------------------------------------------------------------------------
void BattleAI::handleBanana()
{
if (m_is_steering_overridden || m_is_uturn) return;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->getType() == Item::ITEM_BANANA && !item->wasCollected())
{
posData banana_pos = {0};
checkPosition(item->getXYZ(), &banana_pos);
if (banana_pos.angle < 0.2f && banana_pos.distance < 7.5f &&
!banana_pos.behind)
{
// Check whether it's straight ahead towards a banana
// If so, try to do hard turn to avoid
m_adjusting_side = banana_pos.on_side;
m_is_steering_overridden = true;
}
}
}
} // handleBanana
//-----------------------------------------------------------------------------
/** This function finds the polyon edges(portals) that the AI will cross before
* reaching its destination. We start from the current polygon and call
* BattleGraph::getNextShortestPathPoly() to find the next polygon on the shortest
* path to the destination. Then find the common edge between the current
* poly and the next poly, store it and step through the channel.
*
* 1----2----3 In this case, the portals are:
* |strt| | (2,5) (4,5) (10,7) (10,9) (11,12)
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start The start node(polygon) of the channel.
* \param end The end node(polygon) of the channel.
*/
void BattleAI::findPortals(int start, int end)
{
int this_node = start;
// We can't use NULL because NULL==0 which is a valid node, so we initialize
// with a value that is always invalid.
int next_node = -999;
m_portals.clear();
while (next_node != end && this_node != -1 && next_node != -1 && this_node != end)
{
next_node = BattleGraph::get()->getNextShortestPathPoly(this_node, end);
if (next_node == BattleGraph::UNKNOWN_POLY || next_node == -999) return;
std::vector<int> this_node_verts =
NavMesh::get()->getNavPoly(this_node).getVerticesIndex();
std::vector<int> next_node_verts=
NavMesh::get()->getNavPoly(next_node).getVerticesIndex();
// this_node_verts and next_node_verts hold vertices of polygons in CCW order
// We reverse next_node_verts so it becomes easy to compare edges in the next step
std::reverse(next_node_verts.begin(),next_node_verts.end());
Vec3 portalLeft, portalRight;
//bool flag = 0;
for (unsigned int n_i = 0; n_i < next_node_verts.size(); n_i++)
{
for (unsigned int t_i = 0; t_i < this_node_verts.size(); t_i++)
{
if ((next_node_verts[n_i] == this_node_verts[t_i]) &&
(next_node_verts[(n_i+1)%next_node_verts.size()] ==
this_node_verts[(t_i+1)%this_node_verts.size()]))
{
portalLeft = NavMesh::get()->
getVertex(this_node_verts[(t_i+1)%this_node_verts.size()]);
portalRight = NavMesh::get()->getVertex(this_node_verts[t_i]);
}
}
}
m_portals.push_back(std::make_pair(portalLeft, portalRight));
// for debugging:
//m_debug_sphere->setPosition((portalLeft).toIrrVector());
this_node = next_node;
}
} // findPortals
//-----------------------------------------------------------------------------
/** This function implements the funnel algorithm for finding shortest paths
* through a polygon channel. This means that we should move from corner to
* corner to move on the most straight and shortest path to the destination.
* This can be visualized as pulling a string from the end point to the start.
* The string will bend at the corners, and this algorithm will find those
* corners using portals from findPortals(). The AI will aim at the first
* corner and the rest can be used for estimating the curve (braking).
*
* 1----2----3 In this case, the corners are:
* |strt| | <5,10,end>
* 6----5----4
* | |
* 7----10----11----14
* | | | end |
* 8----9-----12----13
*
* \param start_pos The start position (usually the AI's current position).
* \param end_pos The end position (m_target_point).
*/
void BattleAI::stringPull(const Vec3& start_pos, const Vec3& end_pos)
{
Vec3 funnel_apex = start_pos;
Vec3 funnel_left = m_portals[0].first;
Vec3 funnel_right = m_portals[0].second;
unsigned int apex_index=0, fun_left_index=0, fun_right_index=0;
m_portals.push_back(std::make_pair(end_pos, end_pos));
m_path_corners.clear();
const float eps=0.0001f;
for (unsigned int i = 0; i < m_portals.size(); i++)
{
Vec3 portal_left = m_portals[i].first;
Vec3 portal_right = m_portals[i].second;
//Compute for left edge
if ((funnel_left == funnel_apex) ||
portal_left.sideOfLine2D(funnel_apex, funnel_left) <= -eps)
{
funnel_left = 0.98f*portal_left + 0.02f*portal_right;
//funnel_left = portal_left;
fun_left_index = i;
if (portal_left.sideOfLine2D(funnel_apex, funnel_right) < -eps)
{
funnel_apex = funnel_right;
apex_index = fun_right_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
//Compute for right edge
if ((funnel_right == funnel_apex) ||
portal_right.sideOfLine2D(funnel_apex, funnel_right) >= eps)
{
funnel_right = 0.98f*portal_right + 0.02f*portal_left;
//funnel_right = portal_right;
fun_right_index = i;
if (portal_right.sideOfLine2D(funnel_apex, funnel_left) > eps)
{
funnel_apex = funnel_left;
apex_index = fun_left_index;
m_path_corners.push_back(funnel_apex);
funnel_left = funnel_apex;
funnel_right = funnel_apex;
i = apex_index;
continue;
}
}
}
//Push end_pos to m_path_corners so if no corners, we aim at target
m_path_corners.push_back(end_pos);
} // stringPull
//-----------------------------------------------------------------------------
/** This function handles braking. It calls determineTurnRadius() to find out
* the curve radius. Depending on the turn radius, it finds out the maximum
* speed. If the current speed is greater than the max speed and a set minimum
* speed, brakes are applied.
*/
void BattleAI::handleBraking()
{
m_controls->m_brake = false;
if (m_world->getKartNode(m_kart->getWorldKartId())
== BattleGraph::UNKNOWN_POLY ||
m_target_node == BattleGraph::UNKNOWN_POLY ||
m_is_steering_overridden) return;
// A kart will not brake when the speed is already slower than this
// value. This prevents a kart from going too slow (or even backwards)
// in tight curves.
const float MIN_SPEED = 5.0f;
std::vector<Vec3> points;
points.push_back(m_kart->getXYZ());
points.push_back(m_path_corners[0]);
points.push_back((m_path_corners.size()>=2) ? m_path_corners[1] : m_path_corners[0]);
float current_curve_radius = BattleAI::determineTurnRadius(points);
Vec3 d1 = m_kart->getXYZ() - m_target_point;
Vec3 d2 = m_kart->getXYZ() - m_path_corners[0];
if (d1.length2_2d() < d2.length2_2d())
current_curve_radius = d1.length_2d();
float max_turn_speed = m_kart->getSpeedForTurnRadius(current_curve_radius);
if (m_kart->getSpeed() > max_turn_speed &&
m_kart->getSpeed() > MIN_SPEED)
{
m_controls->m_brake = true;
}
} // handleBraking
//-----------------------------------------------------------------------------
/** The turn radius is determined by fitting a parabola to 3 points: current
* location of AI, first corner and the second corner. Once the constants are
* computed, a formula is used to find the radius of curvature at the kart's
* current location.
* NOTE: This method does not apply enough braking, should think of something
* else.
*/
float BattleAI::determineTurnRadius( std::vector<Vec3>& points )
{
// Declaring variables
float a, b;
irr::core::CMatrix4<float> A;
irr::core::CMatrix4<float> X;
irr::core::CMatrix4<float> B;
//Populating matrices
for (unsigned int i = 0; i < 3; i++)
{
A(i, 0) = points[i].x()*points[i].x();
A(i, 1) = points[i].x();
A(i, 2) = 1.0f;
A(i, 3) = 0.0f;
}
A(3, 0) = A(3, 1) = A(3, 2) = 0.0f;
A(3, 3) = 1.0f;
for (unsigned int i = 0; i < 3; i++)
{
B(i, 0) = points[i].z();
B(i, 1) = 0.0f;
B(i, 2) = 0.0f;
B(i, 3) = 0.0f;
}
B(3, 0) = B(3, 1) = B(3, 2) = B(3, 3) = 0.0f;
//Computing inverse : X = inv(A)*B
irr::core::CMatrix4<float> invA;
if (!A.getInverse(invA))
return -1;
X = invA*B;
a = X(0, 0);
b = X(0, 1);
//c = X(0, 2);
float x = points.front().x();
//float z = a*pow(x, 2) + b*x + c;
float dx_by_dz = 2*a*x + b;
float d2x_by_dz = 2*a;
float radius = pow(abs(1 + pow(dx_by_dz, 2)), 1.5f)/ abs(d2x_by_dz);
return radius;
} // determineTurnRadius
//-----------------------------------------------------------------------------
void BattleAI::handleItems(const float dt)
{
m_controls->m_fire = false;
if (m_kart->getKartAnimation() ||
m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING)
return;
// Find a closest kart again, this time we ignore difficulty
findClosestKart(false);
if (!m_closest_kart) return;
m_time_since_last_shot += dt;
float min_bubble_time = 2.0f;
const bool difficulty = m_cur_difficulty == RaceManager::DIFFICULTY_EASY ||
m_cur_difficulty == RaceManager::DIFFICULTY_MEDIUM;
const bool fire_behind = m_closest_kart_pos_data.behind && !difficulty;
const bool perfect_aim = m_closest_kart_pos_data.angle < 0.2f;
switch(m_kart->getPowerup()->getType())
{
case PowerupManager::POWERUP_BUBBLEGUM:
{
Attachment::AttachmentType type = m_kart->getAttachment()->getType();
// Don't use shield when we have a swatter.
if (type == Attachment::ATTACH_SWATTER ||
type == Attachment::ATTACH_NOLOKS_SWATTER)
break;
// Check if a flyable (cake, ...) is close. If so, use bubblegum
// as shield
if (!m_kart->isShielded() &&
projectile_manager->projectileIsClose(m_kart,
m_ai_properties->m_shield_incoming_radius))
{
m_controls->m_fire = true;
m_controls->m_look_back = false;
break;
}
// Avoid dropping all bubble gums one after another
if (m_time_since_last_shot < 3.0f) break;
// Use bubblegum if the next kart behind is 'close' but not too close,
// or can't find a close kart for too long time
if ((m_closest_kart_pos_data.distance < 15.0f &&
m_closest_kart_pos_data.distance > 3.0f) ||
m_time_since_last_shot > 15.0f)
{
m_controls->m_fire = true;
m_controls->m_look_back = true;
break;
}
break; // POWERUP_BUBBLEGUM
}
case PowerupManager::POWERUP_CAKE:
{
// if the kart has a shield, do not break it by using a cake.
if (m_kart->getShieldTime() > min_bubble_time)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 25.0f &&
!m_closest_kart->isInvulnerable())
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_CAKE
case PowerupManager::POWERUP_BOWLING:
{
// if the kart has a shield, do not break it by using a bowling ball.
if (m_kart->getShieldTime() > min_bubble_time)
break;
// Leave some time between shots
if (m_time_since_last_shot < 1.0f) break;
if (m_closest_kart_pos_data.distance < 6.0f &&
(difficulty || perfect_aim))
{
m_controls->m_fire = true;
m_controls->m_look_back = fire_behind;
break;
}
break;
} // POWERUP_BOWLING
case PowerupManager::POWERUP_SWATTER:
{
// Squared distance for which the swatter works
float d2 = m_kart->getKartProperties()->getSwatterDistance();
// if the kart has a shield, do not break it by using a swatter.
if (m_kart->getShieldTime() > min_bubble_time)
break;
if (!m_closest_kart->isSquashed() &&
m_closest_kart_pos_data.distance < d2 &&
m_closest_kart->getSpeed() < m_kart->getSpeed())
{
m_controls->m_fire = true;
m_controls->m_look_back = false;
break;
}
break;
}
// Below powerups won't appear in battle mode, so skip them
case PowerupManager::POWERUP_ZIPPER:
break; // POWERUP_ZIPPER
case PowerupManager::POWERUP_PLUNGER:
break; // POWERUP_PLUNGER
case PowerupManager::POWERUP_SWITCH: // Don't handle switch
m_controls->m_fire = true; // (use it no matter what) for now
break; // POWERUP_SWITCH
case PowerupManager::POWERUP_PARACHUTE:
break; // POWERUP_PARACHUTE
case PowerupManager::POWERUP_ANVIL:
break; // POWERUP_ANVIL
case PowerupManager::POWERUP_RUBBERBALL:
break;
default:
Log::error("BattleAI",
"Invalid or unhandled powerup '%d' in default AI.",
m_kart->getPowerup()->getType());
assert(false);
}
if (m_controls->m_fire)
m_time_since_last_shot = 0.0f;
} // handleItems
//-----------------------------------------------------------------------------
void BattleAI::handleItemCollection(Vec3* aim_point, int* target_node)
{
float distance = 99999.9f;
const std::vector< std::pair<const Item*, int> >& item_list =
BattleGraph::get()->getItemList();
const unsigned int items_count = item_list.size();
if (item_list.empty())
{
// Notice: this should not happen, as it makes no sense
// for an arean without items, if so how can attack happen?
Log::fatal ("BattleAI",
"AI can't find any items in the arena, "
"maybe there is something wrong with the navmesh, "
"make sure it lies closely to the ground.");
return;
}
unsigned int closest_item_num = 0;
for (unsigned int i = 0; i < items_count; ++i)
{
const Item* item = item_list[i].first;
if (item->wasCollected()) continue;
if ((item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL) &&
(m_kart->getEnergy() >
m_kart->getKartProperties()->getNitroSmallContainer()))
continue; // Ignore nitro when already has some
Vec3 d = item->getXYZ() - m_kart->getXYZ();
if (d.length_2d() <= distance &&
(item->getType() == Item::ITEM_BONUS_BOX ||
item->getType() == Item::ITEM_NITRO_BIG ||
item->getType() == Item::ITEM_NITRO_SMALL))
{
closest_item_num = i;
distance = d.length_2d();
}
}
const Item *item_selected = item_list[closest_item_num].first;
if (item_selected->getType() == Item::ITEM_BONUS_BOX ||
item_selected->getType() == Item::ITEM_NITRO_BIG ||
item_selected->getType() == Item::ITEM_NITRO_SMALL)
{
*aim_point = item_selected->getXYZ();
*target_node = item_list[closest_item_num].second;
}
else
{
// Handle when all targets are swapped, which make AIs follow karts
*aim_point = m_closest_kart_point;
*target_node = m_closest_kart_node;
}
} // handleItemCollection
return m_world->isStartPhase();
} // isWaiting

View File

@ -21,139 +21,33 @@
#ifndef HEADER_BATTLE_AI_HPP
#define HEADER_BATTLE_AI_HPP
#undef AI_DEBUG
#ifdef AI_DEBUG
#include "graphics/irr_driver.hpp"
#endif
#include "karts/controller/ai_base_controller.hpp"
#include "race/race_manager.hpp"
#include "utils/random_generator.hpp"
#include "karts/controller/arena_ai.hpp"
class ThreeStrikesBattle;
class Vec3;
class Item;
namespace irr
/** The actual battle AI.
* \ingroup controller
*/
class BattleAI : public ArenaAI
{
namespace scene { class ISceneNode; }
namespace video { class ITexture; }
}
class BattleAI : public AIBaseController
{
private:
/** Holds the position info of targets. */
struct posData {bool behind; bool on_side; float angle; float distance;};
/** Used by handleBanana and UTurn, it tells whether to do left or right
* turning when steering is overridden. */
bool m_adjusting_side;
int m_closest_kart_node;
Vec3 m_closest_kart_point;
/** Pointer to the closest kart around this kart. */
AbstractKart *m_closest_kart;
posData m_closest_kart_pos_data;
posData m_cur_kart_pos_data;
/** Holds the current difficulty. */
RaceManager::Difficulty m_cur_difficulty;
/** Indicates that the steering of kart is overridden, and
* m_time_since_steering_overridden is counting down. */
bool m_is_steering_overridden;
/** Indicates that the kart is currently stuck, and m_time_since_reversing is
* counting down. */
bool m_is_stuck;
/** Indicates that the kart need a uturn to reach a node behind, and
* m_time_since_uturn is counting down. */
bool m_is_uturn;
/** Holds the unique node ai has driven through, useful to tell if AI is
* stuck by determine the size of this set. */
std::set <int> m_on_node;
/** Holds the corner points computed using the funnel algorithm that the AI
* will eventaully move through. See stringPull(). */
std::vector<Vec3> m_path_corners;
/** Holds the set of portals that the kart will cross when moving through
* polygon channel. See findPortals(). */
std::vector<std::pair<Vec3,Vec3> > m_portals;
/** The node(poly) at which the target point lies in. */
int m_target_node;
/** The target point. */
Vec3 m_target_point;
/** Time an item has been collected and not used. */
float m_time_since_last_shot;
/** This is a timer that counts down when the kart is reversing to get unstuck. */
float m_time_since_reversing;
/** This is a timer that counts down when the kart is starting to drive. */
float m_time_since_driving;
/** This is a timer that counts down when the steering of kart is overridden. */
float m_time_since_steering_overridden;
/** This is a timer that counts down when the kart is doing u-turn. */
float m_time_since_uturn;
void checkIfStuck(const float dt);
void checkPosition(const Vec3 &, posData*);
float determineTurnRadius(std::vector<Vec3>& points);
void findClosestKart(bool difficulty);
void findPortals(int start, int end);
void findTarget();
void handleAcceleration(const float dt);
void handleBanana();
void handleBraking();
void handleItems(const float dt);
void handleItemCollection(Vec3*, int*);
void handleSteering(const float dt);
void handleSwatter();
void handleUTurn(const float dt);
void stringPull(const Vec3&, const Vec3&);
protected:
/** Keep a pointer to world. */
ThreeStrikesBattle *m_world;
#ifdef AI_DEBUG
/** For debugging purpose: a sphere indicating where the AI
* is targeting at. */
irr::scene::ISceneNode *m_debug_sphere;
#endif
bool m_mini_skid;
virtual void findClosestKart(bool use_difficulty);
virtual void findTarget();
virtual int getCurrentNode() const;
virtual bool isWaiting() const;
virtual bool canSkid(float steer_fraction) { return m_mini_skid; }
public:
BattleAI(AbstractKart *kart);
~BattleAI();
virtual void update (float delta);
virtual void reset ();
virtual void crashed(const AbstractKart *k) {};
virtual void handleZipper(bool play_sound) {};
virtual void finishedRace(float time) {};
virtual void collectedItem(const Item &item, int add_info=-1,
float previous_energy=0) {};
virtual void setPosition(int p) {};
virtual bool isNetworkController() const { return false; }
virtual bool isPlayerController() const { return false; }
virtual void action(PlayerAction action, int value) {};
virtual void skidBonusTriggered() {};
virtual bool disableSlipstreamBonus() const {return 0; }
virtual void newLap(int lap) {};
};
#endif

View File

@ -74,12 +74,13 @@ private:
*that can be done, and end up setting their respective m_controls
*variable.
*/
void handleSteering(float dt);
void handleRescue(const float DELTA);
void handleSteering(float dt);
void handleRescue(const float DELTA);
void checkCrashes(const int STEPS, const Vec3& pos);
void findNonCrashingPoint(Vec3 *result);
int calcSteps();
void checkCrashes(const int STEPS, const Vec3& pos);
void findNonCrashingPoint(Vec3 *result);
int calcSteps();
virtual bool canSkid(float steer_fraction) { return false; }
public:
EndController(AbstractKart *kart,
Controller *prev_controller);

View File

@ -265,6 +265,13 @@ void PlayerController::update(float dt)
if (!history->replayHistory())
steer(dt, m_steer_val);
if (World::getWorld()->getPhase() == World::GOAL_PHASE)
{
m_controls->m_brake = false;
m_controls->m_accel = 0.0f;
return;
}
if (World::getWorld()->isStartPhase())
{
if (m_controls->m_accel || m_controls->m_brake ||

View File

@ -34,9 +34,6 @@ protected:
float m_penalty_time;
/** This variable is required for battle mode **/
int m_current_node;
virtual void steer(float, int);
// ------------------------------------------------------------------------
/** Called when this kart started too early and got a start penalty. */
@ -58,10 +55,6 @@ public:
{
};
// ------------------------------------------------------------------------
unsigned int getCurrentNode() const { return m_current_node; }
// ------------------------------------------------------------------------
void setCurrentNode(int i) { m_current_node = i; }
// ------------------------------------------------------------------------
virtual bool isPlayerController() const OVERRIDE { return true; }
// ------------------------------------------------------------------------
virtual bool isLocalPlayerController() const OVERRIDE { return true; }

View File

@ -1238,8 +1238,6 @@ void SkiddingAI::handleItems(const float dt)
}
break; // POWERUP_BUBBLEGUM
}
// All the thrown/fired items might be improved by considering the angle
// towards m_kart_ahead.
case PowerupManager::POWERUP_CAKE:
{
// if the kart has a shield, do not break it by using a cake.
@ -1297,6 +1295,22 @@ void SkiddingAI::handleItems(const float dt)
// slower, so it should take longer to hit something which
// can result in changing our target.
if(m_time_since_last_shot < 5.0f) break;
// Consider angle towards karts
bool straight_behind = false;
bool straight_ahead = false;
if (m_kart_behind)
{
posData behind_pos = {0};
checkPosition(m_kart_behind->getXYZ(), &behind_pos);
if (behind_pos.angle < 0.2f) straight_behind = true;
}
if (m_kart_ahead)
{
posData ahead_pos = {0};
checkPosition(m_kart_ahead->getXYZ(), &ahead_pos);
if (ahead_pos.angle < 0.2f) straight_ahead = true;
}
// Bowling balls are slower, so only fire on closer karts - but when
// firing backwards, the kart can be further away, since the ball
// acts a bit like a mine (and the kart is racing towards it, too)
@ -1307,7 +1321,8 @@ void SkiddingAI::handleItems(const float dt)
: m_distance_ahead;
m_controls->m_fire = ( (fire_backwards && distance < 30.0f) ||
(!fire_backwards && distance <10.0f) ) &&
m_time_since_last_shot > 3.0f;
m_time_since_last_shot > 3.0f &&
(straight_behind || straight_ahead);
if(m_controls->m_fire)
m_controls->m_look_back = fire_backwards;
break;
@ -2195,7 +2210,7 @@ void SkiddingAI::handleCurve()
* AIBaseLapController.
* \return True if the kart should skid.
*/
bool SkiddingAI::doSkid(float steer_fraction)
bool SkiddingAI::canSkid(float steer_fraction)
{
if(fabsf(steer_fraction)>1.5f)
{
@ -2304,7 +2319,7 @@ bool SkiddingAI::doSkid(float steer_fraction)
m_kart->getIdent().c_str());
#endif
return false;
} // doSkid
} // canSkid
//-----------------------------------------------------------------------------
/** Converts the steering angle to a lr steering in the range of -1 to 1.
@ -2326,7 +2341,7 @@ void SkiddingAI::setSteering(float angle, float dt)
// Use a simple finite state machine to make sure to randomly decide
// whether to skid or not only once per skid section. See docs for
// m_skid_probability_state for more details.
if(!doSkid(steer_fraction))
if(!canSkid(steer_fraction))
{
m_skid_probability_state = SKID_PROBAB_NOT_YET;
m_controls->m_skid = KartControl::SC_NONE;

View File

@ -94,7 +94,7 @@ the AI does the following steps:
behaviour.
The function handleSteering() then calls setSteering() to set the
actually steering amount. The latter function also decides if skidding
should be done or not (by calling doSkid()).
should be done or not (by calling canSkid()).
- decide if to try to collect or avoid items (handeItems).
It considers all items on quads between the current quad of the kart
and the quad the AI is aiming at (see handleSteering). If it finds
@ -270,7 +270,7 @@ private:
const Vec3 &end,
Vec3 *center,
float *radius);
virtual bool doSkid(float steer_fraction);
virtual bool canSkid(float steer_fraction);
virtual void setSteering(float angle, float dt);
void handleCurve();

View File

@ -0,0 +1,260 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "karts/controller/soccer_ai.hpp"
#include "items/attachment.hpp"
#include "items/powerup.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/kart_control.hpp"
#include "karts/kart_properties.hpp"
#include "modes/soccer_world.hpp"
#include "tracks/battle_graph.hpp"
#ifdef AI_DEBUG
#include "irrlicht.h"
#include <iostream>
using namespace irr;
using namespace std;
#endif
SoccerAI::SoccerAI(AbstractKart *kart)
: ArenaAI(kart)
{
reset();
#ifdef AI_DEBUG
video::SColor col_debug(128, 128, 0, 0);
m_debug_sphere = irr_driver->addSphere(1.0f, col_debug);
m_debug_sphere->setVisible(true);
#endif
m_world = dynamic_cast<SoccerWorld*>(World::getWorld());
m_track = m_world->getTrack();
// Don't call our own setControllerName, since this will add a
// billboard showing 'AIBaseController' to the kart.
Controller::setControllerName("SoccerAI");
} // SoccerAI
//-----------------------------------------------------------------------------
SoccerAI::~SoccerAI()
{
#ifdef AI_DEBUG
irr_driver->removeNode(m_debug_sphere);
#endif
} // ~SoccerAI
//-----------------------------------------------------------------------------
/** Resets the AI when a race is restarted.
*/
void SoccerAI::reset()
{
ArenaAI::reset();
AIBaseController::reset();
m_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);
}
} // reset
//-----------------------------------------------------------------------------
void SoccerAI::update(float dt)
{
m_saving_ball = false;
if (World::getWorld()->getPhase() == World::GOAL_PHASE)
{
m_controls->m_brake = false;
m_controls->m_accel = 0.0f;
AIBaseController::update(dt);
return;
}
ArenaAI::update(dt);
} // update
//-----------------------------------------------------------------------------
void SoccerAI::findClosestKart(bool use_difficulty)
{
float distance = 99999.9f;
const unsigned int n = m_world->getNumKarts();
int closest_kart_num = 0;
for (unsigned int i = 0; i < n; i++)
{
const AbstractKart* kart = m_world->getKart(i);
if (kart->isEliminated()) continue;
if (kart->getWorldKartId() == m_kart->getWorldKartId())
continue; // Skip the same kart
if (m_world->getKartTeam(kart
->getWorldKartId()) == m_world->getKartTeam(m_kart
->getWorldKartId()))
continue; // Skip the kart with the same team
Vec3 d = kart->getXYZ() - m_kart->getXYZ();
if (d.length_2d() <= distance)
{
distance = d.length_2d();
closest_kart_num = i;
}
}
const AbstractKart* closest_kart = m_world->getKart(closest_kart_num);
m_closest_kart_node = m_world->getKartNode(closest_kart_num);
m_closest_kart_point = closest_kart->getXYZ();
m_closest_kart = m_world->getKart(closest_kart_num);
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
} // findClosestKart
//-----------------------------------------------------------------------------
void SoccerAI::findTarget()
{
// 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())
{
m_target_node = m_world->getBallNode();
m_target_point = correctBallPosition(m_world->getBallPosition());
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))
collectItemInArena(&m_target_point , &m_target_node);
else
{
m_target_node = m_world->getBallNode();
m_target_point = correctBallPosition(m_world->getBallPosition());
}
} // findTarget
//-----------------------------------------------------------------------------
Vec3 SoccerAI::correctBallPosition(const Vec3& orig_pos)
{
// Notice: Build with AI_DEBUG and change camera target to an AI kart,
// to debug or see how AI steer with the ball
posData ball_pos = {0};
posData goal_pos = {0};
Vec3 ball_lc(0, 0, 0);
checkPosition(orig_pos, &ball_pos, &ball_lc);
// 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);
if (goal_pos.behind)
{
if (goal_pos.angle > 0.3f && ball_pos.distance < 3.0f &&
!ball_pos.behind)
{
// Only steer with ball if same sides for ball and goal
if (ball_pos.on_side && goal_pos.on_side)
{
ball_lc = ball_lc + Vec3 (1, 0, 1);
return m_kart->getTrans()(ball_lc);
}
else if (!ball_pos.on_side && !goal_pos.on_side)
{
ball_lc = ball_lc - Vec3 (1, 0, 0) + Vec3 (0, 0, 1);
return m_kart->getTrans()(ball_lc);
}
else
m_controls->m_brake = true;
}
else
{
// This case is facing straight ahead opposite goal
// (which is straight behind itself), apply more
// offset for skidding, to save the ball from behind
// scored.
// Notice: this assume map maker make soccer field
// with two goals facing each other straight
ball_lc = (goal_pos.on_side ? ball_lc - Vec3 (2, 0, 0) +
Vec3 (0, 0, 2) : ball_lc + Vec3 (2, 0, 2));
if (ball_pos.distance < 3.0f &&
(m_cur_difficulty == RaceManager::DIFFICULTY_HARD ||
m_cur_difficulty == RaceManager::DIFFICULTY_BEST))
m_saving_ball = true;
return m_kart->getTrans()(ball_lc);
}
}
if (ball_pos.distance < 3.0f &&
!ball_pos.behind && !goal_pos.behind)
{
if (goal_pos.angle < 0.5f)
return orig_pos;
else
{
// Same with above
if (ball_pos.on_side && goal_pos.on_side)
{
ball_lc = ball_lc + Vec3 (1, 0, 1);
return m_kart->getTrans()(ball_lc);
}
else if (!ball_pos.on_side && !goal_pos.on_side)
{
ball_lc = ball_lc - Vec3 (1, 0, 0) + Vec3 (0, 0, 1);
return m_kart->getTrans()(ball_lc);
}
else
m_controls->m_brake = true;
}
}
return orig_pos;
} // correctBallPosition
// ------------------------------------------------------------------------
int SoccerAI::getCurrentNode() const
{
return m_world->getKartNode(m_kart->getWorldKartId());
} // getCurrentNode
// ------------------------------------------------------------------------
bool SoccerAI::isWaiting() const
{
return m_world->isStartPhase();
} // isWaiting

View File

@ -0,0 +1,54 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2016 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_SOCCER_AI_HPP
#define HEADER_SOCCER_AI_HPP
#include "karts/controller/arena_ai.hpp"
class SoccerWorld;
class Vec3;
class Item;
/** The actual soccer AI.
* \ingroup controller
*/
class SoccerAI : public ArenaAI
{
private:
/** Keep a pointer to world. */
SoccerWorld *m_world;
SoccerTeam m_cur_team;
bool m_saving_ball;
Vec3 correctBallPosition(const Vec3&);
virtual void findClosestKart(bool use_difficulty);
virtual void findTarget();
virtual int getCurrentNode() const;
virtual bool isWaiting() const;
virtual bool canSkid(float steer_fraction) { return m_saving_ball; }
public:
SoccerAI(AbstractKart *kart);
~SoccerAI();
virtual void update (float delta);
virtual void reset ();
};
#endif

View File

@ -44,6 +44,7 @@
#include "karts/kart_gfx.hpp"
#include "karts/rescue_animation.hpp"
#include "modes/overworld.hpp"
#include "modes/soccer_world.hpp"
#include "modes/world.hpp"
#include "io/file_manager.hpp"
#include "items/attachment.hpp"
@ -883,7 +884,6 @@ void Kart::setRaceResult()
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL)
{
// TODO NetworkController?
if (m_controller->isLocalPlayerController()) // if player is on this computer
{
PlayerProfile *player = PlayerManager::getCurrentPlayer();
@ -920,8 +920,8 @@ void Kart::setRaceResult()
}
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
{
// TODO complete together with soccer ai!
m_race_result = true;
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
m_race_result = sw->getKartSoccerResult(this->getWorldKartId());
}
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG)
{
@ -931,7 +931,7 @@ void Kart::setRaceResult()
else
Log::warn("Kart", "Unknown game mode given.");
} // setKartResult
} // setRaceResult
//-----------------------------------------------------------------------------
/** Called when an item is collected. It will either adjust the collected

View File

@ -138,6 +138,7 @@ KartModel::KartModel(bool is_master)
m_animation_frame[i]=-1;
m_animation_speed = 25;
m_current_animation = AF_DEFAULT;
m_play_non_loop = false;
} // KartModel
// ----------------------------------------------------------------------------
@ -682,11 +683,12 @@ void KartModel::finishedRace()
/** Enables- or disables the end animation.
* \param type The type of animation to play.
*/
void KartModel::setAnimation(AnimationFrameType type)
void KartModel::setAnimation(AnimationFrameType type, bool play_non_loop)
{
// if animations disabled, give up
if (m_animated_node == NULL) return;
m_play_non_loop = play_non_loop;
m_current_animation = type;
if(m_current_animation==AF_DEFAULT)
{
@ -825,6 +827,12 @@ void KartModel::update(float dt, float distance, float steer, float speed)
// If animations are disabled, stop here
if (m_animated_node == NULL) return;
if (m_play_non_loop && m_animated_node->getLoopMode() == true)
{
m_play_non_loop = false;
this->setAnimation(AF_DEFAULT);
}
// Update the speed-weighted objects' animations
if (m_kart != NULL)
{

View File

@ -207,9 +207,14 @@ private:
/** True if this is the master copy, managed by KartProperties. This
* is mainly used for debugging, e.g. the master copies might not have
* anything attached to it etc. */
* anything attached to it etc. */
bool m_is_master;
/** True if the animation played is non-loop, which will reset to
* AF_DEFAULT after first loop ends. Mainly used in soccer mode for
* animation playing after scored. */
bool m_play_non_loop;
void loadWheelInfo(const XMLNode &node,
const std::string &wheel_name, int index);
@ -306,7 +311,7 @@ public:
AnimationFrameType getAnimation() { return m_current_animation; }
// ------------------------------------------------------------------------
/** Enables- or disables the end animation. */
void setAnimation(AnimationFrameType type);
void setAnimation(AnimationFrameType type, bool play_non_loop = false);
// ------------------------------------------------------------------------
/** Sets the kart this model is currently used for */
void setKart(AbstractKart* k) { m_kart = k; }

View File

@ -27,16 +27,17 @@
#include "karts/kart_properties.hpp"
#include "karts/rescue_animation.hpp"
#include "karts/controller/local_player_controller.hpp"
#include "karts/controller/soccer_ai.hpp"
#include "physics/physics.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/check_goal.hpp"
#include "tracks/check_manager.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "utils/constants.hpp"
#include <IMeshSceneNode.h>
#include <string>
//-----------------------------------------------------------------------------
/** Constructor. Sets up the clock mode etc.
*/
@ -45,7 +46,7 @@ SoccerWorld::SoccerWorld() : WorldWithRank()
if(race_manager->hasTimeTarget())
{
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
countDownReachedZero = false;
m_count_down_reached_zero = false;
}
else
{
@ -69,24 +70,19 @@ SoccerWorld::~SoccerWorld()
*/
void SoccerWorld::init()
{
m_kart_team_map.clear();
m_kart_position_map.clear();
WorldWithRank::init();
m_display_rank = false;
m_goal_timer = 0.f;
m_lastKartToHitBall = -1;
// check for possible problems if AI karts were incorrectly added
if(getNumKarts() > race_manager->getNumPlayers())
{
Log::error("[SoccerWorld]", "No AI exists for this game mode");
exit(1);
}
m_goal_timer = 0.0f;
m_ball_hitter = -1;
m_goal_target = race_manager->getMaxGoal();
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
} // init
//-----------------------------------------------------------------------------
/** Called when a battle is restarted.
/** Called when a soccer game is restarted.
*/
void SoccerWorld::reset()
{
@ -94,31 +90,37 @@ void SoccerWorld::reset()
if(race_manager->hasTimeTarget())
{
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
countDownReachedZero = false;
m_count_down_reached_zero = false;
}
else WorldStatus::setClockMode(CLOCK_CHRONO);
m_can_score_points = true;
memset(m_team_goals, 0, sizeof(m_team_goals));
m_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;
// Reset original positions for the soccer balls
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
assert(tom);
m_redScorers.clear();
m_redScoreTimes.clear();
m_blueScorers.clear();
m_blueScoreTimes.clear();
m_lastKartToHitBall = -1;
PtrVector<TrackObject>& objects = tom->getObjects();
for(unsigned int i=0; i<objects.size(); i++)
for (unsigned int i = 0; i < objects.size(); i++)
{
TrackObject* obj = objects.get(i);
if(!obj->isSoccerBall())
continue;
obj->reset();
obj->getPhysicalObject()->reset();
m_ball = obj;
// Handle one ball only
break;
}
if (!m_ball)
Log::fatal("SoccerWorld","Ball is missing in soccer field, abort.");
if (m_goal_sound != NULL &&
m_goal_sound->getStatus() == SFXBase::SFX_PLAYING)
@ -127,6 +129,10 @@ void SoccerWorld::reset()
}
initKartList();
resetAllNodes();
initGoalNodes();
resetBall();
} // reset
//-----------------------------------------------------------------------------
@ -148,111 +154,127 @@ void SoccerWorld::update(float dt)
WorldWithRank::update(dt);
WorldWithRank::updateTrack(dt);
updateBallPosition(dt);
if (m_track->hasNavMesh())
{
updateKartNodes();
updateDefenders();
}
if (world->getPhase() == World::GOAL_PHASE)
{
if (m_goal_timer == 0.0f)
{
// Stop all karts
for (unsigned int i = 0; i < m_karts.size(); i++)
m_karts[i]->setVelocity(btVector3(0, 0, 0));
}
m_goal_timer += dt;
if (m_goal_timer > 3.0f)
{
world->setPhase(WorldStatus::RACE_PHASE);
m_goal_timer = 0;
m_goal_timer = 0.0f;
if (!isRaceOver())
{
// Reset all karts
for (unsigned int i = 0; i < m_karts.size(); i++)
moveKartAfterRescue(m_karts[i]);
}
}
}
} // update
//-----------------------------------------------------------------------------
void SoccerWorld::onCheckGoalTriggered(bool first_goal)
{
if (isRaceOver())
if (isRaceOver() || isStartPhase())
return;
if (m_can_score_points)
{
m_team_goals[first_goal ? 0 : 1]++;
(first_goal ? m_red_goal++ : m_blue_goal++);
World *world = World::getWorld();
world->setPhase(WorldStatus::GOAL_PHASE);
m_goal_sound->play();
if(m_lastKartToHitBall != -1)
if (m_ball_hitter != -1)
{
if(first_goal)
ScorerData sd;
sd.m_id = m_ball_hitter;
sd.m_correct_goal = isCorrectGoal(m_ball_hitter, first_goal);
if (sd.m_correct_goal)
{
m_redScorers.push_back(m_lastKartToHitBall);
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)
{
// 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_redScoreTimes.push_back(race_manager->getTimeTarget() - world->getTime());
{
m_red_score_times.push_back(race_manager
->getTimeTarget() - world->getTime());
}
else
m_redScoreTimes.push_back(world->getTime());
m_red_score_times.push_back(world->getTime());
}
else
{
m_blueScorers.push_back(m_lastKartToHitBall);
if(race_manager->hasTimeTarget())
m_blueScoreTimes.push_back(race_manager->getTimeTarget() - world->getTime());
m_blue_scorers.push_back(sd);
if (race_manager->hasTimeTarget())
{
m_blue_score_times.push_back(race_manager
->getTimeTarget() - world->getTime());
}
else
m_blueScoreTimes.push_back(world->getTime());
m_blue_score_times.push_back(world->getTime());
}
}
}
// Reset original positions for the soccer balls
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
assert(tom);
PtrVector<TrackObject>& objects = tom->getObjects();
for(unsigned int i=0; i<objects.size(); i++)
{
TrackObject* obj = objects.get(i);
if(!obj->isSoccerBall())
continue;
obj->reset();
obj->getPhysicalObject()->reset();
}
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;
//for(int i=0 ; i < getNumKarts() ; i++
/*if(World::getWorld()->getTrack()->isAutoRescueEnabled() &&
!getKartAnimation() && fabs(getRoll())>60*DEGREE_TO_RAD &&
fabs(getSpeed())<3.0f )
{
new RescueAnimation(this, true);
}*/
// TODO: rescue the karts
} // onCheckGoalTriggered
//-----------------------------------------------------------------------------
/** Sets the last kart that hit the ball, to be able to
* identify the scorer later.
*/
void SoccerWorld::setLastKartTohitBall(unsigned int kartId)
* identify the scorer later.
*/
void SoccerWorld::setBallHitter(unsigned int kart_id)
{
m_lastKartToHitBall = kartId;
} // setLastKartTohitBall
m_ball_hitter = kart_id;
} // setBallHitter
//-----------------------------------------------------------------------------
/** The battle is over if only one kart is left, or no player kart.
/** The soccer game is over if time up or either team wins.
*/
bool SoccerWorld::isRaceOver()
{
// for tests : never over when we have a single player there :)
if (race_manager->getNumPlayers() < 2)
{
return false;
}
if(race_manager->hasTimeTarget())
{
return countDownReachedZero;
return m_count_down_reached_zero;
}
// One team scored the target goals ...
else
{
return (getScore(0) >= m_goal_target || getScore(1) >= m_goal_target);
return (getScore(SOCCER_TEAM_BLUE) >= m_goal_target ||
getScore(SOCCER_TEAM_RED) >= m_goal_target);
}
} // isRaceOver
@ -270,116 +292,54 @@ void SoccerWorld::terminateRace()
//-----------------------------------------------------------------------------
/** Called when the match time ends.
*/
*/
void SoccerWorld::countdownReachedZero()
{
countDownReachedZero = true;
m_count_down_reached_zero = true;
} // countdownReachedZero
//-----------------------------------------------------------------------------
/** Returns the data to display in the race gui.
*/
void SoccerWorld::getKartsDisplayInfo(
std::vector<RaceGUIBase::KartIconDisplayInfo> *info)
{
// TODO!!
/*
const unsigned int kart_amount = getNumKarts();
for(unsigned int i = 0; i < kart_amount ; i++)
{
RaceGUIBase::KartIconDisplayInfo& rank_info = (*info)[i];
// reset color
rank_info.lap = -1;
AbstractKart* kart = getKart(i);
switch(kart->getSoccerTeam())
{
case SOCCER_TEAM_BLUE:
rank_info.r = 0.0f;
rank_info.g = 0.0f;
rank_info.b = 0.7f;
break;
case SOCCER_TEAM_RED:
rank_info.r = 0.9f;
rank_info.g = 0.0f;
rank_info.b = 0.0f;
break;
default:
assert(false && "Soccer team not set to blue or red");
rank_info.r = 0.0f;
rank_info.g = 0.0f;
rank_info.b = 0.0f;
}
}
*/
} // getKartsDisplayInfo
//-----------------------------------------------------------------------------
/** Set position and team for the karts */
void SoccerWorld::initKartList()
{
const unsigned int kart_amount = (unsigned int)m_karts.size();
int team_karts_amount[NB_SOCCER_TEAMS];
memset(team_karts_amount, 0, sizeof(team_karts_amount));
//Loading the indicator textures
irr::video::ITexture *redTeamTexture =
irr::video::ITexture *red =
irr_driver->getTexture(FileManager::GUI, "soccer_player_red.png");
irr::video::ITexture *blueTeamTexture =
irr::video::ITexture *blue =
irr_driver->getTexture(FileManager::GUI, "soccer_player_blue.png");
//Assigning indicators
for(unsigned int i=0; i<kart_amount; i++)
{
scene::ISceneNode *arrowNode;
scene::ISceneNode *arrow_node;
float arrow_pos_height = m_karts[i]->getKartModel()->getHeight()+0.5f;
SoccerTeam team = race_manager->getKartInfo(i).getSoccerTeam();
SoccerTeam team = getKartTeam(i);
arrowNode = irr_driver->addBillboard(core::dimension2d<irr::f32>(0.3f,0.3f),
team==SOCCER_TEAM_RED ? redTeamTexture : blueTeamTexture,
m_karts[i]->getNode(), true);
arrow_node = irr_driver->addBillboard(core::dimension2d<irr::f32>(0.3f,
0.3f), team == SOCCER_TEAM_BLUE ? blue : red, m_karts[i]
->getNode(), true);
arrowNode->setPosition(core::vector3df(0, arrow_pos_height, 0));
arrow_node->setPosition(core::vector3df(0, arrow_pos_height, 0));
}
// Compute start positions for each team
int team_cur_position[NB_SOCCER_TEAMS];
team_cur_position[0] = 1;
for(int i=1 ; i < (int)NB_SOCCER_TEAMS ; i++)
team_cur_position[i] = team_karts_amount[i-1] + team_cur_position[i-1];
// Set kart positions, ordering them by team
for(unsigned int n=0; n<kart_amount; n++)
{
SoccerTeam team = race_manager->getKartInfo(n).getSoccerTeam();
#ifdef DEBUG
// In debug mode it's possible to play soccer with a single player
// (in artist debug mode). Avoid overwriting memory in this case.
if(team==SOCCER_TEAM_NONE) team=SOCCER_TEAM_RED;
#endif
m_karts[n]->setPosition(team_cur_position[team]);
team_cur_position[team]++;
} // next kart
}
} // initKartList
//-----------------------------------------------------------------------------
int SoccerWorld::getScore(unsigned int i)
bool SoccerWorld::getKartSoccerResult(unsigned int kart_id) const
{
return m_team_goals[i];
} // getScore
if (m_red_scorers.size() == m_blue_scorers.size()) return true;
//-----------------------------------------------------------------------------
int SoccerWorld::getTeamLeader(unsigned int team)
{
for(unsigned int i = 0; i< m_karts.size(); i++)
{
if(race_manager->getKartInfo(i).getSoccerTeam() == (SoccerTeam) team)
return i;
}
return -1;
} // getTeamLeader
bool red_win = m_red_scorers.size() > m_blue_scorers.size();
SoccerTeam team = getKartTeam(kart_id);
if ((red_win && team == SOCCER_TEAM_RED) ||
(!red_win && team == SOCCER_TEAM_BLUE))
return true;
else
return false;
} // getKartSoccerResult
//-----------------------------------------------------------------------------
AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
@ -387,19 +347,55 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
RaceManager::KartType kart_type,
PerPlayerDifficulty difficulty)
{
int posIndex = index;
int position = index+1;
int cur_red = getTeamNum(SOCCER_TEAM_RED);
int cur_blue = getTeamNum(SOCCER_TEAM_BLUE);
int pos_index = 0;
int position = index + 1;
SoccerTeam team = SOCCER_TEAM_BLUE;
if(race_manager->getKartInfo(index).getSoccerTeam() == SOCCER_TEAM_RED)
if (kart_type == RaceManager::KT_AI)
{
if(index % 2 != 1) posIndex += 1;
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);
}
}
else
{
team = (index % 2 == 0 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED);
}
m_kart_team_map[index] = team;
}
else
{
if(index % 2 != 0) posIndex += 1;
int rm_id = index -
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
assert(rm_id >= 0);
team = race_manager->getKartInfo(rm_id).getSoccerTeam();
m_kart_team_map[index] = team;
}
btTransform init_pos = getStartTransform(posIndex);
// Notice: In blender, please set 1,3,5,7... for blue starting position;
// 2,4,6,8... for red.
if (team == SOCCER_TEAM_BLUE)
{
pos_index = 1 + 2 * cur_blue;
}
else
{
pos_index = 2 + 2 * cur_red;
}
btTransform init_pos = getStartTransform(pos_index - 1);
m_kart_position_map[index] = (unsigned)(pos_index - 1);
AbstractKart *new_kart = new Kart(kart_ident, index, position, init_pos,
difficulty);
@ -434,4 +430,213 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
} // createKart
//-----------------------------------------------------------------------------
/** Updates the m_kart_on_node value of each kart to localize it
* on the navigation mesh.
*/
void SoccerWorld::updateKartNodes()
{
if (isRaceOver()) return;
const unsigned int n = getNumKarts();
for (unsigned int i = 0; i < n; i++)
{
if (m_karts[i]->isEliminated()) continue;
m_kart_on_node[i] = BattleGraph::get()->pointToNode(m_kart_on_node[i],
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
}
} // updateKartNodes
//-----------------------------------------------------------------------------
/** Localize the ball on the navigation mesh.
*/
void SoccerWorld::updateBallPosition(float dt)
{
if (isRaceOver()) return;
m_ball_position = m_ball->getPresentation<TrackObjectPresentationMesh>()
->getNode()->getPosition();
if (m_track->hasNavMesh())
{
m_ball_on_node = BattleGraph::get()->pointToNode(m_ball_on_node,
m_ball_position, true/*ignore_vertical*/);
if (m_ball_on_node == BattleGraph::UNKNOWN_POLY &&
World::getWorld()->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();
for (unsigned int i = 0; i < m_karts.size(); i++)
moveKartAfterRescue(m_karts[i]);
}
}
else
m_ball_invalid_timer = 0.0f;
}
} // 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()
{
m_kart_on_node.clear();
m_kart_on_node.resize(m_karts.size());
for(unsigned int n=0; n<m_karts.size(); n++)
m_kart_on_node[n] = BattleGraph::UNKNOWN_POLY;
m_ball_on_node = BattleGraph::UNKNOWN_POLY;
m_ball_position = Vec3(0, 0, 0);
m_red_goal_node = BattleGraph::UNKNOWN_POLY;
m_blue_goal_node = BattleGraph::UNKNOWN_POLY;
} // resetAllNodes
//-----------------------------------------------------------------------------
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;
} // getKartTeam
//-----------------------------------------------------------------------------
bool SoccerWorld::isCorrectGoal(unsigned int kart_id, bool first_goal) const
{
SoccerTeam team = getKartTeam(kart_id);
if (first_goal)
{
if (team == SOCCER_TEAM_RED)
return true;
}
else if (!first_goal)
{
if (team == SOCCER_TEAM_BLUE)
return true;
}
return false;
} // isCorrectGoal
//-----------------------------------------------------------------------------
void SoccerWorld::updateDefenders()
{
if (isRaceOver()) return;
float distance = 99999.9f;
int defender = -1;
// Check for red team
for (unsigned int i = 0; i < (unsigned)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)
{
defender = i;
distance = d.length_2d();
}
}
if (defender != -1) m_red_defender = defender;
distance = 99999.9f;
defender = -1;
// Check for blue team
for (unsigned int i = 0; i < (unsigned)m_karts.size(); ++i)
{
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)
{
defender = i;
distance = d.length_2d();
}
}
if (defender != -1) m_blue_defender = defender;
} // updateDefenders
//-----------------------------------------------------------------------------
int SoccerWorld::getTeamNum(SoccerTeam team) const
{
int total = 0;
if (m_kart_team_map.empty()) return total;
for (unsigned int i = 0; i < (unsigned)m_karts.size(); ++i)
{
if (team == getKartTeam(m_karts[i]->getWorldKartId())) total++;
}
return total;
} // getTeamNum
//-----------------------------------------------------------------------------
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;
} // getRescuePositionIndex
//-----------------------------------------------------------------------------
void SoccerWorld::resetBall()
{
m_ball->reset();
m_ball->getPhysicalObject()->reset();
} // resetBall

View File

@ -23,40 +23,91 @@
#include "states_screens/race_gui_base.hpp"
#include "karts/abstract_kart.hpp"
#include <IMesh.h>
#include <string>
#define CLEAR_SPAWN_RANGE 5
class PhysicalObject;
class AbstractKart;
class Controller;
class TrackObject;
/**
* \brief An implementation of World, to provide the soccer game mode
/** An implementation of World, to provide the soccer game mode
* Notice: In soccer world, true goal means blue, false means red.
* \ingroup modes
*/
class SoccerWorld : public WorldWithRank
{
public:
class ScorerData
{
public:
/** World ID of kart which scores. */
unsigned int m_id;
/** Whether this goal is socred correctly (identify for own goal). */
bool m_correct_goal;
}; // ScorerData
protected:
virtual AbstractKart *createKart(const std::string &kart_ident, int index,
int local_player_id, int global_player_id,
RaceManager::KartType type,
PerPlayerDifficulty difficulty);
private:
/** Number of goals each team scored */
int m_team_goals[NB_SOCCER_TEAMS];
/** Keep a pointer to the track object of soccer ball */
TrackObject* m_ball;
/** Number of goals needed to win */
int m_goal_target;
bool countDownReachedZero;
bool m_count_down_reached_zero;
/** Whether or not goals can be scored (they are disabled when a point is scored
and re-enabled when the next game can be played)*/
bool m_can_score_points;
SFXBase *m_goal_sound;
/** Timer for displaying goal text*/
float m_goal_timer;
int m_lastKartToHitBall;
std::vector<int> m_redScorers;
std::vector<float> m_redScoreTimes;
std::vector<int> m_blueScorers;
std::vector<float> m_blueScoreTimes;
float m_ball_invalid_timer;
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;
std::vector<float> m_blue_score_times;
std::map<int, SoccerTeam> m_kart_team_map;
std::map<int, unsigned int> m_kart_position_map;
/** 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;
/** 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();
/** Get number of teammates in a team, used by starting position assign. */
int getTeamNum(SoccerTeam team) const;
public:
SoccerWorld();
@ -72,41 +123,61 @@ public:
// overriding World methods
virtual void reset();
virtual unsigned int getRescuePositionIndex(AbstractKart *kart) OVERRIDE;
virtual bool useFastMusicNearEnd() const { return false; }
virtual void getKartsDisplayInfo(
std::vector<RaceGUIBase::KartIconDisplayInfo> *info);
int getScore(unsigned int i);
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) {}
virtual bool raceHasLaps() { return false; }
virtual const std::string& getIdent() const;
virtual void update(float dt);
// ------------------------------------------------------------------------
void onCheckGoalTriggered(bool first_goal);
int getTeamLeader(unsigned int i);
void setLastKartTohitBall(unsigned int kartId);
std::vector<int> getScorers(unsigned int team)
// ------------------------------------------------------------------------
void setBallHitter(unsigned int kart_id);
// ------------------------------------------------------------------------
/** Get the soccer result of kart in soccer world (including AIs) */
bool getKartSoccerResult(unsigned int kart_id) const;
// ------------------------------------------------------------------------
/** Get the team of kart in soccer world (including AIs) */
SoccerTeam getKartTeam(unsigned int kart_id) const;
// ------------------------------------------------------------------------
const int getScore(SoccerTeam team) const
{ return (team == SOCCER_TEAM_BLUE ? m_blue_goal : m_red_goal); }
// ------------------------------------------------------------------------
const std::vector<ScorerData>& getScorers(SoccerTeam team) const
{ return (team == SOCCER_TEAM_BLUE ? m_blue_scorers : m_red_scorers); }
// ------------------------------------------------------------------------
const std::vector<float>& getScoreTimes(SoccerTeam team) const
{
if(team == 0)
return m_redScorers;
else
return m_blueScorers;
return (team == SOCCER_TEAM_BLUE ?
m_blue_score_times : m_red_score_times);
}
std::vector<float> getScoreTimes(unsigned int team)
// ------------------------------------------------------------------------
const int& getKartNode(unsigned int kart_id) const
{ return m_kart_on_node[kart_id]; }
// ------------------------------------------------------------------------
const int& getBallNode() const
{ return m_ball_on_node; }
// ------------------------------------------------------------------------
const Vec3& getBallPosition() const
{ return m_ball_position; }
// ------------------------------------------------------------------------
const int& getGoalNode(SoccerTeam team) const
{
if(team == 0)
return m_redScoreTimes;
else
return m_blueScoreTimes;
return (team == SOCCER_TEAM_BLUE ? m_blue_goal_node : m_red_goal_node);
}
// ------------------------------------------------------------------------
bool isCorrectGoal(unsigned int kart_id, bool first_goal) const;
// ------------------------------------------------------------------------
const int& getDefender(SoccerTeam team) const
{
return (team == SOCCER_TEAM_BLUE ? m_blue_defender : m_red_defender);
}
private:
void initKartList();
protected:
virtual AbstractKart *createKart(const std::string &kart_ident, int index,
int local_player_id, int global_player_id,
RaceManager::KartType type,
PerPlayerDifficulty difficulty);
}; // SoccerWorld

View File

@ -453,69 +453,9 @@ void ThreeStrikesBattle::updateKartNodes()
{
if (m_karts[i]->isEliminated()) continue;
const int saved_current_node = m_kart_info[i].m_on_node;
if (saved_current_node == BattleGraph::UNKNOWN_POLY)
{
// Try all nodes in the battle graph
bool found = false;
unsigned int node = 0;
while (!found && node < BattleGraph::get()->getNumNodes())
{
const NavPoly& p_all = BattleGraph::get()->getPolyOfNode(node);
if ((p_all.pointInPoly(m_karts[i]->getXYZ())))
{
m_kart_info[i].m_on_node = node;
found = true;
}
node++;
}
}
else
{
// Check if the kart is still on the same node
const NavPoly& p_cur = BattleGraph::get()
->getPolyOfNode(saved_current_node);
if (p_cur.pointInPoly(m_karts[i]->getXYZ())) continue;
// If not then check all adjacent polys
const std::vector<int>& adjacents = NavMesh::get()
->getAdjacentPolys(saved_current_node);
// Set current node to unknown so that if no adjacent polygons,
// we look everywhere the next time updateKartNodes is called.
// This is useful in cases when you are "teleported"
// to some other polygons, ex. rescue
m_kart_info[i].m_on_node = BattleGraph::UNKNOWN_POLY;
bool found = false;
unsigned int num = 0;
while (!found && num < adjacents.size())
{
const NavPoly& p_temp =
BattleGraph::get()->getPolyOfNode(adjacents[num]);
if (p_temp.pointInPoly(m_karts[i]->getXYZ()))
{
m_kart_info[i].m_on_node = adjacents[num];
found = true;
}
num++;
}
// Current node is still unkown
if (m_kart_info[i].m_on_node == BattleGraph::UNKNOWN_POLY)
{
// Calculated distance from saved node to current position,
// if it's close enough than use the saved node anyway, it
// may happen when the kart stays on the edge of obstacles
const NavPoly& p = BattleGraph::get()
->getPolyOfNode(saved_current_node);
const float dist = (p.getCenter() - m_karts[i]->getXYZ()).length_2d();
if (dist < 3.0f)
m_kart_info[i].m_on_node = saved_current_node;
}
}
m_kart_info[i].m_on_node = BattleGraph::get()
->pointToNode(m_kart_info[i].m_on_node,
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
}
}

View File

@ -71,7 +71,7 @@ private:
PtrVector<TrackObject, REF> m_tires;
/** Function to udpate the locations of all karts on the polygon map */
/** Function to update the locations of all karts on the polygon map */
void updateKartNodes();
public:

View File

@ -32,6 +32,7 @@
#include "input/keyboard_device.hpp"
#include "items/projectile_manager.hpp"
#include "karts/controller/battle_ai.hpp"
#include "karts/controller/soccer_ai.hpp"
#include "karts/controller/end_controller.hpp"
#include "karts/controller/local_player_controller.hpp"
#include "karts/controller/skidding_ai.hpp"
@ -351,6 +352,8 @@ Controller* World::loadAIController(AbstractKart *kart)
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
turn=1;
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
turn=2;
// If different AIs should be used, adjust turn (or switch randomly
// or dependent on difficulty)
switch(turn)
@ -361,6 +364,9 @@ Controller* World::loadAIController(AbstractKart *kart)
case 1:
controller = new BattleAI(kart);
break;
case 2:
controller = new SoccerAI(kart);
break;
default:
Log::warn("[World]", "Unknown AI, using default.");
controller = new SkiddingAI(kart);

View File

@ -31,7 +31,6 @@ enum SoccerTeam
SOCCER_TEAM_NONE=-1,
SOCCER_TEAM_RED=0,
SOCCER_TEAM_BLUE=1,
NB_SOCCER_TEAMS
};
/** Game difficulty per player. */

View File

@ -226,7 +226,7 @@ void Physics::update(float dt)
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
{
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
soccerWorld->setLastKartTohitBall(kartId);
soccerWorld->setBallHitter(kartId);
}
continue;
}
@ -288,7 +288,7 @@ void Physics::update(float dt)
{
int kartId = p->getUserPointer(0)->getPointerFlyable()->getOwnerId();
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
soccerWorld->setLastKartTohitBall(kartId);
soccerWorld->setBallHitter(kartId);
}
}

View File

@ -197,7 +197,7 @@ public:
case MINOR_MODE_FOLLOW_LEADER: return true;
case MINOR_MODE_3_STRIKES: return true;
case MINOR_MODE_EASTER_EGG: return false;
case MINOR_MODE_SOCCER: return false;
case MINOR_MODE_SOCCER: return true;
default: assert(false); return false;
}
} // hasAI
@ -611,10 +611,21 @@ public:
const int id = (int)m_minor_mode;
// This uses the numerical id of the mode, see the macros
// LINEAR_RACE and BATTLE_ARENA above for exact meaning.
if (id >= 2000) return true;
if (id >= 2000 && id != 2001) return true;
else return false;
} // isBattleMode
// ------------------------------------------------------------------------
/** \brief Returns true if the current mode is a soccer mode. */
bool isSoccerMode()
{
const int id = (int)m_minor_mode;
// This uses the numerical id of the mode, see the macros
// LINEAR_RACE and BATTLE_ARENA above for exact meaning.
if (id == 2001) return true;
else return false;
} // isSoccerMode
// ------------------------------------------------------------------------
bool isTutorialMode()
{
@ -624,7 +635,7 @@ public:
/** \brief Returns true if the current mode has laps. */
bool modeHasLaps()
{
if (isBattleMode()) return false;
if (isBattleMode() || isSoccerMode()) return false;
const int id = (int)m_minor_mode;
// See meaning of IDs above
const int answer = (id-1000)/100;

View File

@ -95,7 +95,9 @@ void ArenasScreen::beforeAddingWidget()
Track* temp = track_manager->getTrack(n);
if (soccer_mode)
{
if(temp->isSoccer())
if(temp->isSoccer() && (temp->hasNavMesh() ||
race_manager->getNumLocalPlayers() > 1 ||
UserConfigParams::m_artist_debug_mode))
num_of_arenas++;
}
else
@ -239,7 +241,18 @@ void ArenasScreen::buildTrackList()
Track* curr = track_manager->getTrack(n);
if (soccer_mode)
{
if(!curr->isSoccer()) continue;
if(curr->isSoccer() && curr->hasNavMesh() && !arenas_have_navmesh)
arenas_have_navmesh = true;
if(!curr->isSoccer() ||
(!(curr->hasNavMesh() ||
race_manager->getNumLocalPlayers() > 1 ||
UserConfigParams::m_artist_debug_mode)))
{
if (curr->isSoccer())
m_unsupported_arena.insert(n);
continue;
}
}
else
{
@ -280,7 +293,18 @@ void ArenasScreen::buildTrackList()
Track* curr = track_manager->getTrack(currArenas[n]);
if (soccer_mode)
{
if(!curr->isSoccer()) continue;
if(curr->isSoccer() && curr->hasNavMesh() && !arenas_have_navmesh)
arenas_have_navmesh = true;
if(!curr->isSoccer() ||
(!(curr->hasNavMesh() ||
race_manager->getNumLocalPlayers() > 1 ||
UserConfigParams::m_artist_debug_mode)))
{
if (curr->isSoccer())
m_unsupported_arena.insert(currArenas[n]);
continue;
}
}
else
{

View File

@ -48,6 +48,7 @@ using namespace irr;
#include "modes/soccer_world.hpp"
#include "race/race_manager.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "utils/constants.hpp"
#include "utils/string_utils.hpp"
#include "utils/translation.hpp"
@ -224,55 +225,46 @@ void RaceGUI::renderPlayerView(const Camera *camera, float dt)
*/
void RaceGUI::drawScores()
{
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
int offsetY = 5;
int offsetX = 5;
gui::ScalableFont* font = GUIEngine::getFont();
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
int offset_y = 5;
int offset_x = 5;
gui::ScalableFont* font = GUIEngine::getTitleFont();
static video::SColor color = video::SColor(255,255,255,255);
//Draw kart icons above score(denoting teams)
//Draw two teams score
irr::video::ITexture *red_team = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_red.png");
irr::video::ITexture *blue_team = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_blue.png");
"soccer_ball_blue.png");
irr::video::ITexture *team_icon = red_team;
int numLeader = 1;
for(unsigned int i=0; i<soccerWorld->getNumKarts(); i++)
for(unsigned int i=0; i<2; i++)
{
int j = soccerWorld->getTeamLeader(i);
if(j < 0) break;
core::recti position(offset_x, offset_y,
offset_x + 2*m_minimap_player_size, offset_y + 2*m_minimap_player_size);
AbstractKart* kart = soccerWorld->getKart(i);
video::ITexture* icon = kart->getKartProperties()->getMinimapIcon();
core::rect<s32> source(core::position2di(0, 0), icon->getSize());
core::recti position(offsetX, offsetY,
offsetX + 2*m_minimap_player_size, offsetY + 2*m_minimap_player_size);
draw2DImage(icon, position, source,
NULL, NULL, true);
core::stringw score = StringUtils::toWString(soccerWorld->getScore(i));
core::stringw score = StringUtils::toWString(sw->getScore((SoccerTeam)i));
int string_height =
GUIEngine::getFont()->getDimension(score.c_str()).Height;
core::recti pos(position.UpperLeftCorner.X + 5,
position.LowerRightCorner.Y + offsetY,
position.LowerRightCorner.Y + offset_y,
position.LowerRightCorner.X,
position.LowerRightCorner.Y + string_height);
font->draw(score.c_str(),pos,color);
if (numLeader == 2)
if (i == 1)
{
team_icon = blue_team;
}
core::rect<s32> indicatorPos(offsetX, offsetY,
offsetX + (int)(m_minimap_player_size/1.25f),
offsetY + (int)(m_minimap_player_size/1.25f));
core::rect<s32> sourceRect(core::position2d<s32>(0,0),
core::rect<s32> indicator_pos(offset_x, offset_y,
offset_x + (int)(m_minimap_player_size*2),
offset_y + (int)(m_minimap_player_size*2));
core::rect<s32> source_rect(core::position2d<s32>(0,0),
team_icon->getSize());
draw2DImage(team_icon,indicatorPos,sourceRect,
draw2DImage(team_icon,indicator_pos,source_rect,
NULL,NULL,true);
numLeader++;
offsetX += position.LowerRightCorner.X;
offset_x += position.LowerRightCorner.X + 30;
}
} // drawScores
@ -295,7 +287,8 @@ void RaceGUI::drawGlobalTimer()
bool use_digit_font = true;
float elapsed_time = World::getWorld()->getTime();
if (!race_manager->hasTimeTarget())
if (!race_manager->hasTimeTarget() || race_manager
->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
{
sw = core::stringw (
StringUtils::timeToString(elapsed_time).c_str() );
@ -329,7 +322,8 @@ void RaceGUI::drawGlobalTimer()
}
gui::ScalableFont* font = (use_digit_font ? GUIEngine::getHighresDigitFont() : GUIEngine::getFont());
font->setShadow(video::SColor(255, 128, 0, 0));
if (use_digit_font)
font->setShadow(video::SColor(255, 128, 0, 0));
font->setScale(1.0f);
font->draw(sw.c_str(), pos, time_color, false, false, NULL,
true /* ignore RTL */);
@ -343,8 +337,8 @@ void RaceGUI::drawGlobalMiniMap()
{
World *world = World::getWorld();
// draw a map when arena has a navigation mesh.
if ((world->getTrack()->isArena() && !(world->getTrack()->hasNavMesh())) ||
world->getTrack()->isSoccer())
if ((world->getTrack()->isArena() || world->getTrack()->isSoccer()) &&
!(world->getTrack()->hasNavMesh()))
return;
const video::ITexture *old_rtt_mini_map = world->getTrack()->getOldRttMiniMap();
@ -393,6 +387,23 @@ void RaceGUI::drawGlobalMiniMap()
lower_y -(int)(draw_at.getY()-marker_half_size));
draw2DImage(icon, position, source, NULL, NULL, true);
} // for i<getNumKarts
SoccerWorld *sw = dynamic_cast<SoccerWorld*>(World::getWorld());
if (sw)
{
Vec3 draw_at;
world->getTrack()->mapPoint2MiniMap(sw->getBallPosition(), &draw_at);
video::ITexture* icon =
irr_driver->getTexture(FileManager::GUI, "soccer_ball_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)));
draw2DImage(icon, position, source, NULL, NULL, true);
}
} // drawGlobalMiniMap
//-----------------------------------------------------------------------------

View File

@ -297,7 +297,7 @@ void RaceGUIBase::cleanupMessages(const float dt)
/** Draws the powerup icons on the screen (called once for each player).
* \param kart The kart for which to draw the powerup icons.
* \param viewport The viewport into which to draw the icons.
* \param scaling The scaling to use when draing the icons.
* \param scaling The scaling to use when drawing the icons.
*/
void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
const core::recti &viewport,
@ -309,8 +309,13 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|| kart->hasFinishedRace()) return;
int n = kart->getPowerup()->getNum() ;
int many_powerups = 0;
if (n<1) return; // shouldn't happen, but just in case
if (n>5) n=5; // Display at most 5 items
if (n>5)
{
many_powerups = n;
n = 1;
}
int nSize = (int)(64.0f*std::min(scaling.X, scaling.Y));
@ -320,6 +325,8 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
- (n * itemSpacing)/2;
int y1 = viewport.UpperLeftCorner.Y + (int)(20 * scaling.Y);
int x2 = 0;
assert(powerup != NULL);
assert(powerup->getIcon() != NULL);
video::ITexture *t=powerup->getIcon()->getTexture();
@ -328,11 +335,21 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
for ( int i = 0 ; i < n ; i++ )
{
int x2 = (int)(x1+i*itemSpacing);
x2 = (int)(x1+i*itemSpacing);
core::rect<s32> pos(x2, y1, x2+nSize, y1+nSize);
draw2DImage(t, pos, rect, NULL,
NULL, true);
} // for i
if (many_powerups > 0)
{
gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
core::rect<s32> pos(x2+nSize, y1, x2+nSize+nSize, y1+nSize);
font->setScale(1.5f);
font->draw(StringUtils::toWString(many_powerups)+L"x",
pos, video::SColor(255, 255, 255, 255));
font->setScale(1.0f);
}
} // drawPowerupIcons
// ----------------------------------------------------------------------------
@ -439,11 +456,15 @@ void RaceGUIBase::drawGlobalMusicDescription()
gui::IGUIFont* font = GUIEngine::getFont();
float race_time = World::getWorld()->getTime();
// In follow the leader the clock counts backwards, so convert the
// In the modes that the clock counts backwards, convert the
// countdown time to time since start:
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER)
race_time = ((FollowTheLeaderRace*)World::getWorld())->getClockStartTime()
- race_time;
else if (race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER &&
race_manager->hasTimeTarget())
race_time = race_manager->getTimeTarget() - World::getWorld()->getTime();
// ---- Manage pulsing effect
// 3.0 is the duration of ready/set (TODO: don't hardcode)
float timeProgression = (float)(race_time) /

View File

@ -475,7 +475,15 @@ void RaceResultGUI::determineTableLayout()
// 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();
ri->m_kart_name = translations->fribidize(kart->getName());
// 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)
ri->m_kart_name = race_manager->getKartInfo(rm_id).getPlayerName();
else
ri->m_kart_name = translations->fribidize(kart->getName());
video::ITexture *icon =
kart->getKartProperties()->getIconMaterial()->getTexture();
@ -840,7 +848,15 @@ void RaceResultGUI::determineGPLayout()
RowInfo *ri = &(m_all_row_infos[rank]);
ri->m_kart_icon =
kart->getKartProperties()->getIconMaterial()->getTexture();
ri->m_kart_name = translations->fribidize(kart->getName());
const int rm_id = kart_id -
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
if (rm_id >= 0)
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();
// In FTL karts do have a time, which is shown even when the kart
@ -977,136 +993,171 @@ void RaceResultGUI::displaySoccerResults()
{
//Draw win text
core::stringw resultText;
core::stringw result_text;
static video::SColor color = video::SColor(255, 255, 255, 255);
gui::IGUIFont* font = GUIEngine::getTitleFont();
int currX = UserConfigParams::m_width/2;
int current_x = UserConfigParams::m_width/2;
RowInfo *ri = &(m_all_row_infos[0]);
int currY = (int)ri->m_y_pos;
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
int teamScore[2] = {soccerWorld->getScore(0), soccerWorld->getScore(1)};
int current_y = (int)ri->m_y_pos;
SoccerWorld* sw = (SoccerWorld*)World::getWorld();
const int red_score = sw->getScore(SOCCER_TEAM_RED);
const int blue_score = sw->getScore(SOCCER_TEAM_BLUE);
GUIEngine::Widget *table_area = getWidget("result-table");
int height = table_area->m_h + table_area->m_y;
if(teamScore[0] > teamScore[1])
if(red_score > blue_score)
{
resultText = _("Red Team Wins");
result_text = _("Red Team Wins");
}
else if(teamScore[1] > teamScore[0])
else if(blue_score > red_score)
{
resultText = _("Blue Team Wins");
result_text = _("Blue Team Wins");
}
else
{
//Cannot really happen now. Only in time limited matches.
resultText = _("It's a draw");
result_text = _("It's a draw");
}
core::rect<s32> pos(currX, currY, currX, currY);
font->draw(resultText.c_str(), pos, color, true, true);
core::rect<s32> pos(current_x, current_y, current_x, current_y);
font->draw(result_text.c_str(), pos, color, true, true);
core::dimension2du rect = m_font->getDimension(resultText.c_str());
core::dimension2du rect = m_font->getDimension(result_text.c_str());
//Draw team scores:
currY += rect.Height;
currX /= 2;
irr::video::ITexture* redTeamIcon = irr_driver->getTexture(FileManager::GUI,
current_y += rect.Height;
current_x /= 2;
irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_red.png");
irr::video::ITexture* blueTeamIcon = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_blue.png");
irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI,
"soccer_ball_blue.png");
core::recti sourceRect(core::vector2di(0,0), redTeamIcon->getSize());
core::recti destRect(currX, currY, currX+redTeamIcon->getSize().Width/2,
currY+redTeamIcon->getSize().Height/2);
draw2DImage(redTeamIcon, destRect,sourceRect,
core::recti source_rect(core::vector2di(0,0), red_icon->getSize());
core::recti dest_rect(current_x, current_y, current_x+red_icon->getSize().Width/2,
current_y+red_icon->getSize().Height/2);
draw2DImage(red_icon, dest_rect,source_rect,
NULL,NULL, true);
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
destRect = core::recti(currX, currY, currX+redTeamIcon->getSize().Width/2,
currY+redTeamIcon->getSize().Height/2);
draw2DImage(blueTeamIcon,destRect,sourceRect,
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
dest_rect = core::recti(current_x, current_y, current_x+red_icon->getSize().Width/2,
current_y+red_icon->getSize().Height/2);
draw2DImage(blue_icon,dest_rect,source_rect,
NULL, NULL, true);
resultText = StringUtils::toWString(teamScore[1]);
rect = m_font->getDimension(resultText.c_str());
currX += redTeamIcon->getSize().Width/4;
currY += redTeamIcon->getSize().Height/2 + rect.Height/4;
pos = core::rect<s32>(currX, currY, currX, currY);
result_text = StringUtils::toWString(blue_score);
rect = m_font->getDimension(result_text.c_str());
current_x += red_icon->getSize().Width/4;
current_y += red_icon->getSize().Height/2 + rect.Height/4;
pos = core::rect<s32>(current_x, current_y, current_x, current_y);
color = video::SColor(255,255,255,255);
font->draw(resultText.c_str(), pos, color, true, false);
font->draw(result_text.c_str(), pos, color, true, false);
currX -= UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
resultText = StringUtils::toWString(teamScore[0]);
pos = core::rect<s32>(currX,currY,currX,currY);
font->draw(resultText.c_str(), pos, color, true, false);
current_x -= UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
result_text = StringUtils::toWString(red_score);
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
font->draw(result_text.c_str(), pos, color, true, false);
int centerX = UserConfigParams::m_width/2;
pos = core::rect<s32>(centerX, currY, centerX, currY);
int center_x = UserConfigParams::m_width/2;
pos = core::rect<s32>(center_x, current_y, center_x, current_y);
font->draw("-", pos, color, true, false);
//Draw goal scorers:
//The red scorers:
currY += rect.Height/2 + rect.Height/4;
current_y += rect.Height/2 + rect.Height/4;
font = GUIEngine::getSmallFont();
std::vector<int> scorers = soccerWorld->getScorers(0);
std::vector<float> scoreTimes = soccerWorld->getScoreTimes(0);
irr::video::ITexture* scorerIcon;
std::vector<SoccerWorld::ScorerData> scorers = sw->getScorers(SOCCER_TEAM_RED);
std::vector<float> score_times = sw->getScoreTimes(SOCCER_TEAM_RED);
irr::video::ITexture* scorer_icon;
int prevY = currY;
int prev_y = current_y;
for(unsigned int i=0; i<scorers.size(); i++)
{
resultText = soccerWorld->getKart(scorers.at(i))->
getKartProperties()->getName();
resultText.append(" ");
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
rect = m_font->getDimension(resultText.c_str());
const bool own_goal = !(scorers.at(i).m_correct_goal);
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
currY += (height-prevY)/((short)scorers.size()+1);
const int kart_id = scorers.at(i).m_id;
const int rm_id = kart_id -
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
if (rm_id >= 0)
result_text = race_manager->getKartInfo(rm_id).getPlayerName();
else
currY += rect.Height;
result_text = sw->getKart(kart_id)->
getKartProperties()->getName();
if(currY > height) break;
if (own_goal)
{
result_text.append(" ");
result_text.append( _("(Own Goal)") );
}
pos = core::rect<s32>(currX,currY,currX,currY);
font->draw(resultText,pos, color, true, false);
scorerIcon = soccerWorld->getKart(scorers.at(i))
result_text.append(" ");
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
rect = m_font->getDimension(result_text.c_str());
if(height-prev_y < ((short)scorers.size()+1)*(short)rect.Height)
current_y += (height-prev_y)/((short)scorers.size()+1);
else
current_y += rect.Height;
if(current_y > height) break;
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
font->draw(result_text, pos, (own_goal ?
video::SColor(255, 255, 0, 0) : color), true, false);
scorer_icon = sw->getKart(scorers.at(i).m_id)
->getKartProperties()->getIconMaterial()->getTexture();
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
draw2DImage(scorerIcon, destRect, sourceRect,
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
draw2DImage(scorer_icon, dest_rect, source_rect,
NULL, NULL, true);
}
//The blue scorers:
currY = prevY;
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
scorers = soccerWorld->getScorers(1);
scoreTimes = soccerWorld->getScoreTimes(1);
current_y = prev_y;
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
scorers = sw->getScorers(SOCCER_TEAM_BLUE);
score_times = sw->getScoreTimes(SOCCER_TEAM_BLUE);
for(unsigned int i=0; i<scorers.size(); i++)
{
resultText = soccerWorld->getKart(scorers.at(i))->
getKartProperties()->getName();
resultText.append(" ");
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
rect = m_font->getDimension(resultText.c_str());
const bool own_goal = !(scorers.at(i).m_correct_goal);
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
currY += (height-prevY)/((short)scorers.size()+1);
const int kart_id = scorers.at(i).m_id;
const int rm_id = kart_id -
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
if (rm_id >= 0)
result_text = race_manager->getKartInfo(rm_id).getPlayerName();
else
currY += rect.Height;
result_text = sw->getKart(kart_id)->
getKartProperties()->getName();
if(currY > height) break;
if (own_goal)
{
result_text.append(" ");
result_text.append( _("(Own Goal)") );
}
pos = core::rect<s32>(currX,currY,currX,currY);
font->draw(resultText,pos, color, true, false);
scorerIcon = soccerWorld->getKart(scorers.at(i))->
result_text.append(" ");
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
rect = m_font->getDimension(result_text.c_str());
if(height-prev_y < ((short)scorers.size()+1)*(short)rect.Height)
current_y += (height-prev_y)/((short)scorers.size()+1);
else
current_y += rect.Height;
if(current_y > height) break;
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
font->draw(result_text,pos, (own_goal ?
video::SColor(255, 255, 0, 0) : color), true, false);
scorer_icon = sw->getKart(scorers.at(i).m_id)->
getKartProperties()->getIconMaterial()->getTexture();
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
draw2DImage(scorerIcon, destRect, sourceRect,
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
draw2DImage(scorer_icon, dest_rect, source_rect,
NULL, NULL, true);
}
}

View File

@ -33,8 +33,6 @@
#include "states_screens/tracks_screen.hpp"
#include "utils/translation.hpp"
#define ENABLE_SOCCER_MODE
const int CONFIG_CODE_NORMAL = 0;
const int CONFIG_CODE_TIMETRIAL = 1;
const int CONFIG_CODE_FTL = 2;
@ -114,15 +112,10 @@ void RaceSetupScreen::init()
name4 += _("Hit others with weapons until they lose all their lives (only in multiplayer games).");
w2->addItem( name4, IDENT_STRIKES, RaceManager::getIconOf(RaceManager::MINOR_MODE_3_STRIKES));
#ifdef ENABLE_SOCCER_MODE
if (race_manager->getNumLocalPlayers() > 1 || UserConfigParams::m_artist_debug_mode)
{
irr::core::stringw name5 = irr::core::stringw(
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
}
#endif
irr::core::stringw name5 = irr::core::stringw(
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
#define ENABLE_EASTER_EGG_MODE
#ifdef ENABLE_EASTER_EGG_MODE
@ -237,12 +230,7 @@ void RaceSetupScreen::eventCallback(Widget* widget, const std::string& name,
{
race_manager->setMinorMode(RaceManager::MINOR_MODE_SOCCER);
UserConfigParams::m_game_mode = CONFIG_CODE_SOCCER;
race_manager->setNumKarts( race_manager->getNumLocalPlayers() ); // no AI karts;
// 1 player -> no need to choose a team or determine when the match ends
if(race_manager->getNumLocalPlayers() <= 1)
ArenasScreen::getInstance()->push();
else
SoccerSetupScreen::getInstance()->push();
SoccerSetupScreen::getInstance()->push();
}
else if (selectedMode == "locked")
{

View File

@ -19,6 +19,7 @@
#include "audio/sfx_manager.hpp"
#include "config/user_config.hpp"
#include "guiengine/widgets/bubble_widget.hpp"
#include "guiengine/widgets/button_widget.hpp"
#include "guiengine/widgets/spinner_widget.hpp"
#include "guiengine/widgets/check_box_widget.hpp"
@ -34,7 +35,6 @@
#include "states_screens/arenas_screen.hpp"
#include "states_screens/state_manager.hpp"
using namespace GUIEngine;
DEFINE_SCREEN_SINGLETON( SoccerSetupScreen );
@ -65,20 +65,7 @@ void SoccerSetupScreen::eventCallback(Widget* widget, const std::string& name,
{
int nb_players = (int)m_kart_view_info.size();
if (getNumKartsInTeam(SOCCER_TEAM_RED) == 0 ||
getNumKartsInTeam(SOCCER_TEAM_BLUE) == 0)
{
for(int i=0 ; i < nb_players ; i++)
{
if (!m_kart_view_info[i].confirmed)
{
m_kart_view_info[i].view->setBadge(BAD_BADGE);
}
}
SFXManager::get()->quickSound( "anvil" );
return;
}
else if(!areAllKartsConfirmed())
if(!areAllKartsConfirmed())
{
for(int i=0 ; i < nb_players ; i++)
{
@ -112,6 +99,7 @@ void SoccerSetupScreen::eventCallback(Widget* widget, const std::string& name,
{
CheckBoxWidget* timeEnabled = dynamic_cast<CheckBoxWidget*>(widget);
bool timed = timeEnabled->getState();
UserConfigParams::m_soccer_use_time_limit = timed;
getWidget<SpinnerWidget>("goalamount")->setActive(!timed);
getWidget<SpinnerWidget>("timeamount")->setActive(timed);
}
@ -122,19 +110,14 @@ void SoccerSetupScreen::beforeAddingWidget()
{
Widget* central_div = getWidget<Widget>("central_div");
// Compute some dimensions
const core::dimension2d<u32> vs_size = GUIEngine::getTitleFont()->getDimension( L"VS" );
const int vs_width = (int)vs_size.Width;
const int vs_height = (int)vs_size.Height;
const int center_x = central_div->m_x + central_div->m_w/2;
const int center_y = central_div->m_y + central_div->m_h/2;
// Add red/blue team icon above the karts
IconButtonWidget* red = getWidget<IconButtonWidget>("red_team");
IconButtonWidget* blue = getWidget<IconButtonWidget>("blue_team");
red->m_x = central_div->m_x + central_div->m_w/4;
red->m_y = central_div->m_y + red->m_h;
// Add "VS" label at the center of the rounded box
LabelWidget* label_vs = getWidget<LabelWidget>("vs");
label_vs->m_x = center_x - vs_width/2;
label_vs->m_y = center_y - vs_height/2;
label_vs->m_w = vs_width;
label_vs->m_h = vs_height;
blue->m_x = central_div->m_x + (central_div->m_w/4)*3;
blue->m_y = central_div->m_y + blue->m_h;
// Add the 3D views for the karts
int nb_players = race_manager->getNumPlayers();
@ -176,7 +159,9 @@ void SoccerSetupScreen::beforeAddingWidget()
KartViewInfo info;
info.view = kart_view;
info.confirmed = false;
info.team = i&1 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED;
int single_team = UserConfigParams::m_soccer_default_team;
info.team = nb_players == 1 ? (SoccerTeam)single_team :
(i&1 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED);
m_kart_view_info.push_back(info);
race_manager->setKartSoccerTeam(i, info.team);
}
@ -191,23 +176,23 @@ void SoccerSetupScreen::init()
m_schedule_continue = false;
Screen::init();
if (UserConfigParams::m_num_goals <= 0)
UserConfigParams::m_num_goals = 3;
if (UserConfigParams::m_soccer_time_limit <= 0)
UserConfigParams::m_soccer_time_limit = 3;
SpinnerWidget* goalamount = getWidget<SpinnerWidget>("goalamount");
goalamount->setValue(UserConfigParams::m_num_goals);
goalamount->setActive(true);
goalamount->setActive(!UserConfigParams::m_soccer_use_time_limit);
SpinnerWidget* timeAmount = getWidget<SpinnerWidget>("timeamount");
timeAmount->setValue(UserConfigParams::m_soccer_time_limit);
timeAmount->setActive(false);
timeAmount->setActive(UserConfigParams::m_soccer_use_time_limit);
CheckBoxWidget* timeEnabled = getWidget<CheckBoxWidget>("time_enabled");
timeEnabled->setState(false);
timeEnabled->setState(UserConfigParams::m_soccer_use_time_limit);
// Set focus on "continue"
ButtonWidget* bt_continue = getWidget<ButtonWidget>("continue");
@ -229,7 +214,7 @@ void SoccerSetupScreen::tearDown()
// Reset the 'map fire to select' option of the device manager
input_manager->getDeviceManager()->mapFireToSelect(false);
UserConfigParams::m_num_goals = getWidget<SpinnerWidget>("goalamount")->getValue();
UserConfigParams::m_soccer_time_limit = getWidget<SpinnerWidget>("timeamount")->getValue();
@ -255,7 +240,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
if(m_schedule_continue)
return EVENT_BLOCK;
ButtonWidget* bt_continue = getWidget<ButtonWidget>("continue");
ButtonWidget* bt_continue = getWidget<ButtonWidget>("continue");
BubbleWidget* bubble = getWidget<BubbleWidget>("lblLeftRight");
GUIEngine::EventPropagation result = EVENT_LET;
SoccerTeam team_switch = SOCCER_TEAM_NONE;
int nb_players = (int)m_kart_view_info.size();
@ -263,7 +249,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
switch(action)
{
case PA_MENU_LEFT:
if (bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) &&
if ((bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) ||
bubble->isFocusedForPlayer(PLAYER_ID_GAME_MASTER)) &&
m_kart_view_info[playerId].confirmed == false)
{
team_switch = SOCCER_TEAM_RED;
@ -275,7 +262,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
}
break;
case PA_MENU_RIGHT:
if (bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) &&
if ((bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) ||
bubble->isFocusedForPlayer(PLAYER_ID_GAME_MASTER)) &&
m_kart_view_info[playerId].confirmed == false)
{
team_switch = SOCCER_TEAM_BLUE;
@ -308,22 +296,12 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
return EVENT_BLOCK;
}
if (getNumConfirmedKarts() > nb_players-2 &&
(getNumKartsInTeam(SOCCER_TEAM_RED) == 0 ||
getNumKartsInTeam(SOCCER_TEAM_BLUE) == 0))
{
SFXManager::get()->quickSound( "anvil" );
m_kart_view_info[playerId].view->setBadge(BAD_BADGE);
}
else
{
// Confirm team selection
m_kart_view_info[playerId].confirmed = true;
m_kart_view_info[playerId].view->setRotateTo( KART_CONFIRMATION_TARGET_ANGLE, KART_CONFIRMATION_ROTATION_SPEED );
m_kart_view_info[playerId].view->setBadge(OK_BADGE);
m_kart_view_info[playerId].view->unsetBadge(BAD_BADGE);
SFXManager::get()->quickSound( "wee" );
}
// Confirm team selection
m_kart_view_info[playerId].confirmed = true;
m_kart_view_info[playerId].view->setRotateTo( KART_CONFIRMATION_TARGET_ANGLE, KART_CONFIRMATION_ROTATION_SPEED );
m_kart_view_info[playerId].view->setBadge(OK_BADGE);
m_kart_view_info[playerId].view->unsetBadge(BAD_BADGE);
SFXManager::get()->quickSound( "wee" );
return EVENT_BLOCK;
}
case PA_MENU_CANCEL:
@ -349,15 +327,15 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
default:
break;
}
if(team_switch != SOCCER_TEAM_NONE) // A player wants to change his team?
{
if (nb_players == 1)
UserConfigParams::m_soccer_default_team = (int)team_switch;
race_manager->setKartSoccerTeam(playerId, team_switch);
m_kart_view_info[playerId].team = team_switch;
updateKartViewsLayout();
}
return result;
} // filterActions
@ -366,7 +344,7 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
void SoccerSetupScreen::onUpdate(float delta)
{
int nb_players = (int)m_kart_view_info.size();
if(m_schedule_continue)
{
for(int i=0 ; i < nb_players ; i++)
@ -377,7 +355,7 @@ void SoccerSetupScreen::onUpdate(float delta)
m_schedule_continue = false;
ArenasScreen::getInstance()->push();
}
} // onUPdate
} // onUpdate
// ----------------------------------------------------------------------------
bool SoccerSetupScreen::areAllKartsConfirmed() const
@ -395,19 +373,6 @@ bool SoccerSetupScreen::areAllKartsConfirmed() const
return all_confirmed;
} // areAllKartsConfirmed
// ----------------------------------------------------------------------------
int SoccerSetupScreen::getNumKartsInTeam(int team)
{
int karts_in_team = 0;
int nb_players = (int)m_kart_view_info.size();
for(int i=0 ; i < nb_players ; i++)
{
if(m_kart_view_info[i].team == team)
karts_in_team++;
}
return karts_in_team;
} // getNumKartsInTeam
// -----------------------------------------------------------------------------
int SoccerSetupScreen::getNumConfirmedKarts()
{
@ -427,10 +392,8 @@ void SoccerSetupScreen::updateKartViewsLayout()
Widget* central_div = getWidget<Widget>("central_div");
// Compute/get some dimensions
LabelWidget* label_vs = getWidget<LabelWidget>("vs");
const int vs_width = label_vs->m_w;
const int nb_columns = 2; // two karts maximum per column
const int kart_area_width = (central_div->m_w - vs_width) / 2; // size of one half of the screen
const int kart_area_width = (central_div->m_w) / 2; // size of one half of the screen
const int kart_view_size = kart_area_width/nb_columns; // Size (width and height) of a kart view
const int center_x = central_div->m_x + central_div->m_w/2;
const int center_y = central_div->m_y + central_div->m_h/2;
@ -448,8 +411,8 @@ void SoccerSetupScreen::updateKartViewsLayout()
const int start_y[2] = {center_y - nb_rows_per_team[0] * kart_view_size / 2,
center_y - nb_rows_per_team[1] * kart_view_size / 2};
// - center of each half-screen
const int center_x_per_team[2] = { ( central_div->m_x + (center_x - vs_width) ) / 2,
( central_div->m_x+central_div->m_w + (center_x + vs_width) ) / 2,
const int center_x_per_team[2] = { ( central_div->m_x + center_x ) / 2,
( central_div->m_x+central_div->m_w + center_x ) / 2,
};
// Update the layout of the 3D views for the karts
@ -474,4 +437,3 @@ void SoccerSetupScreen::updateKartViewsLayout()
view_info.view->move(pos_x, pos_y, kart_view_size, kart_view_size);
}
} // updateKartViewsLayout

View File

@ -43,7 +43,7 @@ class SoccerSetupScreen : public GUIEngine::Screen, public GUIEngine::ScreenSing
};
AlignedArray<KartViewInfo> m_kart_view_info;
bool m_schedule_continue;
public:
@ -75,7 +75,6 @@ public:
private:
bool areAllKartsConfirmed() const;
int getNumKartsInTeam(int team);
int getNumConfirmedKarts();
void updateKartViewsLayout();
};

View File

@ -154,7 +154,8 @@ void TrackInfoScreen::init()
// -------------
const int local_players = race_manager->getNumLocalPlayers();
const bool has_AI =
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ?
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER ?
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
race_manager->hasAI());
m_ai_kart_spinner->setVisible(has_AI);
@ -170,7 +171,8 @@ void TrackInfoScreen::init()
m_ai_kart_spinner->setValue(num_ai);
race_manager->setNumKarts(num_ai + local_players);
// Set the max karts supported based on the battle arena selected
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
{
m_ai_kart_spinner->setMax(max_arena_players - local_players);
}
@ -181,8 +183,9 @@ void TrackInfoScreen::init()
{
m_ai_kart_spinner->setMin(3 - local_players);
}
// Make sure in battle mode at least 1 ai for single player
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES &&
// Make sure in battle and soccer mode at least 1 ai for single player
else if((race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER) &&
local_players == 1 &&
!UserConfigParams::m_artist_debug_mode)
m_ai_kart_spinner->setMin(1);
@ -310,7 +313,8 @@ void TrackInfoScreen::onEnterPressedInternal()
const int max_arena_players = m_track->getMaxArenaPlayers();
const int local_players = race_manager->getNumLocalPlayers();
const bool has_AI =
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ?
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER ?
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
race_manager->hasAI());

View File

@ -137,7 +137,7 @@ void BattleGraph::findItemsOnGraphNodes()
for (unsigned int j = 0; j < this->getNumNodes(); ++j)
{
if (NavMesh::get()->getNavPoly(j).pointInPoly(xyz))
if (NavMesh::get()->getNavPoly(j).pointInPoly(xyz, false))
polygon = j;
}
@ -149,7 +149,70 @@ void BattleGraph::findItemsOnGraphNodes()
else
Log::debug("BattleGraph","Can't map item number %d with a suitable polygon", i);
}
}
} // findItemsOnGraphNodes
// -----------------------------------------------------------------------------
int BattleGraph::pointToNode(const int cur_node,
const Vec3& cur_point,
bool ignore_vertical) const
{
int final_node = BattleGraph::UNKNOWN_POLY;
if (cur_node == BattleGraph::UNKNOWN_POLY)
{
// Try all nodes in the battle graph
bool found = false;
unsigned int node = 0;
while (!found && node < this->getNumNodes())
{
const NavPoly& p_all = this->getPolyOfNode(node);
if (p_all.pointInPoly(cur_point, ignore_vertical))
{
final_node = node;
found = true;
}
node++;
}
}
else
{
// Check if the point is still on the same node
const NavPoly& p_cur = this->getPolyOfNode(cur_node);
if (p_cur.pointInPoly(cur_point, ignore_vertical)) return cur_node;
// If not then check all adjacent polys
const std::vector<int>& adjacents = NavMesh::get()
->getAdjacentPolys(cur_node);
bool found = false;
unsigned int num = 0;
while (!found && num < adjacents.size())
{
const NavPoly& p_temp = this->getPolyOfNode(adjacents[num]);
if (p_temp.pointInPoly(cur_point, ignore_vertical))
{
final_node = adjacents[num];
found = true;
}
num++;
}
// Current node is still unkown
if (final_node == BattleGraph::UNKNOWN_POLY)
{
// Calculated distance from saved node to current position,
// if it's close enough than use the saved node anyway, it
// may happen when the kart stays on the edge of obstacles
const NavPoly& p = this->getPolyOfNode(cur_node);
const float dist = (p.getCenter() - cur_point).length_2d();
if (dist < 3.0f)
final_node = cur_node;
}
}
return final_node;
} // pointToNode
// -----------------------------------------------------------------------------
@ -158,4 +221,4 @@ const int & BattleGraph::getNextShortestPathPoly(int i, int j) const
if (i == BattleGraph::UNKNOWN_POLY || j == BattleGraph::UNKNOWN_POLY)
return BattleGraph::UNKNOWN_POLY;
return m_parent_poly[j][i];
}
} // getNextShortestPathPoly

View File

@ -126,6 +126,10 @@ public:
{ return m_items_on_graph; }
void findItemsOnGraphNodes();
// ----------------------------------------------------------------------
int pointToNode(const int cur_node,
const Vec3& cur_point,
bool ignore_vertical) const;
}; //BattleGraph
#endif

View File

@ -51,42 +51,28 @@ CheckGoal::CheckGoal(const XMLNode &node, unsigned int index)
*/
void CheckGoal::update(float dt)
{
World *world = World::getWorld();
assert(world);
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
Track* track = world->getTrack();
assert(track);
TrackObjectManager* tom = track->getTrackObjectManager();
assert(tom);
PtrVector<TrackObject>& objects = tom->getObjects();
unsigned int ball_index = 0;
for(unsigned int i=0; i<objects.size(); i++)
if (world)
{
TrackObject* obj = objects.get(i);
if(!obj->isSoccerBall())
continue;
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
if(isTriggered(m_previous_position[ball_index], xyz, -1))
const Vec3 &xyz = world->getBallPosition();
if (isTriggered(m_previous_ball_position, xyz, -1))
{
if(UserConfigParams::m_check_debug)
Log::info("CheckGoal", "Goal check structure %d triggered for object %s.",
m_index, obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getDebugName());
trigger(ball_index);
if (UserConfigParams::m_check_debug)
{
Log::info("CheckGoal", "Goal check structure"
"%d triggered for ball.", m_index);
}
trigger(0);
}
m_previous_position[ball_index] = xyz;
ball_index++;
m_previous_ball_position = xyz;
}
} // update
// ----------------------------------------------------------------------------
/** Called when the check line is triggered. This function creates a cannon
* animation object and attaches it to the kart.
* \param kart_index The index of the kart that triggered the check line.
/** Called when the goal line is triggered. Input any integer for i to use
*/
void CheckGoal::trigger(unsigned int kart_index)
void CheckGoal::trigger(unsigned int i)
{
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
if(!world)
@ -116,21 +102,22 @@ bool CheckGoal::isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
void CheckGoal::reset(const Track &track)
{
CheckStructure::reset(track);
m_previous_ball_position = Vec3(0, 0, 0);
const TrackObjectManager* tom = track.getTrackObjectManager();
assert(tom);
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
m_previous_position.clear();
const PtrVector<TrackObject>& objects = tom->getObjects();
for(unsigned int i=0; i<objects.size(); i++)
if (world)
{
const TrackObject* obj = objects.get(i);
if(!obj->isSoccerBall())
continue;
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
m_previous_position.push_back(xyz);
const Vec3 &xyz = world->getBallPosition();
m_previous_ball_position = xyz;
}
} // reset
// ----------------------------------------------------------------------------
Vec3 CheckGoal::convertTo3DCenter() const
{
float x = m_line.getMiddle().X;
float y = m_line.getMiddle().Y;
return Vec3(x, 0, y);
}

View File

@ -27,6 +27,7 @@ using namespace irr;
class CheckManager;
class XMLNode;
class Track;
class Vec3;
/**
* \brief Implements a simple checkline that will score a point when the
@ -37,6 +38,9 @@ class Track;
class CheckGoal : public CheckStructure
{
private:
/** Previois ball position. */
Vec3 m_previous_ball_position;
/** Which team is this goal for? */
bool m_first_goal;
@ -51,6 +55,11 @@ public:
virtual bool isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
unsigned int indx) OVERRIDE;
virtual void reset(const Track &track) OVERRIDE;
// ------------------------------------------------------------------------
bool getTeam() const { return m_first_goal; }
// ------------------------------------------------------------------------
Vec3 convertTo3DCenter() const;
}; // CheckLine
#endif

View File

@ -53,6 +53,8 @@ CheckStructure::CheckStructure(const XMLNode &node, unsigned int index)
// Cannons don't have a kind specified, so test for the name in this case
else if(node.getName()=="cannon")
m_check_type = CT_CANNON;
else if(node.getName()=="goal")
m_check_type = CT_GOAL;
else
Log::warn("CheckStructure", "Unknown check structure '%s' - ignored.", kind.c_str());

View File

@ -52,7 +52,7 @@ const std::vector<Vec3> NavPoly::getVertices()
//-----------------------------------------------------------------------------
bool NavPoly::pointInPoly(const Vec3& p) const
bool NavPoly::pointInPoly(const Vec3& p, bool ignore_vertical) const
{
std::vector<Vec3> points;
for(unsigned int i=0; i<m_vertices.size(); i++)
@ -70,6 +70,8 @@ bool NavPoly::pointInPoly(const Vec3& p) const
return false;
}
if (ignore_vertical) return true;
// Check for vertical distance too
const float dist = p.getY() - m_center.getY();
if (fabsf(dist) > 1.0f )

View File

@ -61,7 +61,8 @@ public:
// ------------------------------------------------------------------------
/** Returns true if a given point lies in this polygon. */
bool pointInPoly(const Vec3& p) const;
bool pointInPoly(const Vec3& p,
bool ignore_vertical) const;
const Vec3& operator[](int i) const ;

View File

@ -589,11 +589,16 @@ void Track::loadTrackInfo()
if(file_manager->fileExists(m_root+"navmesh.xml"))
m_has_navmesh = true;
else if(m_is_arena)
else if(m_is_arena || m_is_soccer)
{
Log::warn("Track", "NavMesh is not found for arena %s, "
"disable AI for it.\n", m_name.c_str());
}
if (m_is_soccer)
{
// Currently only max eight players in soccer mode
m_max_arena_players = 8;
}
} // loadTrackInfo
@ -716,7 +721,7 @@ void Track::loadQuadGraph(unsigned int mode_id, const bool reverse)
void Track::mapPoint2MiniMap(const Vec3 &xyz, Vec3 *draw_at) const
{
if (m_is_arena && m_has_navmesh)
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
BattleGraph::get()->mapPoint2MiniMap(xyz, draw_at);
else
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
@ -1032,7 +1037,7 @@ void Track::loadMinimap()
core::dimension2du size = m_mini_map_size
.getOptimalSize(!nonpower,!nonsquare);
if (m_is_arena && m_has_navmesh)
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
{
BattleGraph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255),
&m_old_rtt_mini_map, &m_new_rtt_mini_map);
@ -1645,7 +1650,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
// the information about the size of the texture to render the mini
// map to.
if (!m_is_arena && !m_is_soccer && !m_is_cutscene) loadQuadGraph(mode_id, reverse_track);
else if (m_is_arena && !m_is_soccer && !m_is_cutscene && m_has_navmesh)
else if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh)
loadBattleGraph();
ItemManager::create();
@ -1871,7 +1876,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
delete root;
if (m_is_arena && !m_is_soccer && !m_is_cutscene && m_has_navmesh)
if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh)
BattleGraph::get()->findItemsOnGraphNodes();
if (UserConfigParams::m_track_debug &&

View File

@ -492,6 +492,10 @@ void TrackObjectPresentationMesh::init(const XMLNode* xml_node,
m_frame_end = node->getEndFrame();
if (xml_node)
xml_node->get("frame-end", &m_frame_end);
if (World::getWorld() && World::getWorld()->getTrack() && xml_node)
World::getWorld()->getTrack()
->handleAnimatedTextures(m_node, *xml_node);
}
else
{

View File

@ -107,6 +107,10 @@ enum DebugMenuCommand
DEBUG_VIEW_KART_TWO,
DEBUG_VIEW_KART_THREE,
DEBUG_VIEW_KART_FOUR,
DEBUG_VIEW_KART_FIVE,
DEBUG_VIEW_KART_SIX,
DEBUG_VIEW_KART_SEVEN,
DEBUG_VIEW_KART_EIGHT,
DEBUG_HIDE_KARTS,
DEBUG_THROTTLE_FPS,
DEBUG_VISUAL_VALUES,
@ -494,6 +498,22 @@ bool handleContextMenuAction(s32 cmdID)
{
changeCameraTarget(4);
}
else if (cmdID == DEBUG_VIEW_KART_FIVE)
{
changeCameraTarget(5);
}
else if (cmdID == DEBUG_VIEW_KART_SIX)
{
changeCameraTarget(6);
}
else if (cmdID == DEBUG_VIEW_KART_SEVEN)
{
changeCameraTarget(7);
}
else if (cmdID == DEBUG_VIEW_KART_EIGHT)
{
changeCameraTarget(8);
}
else if (cmdID == DEBUG_PRINT_START_POS)
{
if (!world) return false;
@ -691,6 +711,10 @@ bool onEvent(const SEvent &event)
sub->addItem(L"To kart two", DEBUG_VIEW_KART_TWO);
sub->addItem(L"To kart three", DEBUG_VIEW_KART_THREE);
sub->addItem(L"To kart four", DEBUG_VIEW_KART_FOUR);
sub->addItem(L"To kart five", DEBUG_VIEW_KART_FIVE);
sub->addItem(L"To kart six", DEBUG_VIEW_KART_SIX);
sub->addItem(L"To kart seven", DEBUG_VIEW_KART_SEVEN);
sub->addItem(L"To kart eight", DEBUG_VIEW_KART_EIGHT);
mnu->addItem(L"Adjust values", DEBUG_VISUAL_VALUES);