From a91af96637c414a24a6c62bad60a8b0f2deb0e62 Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 5 Oct 2016 16:18:39 +0800 Subject: [PATCH] Initial work on spare tire kart in battle mode Some values are hard-coded for now --- sources.cmake | 2 +- src/items/item.hpp | 3 + src/items/item_manager.cpp | 3 + src/karts/controller/arena_ai.cpp | 3 +- src/karts/controller/arena_ai.hpp | 1 + src/karts/controller/battle_ai.cpp | 14 --- src/karts/controller/battle_ai.hpp | 12 +-- src/karts/controller/spare_tire_ai.cpp | 134 +++++++++++++++++++++++++ src/karts/controller/spare_tire_ai.hpp | 50 +++++++++ src/karts/kart.cpp | 5 + src/modes/three_strikes_battle.cpp | 131 ++++++++++++++++++++++-- src/modes/three_strikes_battle.hpp | 6 ++ src/modes/world.cpp | 6 +- src/modes/world_with_rank.cpp | 7 +- src/utils/debug.cpp | 3 - 15 files changed, 344 insertions(+), 36 deletions(-) create mode 100644 src/karts/controller/spare_tire_ai.cpp create mode 100644 src/karts/controller/spare_tire_ai.hpp diff --git a/sources.cmake b/sources.cmake index ddc029d4f..f484b15d5 100644 --- a/sources.cmake +++ b/sources.cmake @@ -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/*") diff --git a/src/items/item.hpp b/src/items/item.hpp index 763b3008c..99b1b3abf 100644 --- a/src/items/item.hpp +++ b/src/items/item.hpp @@ -276,6 +276,9 @@ public: { return (scene::ISceneNode *) m_node; } + // ------------------------------------------------------------------------ + const btQuaternion& getRotation() const { return m_original_rotation; } + }; // class Item #endif diff --git a/src/items/item_manager.cpp b/src/items/item_manager.cpp index 61c5e3232..6afb9733d 100644 --- a/src/items/item_manager.cpp +++ b/src/items/item_manager.cpp @@ -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(kart->getController()) != NULL) return; if( (item->getType() == Item::ITEM_BUBBLEGUM || item->getType() == Item::ITEM_BUBBLEGUM_NOLOK) && kart->isShielded()) { diff --git a/src/karts/controller/arena_ai.cpp b/src/karts/controller/arena_ai.cpp index 0785829ff..675028c3d 100644 --- a/src/karts/controller/arena_ai.cpp +++ b/src/karts/controller/arena_ai.cpp @@ -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 diff --git a/src/karts/controller/arena_ai.hpp b/src/karts/controller/arena_ai.hpp index 40adaaac5..6a772155f 100644 --- a/src/karts/controller/arena_ai.hpp +++ b/src/karts/controller/arena_ai.hpp @@ -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() {}; diff --git a/src/karts/controller/battle_ai.cpp b/src/karts/controller/battle_ai.cpp index f3cfe7aa7..e5106fdfd 100644 --- a/src/karts/controller/battle_ai.cpp +++ b/src/karts/controller/battle_ai.cpp @@ -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) { diff --git a/src/karts/controller/battle_ai.hpp b/src/karts/controller/battle_ai.hpp index 5a89a5112..d95e1144c 100644 --- a/src/karts/controller/battle_ai.hpp +++ b/src/karts/controller/battle_ai.hpp @@ -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 diff --git a/src/karts/controller/spare_tire_ai.cpp b/src/karts/controller/spare_tire_ai.cpp new file mode 100644 index 000000000..21f994e51 --- /dev/null +++ b/src/karts/controller/spare_tire_ai.cpp @@ -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(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 diff --git a/src/karts/controller/spare_tire_ai.hpp b/src/karts/controller/spare_tire_ai.hpp new file mode 100644 index 000000000..a8d7cb76a --- /dev/null +++ b/src/karts/controller/spare_tire_ai.hpp @@ -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 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 diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index 05bbe88ab..ea2ef13b4 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -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(m_controller) != NULL) return; + m_controller->finishedRace(time); m_kart_model->finishedRace(); race_manager->kartFinishedRace(this, time); diff --git a/src/modes/three_strikes_battle.cpp b/src/modes/three_strikes_battle.cpp index f8bf91434..15837238e 100644 --- a/src/modes/three_strikes_battle.cpp +++ b/src/modes/three_strikes_battle.cpp @@ -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 @@ -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; nsetPosition(-1); + // Eliminate all spare tire karts first, they will be spawned if needed + bool is_sta = false; + if (dynamic_cast(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(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 + (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(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(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(m_spare_tire_karts[0]->getController()); + assert(sta); + + return sta->needUpdate(); +} // spareTireKartsSpawned diff --git a/src/modes/three_strikes_battle.hpp b/src/modes/three_strikes_battle.hpp index 0acfffd43..2f839f465 100644 --- a/src/modes/three_strikes_battle.hpp +++ b/src/modes/three_strikes_battle.hpp @@ -77,6 +77,9 @@ private: int m_start_time; int m_total_hit; + std::vector 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 diff --git a/src/modes/world.cpp b/src/modes/world.cpp index 392d7ee83..592f3914b 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -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(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(); diff --git a/src/modes/world_with_rank.cpp b/src/modes/world_with_rank.cpp index cb84ae2da..e03835954 100644 --- a/src/modes/world_with_rank.cpp +++ b/src/modes/world_with_rank.cpp @@ -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(m_karts[i]->getController()); + if (!m_karts[i]->isEliminated() || (sta && sta->needUpdate())) + getTrackSector(i)->update(m_karts[i]->getXYZ()); } } // updateSectorForKarts diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index 7cd03a8c8..a83fbb161 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -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