Initial work on spare tire kart in battle mode

Some values are hard-coded for now
This commit is contained in:
Benau 2016-10-05 16:18:39 +08:00
parent 43d9db4db0
commit a91af96637
15 changed files with 344 additions and 36 deletions

View File

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

View File

@ -276,6 +276,9 @@ public:
{
return (scene::ISceneNode *) m_node;
}
// ------------------------------------------------------------------------
const btQuaternion& getRotation() const { return m_original_rotation; }
}; // class Item
#endif

View File

@ -29,6 +29,7 @@
#include "graphics/material_manager.hpp"
#include "io/file_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/spare_tire_ai.hpp"
#include "modes/linear_world.hpp"
#include "network/network_config.hpp"
#include "network/race_event_manager.hpp"
@ -287,6 +288,8 @@ Item* ItemManager::newItem(const Vec3& xyz, float distance,
void ItemManager::collectedItem(Item *item, AbstractKart *kart, int add_info)
{
assert(item);
// Spare tire karts don't collect items
if (dynamic_cast<SpareTireAI*>(kart->getController()) != NULL) return;
if( (item->getType() == Item::ITEM_BUBBLEGUM ||
item->getType() == Item::ITEM_BUBBLEGUM_NOLOK) && kart->isShielded())
{

View File

@ -343,7 +343,8 @@ void ArenaAI::configSpeed()
else
{
// Otherwise accelerate
m_controls->setAccel(stk_config->m_ai_acceleration * handicap);
m_controls->setAccel(stk_config->m_ai_acceleration * handicap *
getSpeedCap());
}
} // configSpeed

View File

@ -128,6 +128,7 @@ private:
virtual bool isWaiting() const = 0;
virtual bool isKartOnRoad() const = 0;
virtual void resetAfterStop() {};
virtual float getSpeedCap() const { return 1.0f; }
public:
ArenaAI(AbstractKart *kart);
virtual ~ArenaAI() {};

View File

@ -64,20 +64,6 @@ BattleAI::~BattleAI()
#endif
} // ~BattleAI
//-----------------------------------------------------------------------------
/** Resets the AI when a race is restarted.
*/
void BattleAI::reset()
{
ArenaAI::reset();
} // reset
//-----------------------------------------------------------------------------
void BattleAI::update(float dt)
{
ArenaAI::update(dt);
} // update
//-----------------------------------------------------------------------------
void BattleAI::findClosestKart(bool use_difficulty)
{

View File

@ -30,21 +30,19 @@ class ThreeStrikesBattle;
*/
class BattleAI : public ArenaAI
{
private:
protected:
/** Keep a pointer to world. */
ThreeStrikesBattle *m_world;
virtual int getCurrentNode() const OVERRIDE;
private:
virtual void findClosestKart(bool use_difficulty) OVERRIDE;
virtual void findTarget() OVERRIDE;
virtual int getCurrentNode() const OVERRIDE;
virtual float getKartDistance(const AbstractKart* kart) const OVERRIDE;
virtual bool isKartOnRoad() const OVERRIDE;
virtual bool isWaiting() const OVERRIDE;
public:
BattleAI(AbstractKart *kart);
~BattleAI();
virtual void update (float delta) OVERRIDE;
virtual void reset () OVERRIDE;
BattleAI(AbstractKart *kart);
~BattleAI();
};
#endif

View File

@ -0,0 +1,134 @@
//
// 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/spare_tire_ai.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/kart_gfx.hpp"
#include "modes/three_strikes_battle.hpp"
#include "tracks/arena_graph.hpp"
#include "tracks/arena_node.hpp"
#include "physics/physics.hpp"
#include "utils/random_generator.hpp"
SpareTireAI::SpareTireAI(AbstractKart *kart)
: BattleAI(kart)
{
reset();
// Don't call our own setControllerName, since this will add a
// billboard showing 'AIBaseController' to the kart.
Controller::setControllerName("SpareTireAI");
} // SpareTireAI
//-----------------------------------------------------------------------------
/** Resets the AI when a race is restarted.
*/
void SpareTireAI::reset()
{
BattleAI::reset();
m_fixed_target_nodes.clear();
m_idx = 0;
m_timer = 0.0f;
} // reset
//-----------------------------------------------------------------------------
void SpareTireAI::update(float dt)
{
assert(!m_fixed_target_nodes.empty());
BattleAI::update(dt);
m_timer -= dt;
if (m_timer < 0.0f)
unspawn();
} // update
//-----------------------------------------------------------------------------
void SpareTireAI::findDefaultPath()
{
// Randomly find 3 nodes for spare tire kart to move
assert(m_fixed_target_nodes.empty());
const int nodes = m_graph->getNumNodes();
const float min_dist = sqrtf(nodes);
RandomGenerator random;
while (m_fixed_target_nodes.size() < 3)
{
int node = random.get(nodes);
if (m_fixed_target_nodes.empty())
{
m_fixed_target_nodes.push_back(node);
continue;
}
bool succeed = true;
for (const int& all_node : m_fixed_target_nodes)
{
float dist = m_graph->getDistance(all_node, node);
if (dist < min_dist)
succeed = false;
}
if (succeed)
m_fixed_target_nodes.push_back(node);
}
m_idx = 0;
m_target_node = m_fixed_target_nodes[m_idx];
} // findDefaultPath
//-----------------------------------------------------------------------------
void SpareTireAI::findTarget()
{
if (getCurrentNode() == m_fixed_target_nodes[m_idx])
m_idx = m_idx == 2 ? 0 : m_idx + 1;
const int chosen_node = m_fixed_target_nodes[m_idx];
m_target_node = chosen_node;
m_target_point = m_graph->getNode(chosen_node)->getCenter();
} // findTarget
//-----------------------------------------------------------------------------
void SpareTireAI::spawn(float time_to_last)
{
findDefaultPath();
m_timer = time_to_last;
World::getWorld()->getPhysics()->addKart(m_kart);
m_kart->getKartGFX()->reset();
m_kart->getNode()->setVisible(true);
} // spawn
//-----------------------------------------------------------------------------
void SpareTireAI::unspawn()
{
reset();
m_kart->eliminate();
} // unspawn
//-----------------------------------------------------------------------------
void SpareTireAI::crashed(const AbstractKart *k)
{
// Nothing happen when two spare tire karts crash each other
if (dynamic_cast<const SpareTireAI*>(k->getController()) != NULL) return;
// Max 3 lives only
if (m_world->getKartLife(k->getWorldKartId()) == 3) return;
// Otherwise increase one life for that kart and unspawn
m_world->addKartLife(k->getWorldKartId());
unspawn();
} // crashed

View File

@ -0,0 +1,50 @@
//
// 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_SPARE_TIRE_AI_HPP
#define HEADER_SPARE_TIRE_AI_HPP
#include "karts/controller/battle_ai.hpp"
/** The AI for spare tire karts in battle mode, allowing kart to gain life.
* \ingroup controller
*/
class SpareTireAI : public BattleAI
{
private:
std::vector<int> m_fixed_target_nodes;
int m_idx;
float m_timer;
virtual void findClosestKart(bool use_difficulty) OVERRIDE {}
virtual void findTarget() OVERRIDE;
virtual float getSpeedCap() const OVERRIDE { return 0.7f; }
void findDefaultPath();
public:
SpareTireAI(AbstractKart *kart);
virtual void crashed(const AbstractKart *k) OVERRIDE;
virtual void update(float delta) OVERRIDE;
virtual void reset() OVERRIDE;
void spawn(float time_to_last);
void unspawn();
bool needUpdate() const { return !m_fixed_target_nodes.empty(); }
};
#endif

View File

@ -50,6 +50,7 @@
#include "karts/abstract_kart_animation.hpp"
#include "karts/cached_characteristic.hpp"
#include "karts/controller/end_controller.hpp"
#include "karts/controller/spare_tire_ai.hpp"
#include "karts/explosion_animation.hpp"
#include "karts/kart_gfx.hpp"
#include "karts/kart_model.hpp"
@ -864,6 +865,10 @@ void Kart::finishedRace(float time, bool from_server)
*/
m_finished_race = true;
m_finish_time = time;
// If this is spare tire kart, end now
if (dynamic_cast<SpareTireAI*>(m_controller) != NULL) return;
m_controller->finishedRace(time);
m_kart_model->finishedRace();
race_manager->kartFinishedRace(this, time);

View File

@ -23,13 +23,17 @@
#include "graphics/camera.hpp"
#include "graphics/irr_driver.hpp"
#include "io/file_manager.hpp"
#include "karts/abstract_kart.hpp"
#include "items/item_manager.hpp"
#include "karts/kart.hpp"
#include "karts/controller/spare_tire_ai.hpp"
#include "karts/kart_model.hpp"
#include "karts/kart_properties.hpp"
#include "physics/physics.hpp"
#include "states_screens/race_gui_base.hpp"
#include "tracks/arena_graph.hpp"
#include "tracks/track.hpp"
#include "tracks/track_object_manager.hpp"
#include "tracks/track_sector.hpp"
#include "utils/constants.hpp"
#include <string>
@ -63,6 +67,41 @@ void ThreeStrikesBattle::init()
{
WorldWithRank::init();
m_display_rank = false;
// Spare tire karts only added with large arena
const int all_nodes =
ArenaGraph::get() ? ArenaGraph::get()->getNumNodes() : 0;
if (all_nodes > 200)
{
const unsigned int max_sta_num = unsigned(m_karts.size() * 0.8f);
for (int i = 0; i < all_nodes; i++)
{
// Pre-spawn the spare tire karts on the item position, preventing
// affecting current karts
Item* item = ItemManager::get()->getFirstItemInQuad(i);
if (item == NULL) continue;
btTransform t;
t.setOrigin(item->getXYZ());
t.setRotation(item->getRotation());
AbstractKart* sta = new Kart("nolok", m_karts.size(),
m_karts.size() + 1, t, PLAYER_DIFFICULTY_NORMAL, KRT_BLUE);
sta->init(RaceManager::KartType::KT_AI);
sta->setController(new SpareTireAI(sta));
m_karts.push_back(sta);
m_spare_tire_karts.push_back(sta);
m_kart_track_sector.push_back(new TrackSector());
m_position_index.push_back(0);
#ifdef DEBUG
m_position_used.push_back(false);
#endif
m_track->adjustForFog(sta->getNode());
if (m_spare_tire_karts.size() >= max_sta_num) break;
}
}
m_kart_info.resize(m_karts.size());
} // ThreeStrikesBattle
@ -73,6 +112,7 @@ void ThreeStrikesBattle::init()
ThreeStrikesBattle::~ThreeStrikesBattle()
{
m_tires.clearWithoutDeleting();
m_spare_tire_karts.clear();
irr_driver->grabAllTextures(m_tire);
// Remove the mesh from the cache so that the mesh is properly
@ -88,14 +128,27 @@ void ThreeStrikesBattle::reset()
{
WorldWithRank::reset();
m_sta_spawned_count = 1;
const unsigned int kart_amount = (unsigned int)m_karts.size();
for(unsigned int n=0; n<kart_amount; n++)
{
m_kart_info[n].m_lives = 3;
// no positions in this mode
m_karts[n]->setPosition(-1);
// Eliminate all spare tire karts first, they will be spawned if needed
bool is_sta = false;
if (dynamic_cast<SpareTireAI*>(m_karts[n]->getController()) != NULL)
{
m_kart_info[n].m_lives = -1;
m_karts[n]->setPosition(-1);
m_karts[n]->finishedRace(0.0f);
eliminateKart(n, /*notify_of_elimination*/ false);
is_sta = true;
}
else
{
m_kart_info[n].m_lives = 3;
// no positions in this mode
m_karts[n]->setPosition(-1);
}
scene::ISceneNode* kart_node = m_karts[n]->getNode();
@ -107,11 +160,11 @@ void ThreeStrikesBattle::reset()
if (core::stringc(curr->getName()) == "tire1")
{
curr->setVisible(true);
curr->setVisible(!is_sta);
}
else if (core::stringc(curr->getName()) == "tire2")
{
curr->setVisible(true);
curr->setVisible(!is_sta);
}
}
@ -165,6 +218,15 @@ void ThreeStrikesBattle::kartHit(const unsigned int kart_id)
{
if (isRaceOver()) return;
SpareTireAI* sta =
dynamic_cast<SpareTireAI*>(m_karts[kart_id]->getController());
if (sta)
{
// Unspawn the spare tire kart if it get hit
sta->unspawn();
return;
}
assert(kart_id < m_karts.size());
// make kart lose a life, ignore if in profiling mode
if (!UserConfigParams::m_arena_ai_stats)
@ -305,6 +367,37 @@ void ThreeStrikesBattle::update(float dt)
WorldWithRank::update(dt);
WorldWithRank::updateTrack(dt);
const float period = 20.0f;
if (!m_spare_tire_karts.empty() &&
period < getTimeSinceStart() / float(m_sta_spawned_count))
{
// Spawn spare tire kart when necessary
m_sta_spawned_count++;
// Formula : Total num of karts with life != 3 *
// time period / time since start, so towards the end of game,
// karts are less likely to gain back a life.
int kart_has_few_lives = 0;
for (unsigned int i = 0; i < m_kart_info.size(); i++)
m_kart_info[i].m_lives != 3 ? kart_has_few_lives++ : 0;
float ratio = kart_has_few_lives * period / getTimeSinceStart();
if (ratio > 1.0f)
{
unsigned int spawn_sta = unsigned(ratio);
if (spawn_sta > m_spare_tire_karts.size())
spawn_sta = m_spare_tire_karts.size();
m_race_gui->addMessage(_P("%i spare tire kart has been spawned!",
"%i spare tire karts have been spawned!",
spawn_sta), NULL, 2.0f);
for (unsigned int i = 0; i < spawn_sta; i++)
{
SpareTireAI* sta = dynamic_cast<SpareTireAI*>
(m_spare_tire_karts[i]->getController());
assert(sta);
sta->spawn(period / 2);
}
}
}
if (m_track->hasNavMesh())
updateSectorForKarts();
@ -476,6 +569,8 @@ void ThreeStrikesBattle::getKartsDisplayInfo(
const unsigned int kart_amount = getNumKarts();
for(unsigned int i = 0; i < kart_amount ; i++)
{
if (dynamic_cast<SpareTireAI*>(m_karts[i]->getController()) != NULL)
continue;
RaceGUIBase::KartIconDisplayInfo& rank_info = (*info)[i];
// reset color
@ -509,6 +604,16 @@ void ThreeStrikesBattle::enterRaceOverState()
{
WorldWithRank::enterRaceOverState();
// Unspawn all spare tire karts if neccesary
for (unsigned int i = 0; i < m_spare_tire_karts.size(); i++)
{
SpareTireAI* sta =
dynamic_cast<SpareTireAI*>(m_spare_tire_karts[i]->getController());
assert(sta);
if (sta->needUpdate())
sta->unspawn();
}
if (UserConfigParams::m_arena_ai_stats)
{
float runtime = (irr_driver->getRealTime()-m_start_time)*0.001f;
@ -521,3 +626,15 @@ void ThreeStrikesBattle::enterRaceOverState()
}
} // enterRaceOverState
//-----------------------------------------------------------------------------
bool ThreeStrikesBattle::spareTireKartsSpawned() const
{
// Spare tire karts are spawned if at least 1 of them needs update
assert(!m_spare_tire_karts.empty());
SpareTireAI* sta =
dynamic_cast<SpareTireAI*>(m_spare_tire_karts[0]->getController());
assert(sta);
return sta->needUpdate();
} // spareTireKartsSpawned

View File

@ -77,6 +77,9 @@ private:
int m_start_time;
int m_total_hit;
std::vector<AbstractKart*> m_spare_tire_karts;
int m_sta_spawned_count;
public:
/** Used to show a nice graph when battle is over */
@ -115,6 +118,9 @@ public:
void updateKartRanks();
void increaseRescueCount() { m_total_rescue++; }
void addKartLife(unsigned int id) { m_kart_info[id].m_lives++; }
int getKartLife(unsigned int id) const { return m_kart_info[id].m_lives; }
bool spareTireKartsSpawned() const;
}; // ThreeStrikesBattles

View File

@ -38,6 +38,7 @@
#include "karts/controller/end_controller.hpp"
#include "karts/controller/local_player_controller.hpp"
#include "karts/controller/skidding_ai.hpp"
#include "karts/controller/spare_tire_ai.hpp"
#include "karts/controller/test_ai.hpp"
#include "karts/controller/network_player_controller.hpp"
#include "karts/kart.hpp"
@ -971,8 +972,11 @@ void World::update(float dt)
const int kart_amount = (int)m_karts.size();
for (int i = 0 ; i < kart_amount; ++i)
{
SpareTireAI* sta =
dynamic_cast<SpareTireAI*>(m_karts[i]->getController());
// Update all karts that are not eliminated
if(!m_karts[i]->isEliminated()) m_karts[i]->update(dt) ;
if(!m_karts[i]->isEliminated() || (sta && sta->needUpdate()))
m_karts[i]->update(dt);
}
PROFILER_POP_CPU_MARKER();

View File

@ -18,6 +18,7 @@
#include "modes/world_with_rank.hpp"
#include "karts/abstract_kart.hpp"
#include "karts/controller/spare_tire_ai.hpp"
#include "karts/kart_properties.hpp"
#include "race/history.hpp"
#include "tracks/graph.hpp"
@ -251,7 +252,9 @@ void WorldWithRank::updateSectorForKarts()
assert(n == m_kart_track_sector.size());
for (unsigned int i = 0; i < n; i++)
{
if (m_karts[i]->isEliminated()) continue;
getTrackSector(i)->update(m_karts[i]->getXYZ());
SpareTireAI* sta =
dynamic_cast<SpareTireAI*>(m_karts[i]->getController());
if (!m_karts[i]->isEliminated() || (sta && sta->needUpdate()))
getTrackSector(i)->update(m_karts[i]->getXYZ());
}
} // updateSectorForKarts

View File

@ -182,12 +182,9 @@ void changeCameraTarget(u32 num)
{
AbstractKart* kart = world->getKart(num - 1);
if (kart == NULL) return;
if (kart->isEliminated()) return;
cam->setMode(Camera::CM_NORMAL);
cam->setKart(kart);
}
else
return;
} // changeCameraTarget