170 lines
5.9 KiB
C++
170 lines
5.9 KiB
C++
#include "Battle.hpp"
|
|
#include "../EventHooks/EventDataClasses.hpp"
|
|
#include "../Flow/TurnHandler.hpp"
|
|
#include "../Flow/TurnOrdering.hpp"
|
|
|
|
using namespace CreatureLib;
|
|
using namespace CreatureLib::Battling;
|
|
|
|
const ArbUt::BorrowedPtr<const BattleLibrary>& Battle::GetLibrary() const noexcept { return _library; }
|
|
|
|
bool Battle::CanUse(const ArbUt::BorrowedPtr<BaseTurnChoice>& choice) {
|
|
if (choice->GetKind() == TurnChoiceKind::Attack) {
|
|
// HOOK: change number of uses needed.
|
|
return choice.ForceAs<AttackTurnChoice>()->GetAttack()->GetRemainingUses() >= 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Battle::TrySetChoice(BaseTurnChoice* choice) {
|
|
EnsureNotNull(choice)
|
|
if (!CanUse(choice))
|
|
return false;
|
|
Ensure(choice->GetUser()->GetBattleSide().HasValue())
|
|
choice->GetUser()->GetBattleSide().GetValue()->SetChoice(choice);
|
|
CheckChoicesSetAndRun();
|
|
return true;
|
|
}
|
|
|
|
void Battle::CheckChoicesSetAndRun() {
|
|
try {
|
|
for (auto side : _sides) {
|
|
if (!side->AllChoicesSet()) {
|
|
return;
|
|
}
|
|
}
|
|
for (auto side : _sides) {
|
|
if (!side->AllPossibleSlotsFilled()) {
|
|
return;
|
|
}
|
|
}
|
|
} catch (const ArbUt::Exception& e) {
|
|
throw e;
|
|
} catch (const std::exception& e) {
|
|
THROW("Exception during choices set validation: '" << e.what() << "'.")
|
|
}
|
|
|
|
auto begin = std::chrono::steady_clock::now();
|
|
auto choices = std::vector<std::shared_ptr<BaseTurnChoice>>(_numberOfSides * _creaturesPerSide);
|
|
auto i = 0;
|
|
try {
|
|
for (auto side : _sides) {
|
|
for (auto choice : side->GetChoices()) {
|
|
EnsureNotNull(choice)
|
|
if (choice->GetKind() == TurnChoiceKind::Attack) {
|
|
auto attack = std::static_pointer_cast<AttackTurnChoice>(choice);
|
|
uint8_t uses = 1;
|
|
// HOOK: change number of uses needed.
|
|
if (attack->GetAttack()->GetRemainingUses() < uses) {
|
|
choice = std::shared_ptr<BaseTurnChoice>(_library->GetMiscLibrary()->ReplacementAttack(
|
|
choice->GetUser().GetRaw(), attack->GetTarget()));
|
|
}
|
|
// HOOK: Check if we need to change the move
|
|
}
|
|
choice->__SetRandomValue(_random.Get());
|
|
choices[i] = choice;
|
|
i++;
|
|
}
|
|
side->ResetChoices();
|
|
}
|
|
} catch (const std::exception& e) {
|
|
THROW("Exception during turn initialization: '" << e.what() << "'.")
|
|
}
|
|
_currentTurn++;
|
|
try {
|
|
TurnOrdering::OrderChoices(choices);
|
|
} catch (const std::exception& e) {
|
|
THROW("Exception during turn ordering: '" << e.what() << "'.")
|
|
}
|
|
this->_currentTurnQueue = std::make_unique<ChoiceQueue>(choices);
|
|
TriggerEventListener<TurnStartEvent>();
|
|
try {
|
|
TurnHandler::RunTurn(this->_currentTurnQueue);
|
|
} catch (const ArbUt::Exception& e) {
|
|
throw e;
|
|
} catch (const std::exception& e) {
|
|
THROW("Error during running a turn: '" << e.what() << "'.");
|
|
}
|
|
if (this->_currentTurnQueue->HasCompletedQueue) {
|
|
this->_currentTurnQueue = nullptr;
|
|
}
|
|
TriggerEventListener<TurnEndEvent>();
|
|
auto end = std::chrono::steady_clock::now();
|
|
_lastTurnTime = std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count();
|
|
}
|
|
|
|
ArbUt::BorrowedPtr<ChoiceQueue> Battle::GetCurrentTurnQueue() const noexcept { return _currentTurnQueue; }
|
|
|
|
BattleRandom* Battle::GetRandom() noexcept { return &_random; }
|
|
|
|
bool Battle::CreatureInField(ArbUt::BorrowedPtr<Creature> creature) const {
|
|
return std::any_of(_sides.begin(), _sides.end(), [creature](auto c) { return c->CreatureOnSide(creature); });
|
|
}
|
|
|
|
void Battle::ForceRecall(uint8_t side, uint8_t index) { _sides[side]->SetCreature(nullptr, index); }
|
|
|
|
size_t Battle::ScriptCount() const { return 1; }
|
|
void Battle::GetActiveScripts(ArbUt::List<ScriptWrapper>& scripts) {
|
|
scripts.Append(ScriptWrapper::FromSet(&_volatile));
|
|
}
|
|
|
|
void Battle::SwitchCreature(uint8_t sideIndex, uint8_t index, Creature* c) {
|
|
auto side = this->_sides[sideIndex];
|
|
side->SetCreature(c, index);
|
|
}
|
|
|
|
bool Battle::CanSlotBeFilled(uint8_t side, uint8_t index) const {
|
|
for (const auto& party : _parties) {
|
|
if (party->IsResponsibleForIndex(side, index)) {
|
|
if (party->HasCreaturesNotInField())
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Battle::ValidateBattleState() {
|
|
if (_hasEnded) {
|
|
return;
|
|
}
|
|
bool survivingSideExists = false;
|
|
uint8_t winningSide = 255;
|
|
for (uint8_t i = 0; i < _sides.Count(); i++) {
|
|
auto side = _sides[i];
|
|
if (side->HasFled()) {
|
|
this->_battleResult = BattleResult::Inconclusive();
|
|
this->_hasEnded = true;
|
|
return;
|
|
}
|
|
if (!side->IsDefeated()) {
|
|
if (survivingSideExists) {
|
|
return;
|
|
}
|
|
survivingSideExists = true;
|
|
winningSide = i;
|
|
}
|
|
}
|
|
if (!survivingSideExists) {
|
|
this->_battleResult = BattleResult::Inconclusive();
|
|
this->_hasEnded = true;
|
|
return;
|
|
}
|
|
this->_battleResult = BattleResult::Conclusive(winningSide);
|
|
this->_hasEnded = true;
|
|
}
|
|
void Battle::AddVolatileScript(const ArbUt::StringView& key) {
|
|
auto script = _volatile.Get(key);
|
|
if (script.has_value()) {
|
|
script.value()->Stack();
|
|
return;
|
|
}
|
|
script = _library->LoadScript(ScriptCategory::Battle, key);
|
|
if (!script.has_value()) {
|
|
THROW("Invalid volatile script requested for battle: '" << key.c_str() << "'.");
|
|
}
|
|
return _volatile.Add(script.value().GetRaw());
|
|
}
|
|
void Battle::AddVolatileScript(Script* script) { return _volatile.Add(script); }
|
|
void Battle::RemoveVolatileScript(Script* script) { _volatile.Remove(script->GetName()); }
|
|
void Battle::DisplayText(const ArbUt::StringView& text) { TriggerEventListener<DisplayTextEvent>(text); }
|