Merge remote-tracking branch 'origin/master' into support_nw_splitscreen
This commit is contained in:
commit
ee320588d5
61
.travis.yml
61
.travis.yml
@ -3,44 +3,45 @@
|
|||||||
# Configuration manual:
|
# Configuration manual:
|
||||||
# http://docs.travis-ci.com/user/build-configuration/
|
# http://docs.travis-ci.com/user/build-configuration/
|
||||||
#
|
#
|
||||||
|
sudo: false
|
||||||
language: cpp
|
language: cpp
|
||||||
compiler:
|
compiler:
|
||||||
- gcc
|
- gcc
|
||||||
# - clang
|
- clang
|
||||||
#branches:
|
|
||||||
# only:
|
matrix:
|
||||||
# - master
|
fast_finish: true
|
||||||
before_install:
|
|
||||||
# Update repos
|
env:
|
||||||
- sudo apt-get update -qq
|
matrix:
|
||||||
# Install dependencies
|
- BUILD_TYPE="Debug"
|
||||||
- sudo apt-get install build-essential libogg-dev libvorbis-dev libopenal-dev libxxf86vm-dev libcurl4-openssl-dev libfribidi-dev libbluetooth-dev
|
- BUILD_TYPE="Release"
|
||||||
# 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)
|
addons:
|
||||||
- sudo apt-add-repository "deb http://archive.ubuntu.com/ubuntu trusty main restricted"
|
apt:
|
||||||
- sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 3B4FE6ACC0B21F32
|
packages:
|
||||||
- sudo apt-get update -qq
|
- build-essential
|
||||||
- sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev libglew-dev cmake
|
- 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:
|
before_script:
|
||||||
- export THREADS=`nproc`
|
- export THREADS=$((`nproc` + 1))
|
||||||
# Unfortunately using all threads crashes g++: "g++: internal compiler error: Killed (program cc1plus)"
|
|
||||||
- export THREADS=4
|
|
||||||
- echo "THREADS = $THREADS"
|
- echo "THREADS = $THREADS"
|
||||||
|
- free -mt
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- mkdir "build"
|
||||||
# First a debug build:
|
- cd "build"
|
||||||
- mkdir build-debug
|
- cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCHECK_ASSETS=off
|
||||||
- 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
|
|
||||||
- make VERBOSE=1 -j $THREADS
|
- make VERBOSE=1 -j $THREADS
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
|
@ -215,7 +215,7 @@ endif()
|
|||||||
# Set some compiler options
|
# Set some compiler options
|
||||||
if(UNIX OR MINGW)
|
if(UNIX OR MINGW)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x")
|
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()
|
endif()
|
||||||
|
|
||||||
if(MINGW AND CMAKE_BUILD_TYPE MATCHES Release)
|
if(MINGW AND CMAKE_BUILD_TYPE MATCHES Release)
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB |
BIN
data/gui/soccer_ball_normal.png
Normal file
BIN
data/gui/soccer_ball_normal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -6,38 +6,39 @@
|
|||||||
|
|
||||||
<header width="80%" text="Race Setup" align="center" text_align="center" />
|
<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">
|
<div layout="horizontal-row" width="fit" height="35" align="left">
|
||||||
<bright proportion="1" height="100%"
|
<bright proportion="1" height="100%"
|
||||||
I18N="In soccer setup screen" text="Number of goals to win" text_align="left" />
|
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"/>
|
<spinner id="goalamount" width="200" height="90%" min_value="1" max_value="10"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div layout="horizontal-row" width="fit" height="35" align="left">
|
<div layout="horizontal-row" width="fit" height="35" align="left">
|
||||||
<bright proportion="1" height="100%"
|
<bright proportion="1" height="100%"
|
||||||
I18N="In soccer setup screen" text="Maximum time (min.)" text_align="left" />
|
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"/>
|
<spinner id="timeamount" width="200" height="90%" min_value="1" max_value="15"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div layout="horizontal-row" width="fit" height="35" align="left">
|
<div layout="horizontal-row" width="fit" height="35" align="left">
|
||||||
<bright proportion="1" height="100%"
|
<bright proportion="1" height="100%"
|
||||||
I18N="In soccer setup screen" text="Game type (Goals limit / Time limit)" text_align="left" />
|
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"/>
|
<checkbox id="time_enabled"/>
|
||||||
</div>
|
</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">
|
<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 -->
|
<!-- Content is added programmatically -->
|
||||||
</roundedbox>
|
</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>
|
</div>
|
||||||
|
|
||||||
<button id="continue" I18N="In soccer setup screen" text="Continue" align="center" width="60%"/>
|
<button id="continue" I18N="In soccer setup screen" text="Continue" align="center" width="60%"/>
|
||||||
|
@ -101,6 +101,8 @@
|
|||||||
w-multi=" 0 30 30 100 60 0 0 0 0 0" />
|
w-multi=" 0 30 30 100 60 0 0 0 0 0" />
|
||||||
<battle w="10 30 60 0 0 10 30 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" />
|
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"
|
<tuto w=" 0 0 0 0 0 0 0 0 0 0"
|
||||||
w-multi=" 0 0 100 0 0 0 0 0 0 0" />
|
w-multi=" 0 0 100 0 0 0 0 0 0 0" />
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Modify this file to change the last-modified date when you add/remove a file.
|
# 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_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
|
||||||
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
|
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
|
||||||
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")
|
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")
|
||||||
|
@ -335,9 +335,15 @@ namespace UserConfigParams
|
|||||||
PARAM_PREFIX IntUserConfigParam m_num_goals
|
PARAM_PREFIX IntUserConfigParam m_num_goals
|
||||||
PARAM_DEFAULT( IntUserConfigParam(3, "numgoals",
|
PARAM_DEFAULT( IntUserConfigParam(3, "numgoals",
|
||||||
&m_race_setup_group, "Default number of goals in soccer mode.") );
|
&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_PREFIX IntUserConfigParam m_soccer_time_limit
|
||||||
PARAM_DEFAULT( IntUserConfigParam(3, "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_PREFIX IntUserConfigParam m_difficulty
|
||||||
PARAM_DEFAULT( IntUserConfigParam(0, "difficulty",
|
PARAM_DEFAULT( IntUserConfigParam(0, "difficulty",
|
||||||
&m_race_setup_group,
|
&m_race_setup_group,
|
||||||
|
@ -65,10 +65,18 @@ MaterialManager::~MaterialManager()
|
|||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
Material* MaterialManager::getMaterialFor(video::ITexture* t,
|
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)
|
if (t == NULL)
|
||||||
return getDefaultMaterial(mb->getMaterial().MaterialType);
|
return getDefaultMaterial(material_type);
|
||||||
|
|
||||||
core::stringc img_path = core::stringc(t->getName());
|
core::stringc img_path = core::stringc(t->getName());
|
||||||
const std::string image = StringUtils::getBasename(img_path.c_str());
|
const std::string image = StringUtils::getBasename(img_path.c_str());
|
||||||
@ -94,7 +102,7 @@ Material* MaterialManager::getMaterialFor(video::ITexture* t,
|
|||||||
} // for i
|
} // for i
|
||||||
}
|
}
|
||||||
|
|
||||||
return getDefaultMaterial(mb->getMaterial().MaterialType);
|
return getDefaultMaterial(material_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -59,6 +59,8 @@ public:
|
|||||||
void loadMaterial ();
|
void loadMaterial ();
|
||||||
Material* getMaterialFor(video::ITexture* t,
|
Material* getMaterialFor(video::ITexture* t,
|
||||||
scene::IMeshBuffer *mb);
|
scene::IMeshBuffer *mb);
|
||||||
|
Material* getMaterialFor(video::ITexture* t,
|
||||||
|
video::E_MATERIAL_TYPE material_type);
|
||||||
void setAllMaterialFlags(video::ITexture* t,
|
void setAllMaterialFlags(video::ITexture* t,
|
||||||
scene::IMeshBuffer *mb);
|
scene::IMeshBuffer *mb);
|
||||||
void adjustForFog(video::ITexture* t,
|
void adjustForFog(video::ITexture* t,
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "graphics/glwrap.hpp"
|
#include "graphics/glwrap.hpp"
|
||||||
#include "graphics/irr_driver.hpp"
|
#include "graphics/irr_driver.hpp"
|
||||||
|
#include "graphics/material.hpp"
|
||||||
|
#include "graphics/material_manager.hpp"
|
||||||
#include "graphics/shaders.hpp"
|
#include "graphics/shaders.hpp"
|
||||||
#include "graphics/shared_gpu_objects.hpp"
|
#include "graphics/shared_gpu_objects.hpp"
|
||||||
|
|
||||||
@ -92,12 +94,20 @@ void STKBillboard::render()
|
|||||||
{
|
{
|
||||||
if (irr_driver->getPhase() != TRANSPARENT_PASS)
|
if (irr_driver->getPhase() != TRANSPARENT_PASS)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
core::vector3df pos = getAbsolutePosition();
|
core::vector3df pos = getAbsolutePosition();
|
||||||
glBindVertexArray(billboardvao);
|
glBindVertexArray(billboardvao);
|
||||||
video::ITexture *tex = Material.getTexture(0);
|
video::ITexture *tex = Material.getTexture(0);
|
||||||
if (!tex )
|
if (!tex )
|
||||||
return;
|
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);
|
compressTexture(tex, true, true);
|
||||||
GLuint texid = getTextureGLuint(tex);
|
GLuint texid = getTextureGLuint(tex);
|
||||||
BillboardShader::getInstance()->use();
|
BillboardShader::getInstance()->use();
|
||||||
|
@ -483,6 +483,7 @@ std::set<wchar_t> ScalableFont::getPreloadCharacters(const GUIEngine::TTFLoading
|
|||||||
|
|
||||||
for (u32 i = 47; i < 59; ++i)
|
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)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;
|
break;
|
||||||
case T_BOLD:
|
case T_BOLD:
|
||||||
preload_char = translations->getCurrentAllChar(); //Loading unique characters
|
preload_char = translations->getCurrentAllChar(); //Loading unique characters
|
||||||
|
@ -69,7 +69,7 @@ namespace GUIEngine
|
|||||||
|
|
||||||
void setText(const irr::core::stringw &s);
|
void setText(const irr::core::stringw &s);
|
||||||
|
|
||||||
|
virtual int getHeightNeededAroundLabel() const { return 10; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
#include "karts/abstract_kart.hpp"
|
#include "karts/abstract_kart.hpp"
|
||||||
#include "karts/explosion_animation.hpp"
|
#include "karts/explosion_animation.hpp"
|
||||||
#include "modes/world.hpp"
|
#include "modes/world.hpp"
|
||||||
|
#include "modes/soccer_world.hpp"
|
||||||
#include "physics/physics.hpp"
|
#include "physics/physics.hpp"
|
||||||
#include "tracks/track.hpp"
|
#include "tracks/track.hpp"
|
||||||
#include "utils/constants.hpp"
|
#include "utils/constants.hpp"
|
||||||
@ -226,6 +227,16 @@ void Flyable::getClosestKart(const AbstractKart **minKart,
|
|||||||
if(kart->isEliminated() || kart == m_owner ||
|
if(kart->isEliminated() || kart == m_owner ||
|
||||||
kart->isInvulnerable() ||
|
kart->isInvulnerable() ||
|
||||||
kart->getKartAnimation() ) continue;
|
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();
|
btTransform t=kart->getTrans();
|
||||||
|
|
||||||
Vec3 delta = t.getOrigin()-trans_projectile.getOrigin();
|
Vec3 delta = t.getOrigin()-trans_projectile.getOrigin();
|
||||||
@ -461,6 +472,15 @@ void Flyable::explode(AbstractKart *kart_hit, PhysicalObject *object,
|
|||||||
{
|
{
|
||||||
AbstractKart *kart = world->getKart(i);
|
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
|
// If no secondary hits should be done, only hit the
|
||||||
// direct hit kart.
|
// direct hit kart.
|
||||||
if(!secondary_hits && kart!=kart_hit)
|
if(!secondary_hits && kart!=kart_hit)
|
||||||
|
@ -136,6 +136,7 @@ void PowerupManager::loadAllPowerups()
|
|||||||
loadWeights(*root, "end33", POSITION_END33 );
|
loadWeights(*root, "end33", POSITION_END33 );
|
||||||
loadWeights(*root, "last" , POSITION_LAST );
|
loadWeights(*root, "last" , POSITION_LAST );
|
||||||
loadWeights(*root, "battle" , POSITION_BATTLE_MODE);
|
loadWeights(*root, "battle" , POSITION_BATTLE_MODE);
|
||||||
|
loadWeights(*root, "soccer" , POSITION_SOCCER_MODE);
|
||||||
loadWeights(*root, "tuto", POSITION_TUTORIAL_MODE);
|
loadWeights(*root, "tuto", POSITION_TUTORIAL_MODE);
|
||||||
|
|
||||||
delete root;
|
delete root;
|
||||||
@ -273,7 +274,8 @@ void PowerupManager::updateWeightsForRace(unsigned int num_karts)
|
|||||||
{
|
{
|
||||||
m_position_to_class.clear();
|
m_position_to_class.clear();
|
||||||
// In battle mode no positions exist, so use only position 1
|
// 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++)
|
for(unsigned int position =1; position <= end_position; position++)
|
||||||
{
|
{
|
||||||
// Set up the mapping of position to position class:
|
// Set up the mapping of position to position class:
|
||||||
@ -322,6 +324,7 @@ PowerupManager::PositionClass
|
|||||||
unsigned int position)
|
unsigned int position)
|
||||||
{
|
{
|
||||||
if(race_manager->isBattleMode()) return POSITION_BATTLE_MODE;
|
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(race_manager->isTutorialMode()) return POSITION_TUTORIAL_MODE;
|
||||||
if(position==1) return POSITION_FIRST;
|
if(position==1) return POSITION_FIRST;
|
||||||
if(position==num_karts) return POSITION_LAST;
|
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
|
// Positions start with 1, while the index starts with 0 - so subtract 1
|
||||||
PositionClass pos_class =
|
PositionClass pos_class =
|
||||||
(race_manager->isBattleMode() ? POSITION_BATTLE_MODE :
|
(race_manager->isBattleMode() ? POSITION_BATTLE_MODE :
|
||||||
|
race_manager->isSoccerMode() ? POSITION_SOCCER_MODE :
|
||||||
(race_manager->isTutorialMode() ? POSITION_TUTORIAL_MODE :
|
(race_manager->isTutorialMode() ? POSITION_TUTORIAL_MODE :
|
||||||
m_position_to_class[pos-1]));
|
m_position_to_class[pos-1]));
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ public:
|
|||||||
POSITION_END33,
|
POSITION_END33,
|
||||||
POSITION_LAST,
|
POSITION_LAST,
|
||||||
POSITION_BATTLE_MODE,
|
POSITION_BATTLE_MODE,
|
||||||
|
POSITION_SOCCER_MODE,
|
||||||
POSITION_TUTORIAL_MODE,
|
POSITION_TUTORIAL_MODE,
|
||||||
POSITION_COUNT};
|
POSITION_COUNT};
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
#include "karts/explosion_animation.hpp"
|
#include "karts/explosion_animation.hpp"
|
||||||
#include "karts/kart_properties.hpp"
|
#include "karts/kart_properties.hpp"
|
||||||
#include "modes/world.hpp"
|
#include "modes/world.hpp"
|
||||||
|
#include "modes/soccer_world.hpp"
|
||||||
#include "karts/abstract_kart.hpp"
|
#include "karts/abstract_kart.hpp"
|
||||||
|
|
||||||
#define SWAT_POS_OFFSET core::vector3df(0.0, 0.2f, -0.4f)
|
#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
|
// change the current phase
|
||||||
squashThingsAround();
|
squashThingsAround();
|
||||||
m_animation_phase = SWATTER_FROM_TARGET;
|
m_animation_phase = SWATTER_FROM_TARGET;
|
||||||
if (race_manager->getMinorMode()==
|
if (race_manager
|
||||||
RaceManager::MINOR_MODE_3_STRIKES)
|
->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
|
// after one successful hit
|
||||||
m_discard_now = true;
|
m_discard_now = true;
|
||||||
}
|
}
|
||||||
@ -246,6 +249,15 @@ void Swatter::chooseTarget()
|
|||||||
if (kart->isInvulnerable() || kart->isSquashed())
|
if (kart->isInvulnerable() || kart->isSquashed())
|
||||||
continue;
|
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();
|
float dist2 = (kart->getXYZ()-m_kart->getXYZ()).length2();
|
||||||
if(dist2<min_dist2)
|
if(dist2<min_dist2)
|
||||||
{
|
{
|
||||||
|
@ -183,7 +183,7 @@ float AIBaseController::normalizeAngle(float angle)
|
|||||||
void AIBaseController::setSteering(float angle, float dt)
|
void AIBaseController::setSteering(float angle, float dt)
|
||||||
{
|
{
|
||||||
float steer_fraction = angle / m_kart->getMaxSteerAngle();
|
float steer_fraction = angle / m_kart->getMaxSteerAngle();
|
||||||
if(!doSkid(steer_fraction))
|
if(!canSkid(steer_fraction))
|
||||||
m_controls->m_skid = KartControl::SC_NONE;
|
m_controls->m_skid = KartControl::SC_NONE;
|
||||||
else
|
else
|
||||||
m_controls->m_skid = steer_fraction > 0 ? KartControl::SC_RIGHT
|
m_controls->m_skid = steer_fraction > 0 ? KartControl::SC_RIGHT
|
||||||
@ -213,15 +213,6 @@ void AIBaseController::setSteering(float angle, float dt)
|
|||||||
}
|
}
|
||||||
} // setSteering
|
} // 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
|
/** Certain AI levels will not receive a slipstream bonus in order to
|
||||||
* be not as hard.
|
* be not as hard.
|
||||||
@ -231,23 +222,6 @@ bool AIBaseController::disableSlipstreamBonus() const
|
|||||||
return m_ai_properties->disableSlipstreamUsage();
|
return m_ai_properties->disableSlipstreamUsage();
|
||||||
} // disableSlipstreamBonus
|
} // 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
|
/** 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
|
* 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)
|
} // 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
|
||||||
|
@ -42,6 +42,7 @@ private:
|
|||||||
bool m_stuck;
|
bool m_stuck;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/** Length of the kart, storing it here saves many function calls. */
|
/** Length of the kart, storing it here saves many function calls. */
|
||||||
float m_kart_length;
|
float m_kart_length;
|
||||||
|
|
||||||
@ -49,23 +50,27 @@ protected:
|
|||||||
float m_kart_width;
|
float m_kart_width;
|
||||||
|
|
||||||
/** Keep a pointer to the track to reduce calls */
|
/** Keep a pointer to the track to reduce calls */
|
||||||
Track *m_track;
|
Track *m_track;
|
||||||
|
|
||||||
/** A pointer to the AI properties for this kart. */
|
/** A pointer to the AI properties for this kart. */
|
||||||
const AIProperties *m_ai_properties;
|
const AIProperties *m_ai_properties;
|
||||||
|
|
||||||
static bool m_ai_debug;
|
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 update (float delta) ;
|
||||||
virtual void setSteering (float angle, float dt);
|
virtual void setSteering (float angle, float dt);
|
||||||
void setControllerName(const std::string &name);
|
virtual bool canSkid(float steer_fraction) = 0;
|
||||||
float steerToPoint(const Vec3 &point);
|
|
||||||
float normalizeAngle(float angle);
|
|
||||||
virtual bool doSkid(float steer_fraction);
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** This can be called to detect if the kart is stuck (i.e. repeatedly
|
/** This can be called to detect if the kart is stuck (i.e. repeatedly
|
||||||
* hitting part of the track). */
|
* 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:
|
public:
|
||||||
AIBaseController(AbstractKart *kart);
|
AIBaseController(AbstractKart *kart);
|
||||||
@ -83,7 +88,8 @@ public:
|
|||||||
virtual bool isPlayerController() const { return false; }
|
virtual bool isPlayerController() const { return false; }
|
||||||
virtual bool isLocalPlayerController() const { return false; }
|
virtual bool isLocalPlayerController() const { return false; }
|
||||||
virtual void action(PlayerAction action, int value) {};
|
virtual void action(PlayerAction action, int value) {};
|
||||||
virtual void skidBonusTriggered() {};
|
virtual void skidBonusTriggered() {};
|
||||||
|
|
||||||
}; // AIBaseController
|
}; // AIBaseController
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -70,16 +70,6 @@ public:
|
|||||||
AIBaseLapController(AbstractKart *kart);
|
AIBaseLapController(AbstractKart *kart);
|
||||||
virtual ~AIBaseLapController() {};
|
virtual ~AIBaseLapController() {};
|
||||||
virtual void reset();
|
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
|
}; // AIBaseLapController
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -45,7 +45,7 @@ protected:
|
|||||||
friend class AIBaseController;
|
friend class AIBaseController;
|
||||||
friend class AIBaseLapController;
|
friend class AIBaseLapController;
|
||||||
friend class SkiddingAI;
|
friend class SkiddingAI;
|
||||||
friend class BattleAI;
|
friend class ArenaAI;
|
||||||
|
|
||||||
/** Used to check that all values are defined in the xml file. */
|
/** Used to check that all values are defined in the xml file. */
|
||||||
static float UNDEFINED;
|
static float UNDEFINED;
|
||||||
|
777
src/karts/controller/arena_ai.cpp
Normal file
777
src/karts/controller/arena_ai.cpp
Normal 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
|
135
src/karts/controller/arena_ai.hpp
Normal file
135
src/karts/controller/arena_ai.hpp
Normal 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
|
@ -21,20 +21,10 @@
|
|||||||
#include "karts/controller/battle_ai.hpp"
|
#include "karts/controller/battle_ai.hpp"
|
||||||
|
|
||||||
#include "items/attachment.hpp"
|
#include "items/attachment.hpp"
|
||||||
#include "items/item_manager.hpp"
|
|
||||||
#include "items/powerup.hpp"
|
#include "items/powerup.hpp"
|
||||||
#include "items/projectile_manager.hpp"
|
|
||||||
#include "karts/abstract_kart.hpp"
|
#include "karts/abstract_kart.hpp"
|
||||||
#include "karts/controller/kart_control.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 "modes/three_strikes_battle.hpp"
|
||||||
#include "tracks/battle_graph.hpp"
|
|
||||||
#include "utils/log.hpp"
|
|
||||||
|
|
||||||
#ifdef AI_DEBUG
|
#ifdef AI_DEBUG
|
||||||
#include "irrlicht.h"
|
#include "irrlicht.h"
|
||||||
@ -50,7 +40,7 @@ using namespace std;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
BattleAI::BattleAI(AbstractKart *kart)
|
BattleAI::BattleAI(AbstractKart *kart)
|
||||||
: AIBaseController(kart)
|
: ArenaAI(kart)
|
||||||
{
|
{
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
@ -60,19 +50,8 @@ BattleAI::BattleAI(AbstractKart *kart)
|
|||||||
m_debug_sphere = irr_driver->addSphere(1.0f, col_debug);
|
m_debug_sphere = irr_driver->addSphere(1.0f, col_debug);
|
||||||
m_debug_sphere->setVisible(true);
|
m_debug_sphere->setVisible(true);
|
||||||
#endif
|
#endif
|
||||||
|
m_world = dynamic_cast<ThreeStrikesBattle*>(World::getWorld());
|
||||||
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES)
|
m_track = m_world->getTrack();
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't call our own setControllerName, since this will add a
|
// Don't call our own setControllerName, since this will add a
|
||||||
// billboard showing 'AIBaseController' to the kart.
|
// billboard showing 'AIBaseController' to the kart.
|
||||||
@ -94,162 +73,20 @@ BattleAI::~BattleAI()
|
|||||||
*/
|
*/
|
||||||
void BattleAI::reset()
|
void BattleAI::reset()
|
||||||
{
|
{
|
||||||
m_target_node = BattleGraph::UNKNOWN_POLY;
|
ArenaAI::reset();
|
||||||
m_adjusting_side = false;
|
|
||||||
m_closest_kart = NULL;
|
|
||||||
m_closest_kart_node = BattleGraph::UNKNOWN_POLY;
|
|
||||||
m_closest_kart_point = Vec3(0, 0, 0);
|
|
||||||
m_closest_kart_pos_data = {0};
|
|
||||||
m_cur_kart_pos_data = {0};
|
|
||||||
m_is_steering_overridden = false;
|
|
||||||
m_is_stuck = false;
|
|
||||||
m_is_uturn = false;
|
|
||||||
m_target_point = Vec3(0, 0, 0);
|
|
||||||
m_time_since_last_shot = 0.0f;
|
|
||||||
m_time_since_driving = 0.0f;
|
|
||||||
m_time_since_reversing = 0.0f;
|
|
||||||
m_time_since_steering_overridden = 0.0f;
|
|
||||||
m_time_since_uturn = 0.0f;
|
|
||||||
m_on_node.clear();
|
|
||||||
m_path_corners.clear();
|
|
||||||
m_portals.clear();
|
|
||||||
|
|
||||||
m_cur_difficulty = race_manager->getDifficulty();
|
|
||||||
AIBaseController::reset();
|
AIBaseController::reset();
|
||||||
|
m_mini_skid = false;
|
||||||
} // reset
|
} // reset
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** This is the main entry point for the AI.
|
|
||||||
* It is called once per frame for each AI and determines the behaviour of
|
|
||||||
* the AI, e.g. steering, accelerating/braking, firing.
|
|
||||||
*/
|
|
||||||
void BattleAI::update(float dt)
|
void BattleAI::update(float dt)
|
||||||
{
|
{
|
||||||
// This is used to enable firing an item backwards.
|
m_mini_skid = false;
|
||||||
m_controls->m_look_back = false;
|
ArenaAI::update(dt);
|
||||||
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);
|
|
||||||
|
|
||||||
} // update
|
} // update
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void BattleAI::checkIfStuck(const float dt)
|
void BattleAI::findClosestKart(bool use_difficulty)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
float distance = 99999.9f;
|
float distance = 99999.9f;
|
||||||
const unsigned int n = m_world->getNumKarts();
|
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
|
// Test whether takes current difficulty into account for closest kart
|
||||||
// Notice: it don't affect aiming, this function will be called once
|
// Notice: it don't affect aiming, this function will be called once
|
||||||
// more in handleItems, which ignore difficulty.
|
// more in handleArenaItems, which ignore difficulty.
|
||||||
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && difficulty)
|
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && use_difficulty)
|
||||||
{
|
{
|
||||||
// Skip human players for novice mode unless only human players left
|
// Skip human players for novice mode unless only human players left
|
||||||
const AbstractKart* temp = m_world->getKart(i);
|
const AbstractKart* temp = m_world->getKart(i);
|
||||||
@ -275,7 +112,7 @@ void BattleAI::findClosestKart(bool difficulty)
|
|||||||
m_world->getCurrentNumPlayers()) > 1)
|
m_world->getCurrentNumPlayers()) > 1)
|
||||||
continue;
|
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
|
// Skip AI players for supertux mode
|
||||||
const AbstractKart* temp = m_world->getKart(i);
|
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_node = m_world->getKartNode(closest_kart_num);
|
||||||
m_closest_kart_point = closest_kart->getXYZ();
|
m_closest_kart_point = closest_kart->getXYZ();
|
||||||
|
|
||||||
if (!difficulty)
|
if (!use_difficulty)
|
||||||
{
|
{
|
||||||
m_closest_kart = m_world->getKart(closest_kart_num);
|
m_closest_kart = m_world->getKart(closest_kart_num);
|
||||||
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
|
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
|
} // findClosestKart
|
||||||
|
|
||||||
@ -308,7 +155,7 @@ void BattleAI::findTarget()
|
|||||||
// Find a suitable target to drive to, either powerup or kart
|
// Find a suitable target to drive to, either powerup or kart
|
||||||
if (m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
|
if (m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
|
||||||
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER)
|
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER)
|
||||||
handleItemCollection(&m_target_point , &m_target_node);
|
collectItemInArena(&m_target_point , &m_target_node);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_target_point = m_closest_kart_point;
|
m_target_point = m_closest_kart_point;
|
||||||
@ -316,615 +163,13 @@ void BattleAI::findTarget()
|
|||||||
}
|
}
|
||||||
} // findTarget
|
} // findTarget
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Handles acceleration.
|
int BattleAI::getCurrentNode() const
|
||||||
* \param dt Time step size.
|
|
||||||
*/
|
|
||||||
void BattleAI::handleAcceleration(const float dt)
|
|
||||||
{
|
{
|
||||||
if (m_controls->m_brake)
|
return m_world->getKartNode(m_kart->getWorldKartId());
|
||||||
{
|
} // getCurrentNode
|
||||||
m_controls->m_accel = 0.0f;
|
// ------------------------------------------------------------------------
|
||||||
return;
|
bool BattleAI::isWaiting() const
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
|
return m_world->isStartPhase();
|
||||||
|
} // isWaiting
|
||||||
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
|
|
||||||
|
@ -21,139 +21,33 @@
|
|||||||
#ifndef HEADER_BATTLE_AI_HPP
|
#ifndef HEADER_BATTLE_AI_HPP
|
||||||
#define HEADER_BATTLE_AI_HPP
|
#define HEADER_BATTLE_AI_HPP
|
||||||
|
|
||||||
#undef AI_DEBUG
|
#include "karts/controller/arena_ai.hpp"
|
||||||
#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"
|
|
||||||
|
|
||||||
class ThreeStrikesBattle;
|
class ThreeStrikesBattle;
|
||||||
class Vec3;
|
class Vec3;
|
||||||
class Item;
|
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:
|
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. */
|
/** Keep a pointer to world. */
|
||||||
ThreeStrikesBattle *m_world;
|
ThreeStrikesBattle *m_world;
|
||||||
|
|
||||||
#ifdef AI_DEBUG
|
bool m_mini_skid;
|
||||||
/** For debugging purpose: a sphere indicating where the AI
|
|
||||||
* is targeting at. */
|
|
||||||
irr::scene::ISceneNode *m_debug_sphere;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
virtual void findClosestKart(bool use_difficulty);
|
||||||
|
virtual void findTarget();
|
||||||
|
virtual int getCurrentNode() const;
|
||||||
|
virtual bool isWaiting() const;
|
||||||
|
virtual bool canSkid(float steer_fraction) { return m_mini_skid; }
|
||||||
public:
|
public:
|
||||||
BattleAI(AbstractKart *kart);
|
BattleAI(AbstractKart *kart);
|
||||||
~BattleAI();
|
~BattleAI();
|
||||||
virtual void update (float delta);
|
virtual void update (float delta);
|
||||||
virtual void reset ();
|
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
|
#endif
|
||||||
|
@ -74,12 +74,13 @@ private:
|
|||||||
*that can be done, and end up setting their respective m_controls
|
*that can be done, and end up setting their respective m_controls
|
||||||
*variable.
|
*variable.
|
||||||
*/
|
*/
|
||||||
void handleSteering(float dt);
|
void handleSteering(float dt);
|
||||||
void handleRescue(const float DELTA);
|
void handleRescue(const float DELTA);
|
||||||
|
|
||||||
void checkCrashes(const int STEPS, const Vec3& pos);
|
void checkCrashes(const int STEPS, const Vec3& pos);
|
||||||
void findNonCrashingPoint(Vec3 *result);
|
void findNonCrashingPoint(Vec3 *result);
|
||||||
int calcSteps();
|
int calcSteps();
|
||||||
|
virtual bool canSkid(float steer_fraction) { return false; }
|
||||||
public:
|
public:
|
||||||
EndController(AbstractKart *kart,
|
EndController(AbstractKart *kart,
|
||||||
Controller *prev_controller);
|
Controller *prev_controller);
|
||||||
|
@ -265,6 +265,13 @@ void PlayerController::update(float dt)
|
|||||||
if (!history->replayHistory())
|
if (!history->replayHistory())
|
||||||
steer(dt, m_steer_val);
|
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 (World::getWorld()->isStartPhase())
|
||||||
{
|
{
|
||||||
if (m_controls->m_accel || m_controls->m_brake ||
|
if (m_controls->m_accel || m_controls->m_brake ||
|
||||||
|
@ -34,9 +34,6 @@ protected:
|
|||||||
|
|
||||||
float m_penalty_time;
|
float m_penalty_time;
|
||||||
|
|
||||||
/** This variable is required for battle mode **/
|
|
||||||
int m_current_node;
|
|
||||||
|
|
||||||
virtual void steer(float, int);
|
virtual void steer(float, int);
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Called when this kart started too early and got a start penalty. */
|
/** 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 isPlayerController() const OVERRIDE { return true; }
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
virtual bool isLocalPlayerController() const OVERRIDE { return true; }
|
virtual bool isLocalPlayerController() const OVERRIDE { return true; }
|
||||||
|
@ -1238,8 +1238,6 @@ void SkiddingAI::handleItems(const float dt)
|
|||||||
}
|
}
|
||||||
break; // POWERUP_BUBBLEGUM
|
break; // POWERUP_BUBBLEGUM
|
||||||
}
|
}
|
||||||
// All the thrown/fired items might be improved by considering the angle
|
|
||||||
// towards m_kart_ahead.
|
|
||||||
case PowerupManager::POWERUP_CAKE:
|
case PowerupManager::POWERUP_CAKE:
|
||||||
{
|
{
|
||||||
// if the kart has a shield, do not break it by using a 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
|
// slower, so it should take longer to hit something which
|
||||||
// can result in changing our target.
|
// can result in changing our target.
|
||||||
if(m_time_since_last_shot < 5.0f) break;
|
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
|
// Bowling balls are slower, so only fire on closer karts - but when
|
||||||
// firing backwards, the kart can be further away, since the ball
|
// 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)
|
// 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_distance_ahead;
|
||||||
m_controls->m_fire = ( (fire_backwards && distance < 30.0f) ||
|
m_controls->m_fire = ( (fire_backwards && distance < 30.0f) ||
|
||||||
(!fire_backwards && distance <10.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)
|
if(m_controls->m_fire)
|
||||||
m_controls->m_look_back = fire_backwards;
|
m_controls->m_look_back = fire_backwards;
|
||||||
break;
|
break;
|
||||||
@ -2195,7 +2210,7 @@ void SkiddingAI::handleCurve()
|
|||||||
* AIBaseLapController.
|
* AIBaseLapController.
|
||||||
* \return True if the kart should skid.
|
* \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)
|
if(fabsf(steer_fraction)>1.5f)
|
||||||
{
|
{
|
||||||
@ -2304,7 +2319,7 @@ bool SkiddingAI::doSkid(float steer_fraction)
|
|||||||
m_kart->getIdent().c_str());
|
m_kart->getIdent().c_str());
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
} // doSkid
|
} // canSkid
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Converts the steering angle to a lr steering in the range of -1 to 1.
|
/** 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
|
// 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
|
// whether to skid or not only once per skid section. See docs for
|
||||||
// m_skid_probability_state for more details.
|
// m_skid_probability_state for more details.
|
||||||
if(!doSkid(steer_fraction))
|
if(!canSkid(steer_fraction))
|
||||||
{
|
{
|
||||||
m_skid_probability_state = SKID_PROBAB_NOT_YET;
|
m_skid_probability_state = SKID_PROBAB_NOT_YET;
|
||||||
m_controls->m_skid = KartControl::SC_NONE;
|
m_controls->m_skid = KartControl::SC_NONE;
|
||||||
|
@ -94,7 +94,7 @@ the AI does the following steps:
|
|||||||
behaviour.
|
behaviour.
|
||||||
The function handleSteering() then calls setSteering() to set the
|
The function handleSteering() then calls setSteering() to set the
|
||||||
actually steering amount. The latter function also decides if skidding
|
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).
|
- decide if to try to collect or avoid items (handeItems).
|
||||||
It considers all items on quads between the current quad of the kart
|
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
|
and the quad the AI is aiming at (see handleSteering). If it finds
|
||||||
@ -270,7 +270,7 @@ private:
|
|||||||
const Vec3 &end,
|
const Vec3 &end,
|
||||||
Vec3 *center,
|
Vec3 *center,
|
||||||
float *radius);
|
float *radius);
|
||||||
virtual bool doSkid(float steer_fraction);
|
virtual bool canSkid(float steer_fraction);
|
||||||
virtual void setSteering(float angle, float dt);
|
virtual void setSteering(float angle, float dt);
|
||||||
void handleCurve();
|
void handleCurve();
|
||||||
|
|
||||||
|
260
src/karts/controller/soccer_ai.cpp
Normal file
260
src/karts/controller/soccer_ai.cpp
Normal 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
|
54
src/karts/controller/soccer_ai.hpp
Normal file
54
src/karts/controller/soccer_ai.hpp
Normal 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
|
@ -44,6 +44,7 @@
|
|||||||
#include "karts/kart_gfx.hpp"
|
#include "karts/kart_gfx.hpp"
|
||||||
#include "karts/rescue_animation.hpp"
|
#include "karts/rescue_animation.hpp"
|
||||||
#include "modes/overworld.hpp"
|
#include "modes/overworld.hpp"
|
||||||
|
#include "modes/soccer_world.hpp"
|
||||||
#include "modes/world.hpp"
|
#include "modes/world.hpp"
|
||||||
#include "io/file_manager.hpp"
|
#include "io/file_manager.hpp"
|
||||||
#include "items/attachment.hpp"
|
#include "items/attachment.hpp"
|
||||||
@ -883,7 +884,6 @@ void Kart::setRaceResult()
|
|||||||
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
|
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
|
||||||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL)
|
race_manager->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL)
|
||||||
{
|
{
|
||||||
// TODO NetworkController?
|
|
||||||
if (m_controller->isLocalPlayerController()) // if player is on this computer
|
if (m_controller->isLocalPlayerController()) // if player is on this computer
|
||||||
{
|
{
|
||||||
PlayerProfile *player = PlayerManager::getCurrentPlayer();
|
PlayerProfile *player = PlayerManager::getCurrentPlayer();
|
||||||
@ -920,8 +920,8 @@ void Kart::setRaceResult()
|
|||||||
}
|
}
|
||||||
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
||||||
{
|
{
|
||||||
// TODO complete together with soccer ai!
|
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||||
m_race_result = true;
|
m_race_result = sw->getKartSoccerResult(this->getWorldKartId());
|
||||||
}
|
}
|
||||||
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG)
|
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG)
|
||||||
{
|
{
|
||||||
@ -931,7 +931,7 @@ void Kart::setRaceResult()
|
|||||||
else
|
else
|
||||||
Log::warn("Kart", "Unknown game mode given.");
|
Log::warn("Kart", "Unknown game mode given.");
|
||||||
|
|
||||||
} // setKartResult
|
} // setRaceResult
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Called when an item is collected. It will either adjust the collected
|
/** Called when an item is collected. It will either adjust the collected
|
||||||
|
@ -138,6 +138,7 @@ KartModel::KartModel(bool is_master)
|
|||||||
m_animation_frame[i]=-1;
|
m_animation_frame[i]=-1;
|
||||||
m_animation_speed = 25;
|
m_animation_speed = 25;
|
||||||
m_current_animation = AF_DEFAULT;
|
m_current_animation = AF_DEFAULT;
|
||||||
|
m_play_non_loop = false;
|
||||||
} // KartModel
|
} // KartModel
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -682,11 +683,12 @@ void KartModel::finishedRace()
|
|||||||
/** Enables- or disables the end animation.
|
/** Enables- or disables the end animation.
|
||||||
* \param type The type of animation to play.
|
* \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 animations disabled, give up
|
||||||
if (m_animated_node == NULL) return;
|
if (m_animated_node == NULL) return;
|
||||||
|
|
||||||
|
m_play_non_loop = play_non_loop;
|
||||||
m_current_animation = type;
|
m_current_animation = type;
|
||||||
if(m_current_animation==AF_DEFAULT)
|
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 animations are disabled, stop here
|
||||||
if (m_animated_node == NULL) return;
|
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
|
// Update the speed-weighted objects' animations
|
||||||
if (m_kart != NULL)
|
if (m_kart != NULL)
|
||||||
{
|
{
|
||||||
|
@ -207,9 +207,14 @@ private:
|
|||||||
|
|
||||||
/** True if this is the master copy, managed by KartProperties. This
|
/** True if this is the master copy, managed by KartProperties. This
|
||||||
* is mainly used for debugging, e.g. the master copies might not have
|
* 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;
|
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,
|
void loadWheelInfo(const XMLNode &node,
|
||||||
const std::string &wheel_name, int index);
|
const std::string &wheel_name, int index);
|
||||||
|
|
||||||
@ -306,7 +311,7 @@ public:
|
|||||||
AnimationFrameType getAnimation() { return m_current_animation; }
|
AnimationFrameType getAnimation() { return m_current_animation; }
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Enables- or disables the end 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 */
|
/** Sets the kart this model is currently used for */
|
||||||
void setKart(AbstractKart* k) { m_kart = k; }
|
void setKart(AbstractKart* k) { m_kart = k; }
|
||||||
|
@ -27,16 +27,17 @@
|
|||||||
#include "karts/kart_properties.hpp"
|
#include "karts/kart_properties.hpp"
|
||||||
#include "karts/rescue_animation.hpp"
|
#include "karts/rescue_animation.hpp"
|
||||||
#include "karts/controller/local_player_controller.hpp"
|
#include "karts/controller/local_player_controller.hpp"
|
||||||
|
#include "karts/controller/soccer_ai.hpp"
|
||||||
#include "physics/physics.hpp"
|
#include "physics/physics.hpp"
|
||||||
#include "states_screens/race_gui_base.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.hpp"
|
||||||
#include "tracks/track_object_manager.hpp"
|
#include "tracks/track_object_manager.hpp"
|
||||||
#include "utils/constants.hpp"
|
#include "utils/constants.hpp"
|
||||||
|
|
||||||
#include <IMeshSceneNode.h>
|
#include <IMeshSceneNode.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Constructor. Sets up the clock mode etc.
|
/** Constructor. Sets up the clock mode etc.
|
||||||
*/
|
*/
|
||||||
@ -45,7 +46,7 @@ SoccerWorld::SoccerWorld() : WorldWithRank()
|
|||||||
if(race_manager->hasTimeTarget())
|
if(race_manager->hasTimeTarget())
|
||||||
{
|
{
|
||||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
||||||
countDownReachedZero = false;
|
m_count_down_reached_zero = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -69,24 +70,19 @@ SoccerWorld::~SoccerWorld()
|
|||||||
*/
|
*/
|
||||||
void SoccerWorld::init()
|
void SoccerWorld::init()
|
||||||
{
|
{
|
||||||
|
m_kart_team_map.clear();
|
||||||
|
m_kart_position_map.clear();
|
||||||
WorldWithRank::init();
|
WorldWithRank::init();
|
||||||
m_display_rank = false;
|
m_display_rank = false;
|
||||||
m_goal_timer = 0.f;
|
m_goal_timer = 0.0f;
|
||||||
m_lastKartToHitBall = -1;
|
m_ball_hitter = -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_target = race_manager->getMaxGoal();
|
m_goal_target = race_manager->getMaxGoal();
|
||||||
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
|
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
|
||||||
|
|
||||||
} // init
|
} // init
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Called when a battle is restarted.
|
/** Called when a soccer game is restarted.
|
||||||
*/
|
*/
|
||||||
void SoccerWorld::reset()
|
void SoccerWorld::reset()
|
||||||
{
|
{
|
||||||
@ -94,31 +90,37 @@ void SoccerWorld::reset()
|
|||||||
if(race_manager->hasTimeTarget())
|
if(race_manager->hasTimeTarget())
|
||||||
{
|
{
|
||||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
||||||
countDownReachedZero = false;
|
m_count_down_reached_zero = false;
|
||||||
}
|
}
|
||||||
else WorldStatus::setClockMode(CLOCK_CHRONO);
|
else WorldStatus::setClockMode(CLOCK_CHRONO);
|
||||||
|
|
||||||
m_can_score_points = true;
|
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();
|
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
|
||||||
assert(tom);
|
assert(tom);
|
||||||
m_redScorers.clear();
|
|
||||||
m_redScoreTimes.clear();
|
|
||||||
m_blueScorers.clear();
|
|
||||||
m_blueScoreTimes.clear();
|
|
||||||
m_lastKartToHitBall = -1;
|
|
||||||
PtrVector<TrackObject>& objects = tom->getObjects();
|
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);
|
TrackObject* obj = objects.get(i);
|
||||||
if(!obj->isSoccerBall())
|
if(!obj->isSoccerBall())
|
||||||
continue;
|
continue;
|
||||||
|
m_ball = obj;
|
||||||
obj->reset();
|
// Handle one ball only
|
||||||
obj->getPhysicalObject()->reset();
|
break;
|
||||||
}
|
}
|
||||||
|
if (!m_ball)
|
||||||
|
Log::fatal("SoccerWorld","Ball is missing in soccer field, abort.");
|
||||||
|
|
||||||
if (m_goal_sound != NULL &&
|
if (m_goal_sound != NULL &&
|
||||||
m_goal_sound->getStatus() == SFXBase::SFX_PLAYING)
|
m_goal_sound->getStatus() == SFXBase::SFX_PLAYING)
|
||||||
@ -127,6 +129,10 @@ void SoccerWorld::reset()
|
|||||||
}
|
}
|
||||||
|
|
||||||
initKartList();
|
initKartList();
|
||||||
|
resetAllNodes();
|
||||||
|
initGoalNodes();
|
||||||
|
resetBall();
|
||||||
|
|
||||||
} // reset
|
} // reset
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
@ -148,111 +154,127 @@ void SoccerWorld::update(float dt)
|
|||||||
WorldWithRank::update(dt);
|
WorldWithRank::update(dt);
|
||||||
WorldWithRank::updateTrack(dt);
|
WorldWithRank::updateTrack(dt);
|
||||||
|
|
||||||
|
updateBallPosition(dt);
|
||||||
|
if (m_track->hasNavMesh())
|
||||||
|
{
|
||||||
|
updateKartNodes();
|
||||||
|
updateDefenders();
|
||||||
|
}
|
||||||
|
|
||||||
if (world->getPhase() == World::GOAL_PHASE)
|
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;
|
m_goal_timer += dt;
|
||||||
|
|
||||||
if (m_goal_timer > 3.0f)
|
if (m_goal_timer > 3.0f)
|
||||||
{
|
{
|
||||||
world->setPhase(WorldStatus::RACE_PHASE);
|
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
|
} // update
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void SoccerWorld::onCheckGoalTriggered(bool first_goal)
|
void SoccerWorld::onCheckGoalTriggered(bool first_goal)
|
||||||
{
|
{
|
||||||
if (isRaceOver())
|
if (isRaceOver() || isStartPhase())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_can_score_points)
|
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 *world = World::getWorld();
|
||||||
world->setPhase(WorldStatus::GOAL_PHASE);
|
world->setPhase(WorldStatus::GOAL_PHASE);
|
||||||
m_goal_sound->play();
|
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())
|
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
|
else
|
||||||
m_redScoreTimes.push_back(world->getTime());
|
m_red_score_times.push_back(world->getTime());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_blueScorers.push_back(m_lastKartToHitBall);
|
m_blue_scorers.push_back(sd);
|
||||||
if(race_manager->hasTimeTarget())
|
if (race_manager->hasTimeTarget())
|
||||||
m_blueScoreTimes.push_back(race_manager->getTimeTarget() - world->getTime());
|
{
|
||||||
|
m_blue_score_times.push_back(race_manager
|
||||||
|
->getTimeTarget() - world->getTime());
|
||||||
|
}
|
||||||
else
|
else
|
||||||
m_blueScoreTimes.push_back(world->getTime());
|
m_blue_score_times.push_back(world->getTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset original positions for the soccer balls
|
resetBall();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Resetting the ball triggers the goal check line one more time.
|
//Resetting the ball triggers the goal check line one more time.
|
||||||
//This ensures that only one goal is counted, and the second is ignored.
|
//This ensures that only one goal is counted, and the second is ignored.
|
||||||
m_can_score_points = !m_can_score_points;
|
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
|
} // onCheckGoalTriggered
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Sets the last kart that hit the ball, to be able to
|
/** Sets the last kart that hit the ball, to be able to
|
||||||
* identify the scorer later.
|
* identify the scorer later.
|
||||||
*/
|
*/
|
||||||
void SoccerWorld::setLastKartTohitBall(unsigned int kartId)
|
void SoccerWorld::setBallHitter(unsigned int kart_id)
|
||||||
{
|
{
|
||||||
m_lastKartToHitBall = kartId;
|
m_ball_hitter = kart_id;
|
||||||
} // setLastKartTohitBall
|
} // 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()
|
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())
|
if(race_manager->hasTimeTarget())
|
||||||
{
|
{
|
||||||
return countDownReachedZero;
|
return m_count_down_reached_zero;
|
||||||
}
|
}
|
||||||
// One team scored the target goals ...
|
// One team scored the target goals ...
|
||||||
else
|
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
|
} // isRaceOver
|
||||||
@ -270,116 +292,54 @@ void SoccerWorld::terminateRace()
|
|||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
/** Called when the match time ends.
|
/** Called when the match time ends.
|
||||||
*/
|
*/
|
||||||
void SoccerWorld::countdownReachedZero()
|
void SoccerWorld::countdownReachedZero()
|
||||||
{
|
{
|
||||||
countDownReachedZero = true;
|
m_count_down_reached_zero = true;
|
||||||
} // countdownReachedZero
|
} // 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()
|
void SoccerWorld::initKartList()
|
||||||
{
|
{
|
||||||
const unsigned int kart_amount = (unsigned int)m_karts.size();
|
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
|
//Loading the indicator textures
|
||||||
irr::video::ITexture *redTeamTexture =
|
irr::video::ITexture *red =
|
||||||
irr_driver->getTexture(FileManager::GUI, "soccer_player_red.png");
|
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");
|
irr_driver->getTexture(FileManager::GUI, "soccer_player_blue.png");
|
||||||
|
|
||||||
//Assigning indicators
|
//Assigning indicators
|
||||||
for(unsigned int i=0; i<kart_amount; i++)
|
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;
|
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),
|
arrow_node = irr_driver->addBillboard(core::dimension2d<irr::f32>(0.3f,
|
||||||
team==SOCCER_TEAM_RED ? redTeamTexture : blueTeamTexture,
|
0.3f), team == SOCCER_TEAM_BLUE ? blue : red, m_karts[i]
|
||||||
m_karts[i]->getNode(), true);
|
->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
|
} // initKartList
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
int SoccerWorld::getScore(unsigned int i)
|
bool SoccerWorld::getKartSoccerResult(unsigned int kart_id) const
|
||||||
{
|
{
|
||||||
return m_team_goals[i];
|
if (m_red_scorers.size() == m_blue_scorers.size()) return true;
|
||||||
} // getScore
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
bool red_win = m_red_scorers.size() > m_blue_scorers.size();
|
||||||
int SoccerWorld::getTeamLeader(unsigned int team)
|
SoccerTeam team = getKartTeam(kart_id);
|
||||||
{
|
|
||||||
for(unsigned int i = 0; i< m_karts.size(); i++)
|
if ((red_win && team == SOCCER_TEAM_RED) ||
|
||||||
{
|
(!red_win && team == SOCCER_TEAM_BLUE))
|
||||||
if(race_manager->getKartInfo(i).getSoccerTeam() == (SoccerTeam) team)
|
return true;
|
||||||
return i;
|
else
|
||||||
}
|
return false;
|
||||||
return -1;
|
|
||||||
} // getTeamLeader
|
} // getKartSoccerResult
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
|
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,
|
RaceManager::KartType kart_type,
|
||||||
PerPlayerDifficulty difficulty)
|
PerPlayerDifficulty difficulty)
|
||||||
{
|
{
|
||||||
int posIndex = index;
|
int cur_red = getTeamNum(SOCCER_TEAM_RED);
|
||||||
int position = index+1;
|
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
|
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,
|
AbstractKart *new_kart = new Kart(kart_ident, index, position, init_pos,
|
||||||
difficulty);
|
difficulty);
|
||||||
@ -434,4 +430,213 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
|
|||||||
} // createKart
|
} // 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
|
||||||
|
@ -23,40 +23,91 @@
|
|||||||
#include "states_screens/race_gui_base.hpp"
|
#include "states_screens/race_gui_base.hpp"
|
||||||
#include "karts/abstract_kart.hpp"
|
#include "karts/abstract_kart.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <IMesh.h>
|
#include <IMesh.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#define CLEAR_SPAWN_RANGE 5
|
|
||||||
|
|
||||||
class PhysicalObject;
|
|
||||||
class AbstractKart;
|
class AbstractKart;
|
||||||
class Controller;
|
class Controller;
|
||||||
|
class TrackObject;
|
||||||
|
|
||||||
/**
|
/** An implementation of World, to provide the soccer game mode
|
||||||
* \brief An implementation of World, to provide the soccer game mode
|
* Notice: In soccer world, true goal means blue, false means red.
|
||||||
* \ingroup modes
|
* \ingroup modes
|
||||||
*/
|
*/
|
||||||
class SoccerWorld : public WorldWithRank
|
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:
|
private:
|
||||||
/** Number of goals each team scored */
|
/** Keep a pointer to the track object of soccer ball */
|
||||||
int m_team_goals[NB_SOCCER_TEAMS];
|
TrackObject* m_ball;
|
||||||
|
|
||||||
/** Number of goals needed to win */
|
/** Number of goals needed to win */
|
||||||
int m_goal_target;
|
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
|
/** 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)*/
|
and re-enabled when the next game can be played)*/
|
||||||
bool m_can_score_points;
|
bool m_can_score_points;
|
||||||
SFXBase *m_goal_sound;
|
SFXBase *m_goal_sound;
|
||||||
|
|
||||||
/** Timer for displaying goal text*/
|
/** Timer for displaying goal text*/
|
||||||
float m_goal_timer;
|
float m_goal_timer;
|
||||||
int m_lastKartToHitBall;
|
float m_ball_invalid_timer;
|
||||||
std::vector<int> m_redScorers;
|
int m_ball_hitter;
|
||||||
std::vector<float> m_redScoreTimes;
|
|
||||||
std::vector<int> m_blueScorers;
|
/** Goals data of each team scored */
|
||||||
std::vector<float> m_blueScoreTimes;
|
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:
|
public:
|
||||||
|
|
||||||
SoccerWorld();
|
SoccerWorld();
|
||||||
@ -72,41 +123,61 @@ public:
|
|||||||
// overriding World methods
|
// overriding World methods
|
||||||
virtual void reset();
|
virtual void reset();
|
||||||
|
|
||||||
|
virtual unsigned int getRescuePositionIndex(AbstractKart *kart) OVERRIDE;
|
||||||
|
|
||||||
virtual bool useFastMusicNearEnd() const { return false; }
|
virtual bool useFastMusicNearEnd() const { return false; }
|
||||||
virtual void getKartsDisplayInfo(
|
virtual void getKartsDisplayInfo(
|
||||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info);
|
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) {}
|
||||||
int getScore(unsigned int i);
|
|
||||||
virtual bool raceHasLaps() { return false; }
|
virtual bool raceHasLaps() { return false; }
|
||||||
|
|
||||||
virtual const std::string& getIdent() const;
|
virtual const std::string& getIdent() const;
|
||||||
|
|
||||||
virtual void update(float dt);
|
virtual void update(float dt);
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void onCheckGoalTriggered(bool first_goal);
|
void onCheckGoalTriggered(bool first_goal);
|
||||||
int getTeamLeader(unsigned int i);
|
// ------------------------------------------------------------------------
|
||||||
void setLastKartTohitBall(unsigned int kartId);
|
void setBallHitter(unsigned int kart_id);
|
||||||
std::vector<int> getScorers(unsigned int team)
|
// ------------------------------------------------------------------------
|
||||||
|
/** 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 (team == SOCCER_TEAM_BLUE ?
|
||||||
return m_redScorers;
|
m_blue_score_times : m_red_score_times);
|
||||||
else
|
|
||||||
return m_blueScorers;
|
|
||||||
}
|
}
|
||||||
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 (team == SOCCER_TEAM_BLUE ? m_blue_goal_node : m_red_goal_node);
|
||||||
return m_redScoreTimes;
|
}
|
||||||
else
|
// ------------------------------------------------------------------------
|
||||||
return m_blueScoreTimes;
|
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
|
}; // SoccerWorld
|
||||||
|
|
||||||
|
|
||||||
|
@ -453,69 +453,9 @@ void ThreeStrikesBattle::updateKartNodes()
|
|||||||
{
|
{
|
||||||
if (m_karts[i]->isEliminated()) continue;
|
if (m_karts[i]->isEliminated()) continue;
|
||||||
|
|
||||||
const int saved_current_node = m_kart_info[i].m_on_node;
|
m_kart_info[i].m_on_node = BattleGraph::get()
|
||||||
|
->pointToNode(m_kart_info[i].m_on_node,
|
||||||
if (saved_current_node == BattleGraph::UNKNOWN_POLY)
|
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ private:
|
|||||||
|
|
||||||
PtrVector<TrackObject, REF> m_tires;
|
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();
|
void updateKartNodes();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include "input/keyboard_device.hpp"
|
#include "input/keyboard_device.hpp"
|
||||||
#include "items/projectile_manager.hpp"
|
#include "items/projectile_manager.hpp"
|
||||||
#include "karts/controller/battle_ai.hpp"
|
#include "karts/controller/battle_ai.hpp"
|
||||||
|
#include "karts/controller/soccer_ai.hpp"
|
||||||
#include "karts/controller/end_controller.hpp"
|
#include "karts/controller/end_controller.hpp"
|
||||||
#include "karts/controller/local_player_controller.hpp"
|
#include "karts/controller/local_player_controller.hpp"
|
||||||
#include "karts/controller/skidding_ai.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)
|
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
|
||||||
turn=1;
|
turn=1;
|
||||||
|
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
|
||||||
|
turn=2;
|
||||||
// If different AIs should be used, adjust turn (or switch randomly
|
// If different AIs should be used, adjust turn (or switch randomly
|
||||||
// or dependent on difficulty)
|
// or dependent on difficulty)
|
||||||
switch(turn)
|
switch(turn)
|
||||||
@ -361,6 +364,9 @@ Controller* World::loadAIController(AbstractKart *kart)
|
|||||||
case 1:
|
case 1:
|
||||||
controller = new BattleAI(kart);
|
controller = new BattleAI(kart);
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
controller = new SoccerAI(kart);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Log::warn("[World]", "Unknown AI, using default.");
|
Log::warn("[World]", "Unknown AI, using default.");
|
||||||
controller = new SkiddingAI(kart);
|
controller = new SkiddingAI(kart);
|
||||||
|
@ -31,7 +31,6 @@ enum SoccerTeam
|
|||||||
SOCCER_TEAM_NONE=-1,
|
SOCCER_TEAM_NONE=-1,
|
||||||
SOCCER_TEAM_RED=0,
|
SOCCER_TEAM_RED=0,
|
||||||
SOCCER_TEAM_BLUE=1,
|
SOCCER_TEAM_BLUE=1,
|
||||||
NB_SOCCER_TEAMS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Game difficulty per player. */
|
/** Game difficulty per player. */
|
||||||
|
@ -226,7 +226,7 @@ void Physics::update(float dt)
|
|||||||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
||||||
{
|
{
|
||||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
||||||
soccerWorld->setLastKartTohitBall(kartId);
|
soccerWorld->setBallHitter(kartId);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -288,7 +288,7 @@ void Physics::update(float dt)
|
|||||||
{
|
{
|
||||||
int kartId = p->getUserPointer(0)->getPointerFlyable()->getOwnerId();
|
int kartId = p->getUserPointer(0)->getPointerFlyable()->getOwnerId();
|
||||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
||||||
soccerWorld->setLastKartTohitBall(kartId);
|
soccerWorld->setBallHitter(kartId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,7 @@ public:
|
|||||||
case MINOR_MODE_FOLLOW_LEADER: return true;
|
case MINOR_MODE_FOLLOW_LEADER: return true;
|
||||||
case MINOR_MODE_3_STRIKES: return true;
|
case MINOR_MODE_3_STRIKES: return true;
|
||||||
case MINOR_MODE_EASTER_EGG: return false;
|
case MINOR_MODE_EASTER_EGG: return false;
|
||||||
case MINOR_MODE_SOCCER: return false;
|
case MINOR_MODE_SOCCER: return true;
|
||||||
default: assert(false); return false;
|
default: assert(false); return false;
|
||||||
}
|
}
|
||||||
} // hasAI
|
} // hasAI
|
||||||
@ -611,10 +611,21 @@ public:
|
|||||||
const int id = (int)m_minor_mode;
|
const int id = (int)m_minor_mode;
|
||||||
// This uses the numerical id of the mode, see the macros
|
// This uses the numerical id of the mode, see the macros
|
||||||
// LINEAR_RACE and BATTLE_ARENA above for exact meaning.
|
// LINEAR_RACE and BATTLE_ARENA above for exact meaning.
|
||||||
if (id >= 2000) return true;
|
if (id >= 2000 && id != 2001) return true;
|
||||||
else return false;
|
else return false;
|
||||||
} // isBattleMode
|
} // 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()
|
bool isTutorialMode()
|
||||||
{
|
{
|
||||||
@ -624,7 +635,7 @@ public:
|
|||||||
/** \brief Returns true if the current mode has laps. */
|
/** \brief Returns true if the current mode has laps. */
|
||||||
bool modeHasLaps()
|
bool modeHasLaps()
|
||||||
{
|
{
|
||||||
if (isBattleMode()) return false;
|
if (isBattleMode() || isSoccerMode()) return false;
|
||||||
const int id = (int)m_minor_mode;
|
const int id = (int)m_minor_mode;
|
||||||
// See meaning of IDs above
|
// See meaning of IDs above
|
||||||
const int answer = (id-1000)/100;
|
const int answer = (id-1000)/100;
|
||||||
|
@ -95,7 +95,9 @@ void ArenasScreen::beforeAddingWidget()
|
|||||||
Track* temp = track_manager->getTrack(n);
|
Track* temp = track_manager->getTrack(n);
|
||||||
if (soccer_mode)
|
if (soccer_mode)
|
||||||
{
|
{
|
||||||
if(temp->isSoccer())
|
if(temp->isSoccer() && (temp->hasNavMesh() ||
|
||||||
|
race_manager->getNumLocalPlayers() > 1 ||
|
||||||
|
UserConfigParams::m_artist_debug_mode))
|
||||||
num_of_arenas++;
|
num_of_arenas++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -239,7 +241,18 @@ void ArenasScreen::buildTrackList()
|
|||||||
Track* curr = track_manager->getTrack(n);
|
Track* curr = track_manager->getTrack(n);
|
||||||
if (soccer_mode)
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -280,7 +293,18 @@ void ArenasScreen::buildTrackList()
|
|||||||
Track* curr = track_manager->getTrack(currArenas[n]);
|
Track* curr = track_manager->getTrack(currArenas[n]);
|
||||||
if (soccer_mode)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -48,6 +48,7 @@ using namespace irr;
|
|||||||
#include "modes/soccer_world.hpp"
|
#include "modes/soccer_world.hpp"
|
||||||
#include "race/race_manager.hpp"
|
#include "race/race_manager.hpp"
|
||||||
#include "tracks/track.hpp"
|
#include "tracks/track.hpp"
|
||||||
|
#include "tracks/track_object_manager.hpp"
|
||||||
#include "utils/constants.hpp"
|
#include "utils/constants.hpp"
|
||||||
#include "utils/string_utils.hpp"
|
#include "utils/string_utils.hpp"
|
||||||
#include "utils/translation.hpp"
|
#include "utils/translation.hpp"
|
||||||
@ -224,55 +225,46 @@ void RaceGUI::renderPlayerView(const Camera *camera, float dt)
|
|||||||
*/
|
*/
|
||||||
void RaceGUI::drawScores()
|
void RaceGUI::drawScores()
|
||||||
{
|
{
|
||||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||||
int offsetY = 5;
|
int offset_y = 5;
|
||||||
int offsetX = 5;
|
int offset_x = 5;
|
||||||
gui::ScalableFont* font = GUIEngine::getFont();
|
gui::ScalableFont* font = GUIEngine::getTitleFont();
|
||||||
static video::SColor color = video::SColor(255,255,255,255);
|
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,
|
irr::video::ITexture *red_team = irr_driver->getTexture(FileManager::GUI,
|
||||||
"soccer_ball_red.png");
|
"soccer_ball_red.png");
|
||||||
irr::video::ITexture *blue_team = irr_driver->getTexture(FileManager::GUI,
|
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;
|
irr::video::ITexture *team_icon = red_team;
|
||||||
|
|
||||||
int numLeader = 1;
|
for(unsigned int i=0; i<2; i++)
|
||||||
for(unsigned int i=0; i<soccerWorld->getNumKarts(); i++)
|
|
||||||
{
|
{
|
||||||
int j = soccerWorld->getTeamLeader(i);
|
core::recti position(offset_x, offset_y,
|
||||||
if(j < 0) break;
|
offset_x + 2*m_minimap_player_size, offset_y + 2*m_minimap_player_size);
|
||||||
|
|
||||||
AbstractKart* kart = soccerWorld->getKart(i);
|
core::stringw score = StringUtils::toWString(sw->getScore((SoccerTeam)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));
|
|
||||||
int string_height =
|
int string_height =
|
||||||
GUIEngine::getFont()->getDimension(score.c_str()).Height;
|
GUIEngine::getFont()->getDimension(score.c_str()).Height;
|
||||||
core::recti pos(position.UpperLeftCorner.X + 5,
|
core::recti pos(position.UpperLeftCorner.X + 5,
|
||||||
position.LowerRightCorner.Y + offsetY,
|
position.LowerRightCorner.Y + offset_y,
|
||||||
position.LowerRightCorner.X,
|
position.LowerRightCorner.X,
|
||||||
position.LowerRightCorner.Y + string_height);
|
position.LowerRightCorner.Y + string_height);
|
||||||
|
|
||||||
font->draw(score.c_str(),pos,color);
|
font->draw(score.c_str(),pos,color);
|
||||||
|
|
||||||
if (numLeader == 2)
|
if (i == 1)
|
||||||
{
|
{
|
||||||
team_icon = blue_team;
|
team_icon = blue_team;
|
||||||
}
|
}
|
||||||
core::rect<s32> indicatorPos(offsetX, offsetY,
|
core::rect<s32> indicator_pos(offset_x, offset_y,
|
||||||
offsetX + (int)(m_minimap_player_size/1.25f),
|
offset_x + (int)(m_minimap_player_size*2),
|
||||||
offsetY + (int)(m_minimap_player_size/1.25f));
|
offset_y + (int)(m_minimap_player_size*2));
|
||||||
core::rect<s32> sourceRect(core::position2d<s32>(0,0),
|
core::rect<s32> source_rect(core::position2d<s32>(0,0),
|
||||||
team_icon->getSize());
|
team_icon->getSize());
|
||||||
draw2DImage(team_icon,indicatorPos,sourceRect,
|
draw2DImage(team_icon,indicator_pos,source_rect,
|
||||||
NULL,NULL,true);
|
NULL,NULL,true);
|
||||||
numLeader++;
|
offset_x += position.LowerRightCorner.X + 30;
|
||||||
offsetX += position.LowerRightCorner.X;
|
|
||||||
}
|
}
|
||||||
} // drawScores
|
} // drawScores
|
||||||
|
|
||||||
@ -295,7 +287,8 @@ void RaceGUI::drawGlobalTimer()
|
|||||||
bool use_digit_font = true;
|
bool use_digit_font = true;
|
||||||
|
|
||||||
float elapsed_time = World::getWorld()->getTime();
|
float elapsed_time = World::getWorld()->getTime();
|
||||||
if (!race_manager->hasTimeTarget())
|
if (!race_manager->hasTimeTarget() || race_manager
|
||||||
|
->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
|
||||||
{
|
{
|
||||||
sw = core::stringw (
|
sw = core::stringw (
|
||||||
StringUtils::timeToString(elapsed_time).c_str() );
|
StringUtils::timeToString(elapsed_time).c_str() );
|
||||||
@ -329,7 +322,8 @@ void RaceGUI::drawGlobalTimer()
|
|||||||
}
|
}
|
||||||
|
|
||||||
gui::ScalableFont* font = (use_digit_font ? GUIEngine::getHighresDigitFont() : GUIEngine::getFont());
|
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->setScale(1.0f);
|
||||||
font->draw(sw.c_str(), pos, time_color, false, false, NULL,
|
font->draw(sw.c_str(), pos, time_color, false, false, NULL,
|
||||||
true /* ignore RTL */);
|
true /* ignore RTL */);
|
||||||
@ -343,8 +337,8 @@ void RaceGUI::drawGlobalMiniMap()
|
|||||||
{
|
{
|
||||||
World *world = World::getWorld();
|
World *world = World::getWorld();
|
||||||
// draw a map when arena has a navigation mesh.
|
// draw a map when arena has a navigation mesh.
|
||||||
if ((world->getTrack()->isArena() && !(world->getTrack()->hasNavMesh())) ||
|
if ((world->getTrack()->isArena() || world->getTrack()->isSoccer()) &&
|
||||||
world->getTrack()->isSoccer())
|
!(world->getTrack()->hasNavMesh()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const video::ITexture *old_rtt_mini_map = world->getTrack()->getOldRttMiniMap();
|
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));
|
lower_y -(int)(draw_at.getY()-marker_half_size));
|
||||||
draw2DImage(icon, position, source, NULL, NULL, true);
|
draw2DImage(icon, position, source, NULL, NULL, true);
|
||||||
} // for i<getNumKarts
|
} // 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
|
} // drawGlobalMiniMap
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -297,7 +297,7 @@ void RaceGUIBase::cleanupMessages(const float dt)
|
|||||||
/** Draws the powerup icons on the screen (called once for each player).
|
/** Draws the powerup icons on the screen (called once for each player).
|
||||||
* \param kart The kart for which to draw the powerup icons.
|
* \param kart The kart for which to draw the powerup icons.
|
||||||
* \param viewport The viewport into which to draw the 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,
|
void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
||||||
const core::recti &viewport,
|
const core::recti &viewport,
|
||||||
@ -309,8 +309,13 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
|||||||
|| kart->hasFinishedRace()) return;
|
|| kart->hasFinishedRace()) return;
|
||||||
|
|
||||||
int n = kart->getPowerup()->getNum() ;
|
int n = kart->getPowerup()->getNum() ;
|
||||||
|
int many_powerups = 0;
|
||||||
if (n<1) return; // shouldn't happen, but just in case
|
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));
|
int nSize = (int)(64.0f*std::min(scaling.X, scaling.Y));
|
||||||
|
|
||||||
@ -320,6 +325,8 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
|||||||
- (n * itemSpacing)/2;
|
- (n * itemSpacing)/2;
|
||||||
int y1 = viewport.UpperLeftCorner.Y + (int)(20 * scaling.Y);
|
int y1 = viewport.UpperLeftCorner.Y + (int)(20 * scaling.Y);
|
||||||
|
|
||||||
|
int x2 = 0;
|
||||||
|
|
||||||
assert(powerup != NULL);
|
assert(powerup != NULL);
|
||||||
assert(powerup->getIcon() != NULL);
|
assert(powerup->getIcon() != NULL);
|
||||||
video::ITexture *t=powerup->getIcon()->getTexture();
|
video::ITexture *t=powerup->getIcon()->getTexture();
|
||||||
@ -328,11 +335,21 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
|||||||
|
|
||||||
for ( int i = 0 ; i < n ; i++ )
|
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);
|
core::rect<s32> pos(x2, y1, x2+nSize, y1+nSize);
|
||||||
draw2DImage(t, pos, rect, NULL,
|
draw2DImage(t, pos, rect, NULL,
|
||||||
NULL, true);
|
NULL, true);
|
||||||
} // for i
|
} // 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
|
} // drawPowerupIcons
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -439,11 +456,15 @@ void RaceGUIBase::drawGlobalMusicDescription()
|
|||||||
gui::IGUIFont* font = GUIEngine::getFont();
|
gui::IGUIFont* font = GUIEngine::getFont();
|
||||||
|
|
||||||
float race_time = World::getWorld()->getTime();
|
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:
|
// countdown time to time since start:
|
||||||
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER)
|
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER)
|
||||||
race_time = ((FollowTheLeaderRace*)World::getWorld())->getClockStartTime()
|
race_time = ((FollowTheLeaderRace*)World::getWorld())->getClockStartTime()
|
||||||
- race_time;
|
- 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
|
// ---- Manage pulsing effect
|
||||||
// 3.0 is the duration of ready/set (TODO: don't hardcode)
|
// 3.0 is the duration of ready/set (TODO: don't hardcode)
|
||||||
float timeProgression = (float)(race_time) /
|
float timeProgression = (float)(race_time) /
|
||||||
|
@ -475,7 +475,15 @@ void RaceResultGUI::determineTableLayout()
|
|||||||
// Save a pointer to the current row_info entry
|
// Save a pointer to the current row_info entry
|
||||||
RowInfo *ri = &(m_all_row_infos[position-first_position]);
|
RowInfo *ri = &(m_all_row_infos[position-first_position]);
|
||||||
ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
|
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 =
|
video::ITexture *icon =
|
||||||
kart->getKartProperties()->getIconMaterial()->getTexture();
|
kart->getKartProperties()->getIconMaterial()->getTexture();
|
||||||
@ -840,7 +848,15 @@ void RaceResultGUI::determineGPLayout()
|
|||||||
RowInfo *ri = &(m_all_row_infos[rank]);
|
RowInfo *ri = &(m_all_row_infos[rank]);
|
||||||
ri->m_kart_icon =
|
ri->m_kart_icon =
|
||||||
kart->getKartProperties()->getIconMaterial()->getTexture();
|
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();
|
ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
|
||||||
|
|
||||||
// In FTL karts do have a time, which is shown even when the kart
|
// In FTL karts do have a time, which is shown even when the kart
|
||||||
@ -977,136 +993,171 @@ void RaceResultGUI::displaySoccerResults()
|
|||||||
{
|
{
|
||||||
|
|
||||||
//Draw win text
|
//Draw win text
|
||||||
core::stringw resultText;
|
core::stringw result_text;
|
||||||
static video::SColor color = video::SColor(255, 255, 255, 255);
|
static video::SColor color = video::SColor(255, 255, 255, 255);
|
||||||
gui::IGUIFont* font = GUIEngine::getTitleFont();
|
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]);
|
RowInfo *ri = &(m_all_row_infos[0]);
|
||||||
int currY = (int)ri->m_y_pos;
|
int current_y = (int)ri->m_y_pos;
|
||||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
SoccerWorld* sw = (SoccerWorld*)World::getWorld();
|
||||||
int teamScore[2] = {soccerWorld->getScore(0), soccerWorld->getScore(1)};
|
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");
|
GUIEngine::Widget *table_area = getWidget("result-table");
|
||||||
int height = table_area->m_h + table_area->m_y;
|
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
|
else
|
||||||
{
|
{
|
||||||
//Cannot really happen now. Only in time limited matches.
|
//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);
|
core::rect<s32> pos(current_x, current_y, current_x, current_y);
|
||||||
font->draw(resultText.c_str(), pos, color, true, true);
|
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:
|
//Draw team scores:
|
||||||
currY += rect.Height;
|
current_y += rect.Height;
|
||||||
currX /= 2;
|
current_x /= 2;
|
||||||
irr::video::ITexture* redTeamIcon = irr_driver->getTexture(FileManager::GUI,
|
irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI,
|
||||||
"soccer_ball_red.png");
|
"soccer_ball_red.png");
|
||||||
irr::video::ITexture* blueTeamIcon = irr_driver->getTexture(FileManager::GUI,
|
irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI,
|
||||||
"soccer_ball_blue.png");
|
"soccer_ball_blue.png");
|
||||||
|
|
||||||
core::recti sourceRect(core::vector2di(0,0), redTeamIcon->getSize());
|
core::recti source_rect(core::vector2di(0,0), red_icon->getSize());
|
||||||
core::recti destRect(currX, currY, currX+redTeamIcon->getSize().Width/2,
|
core::recti dest_rect(current_x, current_y, current_x+red_icon->getSize().Width/2,
|
||||||
currY+redTeamIcon->getSize().Height/2);
|
current_y+red_icon->getSize().Height/2);
|
||||||
draw2DImage(redTeamIcon, destRect,sourceRect,
|
draw2DImage(red_icon, dest_rect,source_rect,
|
||||||
NULL,NULL, true);
|
NULL,NULL, true);
|
||||||
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
|
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
|
||||||
destRect = core::recti(currX, currY, currX+redTeamIcon->getSize().Width/2,
|
dest_rect = core::recti(current_x, current_y, current_x+red_icon->getSize().Width/2,
|
||||||
currY+redTeamIcon->getSize().Height/2);
|
current_y+red_icon->getSize().Height/2);
|
||||||
draw2DImage(blueTeamIcon,destRect,sourceRect,
|
draw2DImage(blue_icon,dest_rect,source_rect,
|
||||||
NULL, NULL, true);
|
NULL, NULL, true);
|
||||||
|
|
||||||
resultText = StringUtils::toWString(teamScore[1]);
|
result_text = StringUtils::toWString(blue_score);
|
||||||
rect = m_font->getDimension(resultText.c_str());
|
rect = m_font->getDimension(result_text.c_str());
|
||||||
currX += redTeamIcon->getSize().Width/4;
|
current_x += red_icon->getSize().Width/4;
|
||||||
currY += redTeamIcon->getSize().Height/2 + rect.Height/4;
|
current_y += red_icon->getSize().Height/2 + rect.Height/4;
|
||||||
pos = core::rect<s32>(currX, currY, currX, currY);
|
pos = core::rect<s32>(current_x, current_y, current_x, current_y);
|
||||||
color = video::SColor(255,255,255,255);
|
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;
|
current_x -= UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
|
||||||
resultText = StringUtils::toWString(teamScore[0]);
|
result_text = StringUtils::toWString(red_score);
|
||||||
pos = core::rect<s32>(currX,currY,currX,currY);
|
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
|
||||||
font->draw(resultText.c_str(), pos, color, true, false);
|
font->draw(result_text.c_str(), pos, color, true, false);
|
||||||
|
|
||||||
int centerX = UserConfigParams::m_width/2;
|
int center_x = UserConfigParams::m_width/2;
|
||||||
pos = core::rect<s32>(centerX, currY, centerX, currY);
|
pos = core::rect<s32>(center_x, current_y, center_x, current_y);
|
||||||
font->draw("-", pos, color, true, false);
|
font->draw("-", pos, color, true, false);
|
||||||
|
|
||||||
//Draw goal scorers:
|
//Draw goal scorers:
|
||||||
//The red scorers:
|
//The red scorers:
|
||||||
currY += rect.Height/2 + rect.Height/4;
|
current_y += rect.Height/2 + rect.Height/4;
|
||||||
font = GUIEngine::getSmallFont();
|
font = GUIEngine::getSmallFont();
|
||||||
std::vector<int> scorers = soccerWorld->getScorers(0);
|
std::vector<SoccerWorld::ScorerData> scorers = sw->getScorers(SOCCER_TEAM_RED);
|
||||||
std::vector<float> scoreTimes = soccerWorld->getScoreTimes(0);
|
std::vector<float> score_times = sw->getScoreTimes(SOCCER_TEAM_RED);
|
||||||
irr::video::ITexture* scorerIcon;
|
irr::video::ITexture* scorer_icon;
|
||||||
|
|
||||||
int prevY = currY;
|
int prev_y = current_y;
|
||||||
for(unsigned int i=0; i<scorers.size(); i++)
|
for(unsigned int i=0; i<scorers.size(); i++)
|
||||||
{
|
{
|
||||||
resultText = soccerWorld->getKart(scorers.at(i))->
|
const bool own_goal = !(scorers.at(i).m_correct_goal);
|
||||||
getKartProperties()->getName();
|
|
||||||
resultText.append(" ");
|
|
||||||
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
|
|
||||||
rect = m_font->getDimension(resultText.c_str());
|
|
||||||
|
|
||||||
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
|
const int kart_id = scorers.at(i).m_id;
|
||||||
currY += (height-prevY)/((short)scorers.size()+1);
|
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
|
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);
|
result_text.append(" ");
|
||||||
font->draw(resultText,pos, color, true, false);
|
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
|
||||||
scorerIcon = soccerWorld->getKart(scorers.at(i))
|
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();
|
->getKartProperties()->getIconMaterial()->getTexture();
|
||||||
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
|
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
|
||||||
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
|
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
|
||||||
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
|
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
|
||||||
draw2DImage(scorerIcon, destRect, sourceRect,
|
draw2DImage(scorer_icon, dest_rect, source_rect,
|
||||||
NULL, NULL, true);
|
NULL, NULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//The blue scorers:
|
//The blue scorers:
|
||||||
currY = prevY;
|
current_y = prev_y;
|
||||||
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
|
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
|
||||||
scorers = soccerWorld->getScorers(1);
|
scorers = sw->getScorers(SOCCER_TEAM_BLUE);
|
||||||
scoreTimes = soccerWorld->getScoreTimes(1);
|
score_times = sw->getScoreTimes(SOCCER_TEAM_BLUE);
|
||||||
for(unsigned int i=0; i<scorers.size(); i++)
|
for(unsigned int i=0; i<scorers.size(); i++)
|
||||||
{
|
{
|
||||||
resultText = soccerWorld->getKart(scorers.at(i))->
|
const bool own_goal = !(scorers.at(i).m_correct_goal);
|
||||||
getKartProperties()->getName();
|
|
||||||
resultText.append(" ");
|
|
||||||
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
|
|
||||||
rect = m_font->getDimension(resultText.c_str());
|
|
||||||
|
|
||||||
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
|
const int kart_id = scorers.at(i).m_id;
|
||||||
currY += (height-prevY)/((short)scorers.size()+1);
|
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
|
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);
|
result_text.append(" ");
|
||||||
font->draw(resultText,pos, color, true, false);
|
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
|
||||||
scorerIcon = soccerWorld->getKart(scorers.at(i))->
|
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();
|
getKartProperties()->getIconMaterial()->getTexture();
|
||||||
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
|
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
|
||||||
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
|
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
|
||||||
|
|
||||||
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
|
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
|
||||||
draw2DImage(scorerIcon, destRect, sourceRect,
|
draw2DImage(scorer_icon, dest_rect, source_rect,
|
||||||
NULL, NULL, true);
|
NULL, NULL, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,6 @@
|
|||||||
#include "states_screens/tracks_screen.hpp"
|
#include "states_screens/tracks_screen.hpp"
|
||||||
#include "utils/translation.hpp"
|
#include "utils/translation.hpp"
|
||||||
|
|
||||||
#define ENABLE_SOCCER_MODE
|
|
||||||
|
|
||||||
const int CONFIG_CODE_NORMAL = 0;
|
const int CONFIG_CODE_NORMAL = 0;
|
||||||
const int CONFIG_CODE_TIMETRIAL = 1;
|
const int CONFIG_CODE_TIMETRIAL = 1;
|
||||||
const int CONFIG_CODE_FTL = 2;
|
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).");
|
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));
|
w2->addItem( name4, IDENT_STRIKES, RaceManager::getIconOf(RaceManager::MINOR_MODE_3_STRIKES));
|
||||||
|
|
||||||
#ifdef ENABLE_SOCCER_MODE
|
irr::core::stringw name5 = irr::core::stringw(
|
||||||
if (race_manager->getNumLocalPlayers() > 1 || UserConfigParams::m_artist_debug_mode)
|
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
|
||||||
{
|
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
|
||||||
irr::core::stringw name5 = irr::core::stringw(
|
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
|
||||||
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
|
|
||||||
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
|
|
||||||
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ENABLE_EASTER_EGG_MODE
|
#define ENABLE_EASTER_EGG_MODE
|
||||||
#ifdef 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);
|
race_manager->setMinorMode(RaceManager::MINOR_MODE_SOCCER);
|
||||||
UserConfigParams::m_game_mode = CONFIG_CODE_SOCCER;
|
UserConfigParams::m_game_mode = CONFIG_CODE_SOCCER;
|
||||||
race_manager->setNumKarts( race_manager->getNumLocalPlayers() ); // no AI karts;
|
SoccerSetupScreen::getInstance()->push();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
else if (selectedMode == "locked")
|
else if (selectedMode == "locked")
|
||||||
{
|
{
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "audio/sfx_manager.hpp"
|
#include "audio/sfx_manager.hpp"
|
||||||
#include "config/user_config.hpp"
|
#include "config/user_config.hpp"
|
||||||
|
#include "guiengine/widgets/bubble_widget.hpp"
|
||||||
#include "guiengine/widgets/button_widget.hpp"
|
#include "guiengine/widgets/button_widget.hpp"
|
||||||
#include "guiengine/widgets/spinner_widget.hpp"
|
#include "guiengine/widgets/spinner_widget.hpp"
|
||||||
#include "guiengine/widgets/check_box_widget.hpp"
|
#include "guiengine/widgets/check_box_widget.hpp"
|
||||||
@ -34,7 +35,6 @@
|
|||||||
#include "states_screens/arenas_screen.hpp"
|
#include "states_screens/arenas_screen.hpp"
|
||||||
#include "states_screens/state_manager.hpp"
|
#include "states_screens/state_manager.hpp"
|
||||||
|
|
||||||
|
|
||||||
using namespace GUIEngine;
|
using namespace GUIEngine;
|
||||||
DEFINE_SCREEN_SINGLETON( SoccerSetupScreen );
|
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();
|
int nb_players = (int)m_kart_view_info.size();
|
||||||
|
|
||||||
if (getNumKartsInTeam(SOCCER_TEAM_RED) == 0 ||
|
if(!areAllKartsConfirmed())
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
for(int i=0 ; i < nb_players ; i++)
|
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);
|
CheckBoxWidget* timeEnabled = dynamic_cast<CheckBoxWidget*>(widget);
|
||||||
bool timed = timeEnabled->getState();
|
bool timed = timeEnabled->getState();
|
||||||
|
UserConfigParams::m_soccer_use_time_limit = timed;
|
||||||
getWidget<SpinnerWidget>("goalamount")->setActive(!timed);
|
getWidget<SpinnerWidget>("goalamount")->setActive(!timed);
|
||||||
getWidget<SpinnerWidget>("timeamount")->setActive(timed);
|
getWidget<SpinnerWidget>("timeamount")->setActive(timed);
|
||||||
}
|
}
|
||||||
@ -122,19 +110,14 @@ void SoccerSetupScreen::beforeAddingWidget()
|
|||||||
{
|
{
|
||||||
Widget* central_div = getWidget<Widget>("central_div");
|
Widget* central_div = getWidget<Widget>("central_div");
|
||||||
|
|
||||||
// Compute some dimensions
|
// Add red/blue team icon above the karts
|
||||||
const core::dimension2d<u32> vs_size = GUIEngine::getTitleFont()->getDimension( L"VS" );
|
IconButtonWidget* red = getWidget<IconButtonWidget>("red_team");
|
||||||
const int vs_width = (int)vs_size.Width;
|
IconButtonWidget* blue = getWidget<IconButtonWidget>("blue_team");
|
||||||
const int vs_height = (int)vs_size.Height;
|
red->m_x = central_div->m_x + central_div->m_w/4;
|
||||||
const int center_x = central_div->m_x + central_div->m_w/2;
|
red->m_y = central_div->m_y + red->m_h;
|
||||||
const int center_y = central_div->m_y + central_div->m_h/2;
|
|
||||||
|
|
||||||
// Add "VS" label at the center of the rounded box
|
blue->m_x = central_div->m_x + (central_div->m_w/4)*3;
|
||||||
LabelWidget* label_vs = getWidget<LabelWidget>("vs");
|
blue->m_y = central_div->m_y + blue->m_h;
|
||||||
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;
|
|
||||||
|
|
||||||
// Add the 3D views for the karts
|
// Add the 3D views for the karts
|
||||||
int nb_players = race_manager->getNumPlayers();
|
int nb_players = race_manager->getNumPlayers();
|
||||||
@ -176,7 +159,9 @@ void SoccerSetupScreen::beforeAddingWidget()
|
|||||||
KartViewInfo info;
|
KartViewInfo info;
|
||||||
info.view = kart_view;
|
info.view = kart_view;
|
||||||
info.confirmed = false;
|
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);
|
m_kart_view_info.push_back(info);
|
||||||
race_manager->setKartSoccerTeam(i, info.team);
|
race_manager->setKartSoccerTeam(i, info.team);
|
||||||
}
|
}
|
||||||
@ -191,23 +176,23 @@ void SoccerSetupScreen::init()
|
|||||||
m_schedule_continue = false;
|
m_schedule_continue = false;
|
||||||
|
|
||||||
Screen::init();
|
Screen::init();
|
||||||
|
|
||||||
if (UserConfigParams::m_num_goals <= 0)
|
if (UserConfigParams::m_num_goals <= 0)
|
||||||
UserConfigParams::m_num_goals = 3;
|
UserConfigParams::m_num_goals = 3;
|
||||||
|
|
||||||
if (UserConfigParams::m_soccer_time_limit <= 0)
|
if (UserConfigParams::m_soccer_time_limit <= 0)
|
||||||
UserConfigParams::m_soccer_time_limit = 3;
|
UserConfigParams::m_soccer_time_limit = 3;
|
||||||
|
|
||||||
SpinnerWidget* goalamount = getWidget<SpinnerWidget>("goalamount");
|
SpinnerWidget* goalamount = getWidget<SpinnerWidget>("goalamount");
|
||||||
goalamount->setValue(UserConfigParams::m_num_goals);
|
goalamount->setValue(UserConfigParams::m_num_goals);
|
||||||
goalamount->setActive(true);
|
goalamount->setActive(!UserConfigParams::m_soccer_use_time_limit);
|
||||||
|
|
||||||
SpinnerWidget* timeAmount = getWidget<SpinnerWidget>("timeamount");
|
SpinnerWidget* timeAmount = getWidget<SpinnerWidget>("timeamount");
|
||||||
timeAmount->setValue(UserConfigParams::m_soccer_time_limit);
|
timeAmount->setValue(UserConfigParams::m_soccer_time_limit);
|
||||||
timeAmount->setActive(false);
|
timeAmount->setActive(UserConfigParams::m_soccer_use_time_limit);
|
||||||
|
|
||||||
CheckBoxWidget* timeEnabled = getWidget<CheckBoxWidget>("time_enabled");
|
CheckBoxWidget* timeEnabled = getWidget<CheckBoxWidget>("time_enabled");
|
||||||
timeEnabled->setState(false);
|
timeEnabled->setState(UserConfigParams::m_soccer_use_time_limit);
|
||||||
|
|
||||||
// Set focus on "continue"
|
// Set focus on "continue"
|
||||||
ButtonWidget* bt_continue = getWidget<ButtonWidget>("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
|
// Reset the 'map fire to select' option of the device manager
|
||||||
input_manager->getDeviceManager()->mapFireToSelect(false);
|
input_manager->getDeviceManager()->mapFireToSelect(false);
|
||||||
|
|
||||||
UserConfigParams::m_num_goals = getWidget<SpinnerWidget>("goalamount")->getValue();
|
UserConfigParams::m_num_goals = getWidget<SpinnerWidget>("goalamount")->getValue();
|
||||||
UserConfigParams::m_soccer_time_limit = getWidget<SpinnerWidget>("timeamount")->getValue();
|
UserConfigParams::m_soccer_time_limit = getWidget<SpinnerWidget>("timeamount")->getValue();
|
||||||
|
|
||||||
@ -255,7 +240,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
|||||||
if(m_schedule_continue)
|
if(m_schedule_continue)
|
||||||
return EVENT_BLOCK;
|
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;
|
GUIEngine::EventPropagation result = EVENT_LET;
|
||||||
SoccerTeam team_switch = SOCCER_TEAM_NONE;
|
SoccerTeam team_switch = SOCCER_TEAM_NONE;
|
||||||
int nb_players = (int)m_kart_view_info.size();
|
int nb_players = (int)m_kart_view_info.size();
|
||||||
@ -263,7 +249,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
|||||||
switch(action)
|
switch(action)
|
||||||
{
|
{
|
||||||
case PA_MENU_LEFT:
|
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)
|
m_kart_view_info[playerId].confirmed == false)
|
||||||
{
|
{
|
||||||
team_switch = SOCCER_TEAM_RED;
|
team_switch = SOCCER_TEAM_RED;
|
||||||
@ -275,7 +262,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PA_MENU_RIGHT:
|
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)
|
m_kart_view_info[playerId].confirmed == false)
|
||||||
{
|
{
|
||||||
team_switch = SOCCER_TEAM_BLUE;
|
team_switch = SOCCER_TEAM_BLUE;
|
||||||
@ -308,22 +296,12 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
|||||||
return EVENT_BLOCK;
|
return EVENT_BLOCK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getNumConfirmedKarts() > nb_players-2 &&
|
// Confirm team selection
|
||||||
(getNumKartsInTeam(SOCCER_TEAM_RED) == 0 ||
|
m_kart_view_info[playerId].confirmed = true;
|
||||||
getNumKartsInTeam(SOCCER_TEAM_BLUE) == 0))
|
m_kart_view_info[playerId].view->setRotateTo( KART_CONFIRMATION_TARGET_ANGLE, KART_CONFIRMATION_ROTATION_SPEED );
|
||||||
{
|
m_kart_view_info[playerId].view->setBadge(OK_BADGE);
|
||||||
SFXManager::get()->quickSound( "anvil" );
|
m_kart_view_info[playerId].view->unsetBadge(BAD_BADGE);
|
||||||
m_kart_view_info[playerId].view->setBadge(BAD_BADGE);
|
SFXManager::get()->quickSound( "wee" );
|
||||||
}
|
|
||||||
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" );
|
|
||||||
}
|
|
||||||
return EVENT_BLOCK;
|
return EVENT_BLOCK;
|
||||||
}
|
}
|
||||||
case PA_MENU_CANCEL:
|
case PA_MENU_CANCEL:
|
||||||
@ -349,15 +327,15 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(team_switch != SOCCER_TEAM_NONE) // A player wants to change his team?
|
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);
|
race_manager->setKartSoccerTeam(playerId, team_switch);
|
||||||
m_kart_view_info[playerId].team = team_switch;
|
m_kart_view_info[playerId].team = team_switch;
|
||||||
updateKartViewsLayout();
|
updateKartViewsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} // filterActions
|
} // filterActions
|
||||||
@ -366,7 +344,7 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
|||||||
void SoccerSetupScreen::onUpdate(float delta)
|
void SoccerSetupScreen::onUpdate(float delta)
|
||||||
{
|
{
|
||||||
int nb_players = (int)m_kart_view_info.size();
|
int nb_players = (int)m_kart_view_info.size();
|
||||||
|
|
||||||
if(m_schedule_continue)
|
if(m_schedule_continue)
|
||||||
{
|
{
|
||||||
for(int i=0 ; i < nb_players ; i++)
|
for(int i=0 ; i < nb_players ; i++)
|
||||||
@ -377,7 +355,7 @@ void SoccerSetupScreen::onUpdate(float delta)
|
|||||||
m_schedule_continue = false;
|
m_schedule_continue = false;
|
||||||
ArenasScreen::getInstance()->push();
|
ArenasScreen::getInstance()->push();
|
||||||
}
|
}
|
||||||
} // onUPdate
|
} // onUpdate
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
bool SoccerSetupScreen::areAllKartsConfirmed() const
|
bool SoccerSetupScreen::areAllKartsConfirmed() const
|
||||||
@ -395,19 +373,6 @@ bool SoccerSetupScreen::areAllKartsConfirmed() const
|
|||||||
return all_confirmed;
|
return all_confirmed;
|
||||||
} // areAllKartsConfirmed
|
} // 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()
|
int SoccerSetupScreen::getNumConfirmedKarts()
|
||||||
{
|
{
|
||||||
@ -427,10 +392,8 @@ void SoccerSetupScreen::updateKartViewsLayout()
|
|||||||
Widget* central_div = getWidget<Widget>("central_div");
|
Widget* central_div = getWidget<Widget>("central_div");
|
||||||
|
|
||||||
// Compute/get some dimensions
|
// 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 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 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_x = central_div->m_x + central_div->m_w/2;
|
||||||
const int center_y = central_div->m_y + central_div->m_h/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,
|
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_y - nb_rows_per_team[1] * kart_view_size / 2};
|
||||||
// - center of each half-screen
|
// - center of each half-screen
|
||||||
const int center_x_per_team[2] = { ( central_div->m_x + (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 + vs_width) ) / 2,
|
( central_div->m_x+central_div->m_w + center_x ) / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the layout of the 3D views for the karts
|
// 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);
|
view_info.view->move(pos_x, pos_y, kart_view_size, kart_view_size);
|
||||||
}
|
}
|
||||||
} // updateKartViewsLayout
|
} // updateKartViewsLayout
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ class SoccerSetupScreen : public GUIEngine::Screen, public GUIEngine::ScreenSing
|
|||||||
};
|
};
|
||||||
|
|
||||||
AlignedArray<KartViewInfo> m_kart_view_info;
|
AlignedArray<KartViewInfo> m_kart_view_info;
|
||||||
|
|
||||||
bool m_schedule_continue;
|
bool m_schedule_continue;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -75,7 +75,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool areAllKartsConfirmed() const;
|
bool areAllKartsConfirmed() const;
|
||||||
int getNumKartsInTeam(int team);
|
|
||||||
int getNumConfirmedKarts();
|
int getNumConfirmedKarts();
|
||||||
void updateKartViewsLayout();
|
void updateKartViewsLayout();
|
||||||
};
|
};
|
||||||
|
@ -154,7 +154,8 @@ void TrackInfoScreen::init()
|
|||||||
// -------------
|
// -------------
|
||||||
const int local_players = race_manager->getNumLocalPlayers();
|
const int local_players = race_manager->getNumLocalPlayers();
|
||||||
const bool has_AI =
|
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 :
|
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
|
||||||
race_manager->hasAI());
|
race_manager->hasAI());
|
||||||
m_ai_kart_spinner->setVisible(has_AI);
|
m_ai_kart_spinner->setVisible(has_AI);
|
||||||
@ -170,7 +171,8 @@ void TrackInfoScreen::init()
|
|||||||
m_ai_kart_spinner->setValue(num_ai);
|
m_ai_kart_spinner->setValue(num_ai);
|
||||||
race_manager->setNumKarts(num_ai + local_players);
|
race_manager->setNumKarts(num_ai + local_players);
|
||||||
// Set the max karts supported based on the battle arena selected
|
// 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);
|
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);
|
m_ai_kart_spinner->setMin(3 - local_players);
|
||||||
}
|
}
|
||||||
// Make sure in battle mode at least 1 ai for single player
|
// Make sure in battle and soccer mode at least 1 ai for single player
|
||||||
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES &&
|
else if((race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
||||||
|
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER) &&
|
||||||
local_players == 1 &&
|
local_players == 1 &&
|
||||||
!UserConfigParams::m_artist_debug_mode)
|
!UserConfigParams::m_artist_debug_mode)
|
||||||
m_ai_kart_spinner->setMin(1);
|
m_ai_kart_spinner->setMin(1);
|
||||||
@ -310,7 +313,8 @@ void TrackInfoScreen::onEnterPressedInternal()
|
|||||||
const int max_arena_players = m_track->getMaxArenaPlayers();
|
const int max_arena_players = m_track->getMaxArenaPlayers();
|
||||||
const int local_players = race_manager->getNumLocalPlayers();
|
const int local_players = race_manager->getNumLocalPlayers();
|
||||||
const bool has_AI =
|
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 :
|
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
|
||||||
race_manager->hasAI());
|
race_manager->hasAI());
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ void BattleGraph::findItemsOnGraphNodes()
|
|||||||
|
|
||||||
for (unsigned int j = 0; j < this->getNumNodes(); ++j)
|
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;
|
polygon = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,70 @@ void BattleGraph::findItemsOnGraphNodes()
|
|||||||
else
|
else
|
||||||
Log::debug("BattleGraph","Can't map item number %d with a suitable polygon", i);
|
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)
|
if (i == BattleGraph::UNKNOWN_POLY || j == BattleGraph::UNKNOWN_POLY)
|
||||||
return BattleGraph::UNKNOWN_POLY;
|
return BattleGraph::UNKNOWN_POLY;
|
||||||
return m_parent_poly[j][i];
|
return m_parent_poly[j][i];
|
||||||
}
|
} // getNextShortestPathPoly
|
||||||
|
@ -126,6 +126,10 @@ public:
|
|||||||
{ return m_items_on_graph; }
|
{ return m_items_on_graph; }
|
||||||
|
|
||||||
void findItemsOnGraphNodes();
|
void findItemsOnGraphNodes();
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
int pointToNode(const int cur_node,
|
||||||
|
const Vec3& cur_point,
|
||||||
|
bool ignore_vertical) const;
|
||||||
}; //BattleGraph
|
}; //BattleGraph
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -51,42 +51,28 @@ CheckGoal::CheckGoal(const XMLNode &node, unsigned int index)
|
|||||||
*/
|
*/
|
||||||
void CheckGoal::update(float dt)
|
void CheckGoal::update(float dt)
|
||||||
{
|
{
|
||||||
World *world = World::getWorld();
|
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||||
assert(world);
|
|
||||||
|
|
||||||
Track* track = world->getTrack();
|
if (world)
|
||||||
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++)
|
|
||||||
{
|
{
|
||||||
TrackObject* obj = objects.get(i);
|
const Vec3 &xyz = world->getBallPosition();
|
||||||
if(!obj->isSoccerBall())
|
if (isTriggered(m_previous_ball_position, xyz, -1))
|
||||||
continue;
|
|
||||||
|
|
||||||
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
|
|
||||||
if(isTriggered(m_previous_position[ball_index], xyz, -1))
|
|
||||||
{
|
{
|
||||||
if(UserConfigParams::m_check_debug)
|
if (UserConfigParams::m_check_debug)
|
||||||
Log::info("CheckGoal", "Goal check structure %d triggered for object %s.",
|
{
|
||||||
m_index, obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getDebugName());
|
Log::info("CheckGoal", "Goal check structure"
|
||||||
trigger(ball_index);
|
"%d triggered for ball.", m_index);
|
||||||
|
}
|
||||||
|
trigger(0);
|
||||||
}
|
}
|
||||||
m_previous_position[ball_index] = xyz;
|
m_previous_ball_position = xyz;
|
||||||
ball_index++;
|
|
||||||
}
|
}
|
||||||
} // update
|
} // update
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
/** Called when the check line is triggered. This function creates a cannon
|
/** Called when the goal line is triggered. Input any integer for i to use
|
||||||
* animation object and attaches it to the kart.
|
|
||||||
* \param kart_index The index of the kart that triggered the check line.
|
|
||||||
*/
|
*/
|
||||||
void CheckGoal::trigger(unsigned int kart_index)
|
void CheckGoal::trigger(unsigned int i)
|
||||||
{
|
{
|
||||||
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||||
if(!world)
|
if(!world)
|
||||||
@ -116,21 +102,22 @@ bool CheckGoal::isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
|
|||||||
void CheckGoal::reset(const Track &track)
|
void CheckGoal::reset(const Track &track)
|
||||||
{
|
{
|
||||||
CheckStructure::reset(track);
|
CheckStructure::reset(track);
|
||||||
|
m_previous_ball_position = Vec3(0, 0, 0);
|
||||||
|
|
||||||
const TrackObjectManager* tom = track.getTrackObjectManager();
|
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||||
assert(tom);
|
|
||||||
|
|
||||||
m_previous_position.clear();
|
if (world)
|
||||||
|
|
||||||
const PtrVector<TrackObject>& objects = tom->getObjects();
|
|
||||||
for(unsigned int i=0; i<objects.size(); i++)
|
|
||||||
{
|
{
|
||||||
const TrackObject* obj = objects.get(i);
|
const Vec3 &xyz = world->getBallPosition();
|
||||||
if(!obj->isSoccerBall())
|
m_previous_ball_position = xyz;
|
||||||
continue;
|
|
||||||
|
|
||||||
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
|
|
||||||
|
|
||||||
m_previous_position.push_back(xyz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // reset
|
} // reset
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
Vec3 CheckGoal::convertTo3DCenter() const
|
||||||
|
{
|
||||||
|
float x = m_line.getMiddle().X;
|
||||||
|
float y = m_line.getMiddle().Y;
|
||||||
|
return Vec3(x, 0, y);
|
||||||
|
}
|
||||||
|
@ -27,6 +27,7 @@ using namespace irr;
|
|||||||
class CheckManager;
|
class CheckManager;
|
||||||
class XMLNode;
|
class XMLNode;
|
||||||
class Track;
|
class Track;
|
||||||
|
class Vec3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Implements a simple checkline that will score a point when the
|
* \brief Implements a simple checkline that will score a point when the
|
||||||
@ -37,6 +38,9 @@ class Track;
|
|||||||
class CheckGoal : public CheckStructure
|
class CheckGoal : public CheckStructure
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
/** Previois ball position. */
|
||||||
|
Vec3 m_previous_ball_position;
|
||||||
|
|
||||||
/** Which team is this goal for? */
|
/** Which team is this goal for? */
|
||||||
bool m_first_goal;
|
bool m_first_goal;
|
||||||
|
|
||||||
@ -51,6 +55,11 @@ public:
|
|||||||
virtual bool isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
|
virtual bool isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
|
||||||
unsigned int indx) OVERRIDE;
|
unsigned int indx) OVERRIDE;
|
||||||
virtual void reset(const Track &track) OVERRIDE;
|
virtual void reset(const Track &track) OVERRIDE;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool getTeam() const { return m_first_goal; }
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Vec3 convertTo3DCenter() const;
|
||||||
}; // CheckLine
|
}; // CheckLine
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -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
|
// Cannons don't have a kind specified, so test for the name in this case
|
||||||
else if(node.getName()=="cannon")
|
else if(node.getName()=="cannon")
|
||||||
m_check_type = CT_CANNON;
|
m_check_type = CT_CANNON;
|
||||||
|
else if(node.getName()=="goal")
|
||||||
|
m_check_type = CT_GOAL;
|
||||||
else
|
else
|
||||||
Log::warn("CheckStructure", "Unknown check structure '%s' - ignored.", kind.c_str());
|
Log::warn("CheckStructure", "Unknown check structure '%s' - ignored.", kind.c_str());
|
||||||
|
|
||||||
|
@ -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;
|
std::vector<Vec3> points;
|
||||||
for(unsigned int i=0; i<m_vertices.size(); i++)
|
for(unsigned int i=0; i<m_vertices.size(); i++)
|
||||||
@ -70,6 +70,8 @@ bool NavPoly::pointInPoly(const Vec3& p) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ignore_vertical) return true;
|
||||||
|
|
||||||
// Check for vertical distance too
|
// Check for vertical distance too
|
||||||
const float dist = p.getY() - m_center.getY();
|
const float dist = p.getY() - m_center.getY();
|
||||||
if (fabsf(dist) > 1.0f )
|
if (fabsf(dist) > 1.0f )
|
||||||
|
@ -61,7 +61,8 @@ public:
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
/** Returns true if a given point lies in this polygon. */
|
/** 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 ;
|
const Vec3& operator[](int i) const ;
|
||||||
|
|
||||||
|
@ -589,11 +589,16 @@ void Track::loadTrackInfo()
|
|||||||
|
|
||||||
if(file_manager->fileExists(m_root+"navmesh.xml"))
|
if(file_manager->fileExists(m_root+"navmesh.xml"))
|
||||||
m_has_navmesh = true;
|
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, "
|
Log::warn("Track", "NavMesh is not found for arena %s, "
|
||||||
"disable AI for it.\n", m_name.c_str());
|
"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
|
} // 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
|
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);
|
BattleGraph::get()->mapPoint2MiniMap(xyz, draw_at);
|
||||||
else
|
else
|
||||||
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
|
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
|
||||||
@ -1032,7 +1037,7 @@ void Track::loadMinimap()
|
|||||||
core::dimension2du size = m_mini_map_size
|
core::dimension2du size = m_mini_map_size
|
||||||
.getOptimalSize(!nonpower,!nonsquare);
|
.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),
|
BattleGraph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255),
|
||||||
&m_old_rtt_mini_map, &m_new_rtt_mini_map);
|
&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
|
// the information about the size of the texture to render the mini
|
||||||
// map to.
|
// map to.
|
||||||
if (!m_is_arena && !m_is_soccer && !m_is_cutscene) loadQuadGraph(mode_id, reverse_track);
|
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();
|
loadBattleGraph();
|
||||||
|
|
||||||
ItemManager::create();
|
ItemManager::create();
|
||||||
@ -1871,7 +1876,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
|
|||||||
|
|
||||||
delete root;
|
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();
|
BattleGraph::get()->findItemsOnGraphNodes();
|
||||||
|
|
||||||
if (UserConfigParams::m_track_debug &&
|
if (UserConfigParams::m_track_debug &&
|
||||||
|
@ -492,6 +492,10 @@ void TrackObjectPresentationMesh::init(const XMLNode* xml_node,
|
|||||||
m_frame_end = node->getEndFrame();
|
m_frame_end = node->getEndFrame();
|
||||||
if (xml_node)
|
if (xml_node)
|
||||||
xml_node->get("frame-end", &m_frame_end);
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -107,6 +107,10 @@ enum DebugMenuCommand
|
|||||||
DEBUG_VIEW_KART_TWO,
|
DEBUG_VIEW_KART_TWO,
|
||||||
DEBUG_VIEW_KART_THREE,
|
DEBUG_VIEW_KART_THREE,
|
||||||
DEBUG_VIEW_KART_FOUR,
|
DEBUG_VIEW_KART_FOUR,
|
||||||
|
DEBUG_VIEW_KART_FIVE,
|
||||||
|
DEBUG_VIEW_KART_SIX,
|
||||||
|
DEBUG_VIEW_KART_SEVEN,
|
||||||
|
DEBUG_VIEW_KART_EIGHT,
|
||||||
DEBUG_HIDE_KARTS,
|
DEBUG_HIDE_KARTS,
|
||||||
DEBUG_THROTTLE_FPS,
|
DEBUG_THROTTLE_FPS,
|
||||||
DEBUG_VISUAL_VALUES,
|
DEBUG_VISUAL_VALUES,
|
||||||
@ -494,6 +498,22 @@ bool handleContextMenuAction(s32 cmdID)
|
|||||||
{
|
{
|
||||||
changeCameraTarget(4);
|
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)
|
else if (cmdID == DEBUG_PRINT_START_POS)
|
||||||
{
|
{
|
||||||
if (!world) return false;
|
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 two", DEBUG_VIEW_KART_TWO);
|
||||||
sub->addItem(L"To kart three", DEBUG_VIEW_KART_THREE);
|
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 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);
|
mnu->addItem(L"Adjust values", DEBUG_VISUAL_VALUES);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user