From 880f31a7c66927420537aeecd546e8da0562a237 Mon Sep 17 00:00:00 2001 From: krzys-h Date: Wed, 17 May 2017 18:22:27 +0200 Subject: [PATCH] Add basics of scoreboard implementation; better support for multiple teams --- src/CMakeLists.txt | 2 + src/graphics/engine/lightning.cpp | 2 +- src/graphics/engine/particle.cpp | 12 +- src/graphics/engine/pyro.cpp | 2 + src/level/robotmain.cpp | 82 +++++++++++- src/level/robotmain.h | 11 ++ src/level/scene_conditions.cpp | 149 ++++++++++++---------- src/level/scene_conditions.h | 23 +++- src/level/scoreboard.cpp | 94 ++++++++++++++ src/level/scoreboard.h | 120 +++++++++++++++++ src/object/interface/damageable_object.h | 4 +- src/object/interface/destroyable_object.h | 3 +- src/object/object_manager.cpp | 4 +- src/object/object_manager.h | 3 +- src/object/old_object.cpp | 19 ++- src/object/old_object.h | 4 +- src/physics/physics.cpp | 5 + 17 files changed, 442 insertions(+), 97 deletions(-) create mode 100644 src/level/scoreboard.cpp create mode 100644 src/level/scoreboard.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 117648f3..e0857506 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -241,6 +241,8 @@ set(BASE_SOURCES level/robotmain.h level/scene_conditions.cpp level/scene_conditions.h + level/scoreboard.cpp + level/scoreboard.h math/all.h math/const.h math/func.h diff --git a/src/graphics/engine/lightning.cpp b/src/graphics/engine/lightning.cpp index 18192fe5..190f5db8 100644 --- a/src/graphics/engine/lightning.cpp +++ b/src/graphics/engine/lightning.cpp @@ -131,7 +131,7 @@ bool CLightning::EventFrame(const Event &event) else { assert(obj->Implements(ObjectInterfaceType::Destroyable)); - dynamic_cast(obj)->DamageObject(DamageType::Lightning); + dynamic_cast(obj)->DamageObject(DamageType::Lightning, std::numeric_limits::infinity()); } } diff --git a/src/graphics/engine/particle.cpp b/src/graphics/engine/particle.cpp index 6bd310e8..e63a8c6a 100644 --- a/src/graphics/engine/particle.cpp +++ b/src/graphics/engine/particle.cpp @@ -954,7 +954,7 @@ void CParticle::FrameParticle(float rTime) m_particle[i].goal = m_particle[i].pos; if (object != nullptr && object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Phazer, 0.002f); + dynamic_cast(object)->DamageObject(DamageType::Phazer, 0.002f, m_particle[i].objFather); } m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration); @@ -1157,7 +1157,7 @@ void CParticle::FrameParticle(float rTime) { if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Fire, 0.001f); + dynamic_cast(object)->DamageObject(DamageType::Fire, 0.001f, m_particle[i].objFather); } m_exploGunCounter++; @@ -1241,7 +1241,7 @@ void CParticle::FrameParticle(float rTime) if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Organic, 0.1f); // starts explosion + dynamic_cast(object)->DamageObject(DamageType::Organic, 0.1f, m_particle[i].objFather); // starts explosion } } } @@ -1286,7 +1286,7 @@ void CParticle::FrameParticle(float rTime) { if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Fire); // starts explosion + dynamic_cast(object)->DamageObject(DamageType::Fire, std::numeric_limits::infinity(), m_particle[i].objFather); // starts explosion } } } @@ -1345,7 +1345,7 @@ void CParticle::FrameParticle(float rTime) { if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Organic, 0.001f); + dynamic_cast(object)->DamageObject(DamageType::Organic, 0.001f, m_particle[i].objFather); } m_exploGunCounter ++; @@ -2423,7 +2423,7 @@ void CParticle::FrameParticle(float rTime) if (object != nullptr) { assert(object->Implements(ObjectInterfaceType::Damageable)); - dynamic_cast(object)->DamageObject(DamageType::Tower); + dynamic_cast(object)->DamageObject(DamageType::Tower, std::numeric_limits::infinity(), m_particle[i].objFather); } } diff --git a/src/graphics/engine/pyro.cpp b/src/graphics/engine/pyro.cpp index 8ac406e8..6f3961af 100644 --- a/src/graphics/engine/pyro.cpp +++ b/src/graphics/engine/pyro.cpp @@ -2296,6 +2296,7 @@ void CPyro::FallProgress(float rTime) if (floor) // reaches the ground? { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); + // TODO: implement "killer"? dynamic_cast(m_object)->DestroyObject(DestructionType::Explosion); } } @@ -2319,6 +2320,7 @@ void CPyro::FallProgress(float rTime) else { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); + // TODO: implement "killer"? dynamic_cast(m_object)->DestroyObject(DestructionType::Explosion); } } diff --git a/src/level/robotmain.cpp b/src/level/robotmain.cpp index 2797e3d3..b723b58c 100644 --- a/src/level/robotmain.cpp +++ b/src/level/robotmain.cpp @@ -55,6 +55,7 @@ #include "level/mainmovie.h" #include "level/player_profile.h" #include "level/scene_conditions.h" +#include "level/scoreboard.h" #include "level/parser/parser.h" @@ -2518,7 +2519,7 @@ bool CRobotMain::EventFrame(const Event &event) if (m_phase == PHASE_SIMUL) { - if (!m_editLock) + if (!m_editLock && !m_engine->GetPause()) { CheckEndMission(true); UpdateAudio(true); @@ -2695,6 +2696,8 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) m_endTakeResearch = 0; m_endTakeWinDelay = 2.0f; m_endTakeLostDelay = 2.0f; + m_teamFinished.clear(); + m_scoreboard.reset(); m_globalMagnifyDamage = 1.0f; m_obligatoryTokens.clear(); m_mapShow = true; @@ -3552,6 +3555,34 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) continue; } + if (line->GetCommand() == "Scoreboard" && !resetObject) + { + if (line->GetParam("enable")->AsBool(false)) + { + // Create the scoreboard + m_scoreboard = MakeUnique(); + } + continue; + } + if (line->GetCommand() == "ScoreboardKillRule" && !resetObject) + { + if (!m_scoreboard) + throw CLevelParserException("ScoreboardKillRule encountered but scoreboard is not enabled"); + auto rule = MakeUnique(); + rule->Read(line.get()); + m_scoreboard->AddKillRule(std::move(rule)); + continue; + } + if (line->GetCommand() == "ScoreboardEndTakeRule" && !resetObject) + { + if (!m_scoreboard) + throw CLevelParserException("ScoreboardEndTakeRule encountered but scoreboard is not enabled"); + auto rule = MakeUnique(); + rule->Read(line.get()); + m_scoreboard->AddEndTakeRule(std::move(rule)); + continue; + } + if (line->GetCommand() == "ObligatoryToken" && !resetObject) { std::string token = line->GetParam("text")->AsString(); @@ -4909,7 +4940,7 @@ Error CRobotMain::ProcessEndMissionTake() int team = it.first; if (team == 0) continue; usesTeamConditions = true; - if (!m_objMan->TeamExists(team)) continue; + if (m_teamFinished[team]) continue; teamCount++; } @@ -4924,8 +4955,32 @@ Error CRobotMain::ProcessEndMissionTake() if (teamCount == 0) { - GetLogger()->Info("All teams died, mission ended with failure\n"); - m_missionResult = INFO_LOST; + GetLogger()->Info("All teams died, mission ended\n"); + if (m_scoreboard) + { + std::string details = ""; + for (auto it : teams) + { + int team = it.first; + if (team == 0) continue; + details += "Team "+boost::lexical_cast(team)+": "+boost::lexical_cast(m_scoreboard->GetScore(team))+" points\n"; + } + m_ui->GetDialog()->StartInformation( + "Results", + "The battle has ended", + details, + false, true, + [&]() { + ChangePhase(PHASE_WIN); + } + ); + m_endTakeWinDelay = 0.0f; + m_missionResult = ERR_OK; + } + else + { + m_missionResult = INFO_LOST; + } } else { @@ -4933,7 +4988,7 @@ Error CRobotMain::ProcessEndMissionTake() { int team = it.first; if (team == 0) continue; - if (!m_objMan->TeamExists(team)) continue; + if (m_teamFinished[team]) continue; Error result = ProcessEndMissionTakeForGroup(it.second); if (result == INFO_LOST || result == INFO_LOSTq) @@ -4944,10 +4999,12 @@ Error CRobotMain::ProcessEndMissionTake() m_displayText->SetEnable(false); // To prevent "bot destroyed" messages m_objMan->DestroyTeam(team); m_displayText->SetEnable(true); + + m_teamFinished[team] = true; } else if (result == ERR_OK) { - if (m_winDelay == 0.0f) + /*if (m_winDelay == 0.0f) { GetLogger()->Info("Team %d won\n", team); @@ -4963,7 +5020,13 @@ Error CRobotMain::ProcessEndMissionTake() m_displayText->SetEnable(false); } m_missionResult = ERR_OK; - return ERR_OK; + return ERR_OK;*/ + GetLogger()->Info("Team %d finished\n", team); + m_displayText->DisplayText(("<<< Team "+boost::lexical_cast(team)+" finished >>>").c_str(), Math::Vector(0.0f,0.0f,0.0f)); + if (m_scoreboard) + m_scoreboard->ProcessEndTake(team); + m_objMan->DestroyTeam(team, DestructionType::Win); + m_teamFinished[team] = true; } } } @@ -5817,3 +5880,8 @@ std::string CRobotMain::GetPreviousFromCommandHistory() return ""; return m_commandHistory[--m_commandHistoryIndex]; } + +CScoreboard* CRobotMain::GetScoreboard() +{ + return m_scoreboard.get(); +} diff --git a/src/level/robotmain.h b/src/level/robotmain.h index dad1bed0..6bde5f6b 100644 --- a/src/level/robotmain.h +++ b/src/level/robotmain.h @@ -87,6 +87,7 @@ class CInput; class CObjectManager; class CSceneEndCondition; class CAudioChangeCondition; +class CScoreboard; class CPlayerProfile; class CSettings; class COldObject; @@ -307,6 +308,10 @@ public: //! Return list of scripts to load to robot created in BotFactory std::vector GetNewScriptNames(ObjectType type); + //! Return the scoreboard manager + //! Note: this may return nullptr if the scoreboard is not enabled! + CScoreboard* GetScoreboard(); + void SelectPlayer(std::string playerName); CPlayerProfile* GetPlayerProfile(); @@ -655,9 +660,15 @@ protected: long m_endTakeResearch = 0; float m_endTakeWinDelay = 0.0f; float m_endTakeLostDelay = 0.0f; + //! Set to true for teams that have already finished + std::map m_teamFinished; std::vector> m_audioChange; + //! The scoreboard + //! If the scoreboard is not enabled for this level, this will be null + std::unique_ptr m_scoreboard; + std::map m_obligatoryTokens; //! Enabled buildings diff --git a/src/level/scene_conditions.cpp b/src/level/scene_conditions.cpp index e475dee9..d4ed1bfc 100644 --- a/src/level/scene_conditions.cpp +++ b/src/level/scene_conditions.cpp @@ -29,11 +29,13 @@ #include "object/interface/powered_object.h" #include "object/interface/transportable_object.h" +#include -void CSceneCondition::Read(CLevelParserLine* line) + +void CObjectCondition::Read(CLevelParserLine* line) { this->pos = line->GetParam("pos")->AsPoint(Math::Vector(0.0f, 0.0f, 0.0f))*g_unit; - this->dist = line->GetParam("dist")->AsFloat(8.0f)*g_unit; + this->dist = line->GetParam("dist")->AsFloat(std::numeric_limits::infinity())*g_unit; this->type = line->GetParam("type")->AsObjectType(OBJECT_NULL); this->powermin = line->GetParam("powermin")->AsFloat(-1); this->powermax = line->GetParam("powermax")->AsFloat(100); @@ -41,6 +43,82 @@ void CSceneCondition::Read(CLevelParserLine* line) this->drive = line->GetParam("drive")->AsDriveType(DriveType::Other); this->countTransported = line->GetParam("countTransported")->AsBool(true); this->team = line->GetParam("team")->AsInt(0); +} + +bool CObjectCondition::CheckForObject(CObject* obj) +{ + if (!this->countTransported) + { + if (IsObjectBeingTransported(obj)) return false; + } + + ObjectType type = obj->GetType(); + + ToolType tool = GetToolFromObject(type); + DriveType drive = GetDriveFromObject(type); + if (this->tool != ToolType::Other && + tool != this->tool) + return false; + + if (this->drive != DriveType::Other && + drive != this->drive) + return false; + + if (this->tool == ToolType::Other && + this->drive == DriveType::Other && + type != this->type && + this->type != OBJECT_NULL) + return false; + + if ((this->team > 0 && obj->GetTeam() != this->team) || + (this->team < 0 && (obj->GetTeam() == -(this->team) || obj->GetTeam() == 0))) + return false; + + float energyLevel = -1; + CPowerContainerObject* power = nullptr; + if (obj->Implements(ObjectInterfaceType::PowerContainer)) + { + power = dynamic_cast(obj); + } + else if (obj->Implements(ObjectInterfaceType::Powered)) + { + CObject* powerObj = dynamic_cast(obj)->GetPower(); + if(powerObj != nullptr && powerObj->Implements(ObjectInterfaceType::PowerContainer)) + { + power = dynamic_cast(powerObj); + } + } + + if (power != nullptr) + { + energyLevel = power->GetEnergy(); + if (power->GetCapacity() > 1.0f) energyLevel *= 10; // TODO: Who designed it like that ?!?! + } + if (energyLevel < this->powermin || energyLevel > this->powermax) return false; + + Math::Vector oPos; + if (IsObjectBeingTransported(obj)) + oPos = dynamic_cast(obj)->GetTransporter()->GetPosition(); + else + oPos = obj->GetPosition(); + oPos.y = 0.0f; + + Math::Vector bPos = this->pos; + bPos.y = 0.0f; + + if (Math::DistanceProjected(oPos, bPos) <= this->dist) + return true; + + return false; +} + +void CSceneCondition::Read(CLevelParserLine* line) +{ + CObjectCondition::Read(line); + + // Scene conditions STILL use a different default value + // See issue #759 + this->dist = line->GetParam("dist")->AsFloat(8.0f)*g_unit; this->min = line->GetParam("min")->AsInt(1); this->max = line->GetParam("max")->AsInt(9999); @@ -48,74 +126,12 @@ void CSceneCondition::Read(CLevelParserLine* line) int CSceneCondition::CountObjects() { - Math::Vector bPos = this->pos; - bPos.y = 0.0f; - - Math::Vector oPos; - int nb = 0; for (CObject* obj : CObjectManager::GetInstancePointer()->GetAllObjects()) { if (!obj->GetActive()) continue; - - if (!this->countTransported) - { - if (IsObjectBeingTransported(obj)) continue; - } - - ObjectType type = obj->GetType(); - - ToolType tool = GetToolFromObject(type); - DriveType drive = GetDriveFromObject(type); - if (this->tool != ToolType::Other && - tool != this->tool) - continue; - - if (this->drive != DriveType::Other && - drive != this->drive) - continue; - - if (this->tool == ToolType::Other && - this->drive == DriveType::Other && - type != this->type && - this->type != OBJECT_NULL) - continue; - - if ((this->team > 0 && obj->GetTeam() != this->team) || - (this->team < 0 && (obj->GetTeam() == -(this->team) || obj->GetTeam() == 0))) - continue; - - float energyLevel = -1; - CPowerContainerObject* power = nullptr; - if (obj->Implements(ObjectInterfaceType::PowerContainer)) - { - power = dynamic_cast(obj); - } - else if (obj->Implements(ObjectInterfaceType::Powered)) - { - CObject* powerObj = dynamic_cast(obj)->GetPower(); - if(powerObj != nullptr && powerObj->Implements(ObjectInterfaceType::PowerContainer)) - { - power = dynamic_cast(powerObj); - } - } - - if (power != nullptr) - { - energyLevel = power->GetEnergy(); - if (power->GetCapacity() > 1.0f) energyLevel *= 10; // TODO: Who designed it like that ?!?! - } - if (energyLevel < this->powermin || energyLevel > this->powermax) continue; - - if (IsObjectBeingTransported(obj)) - oPos = dynamic_cast(obj)->GetTransporter()->GetPosition(); - else - oPos = obj->GetPosition(); - - oPos.y = 0.0f; - - if (Math::DistanceProjected(oPos, bPos) <= this->dist) - nb ++; + if (!CheckForObject(obj)) continue; + nb ++; } return nb; } @@ -126,7 +142,6 @@ bool CSceneCondition::Check() return nb >= this->min && nb <= this->max; } - void CSceneEndCondition::Read(CLevelParserLine* line) { CSceneCondition::Read(line); diff --git a/src/level/scene_conditions.h b/src/level/scene_conditions.h index 258cc7ef..721c486c 100644 --- a/src/level/scene_conditions.h +++ b/src/level/scene_conditions.h @@ -34,12 +34,13 @@ #include "object/tool_type.h" class CLevelParserLine; +class CObject; /** - * \class CSceneCondition - * \brief Base scene condition structure + * \class CObjectCondition + * \brief Base object condition structure */ -class CSceneCondition +class CObjectCondition { public: Math::Vector pos = Math::Vector(0.0f, 0.0f, 0.0f)*g_unit; @@ -52,11 +53,25 @@ public: bool countTransported = true; int team = 0; + //! Read from line in scene file + virtual void Read(CLevelParserLine* line); + + //! Checks if this condition is met + bool CheckForObject(CObject* obj); +}; + +/** + * \class CSceneCondition + * \brief Base scene condition structure + */ +class CSceneCondition : public CObjectCondition +{ +public: int min = 1; // wins if > int max = 9999; // wins if < //! Read from line in scene file - virtual void Read(CLevelParserLine* line); + void Read(CLevelParserLine* line) override; //! Checks if this condition is met bool Check(); diff --git a/src/level/scoreboard.cpp b/src/level/scoreboard.cpp new file mode 100644 index 00000000..7208dd49 --- /dev/null +++ b/src/level/scoreboard.cpp @@ -0,0 +1,94 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2017, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * 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, see http://gnu.org/licenses + */ + +#include "level/scoreboard.h" + +#include "level/parser/parserline.h" + +#include "level/robotmain.h" + +#include "object/object.h" + +#include "ui/displaytext.h" + +#include + +void CScoreboard::CScoreboardRule::Read(CLevelParserLine* line) +{ + this->score = line->GetParam("score")->AsInt(); +} + +void CScoreboard::CScoreboardKillRule::Read(CLevelParserLine* line) +{ + CScoreboardRule::Read(line); + CObjectCondition::Read(line); +} + +void CScoreboard::CScoreboardEndTakeRule::Read(CLevelParserLine* line) +{ + CScoreboardRule::Read(line); + this->team = line->GetParam("team")->AsInt(0); +} + +void CScoreboard::AddKillRule(std::unique_ptr rule) +{ + m_rulesKill.push_back(std::move(rule)); +} + +void CScoreboard::AddEndTakeRule(std::unique_ptr rule) +{ + m_rulesEndTake.push_back(std::move(rule)); +} + +void CScoreboard::ProcessKill(CObject* target, CObject* killer) +{ + if (killer == nullptr) return; + if (killer->GetTeam() == 0) return; + for (auto& rule : m_rulesKill) + { + if ((rule->team == killer->GetTeam() || rule->team == 0) && + rule->CheckForObject(target)) + { + AddPoints(killer->GetTeam(), rule->score); + } + } +} + +void CScoreboard::ProcessEndTake(int team) +{ + for (auto& rule : m_rulesEndTake) + { + if (rule->team == team || rule->team == 0) + { + AddPoints(team, rule->score); + } + } +} + +void CScoreboard::AddPoints(int team, int points) +{ + GetLogger()->Info("Team %d earned %d points\n", team, points); + CRobotMain::GetInstancePointer()->GetDisplayText()->DisplayText(("<<< Team "+boost::lexical_cast(team)+" recieved "+boost::lexical_cast(points)+" points! >>>").c_str(), Math::Vector(0.0f,0.0f,0.0f), 15.0f, 60.0f, 10.0f, Ui::TT_WARNING); + m_score[team] += points; +} + +int CScoreboard::GetScore(int team) +{ + return m_score[team]; +} diff --git a/src/level/scoreboard.h b/src/level/scoreboard.h new file mode 100644 index 00000000..c0c2512b --- /dev/null +++ b/src/level/scoreboard.h @@ -0,0 +1,120 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2017, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * 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, see http://gnu.org/licenses + */ + +/** + * \file level/scoreboard.h + * \brief Code responsible for managing the scoreboard used to score complex code battles + */ + +#pragma once + +#include "level/scene_conditions.h" + +#include +#include +#include + +class CObject; + +/** + * \class CScoreboard + * \brief Scoreboard used to score complex code battles + * + * \todo This is pretty much a work-in-progress hack for Diversity. Be wary of possible API changes. + * + * \todo Proper documentation + * + * \see CRobotMain::GetScoreboard() + * + * \section example Usage example + * \code{.scene} + * Scoreboard enable=true // enable the scoreboard + * ScoreboardKillRule type=WheeledShooter team=1 score=500 // destruction of team 1's WheeledShooter gives 100 points to the team that destroyed it + * ScoreboardKillRule type=TargetBot score=100 // destruction of TargetBot (any team) gives 100 points + * ScoreboardEndTakeRule score=1000 // completion of EndMissionTake objectives for any team results in 1000 points for that team + * \endcode + */ +class CScoreboard +{ +public: + //! Creates the scoreboard + //! The scoreboard exists only if enabled in level file + CScoreboard() {}; + //! Destroys the scoreboard + ~CScoreboard() {}; + +public: + /** + * \class CScoreboardRule + * \brief Base class for scoreboard rules + */ + class CScoreboardRule + { + public: + int score = 0; + + //! Read from line in scene file + virtual void Read(CLevelParserLine* line); + }; + + /** + * \class CScoreboardKillRule + * \brief Scoreboard rule for destroying other objects + * \see CScoreboard::AddKillRule() + */ + class CScoreboardKillRule : public CScoreboardRule, public CObjectCondition + { + public: + //! Read from line in scene file + void Read(CLevelParserLine* line) override; + }; + + /** + * \class CScoreboardEndTakeRule + * \brief Scoreboard rule for EndMissionTake rewards + * \see CScoreboard::AddEndTakeRule() + */ + class CScoreboardEndTakeRule : public CScoreboardRule + { + public: + int team = 0; + + //! Read from line in scene file + void Read(CLevelParserLine* line) override; + }; + +public: + void AddKillRule(std::unique_ptr rule); + void AddEndTakeRule(std::unique_ptr rule); + + //! Called after an object is destroyed by another object + //! \param target The object that has just been destroyed + //! \param killer The object that caused the destruction, can be null + void ProcessKill(CObject* target, CObject* killer = nullptr); + //! Called after EndTake contition has been met, used to handle ScoreboardEndTakeRule + void ProcessEndTake(int team); + + void AddPoints(int team, int points); + int GetScore(int team); + +private: + std::vector> m_rulesKill = {}; + std::vector> m_rulesEndTake = {}; + std::map m_score; +}; \ No newline at end of file diff --git a/src/object/interface/damageable_object.h b/src/object/interface/damageable_object.h index 27b35ca7..0813a286 100644 --- a/src/object/interface/damageable_object.h +++ b/src/object/interface/damageable_object.h @@ -23,6 +23,8 @@ #include +class CObject; + /** * \enum DamageType * \brief Type of damage, for use in CDamageableObject::DamageObject @@ -56,5 +58,5 @@ public: //! Damage the object, with the given force. Returns true if the object has been fully destroyed (assuming the object is destroyable, of course). If force == infinity, destroy immediately (this is the default value) /** NOTE: You should never assume that after this function exits, the object is destroyed, unless it returns true. Even if you specify force = infinity, if may still sometimes decide not to destroy the object. */ - virtual bool DamageObject(DamageType type, float force = std::numeric_limits::infinity()) = 0; + virtual bool DamageObject(DamageType type, float force = std::numeric_limits::infinity(), CObject* killer = nullptr) = 0; }; diff --git a/src/object/interface/destroyable_object.h b/src/object/interface/destroyable_object.h index d3e6ee02..48f4736b 100644 --- a/src/object/interface/destroyable_object.h +++ b/src/object/interface/destroyable_object.h @@ -32,6 +32,7 @@ enum class DestructionType ExplosionWater = 2, //!< explosion underwater Burn = 3, //!< burning Drowned = 4, //!< drowned (only for Me) + Win = 5, //!< used when removing objects from a team that won }; /** @@ -65,7 +66,7 @@ public: //! Destroy the object immediately. Use this only if you are 100% sure this is what you want, because object with magnifyDamage=0 should be able to bypass all damage. It's recommended to use CDamageableObject::DamageObject() instead. /** NOTE: After this function exits, you can assume the object has been definetly destroyed */ - virtual void DestroyObject(DestructionType type) = 0; + virtual void DestroyObject(DestructionType type, CObject* killer = nullptr) = 0; //! Returns the distance modifier for CLightning, used to modify hit probability. Value in range [0..1], where 0 is never and 1 is normal probability virtual float GetLightningHitProbability() = 0; diff --git a/src/object/object_manager.cpp b/src/object/object_manager.cpp index a903a3a4..9483936a 100644 --- a/src/object/object_manager.cpp +++ b/src/object/object_manager.cpp @@ -199,7 +199,7 @@ bool CObjectManager::TeamExists(int team) return false; } -void CObjectManager::DestroyTeam(int team) +void CObjectManager::DestroyTeam(int team, DestructionType destructionType) { assert(team != 0); @@ -209,7 +209,7 @@ void CObjectManager::DestroyTeam(int team) { if (object->Implements(ObjectInterfaceType::Destroyable)) { - dynamic_cast(object)->DestroyObject(DestructionType::Explosion); + dynamic_cast(object)->DestroyObject(destructionType); } else { diff --git a/src/object/object_manager.h b/src/object/object_manager.h index 8a4cbd6e..73ae0015 100644 --- a/src/object/object_manager.h +++ b/src/object/object_manager.h @@ -32,6 +32,7 @@ #include "object/object_create_params.h" #include "object/object_interface_type.h" #include "object/object_type.h" +#include "object/interface/destroyable_object.h" #include #include @@ -180,7 +181,7 @@ public: //! Destroy all objects of team // TODO: This should be probably moved to separate class - void DestroyTeam(int team); + void DestroyTeam(int team, DestructionType destructionType = DestructionType::Explosion); //! Counts all objects implementing given interface int CountObjectsImplementing(ObjectInterfaceType interface); diff --git a/src/object/old_object.cpp b/src/object/old_object.cpp index 5c67ee44..fb510d8d 100644 --- a/src/object/old_object.cpp +++ b/src/object/old_object.cpp @@ -34,6 +34,7 @@ #include "graphics/engine/terrain.h" #include "level/robotmain.h" +#include "level/scoreboard.h" #include "level/parser/parserexceptions.h" #include "level/parser/parserline.h" @@ -335,7 +336,7 @@ void COldObject::Simplify() } -bool COldObject::DamageObject(DamageType type, float force) +bool COldObject::DamageObject(DamageType type, float force, CObject* killer) { assert(Implements(ObjectInterfaceType::Damageable)); assert(!Implements(ObjectInterfaceType::Destroyable) || Implements(ObjectInterfaceType::Shielded) || Implements(ObjectInterfaceType::Fragile)); @@ -355,7 +356,7 @@ bool COldObject::DamageObject(DamageType type, float force) { if ( m_type == OBJECT_BOMB && type != DamageType::Explosive ) return false; // Mine can't be destroyed by shooting - DestroyObject(DestructionType::Explosion); + DestroyObject(DestructionType::Explosion, killer); return true; } @@ -400,11 +401,11 @@ bool COldObject::DamageObject(DamageType type, float force) { if (type == DamageType::Fire) { - DestroyObject(DestructionType::Burn); + DestroyObject(DestructionType::Burn, killer); } else { - DestroyObject(DestructionType::Explosion); + DestroyObject(DestructionType::Explosion, killer); } return true; } @@ -425,7 +426,7 @@ bool COldObject::DamageObject(DamageType type, float force) return false; } -void COldObject::DestroyObject(DestructionType type) +void COldObject::DestroyObject(DestructionType type, CObject* killer) { assert(Implements(ObjectInterfaceType::Destroyable)); @@ -522,6 +523,10 @@ void COldObject::DestroyObject(DestructionType type) { pyroType = Gfx::PT_DEADW; } + else if ( type == DestructionType::Win ) + { + pyroType = Gfx::PT_WPCHECK; + } assert(pyroType != Gfx::PT_NULL); m_engine->GetPyroManager()->Create(pyroType, this); @@ -539,6 +544,10 @@ void COldObject::DestroyObject(DestructionType type) } m_main->RemoveFromSelectionHistory(this); + CScoreboard* scoreboard = m_main->GetScoreboard(); + if (scoreboard) + scoreboard->ProcessKill(this, killer); + m_team = 0; // Back to neutral on destruction if ( m_botVar != nullptr ) diff --git a/src/object/old_object.h b/src/object/old_object.h index b000ac62..bc2d0b0a 100644 --- a/src/object/old_object.h +++ b/src/object/old_object.h @@ -110,8 +110,8 @@ public: void Simplify() override; - bool DamageObject(DamageType type, float force = std::numeric_limits::infinity()) override; - void DestroyObject(DestructionType type) override; + bool DamageObject(DamageType type, float force = std::numeric_limits::infinity(), CObject* killer = nullptr) override; + void DestroyObject(DestructionType type, CObject* killer = nullptr) override; bool EventProcess(const Event& event) override; void UpdateMapping(); diff --git a/src/physics/physics.cpp b/src/physics/physics.cpp index 8fcc08c3..6125a699 100644 --- a/src/physics/physics.cpp +++ b/src/physics/physics.cpp @@ -2701,6 +2701,7 @@ bool CPhysics::ExploOther(ObjectType iType, if ( force > destructionForce ) { + // TODO: implement "killer"? dynamic_cast(pObj)->DamageObject(damageType); } } @@ -2726,6 +2727,7 @@ bool CPhysics::ExploOther(ObjectType iType, oType == OBJECT_HUSTON ) // building? { assert(pObj->Implements(ObjectInterfaceType::Damageable)); + // TODO: implement "killer"? dynamic_cast(pObj)->DamageObject(DamageType::Collision, force/400.0f); } @@ -2756,6 +2758,7 @@ bool CPhysics::ExploOther(ObjectType iType, oType == OBJECT_MOBILEit ) // vehicle? { assert(pObj->Implements(ObjectInterfaceType::Damageable)); + // TODO: implement "killer"? dynamic_cast(pObj)->DamageObject(DamageType::Collision, force/200.0f); } } @@ -2780,6 +2783,7 @@ int CPhysics::ExploHimself(ObjectType iType, ObjectType oType, float force) if ( force > destructionForce && destructionForce >= 0.0f ) { + // TODO: implement "killer"? dynamic_cast(m_object)->DamageObject(DamageType::Explosive); return 2; } @@ -2860,6 +2864,7 @@ int CPhysics::ExploHimself(ObjectType iType, ObjectType oType, float force) force /= 200.0f; } + // TODO: implement "killer"? if ( dynamic_cast(m_object)->DamageObject(DamageType::Collision, force) ) return 2; } }