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:
|
||||
# http://docs.travis-ci.com/user/build-configuration/
|
||||
#
|
||||
sudo: false
|
||||
language: cpp
|
||||
compiler:
|
||||
- gcc
|
||||
# - clang
|
||||
#branches:
|
||||
# only:
|
||||
# - master
|
||||
before_install:
|
||||
# Update repos
|
||||
- sudo apt-get update -qq
|
||||
# Install dependencies
|
||||
- sudo apt-get install build-essential libogg-dev libvorbis-dev libopenal-dev libxxf86vm-dev libcurl4-openssl-dev libfribidi-dev libbluetooth-dev
|
||||
# Install mesa from an other repo (a newer version is required). Quantal is not supported anymore, saucy is only supported till July 2014,
|
||||
# so we try to use trusty (precise which is what traiv uses a too old mesa version which doesn't link)
|
||||
- sudo apt-add-repository "deb http://archive.ubuntu.com/ubuntu trusty main restricted"
|
||||
- sudo apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 3B4FE6ACC0B21F32
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev libglew-dev cmake
|
||||
- clang
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- BUILD_TYPE="Debug"
|
||||
- BUILD_TYPE="Release"
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- build-essential
|
||||
- libogg-dev
|
||||
- libvorbis-dev
|
||||
- libopenal-dev
|
||||
- libxxf86vm-dev
|
||||
- libcurl4-openssl-dev
|
||||
- libfribidi-dev
|
||||
- libbluetooth-dev
|
||||
- libgl1-mesa-dev
|
||||
- libglu1-mesa-dev
|
||||
- libglew-dev
|
||||
- cmake
|
||||
|
||||
before_script:
|
||||
- export THREADS=`nproc`
|
||||
# Unfortunately using all threads crashes g++: "g++: internal compiler error: Killed (program cc1plus)"
|
||||
- export THREADS=4
|
||||
- export THREADS=$((`nproc` + 1))
|
||||
- echo "THREADS = $THREADS"
|
||||
- free -mt
|
||||
|
||||
script:
|
||||
|
||||
# First a debug build:
|
||||
- mkdir build-debug
|
||||
- cd build-debug
|
||||
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DCHECK_ASSETS=off
|
||||
- make VERBOSE=1 -j $THREADS
|
||||
|
||||
# Then a release build:
|
||||
- cd ..
|
||||
- mkdir build-release
|
||||
- cd build-release
|
||||
- cmake .. -DCMAKE_BUILD_TYPE=Release -DCHECK_ASSETS=off
|
||||
- mkdir "build"
|
||||
- cd "build"
|
||||
- cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCHECK_ASSETS=off
|
||||
- make VERBOSE=1 -j $THREADS
|
||||
|
||||
notifications:
|
||||
|
@ -215,7 +215,7 @@ endif()
|
||||
# Set some compiler options
|
||||
if(UNIX OR MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unused-function")
|
||||
endif()
|
||||
|
||||
if(MINGW AND CMAKE_BUILD_TYPE MATCHES Release)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB |
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" />
|
||||
|
||||
<spacer height="25" width="25"/>
|
||||
<spacer height="10" width="25"/>
|
||||
|
||||
<div layout="horizontal-row" width="fit" height="35" align="left">
|
||||
<bright proportion="1" height="100%"
|
||||
I18N="In soccer setup screen" text="Number of goals to win" text_align="left" />
|
||||
<spacer width="50" height="25"/>
|
||||
<spacer width="10" height="25"/>
|
||||
<spinner id="goalamount" width="200" height="90%" min_value="1" max_value="10"/>
|
||||
</div>
|
||||
|
||||
<div layout="horizontal-row" width="fit" height="35" align="left">
|
||||
<bright proportion="1" height="100%"
|
||||
I18N="In soccer setup screen" text="Maximum time (min.)" text_align="left" />
|
||||
<spacer width="50" height ="25"/>
|
||||
<spacer width="10" height ="25"/>
|
||||
<spinner id="timeamount" width="200" height="90%" min_value="1" max_value="15"/>
|
||||
</div>
|
||||
|
||||
<div layout="horizontal-row" width="fit" height="35" align="left">
|
||||
<bright proportion="1" height="100%"
|
||||
I18N="In soccer setup screen" text="Game type (Goals limit / Time limit)" text_align="left" />
|
||||
<spacer width="50" height="25"/>
|
||||
<spacer width="10" height="25"/>
|
||||
<checkbox id="time_enabled"/>
|
||||
</div>
|
||||
|
||||
<spacer height="40" width="25"/>
|
||||
<spacer height="10" width="25"/>
|
||||
|
||||
<bright height="15" width="25" I18N="In soccer setup screen" text="Use left/right to choose your team and press fire" text_align="center" align="center" />
|
||||
<bubble height="fit" width="100%" id="lblLeftRight" I18N="In soccer setup screen" text="Use left/right to choose your team and press fire" word_wrap="true" text_align="center"/>
|
||||
|
||||
<div id="central_div" layout="horizontal-row" width="100%" proportion="1" align="center">
|
||||
<roundedbox y="5%" width="100%" layout="horizontal-row" height="100%">
|
||||
<roundedbox y="5%" width="100%" layout="horizontal-row" height="95%">
|
||||
<!-- Content is added programmatically -->
|
||||
</roundedbox>
|
||||
<header id="vs" text="VS"/> <!-- Layout is done programmatically -->
|
||||
<icon id="red_team" I18N="In soccer setup screen" text="Red Team" icon="gui/soccer_ball_red.png" width="font" height="font"/> <!-- Layout is done programmatically -->
|
||||
<icon id="blue_team" I18N="In soccer setup screen" text="Blue Team" icon="gui/soccer_ball_blue.png" width="font" height="font"/>
|
||||
</div>
|
||||
|
||||
<button id="continue" I18N="In soccer setup screen" text="Continue" align="center" width="60%"/>
|
||||
|
@ -101,6 +101,8 @@
|
||||
w-multi=" 0 30 30 100 60 0 0 0 0 0" />
|
||||
<battle w="10 30 60 0 0 10 30 0 0 0"
|
||||
w-multi=" 0 0 5 0 0 0 0 0 0 0" />
|
||||
<soccer w=" 0 30 60 0 0 10 30 0 0 0"
|
||||
w-multi=" 0 0 5 0 0 0 0 0 0 0" />
|
||||
<tuto w=" 0 0 0 0 0 0 0 0 0 0"
|
||||
w-multi=" 0 0 100 0 0 0 0 0 0 0" />
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Modify this file to change the last-modified date when you add/remove a file.
|
||||
# This will then trigger a new cmake run automatically.
|
||||
# This will then trigger a new cmake run automatically.
|
||||
file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp")
|
||||
file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp")
|
||||
file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*")
|
||||
|
@ -335,9 +335,15 @@ namespace UserConfigParams
|
||||
PARAM_PREFIX IntUserConfigParam m_num_goals
|
||||
PARAM_DEFAULT( IntUserConfigParam(3, "numgoals",
|
||||
&m_race_setup_group, "Default number of goals in soccer mode.") );
|
||||
PARAM_PREFIX IntUserConfigParam m_soccer_default_team
|
||||
PARAM_DEFAULT( IntUserConfigParam(0, "soccer-default-team",
|
||||
&m_race_setup_group, "Default team in soccer mode for single player.") );
|
||||
PARAM_PREFIX IntUserConfigParam m_soccer_time_limit
|
||||
PARAM_DEFAULT( IntUserConfigParam(3, "soccer-time-limit",
|
||||
&m_race_setup_group, "Limit in soccer time mode.") );
|
||||
&m_race_setup_group, "Time limit in soccer mode.") );
|
||||
PARAM_PREFIX BoolUserConfigParam m_soccer_use_time_limit
|
||||
PARAM_DEFAULT( BoolUserConfigParam(false, "soccer-use-time-limit",
|
||||
&m_race_setup_group, "Enable time limit in soccer mode.") );
|
||||
PARAM_PREFIX IntUserConfigParam m_difficulty
|
||||
PARAM_DEFAULT( IntUserConfigParam(0, "difficulty",
|
||||
&m_race_setup_group,
|
||||
|
@ -65,10 +65,18 @@ MaterialManager::~MaterialManager()
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Material* MaterialManager::getMaterialFor(video::ITexture* t,
|
||||
scene::IMeshBuffer *mb)
|
||||
scene::IMeshBuffer *mb)
|
||||
{
|
||||
return getMaterialFor(t, mb->getMaterial().MaterialType);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Material* MaterialManager::getMaterialFor(video::ITexture* t,
|
||||
video::E_MATERIAL_TYPE material_type)
|
||||
{
|
||||
if (t == NULL)
|
||||
return getDefaultMaterial(mb->getMaterial().MaterialType);
|
||||
return getDefaultMaterial(material_type);
|
||||
|
||||
core::stringc img_path = core::stringc(t->getName());
|
||||
const std::string image = StringUtils::getBasename(img_path.c_str());
|
||||
@ -94,7 +102,7 @@ Material* MaterialManager::getMaterialFor(video::ITexture* t,
|
||||
} // for i
|
||||
}
|
||||
|
||||
return getDefaultMaterial(mb->getMaterial().MaterialType);
|
||||
return getDefaultMaterial(material_type);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -59,6 +59,8 @@ public:
|
||||
void loadMaterial ();
|
||||
Material* getMaterialFor(video::ITexture* t,
|
||||
scene::IMeshBuffer *mb);
|
||||
Material* getMaterialFor(video::ITexture* t,
|
||||
video::E_MATERIAL_TYPE material_type);
|
||||
void setAllMaterialFlags(video::ITexture* t,
|
||||
scene::IMeshBuffer *mb);
|
||||
void adjustForFog(video::ITexture* t,
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
#include "graphics/glwrap.hpp"
|
||||
#include "graphics/irr_driver.hpp"
|
||||
#include "graphics/material.hpp"
|
||||
#include "graphics/material_manager.hpp"
|
||||
#include "graphics/shaders.hpp"
|
||||
#include "graphics/shared_gpu_objects.hpp"
|
||||
|
||||
@ -92,12 +94,20 @@ void STKBillboard::render()
|
||||
{
|
||||
if (irr_driver->getPhase() != TRANSPARENT_PASS)
|
||||
return;
|
||||
|
||||
core::vector3df pos = getAbsolutePosition();
|
||||
glBindVertexArray(billboardvao);
|
||||
video::ITexture *tex = Material.getTexture(0);
|
||||
if (!tex )
|
||||
return;
|
||||
|
||||
::Material* material = material_manager->getMaterialFor(tex,
|
||||
video::E_MATERIAL_TYPE::EMT_ONETEXTURE_BLEND);
|
||||
if (material->getShaderType() == Material::SHADERTYPE_ADDITIVE)
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
else
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
compressTexture(tex, true, true);
|
||||
GLuint texid = getTextureGLuint(tex);
|
||||
BillboardShader::getInstance()->use();
|
||||
|
@ -483,6 +483,7 @@ std::set<wchar_t> ScalableFont::getPreloadCharacters(const GUIEngine::TTFLoading
|
||||
|
||||
for (u32 i = 47; i < 59; ++i)
|
||||
preload_char.insert((wchar_t)i); //Include chars used by timer and laps count only
|
||||
preload_char.insert((wchar_t)120); //Used when displaying multiple items, e.g. 6x
|
||||
break;
|
||||
case T_BOLD:
|
||||
preload_char = translations->getCurrentAllChar(); //Loading unique characters
|
||||
|
@ -69,7 +69,7 @@ namespace GUIEngine
|
||||
|
||||
void setText(const irr::core::stringw &s);
|
||||
|
||||
|
||||
virtual int getHeightNeededAroundLabel() const { return 10; }
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "karts/abstract_kart.hpp"
|
||||
#include "karts/explosion_animation.hpp"
|
||||
#include "modes/world.hpp"
|
||||
#include "modes/soccer_world.hpp"
|
||||
#include "physics/physics.hpp"
|
||||
#include "tracks/track.hpp"
|
||||
#include "utils/constants.hpp"
|
||||
@ -226,6 +227,16 @@ void Flyable::getClosestKart(const AbstractKart **minKart,
|
||||
if(kart->isEliminated() || kart == m_owner ||
|
||||
kart->isInvulnerable() ||
|
||||
kart->getKartAnimation() ) continue;
|
||||
|
||||
const SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
if (sw)
|
||||
{
|
||||
// Don't hit teammates in soccer world
|
||||
if (sw->getKartTeam(kart->getWorldKartId()) == sw
|
||||
->getKartTeam(m_owner->getWorldKartId()))
|
||||
continue;
|
||||
}
|
||||
|
||||
btTransform t=kart->getTrans();
|
||||
|
||||
Vec3 delta = t.getOrigin()-trans_projectile.getOrigin();
|
||||
@ -461,6 +472,15 @@ void Flyable::explode(AbstractKart *kart_hit, PhysicalObject *object,
|
||||
{
|
||||
AbstractKart *kart = world->getKart(i);
|
||||
|
||||
const SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
if (sw)
|
||||
{
|
||||
// Don't explode teammates in soccer world
|
||||
if (sw->getKartTeam(kart->getWorldKartId()) == sw
|
||||
->getKartTeam(m_owner->getWorldKartId()))
|
||||
continue;
|
||||
}
|
||||
|
||||
// If no secondary hits should be done, only hit the
|
||||
// direct hit kart.
|
||||
if(!secondary_hits && kart!=kart_hit)
|
||||
|
@ -136,6 +136,7 @@ void PowerupManager::loadAllPowerups()
|
||||
loadWeights(*root, "end33", POSITION_END33 );
|
||||
loadWeights(*root, "last" , POSITION_LAST );
|
||||
loadWeights(*root, "battle" , POSITION_BATTLE_MODE);
|
||||
loadWeights(*root, "soccer" , POSITION_SOCCER_MODE);
|
||||
loadWeights(*root, "tuto", POSITION_TUTORIAL_MODE);
|
||||
|
||||
delete root;
|
||||
@ -273,7 +274,8 @@ void PowerupManager::updateWeightsForRace(unsigned int num_karts)
|
||||
{
|
||||
m_position_to_class.clear();
|
||||
// In battle mode no positions exist, so use only position 1
|
||||
unsigned int end_position = (race_manager->isBattleMode()) ? 1 : num_karts;
|
||||
unsigned int end_position = (race_manager->isBattleMode() ||
|
||||
race_manager->isSoccerMode()) ? 1 : num_karts;
|
||||
for(unsigned int position =1; position <= end_position; position++)
|
||||
{
|
||||
// Set up the mapping of position to position class:
|
||||
@ -322,6 +324,7 @@ PowerupManager::PositionClass
|
||||
unsigned int position)
|
||||
{
|
||||
if(race_manager->isBattleMode()) return POSITION_BATTLE_MODE;
|
||||
if(race_manager->isSoccerMode()) return POSITION_SOCCER_MODE;
|
||||
if(race_manager->isTutorialMode()) return POSITION_TUTORIAL_MODE;
|
||||
if(position==1) return POSITION_FIRST;
|
||||
if(position==num_karts) return POSITION_LAST;
|
||||
@ -354,6 +357,7 @@ PowerupManager::PowerupType PowerupManager::getRandomPowerup(unsigned int pos,
|
||||
// Positions start with 1, while the index starts with 0 - so subtract 1
|
||||
PositionClass pos_class =
|
||||
(race_manager->isBattleMode() ? POSITION_BATTLE_MODE :
|
||||
race_manager->isSoccerMode() ? POSITION_SOCCER_MODE :
|
||||
(race_manager->isTutorialMode() ? POSITION_TUTORIAL_MODE :
|
||||
m_position_to_class[pos-1]));
|
||||
|
||||
|
@ -96,6 +96,7 @@ public:
|
||||
POSITION_END33,
|
||||
POSITION_LAST,
|
||||
POSITION_BATTLE_MODE,
|
||||
POSITION_SOCCER_MODE,
|
||||
POSITION_TUTORIAL_MODE,
|
||||
POSITION_COUNT};
|
||||
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include "karts/explosion_animation.hpp"
|
||||
#include "karts/kart_properties.hpp"
|
||||
#include "modes/world.hpp"
|
||||
#include "modes/soccer_world.hpp"
|
||||
#include "karts/abstract_kart.hpp"
|
||||
|
||||
#define SWAT_POS_OFFSET core::vector3df(0.0, 0.2f, -0.4f)
|
||||
@ -196,10 +197,12 @@ bool Swatter::updateAndTestFinished(float dt)
|
||||
// change the current phase
|
||||
squashThingsAround();
|
||||
m_animation_phase = SWATTER_FROM_TARGET;
|
||||
if (race_manager->getMinorMode()==
|
||||
RaceManager::MINOR_MODE_3_STRIKES)
|
||||
if (race_manager
|
||||
->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
||||
race_manager
|
||||
->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
|
||||
{
|
||||
// Remove swatter from kart in 3 strikes battle
|
||||
// Remove swatter from kart in arena gameplay
|
||||
// after one successful hit
|
||||
m_discard_now = true;
|
||||
}
|
||||
@ -246,6 +249,15 @@ void Swatter::chooseTarget()
|
||||
if (kart->isInvulnerable() || kart->isSquashed())
|
||||
continue;
|
||||
|
||||
const SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
if (sw)
|
||||
{
|
||||
// Don't hit teammates in soccer world
|
||||
if (sw->getKartTeam(kart->getWorldKartId()) == sw
|
||||
->getKartTeam(m_kart->getWorldKartId()))
|
||||
continue;
|
||||
}
|
||||
|
||||
float dist2 = (kart->getXYZ()-m_kart->getXYZ()).length2();
|
||||
if(dist2<min_dist2)
|
||||
{
|
||||
|
@ -183,7 +183,7 @@ float AIBaseController::normalizeAngle(float angle)
|
||||
void AIBaseController::setSteering(float angle, float dt)
|
||||
{
|
||||
float steer_fraction = angle / m_kart->getMaxSteerAngle();
|
||||
if(!doSkid(steer_fraction))
|
||||
if(!canSkid(steer_fraction))
|
||||
m_controls->m_skid = KartControl::SC_NONE;
|
||||
else
|
||||
m_controls->m_skid = steer_fraction > 0 ? KartControl::SC_RIGHT
|
||||
@ -213,15 +213,6 @@ void AIBaseController::setSteering(float angle, float dt)
|
||||
}
|
||||
} // setSteering
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Determines if the kart should skid. The base implementation enables
|
||||
* skidding if a sharp turn is needed (which is for the old skidding
|
||||
* implementation).
|
||||
* \param steer_fraction The steering fraction as computed by the
|
||||
* AIBaseController.
|
||||
* \return True if the kart should skid.
|
||||
*/
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
/** Certain AI levels will not receive a slipstream bonus in order to
|
||||
* be not as hard.
|
||||
@ -231,23 +222,6 @@ bool AIBaseController::disableSlipstreamBonus() const
|
||||
return m_ai_properties->disableSlipstreamUsage();
|
||||
} // disableSlipstreamBonus
|
||||
|
||||
|
||||
bool AIBaseController::doSkid(float steer_fraction)
|
||||
{
|
||||
// Disable skidding when a plunger is in the face
|
||||
if(m_kart->getBlockedByPlungerTime()>0) return false;
|
||||
|
||||
// FIXME: Disable skidding for now if the new skidding
|
||||
// code is activated, since the AI can not handle this
|
||||
// properly.
|
||||
if(m_kart->getKartProperties()->getSkidVisualTime() > 0)
|
||||
return false;
|
||||
|
||||
// Otherwise return if we need a sharp turn (which is
|
||||
// for the old skidding implementation).
|
||||
return fabsf(steer_fraction)>=m_ai_properties->m_skidding_threshold;
|
||||
} // doSkid
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This is called when the kart crashed with the terrain. This subroutine
|
||||
* tries to detect if the AI is stuck by determining if a certain number
|
||||
@ -305,3 +279,34 @@ void AIBaseController::crashed(const Material *m)
|
||||
}
|
||||
|
||||
} // crashed(Material)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void AIBaseController::checkPosition(const Vec3 &point,
|
||||
posData *pos_data,
|
||||
Vec3 *lc) const
|
||||
{
|
||||
// Convert to local coordinates from the point of view of current kart
|
||||
btQuaternion q(btVector3(0, 1, 0), -m_kart->getHeading());
|
||||
Vec3 p = point - m_kart->getXYZ();
|
||||
Vec3 local_coordinates = quatRotate(q, p);
|
||||
|
||||
// Save local coordinates for later use if needed
|
||||
if (lc) *lc = local_coordinates;
|
||||
|
||||
// on_side: tell whether it's left or right hand side
|
||||
if (local_coordinates.getX() < 0)
|
||||
pos_data->on_side = true;
|
||||
else
|
||||
pos_data->on_side = false;
|
||||
|
||||
// behind: tell whether it's behind or not
|
||||
if (local_coordinates.getZ() < 0)
|
||||
pos_data->behind = true;
|
||||
else
|
||||
pos_data->behind = false;
|
||||
|
||||
pos_data->angle = atan2(fabsf(local_coordinates.getX()),
|
||||
fabsf(local_coordinates.getZ()));
|
||||
pos_data->distance = p.length_2d();
|
||||
|
||||
} // checkPosition
|
||||
|
@ -42,6 +42,7 @@ private:
|
||||
bool m_stuck;
|
||||
|
||||
protected:
|
||||
|
||||
/** Length of the kart, storing it here saves many function calls. */
|
||||
float m_kart_length;
|
||||
|
||||
@ -49,23 +50,27 @@ protected:
|
||||
float m_kart_width;
|
||||
|
||||
/** Keep a pointer to the track to reduce calls */
|
||||
Track *m_track;
|
||||
Track *m_track;
|
||||
|
||||
/** A pointer to the AI properties for this kart. */
|
||||
const AIProperties *m_ai_properties;
|
||||
|
||||
static bool m_ai_debug;
|
||||
|
||||
/** Position info structure of targets. */
|
||||
struct posData {bool behind; bool on_side; float angle; float distance;};
|
||||
|
||||
void setControllerName(const std::string &name);
|
||||
float steerToPoint(const Vec3 &point);
|
||||
float normalizeAngle(float angle);
|
||||
virtual void update (float delta) ;
|
||||
virtual void setSteering (float angle, float dt);
|
||||
void setControllerName(const std::string &name);
|
||||
float steerToPoint(const Vec3 &point);
|
||||
float normalizeAngle(float angle);
|
||||
virtual bool doSkid(float steer_fraction);
|
||||
virtual bool canSkid(float steer_fraction) = 0;
|
||||
// ------------------------------------------------------------------------
|
||||
/** This can be called to detect if the kart is stuck (i.e. repeatedly
|
||||
* hitting part of the track). */
|
||||
bool isStuck() const { return m_stuck; }
|
||||
bool isStuck() const { return m_stuck; }
|
||||
void checkPosition(const Vec3&, posData*, Vec3* lc = NULL) const;
|
||||
|
||||
public:
|
||||
AIBaseController(AbstractKart *kart);
|
||||
@ -83,7 +88,8 @@ public:
|
||||
virtual bool isPlayerController() const { return false; }
|
||||
virtual bool isLocalPlayerController() const { return false; }
|
||||
virtual void action(PlayerAction action, int value) {};
|
||||
virtual void skidBonusTriggered() {};
|
||||
virtual void skidBonusTriggered() {};
|
||||
|
||||
}; // AIBaseController
|
||||
|
||||
#endif
|
||||
|
@ -70,16 +70,6 @@ public:
|
||||
AIBaseLapController(AbstractKart *kart);
|
||||
virtual ~AIBaseLapController() {};
|
||||
virtual void reset();
|
||||
virtual void crashed(const AbstractKart *k) {};
|
||||
virtual void handleZipper(bool play_sound) {};
|
||||
virtual void finishedRace(float time) {};
|
||||
virtual void collectedItem(const Item &item, int add_info=-1,
|
||||
float previous_energy=0) {};
|
||||
virtual void setPosition(int p) {};
|
||||
virtual bool isNetworkController() const { return false; }
|
||||
virtual bool isPlayerController() const { return false; }
|
||||
virtual void action(PlayerAction action, int value) {};
|
||||
virtual void skidBonusTriggered() {};
|
||||
}; // AIBaseLapController
|
||||
|
||||
#endif
|
||||
|
@ -45,7 +45,7 @@ protected:
|
||||
friend class AIBaseController;
|
||||
friend class AIBaseLapController;
|
||||
friend class SkiddingAI;
|
||||
friend class BattleAI;
|
||||
friend class ArenaAI;
|
||||
|
||||
/** Used to check that all values are defined in the xml file. */
|
||||
static float UNDEFINED;
|
||||
|
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 "items/attachment.hpp"
|
||||
#include "items/item_manager.hpp"
|
||||
#include "items/powerup.hpp"
|
||||
#include "items/projectile_manager.hpp"
|
||||
#include "karts/abstract_kart.hpp"
|
||||
#include "karts/controller/kart_control.hpp"
|
||||
#include "karts/controller/player_controller.hpp"
|
||||
#include "karts/controller/ai_properties.hpp"
|
||||
#include "karts/kart_properties.hpp"
|
||||
#include "karts/max_speed.hpp"
|
||||
#include "karts/rescue_animation.hpp"
|
||||
#include "karts/skidding.hpp"
|
||||
#include "modes/three_strikes_battle.hpp"
|
||||
#include "tracks/battle_graph.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
#ifdef AI_DEBUG
|
||||
#include "irrlicht.h"
|
||||
@ -50,7 +40,7 @@ using namespace std;
|
||||
#endif
|
||||
|
||||
BattleAI::BattleAI(AbstractKart *kart)
|
||||
: AIBaseController(kart)
|
||||
: ArenaAI(kart)
|
||||
{
|
||||
|
||||
reset();
|
||||
@ -60,19 +50,8 @@ BattleAI::BattleAI(AbstractKart *kart)
|
||||
m_debug_sphere = irr_driver->addSphere(1.0f, col_debug);
|
||||
m_debug_sphere->setVisible(true);
|
||||
#endif
|
||||
|
||||
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES)
|
||||
{
|
||||
m_world = dynamic_cast<ThreeStrikesBattle*>(World::getWorld());
|
||||
m_track = m_world->getTrack();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Those variables are not defined in a battle mode (m_world is
|
||||
// a linear world, since it assumes the existance of drivelines)
|
||||
m_world = NULL;
|
||||
m_track = NULL;
|
||||
}
|
||||
m_world = dynamic_cast<ThreeStrikesBattle*>(World::getWorld());
|
||||
m_track = m_world->getTrack();
|
||||
|
||||
// Don't call our own setControllerName, since this will add a
|
||||
// billboard showing 'AIBaseController' to the kart.
|
||||
@ -94,162 +73,20 @@ BattleAI::~BattleAI()
|
||||
*/
|
||||
void BattleAI::reset()
|
||||
{
|
||||
m_target_node = BattleGraph::UNKNOWN_POLY;
|
||||
m_adjusting_side = false;
|
||||
m_closest_kart = NULL;
|
||||
m_closest_kart_node = BattleGraph::UNKNOWN_POLY;
|
||||
m_closest_kart_point = Vec3(0, 0, 0);
|
||||
m_closest_kart_pos_data = {0};
|
||||
m_cur_kart_pos_data = {0};
|
||||
m_is_steering_overridden = false;
|
||||
m_is_stuck = false;
|
||||
m_is_uturn = false;
|
||||
m_target_point = Vec3(0, 0, 0);
|
||||
m_time_since_last_shot = 0.0f;
|
||||
m_time_since_driving = 0.0f;
|
||||
m_time_since_reversing = 0.0f;
|
||||
m_time_since_steering_overridden = 0.0f;
|
||||
m_time_since_uturn = 0.0f;
|
||||
m_on_node.clear();
|
||||
m_path_corners.clear();
|
||||
m_portals.clear();
|
||||
|
||||
m_cur_difficulty = race_manager->getDifficulty();
|
||||
ArenaAI::reset();
|
||||
AIBaseController::reset();
|
||||
m_mini_skid = false;
|
||||
} // reset
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This is the main entry point for the AI.
|
||||
* It is called once per frame for each AI and determines the behaviour of
|
||||
* the AI, e.g. steering, accelerating/braking, firing.
|
||||
*/
|
||||
void BattleAI::update(float dt)
|
||||
{
|
||||
// This is used to enable firing an item backwards.
|
||||
m_controls->m_look_back = false;
|
||||
m_controls->m_nitro = false;
|
||||
|
||||
// Don't do anything if there is currently a kart animations shown.
|
||||
if (m_kart->getKartAnimation())
|
||||
return;
|
||||
|
||||
if (m_world->isStartPhase())
|
||||
{
|
||||
AIBaseController::update(dt);
|
||||
return;
|
||||
}
|
||||
|
||||
checkIfStuck(dt);
|
||||
if (m_is_stuck && !m_is_uturn)
|
||||
{
|
||||
setSteering(0.0f, dt);
|
||||
|
||||
if (fabsf(m_kart->getSpeed()) >
|
||||
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
|
||||
&& m_kart->getSpeed() < 0)
|
||||
m_controls->m_accel = -0.06f;
|
||||
else
|
||||
m_controls->m_accel = -4.0f;
|
||||
|
||||
m_time_since_reversing += dt;
|
||||
|
||||
if (m_time_since_reversing >= 1.0f)
|
||||
{
|
||||
m_is_stuck = false;
|
||||
m_time_since_reversing = 0.0f;
|
||||
}
|
||||
AIBaseController::update(dt);
|
||||
return;
|
||||
}
|
||||
|
||||
findClosestKart(true);
|
||||
findTarget();
|
||||
handleItems(dt);
|
||||
handleBanana();
|
||||
|
||||
if (m_kart->getSpeed() > 15.0f && m_cur_kart_pos_data.angle < 0.2f)
|
||||
{
|
||||
// Only use nitro when target is straight
|
||||
m_controls->m_nitro = true;
|
||||
}
|
||||
|
||||
if (m_is_uturn)
|
||||
{
|
||||
handleUTurn(dt);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleAcceleration(dt);
|
||||
handleSteering(dt);
|
||||
handleBraking();
|
||||
}
|
||||
|
||||
AIBaseController::update(dt);
|
||||
|
||||
m_mini_skid = false;
|
||||
ArenaAI::update(dt);
|
||||
} // update
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void BattleAI::checkIfStuck(const float dt)
|
||||
{
|
||||
if (m_is_stuck) return;
|
||||
|
||||
if (m_kart->getKartAnimation() || m_world->isStartPhase())
|
||||
{
|
||||
m_on_node.clear();
|
||||
m_time_since_driving = 0.0f;
|
||||
}
|
||||
|
||||
m_on_node.insert(m_world->getKartNode(m_kart->getWorldKartId()));
|
||||
m_time_since_driving += dt;
|
||||
|
||||
if ((m_time_since_driving >=
|
||||
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f)
|
||||
&& m_on_node.size() < 2 && !m_is_uturn &&
|
||||
fabsf(m_kart->getSpeed()) < 3.0f) || isStuck() == true)
|
||||
{
|
||||
// Check whether a kart stay on the same node for a period of time
|
||||
// Or crashed 3 times
|
||||
m_on_node.clear();
|
||||
m_time_since_driving = 0.0f;
|
||||
AIBaseController::reset();
|
||||
m_is_stuck = true;
|
||||
}
|
||||
else if (m_time_since_driving >=
|
||||
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
|
||||
{
|
||||
m_on_node.clear(); // Reset for any correct movement
|
||||
m_time_since_driving = 0.0f;
|
||||
}
|
||||
|
||||
} // checkIfStuck
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void BattleAI::checkPosition(const Vec3 &point, posData *pos_data)
|
||||
{
|
||||
// Convert to local coordinates from the point of view of current kart
|
||||
btQuaternion q(btVector3(0, 1, 0), -m_kart->getHeading());
|
||||
Vec3 p = point - m_kart->getXYZ();
|
||||
Vec3 local_coordinates = quatRotate(q, p);
|
||||
|
||||
// on_side: tell whether it's left or right hand side
|
||||
if (local_coordinates.getX() < 0)
|
||||
pos_data->on_side = true;
|
||||
else
|
||||
pos_data->on_side = false;
|
||||
|
||||
// behind: tell whether it's behind or not
|
||||
if (local_coordinates.getZ() < 0)
|
||||
pos_data->behind = true;
|
||||
else
|
||||
pos_data->behind = false;
|
||||
|
||||
pos_data->angle = atan2(fabsf(local_coordinates.getX()),
|
||||
fabsf(local_coordinates.getZ()));
|
||||
pos_data->distance = p.length_2d();
|
||||
} // checkPosition
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void BattleAI::findClosestKart(bool difficulty)
|
||||
void BattleAI::findClosestKart(bool use_difficulty)
|
||||
{
|
||||
float distance = 99999.9f;
|
||||
const unsigned int n = m_world->getNumKarts();
|
||||
@ -265,8 +102,8 @@ void BattleAI::findClosestKart(bool difficulty)
|
||||
|
||||
// Test whether takes current difficulty into account for closest kart
|
||||
// Notice: it don't affect aiming, this function will be called once
|
||||
// more in handleItems, which ignore difficulty.
|
||||
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && difficulty)
|
||||
// more in handleArenaItems, which ignore difficulty.
|
||||
if (m_cur_difficulty == RaceManager::DIFFICULTY_EASY && use_difficulty)
|
||||
{
|
||||
// Skip human players for novice mode unless only human players left
|
||||
const AbstractKart* temp = m_world->getKart(i);
|
||||
@ -275,7 +112,7 @@ void BattleAI::findClosestKart(bool difficulty)
|
||||
m_world->getCurrentNumPlayers()) > 1)
|
||||
continue;
|
||||
}
|
||||
else if (m_cur_difficulty == RaceManager::DIFFICULTY_BEST && difficulty)
|
||||
else if (m_cur_difficulty == RaceManager::DIFFICULTY_BEST && use_difficulty)
|
||||
{
|
||||
// Skip AI players for supertux mode
|
||||
const AbstractKart* temp = m_world->getKart(i);
|
||||
@ -295,10 +132,20 @@ void BattleAI::findClosestKart(bool difficulty)
|
||||
m_closest_kart_node = m_world->getKartNode(closest_kart_num);
|
||||
m_closest_kart_point = closest_kart->getXYZ();
|
||||
|
||||
if (!difficulty)
|
||||
if (!use_difficulty)
|
||||
{
|
||||
m_closest_kart = m_world->getKart(closest_kart_num);
|
||||
checkPosition(m_closest_kart_point, &m_closest_kart_pos_data);
|
||||
|
||||
// Do a mini-skid to closest kart only when firing target,
|
||||
// not straight ahead, not too far, in front of it
|
||||
// and with suitable difficulties.
|
||||
if (m_closest_kart_pos_data.angle > 0.2f &&
|
||||
m_closest_kart_pos_data.distance < 20.0f &&
|
||||
!m_closest_kart_pos_data.behind &&
|
||||
(m_cur_difficulty == RaceManager::DIFFICULTY_HARD ||
|
||||
m_cur_difficulty == RaceManager::DIFFICULTY_BEST))
|
||||
m_mini_skid = true;
|
||||
}
|
||||
} // findClosestKart
|
||||
|
||||
@ -308,7 +155,7 @@ void BattleAI::findTarget()
|
||||
// Find a suitable target to drive to, either powerup or kart
|
||||
if (m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING &&
|
||||
m_kart->getAttachment()->getType() != Attachment::ATTACH_SWATTER)
|
||||
handleItemCollection(&m_target_point , &m_target_node);
|
||||
collectItemInArena(&m_target_point , &m_target_node);
|
||||
else
|
||||
{
|
||||
m_target_point = m_closest_kart_point;
|
||||
@ -316,615 +163,13 @@ void BattleAI::findTarget()
|
||||
}
|
||||
} // findTarget
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Handles acceleration.
|
||||
* \param dt Time step size.
|
||||
*/
|
||||
void BattleAI::handleAcceleration(const float dt)
|
||||
// ------------------------------------------------------------------------
|
||||
int BattleAI::getCurrentNode() const
|
||||
{
|
||||
if (m_controls->m_brake)
|
||||
{
|
||||
m_controls->m_accel = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
const float handicap =
|
||||
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 0.7f : 1.0f);
|
||||
m_controls->m_accel = stk_config->m_ai_acceleration * handicap;
|
||||
|
||||
} // handleAcceleration
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void BattleAI::handleUTurn(const float dt)
|
||||
return m_world->getKartNode(m_kart->getWorldKartId());
|
||||
} // getCurrentNode
|
||||
// ------------------------------------------------------------------------
|
||||
bool BattleAI::isWaiting() const
|
||||
{
|
||||
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
|
||||
|
||||
if (fabsf(m_kart->getSpeed()) >
|
||||
(m_kart->getKartProperties()->getEngineMaxSpeed() / 5)
|
||||
&& m_kart->getSpeed() < 0) // Try to emulate reverse like human players
|
||||
m_controls->m_accel = -0.06f;
|
||||
else
|
||||
m_controls->m_accel = -5.0f;
|
||||
|
||||
if (m_time_since_uturn >=
|
||||
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 2.0f : 1.5f))
|
||||
setSteering(-(turn_side), dt); // Preventing keep going around circle
|
||||
else
|
||||
setSteering(turn_side, dt);
|
||||
m_time_since_uturn += dt;
|
||||
|
||||
checkPosition(m_target_point, &m_cur_kart_pos_data);
|
||||
if (!m_cur_kart_pos_data.behind || m_time_since_uturn >
|
||||
(m_cur_difficulty == RaceManager::DIFFICULTY_EASY ? 3.5f : 3.0f))
|
||||
{
|
||||
m_is_uturn = false;
|
||||
m_time_since_uturn = 0.0f;
|
||||
}
|
||||
else
|
||||
m_is_uturn = true;
|
||||
} // handleUTurn
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This function sets the steering.
|
||||
* \param dt Time step size.
|
||||
*/
|
||||
void BattleAI::handleSteering(const float dt)
|
||||
{
|
||||
const int current_node = m_world->getKartNode(m_kart->getWorldKartId());
|
||||
|
||||
if (current_node == BattleGraph::UNKNOWN_POLY ||
|
||||
m_target_node == BattleGraph::UNKNOWN_POLY) return;
|
||||
|
||||
if (m_is_steering_overridden)
|
||||
{
|
||||
// Steering is overridden to avoid eating banana
|
||||
const float turn_side = (m_adjusting_side ? 1.0f : -1.0f);
|
||||
m_time_since_steering_overridden += dt;
|
||||
if (m_time_since_steering_overridden > 0.35f)
|
||||
setSteering(-(turn_side), dt);
|
||||
else
|
||||
setSteering(turn_side, dt);
|
||||
if (m_time_since_steering_overridden > 0.7f)
|
||||
{
|
||||
m_is_steering_overridden = false;
|
||||
m_time_since_steering_overridden = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_target_node == current_node)
|
||||
{
|
||||
// Very close to the item, steer directly
|
||||
checkPosition(m_target_point, &m_cur_kart_pos_data);
|
||||
#ifdef AI_DEBUG
|
||||
m_debug_sphere->setPosition(m_target_point.toIrrVector());
|
||||
#endif
|
||||
if (m_cur_kart_pos_data.behind)
|
||||
{
|
||||
m_adjusting_side = m_cur_kart_pos_data.on_side;
|
||||
m_is_uturn = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float target_angle = steerToPoint(m_target_point);
|
||||
setSteering(target_angle, dt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
else if (m_target_node != current_node)
|
||||
{
|
||||
findPortals(current_node, m_target_node);
|
||||
stringPull(m_kart->getXYZ(), m_target_point);
|
||||
if (m_path_corners.size() > 0)
|
||||
m_target_point = m_path_corners[0];
|
||||
|
||||
checkPosition(m_target_point, &m_cur_kart_pos_data);
|
||||
#ifdef AI_DEBUG
|
||||
m_debug_sphere->setPosition(m_target_point.toIrrVector());
|
||||
#endif
|
||||
if (m_cur_kart_pos_data.behind)
|
||||
{
|
||||
m_adjusting_side = m_cur_kart_pos_data.on_side;
|
||||
m_is_uturn = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float target_angle = steerToPoint(m_target_point);
|
||||
setSteering(target_angle, dt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Do nothing (go straight) if no targets found
|
||||
setSteering(0.0f, dt);
|
||||
return;
|
||||
}
|
||||
} // handleSteering
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void BattleAI::handleBanana()
|
||||
{
|
||||
if (m_is_steering_overridden || m_is_uturn) return;
|
||||
|
||||
const std::vector< std::pair<const Item*, int> >& item_list =
|
||||
BattleGraph::get()->getItemList();
|
||||
const unsigned int items_count = item_list.size();
|
||||
for (unsigned int i = 0; i < items_count; ++i)
|
||||
{
|
||||
const Item* item = item_list[i].first;
|
||||
if (item->getType() == Item::ITEM_BANANA && !item->wasCollected())
|
||||
{
|
||||
posData banana_pos = {0};
|
||||
checkPosition(item->getXYZ(), &banana_pos);
|
||||
if (banana_pos.angle < 0.2f && banana_pos.distance < 7.5f &&
|
||||
!banana_pos.behind)
|
||||
{
|
||||
// Check whether it's straight ahead towards a banana
|
||||
// If so, try to do hard turn to avoid
|
||||
m_adjusting_side = banana_pos.on_side;
|
||||
m_is_steering_overridden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // handleBanana
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This function finds the polyon edges(portals) that the AI will cross before
|
||||
* reaching its destination. We start from the current polygon and call
|
||||
* BattleGraph::getNextShortestPathPoly() to find the next polygon on the shortest
|
||||
* path to the destination. Then find the common edge between the current
|
||||
* poly and the next poly, store it and step through the channel.
|
||||
*
|
||||
* 1----2----3 In this case, the portals are:
|
||||
* |strt| | (2,5) (4,5) (10,7) (10,9) (11,12)
|
||||
* 6----5----4
|
||||
* | |
|
||||
* 7----10----11----14
|
||||
* | | | end |
|
||||
* 8----9-----12----13
|
||||
*
|
||||
* \param start The start node(polygon) of the channel.
|
||||
* \param end The end node(polygon) of the channel.
|
||||
*/
|
||||
void BattleAI::findPortals(int start, int end)
|
||||
{
|
||||
int this_node = start;
|
||||
|
||||
// We can't use NULL because NULL==0 which is a valid node, so we initialize
|
||||
// with a value that is always invalid.
|
||||
int next_node = -999;
|
||||
|
||||
m_portals.clear();
|
||||
|
||||
while (next_node != end && this_node != -1 && next_node != -1 && this_node != end)
|
||||
{
|
||||
next_node = BattleGraph::get()->getNextShortestPathPoly(this_node, end);
|
||||
if (next_node == BattleGraph::UNKNOWN_POLY || next_node == -999) return;
|
||||
|
||||
std::vector<int> this_node_verts =
|
||||
NavMesh::get()->getNavPoly(this_node).getVerticesIndex();
|
||||
std::vector<int> next_node_verts=
|
||||
NavMesh::get()->getNavPoly(next_node).getVerticesIndex();
|
||||
|
||||
// this_node_verts and next_node_verts hold vertices of polygons in CCW order
|
||||
// We reverse next_node_verts so it becomes easy to compare edges in the next step
|
||||
std::reverse(next_node_verts.begin(),next_node_verts.end());
|
||||
|
||||
Vec3 portalLeft, portalRight;
|
||||
//bool flag = 0;
|
||||
for (unsigned int n_i = 0; n_i < next_node_verts.size(); n_i++)
|
||||
{
|
||||
for (unsigned int t_i = 0; t_i < this_node_verts.size(); t_i++)
|
||||
{
|
||||
if ((next_node_verts[n_i] == this_node_verts[t_i]) &&
|
||||
(next_node_verts[(n_i+1)%next_node_verts.size()] ==
|
||||
this_node_verts[(t_i+1)%this_node_verts.size()]))
|
||||
{
|
||||
portalLeft = NavMesh::get()->
|
||||
getVertex(this_node_verts[(t_i+1)%this_node_verts.size()]);
|
||||
|
||||
portalRight = NavMesh::get()->getVertex(this_node_verts[t_i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_portals.push_back(std::make_pair(portalLeft, portalRight));
|
||||
// for debugging:
|
||||
//m_debug_sphere->setPosition((portalLeft).toIrrVector());
|
||||
this_node = next_node;
|
||||
}
|
||||
} // findPortals
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This function implements the funnel algorithm for finding shortest paths
|
||||
* through a polygon channel. This means that we should move from corner to
|
||||
* corner to move on the most straight and shortest path to the destination.
|
||||
* This can be visualized as pulling a string from the end point to the start.
|
||||
* The string will bend at the corners, and this algorithm will find those
|
||||
* corners using portals from findPortals(). The AI will aim at the first
|
||||
* corner and the rest can be used for estimating the curve (braking).
|
||||
*
|
||||
* 1----2----3 In this case, the corners are:
|
||||
* |strt| | <5,10,end>
|
||||
* 6----5----4
|
||||
* | |
|
||||
* 7----10----11----14
|
||||
* | | | end |
|
||||
* 8----9-----12----13
|
||||
*
|
||||
* \param start_pos The start position (usually the AI's current position).
|
||||
* \param end_pos The end position (m_target_point).
|
||||
*/
|
||||
void BattleAI::stringPull(const Vec3& start_pos, const Vec3& end_pos)
|
||||
{
|
||||
Vec3 funnel_apex = start_pos;
|
||||
Vec3 funnel_left = m_portals[0].first;
|
||||
Vec3 funnel_right = m_portals[0].second;
|
||||
unsigned int apex_index=0, fun_left_index=0, fun_right_index=0;
|
||||
m_portals.push_back(std::make_pair(end_pos, end_pos));
|
||||
m_path_corners.clear();
|
||||
const float eps=0.0001f;
|
||||
|
||||
for (unsigned int i = 0; i < m_portals.size(); i++)
|
||||
{
|
||||
Vec3 portal_left = m_portals[i].first;
|
||||
Vec3 portal_right = m_portals[i].second;
|
||||
|
||||
//Compute for left edge
|
||||
if ((funnel_left == funnel_apex) ||
|
||||
portal_left.sideOfLine2D(funnel_apex, funnel_left) <= -eps)
|
||||
{
|
||||
funnel_left = 0.98f*portal_left + 0.02f*portal_right;
|
||||
//funnel_left = portal_left;
|
||||
fun_left_index = i;
|
||||
|
||||
if (portal_left.sideOfLine2D(funnel_apex, funnel_right) < -eps)
|
||||
{
|
||||
funnel_apex = funnel_right;
|
||||
apex_index = fun_right_index;
|
||||
m_path_corners.push_back(funnel_apex);
|
||||
|
||||
funnel_left = funnel_apex;
|
||||
funnel_right = funnel_apex;
|
||||
i = apex_index;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//Compute for right edge
|
||||
if ((funnel_right == funnel_apex) ||
|
||||
portal_right.sideOfLine2D(funnel_apex, funnel_right) >= eps)
|
||||
{
|
||||
funnel_right = 0.98f*portal_right + 0.02f*portal_left;
|
||||
//funnel_right = portal_right;
|
||||
fun_right_index = i;
|
||||
|
||||
if (portal_right.sideOfLine2D(funnel_apex, funnel_left) > eps)
|
||||
{
|
||||
funnel_apex = funnel_left;
|
||||
apex_index = fun_left_index;
|
||||
m_path_corners.push_back(funnel_apex);
|
||||
|
||||
funnel_left = funnel_apex;
|
||||
funnel_right = funnel_apex;
|
||||
i = apex_index;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Push end_pos to m_path_corners so if no corners, we aim at target
|
||||
m_path_corners.push_back(end_pos);
|
||||
} // stringPull
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** This function handles braking. It calls determineTurnRadius() to find out
|
||||
* the curve radius. Depending on the turn radius, it finds out the maximum
|
||||
* speed. If the current speed is greater than the max speed and a set minimum
|
||||
* speed, brakes are applied.
|
||||
*/
|
||||
void BattleAI::handleBraking()
|
||||
{
|
||||
m_controls->m_brake = false;
|
||||
|
||||
if (m_world->getKartNode(m_kart->getWorldKartId())
|
||||
== BattleGraph::UNKNOWN_POLY ||
|
||||
m_target_node == BattleGraph::UNKNOWN_POLY ||
|
||||
m_is_steering_overridden) return;
|
||||
|
||||
// A kart will not brake when the speed is already slower than this
|
||||
// value. This prevents a kart from going too slow (or even backwards)
|
||||
// in tight curves.
|
||||
const float MIN_SPEED = 5.0f;
|
||||
|
||||
std::vector<Vec3> points;
|
||||
|
||||
points.push_back(m_kart->getXYZ());
|
||||
points.push_back(m_path_corners[0]);
|
||||
points.push_back((m_path_corners.size()>=2) ? m_path_corners[1] : m_path_corners[0]);
|
||||
|
||||
float current_curve_radius = BattleAI::determineTurnRadius(points);
|
||||
|
||||
Vec3 d1 = m_kart->getXYZ() - m_target_point;
|
||||
Vec3 d2 = m_kart->getXYZ() - m_path_corners[0];
|
||||
if (d1.length2_2d() < d2.length2_2d())
|
||||
current_curve_radius = d1.length_2d();
|
||||
|
||||
float max_turn_speed = m_kart->getSpeedForTurnRadius(current_curve_radius);
|
||||
|
||||
if (m_kart->getSpeed() > max_turn_speed &&
|
||||
m_kart->getSpeed() > MIN_SPEED)
|
||||
{
|
||||
m_controls->m_brake = true;
|
||||
}
|
||||
|
||||
} // handleBraking
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** The turn radius is determined by fitting a parabola to 3 points: current
|
||||
* location of AI, first corner and the second corner. Once the constants are
|
||||
* computed, a formula is used to find the radius of curvature at the kart's
|
||||
* current location.
|
||||
* NOTE: This method does not apply enough braking, should think of something
|
||||
* else.
|
||||
*/
|
||||
float BattleAI::determineTurnRadius( std::vector<Vec3>& points )
|
||||
{
|
||||
// Declaring variables
|
||||
float a, b;
|
||||
irr::core::CMatrix4<float> A;
|
||||
irr::core::CMatrix4<float> X;
|
||||
irr::core::CMatrix4<float> B;
|
||||
|
||||
//Populating matrices
|
||||
for (unsigned int i = 0; i < 3; i++)
|
||||
{
|
||||
A(i, 0) = points[i].x()*points[i].x();
|
||||
A(i, 1) = points[i].x();
|
||||
A(i, 2) = 1.0f;
|
||||
A(i, 3) = 0.0f;
|
||||
}
|
||||
A(3, 0) = A(3, 1) = A(3, 2) = 0.0f;
|
||||
A(3, 3) = 1.0f;
|
||||
|
||||
for (unsigned int i = 0; i < 3; i++)
|
||||
{
|
||||
B(i, 0) = points[i].z();
|
||||
B(i, 1) = 0.0f;
|
||||
B(i, 2) = 0.0f;
|
||||
B(i, 3) = 0.0f;
|
||||
}
|
||||
B(3, 0) = B(3, 1) = B(3, 2) = B(3, 3) = 0.0f;
|
||||
|
||||
//Computing inverse : X = inv(A)*B
|
||||
irr::core::CMatrix4<float> invA;
|
||||
if (!A.getInverse(invA))
|
||||
return -1;
|
||||
|
||||
X = invA*B;
|
||||
a = X(0, 0);
|
||||
b = X(0, 1);
|
||||
//c = X(0, 2);
|
||||
|
||||
float x = points.front().x();
|
||||
//float z = a*pow(x, 2) + b*x + c;
|
||||
float dx_by_dz = 2*a*x + b;
|
||||
float d2x_by_dz = 2*a;
|
||||
|
||||
float radius = pow(abs(1 + pow(dx_by_dz, 2)), 1.5f)/ abs(d2x_by_dz);
|
||||
|
||||
return radius;
|
||||
} // determineTurnRadius
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void BattleAI::handleItems(const float dt)
|
||||
{
|
||||
m_controls->m_fire = false;
|
||||
if (m_kart->getKartAnimation() ||
|
||||
m_kart->getPowerup()->getType() == PowerupManager::POWERUP_NOTHING)
|
||||
return;
|
||||
|
||||
// Find a closest kart again, this time we ignore difficulty
|
||||
findClosestKart(false);
|
||||
|
||||
if (!m_closest_kart) return;
|
||||
|
||||
m_time_since_last_shot += dt;
|
||||
|
||||
float min_bubble_time = 2.0f;
|
||||
const bool difficulty = m_cur_difficulty == RaceManager::DIFFICULTY_EASY ||
|
||||
m_cur_difficulty == RaceManager::DIFFICULTY_MEDIUM;
|
||||
|
||||
const bool fire_behind = m_closest_kart_pos_data.behind && !difficulty;
|
||||
|
||||
const bool perfect_aim = m_closest_kart_pos_data.angle < 0.2f;
|
||||
|
||||
switch(m_kart->getPowerup()->getType())
|
||||
{
|
||||
case PowerupManager::POWERUP_BUBBLEGUM:
|
||||
{
|
||||
Attachment::AttachmentType type = m_kart->getAttachment()->getType();
|
||||
// Don't use shield when we have a swatter.
|
||||
if (type == Attachment::ATTACH_SWATTER ||
|
||||
type == Attachment::ATTACH_NOLOKS_SWATTER)
|
||||
break;
|
||||
|
||||
// Check if a flyable (cake, ...) is close. If so, use bubblegum
|
||||
// as shield
|
||||
if (!m_kart->isShielded() &&
|
||||
projectile_manager->projectileIsClose(m_kart,
|
||||
m_ai_properties->m_shield_incoming_radius))
|
||||
{
|
||||
m_controls->m_fire = true;
|
||||
m_controls->m_look_back = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Avoid dropping all bubble gums one after another
|
||||
if (m_time_since_last_shot < 3.0f) break;
|
||||
|
||||
// Use bubblegum if the next kart behind is 'close' but not too close,
|
||||
// or can't find a close kart for too long time
|
||||
if ((m_closest_kart_pos_data.distance < 15.0f &&
|
||||
m_closest_kart_pos_data.distance > 3.0f) ||
|
||||
m_time_since_last_shot > 15.0f)
|
||||
{
|
||||
m_controls->m_fire = true;
|
||||
m_controls->m_look_back = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break; // POWERUP_BUBBLEGUM
|
||||
}
|
||||
case PowerupManager::POWERUP_CAKE:
|
||||
{
|
||||
// if the kart has a shield, do not break it by using a cake.
|
||||
if (m_kart->getShieldTime() > min_bubble_time)
|
||||
break;
|
||||
|
||||
// Leave some time between shots
|
||||
if (m_time_since_last_shot < 1.0f) break;
|
||||
|
||||
if (m_closest_kart_pos_data.distance < 25.0f &&
|
||||
!m_closest_kart->isInvulnerable())
|
||||
{
|
||||
m_controls->m_fire = true;
|
||||
m_controls->m_look_back = fire_behind;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
} // POWERUP_CAKE
|
||||
|
||||
case PowerupManager::POWERUP_BOWLING:
|
||||
{
|
||||
// if the kart has a shield, do not break it by using a bowling ball.
|
||||
if (m_kart->getShieldTime() > min_bubble_time)
|
||||
break;
|
||||
|
||||
// Leave some time between shots
|
||||
if (m_time_since_last_shot < 1.0f) break;
|
||||
|
||||
if (m_closest_kart_pos_data.distance < 6.0f &&
|
||||
(difficulty || perfect_aim))
|
||||
{
|
||||
m_controls->m_fire = true;
|
||||
m_controls->m_look_back = fire_behind;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
} // POWERUP_BOWLING
|
||||
|
||||
case PowerupManager::POWERUP_SWATTER:
|
||||
{
|
||||
// Squared distance for which the swatter works
|
||||
float d2 = m_kart->getKartProperties()->getSwatterDistance();
|
||||
// if the kart has a shield, do not break it by using a swatter.
|
||||
if (m_kart->getShieldTime() > min_bubble_time)
|
||||
break;
|
||||
|
||||
if (!m_closest_kart->isSquashed() &&
|
||||
m_closest_kart_pos_data.distance < d2 &&
|
||||
m_closest_kart->getSpeed() < m_kart->getSpeed())
|
||||
{
|
||||
m_controls->m_fire = true;
|
||||
m_controls->m_look_back = false;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Below powerups won't appear in battle mode, so skip them
|
||||
case PowerupManager::POWERUP_ZIPPER:
|
||||
break; // POWERUP_ZIPPER
|
||||
|
||||
case PowerupManager::POWERUP_PLUNGER:
|
||||
break; // POWERUP_PLUNGER
|
||||
|
||||
case PowerupManager::POWERUP_SWITCH: // Don't handle switch
|
||||
m_controls->m_fire = true; // (use it no matter what) for now
|
||||
break; // POWERUP_SWITCH
|
||||
|
||||
case PowerupManager::POWERUP_PARACHUTE:
|
||||
break; // POWERUP_PARACHUTE
|
||||
|
||||
case PowerupManager::POWERUP_ANVIL:
|
||||
break; // POWERUP_ANVIL
|
||||
|
||||
case PowerupManager::POWERUP_RUBBERBALL:
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::error("BattleAI",
|
||||
"Invalid or unhandled powerup '%d' in default AI.",
|
||||
m_kart->getPowerup()->getType());
|
||||
assert(false);
|
||||
}
|
||||
if (m_controls->m_fire)
|
||||
m_time_since_last_shot = 0.0f;
|
||||
} // handleItems
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void BattleAI::handleItemCollection(Vec3* aim_point, int* target_node)
|
||||
{
|
||||
float distance = 99999.9f;
|
||||
const std::vector< std::pair<const Item*, int> >& item_list =
|
||||
BattleGraph::get()->getItemList();
|
||||
const unsigned int items_count = item_list.size();
|
||||
|
||||
if (item_list.empty())
|
||||
{
|
||||
// Notice: this should not happen, as it makes no sense
|
||||
// for an arean without items, if so how can attack happen?
|
||||
Log::fatal ("BattleAI",
|
||||
"AI can't find any items in the arena, "
|
||||
"maybe there is something wrong with the navmesh, "
|
||||
"make sure it lies closely to the ground.");
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int closest_item_num = 0;
|
||||
|
||||
for (unsigned int i = 0; i < items_count; ++i)
|
||||
{
|
||||
const Item* item = item_list[i].first;
|
||||
|
||||
if (item->wasCollected()) continue;
|
||||
|
||||
if ((item->getType() == Item::ITEM_NITRO_BIG ||
|
||||
item->getType() == Item::ITEM_NITRO_SMALL) &&
|
||||
(m_kart->getEnergy() >
|
||||
m_kart->getKartProperties()->getNitroSmallContainer()))
|
||||
continue; // Ignore nitro when already has some
|
||||
|
||||
Vec3 d = item->getXYZ() - m_kart->getXYZ();
|
||||
if (d.length_2d() <= distance &&
|
||||
(item->getType() == Item::ITEM_BONUS_BOX ||
|
||||
item->getType() == Item::ITEM_NITRO_BIG ||
|
||||
item->getType() == Item::ITEM_NITRO_SMALL))
|
||||
{
|
||||
closest_item_num = i;
|
||||
distance = d.length_2d();
|
||||
}
|
||||
}
|
||||
|
||||
const Item *item_selected = item_list[closest_item_num].first;
|
||||
if (item_selected->getType() == Item::ITEM_BONUS_BOX ||
|
||||
item_selected->getType() == Item::ITEM_NITRO_BIG ||
|
||||
item_selected->getType() == Item::ITEM_NITRO_SMALL)
|
||||
{
|
||||
*aim_point = item_selected->getXYZ();
|
||||
*target_node = item_list[closest_item_num].second;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle when all targets are swapped, which make AIs follow karts
|
||||
*aim_point = m_closest_kart_point;
|
||||
*target_node = m_closest_kart_node;
|
||||
}
|
||||
} // handleItemCollection
|
||||
return m_world->isStartPhase();
|
||||
} // isWaiting
|
||||
|
@ -21,139 +21,33 @@
|
||||
#ifndef HEADER_BATTLE_AI_HPP
|
||||
#define HEADER_BATTLE_AI_HPP
|
||||
|
||||
#undef AI_DEBUG
|
||||
#ifdef AI_DEBUG
|
||||
#include "graphics/irr_driver.hpp"
|
||||
#endif
|
||||
|
||||
#include "karts/controller/ai_base_controller.hpp"
|
||||
#include "race/race_manager.hpp"
|
||||
#include "utils/random_generator.hpp"
|
||||
#include "karts/controller/arena_ai.hpp"
|
||||
|
||||
class ThreeStrikesBattle;
|
||||
class Vec3;
|
||||
class Item;
|
||||
|
||||
namespace irr
|
||||
/** The actual battle AI.
|
||||
* \ingroup controller
|
||||
*/
|
||||
class BattleAI : public ArenaAI
|
||||
{
|
||||
namespace scene { class ISceneNode; }
|
||||
namespace video { class ITexture; }
|
||||
}
|
||||
|
||||
class BattleAI : public AIBaseController
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
/** Holds the position info of targets. */
|
||||
struct posData {bool behind; bool on_side; float angle; float distance;};
|
||||
|
||||
/** Used by handleBanana and UTurn, it tells whether to do left or right
|
||||
* turning when steering is overridden. */
|
||||
bool m_adjusting_side;
|
||||
|
||||
int m_closest_kart_node;
|
||||
Vec3 m_closest_kart_point;
|
||||
|
||||
/** Pointer to the closest kart around this kart. */
|
||||
AbstractKart *m_closest_kart;
|
||||
|
||||
posData m_closest_kart_pos_data;
|
||||
posData m_cur_kart_pos_data;
|
||||
|
||||
/** Holds the current difficulty. */
|
||||
RaceManager::Difficulty m_cur_difficulty;
|
||||
|
||||
/** Indicates that the steering of kart is overridden, and
|
||||
* m_time_since_steering_overridden is counting down. */
|
||||
bool m_is_steering_overridden;
|
||||
|
||||
/** Indicates that the kart is currently stuck, and m_time_since_reversing is
|
||||
* counting down. */
|
||||
bool m_is_stuck;
|
||||
|
||||
/** Indicates that the kart need a uturn to reach a node behind, and
|
||||
* m_time_since_uturn is counting down. */
|
||||
bool m_is_uturn;
|
||||
|
||||
/** Holds the unique node ai has driven through, useful to tell if AI is
|
||||
* stuck by determine the size of this set. */
|
||||
std::set <int> m_on_node;
|
||||
|
||||
/** Holds the corner points computed using the funnel algorithm that the AI
|
||||
* will eventaully move through. See stringPull(). */
|
||||
std::vector<Vec3> m_path_corners;
|
||||
|
||||
/** Holds the set of portals that the kart will cross when moving through
|
||||
* polygon channel. See findPortals(). */
|
||||
std::vector<std::pair<Vec3,Vec3> > m_portals;
|
||||
|
||||
/** The node(poly) at which the target point lies in. */
|
||||
int m_target_node;
|
||||
|
||||
/** The target point. */
|
||||
Vec3 m_target_point;
|
||||
|
||||
/** Time an item has been collected and not used. */
|
||||
float m_time_since_last_shot;
|
||||
|
||||
/** This is a timer that counts down when the kart is reversing to get unstuck. */
|
||||
float m_time_since_reversing;
|
||||
|
||||
/** This is a timer that counts down when the kart is starting to drive. */
|
||||
float m_time_since_driving;
|
||||
|
||||
/** This is a timer that counts down when the steering of kart is overridden. */
|
||||
float m_time_since_steering_overridden;
|
||||
|
||||
/** This is a timer that counts down when the kart is doing u-turn. */
|
||||
float m_time_since_uturn;
|
||||
|
||||
void checkIfStuck(const float dt);
|
||||
void checkPosition(const Vec3 &, posData*);
|
||||
float determineTurnRadius(std::vector<Vec3>& points);
|
||||
void findClosestKart(bool difficulty);
|
||||
void findPortals(int start, int end);
|
||||
void findTarget();
|
||||
void handleAcceleration(const float dt);
|
||||
void handleBanana();
|
||||
void handleBraking();
|
||||
void handleItems(const float dt);
|
||||
void handleItemCollection(Vec3*, int*);
|
||||
void handleSteering(const float dt);
|
||||
void handleSwatter();
|
||||
void handleUTurn(const float dt);
|
||||
void stringPull(const Vec3&, const Vec3&);
|
||||
|
||||
protected:
|
||||
|
||||
/** Keep a pointer to world. */
|
||||
ThreeStrikesBattle *m_world;
|
||||
|
||||
#ifdef AI_DEBUG
|
||||
/** For debugging purpose: a sphere indicating where the AI
|
||||
* is targeting at. */
|
||||
irr::scene::ISceneNode *m_debug_sphere;
|
||||
#endif
|
||||
bool m_mini_skid;
|
||||
|
||||
virtual void findClosestKart(bool use_difficulty);
|
||||
virtual void findTarget();
|
||||
virtual int getCurrentNode() const;
|
||||
virtual bool isWaiting() const;
|
||||
virtual bool canSkid(float steer_fraction) { return m_mini_skid; }
|
||||
public:
|
||||
BattleAI(AbstractKart *kart);
|
||||
~BattleAI();
|
||||
virtual void update (float delta);
|
||||
virtual void reset ();
|
||||
|
||||
virtual void crashed(const AbstractKart *k) {};
|
||||
virtual void handleZipper(bool play_sound) {};
|
||||
virtual void finishedRace(float time) {};
|
||||
virtual void collectedItem(const Item &item, int add_info=-1,
|
||||
float previous_energy=0) {};
|
||||
virtual void setPosition(int p) {};
|
||||
virtual bool isNetworkController() const { return false; }
|
||||
virtual bool isPlayerController() const { return false; }
|
||||
virtual void action(PlayerAction action, int value) {};
|
||||
virtual void skidBonusTriggered() {};
|
||||
virtual bool disableSlipstreamBonus() const {return 0; }
|
||||
virtual void newLap(int lap) {};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -74,12 +74,13 @@ private:
|
||||
*that can be done, and end up setting their respective m_controls
|
||||
*variable.
|
||||
*/
|
||||
void handleSteering(float dt);
|
||||
void handleRescue(const float DELTA);
|
||||
void handleSteering(float dt);
|
||||
void handleRescue(const float DELTA);
|
||||
|
||||
void checkCrashes(const int STEPS, const Vec3& pos);
|
||||
void findNonCrashingPoint(Vec3 *result);
|
||||
int calcSteps();
|
||||
void checkCrashes(const int STEPS, const Vec3& pos);
|
||||
void findNonCrashingPoint(Vec3 *result);
|
||||
int calcSteps();
|
||||
virtual bool canSkid(float steer_fraction) { return false; }
|
||||
public:
|
||||
EndController(AbstractKart *kart,
|
||||
Controller *prev_controller);
|
||||
|
@ -265,6 +265,13 @@ void PlayerController::update(float dt)
|
||||
if (!history->replayHistory())
|
||||
steer(dt, m_steer_val);
|
||||
|
||||
if (World::getWorld()->getPhase() == World::GOAL_PHASE)
|
||||
{
|
||||
m_controls->m_brake = false;
|
||||
m_controls->m_accel = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
if (World::getWorld()->isStartPhase())
|
||||
{
|
||||
if (m_controls->m_accel || m_controls->m_brake ||
|
||||
|
@ -34,9 +34,6 @@ protected:
|
||||
|
||||
float m_penalty_time;
|
||||
|
||||
/** This variable is required for battle mode **/
|
||||
int m_current_node;
|
||||
|
||||
virtual void steer(float, int);
|
||||
// ------------------------------------------------------------------------
|
||||
/** Called when this kart started too early and got a start penalty. */
|
||||
@ -58,10 +55,6 @@ public:
|
||||
{
|
||||
};
|
||||
// ------------------------------------------------------------------------
|
||||
unsigned int getCurrentNode() const { return m_current_node; }
|
||||
// ------------------------------------------------------------------------
|
||||
void setCurrentNode(int i) { m_current_node = i; }
|
||||
// ------------------------------------------------------------------------
|
||||
virtual bool isPlayerController() const OVERRIDE { return true; }
|
||||
// ------------------------------------------------------------------------
|
||||
virtual bool isLocalPlayerController() const OVERRIDE { return true; }
|
||||
|
@ -1238,8 +1238,6 @@ void SkiddingAI::handleItems(const float dt)
|
||||
}
|
||||
break; // POWERUP_BUBBLEGUM
|
||||
}
|
||||
// All the thrown/fired items might be improved by considering the angle
|
||||
// towards m_kart_ahead.
|
||||
case PowerupManager::POWERUP_CAKE:
|
||||
{
|
||||
// if the kart has a shield, do not break it by using a cake.
|
||||
@ -1297,6 +1295,22 @@ void SkiddingAI::handleItems(const float dt)
|
||||
// slower, so it should take longer to hit something which
|
||||
// can result in changing our target.
|
||||
if(m_time_since_last_shot < 5.0f) break;
|
||||
// Consider angle towards karts
|
||||
bool straight_behind = false;
|
||||
bool straight_ahead = false;
|
||||
if (m_kart_behind)
|
||||
{
|
||||
posData behind_pos = {0};
|
||||
checkPosition(m_kart_behind->getXYZ(), &behind_pos);
|
||||
if (behind_pos.angle < 0.2f) straight_behind = true;
|
||||
}
|
||||
if (m_kart_ahead)
|
||||
{
|
||||
posData ahead_pos = {0};
|
||||
checkPosition(m_kart_ahead->getXYZ(), &ahead_pos);
|
||||
if (ahead_pos.angle < 0.2f) straight_ahead = true;
|
||||
}
|
||||
|
||||
// Bowling balls are slower, so only fire on closer karts - but when
|
||||
// firing backwards, the kart can be further away, since the ball
|
||||
// acts a bit like a mine (and the kart is racing towards it, too)
|
||||
@ -1307,7 +1321,8 @@ void SkiddingAI::handleItems(const float dt)
|
||||
: m_distance_ahead;
|
||||
m_controls->m_fire = ( (fire_backwards && distance < 30.0f) ||
|
||||
(!fire_backwards && distance <10.0f) ) &&
|
||||
m_time_since_last_shot > 3.0f;
|
||||
m_time_since_last_shot > 3.0f &&
|
||||
(straight_behind || straight_ahead);
|
||||
if(m_controls->m_fire)
|
||||
m_controls->m_look_back = fire_backwards;
|
||||
break;
|
||||
@ -2195,7 +2210,7 @@ void SkiddingAI::handleCurve()
|
||||
* AIBaseLapController.
|
||||
* \return True if the kart should skid.
|
||||
*/
|
||||
bool SkiddingAI::doSkid(float steer_fraction)
|
||||
bool SkiddingAI::canSkid(float steer_fraction)
|
||||
{
|
||||
if(fabsf(steer_fraction)>1.5f)
|
||||
{
|
||||
@ -2304,7 +2319,7 @@ bool SkiddingAI::doSkid(float steer_fraction)
|
||||
m_kart->getIdent().c_str());
|
||||
#endif
|
||||
return false;
|
||||
} // doSkid
|
||||
} // canSkid
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Converts the steering angle to a lr steering in the range of -1 to 1.
|
||||
@ -2326,7 +2341,7 @@ void SkiddingAI::setSteering(float angle, float dt)
|
||||
// Use a simple finite state machine to make sure to randomly decide
|
||||
// whether to skid or not only once per skid section. See docs for
|
||||
// m_skid_probability_state for more details.
|
||||
if(!doSkid(steer_fraction))
|
||||
if(!canSkid(steer_fraction))
|
||||
{
|
||||
m_skid_probability_state = SKID_PROBAB_NOT_YET;
|
||||
m_controls->m_skid = KartControl::SC_NONE;
|
||||
|
@ -94,7 +94,7 @@ the AI does the following steps:
|
||||
behaviour.
|
||||
The function handleSteering() then calls setSteering() to set the
|
||||
actually steering amount. The latter function also decides if skidding
|
||||
should be done or not (by calling doSkid()).
|
||||
should be done or not (by calling canSkid()).
|
||||
- decide if to try to collect or avoid items (handeItems).
|
||||
It considers all items on quads between the current quad of the kart
|
||||
and the quad the AI is aiming at (see handleSteering). If it finds
|
||||
@ -270,7 +270,7 @@ private:
|
||||
const Vec3 &end,
|
||||
Vec3 *center,
|
||||
float *radius);
|
||||
virtual bool doSkid(float steer_fraction);
|
||||
virtual bool canSkid(float steer_fraction);
|
||||
virtual void setSteering(float angle, float dt);
|
||||
void handleCurve();
|
||||
|
||||
|
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/rescue_animation.hpp"
|
||||
#include "modes/overworld.hpp"
|
||||
#include "modes/soccer_world.hpp"
|
||||
#include "modes/world.hpp"
|
||||
#include "io/file_manager.hpp"
|
||||
#include "items/attachment.hpp"
|
||||
@ -883,7 +884,6 @@ void Kart::setRaceResult()
|
||||
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE ||
|
||||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_TIME_TRIAL)
|
||||
{
|
||||
// TODO NetworkController?
|
||||
if (m_controller->isLocalPlayerController()) // if player is on this computer
|
||||
{
|
||||
PlayerProfile *player = PlayerManager::getCurrentPlayer();
|
||||
@ -920,8 +920,8 @@ void Kart::setRaceResult()
|
||||
}
|
||||
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
||||
{
|
||||
// TODO complete together with soccer ai!
|
||||
m_race_result = true;
|
||||
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
m_race_result = sw->getKartSoccerResult(this->getWorldKartId());
|
||||
}
|
||||
else if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_EASTER_EGG)
|
||||
{
|
||||
@ -931,7 +931,7 @@ void Kart::setRaceResult()
|
||||
else
|
||||
Log::warn("Kart", "Unknown game mode given.");
|
||||
|
||||
} // setKartResult
|
||||
} // setRaceResult
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Called when an item is collected. It will either adjust the collected
|
||||
|
@ -138,6 +138,7 @@ KartModel::KartModel(bool is_master)
|
||||
m_animation_frame[i]=-1;
|
||||
m_animation_speed = 25;
|
||||
m_current_animation = AF_DEFAULT;
|
||||
m_play_non_loop = false;
|
||||
} // KartModel
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -682,11 +683,12 @@ void KartModel::finishedRace()
|
||||
/** Enables- or disables the end animation.
|
||||
* \param type The type of animation to play.
|
||||
*/
|
||||
void KartModel::setAnimation(AnimationFrameType type)
|
||||
void KartModel::setAnimation(AnimationFrameType type, bool play_non_loop)
|
||||
{
|
||||
// if animations disabled, give up
|
||||
if (m_animated_node == NULL) return;
|
||||
|
||||
m_play_non_loop = play_non_loop;
|
||||
m_current_animation = type;
|
||||
if(m_current_animation==AF_DEFAULT)
|
||||
{
|
||||
@ -825,6 +827,12 @@ void KartModel::update(float dt, float distance, float steer, float speed)
|
||||
// If animations are disabled, stop here
|
||||
if (m_animated_node == NULL) return;
|
||||
|
||||
if (m_play_non_loop && m_animated_node->getLoopMode() == true)
|
||||
{
|
||||
m_play_non_loop = false;
|
||||
this->setAnimation(AF_DEFAULT);
|
||||
}
|
||||
|
||||
// Update the speed-weighted objects' animations
|
||||
if (m_kart != NULL)
|
||||
{
|
||||
|
@ -207,9 +207,14 @@ private:
|
||||
|
||||
/** True if this is the master copy, managed by KartProperties. This
|
||||
* is mainly used for debugging, e.g. the master copies might not have
|
||||
* anything attached to it etc. */
|
||||
* anything attached to it etc. */
|
||||
bool m_is_master;
|
||||
|
||||
/** True if the animation played is non-loop, which will reset to
|
||||
* AF_DEFAULT after first loop ends. Mainly used in soccer mode for
|
||||
* animation playing after scored. */
|
||||
bool m_play_non_loop;
|
||||
|
||||
void loadWheelInfo(const XMLNode &node,
|
||||
const std::string &wheel_name, int index);
|
||||
|
||||
@ -306,7 +311,7 @@ public:
|
||||
AnimationFrameType getAnimation() { return m_current_animation; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Enables- or disables the end animation. */
|
||||
void setAnimation(AnimationFrameType type);
|
||||
void setAnimation(AnimationFrameType type, bool play_non_loop = false);
|
||||
// ------------------------------------------------------------------------
|
||||
/** Sets the kart this model is currently used for */
|
||||
void setKart(AbstractKart* k) { m_kart = k; }
|
||||
|
@ -27,16 +27,17 @@
|
||||
#include "karts/kart_properties.hpp"
|
||||
#include "karts/rescue_animation.hpp"
|
||||
#include "karts/controller/local_player_controller.hpp"
|
||||
#include "karts/controller/soccer_ai.hpp"
|
||||
#include "physics/physics.hpp"
|
||||
#include "states_screens/race_gui_base.hpp"
|
||||
#include "tracks/check_goal.hpp"
|
||||
#include "tracks/check_manager.hpp"
|
||||
#include "tracks/track.hpp"
|
||||
#include "tracks/track_object_manager.hpp"
|
||||
#include "utils/constants.hpp"
|
||||
|
||||
#include <IMeshSceneNode.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Constructor. Sets up the clock mode etc.
|
||||
*/
|
||||
@ -45,7 +46,7 @@ SoccerWorld::SoccerWorld() : WorldWithRank()
|
||||
if(race_manager->hasTimeTarget())
|
||||
{
|
||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
||||
countDownReachedZero = false;
|
||||
m_count_down_reached_zero = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -69,24 +70,19 @@ SoccerWorld::~SoccerWorld()
|
||||
*/
|
||||
void SoccerWorld::init()
|
||||
{
|
||||
m_kart_team_map.clear();
|
||||
m_kart_position_map.clear();
|
||||
WorldWithRank::init();
|
||||
m_display_rank = false;
|
||||
m_goal_timer = 0.f;
|
||||
m_lastKartToHitBall = -1;
|
||||
|
||||
// check for possible problems if AI karts were incorrectly added
|
||||
if(getNumKarts() > race_manager->getNumPlayers())
|
||||
{
|
||||
Log::error("[SoccerWorld]", "No AI exists for this game mode");
|
||||
exit(1);
|
||||
}
|
||||
m_goal_timer = 0.0f;
|
||||
m_ball_hitter = -1;
|
||||
m_goal_target = race_manager->getMaxGoal();
|
||||
m_goal_sound = SFXManager::get()->createSoundSource("goal_scored");
|
||||
|
||||
} // init
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Called when a battle is restarted.
|
||||
/** Called when a soccer game is restarted.
|
||||
*/
|
||||
void SoccerWorld::reset()
|
||||
{
|
||||
@ -94,31 +90,37 @@ void SoccerWorld::reset()
|
||||
if(race_manager->hasTimeTarget())
|
||||
{
|
||||
WorldStatus::setClockMode(WorldStatus::CLOCK_COUNTDOWN, race_manager->getTimeTarget());
|
||||
countDownReachedZero = false;
|
||||
m_count_down_reached_zero = false;
|
||||
}
|
||||
else WorldStatus::setClockMode(CLOCK_CHRONO);
|
||||
|
||||
m_can_score_points = true;
|
||||
memset(m_team_goals, 0, sizeof(m_team_goals));
|
||||
m_red_goal = 0;
|
||||
m_blue_goal = 0;
|
||||
m_red_scorers.clear();
|
||||
m_red_score_times.clear();
|
||||
m_blue_scorers.clear();
|
||||
m_blue_score_times.clear();
|
||||
m_ball_hitter = -1;
|
||||
m_ball = NULL;
|
||||
m_red_defender = -1;
|
||||
m_blue_defender = -1;
|
||||
m_ball_invalid_timer = 0.0f;
|
||||
|
||||
// Reset original positions for the soccer balls
|
||||
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
|
||||
assert(tom);
|
||||
m_redScorers.clear();
|
||||
m_redScoreTimes.clear();
|
||||
m_blueScorers.clear();
|
||||
m_blueScoreTimes.clear();
|
||||
m_lastKartToHitBall = -1;
|
||||
PtrVector<TrackObject>& objects = tom->getObjects();
|
||||
for(unsigned int i=0; i<objects.size(); i++)
|
||||
for (unsigned int i = 0; i < objects.size(); i++)
|
||||
{
|
||||
TrackObject* obj = objects.get(i);
|
||||
if(!obj->isSoccerBall())
|
||||
continue;
|
||||
|
||||
obj->reset();
|
||||
obj->getPhysicalObject()->reset();
|
||||
m_ball = obj;
|
||||
// Handle one ball only
|
||||
break;
|
||||
}
|
||||
if (!m_ball)
|
||||
Log::fatal("SoccerWorld","Ball is missing in soccer field, abort.");
|
||||
|
||||
if (m_goal_sound != NULL &&
|
||||
m_goal_sound->getStatus() == SFXBase::SFX_PLAYING)
|
||||
@ -127,6 +129,10 @@ void SoccerWorld::reset()
|
||||
}
|
||||
|
||||
initKartList();
|
||||
resetAllNodes();
|
||||
initGoalNodes();
|
||||
resetBall();
|
||||
|
||||
} // reset
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -148,111 +154,127 @@ void SoccerWorld::update(float dt)
|
||||
WorldWithRank::update(dt);
|
||||
WorldWithRank::updateTrack(dt);
|
||||
|
||||
updateBallPosition(dt);
|
||||
if (m_track->hasNavMesh())
|
||||
{
|
||||
updateKartNodes();
|
||||
updateDefenders();
|
||||
}
|
||||
|
||||
if (world->getPhase() == World::GOAL_PHASE)
|
||||
{
|
||||
if (m_goal_timer == 0.0f)
|
||||
{
|
||||
// Stop all karts
|
||||
for (unsigned int i = 0; i < m_karts.size(); i++)
|
||||
m_karts[i]->setVelocity(btVector3(0, 0, 0));
|
||||
}
|
||||
m_goal_timer += dt;
|
||||
|
||||
if (m_goal_timer > 3.0f)
|
||||
{
|
||||
world->setPhase(WorldStatus::RACE_PHASE);
|
||||
m_goal_timer = 0;
|
||||
m_goal_timer = 0.0f;
|
||||
if (!isRaceOver())
|
||||
{
|
||||
// Reset all karts
|
||||
for (unsigned int i = 0; i < m_karts.size(); i++)
|
||||
moveKartAfterRescue(m_karts[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // update
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::onCheckGoalTriggered(bool first_goal)
|
||||
{
|
||||
if (isRaceOver())
|
||||
if (isRaceOver() || isStartPhase())
|
||||
return;
|
||||
|
||||
if (m_can_score_points)
|
||||
{
|
||||
m_team_goals[first_goal ? 0 : 1]++;
|
||||
(first_goal ? m_red_goal++ : m_blue_goal++);
|
||||
|
||||
World *world = World::getWorld();
|
||||
world->setPhase(WorldStatus::GOAL_PHASE);
|
||||
m_goal_sound->play();
|
||||
if(m_lastKartToHitBall != -1)
|
||||
if (m_ball_hitter != -1)
|
||||
{
|
||||
if(first_goal)
|
||||
ScorerData sd;
|
||||
sd.m_id = m_ball_hitter;
|
||||
sd.m_correct_goal = isCorrectGoal(m_ball_hitter, first_goal);
|
||||
|
||||
if (sd.m_correct_goal)
|
||||
{
|
||||
m_redScorers.push_back(m_lastKartToHitBall);
|
||||
m_karts[m_ball_hitter]->getKartModel()
|
||||
->setAnimation(KartModel::AF_WIN_START, true/* play_non_loop*/);
|
||||
}
|
||||
|
||||
else if (!sd.m_correct_goal)
|
||||
{
|
||||
m_karts[m_ball_hitter]->getKartModel()
|
||||
->setAnimation(KartModel::AF_LOSE_START, true/* play_non_loop*/);
|
||||
}
|
||||
|
||||
if (first_goal)
|
||||
{
|
||||
// Notice: true first_goal means it's blue goal being shoot,
|
||||
// so red team can score
|
||||
m_red_scorers.push_back(sd);
|
||||
if(race_manager->hasTimeTarget())
|
||||
m_redScoreTimes.push_back(race_manager->getTimeTarget() - world->getTime());
|
||||
{
|
||||
m_red_score_times.push_back(race_manager
|
||||
->getTimeTarget() - world->getTime());
|
||||
}
|
||||
else
|
||||
m_redScoreTimes.push_back(world->getTime());
|
||||
m_red_score_times.push_back(world->getTime());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_blueScorers.push_back(m_lastKartToHitBall);
|
||||
if(race_manager->hasTimeTarget())
|
||||
m_blueScoreTimes.push_back(race_manager->getTimeTarget() - world->getTime());
|
||||
m_blue_scorers.push_back(sd);
|
||||
if (race_manager->hasTimeTarget())
|
||||
{
|
||||
m_blue_score_times.push_back(race_manager
|
||||
->getTimeTarget() - world->getTime());
|
||||
}
|
||||
else
|
||||
m_blueScoreTimes.push_back(world->getTime());
|
||||
m_blue_score_times.push_back(world->getTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset original positions for the soccer balls
|
||||
TrackObjectManager* tom = getTrack()->getTrackObjectManager();
|
||||
assert(tom);
|
||||
|
||||
PtrVector<TrackObject>& objects = tom->getObjects();
|
||||
for(unsigned int i=0; i<objects.size(); i++)
|
||||
{
|
||||
TrackObject* obj = objects.get(i);
|
||||
if(!obj->isSoccerBall())
|
||||
continue;
|
||||
|
||||
obj->reset();
|
||||
obj->getPhysicalObject()->reset();
|
||||
}
|
||||
|
||||
resetBall();
|
||||
//Resetting the ball triggers the goal check line one more time.
|
||||
//This ensures that only one goal is counted, and the second is ignored.
|
||||
m_can_score_points = !m_can_score_points;
|
||||
|
||||
//for(int i=0 ; i < getNumKarts() ; i++
|
||||
|
||||
/*if(World::getWorld()->getTrack()->isAutoRescueEnabled() &&
|
||||
!getKartAnimation() && fabs(getRoll())>60*DEGREE_TO_RAD &&
|
||||
fabs(getSpeed())<3.0f )
|
||||
{
|
||||
new RescueAnimation(this, true);
|
||||
}*/
|
||||
|
||||
// TODO: rescue the karts
|
||||
} // onCheckGoalTriggered
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Sets the last kart that hit the ball, to be able to
|
||||
* identify the scorer later.
|
||||
*/
|
||||
void SoccerWorld::setLastKartTohitBall(unsigned int kartId)
|
||||
* identify the scorer later.
|
||||
*/
|
||||
void SoccerWorld::setBallHitter(unsigned int kart_id)
|
||||
{
|
||||
m_lastKartToHitBall = kartId;
|
||||
} // setLastKartTohitBall
|
||||
m_ball_hitter = kart_id;
|
||||
} // setBallHitter
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** The battle is over if only one kart is left, or no player kart.
|
||||
/** The soccer game is over if time up or either team wins.
|
||||
*/
|
||||
bool SoccerWorld::isRaceOver()
|
||||
{
|
||||
// for tests : never over when we have a single player there :)
|
||||
if (race_manager->getNumPlayers() < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(race_manager->hasTimeTarget())
|
||||
{
|
||||
return countDownReachedZero;
|
||||
return m_count_down_reached_zero;
|
||||
}
|
||||
// One team scored the target goals ...
|
||||
else
|
||||
{
|
||||
return (getScore(0) >= m_goal_target || getScore(1) >= m_goal_target);
|
||||
return (getScore(SOCCER_TEAM_BLUE) >= m_goal_target ||
|
||||
getScore(SOCCER_TEAM_RED) >= m_goal_target);
|
||||
}
|
||||
|
||||
} // isRaceOver
|
||||
@ -270,116 +292,54 @@ void SoccerWorld::terminateRace()
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Called when the match time ends.
|
||||
*/
|
||||
*/
|
||||
void SoccerWorld::countdownReachedZero()
|
||||
{
|
||||
countDownReachedZero = true;
|
||||
m_count_down_reached_zero = true;
|
||||
} // countdownReachedZero
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Returns the data to display in the race gui.
|
||||
*/
|
||||
void SoccerWorld::getKartsDisplayInfo(
|
||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info)
|
||||
{
|
||||
// TODO!!
|
||||
/*
|
||||
const unsigned int kart_amount = getNumKarts();
|
||||
for(unsigned int i = 0; i < kart_amount ; i++)
|
||||
{
|
||||
RaceGUIBase::KartIconDisplayInfo& rank_info = (*info)[i];
|
||||
|
||||
// reset color
|
||||
rank_info.lap = -1;
|
||||
|
||||
AbstractKart* kart = getKart(i);
|
||||
switch(kart->getSoccerTeam())
|
||||
{
|
||||
case SOCCER_TEAM_BLUE:
|
||||
rank_info.r = 0.0f;
|
||||
rank_info.g = 0.0f;
|
||||
rank_info.b = 0.7f;
|
||||
break;
|
||||
case SOCCER_TEAM_RED:
|
||||
rank_info.r = 0.9f;
|
||||
rank_info.g = 0.0f;
|
||||
rank_info.b = 0.0f;
|
||||
break;
|
||||
default:
|
||||
assert(false && "Soccer team not set to blue or red");
|
||||
rank_info.r = 0.0f;
|
||||
rank_info.g = 0.0f;
|
||||
rank_info.b = 0.0f;
|
||||
}
|
||||
}
|
||||
*/
|
||||
} // getKartsDisplayInfo
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Set position and team for the karts */
|
||||
void SoccerWorld::initKartList()
|
||||
{
|
||||
const unsigned int kart_amount = (unsigned int)m_karts.size();
|
||||
|
||||
int team_karts_amount[NB_SOCCER_TEAMS];
|
||||
memset(team_karts_amount, 0, sizeof(team_karts_amount));
|
||||
|
||||
//Loading the indicator textures
|
||||
irr::video::ITexture *redTeamTexture =
|
||||
irr::video::ITexture *red =
|
||||
irr_driver->getTexture(FileManager::GUI, "soccer_player_red.png");
|
||||
irr::video::ITexture *blueTeamTexture =
|
||||
irr::video::ITexture *blue =
|
||||
irr_driver->getTexture(FileManager::GUI, "soccer_player_blue.png");
|
||||
|
||||
//Assigning indicators
|
||||
for(unsigned int i=0; i<kart_amount; i++)
|
||||
{
|
||||
scene::ISceneNode *arrowNode;
|
||||
scene::ISceneNode *arrow_node;
|
||||
float arrow_pos_height = m_karts[i]->getKartModel()->getHeight()+0.5f;
|
||||
SoccerTeam team = race_manager->getKartInfo(i).getSoccerTeam();
|
||||
SoccerTeam team = getKartTeam(i);
|
||||
|
||||
arrowNode = irr_driver->addBillboard(core::dimension2d<irr::f32>(0.3f,0.3f),
|
||||
team==SOCCER_TEAM_RED ? redTeamTexture : blueTeamTexture,
|
||||
m_karts[i]->getNode(), true);
|
||||
arrow_node = irr_driver->addBillboard(core::dimension2d<irr::f32>(0.3f,
|
||||
0.3f), team == SOCCER_TEAM_BLUE ? blue : red, m_karts[i]
|
||||
->getNode(), true);
|
||||
|
||||
arrowNode->setPosition(core::vector3df(0, arrow_pos_height, 0));
|
||||
arrow_node->setPosition(core::vector3df(0, arrow_pos_height, 0));
|
||||
}
|
||||
|
||||
// Compute start positions for each team
|
||||
int team_cur_position[NB_SOCCER_TEAMS];
|
||||
team_cur_position[0] = 1;
|
||||
for(int i=1 ; i < (int)NB_SOCCER_TEAMS ; i++)
|
||||
team_cur_position[i] = team_karts_amount[i-1] + team_cur_position[i-1];
|
||||
|
||||
// Set kart positions, ordering them by team
|
||||
for(unsigned int n=0; n<kart_amount; n++)
|
||||
{
|
||||
SoccerTeam team = race_manager->getKartInfo(n).getSoccerTeam();
|
||||
#ifdef DEBUG
|
||||
// In debug mode it's possible to play soccer with a single player
|
||||
// (in artist debug mode). Avoid overwriting memory in this case.
|
||||
if(team==SOCCER_TEAM_NONE) team=SOCCER_TEAM_RED;
|
||||
#endif
|
||||
m_karts[n]->setPosition(team_cur_position[team]);
|
||||
team_cur_position[team]++;
|
||||
} // next kart
|
||||
}
|
||||
} // initKartList
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
int SoccerWorld::getScore(unsigned int i)
|
||||
bool SoccerWorld::getKartSoccerResult(unsigned int kart_id) const
|
||||
{
|
||||
return m_team_goals[i];
|
||||
} // getScore
|
||||
if (m_red_scorers.size() == m_blue_scorers.size()) return true;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
int SoccerWorld::getTeamLeader(unsigned int team)
|
||||
{
|
||||
for(unsigned int i = 0; i< m_karts.size(); i++)
|
||||
{
|
||||
if(race_manager->getKartInfo(i).getSoccerTeam() == (SoccerTeam) team)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
} // getTeamLeader
|
||||
bool red_win = m_red_scorers.size() > m_blue_scorers.size();
|
||||
SoccerTeam team = getKartTeam(kart_id);
|
||||
|
||||
if ((red_win && team == SOCCER_TEAM_RED) ||
|
||||
(!red_win && team == SOCCER_TEAM_BLUE))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
|
||||
} // getKartSoccerResult
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
|
||||
@ -387,19 +347,55 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
|
||||
RaceManager::KartType kart_type,
|
||||
PerPlayerDifficulty difficulty)
|
||||
{
|
||||
int posIndex = index;
|
||||
int position = index+1;
|
||||
int cur_red = getTeamNum(SOCCER_TEAM_RED);
|
||||
int cur_blue = getTeamNum(SOCCER_TEAM_BLUE);
|
||||
int pos_index = 0;
|
||||
int position = index + 1;
|
||||
SoccerTeam team = SOCCER_TEAM_BLUE;
|
||||
|
||||
if(race_manager->getKartInfo(index).getSoccerTeam() == SOCCER_TEAM_RED)
|
||||
if (kart_type == RaceManager::KT_AI)
|
||||
{
|
||||
if(index % 2 != 1) posIndex += 1;
|
||||
if (race_manager->getNumPlayers() == 1)
|
||||
{
|
||||
// Make AI even when single player choose a different team
|
||||
if (race_manager->getKartInfo(0).getSoccerTeam() == SOCCER_TEAM_RED)
|
||||
{
|
||||
team = (index % 2 == 0 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED);
|
||||
}
|
||||
else
|
||||
{
|
||||
team = (index % 2 == 0 ? SOCCER_TEAM_RED : SOCCER_TEAM_BLUE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
team = (index % 2 == 0 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED);
|
||||
}
|
||||
m_kart_team_map[index] = team;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(index % 2 != 0) posIndex += 1;
|
||||
int rm_id = index -
|
||||
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
|
||||
|
||||
assert(rm_id >= 0);
|
||||
team = race_manager->getKartInfo(rm_id).getSoccerTeam();
|
||||
m_kart_team_map[index] = team;
|
||||
}
|
||||
|
||||
btTransform init_pos = getStartTransform(posIndex);
|
||||
// Notice: In blender, please set 1,3,5,7... for blue starting position;
|
||||
// 2,4,6,8... for red.
|
||||
if (team == SOCCER_TEAM_BLUE)
|
||||
{
|
||||
pos_index = 1 + 2 * cur_blue;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos_index = 2 + 2 * cur_red;
|
||||
}
|
||||
|
||||
btTransform init_pos = getStartTransform(pos_index - 1);
|
||||
m_kart_position_map[index] = (unsigned)(pos_index - 1);
|
||||
|
||||
AbstractKart *new_kart = new Kart(kart_ident, index, position, init_pos,
|
||||
difficulty);
|
||||
@ -434,4 +430,213 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index,
|
||||
} // createKart
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Updates the m_kart_on_node value of each kart to localize it
|
||||
* on the navigation mesh.
|
||||
*/
|
||||
void SoccerWorld::updateKartNodes()
|
||||
{
|
||||
if (isRaceOver()) return;
|
||||
|
||||
const unsigned int n = getNumKarts();
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
{
|
||||
if (m_karts[i]->isEliminated()) continue;
|
||||
|
||||
m_kart_on_node[i] = BattleGraph::get()->pointToNode(m_kart_on_node[i],
|
||||
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
|
||||
}
|
||||
} // updateKartNodes
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Localize the ball on the navigation mesh.
|
||||
*/
|
||||
void SoccerWorld::updateBallPosition(float dt)
|
||||
{
|
||||
if (isRaceOver()) return;
|
||||
|
||||
m_ball_position = m_ball->getPresentation<TrackObjectPresentationMesh>()
|
||||
->getNode()->getPosition();
|
||||
|
||||
if (m_track->hasNavMesh())
|
||||
{
|
||||
m_ball_on_node = BattleGraph::get()->pointToNode(m_ball_on_node,
|
||||
m_ball_position, true/*ignore_vertical*/);
|
||||
|
||||
if (m_ball_on_node == BattleGraph::UNKNOWN_POLY &&
|
||||
World::getWorld()->getPhase() == RACE_PHASE)
|
||||
{
|
||||
m_ball_invalid_timer += dt;
|
||||
// Reset the ball and karts if out of navmesh after 2 seconds
|
||||
if (m_ball_invalid_timer >= 2.0f)
|
||||
{
|
||||
m_ball_invalid_timer = 0.0f;
|
||||
resetBall();
|
||||
for (unsigned int i = 0; i < m_karts.size(); i++)
|
||||
moveKartAfterRescue(m_karts[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
m_ball_invalid_timer = 0.0f;
|
||||
}
|
||||
|
||||
} // updateBallPosition
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/** Localize two goals on the navigation mesh.
|
||||
*/
|
||||
void SoccerWorld::initGoalNodes()
|
||||
{
|
||||
if (!m_track->hasNavMesh()) return;
|
||||
|
||||
unsigned int n = CheckManager::get()->getCheckStructureCount();
|
||||
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
{
|
||||
CheckGoal* goal =
|
||||
dynamic_cast<CheckGoal*>(CheckManager::get()->getCheckStructure(i));
|
||||
if (goal)
|
||||
{
|
||||
if (goal->getTeam())
|
||||
{
|
||||
m_blue_goal_node = BattleGraph::get()->pointToNode(m_blue_goal_node,
|
||||
goal->convertTo3DCenter(), true/*ignore_vertical*/);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_red_goal_node = BattleGraph::get()->pointToNode(m_red_goal_node,
|
||||
goal->convertTo3DCenter(), true/*ignore_vertical*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // initGoalNodes
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::resetAllNodes()
|
||||
{
|
||||
m_kart_on_node.clear();
|
||||
m_kart_on_node.resize(m_karts.size());
|
||||
for(unsigned int n=0; n<m_karts.size(); n++)
|
||||
m_kart_on_node[n] = BattleGraph::UNKNOWN_POLY;
|
||||
m_ball_on_node = BattleGraph::UNKNOWN_POLY;
|
||||
m_ball_position = Vec3(0, 0, 0);
|
||||
m_red_goal_node = BattleGraph::UNKNOWN_POLY;
|
||||
m_blue_goal_node = BattleGraph::UNKNOWN_POLY;
|
||||
} // resetAllNodes
|
||||
//-----------------------------------------------------------------------------
|
||||
SoccerTeam SoccerWorld::getKartTeam(unsigned int kart_id) const
|
||||
{
|
||||
std::map<int, SoccerTeam>::const_iterator n = m_kart_team_map.find(kart_id);
|
||||
if (n != m_kart_team_map.end())
|
||||
{
|
||||
return n->second;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
Log::warn("SoccerWorld", "Unknown team, using blue default.");
|
||||
return SOCCER_TEAM_BLUE;
|
||||
|
||||
} // getKartTeam
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
bool SoccerWorld::isCorrectGoal(unsigned int kart_id, bool first_goal) const
|
||||
{
|
||||
SoccerTeam team = getKartTeam(kart_id);
|
||||
if (first_goal)
|
||||
{
|
||||
if (team == SOCCER_TEAM_RED)
|
||||
return true;
|
||||
}
|
||||
else if (!first_goal)
|
||||
{
|
||||
if (team == SOCCER_TEAM_BLUE)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // isCorrectGoal
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::updateDefenders()
|
||||
{
|
||||
if (isRaceOver()) return;
|
||||
|
||||
float distance = 99999.9f;
|
||||
int defender = -1;
|
||||
|
||||
// Check for red team
|
||||
for (unsigned int i = 0; i < (unsigned)m_karts.size(); ++i)
|
||||
{
|
||||
if (m_karts[i]->getController()->isPlayerController() ||
|
||||
getKartTeam(m_karts[i]->getWorldKartId()) != SOCCER_TEAM_RED)
|
||||
continue;
|
||||
|
||||
Vec3 d = NavMesh::get()->getNavPoly(this
|
||||
->getGoalNode(SOCCER_TEAM_RED)).getCenter()
|
||||
- m_karts[i]->getXYZ();
|
||||
|
||||
if (d.length_2d() <= distance)
|
||||
{
|
||||
defender = i;
|
||||
distance = d.length_2d();
|
||||
}
|
||||
}
|
||||
if (defender != -1) m_red_defender = defender;
|
||||
|
||||
distance = 99999.9f;
|
||||
defender = -1;
|
||||
|
||||
// Check for blue team
|
||||
for (unsigned int i = 0; i < (unsigned)m_karts.size(); ++i)
|
||||
{
|
||||
if (m_karts[i]->getController()->isPlayerController() ||
|
||||
getKartTeam(m_karts[i]->getWorldKartId()) != SOCCER_TEAM_BLUE)
|
||||
continue;
|
||||
|
||||
Vec3 d = NavMesh::get()->getNavPoly(this
|
||||
->getGoalNode(SOCCER_TEAM_BLUE)).getCenter()
|
||||
- m_karts[i]->getXYZ();
|
||||
|
||||
if (d.length_2d() <= distance)
|
||||
{
|
||||
defender = i;
|
||||
distance = d.length_2d();
|
||||
}
|
||||
}
|
||||
if (defender != -1) m_blue_defender = defender;
|
||||
|
||||
} // updateDefenders
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
int SoccerWorld::getTeamNum(SoccerTeam team) const
|
||||
{
|
||||
int total = 0;
|
||||
if (m_kart_team_map.empty()) return total;
|
||||
|
||||
for (unsigned int i = 0; i < (unsigned)m_karts.size(); ++i)
|
||||
{
|
||||
if (team == getKartTeam(m_karts[i]->getWorldKartId())) total++;
|
||||
}
|
||||
|
||||
return total;
|
||||
} // getTeamNum
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
unsigned int SoccerWorld::getRescuePositionIndex(AbstractKart *kart)
|
||||
{
|
||||
std::map<int, unsigned int>::const_iterator n =
|
||||
m_kart_position_map.find(kart->getWorldKartId());
|
||||
if (n != m_kart_position_map.end())
|
||||
{
|
||||
return n->second;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
Log::warn("SoccerWorld", "Unknown kart, using default starting position.");
|
||||
return 0;
|
||||
} // getRescuePositionIndex
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void SoccerWorld::resetBall()
|
||||
{
|
||||
m_ball->reset();
|
||||
m_ball->getPhysicalObject()->reset();
|
||||
} // resetBall
|
||||
|
@ -23,40 +23,91 @@
|
||||
#include "states_screens/race_gui_base.hpp"
|
||||
#include "karts/abstract_kart.hpp"
|
||||
|
||||
|
||||
#include <IMesh.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define CLEAR_SPAWN_RANGE 5
|
||||
|
||||
class PhysicalObject;
|
||||
class AbstractKart;
|
||||
class Controller;
|
||||
class TrackObject;
|
||||
|
||||
/**
|
||||
* \brief An implementation of World, to provide the soccer game mode
|
||||
/** An implementation of World, to provide the soccer game mode
|
||||
* Notice: In soccer world, true goal means blue, false means red.
|
||||
* \ingroup modes
|
||||
*/
|
||||
class SoccerWorld : public WorldWithRank
|
||||
{
|
||||
public:
|
||||
class ScorerData
|
||||
{
|
||||
public:
|
||||
/** World ID of kart which scores. */
|
||||
unsigned int m_id;
|
||||
/** Whether this goal is socred correctly (identify for own goal). */
|
||||
bool m_correct_goal;
|
||||
}; // ScorerData
|
||||
|
||||
protected:
|
||||
virtual AbstractKart *createKart(const std::string &kart_ident, int index,
|
||||
int local_player_id, int global_player_id,
|
||||
RaceManager::KartType type,
|
||||
PerPlayerDifficulty difficulty);
|
||||
|
||||
private:
|
||||
/** Number of goals each team scored */
|
||||
int m_team_goals[NB_SOCCER_TEAMS];
|
||||
/** Keep a pointer to the track object of soccer ball */
|
||||
TrackObject* m_ball;
|
||||
|
||||
/** Number of goals needed to win */
|
||||
int m_goal_target;
|
||||
bool countDownReachedZero;
|
||||
bool m_count_down_reached_zero;
|
||||
|
||||
/** Whether or not goals can be scored (they are disabled when a point is scored
|
||||
and re-enabled when the next game can be played)*/
|
||||
bool m_can_score_points;
|
||||
SFXBase *m_goal_sound;
|
||||
|
||||
/** Timer for displaying goal text*/
|
||||
float m_goal_timer;
|
||||
int m_lastKartToHitBall;
|
||||
std::vector<int> m_redScorers;
|
||||
std::vector<float> m_redScoreTimes;
|
||||
std::vector<int> m_blueScorers;
|
||||
std::vector<float> m_blueScoreTimes;
|
||||
float m_ball_invalid_timer;
|
||||
int m_ball_hitter;
|
||||
|
||||
/** Goals data of each team scored */
|
||||
int m_red_goal;
|
||||
int m_blue_goal;
|
||||
std::vector<ScorerData> m_red_scorers;
|
||||
std::vector<float> m_red_score_times;
|
||||
std::vector<ScorerData> m_blue_scorers;
|
||||
std::vector<float> m_blue_score_times;
|
||||
|
||||
std::map<int, SoccerTeam> m_kart_team_map;
|
||||
std::map<int, unsigned int> m_kart_position_map;
|
||||
|
||||
/** Data generated from navmesh */
|
||||
std::vector<int> m_kart_on_node;
|
||||
int m_ball_on_node;
|
||||
Vec3 m_ball_position;
|
||||
int m_red_goal_node;
|
||||
int m_blue_goal_node;
|
||||
|
||||
int m_red_defender;
|
||||
int m_blue_defender;
|
||||
|
||||
/** Set the team for the karts */
|
||||
void initKartList();
|
||||
/** Function to init the locations of two goals on the polygon map */
|
||||
void initGoalNodes();
|
||||
/** Function to update the locations of all karts on the polygon map */
|
||||
void updateKartNodes();
|
||||
/** Function to update the location the ball on the polygon map */
|
||||
void updateBallPosition(float dt);
|
||||
/** Clean up */
|
||||
void resetAllNodes();
|
||||
/** Reset the ball to original starting position. */
|
||||
void resetBall();
|
||||
/** Function to update the AI which is the closest to its goal to defend. */
|
||||
void updateDefenders();
|
||||
/** Get number of teammates in a team, used by starting position assign. */
|
||||
int getTeamNum(SoccerTeam team) const;
|
||||
|
||||
public:
|
||||
|
||||
SoccerWorld();
|
||||
@ -72,41 +123,61 @@ public:
|
||||
// overriding World methods
|
||||
virtual void reset();
|
||||
|
||||
virtual unsigned int getRescuePositionIndex(AbstractKart *kart) OVERRIDE;
|
||||
|
||||
virtual bool useFastMusicNearEnd() const { return false; }
|
||||
virtual void getKartsDisplayInfo(
|
||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info);
|
||||
int getScore(unsigned int i);
|
||||
std::vector<RaceGUIBase::KartIconDisplayInfo> *info) {}
|
||||
|
||||
virtual bool raceHasLaps() { return false; }
|
||||
|
||||
virtual const std::string& getIdent() const;
|
||||
|
||||
virtual void update(float dt);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
void onCheckGoalTriggered(bool first_goal);
|
||||
int getTeamLeader(unsigned int i);
|
||||
void setLastKartTohitBall(unsigned int kartId);
|
||||
std::vector<int> getScorers(unsigned int team)
|
||||
// ------------------------------------------------------------------------
|
||||
void setBallHitter(unsigned int kart_id);
|
||||
// ------------------------------------------------------------------------
|
||||
/** Get the soccer result of kart in soccer world (including AIs) */
|
||||
bool getKartSoccerResult(unsigned int kart_id) const;
|
||||
// ------------------------------------------------------------------------
|
||||
/** Get the team of kart in soccer world (including AIs) */
|
||||
SoccerTeam getKartTeam(unsigned int kart_id) const;
|
||||
// ------------------------------------------------------------------------
|
||||
const int getScore(SoccerTeam team) const
|
||||
{ return (team == SOCCER_TEAM_BLUE ? m_blue_goal : m_red_goal); }
|
||||
// ------------------------------------------------------------------------
|
||||
const std::vector<ScorerData>& getScorers(SoccerTeam team) const
|
||||
{ return (team == SOCCER_TEAM_BLUE ? m_blue_scorers : m_red_scorers); }
|
||||
// ------------------------------------------------------------------------
|
||||
const std::vector<float>& getScoreTimes(SoccerTeam team) const
|
||||
{
|
||||
if(team == 0)
|
||||
return m_redScorers;
|
||||
else
|
||||
return m_blueScorers;
|
||||
return (team == SOCCER_TEAM_BLUE ?
|
||||
m_blue_score_times : m_red_score_times);
|
||||
}
|
||||
std::vector<float> getScoreTimes(unsigned int team)
|
||||
// ------------------------------------------------------------------------
|
||||
const int& getKartNode(unsigned int kart_id) const
|
||||
{ return m_kart_on_node[kart_id]; }
|
||||
// ------------------------------------------------------------------------
|
||||
const int& getBallNode() const
|
||||
{ return m_ball_on_node; }
|
||||
// ------------------------------------------------------------------------
|
||||
const Vec3& getBallPosition() const
|
||||
{ return m_ball_position; }
|
||||
// ------------------------------------------------------------------------
|
||||
const int& getGoalNode(SoccerTeam team) const
|
||||
{
|
||||
if(team == 0)
|
||||
return m_redScoreTimes;
|
||||
else
|
||||
return m_blueScoreTimes;
|
||||
return (team == SOCCER_TEAM_BLUE ? m_blue_goal_node : m_red_goal_node);
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
bool isCorrectGoal(unsigned int kart_id, bool first_goal) const;
|
||||
// ------------------------------------------------------------------------
|
||||
const int& getDefender(SoccerTeam team) const
|
||||
{
|
||||
return (team == SOCCER_TEAM_BLUE ? m_blue_defender : m_red_defender);
|
||||
}
|
||||
|
||||
private:
|
||||
void initKartList();
|
||||
protected:
|
||||
virtual AbstractKart *createKart(const std::string &kart_ident, int index,
|
||||
int local_player_id, int global_player_id,
|
||||
RaceManager::KartType type,
|
||||
PerPlayerDifficulty difficulty);
|
||||
}; // SoccerWorld
|
||||
|
||||
|
||||
|
@ -453,69 +453,9 @@ void ThreeStrikesBattle::updateKartNodes()
|
||||
{
|
||||
if (m_karts[i]->isEliminated()) continue;
|
||||
|
||||
const int saved_current_node = m_kart_info[i].m_on_node;
|
||||
|
||||
if (saved_current_node == BattleGraph::UNKNOWN_POLY)
|
||||
{
|
||||
// Try all nodes in the battle graph
|
||||
bool found = false;
|
||||
unsigned int node = 0;
|
||||
while (!found && node < BattleGraph::get()->getNumNodes())
|
||||
{
|
||||
const NavPoly& p_all = BattleGraph::get()->getPolyOfNode(node);
|
||||
if ((p_all.pointInPoly(m_karts[i]->getXYZ())))
|
||||
{
|
||||
m_kart_info[i].m_on_node = node;
|
||||
found = true;
|
||||
}
|
||||
node++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the kart is still on the same node
|
||||
const NavPoly& p_cur = BattleGraph::get()
|
||||
->getPolyOfNode(saved_current_node);
|
||||
if (p_cur.pointInPoly(m_karts[i]->getXYZ())) continue;
|
||||
|
||||
// If not then check all adjacent polys
|
||||
const std::vector<int>& adjacents = NavMesh::get()
|
||||
->getAdjacentPolys(saved_current_node);
|
||||
|
||||
// Set current node to unknown so that if no adjacent polygons,
|
||||
// we look everywhere the next time updateKartNodes is called.
|
||||
// This is useful in cases when you are "teleported"
|
||||
// to some other polygons, ex. rescue
|
||||
m_kart_info[i].m_on_node = BattleGraph::UNKNOWN_POLY;
|
||||
|
||||
bool found = false;
|
||||
unsigned int num = 0;
|
||||
while (!found && num < adjacents.size())
|
||||
{
|
||||
const NavPoly& p_temp =
|
||||
BattleGraph::get()->getPolyOfNode(adjacents[num]);
|
||||
if (p_temp.pointInPoly(m_karts[i]->getXYZ()))
|
||||
{
|
||||
m_kart_info[i].m_on_node = adjacents[num];
|
||||
found = true;
|
||||
}
|
||||
num++;
|
||||
}
|
||||
|
||||
// Current node is still unkown
|
||||
if (m_kart_info[i].m_on_node == BattleGraph::UNKNOWN_POLY)
|
||||
{
|
||||
// Calculated distance from saved node to current position,
|
||||
// if it's close enough than use the saved node anyway, it
|
||||
// may happen when the kart stays on the edge of obstacles
|
||||
const NavPoly& p = BattleGraph::get()
|
||||
->getPolyOfNode(saved_current_node);
|
||||
const float dist = (p.getCenter() - m_karts[i]->getXYZ()).length_2d();
|
||||
|
||||
if (dist < 3.0f)
|
||||
m_kart_info[i].m_on_node = saved_current_node;
|
||||
}
|
||||
}
|
||||
m_kart_info[i].m_on_node = BattleGraph::get()
|
||||
->pointToNode(m_kart_info[i].m_on_node,
|
||||
m_karts[i]->getXYZ(), false/*ignore_vertical*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ private:
|
||||
|
||||
PtrVector<TrackObject, REF> m_tires;
|
||||
|
||||
/** Function to udpate the locations of all karts on the polygon map */
|
||||
/** Function to update the locations of all karts on the polygon map */
|
||||
void updateKartNodes();
|
||||
|
||||
public:
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "input/keyboard_device.hpp"
|
||||
#include "items/projectile_manager.hpp"
|
||||
#include "karts/controller/battle_ai.hpp"
|
||||
#include "karts/controller/soccer_ai.hpp"
|
||||
#include "karts/controller/end_controller.hpp"
|
||||
#include "karts/controller/local_player_controller.hpp"
|
||||
#include "karts/controller/skidding_ai.hpp"
|
||||
@ -351,6 +352,8 @@ Controller* World::loadAIController(AbstractKart *kart)
|
||||
|
||||
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
|
||||
turn=1;
|
||||
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
|
||||
turn=2;
|
||||
// If different AIs should be used, adjust turn (or switch randomly
|
||||
// or dependent on difficulty)
|
||||
switch(turn)
|
||||
@ -361,6 +364,9 @@ Controller* World::loadAIController(AbstractKart *kart)
|
||||
case 1:
|
||||
controller = new BattleAI(kart);
|
||||
break;
|
||||
case 2:
|
||||
controller = new SoccerAI(kart);
|
||||
break;
|
||||
default:
|
||||
Log::warn("[World]", "Unknown AI, using default.");
|
||||
controller = new SkiddingAI(kart);
|
||||
|
@ -31,7 +31,6 @@ enum SoccerTeam
|
||||
SOCCER_TEAM_NONE=-1,
|
||||
SOCCER_TEAM_RED=0,
|
||||
SOCCER_TEAM_BLUE=1,
|
||||
NB_SOCCER_TEAMS
|
||||
};
|
||||
|
||||
/** Game difficulty per player. */
|
||||
|
@ -226,7 +226,7 @@ void Physics::update(float dt)
|
||||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
||||
{
|
||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
||||
soccerWorld->setLastKartTohitBall(kartId);
|
||||
soccerWorld->setBallHitter(kartId);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -288,7 +288,7 @@ void Physics::update(float dt)
|
||||
{
|
||||
int kartId = p->getUserPointer(0)->getPointerFlyable()->getOwnerId();
|
||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
||||
soccerWorld->setLastKartTohitBall(kartId);
|
||||
soccerWorld->setBallHitter(kartId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ public:
|
||||
case MINOR_MODE_FOLLOW_LEADER: return true;
|
||||
case MINOR_MODE_3_STRIKES: return true;
|
||||
case MINOR_MODE_EASTER_EGG: return false;
|
||||
case MINOR_MODE_SOCCER: return false;
|
||||
case MINOR_MODE_SOCCER: return true;
|
||||
default: assert(false); return false;
|
||||
}
|
||||
} // hasAI
|
||||
@ -611,10 +611,21 @@ public:
|
||||
const int id = (int)m_minor_mode;
|
||||
// This uses the numerical id of the mode, see the macros
|
||||
// LINEAR_RACE and BATTLE_ARENA above for exact meaning.
|
||||
if (id >= 2000) return true;
|
||||
if (id >= 2000 && id != 2001) return true;
|
||||
else return false;
|
||||
} // isBattleMode
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
/** \brief Returns true if the current mode is a soccer mode. */
|
||||
bool isSoccerMode()
|
||||
{
|
||||
const int id = (int)m_minor_mode;
|
||||
// This uses the numerical id of the mode, see the macros
|
||||
// LINEAR_RACE and BATTLE_ARENA above for exact meaning.
|
||||
if (id == 2001) return true;
|
||||
else return false;
|
||||
} // isSoccerMode
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
bool isTutorialMode()
|
||||
{
|
||||
@ -624,7 +635,7 @@ public:
|
||||
/** \brief Returns true if the current mode has laps. */
|
||||
bool modeHasLaps()
|
||||
{
|
||||
if (isBattleMode()) return false;
|
||||
if (isBattleMode() || isSoccerMode()) return false;
|
||||
const int id = (int)m_minor_mode;
|
||||
// See meaning of IDs above
|
||||
const int answer = (id-1000)/100;
|
||||
|
@ -95,7 +95,9 @@ void ArenasScreen::beforeAddingWidget()
|
||||
Track* temp = track_manager->getTrack(n);
|
||||
if (soccer_mode)
|
||||
{
|
||||
if(temp->isSoccer())
|
||||
if(temp->isSoccer() && (temp->hasNavMesh() ||
|
||||
race_manager->getNumLocalPlayers() > 1 ||
|
||||
UserConfigParams::m_artist_debug_mode))
|
||||
num_of_arenas++;
|
||||
}
|
||||
else
|
||||
@ -239,7 +241,18 @@ void ArenasScreen::buildTrackList()
|
||||
Track* curr = track_manager->getTrack(n);
|
||||
if (soccer_mode)
|
||||
{
|
||||
if(!curr->isSoccer()) continue;
|
||||
if(curr->isSoccer() && curr->hasNavMesh() && !arenas_have_navmesh)
|
||||
arenas_have_navmesh = true;
|
||||
|
||||
if(!curr->isSoccer() ||
|
||||
(!(curr->hasNavMesh() ||
|
||||
race_manager->getNumLocalPlayers() > 1 ||
|
||||
UserConfigParams::m_artist_debug_mode)))
|
||||
{
|
||||
if (curr->isSoccer())
|
||||
m_unsupported_arena.insert(n);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -280,7 +293,18 @@ void ArenasScreen::buildTrackList()
|
||||
Track* curr = track_manager->getTrack(currArenas[n]);
|
||||
if (soccer_mode)
|
||||
{
|
||||
if(!curr->isSoccer()) continue;
|
||||
if(curr->isSoccer() && curr->hasNavMesh() && !arenas_have_navmesh)
|
||||
arenas_have_navmesh = true;
|
||||
|
||||
if(!curr->isSoccer() ||
|
||||
(!(curr->hasNavMesh() ||
|
||||
race_manager->getNumLocalPlayers() > 1 ||
|
||||
UserConfigParams::m_artist_debug_mode)))
|
||||
{
|
||||
if (curr->isSoccer())
|
||||
m_unsupported_arena.insert(currArenas[n]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -48,6 +48,7 @@ using namespace irr;
|
||||
#include "modes/soccer_world.hpp"
|
||||
#include "race/race_manager.hpp"
|
||||
#include "tracks/track.hpp"
|
||||
#include "tracks/track_object_manager.hpp"
|
||||
#include "utils/constants.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
#include "utils/translation.hpp"
|
||||
@ -224,55 +225,46 @@ void RaceGUI::renderPlayerView(const Camera *camera, float dt)
|
||||
*/
|
||||
void RaceGUI::drawScores()
|
||||
{
|
||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
||||
int offsetY = 5;
|
||||
int offsetX = 5;
|
||||
gui::ScalableFont* font = GUIEngine::getFont();
|
||||
SoccerWorld* sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
int offset_y = 5;
|
||||
int offset_x = 5;
|
||||
gui::ScalableFont* font = GUIEngine::getTitleFont();
|
||||
static video::SColor color = video::SColor(255,255,255,255);
|
||||
|
||||
//Draw kart icons above score(denoting teams)
|
||||
//Draw two teams score
|
||||
irr::video::ITexture *red_team = irr_driver->getTexture(FileManager::GUI,
|
||||
"soccer_ball_red.png");
|
||||
irr::video::ITexture *blue_team = irr_driver->getTexture(FileManager::GUI,
|
||||
"soccer_ball_blue.png");
|
||||
"soccer_ball_blue.png");
|
||||
irr::video::ITexture *team_icon = red_team;
|
||||
|
||||
int numLeader = 1;
|
||||
for(unsigned int i=0; i<soccerWorld->getNumKarts(); i++)
|
||||
for(unsigned int i=0; i<2; i++)
|
||||
{
|
||||
int j = soccerWorld->getTeamLeader(i);
|
||||
if(j < 0) break;
|
||||
core::recti position(offset_x, offset_y,
|
||||
offset_x + 2*m_minimap_player_size, offset_y + 2*m_minimap_player_size);
|
||||
|
||||
AbstractKart* kart = soccerWorld->getKart(i);
|
||||
video::ITexture* icon = kart->getKartProperties()->getMinimapIcon();
|
||||
core::rect<s32> source(core::position2di(0, 0), icon->getSize());
|
||||
core::recti position(offsetX, offsetY,
|
||||
offsetX + 2*m_minimap_player_size, offsetY + 2*m_minimap_player_size);
|
||||
draw2DImage(icon, position, source,
|
||||
NULL, NULL, true);
|
||||
core::stringw score = StringUtils::toWString(soccerWorld->getScore(i));
|
||||
core::stringw score = StringUtils::toWString(sw->getScore((SoccerTeam)i));
|
||||
int string_height =
|
||||
GUIEngine::getFont()->getDimension(score.c_str()).Height;
|
||||
core::recti pos(position.UpperLeftCorner.X + 5,
|
||||
position.LowerRightCorner.Y + offsetY,
|
||||
position.LowerRightCorner.Y + offset_y,
|
||||
position.LowerRightCorner.X,
|
||||
position.LowerRightCorner.Y + string_height);
|
||||
|
||||
font->draw(score.c_str(),pos,color);
|
||||
|
||||
if (numLeader == 2)
|
||||
if (i == 1)
|
||||
{
|
||||
team_icon = blue_team;
|
||||
}
|
||||
core::rect<s32> indicatorPos(offsetX, offsetY,
|
||||
offsetX + (int)(m_minimap_player_size/1.25f),
|
||||
offsetY + (int)(m_minimap_player_size/1.25f));
|
||||
core::rect<s32> sourceRect(core::position2d<s32>(0,0),
|
||||
core::rect<s32> indicator_pos(offset_x, offset_y,
|
||||
offset_x + (int)(m_minimap_player_size*2),
|
||||
offset_y + (int)(m_minimap_player_size*2));
|
||||
core::rect<s32> source_rect(core::position2d<s32>(0,0),
|
||||
team_icon->getSize());
|
||||
draw2DImage(team_icon,indicatorPos,sourceRect,
|
||||
draw2DImage(team_icon,indicator_pos,source_rect,
|
||||
NULL,NULL,true);
|
||||
numLeader++;
|
||||
offsetX += position.LowerRightCorner.X;
|
||||
offset_x += position.LowerRightCorner.X + 30;
|
||||
}
|
||||
} // drawScores
|
||||
|
||||
@ -295,7 +287,8 @@ void RaceGUI::drawGlobalTimer()
|
||||
bool use_digit_font = true;
|
||||
|
||||
float elapsed_time = World::getWorld()->getTime();
|
||||
if (!race_manager->hasTimeTarget())
|
||||
if (!race_manager->hasTimeTarget() || race_manager
|
||||
->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
|
||||
{
|
||||
sw = core::stringw (
|
||||
StringUtils::timeToString(elapsed_time).c_str() );
|
||||
@ -329,7 +322,8 @@ void RaceGUI::drawGlobalTimer()
|
||||
}
|
||||
|
||||
gui::ScalableFont* font = (use_digit_font ? GUIEngine::getHighresDigitFont() : GUIEngine::getFont());
|
||||
font->setShadow(video::SColor(255, 128, 0, 0));
|
||||
if (use_digit_font)
|
||||
font->setShadow(video::SColor(255, 128, 0, 0));
|
||||
font->setScale(1.0f);
|
||||
font->draw(sw.c_str(), pos, time_color, false, false, NULL,
|
||||
true /* ignore RTL */);
|
||||
@ -343,8 +337,8 @@ void RaceGUI::drawGlobalMiniMap()
|
||||
{
|
||||
World *world = World::getWorld();
|
||||
// draw a map when arena has a navigation mesh.
|
||||
if ((world->getTrack()->isArena() && !(world->getTrack()->hasNavMesh())) ||
|
||||
world->getTrack()->isSoccer())
|
||||
if ((world->getTrack()->isArena() || world->getTrack()->isSoccer()) &&
|
||||
!(world->getTrack()->hasNavMesh()))
|
||||
return;
|
||||
|
||||
const video::ITexture *old_rtt_mini_map = world->getTrack()->getOldRttMiniMap();
|
||||
@ -393,6 +387,23 @@ void RaceGUI::drawGlobalMiniMap()
|
||||
lower_y -(int)(draw_at.getY()-marker_half_size));
|
||||
draw2DImage(icon, position, source, NULL, NULL, true);
|
||||
} // for i<getNumKarts
|
||||
|
||||
SoccerWorld *sw = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
if (sw)
|
||||
{
|
||||
Vec3 draw_at;
|
||||
world->getTrack()->mapPoint2MiniMap(sw->getBallPosition(), &draw_at);
|
||||
video::ITexture* icon =
|
||||
irr_driver->getTexture(FileManager::GUI, "soccer_ball_normal.png");
|
||||
|
||||
core::rect<s32> source(core::position2di(0, 0), icon->getSize());
|
||||
core::rect<s32> position(m_map_left+(int)(draw_at.getX()-(m_minimap_ai_size>>2)),
|
||||
lower_y -(int)(draw_at.getY()+(m_minimap_ai_size>>2)),
|
||||
m_map_left+(int)(draw_at.getX()+(m_minimap_ai_size>>2)),
|
||||
lower_y -(int)(draw_at.getY()-(m_minimap_ai_size>>2)));
|
||||
draw2DImage(icon, position, source, NULL, NULL, true);
|
||||
}
|
||||
|
||||
} // drawGlobalMiniMap
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -297,7 +297,7 @@ void RaceGUIBase::cleanupMessages(const float dt)
|
||||
/** Draws the powerup icons on the screen (called once for each player).
|
||||
* \param kart The kart for which to draw the powerup icons.
|
||||
* \param viewport The viewport into which to draw the icons.
|
||||
* \param scaling The scaling to use when draing the icons.
|
||||
* \param scaling The scaling to use when drawing the icons.
|
||||
*/
|
||||
void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
||||
const core::recti &viewport,
|
||||
@ -309,8 +309,13 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
||||
|| kart->hasFinishedRace()) return;
|
||||
|
||||
int n = kart->getPowerup()->getNum() ;
|
||||
int many_powerups = 0;
|
||||
if (n<1) return; // shouldn't happen, but just in case
|
||||
if (n>5) n=5; // Display at most 5 items
|
||||
if (n>5)
|
||||
{
|
||||
many_powerups = n;
|
||||
n = 1;
|
||||
}
|
||||
|
||||
int nSize = (int)(64.0f*std::min(scaling.X, scaling.Y));
|
||||
|
||||
@ -320,6 +325,8 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
||||
- (n * itemSpacing)/2;
|
||||
int y1 = viewport.UpperLeftCorner.Y + (int)(20 * scaling.Y);
|
||||
|
||||
int x2 = 0;
|
||||
|
||||
assert(powerup != NULL);
|
||||
assert(powerup->getIcon() != NULL);
|
||||
video::ITexture *t=powerup->getIcon()->getTexture();
|
||||
@ -328,11 +335,21 @@ void RaceGUIBase::drawPowerupIcons(const AbstractKart* kart,
|
||||
|
||||
for ( int i = 0 ; i < n ; i++ )
|
||||
{
|
||||
int x2 = (int)(x1+i*itemSpacing);
|
||||
x2 = (int)(x1+i*itemSpacing);
|
||||
core::rect<s32> pos(x2, y1, x2+nSize, y1+nSize);
|
||||
draw2DImage(t, pos, rect, NULL,
|
||||
NULL, true);
|
||||
} // for i
|
||||
|
||||
if (many_powerups > 0)
|
||||
{
|
||||
gui::ScalableFont* font = GUIEngine::getHighresDigitFont();
|
||||
core::rect<s32> pos(x2+nSize, y1, x2+nSize+nSize, y1+nSize);
|
||||
font->setScale(1.5f);
|
||||
font->draw(StringUtils::toWString(many_powerups)+L"x",
|
||||
pos, video::SColor(255, 255, 255, 255));
|
||||
font->setScale(1.0f);
|
||||
}
|
||||
} // drawPowerupIcons
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -439,11 +456,15 @@ void RaceGUIBase::drawGlobalMusicDescription()
|
||||
gui::IGUIFont* font = GUIEngine::getFont();
|
||||
|
||||
float race_time = World::getWorld()->getTime();
|
||||
// In follow the leader the clock counts backwards, so convert the
|
||||
// In the modes that the clock counts backwards, convert the
|
||||
// countdown time to time since start:
|
||||
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_FOLLOW_LEADER)
|
||||
race_time = ((FollowTheLeaderRace*)World::getWorld())->getClockStartTime()
|
||||
- race_time;
|
||||
else if (race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER &&
|
||||
race_manager->hasTimeTarget())
|
||||
race_time = race_manager->getTimeTarget() - World::getWorld()->getTime();
|
||||
|
||||
// ---- Manage pulsing effect
|
||||
// 3.0 is the duration of ready/set (TODO: don't hardcode)
|
||||
float timeProgression = (float)(race_time) /
|
||||
|
@ -475,7 +475,15 @@ void RaceResultGUI::determineTableLayout()
|
||||
// Save a pointer to the current row_info entry
|
||||
RowInfo *ri = &(m_all_row_infos[position-first_position]);
|
||||
ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
|
||||
ri->m_kart_name = translations->fribidize(kart->getName());
|
||||
|
||||
// Identify Human player, if so display real name other than kart name
|
||||
const int rm_id = kart->getWorldKartId() -
|
||||
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
|
||||
|
||||
if (rm_id >= 0)
|
||||
ri->m_kart_name = race_manager->getKartInfo(rm_id).getPlayerName();
|
||||
else
|
||||
ri->m_kart_name = translations->fribidize(kart->getName());
|
||||
|
||||
video::ITexture *icon =
|
||||
kart->getKartProperties()->getIconMaterial()->getTexture();
|
||||
@ -840,7 +848,15 @@ void RaceResultGUI::determineGPLayout()
|
||||
RowInfo *ri = &(m_all_row_infos[rank]);
|
||||
ri->m_kart_icon =
|
||||
kart->getKartProperties()->getIconMaterial()->getTexture();
|
||||
ri->m_kart_name = translations->fribidize(kart->getName());
|
||||
|
||||
const int rm_id = kart_id -
|
||||
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
|
||||
|
||||
if (rm_id >= 0)
|
||||
ri->m_kart_name = race_manager->getKartInfo(rm_id).getPlayerName();
|
||||
else
|
||||
ri->m_kart_name = translations->fribidize(kart->getName());
|
||||
|
||||
ri->m_is_player_kart = kart->getController()->isLocalPlayerController();
|
||||
|
||||
// In FTL karts do have a time, which is shown even when the kart
|
||||
@ -977,136 +993,171 @@ void RaceResultGUI::displaySoccerResults()
|
||||
{
|
||||
|
||||
//Draw win text
|
||||
core::stringw resultText;
|
||||
core::stringw result_text;
|
||||
static video::SColor color = video::SColor(255, 255, 255, 255);
|
||||
gui::IGUIFont* font = GUIEngine::getTitleFont();
|
||||
int currX = UserConfigParams::m_width/2;
|
||||
int current_x = UserConfigParams::m_width/2;
|
||||
RowInfo *ri = &(m_all_row_infos[0]);
|
||||
int currY = (int)ri->m_y_pos;
|
||||
SoccerWorld* soccerWorld = (SoccerWorld*)World::getWorld();
|
||||
int teamScore[2] = {soccerWorld->getScore(0), soccerWorld->getScore(1)};
|
||||
int current_y = (int)ri->m_y_pos;
|
||||
SoccerWorld* sw = (SoccerWorld*)World::getWorld();
|
||||
const int red_score = sw->getScore(SOCCER_TEAM_RED);
|
||||
const int blue_score = sw->getScore(SOCCER_TEAM_BLUE);
|
||||
|
||||
GUIEngine::Widget *table_area = getWidget("result-table");
|
||||
int height = table_area->m_h + table_area->m_y;
|
||||
|
||||
if(teamScore[0] > teamScore[1])
|
||||
if(red_score > blue_score)
|
||||
{
|
||||
resultText = _("Red Team Wins");
|
||||
result_text = _("Red Team Wins");
|
||||
}
|
||||
else if(teamScore[1] > teamScore[0])
|
||||
else if(blue_score > red_score)
|
||||
{
|
||||
resultText = _("Blue Team Wins");
|
||||
result_text = _("Blue Team Wins");
|
||||
}
|
||||
else
|
||||
{
|
||||
//Cannot really happen now. Only in time limited matches.
|
||||
resultText = _("It's a draw");
|
||||
result_text = _("It's a draw");
|
||||
}
|
||||
core::rect<s32> pos(currX, currY, currX, currY);
|
||||
font->draw(resultText.c_str(), pos, color, true, true);
|
||||
core::rect<s32> pos(current_x, current_y, current_x, current_y);
|
||||
font->draw(result_text.c_str(), pos, color, true, true);
|
||||
|
||||
core::dimension2du rect = m_font->getDimension(resultText.c_str());
|
||||
core::dimension2du rect = m_font->getDimension(result_text.c_str());
|
||||
|
||||
//Draw team scores:
|
||||
currY += rect.Height;
|
||||
currX /= 2;
|
||||
irr::video::ITexture* redTeamIcon = irr_driver->getTexture(FileManager::GUI,
|
||||
current_y += rect.Height;
|
||||
current_x /= 2;
|
||||
irr::video::ITexture* red_icon = irr_driver->getTexture(FileManager::GUI,
|
||||
"soccer_ball_red.png");
|
||||
irr::video::ITexture* blueTeamIcon = irr_driver->getTexture(FileManager::GUI,
|
||||
"soccer_ball_blue.png");
|
||||
irr::video::ITexture* blue_icon = irr_driver->getTexture(FileManager::GUI,
|
||||
"soccer_ball_blue.png");
|
||||
|
||||
core::recti sourceRect(core::vector2di(0,0), redTeamIcon->getSize());
|
||||
core::recti destRect(currX, currY, currX+redTeamIcon->getSize().Width/2,
|
||||
currY+redTeamIcon->getSize().Height/2);
|
||||
draw2DImage(redTeamIcon, destRect,sourceRect,
|
||||
core::recti source_rect(core::vector2di(0,0), red_icon->getSize());
|
||||
core::recti dest_rect(current_x, current_y, current_x+red_icon->getSize().Width/2,
|
||||
current_y+red_icon->getSize().Height/2);
|
||||
draw2DImage(red_icon, dest_rect,source_rect,
|
||||
NULL,NULL, true);
|
||||
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
|
||||
destRect = core::recti(currX, currY, currX+redTeamIcon->getSize().Width/2,
|
||||
currY+redTeamIcon->getSize().Height/2);
|
||||
draw2DImage(blueTeamIcon,destRect,sourceRect,
|
||||
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
|
||||
dest_rect = core::recti(current_x, current_y, current_x+red_icon->getSize().Width/2,
|
||||
current_y+red_icon->getSize().Height/2);
|
||||
draw2DImage(blue_icon,dest_rect,source_rect,
|
||||
NULL, NULL, true);
|
||||
|
||||
resultText = StringUtils::toWString(teamScore[1]);
|
||||
rect = m_font->getDimension(resultText.c_str());
|
||||
currX += redTeamIcon->getSize().Width/4;
|
||||
currY += redTeamIcon->getSize().Height/2 + rect.Height/4;
|
||||
pos = core::rect<s32>(currX, currY, currX, currY);
|
||||
result_text = StringUtils::toWString(blue_score);
|
||||
rect = m_font->getDimension(result_text.c_str());
|
||||
current_x += red_icon->getSize().Width/4;
|
||||
current_y += red_icon->getSize().Height/2 + rect.Height/4;
|
||||
pos = core::rect<s32>(current_x, current_y, current_x, current_y);
|
||||
color = video::SColor(255,255,255,255);
|
||||
font->draw(resultText.c_str(), pos, color, true, false);
|
||||
font->draw(result_text.c_str(), pos, color, true, false);
|
||||
|
||||
currX -= UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
|
||||
resultText = StringUtils::toWString(teamScore[0]);
|
||||
pos = core::rect<s32>(currX,currY,currX,currY);
|
||||
font->draw(resultText.c_str(), pos, color, true, false);
|
||||
current_x -= UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
|
||||
result_text = StringUtils::toWString(red_score);
|
||||
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
|
||||
font->draw(result_text.c_str(), pos, color, true, false);
|
||||
|
||||
int centerX = UserConfigParams::m_width/2;
|
||||
pos = core::rect<s32>(centerX, currY, centerX, currY);
|
||||
int center_x = UserConfigParams::m_width/2;
|
||||
pos = core::rect<s32>(center_x, current_y, center_x, current_y);
|
||||
font->draw("-", pos, color, true, false);
|
||||
|
||||
//Draw goal scorers:
|
||||
//The red scorers:
|
||||
currY += rect.Height/2 + rect.Height/4;
|
||||
current_y += rect.Height/2 + rect.Height/4;
|
||||
font = GUIEngine::getSmallFont();
|
||||
std::vector<int> scorers = soccerWorld->getScorers(0);
|
||||
std::vector<float> scoreTimes = soccerWorld->getScoreTimes(0);
|
||||
irr::video::ITexture* scorerIcon;
|
||||
std::vector<SoccerWorld::ScorerData> scorers = sw->getScorers(SOCCER_TEAM_RED);
|
||||
std::vector<float> score_times = sw->getScoreTimes(SOCCER_TEAM_RED);
|
||||
irr::video::ITexture* scorer_icon;
|
||||
|
||||
int prevY = currY;
|
||||
int prev_y = current_y;
|
||||
for(unsigned int i=0; i<scorers.size(); i++)
|
||||
{
|
||||
resultText = soccerWorld->getKart(scorers.at(i))->
|
||||
getKartProperties()->getName();
|
||||
resultText.append(" ");
|
||||
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
|
||||
rect = m_font->getDimension(resultText.c_str());
|
||||
const bool own_goal = !(scorers.at(i).m_correct_goal);
|
||||
|
||||
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
|
||||
currY += (height-prevY)/((short)scorers.size()+1);
|
||||
const int kart_id = scorers.at(i).m_id;
|
||||
const int rm_id = kart_id -
|
||||
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
|
||||
|
||||
if (rm_id >= 0)
|
||||
result_text = race_manager->getKartInfo(rm_id).getPlayerName();
|
||||
else
|
||||
currY += rect.Height;
|
||||
result_text = sw->getKart(kart_id)->
|
||||
getKartProperties()->getName();
|
||||
|
||||
if(currY > height) break;
|
||||
if (own_goal)
|
||||
{
|
||||
result_text.append(" ");
|
||||
result_text.append( _("(Own Goal)") );
|
||||
}
|
||||
|
||||
pos = core::rect<s32>(currX,currY,currX,currY);
|
||||
font->draw(resultText,pos, color, true, false);
|
||||
scorerIcon = soccerWorld->getKart(scorers.at(i))
|
||||
result_text.append(" ");
|
||||
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
|
||||
rect = m_font->getDimension(result_text.c_str());
|
||||
|
||||
if(height-prev_y < ((short)scorers.size()+1)*(short)rect.Height)
|
||||
current_y += (height-prev_y)/((short)scorers.size()+1);
|
||||
else
|
||||
current_y += rect.Height;
|
||||
|
||||
if(current_y > height) break;
|
||||
|
||||
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
|
||||
font->draw(result_text, pos, (own_goal ?
|
||||
video::SColor(255, 255, 0, 0) : color), true, false);
|
||||
scorer_icon = sw->getKart(scorers.at(i).m_id)
|
||||
->getKartProperties()->getIconMaterial()->getTexture();
|
||||
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
|
||||
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
|
||||
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
|
||||
draw2DImage(scorerIcon, destRect, sourceRect,
|
||||
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
|
||||
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
|
||||
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
|
||||
draw2DImage(scorer_icon, dest_rect, source_rect,
|
||||
NULL, NULL, true);
|
||||
}
|
||||
|
||||
//The blue scorers:
|
||||
currY = prevY;
|
||||
currX += UserConfigParams::m_width/2 - redTeamIcon->getSize().Width/2;
|
||||
scorers = soccerWorld->getScorers(1);
|
||||
scoreTimes = soccerWorld->getScoreTimes(1);
|
||||
current_y = prev_y;
|
||||
current_x += UserConfigParams::m_width/2 - red_icon->getSize().Width/2;
|
||||
scorers = sw->getScorers(SOCCER_TEAM_BLUE);
|
||||
score_times = sw->getScoreTimes(SOCCER_TEAM_BLUE);
|
||||
for(unsigned int i=0; i<scorers.size(); i++)
|
||||
{
|
||||
resultText = soccerWorld->getKart(scorers.at(i))->
|
||||
getKartProperties()->getName();
|
||||
resultText.append(" ");
|
||||
resultText.append(StringUtils::timeToString(scoreTimes.at(i)).c_str());
|
||||
rect = m_font->getDimension(resultText.c_str());
|
||||
const bool own_goal = !(scorers.at(i).m_correct_goal);
|
||||
|
||||
if(height-prevY < ((short)scorers.size()+1)*(short)rect.Height)
|
||||
currY += (height-prevY)/((short)scorers.size()+1);
|
||||
const int kart_id = scorers.at(i).m_id;
|
||||
const int rm_id = kart_id -
|
||||
(race_manager->getNumberOfKarts() - race_manager->getNumPlayers());
|
||||
|
||||
if (rm_id >= 0)
|
||||
result_text = race_manager->getKartInfo(rm_id).getPlayerName();
|
||||
else
|
||||
currY += rect.Height;
|
||||
result_text = sw->getKart(kart_id)->
|
||||
getKartProperties()->getName();
|
||||
|
||||
if(currY > height) break;
|
||||
if (own_goal)
|
||||
{
|
||||
result_text.append(" ");
|
||||
result_text.append( _("(Own Goal)") );
|
||||
}
|
||||
|
||||
pos = core::rect<s32>(currX,currY,currX,currY);
|
||||
font->draw(resultText,pos, color, true, false);
|
||||
scorerIcon = soccerWorld->getKart(scorers.at(i))->
|
||||
result_text.append(" ");
|
||||
result_text.append(StringUtils::timeToString(score_times.at(i)).c_str());
|
||||
rect = m_font->getDimension(result_text.c_str());
|
||||
|
||||
if(height-prev_y < ((short)scorers.size()+1)*(short)rect.Height)
|
||||
current_y += (height-prev_y)/((short)scorers.size()+1);
|
||||
else
|
||||
current_y += rect.Height;
|
||||
|
||||
if(current_y > height) break;
|
||||
|
||||
pos = core::rect<s32>(current_x,current_y,current_x,current_y);
|
||||
font->draw(result_text,pos, (own_goal ?
|
||||
video::SColor(255, 255, 0, 0) : color), true, false);
|
||||
scorer_icon = sw->getKart(scorers.at(i).m_id)->
|
||||
getKartProperties()->getIconMaterial()->getTexture();
|
||||
sourceRect = core::recti(core::vector2di(0,0), scorerIcon->getSize());
|
||||
irr::u32 offsetX = GUIEngine::getFont()->getDimension(resultText.c_str()).Width/2;
|
||||
source_rect = core::recti(core::vector2di(0,0), scorer_icon->getSize());
|
||||
irr::u32 offset_x = GUIEngine::getFont()->getDimension(result_text.c_str()).Width/2;
|
||||
|
||||
destRect = core::recti(currX-offsetX-30, currY, currX-offsetX, currY+ 30);
|
||||
draw2DImage(scorerIcon, destRect, sourceRect,
|
||||
dest_rect = core::recti(current_x-offset_x-30, current_y, current_x-offset_x, current_y+ 30);
|
||||
draw2DImage(scorer_icon, dest_rect, source_rect,
|
||||
NULL, NULL, true);
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,6 @@
|
||||
#include "states_screens/tracks_screen.hpp"
|
||||
#include "utils/translation.hpp"
|
||||
|
||||
#define ENABLE_SOCCER_MODE
|
||||
|
||||
const int CONFIG_CODE_NORMAL = 0;
|
||||
const int CONFIG_CODE_TIMETRIAL = 1;
|
||||
const int CONFIG_CODE_FTL = 2;
|
||||
@ -114,15 +112,10 @@ void RaceSetupScreen::init()
|
||||
name4 += _("Hit others with weapons until they lose all their lives (only in multiplayer games).");
|
||||
w2->addItem( name4, IDENT_STRIKES, RaceManager::getIconOf(RaceManager::MINOR_MODE_3_STRIKES));
|
||||
|
||||
#ifdef ENABLE_SOCCER_MODE
|
||||
if (race_manager->getNumLocalPlayers() > 1 || UserConfigParams::m_artist_debug_mode)
|
||||
{
|
||||
irr::core::stringw name5 = irr::core::stringw(
|
||||
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
|
||||
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
|
||||
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
|
||||
}
|
||||
#endif
|
||||
irr::core::stringw name5 = irr::core::stringw(
|
||||
RaceManager::getNameOf(RaceManager::MINOR_MODE_SOCCER)) + L"\n";
|
||||
name5 += _("Push the ball to the opposite cage to score goals (only in multiplayer games).");
|
||||
w2->addItem( name5, IDENT_SOCCER, RaceManager::getIconOf(RaceManager::MINOR_MODE_SOCCER));
|
||||
|
||||
#define ENABLE_EASTER_EGG_MODE
|
||||
#ifdef ENABLE_EASTER_EGG_MODE
|
||||
@ -237,12 +230,7 @@ void RaceSetupScreen::eventCallback(Widget* widget, const std::string& name,
|
||||
{
|
||||
race_manager->setMinorMode(RaceManager::MINOR_MODE_SOCCER);
|
||||
UserConfigParams::m_game_mode = CONFIG_CODE_SOCCER;
|
||||
race_manager->setNumKarts( race_manager->getNumLocalPlayers() ); // no AI karts;
|
||||
// 1 player -> no need to choose a team or determine when the match ends
|
||||
if(race_manager->getNumLocalPlayers() <= 1)
|
||||
ArenasScreen::getInstance()->push();
|
||||
else
|
||||
SoccerSetupScreen::getInstance()->push();
|
||||
SoccerSetupScreen::getInstance()->push();
|
||||
}
|
||||
else if (selectedMode == "locked")
|
||||
{
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "audio/sfx_manager.hpp"
|
||||
#include "config/user_config.hpp"
|
||||
#include "guiengine/widgets/bubble_widget.hpp"
|
||||
#include "guiengine/widgets/button_widget.hpp"
|
||||
#include "guiengine/widgets/spinner_widget.hpp"
|
||||
#include "guiengine/widgets/check_box_widget.hpp"
|
||||
@ -34,7 +35,6 @@
|
||||
#include "states_screens/arenas_screen.hpp"
|
||||
#include "states_screens/state_manager.hpp"
|
||||
|
||||
|
||||
using namespace GUIEngine;
|
||||
DEFINE_SCREEN_SINGLETON( SoccerSetupScreen );
|
||||
|
||||
@ -65,20 +65,7 @@ void SoccerSetupScreen::eventCallback(Widget* widget, const std::string& name,
|
||||
{
|
||||
int nb_players = (int)m_kart_view_info.size();
|
||||
|
||||
if (getNumKartsInTeam(SOCCER_TEAM_RED) == 0 ||
|
||||
getNumKartsInTeam(SOCCER_TEAM_BLUE) == 0)
|
||||
{
|
||||
for(int i=0 ; i < nb_players ; i++)
|
||||
{
|
||||
if (!m_kart_view_info[i].confirmed)
|
||||
{
|
||||
m_kart_view_info[i].view->setBadge(BAD_BADGE);
|
||||
}
|
||||
}
|
||||
SFXManager::get()->quickSound( "anvil" );
|
||||
return;
|
||||
}
|
||||
else if(!areAllKartsConfirmed())
|
||||
if(!areAllKartsConfirmed())
|
||||
{
|
||||
for(int i=0 ; i < nb_players ; i++)
|
||||
{
|
||||
@ -112,6 +99,7 @@ void SoccerSetupScreen::eventCallback(Widget* widget, const std::string& name,
|
||||
{
|
||||
CheckBoxWidget* timeEnabled = dynamic_cast<CheckBoxWidget*>(widget);
|
||||
bool timed = timeEnabled->getState();
|
||||
UserConfigParams::m_soccer_use_time_limit = timed;
|
||||
getWidget<SpinnerWidget>("goalamount")->setActive(!timed);
|
||||
getWidget<SpinnerWidget>("timeamount")->setActive(timed);
|
||||
}
|
||||
@ -122,19 +110,14 @@ void SoccerSetupScreen::beforeAddingWidget()
|
||||
{
|
||||
Widget* central_div = getWidget<Widget>("central_div");
|
||||
|
||||
// Compute some dimensions
|
||||
const core::dimension2d<u32> vs_size = GUIEngine::getTitleFont()->getDimension( L"VS" );
|
||||
const int vs_width = (int)vs_size.Width;
|
||||
const int vs_height = (int)vs_size.Height;
|
||||
const int center_x = central_div->m_x + central_div->m_w/2;
|
||||
const int center_y = central_div->m_y + central_div->m_h/2;
|
||||
// Add red/blue team icon above the karts
|
||||
IconButtonWidget* red = getWidget<IconButtonWidget>("red_team");
|
||||
IconButtonWidget* blue = getWidget<IconButtonWidget>("blue_team");
|
||||
red->m_x = central_div->m_x + central_div->m_w/4;
|
||||
red->m_y = central_div->m_y + red->m_h;
|
||||
|
||||
// Add "VS" label at the center of the rounded box
|
||||
LabelWidget* label_vs = getWidget<LabelWidget>("vs");
|
||||
label_vs->m_x = center_x - vs_width/2;
|
||||
label_vs->m_y = center_y - vs_height/2;
|
||||
label_vs->m_w = vs_width;
|
||||
label_vs->m_h = vs_height;
|
||||
blue->m_x = central_div->m_x + (central_div->m_w/4)*3;
|
||||
blue->m_y = central_div->m_y + blue->m_h;
|
||||
|
||||
// Add the 3D views for the karts
|
||||
int nb_players = race_manager->getNumPlayers();
|
||||
@ -176,7 +159,9 @@ void SoccerSetupScreen::beforeAddingWidget()
|
||||
KartViewInfo info;
|
||||
info.view = kart_view;
|
||||
info.confirmed = false;
|
||||
info.team = i&1 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED;
|
||||
int single_team = UserConfigParams::m_soccer_default_team;
|
||||
info.team = nb_players == 1 ? (SoccerTeam)single_team :
|
||||
(i&1 ? SOCCER_TEAM_BLUE : SOCCER_TEAM_RED);
|
||||
m_kart_view_info.push_back(info);
|
||||
race_manager->setKartSoccerTeam(i, info.team);
|
||||
}
|
||||
@ -191,23 +176,23 @@ void SoccerSetupScreen::init()
|
||||
m_schedule_continue = false;
|
||||
|
||||
Screen::init();
|
||||
|
||||
|
||||
if (UserConfigParams::m_num_goals <= 0)
|
||||
UserConfigParams::m_num_goals = 3;
|
||||
|
||||
|
||||
if (UserConfigParams::m_soccer_time_limit <= 0)
|
||||
UserConfigParams::m_soccer_time_limit = 3;
|
||||
|
||||
SpinnerWidget* goalamount = getWidget<SpinnerWidget>("goalamount");
|
||||
goalamount->setValue(UserConfigParams::m_num_goals);
|
||||
goalamount->setActive(true);
|
||||
goalamount->setActive(!UserConfigParams::m_soccer_use_time_limit);
|
||||
|
||||
SpinnerWidget* timeAmount = getWidget<SpinnerWidget>("timeamount");
|
||||
timeAmount->setValue(UserConfigParams::m_soccer_time_limit);
|
||||
timeAmount->setActive(false);
|
||||
timeAmount->setActive(UserConfigParams::m_soccer_use_time_limit);
|
||||
|
||||
CheckBoxWidget* timeEnabled = getWidget<CheckBoxWidget>("time_enabled");
|
||||
timeEnabled->setState(false);
|
||||
timeEnabled->setState(UserConfigParams::m_soccer_use_time_limit);
|
||||
|
||||
// Set focus on "continue"
|
||||
ButtonWidget* bt_continue = getWidget<ButtonWidget>("continue");
|
||||
@ -229,7 +214,7 @@ void SoccerSetupScreen::tearDown()
|
||||
|
||||
// Reset the 'map fire to select' option of the device manager
|
||||
input_manager->getDeviceManager()->mapFireToSelect(false);
|
||||
|
||||
|
||||
UserConfigParams::m_num_goals = getWidget<SpinnerWidget>("goalamount")->getValue();
|
||||
UserConfigParams::m_soccer_time_limit = getWidget<SpinnerWidget>("timeamount")->getValue();
|
||||
|
||||
@ -255,7 +240,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
||||
if(m_schedule_continue)
|
||||
return EVENT_BLOCK;
|
||||
|
||||
ButtonWidget* bt_continue = getWidget<ButtonWidget>("continue");
|
||||
ButtonWidget* bt_continue = getWidget<ButtonWidget>("continue");
|
||||
BubbleWidget* bubble = getWidget<BubbleWidget>("lblLeftRight");
|
||||
GUIEngine::EventPropagation result = EVENT_LET;
|
||||
SoccerTeam team_switch = SOCCER_TEAM_NONE;
|
||||
int nb_players = (int)m_kart_view_info.size();
|
||||
@ -263,7 +249,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
||||
switch(action)
|
||||
{
|
||||
case PA_MENU_LEFT:
|
||||
if (bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) &&
|
||||
if ((bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) ||
|
||||
bubble->isFocusedForPlayer(PLAYER_ID_GAME_MASTER)) &&
|
||||
m_kart_view_info[playerId].confirmed == false)
|
||||
{
|
||||
team_switch = SOCCER_TEAM_RED;
|
||||
@ -275,7 +262,8 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
||||
}
|
||||
break;
|
||||
case PA_MENU_RIGHT:
|
||||
if (bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) &&
|
||||
if ((bt_continue->isFocusedForPlayer(PLAYER_ID_GAME_MASTER) ||
|
||||
bubble->isFocusedForPlayer(PLAYER_ID_GAME_MASTER)) &&
|
||||
m_kart_view_info[playerId].confirmed == false)
|
||||
{
|
||||
team_switch = SOCCER_TEAM_BLUE;
|
||||
@ -308,22 +296,12 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
||||
return EVENT_BLOCK;
|
||||
}
|
||||
|
||||
if (getNumConfirmedKarts() > nb_players-2 &&
|
||||
(getNumKartsInTeam(SOCCER_TEAM_RED) == 0 ||
|
||||
getNumKartsInTeam(SOCCER_TEAM_BLUE) == 0))
|
||||
{
|
||||
SFXManager::get()->quickSound( "anvil" );
|
||||
m_kart_view_info[playerId].view->setBadge(BAD_BADGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Confirm team selection
|
||||
m_kart_view_info[playerId].confirmed = true;
|
||||
m_kart_view_info[playerId].view->setRotateTo( KART_CONFIRMATION_TARGET_ANGLE, KART_CONFIRMATION_ROTATION_SPEED );
|
||||
m_kart_view_info[playerId].view->setBadge(OK_BADGE);
|
||||
m_kart_view_info[playerId].view->unsetBadge(BAD_BADGE);
|
||||
SFXManager::get()->quickSound( "wee" );
|
||||
}
|
||||
// Confirm team selection
|
||||
m_kart_view_info[playerId].confirmed = true;
|
||||
m_kart_view_info[playerId].view->setRotateTo( KART_CONFIRMATION_TARGET_ANGLE, KART_CONFIRMATION_ROTATION_SPEED );
|
||||
m_kart_view_info[playerId].view->setBadge(OK_BADGE);
|
||||
m_kart_view_info[playerId].view->unsetBadge(BAD_BADGE);
|
||||
SFXManager::get()->quickSound( "wee" );
|
||||
return EVENT_BLOCK;
|
||||
}
|
||||
case PA_MENU_CANCEL:
|
||||
@ -349,15 +327,15 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if(team_switch != SOCCER_TEAM_NONE) // A player wants to change his team?
|
||||
{
|
||||
if (nb_players == 1)
|
||||
UserConfigParams::m_soccer_default_team = (int)team_switch;
|
||||
race_manager->setKartSoccerTeam(playerId, team_switch);
|
||||
m_kart_view_info[playerId].team = team_switch;
|
||||
updateKartViewsLayout();
|
||||
}
|
||||
|
||||
|
||||
|
||||
return result;
|
||||
} // filterActions
|
||||
@ -366,7 +344,7 @@ GUIEngine::EventPropagation SoccerSetupScreen::filterActions(PlayerAction action
|
||||
void SoccerSetupScreen::onUpdate(float delta)
|
||||
{
|
||||
int nb_players = (int)m_kart_view_info.size();
|
||||
|
||||
|
||||
if(m_schedule_continue)
|
||||
{
|
||||
for(int i=0 ; i < nb_players ; i++)
|
||||
@ -377,7 +355,7 @@ void SoccerSetupScreen::onUpdate(float delta)
|
||||
m_schedule_continue = false;
|
||||
ArenasScreen::getInstance()->push();
|
||||
}
|
||||
} // onUPdate
|
||||
} // onUpdate
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
bool SoccerSetupScreen::areAllKartsConfirmed() const
|
||||
@ -395,19 +373,6 @@ bool SoccerSetupScreen::areAllKartsConfirmed() const
|
||||
return all_confirmed;
|
||||
} // areAllKartsConfirmed
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
int SoccerSetupScreen::getNumKartsInTeam(int team)
|
||||
{
|
||||
int karts_in_team = 0;
|
||||
int nb_players = (int)m_kart_view_info.size();
|
||||
for(int i=0 ; i < nb_players ; i++)
|
||||
{
|
||||
if(m_kart_view_info[i].team == team)
|
||||
karts_in_team++;
|
||||
}
|
||||
return karts_in_team;
|
||||
} // getNumKartsInTeam
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
int SoccerSetupScreen::getNumConfirmedKarts()
|
||||
{
|
||||
@ -427,10 +392,8 @@ void SoccerSetupScreen::updateKartViewsLayout()
|
||||
Widget* central_div = getWidget<Widget>("central_div");
|
||||
|
||||
// Compute/get some dimensions
|
||||
LabelWidget* label_vs = getWidget<LabelWidget>("vs");
|
||||
const int vs_width = label_vs->m_w;
|
||||
const int nb_columns = 2; // two karts maximum per column
|
||||
const int kart_area_width = (central_div->m_w - vs_width) / 2; // size of one half of the screen
|
||||
const int kart_area_width = (central_div->m_w) / 2; // size of one half of the screen
|
||||
const int kart_view_size = kart_area_width/nb_columns; // Size (width and height) of a kart view
|
||||
const int center_x = central_div->m_x + central_div->m_w/2;
|
||||
const int center_y = central_div->m_y + central_div->m_h/2;
|
||||
@ -448,8 +411,8 @@ void SoccerSetupScreen::updateKartViewsLayout()
|
||||
const int start_y[2] = {center_y - nb_rows_per_team[0] * kart_view_size / 2,
|
||||
center_y - nb_rows_per_team[1] * kart_view_size / 2};
|
||||
// - center of each half-screen
|
||||
const int center_x_per_team[2] = { ( central_div->m_x + (center_x - vs_width) ) / 2,
|
||||
( central_div->m_x+central_div->m_w + (center_x + vs_width) ) / 2,
|
||||
const int center_x_per_team[2] = { ( central_div->m_x + center_x ) / 2,
|
||||
( central_div->m_x+central_div->m_w + center_x ) / 2,
|
||||
};
|
||||
|
||||
// Update the layout of the 3D views for the karts
|
||||
@ -474,4 +437,3 @@ void SoccerSetupScreen::updateKartViewsLayout()
|
||||
view_info.view->move(pos_x, pos_y, kart_view_size, kart_view_size);
|
||||
}
|
||||
} // updateKartViewsLayout
|
||||
|
||||
|
@ -43,7 +43,7 @@ class SoccerSetupScreen : public GUIEngine::Screen, public GUIEngine::ScreenSing
|
||||
};
|
||||
|
||||
AlignedArray<KartViewInfo> m_kart_view_info;
|
||||
|
||||
|
||||
bool m_schedule_continue;
|
||||
|
||||
public:
|
||||
@ -75,7 +75,6 @@ public:
|
||||
|
||||
private:
|
||||
bool areAllKartsConfirmed() const;
|
||||
int getNumKartsInTeam(int team);
|
||||
int getNumConfirmedKarts();
|
||||
void updateKartViewsLayout();
|
||||
};
|
||||
|
@ -154,7 +154,8 @@ void TrackInfoScreen::init()
|
||||
// -------------
|
||||
const int local_players = race_manager->getNumLocalPlayers();
|
||||
const bool has_AI =
|
||||
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ?
|
||||
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ||
|
||||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER ?
|
||||
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
|
||||
race_manager->hasAI());
|
||||
m_ai_kart_spinner->setVisible(has_AI);
|
||||
@ -170,7 +171,8 @@ void TrackInfoScreen::init()
|
||||
m_ai_kart_spinner->setValue(num_ai);
|
||||
race_manager->setNumKarts(num_ai + local_players);
|
||||
// Set the max karts supported based on the battle arena selected
|
||||
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES)
|
||||
if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
||||
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER)
|
||||
{
|
||||
m_ai_kart_spinner->setMax(max_arena_players - local_players);
|
||||
}
|
||||
@ -181,8 +183,9 @@ void TrackInfoScreen::init()
|
||||
{
|
||||
m_ai_kart_spinner->setMin(3 - local_players);
|
||||
}
|
||||
// Make sure in battle mode at least 1 ai for single player
|
||||
else if(race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES &&
|
||||
// Make sure in battle and soccer mode at least 1 ai for single player
|
||||
else if((race_manager->getMinorMode()==RaceManager::MINOR_MODE_3_STRIKES ||
|
||||
race_manager->getMinorMode()==RaceManager::MINOR_MODE_SOCCER) &&
|
||||
local_players == 1 &&
|
||||
!UserConfigParams::m_artist_debug_mode)
|
||||
m_ai_kart_spinner->setMin(1);
|
||||
@ -310,7 +313,8 @@ void TrackInfoScreen::onEnterPressedInternal()
|
||||
const int max_arena_players = m_track->getMaxArenaPlayers();
|
||||
const int local_players = race_manager->getNumLocalPlayers();
|
||||
const bool has_AI =
|
||||
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ?
|
||||
(race_manager->getMinorMode() == RaceManager::MINOR_MODE_3_STRIKES ||
|
||||
race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER ?
|
||||
m_track->hasNavMesh() && (max_arena_players - local_players) > 0 :
|
||||
race_manager->hasAI());
|
||||
|
||||
|
@ -137,7 +137,7 @@ void BattleGraph::findItemsOnGraphNodes()
|
||||
|
||||
for (unsigned int j = 0; j < this->getNumNodes(); ++j)
|
||||
{
|
||||
if (NavMesh::get()->getNavPoly(j).pointInPoly(xyz))
|
||||
if (NavMesh::get()->getNavPoly(j).pointInPoly(xyz, false))
|
||||
polygon = j;
|
||||
}
|
||||
|
||||
@ -149,7 +149,70 @@ void BattleGraph::findItemsOnGraphNodes()
|
||||
else
|
||||
Log::debug("BattleGraph","Can't map item number %d with a suitable polygon", i);
|
||||
}
|
||||
}
|
||||
} // findItemsOnGraphNodes
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
int BattleGraph::pointToNode(const int cur_node,
|
||||
const Vec3& cur_point,
|
||||
bool ignore_vertical) const
|
||||
{
|
||||
int final_node = BattleGraph::UNKNOWN_POLY;
|
||||
|
||||
if (cur_node == BattleGraph::UNKNOWN_POLY)
|
||||
{
|
||||
// Try all nodes in the battle graph
|
||||
bool found = false;
|
||||
unsigned int node = 0;
|
||||
while (!found && node < this->getNumNodes())
|
||||
{
|
||||
const NavPoly& p_all = this->getPolyOfNode(node);
|
||||
if (p_all.pointInPoly(cur_point, ignore_vertical))
|
||||
{
|
||||
final_node = node;
|
||||
found = true;
|
||||
}
|
||||
node++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if the point is still on the same node
|
||||
const NavPoly& p_cur = this->getPolyOfNode(cur_node);
|
||||
if (p_cur.pointInPoly(cur_point, ignore_vertical)) return cur_node;
|
||||
|
||||
// If not then check all adjacent polys
|
||||
const std::vector<int>& adjacents = NavMesh::get()
|
||||
->getAdjacentPolys(cur_node);
|
||||
|
||||
bool found = false;
|
||||
unsigned int num = 0;
|
||||
while (!found && num < adjacents.size())
|
||||
{
|
||||
const NavPoly& p_temp = this->getPolyOfNode(adjacents[num]);
|
||||
if (p_temp.pointInPoly(cur_point, ignore_vertical))
|
||||
{
|
||||
final_node = adjacents[num];
|
||||
found = true;
|
||||
}
|
||||
num++;
|
||||
}
|
||||
|
||||
// Current node is still unkown
|
||||
if (final_node == BattleGraph::UNKNOWN_POLY)
|
||||
{
|
||||
// Calculated distance from saved node to current position,
|
||||
// if it's close enough than use the saved node anyway, it
|
||||
// may happen when the kart stays on the edge of obstacles
|
||||
const NavPoly& p = this->getPolyOfNode(cur_node);
|
||||
const float dist = (p.getCenter() - cur_point).length_2d();
|
||||
|
||||
if (dist < 3.0f)
|
||||
final_node = cur_node;
|
||||
}
|
||||
}
|
||||
return final_node;
|
||||
} // pointToNode
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@ -158,4 +221,4 @@ const int & BattleGraph::getNextShortestPathPoly(int i, int j) const
|
||||
if (i == BattleGraph::UNKNOWN_POLY || j == BattleGraph::UNKNOWN_POLY)
|
||||
return BattleGraph::UNKNOWN_POLY;
|
||||
return m_parent_poly[j][i];
|
||||
}
|
||||
} // getNextShortestPathPoly
|
||||
|
@ -126,6 +126,10 @@ public:
|
||||
{ return m_items_on_graph; }
|
||||
|
||||
void findItemsOnGraphNodes();
|
||||
// ----------------------------------------------------------------------
|
||||
int pointToNode(const int cur_node,
|
||||
const Vec3& cur_point,
|
||||
bool ignore_vertical) const;
|
||||
}; //BattleGraph
|
||||
|
||||
#endif
|
||||
|
@ -51,42 +51,28 @@ CheckGoal::CheckGoal(const XMLNode &node, unsigned int index)
|
||||
*/
|
||||
void CheckGoal::update(float dt)
|
||||
{
|
||||
World *world = World::getWorld();
|
||||
assert(world);
|
||||
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
|
||||
Track* track = world->getTrack();
|
||||
assert(track);
|
||||
|
||||
TrackObjectManager* tom = track->getTrackObjectManager();
|
||||
assert(tom);
|
||||
|
||||
PtrVector<TrackObject>& objects = tom->getObjects();
|
||||
unsigned int ball_index = 0;
|
||||
for(unsigned int i=0; i<objects.size(); i++)
|
||||
if (world)
|
||||
{
|
||||
TrackObject* obj = objects.get(i);
|
||||
if(!obj->isSoccerBall())
|
||||
continue;
|
||||
|
||||
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
|
||||
if(isTriggered(m_previous_position[ball_index], xyz, -1))
|
||||
const Vec3 &xyz = world->getBallPosition();
|
||||
if (isTriggered(m_previous_ball_position, xyz, -1))
|
||||
{
|
||||
if(UserConfigParams::m_check_debug)
|
||||
Log::info("CheckGoal", "Goal check structure %d triggered for object %s.",
|
||||
m_index, obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getDebugName());
|
||||
trigger(ball_index);
|
||||
if (UserConfigParams::m_check_debug)
|
||||
{
|
||||
Log::info("CheckGoal", "Goal check structure"
|
||||
"%d triggered for ball.", m_index);
|
||||
}
|
||||
trigger(0);
|
||||
}
|
||||
m_previous_position[ball_index] = xyz;
|
||||
ball_index++;
|
||||
m_previous_ball_position = xyz;
|
||||
}
|
||||
} // update
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Called when the check line is triggered. This function creates a cannon
|
||||
* animation object and attaches it to the kart.
|
||||
* \param kart_index The index of the kart that triggered the check line.
|
||||
/** Called when the goal line is triggered. Input any integer for i to use
|
||||
*/
|
||||
void CheckGoal::trigger(unsigned int kart_index)
|
||||
void CheckGoal::trigger(unsigned int i)
|
||||
{
|
||||
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
if(!world)
|
||||
@ -116,21 +102,22 @@ bool CheckGoal::isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
|
||||
void CheckGoal::reset(const Track &track)
|
||||
{
|
||||
CheckStructure::reset(track);
|
||||
m_previous_ball_position = Vec3(0, 0, 0);
|
||||
|
||||
const TrackObjectManager* tom = track.getTrackObjectManager();
|
||||
assert(tom);
|
||||
SoccerWorld* world = dynamic_cast<SoccerWorld*>(World::getWorld());
|
||||
|
||||
m_previous_position.clear();
|
||||
|
||||
const PtrVector<TrackObject>& objects = tom->getObjects();
|
||||
for(unsigned int i=0; i<objects.size(); i++)
|
||||
if (world)
|
||||
{
|
||||
const TrackObject* obj = objects.get(i);
|
||||
if(!obj->isSoccerBall())
|
||||
continue;
|
||||
|
||||
const Vec3 &xyz = obj->getPresentation<TrackObjectPresentationMesh>()->getNode()->getPosition();
|
||||
|
||||
m_previous_position.push_back(xyz);
|
||||
const Vec3 &xyz = world->getBallPosition();
|
||||
m_previous_ball_position = xyz;
|
||||
}
|
||||
|
||||
} // reset
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
Vec3 CheckGoal::convertTo3DCenter() const
|
||||
{
|
||||
float x = m_line.getMiddle().X;
|
||||
float y = m_line.getMiddle().Y;
|
||||
return Vec3(x, 0, y);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ using namespace irr;
|
||||
class CheckManager;
|
||||
class XMLNode;
|
||||
class Track;
|
||||
class Vec3;
|
||||
|
||||
/**
|
||||
* \brief Implements a simple checkline that will score a point when the
|
||||
@ -37,6 +38,9 @@ class Track;
|
||||
class CheckGoal : public CheckStructure
|
||||
{
|
||||
private:
|
||||
/** Previois ball position. */
|
||||
Vec3 m_previous_ball_position;
|
||||
|
||||
/** Which team is this goal for? */
|
||||
bool m_first_goal;
|
||||
|
||||
@ -51,6 +55,11 @@ public:
|
||||
virtual bool isTriggered(const Vec3 &old_pos, const Vec3 &new_pos,
|
||||
unsigned int indx) OVERRIDE;
|
||||
virtual void reset(const Track &track) OVERRIDE;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
bool getTeam() const { return m_first_goal; }
|
||||
// ------------------------------------------------------------------------
|
||||
Vec3 convertTo3DCenter() const;
|
||||
}; // CheckLine
|
||||
|
||||
#endif
|
||||
|
@ -53,6 +53,8 @@ CheckStructure::CheckStructure(const XMLNode &node, unsigned int index)
|
||||
// Cannons don't have a kind specified, so test for the name in this case
|
||||
else if(node.getName()=="cannon")
|
||||
m_check_type = CT_CANNON;
|
||||
else if(node.getName()=="goal")
|
||||
m_check_type = CT_GOAL;
|
||||
else
|
||||
Log::warn("CheckStructure", "Unknown check structure '%s' - ignored.", kind.c_str());
|
||||
|
||||
|
@ -52,7 +52,7 @@ const std::vector<Vec3> NavPoly::getVertices()
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool NavPoly::pointInPoly(const Vec3& p) const
|
||||
bool NavPoly::pointInPoly(const Vec3& p, bool ignore_vertical) const
|
||||
{
|
||||
std::vector<Vec3> points;
|
||||
for(unsigned int i=0; i<m_vertices.size(); i++)
|
||||
@ -70,6 +70,8 @@ bool NavPoly::pointInPoly(const Vec3& p) const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ignore_vertical) return true;
|
||||
|
||||
// Check for vertical distance too
|
||||
const float dist = p.getY() - m_center.getY();
|
||||
if (fabsf(dist) > 1.0f )
|
||||
|
@ -61,7 +61,8 @@ public:
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns true if a given point lies in this polygon. */
|
||||
bool pointInPoly(const Vec3& p) const;
|
||||
bool pointInPoly(const Vec3& p,
|
||||
bool ignore_vertical) const;
|
||||
|
||||
const Vec3& operator[](int i) const ;
|
||||
|
||||
|
@ -589,11 +589,16 @@ void Track::loadTrackInfo()
|
||||
|
||||
if(file_manager->fileExists(m_root+"navmesh.xml"))
|
||||
m_has_navmesh = true;
|
||||
else if(m_is_arena)
|
||||
else if(m_is_arena || m_is_soccer)
|
||||
{
|
||||
Log::warn("Track", "NavMesh is not found for arena %s, "
|
||||
"disable AI for it.\n", m_name.c_str());
|
||||
}
|
||||
if (m_is_soccer)
|
||||
{
|
||||
// Currently only max eight players in soccer mode
|
||||
m_max_arena_players = 8;
|
||||
}
|
||||
|
||||
} // loadTrackInfo
|
||||
|
||||
@ -716,7 +721,7 @@ void Track::loadQuadGraph(unsigned int mode_id, const bool reverse)
|
||||
|
||||
void Track::mapPoint2MiniMap(const Vec3 &xyz, Vec3 *draw_at) const
|
||||
{
|
||||
if (m_is_arena && m_has_navmesh)
|
||||
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
|
||||
BattleGraph::get()->mapPoint2MiniMap(xyz, draw_at);
|
||||
else
|
||||
QuadGraph::get()->mapPoint2MiniMap(xyz, draw_at);
|
||||
@ -1032,7 +1037,7 @@ void Track::loadMinimap()
|
||||
core::dimension2du size = m_mini_map_size
|
||||
.getOptimalSize(!nonpower,!nonsquare);
|
||||
|
||||
if (m_is_arena && m_has_navmesh)
|
||||
if ((m_is_arena || m_is_soccer) && m_has_navmesh)
|
||||
{
|
||||
BattleGraph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255),
|
||||
&m_old_rtt_mini_map, &m_new_rtt_mini_map);
|
||||
@ -1645,7 +1650,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
|
||||
// the information about the size of the texture to render the mini
|
||||
// map to.
|
||||
if (!m_is_arena && !m_is_soccer && !m_is_cutscene) loadQuadGraph(mode_id, reverse_track);
|
||||
else if (m_is_arena && !m_is_soccer && !m_is_cutscene && m_has_navmesh)
|
||||
else if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh)
|
||||
loadBattleGraph();
|
||||
|
||||
ItemManager::create();
|
||||
@ -1871,7 +1876,7 @@ void Track::loadTrackModel(bool reverse_track, unsigned int mode_id)
|
||||
|
||||
delete root;
|
||||
|
||||
if (m_is_arena && !m_is_soccer && !m_is_cutscene && m_has_navmesh)
|
||||
if ((m_is_arena || m_is_soccer) && !m_is_cutscene && m_has_navmesh)
|
||||
BattleGraph::get()->findItemsOnGraphNodes();
|
||||
|
||||
if (UserConfigParams::m_track_debug &&
|
||||
|
@ -492,6 +492,10 @@ void TrackObjectPresentationMesh::init(const XMLNode* xml_node,
|
||||
m_frame_end = node->getEndFrame();
|
||||
if (xml_node)
|
||||
xml_node->get("frame-end", &m_frame_end);
|
||||
|
||||
if (World::getWorld() && World::getWorld()->getTrack() && xml_node)
|
||||
World::getWorld()->getTrack()
|
||||
->handleAnimatedTextures(m_node, *xml_node);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -107,6 +107,10 @@ enum DebugMenuCommand
|
||||
DEBUG_VIEW_KART_TWO,
|
||||
DEBUG_VIEW_KART_THREE,
|
||||
DEBUG_VIEW_KART_FOUR,
|
||||
DEBUG_VIEW_KART_FIVE,
|
||||
DEBUG_VIEW_KART_SIX,
|
||||
DEBUG_VIEW_KART_SEVEN,
|
||||
DEBUG_VIEW_KART_EIGHT,
|
||||
DEBUG_HIDE_KARTS,
|
||||
DEBUG_THROTTLE_FPS,
|
||||
DEBUG_VISUAL_VALUES,
|
||||
@ -494,6 +498,22 @@ bool handleContextMenuAction(s32 cmdID)
|
||||
{
|
||||
changeCameraTarget(4);
|
||||
}
|
||||
else if (cmdID == DEBUG_VIEW_KART_FIVE)
|
||||
{
|
||||
changeCameraTarget(5);
|
||||
}
|
||||
else if (cmdID == DEBUG_VIEW_KART_SIX)
|
||||
{
|
||||
changeCameraTarget(6);
|
||||
}
|
||||
else if (cmdID == DEBUG_VIEW_KART_SEVEN)
|
||||
{
|
||||
changeCameraTarget(7);
|
||||
}
|
||||
else if (cmdID == DEBUG_VIEW_KART_EIGHT)
|
||||
{
|
||||
changeCameraTarget(8);
|
||||
}
|
||||
else if (cmdID == DEBUG_PRINT_START_POS)
|
||||
{
|
||||
if (!world) return false;
|
||||
@ -691,6 +711,10 @@ bool onEvent(const SEvent &event)
|
||||
sub->addItem(L"To kart two", DEBUG_VIEW_KART_TWO);
|
||||
sub->addItem(L"To kart three", DEBUG_VIEW_KART_THREE);
|
||||
sub->addItem(L"To kart four", DEBUG_VIEW_KART_FOUR);
|
||||
sub->addItem(L"To kart five", DEBUG_VIEW_KART_FIVE);
|
||||
sub->addItem(L"To kart six", DEBUG_VIEW_KART_SIX);
|
||||
sub->addItem(L"To kart seven", DEBUG_VIEW_KART_SEVEN);
|
||||
sub->addItem(L"To kart eight", DEBUG_VIEW_KART_EIGHT);
|
||||
|
||||
mnu->addItem(L"Adjust values", DEBUG_VISUAL_VALUES);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user