3 #include <BLIB/Util/Random.hpp>
17 int critChance(
int stage) {
63 int computeDamage(
int pwr,
int atk,
int def,
int userLevel) {
64 const int base = ((2 * userLevel / 5 + 2) * (atk / def) * pwr) / 50 + 2;
65 const float m = bl::util::Random::get<float>(0.85f, 1.f);
66 return static_cast<int>(
static_cast<float>(
base) * m);
91 class BattlerAttackFinalizer {
97 ~BattlerAttackFinalizer() { battler.getSubstate().lastMoveUsed = move; }
107 : currentStageInitialized(false)
108 , finalEffectsApplied(false)
109 , currentFainter(nullptr)
110 , midturnSwitcher(nullptr)
112 , xpAwardRemaining(0)
114 , learnMove(pplmn::
MoveId::Unknown)
117 , switchAfterMove(false)
122 if (!currentStageInitialized) {
123 if (viewSynced && queueEmpty) {
125 currentStageInitialized =
true;
128 else { checkCurrentStage(viewSynced, queueEmpty); }
131 void LocalBattleController::initCurrentStage() {
135 case Stage::WildIntro:
142 case Stage::TrainerIntro:
147 case Stage::NetworkIntro:
152 case Stage::IntroSendInSelf:
159 case Stage::IntroSendInOpponent:
166 case Stage::WaitingChoices:
167 finalEffectsApplied =
false;
172 case Stage::TurnStart:
177 case Stage::PreUseItem:
184 case Stage::UsingItem:
203 case Stage::PeopleballThrown:
211 case Stage::CloneBallThrown:
219 case Stage::PeopleballRocking:
223 case Stage::PeopleballStealFailed:
227 case Stage::CloneBallFailed:
233 case Stage::PeopleballBrokeout:
242 case Stage::BeforeSwitch:
244 else { setBattleState(Stage::NextBattler); }
247 case Stage::Switching:
251 case Stage::AfterSwitch:
259 case Stage::RunFailed:
266 case Stage::Attacking:
270 case Stage::ResolveAfterAttackAbilities:
274 case Stage::ResolveAttackEffect:
278 case Stage::WaitingMidTurnSwitch:
282 case Stage::BeforeMidTurnSwitch:
283 startSwitch(*midturnSwitcher);
286 case Stage::MidTurnSwitching:
290 case Stage::AfterMidTurnSwitch:
291 postSwitch(*midturnSwitcher);
294 case Stage::RoarSwitching:
298 case Stage::AfterRoarSwitch:
299 postSwitch(*midturnSwitcher);
302 case Stage::Fainting:
306 BL_LOG_CRITICAL <<
"In faint state but neither battler has fainted";
307 setBattleState(Stage::Completed);
309 if (currentFainter !=
nullptr) {
311 preFaint(*currentFainter);
315 case Stage::XpAwardPeoplemonBegin:
325 case Stage::XpAwarding:
331 case Stage::LevelingUp: {
333 learnMove = ppl.levelUp();
334 if (ppl.currentLevel() >= ppl.evolveLevel()) { ppl.pendingEvolution() =
true; }
342 case Stage::WaitingLearnMoveChoice:
349 case Stage::WaitingForgetMoveChoice:
353 case Stage::WaitingFaintSwitch:
357 case Stage::FaintSwitching:
361 case Stage::AfterFaintSwitch:
362 postSwitch(*currentFainter);
363 currentFainter =
nullptr;
366 case Stage::RoundFinalEffectsPlayer:
370 case Stage::RoundFinalEffectsEnemy:
374 case Stage::TrainerDefeated:
382 case Stage::PeoplemonCaught:
394 case Stage::PeoplemonCloned:
403 case Stage::ChoosingNickname:
409 case Stage::SavingPeoplemon: {
429 case Stage::NetworkDefeated:
433 case Stage::NetworkLost:
437 case Stage::Whiteout:
442 case Stage::NetworkDisconnect:
443 case Stage::NextBattler:
444 case Stage::Completed:
445 case Stage::RoundStart:
446 case Stage::CheckPlayerContinue:
447 case Stage::WaitingPlayerContinue:
448 case Stage::CheckFaint:
449 case Stage::XpAwardBegin:
450 case Stage::RoundEnd:
451 case Stage::BeforeRun:
454 setBattleState(Stage::Completed);
459 void LocalBattleController::checkCurrentStage(
bool viewSynced,
bool queueEmpty) {
462 if (viewSynced && queueEmpty) {
464 case Stage::WildIntro:
465 setBattleState(Stage::IntroSendInSelf);
468 case Stage::TrainerIntro:
469 setBattleState(Stage::IntroSendInOpponent);
472 case Stage::NetworkIntro:
473 setBattleState(Stage::IntroSendInOpponent);
476 case Stage::IntroSendInSelf:
477 setBattleState(Stage::WaitingChoices);
480 case Stage::IntroSendInOpponent:
481 setBattleState(Stage::IntroSendInSelf);
484 case Stage::WaitingChoices:
486 setBattleState(Stage::RoundStart);
490 case Stage::TurnStart:
493 setBattleState(Stage::Attacking);
496 setBattleState(Stage::PreUseItem);
499 setBattleState(Stage::BeforeRun);
502 setBattleState(Stage::BeforeSwitch);
506 setBattleState(Stage::Completed);
511 case Stage::PreUseItem:
512 setBattleState(Stage::UsingItem);
515 case Stage::UsingItem:
516 setBattleState(Stage::NextBattler);
519 case Stage::PeopleballThrown:
520 setBattleState(Stage::PeopleballRocking);
523 case Stage::PeopleballRocking:
524 if (curShake == 4) { setBattleState(Stage::PeoplemonCaught); }
536 else { setBattleState(Stage::PeopleballBrokeout); }
540 case Stage::CloneBallThrown:
541 setBattleState(Stage::PeoplemonCloned);
544 case Stage::PeopleballBrokeout:
545 case Stage::PeopleballStealFailed:
546 case Stage::CloneBallFailed:
547 setBattleState(Stage::NextBattler);
550 case Stage::BeforeSwitch:
551 setBattleState(Stage::Switching);
554 case Stage::Switching:
555 setBattleState(Stage::AfterSwitch);
558 case Stage::AfterSwitch:
559 setBattleState(Stage::NextBattler);
563 setBattleState(Stage::Completed);
566 case Stage::RunFailed:
567 setBattleState(Stage::NextBattler);
570 case Stage::Attacking:
571 setBattleState(Stage::ResolveAfterAttackAbilities);
574 case Stage::ResolveAfterAttackAbilities:
576 setBattleState(Stage::ResolveAttackEffect);
579 case Stage::ResolveAttackEffect:
581 if (!switchAfterMove) { setBattleState(Stage::NextBattler); }
585 case Stage::WaitingMidTurnSwitch:
586 if (midturnSwitcher->
actionSelected()) { setBattleState(Stage::BeforeMidTurnSwitch); }
589 case Stage::BeforeMidTurnSwitch:
590 setBattleState(Stage::MidTurnSwitching);
593 case Stage::MidTurnSwitching:
594 setBattleState(Stage::AfterMidTurnSwitch);
597 case Stage::AfterMidTurnSwitch:
598 setBattleState(Stage::NextBattler);
601 case Stage::RoarSwitching:
602 setBattleState(Stage::AfterRoarSwitch);
605 case Stage::AfterRoarSwitch:
606 setBattleState(Stage::NextBattler);
609 case Stage::RoundFinalEffectsPlayer:
610 setBattleState(Stage::RoundFinalEffectsEnemy);
613 case Stage::RoundFinalEffectsEnemy:
614 setBattleState(Stage::RoundEnd);
617 case Stage::Fainting:
620 setBattleState(Stage::XpAwardBegin);
622 else { setBattleState(Stage::CheckPlayerContinue); }
625 case Stage::XpAwardPeoplemonBegin:
626 setBattleState(Stage::XpAwarding);
629 case Stage::XpAwarding:
630 if (xpAwardRemaining > 0) { setBattleState(Stage::LevelingUp); }
633 if (xpAwardIndex >= 0) {
634 xpAwardRemaining = xpAward;
636 xpAwardRemaining *= 2;
638 setBattleState(Stage::XpAwardPeoplemonBegin);
640 else { setBattleState(Stage::CheckFaint); }
644 case Stage::LevelingUp:
652 setBattleState(Stage::XpAwarding);
660 setBattleState(Stage::WaitingLearnMoveChoice);
663 else { setBattleState(Stage::XpAwarding); }
666 case Stage::WaitingLearnMoveChoice:
668 setBattleState(Stage::WaitingForgetMoveChoice);
676 setBattleState(Stage::XpAwarding);
680 case Stage::WaitingForgetMoveChoice:
703 setBattleState(Stage::XpAwarding);
706 case Stage::WaitingPlayerContinue:
709 else { setBattleState(Stage::Completed); }
713 case Stage::WaitingFaintSwitch:
714 if (currentFainter->
actionSelected()) { setBattleState(Stage::FaintSwitching); }
717 case Stage::FaintSwitching:
718 setBattleState(Stage::AfterFaintSwitch);
721 case Stage::AfterFaintSwitch:
722 setBattleState(Stage::RoundEnd);
725 case Stage::PeoplemonCaught:
726 case Stage::PeoplemonCloned:
728 else { setBattleState(Stage::SavingPeoplemon); }
731 case Stage::ChoosingNickname:
732 setBattleState(Stage::SavingPeoplemon);
735 case Stage::SavingPeoplemon:
741 case Stage::TrainerDefeated:
742 case Stage::NetworkDefeated:
743 case Stage::NetworkLost:
744 case Stage::Whiteout:
745 setBattleState(Stage::Completed);
748 case Stage::NetworkDisconnect:
749 case Stage::NextBattler:
750 case Stage::Completed:
751 case Stage::RoundStart:
752 case Stage::CheckPlayerContinue:
753 case Stage::CheckFaint:
754 case Stage::XpAwardBegin:
755 case Stage::RoundEnd:
756 case Stage::BeforeRun:
762 setBattleState(Stage::Completed);
771 Stage nns = getNextStage(ns);
774 nns = getNextStage(nns);
778 currentStageInitialized =
false;
785 case Stage::NextBattler:
788 return Stage::Fainting;
796 case Stage::RoundStart: {
803 if (pp < op) { pfirst =
false; }
807 if (eps > lps) { pfirst =
false; }
808 else if (lps == eps) { pfirst = bl::util::Random::get<int>(0, 100) <= 50; }
815 return Stage::TurnStart;
818 case Stage::RoundEnd:
821 return Stage::Fainting;
825 return Stage::WaitingChoices;
828 case Stage::CheckPlayerContinue:
832 return Stage::WaitingPlayerContinue;
834 return Stage::CheckFaint;
836 case Stage::CheckFaint:
837 if (currentFainter->
canFight()) {
return Stage::WaitingFaintSwitch; }
844 return Stage::TrainerDefeated;
846 return Stage::NetworkDefeated;
849 return Stage::Completed;
854 return Stage::Whiteout;
859 case Stage::XpAwardBegin:
863 xpAwardRemaining = xpAward;
865 return xpAwardIndex >= 0 ? Stage::XpAwardPeoplemonBegin : Stage::CheckFaint;
867 case Stage::BeforeRun: {
873 const int odds = ((ps * 128 / os) + 30 * runCount) % 256;
875 #ifdef PEOPLEMON_DEBUG
876 const bool alwaysRun = debug::DebugOverrides::get().alwaysRun ||
884 if (bl::util::Random::get<int>(0, 255) < odds || alwaysRun) {
return Stage::Running; }
885 else {
return Stage::RunFailed; }
888 case Stage::UsingItem:
893 return Stage::CloneBallThrown;
895 else {
return Stage::CloneBallFailed; }
899 else {
return Stage::PeopleballStealFailed; }
901 return Stage::UsingItem;
908 void LocalBattleController::onCommandQueued(
const Command&) {}
910 void LocalBattleController::onCommandProcessed(
const Command&) {}
912 void LocalBattleController::onUpdate(
bool vs,
bool qe) {
updateBattleState(vs, qe); }
914 void LocalBattleController::startUseMove(Battler& user,
int index) {
916 switchAfterMove =
false;
920 pplmn::BattlePeoplemon& defender = victim.activePeoplemon();
922 user.getSubstate().lastMoveIndex = index;
926 if (bl::util::Random::get<int>(0, 100) <= 20) {
927 attacker.clearAilments(
nullptr);
939 if (!user.getSubstate().gotBaked) {
940 if (index < 0 || user.activePeoplemon().base().knownMoves()[index].curPP == 0) {
941 user.activePeoplemon().base().currentHp() = 0;
949 pplmn::OwnedMove& move =
950 !user.getSubstate().gotBaked ? user.activePeoplemon().base().knownMoves()[index] : skimpOut;
951 const bool isChargeSecondTurn = user.getSubstate().chargingMove >= 0;
952 if (!isChargeSecondTurn) { move.curPP -= 1; }
953 else { user.getSubstate().chargingMove = -1; }
962 BattlerAttackFinalizer _finalizer(user, usedMove);
970 const int accStage = attacker.battleStages().acc;
971 const int evdStage = defender.battleStages().evade;
972 const int stage = std::max(std::min(accStage - evdStage, 6), -6);
974 bool hit = bl::util::Random::get<int>(0, 100) <= hitChance || acc == 0;
977 const auto printAttackPrequel = [
this, &move, isChargeSecondTurn, userIsHost]() {
979 if (!isChargeSecondTurn) {
queueCommand({cmd::Message(move.id, userIsHost)},
true); }
985 if (move.id != usedMove) {
991 if (user.getSubstate().ballSet &&
1002 printAttackPrequel();
1008 printAttackPrequel();
1019 if (checkMoveCancelled(
1020 user, victim, index, usedMove, pwr, moveType, effect, isChargeSecondTurn)) {
1026 const std::int16_t r = bl::util::Random::get<std::int16_t>(1, 20);
1028 user.activePeoplemon().base().currentHp() = 1;
1043 const int bc = user.getBroCount();
1044 pwr =
static_cast<float>(pwr) * (
static_cast<float>(bc) * 1.5f);
1049 if (attacker.base().currentHp() <= attacker.currentStats().hp / 4 && pwr > 0) {
1074 user.getSubstate().lastDamageTaken > 0) {
1075 damage = user.getSubstate().lastDamageTaken * 2;
1084 user.getSubstate().lastDamageTaken > 0) {
1085 damage = user.getSubstate().lastDamageTaken * 2;
1094 queueCommand({cmd::Animation(!userIsHost, usedMove)},
true);
1101 int atk = special ? attacker.currentStats().spatk : attacker.currentStats().atk;
1102 int def = special ? defender.currentStats().spdef : defender.currentStats().def;
1104 float effective = getEffectivenessMultiplier(attacker, defender, usedMove, moveType);
1105 bool crit = bl::util::Random::get<int>(0, 100) <= critChance(attacker.battleStages().crit);
1123 atk = atk * 11 / 10;
1129 atk = atk * 11 / 10;
1135 if (attacker.base().currentHp() <= attacker.currentStats().hp / 4) {
1138 atk = atk * 11 / 10;
1144 if (defender.base().currentHp() <= defender.currentStats().hp / 4) {
1147 def = def * 11 / 10;
1152 damage = computeDamage(pwr, atk, def, attacker.base().currentLevel());
1153 damage =
static_cast<int>(
static_cast<float>(damage) * stab * effective);
1154 if (crit) { damage *= 2; }
1160 BL_LOG_INFO <<
pplmn::Move::name(move.id) <<
" (pwr " << pwr <<
") did " << damage
1161 <<
" damage to " << defender.base().name();
1162 applyDamageWithChecks(victim, defender, usedMove, damage);
1165 if (effective > 1.1f) {
1166 victim.getSubstate().lastMoveSuperEffective = usedMove;
1167 victim.getSubstate().preserveLastSuper =
true;
1170 else if (effective < 0.9f) {
1181 bool LocalBattleController::checkMoveCancelled(Battler& user, Battler& victim,
int i,
1186 const bool userIsHost = user.isHost();
1187 const bool victimIsHost = !userIsHost;
1205 if (isJokeMove(move) && teachThisTurn(victim)) {
1213 if (user.getSubstate().turnsConfused <= 0) {
1220 if (bl::util::Random::get<int>(0, 100) <= 50) {
1221 const int atk = user.activePeoplemon().currentStats().atk;
1222 const int def = user.activePeoplemon().currentStats().def;
1223 const int lvl = user.activePeoplemon().base().currentLevel();
1224 const int dmg = computeDamage(40, atk, def, lvl);
1225 user.activePeoplemon().applyDamage(dmg);
1235 if (bl::util::Random::get<int>(0, 100) <= 25) {
1243 if (targetsVictim) {
1244 const float effective =
1246 if (effective < 0.1f) {
1253 if (targetsVictim && victim.getSubstate().isProtected) {
1260 user.getSubstate().chargingMove = i;
1282 void LocalBattleController::applyDamageWithChecks(Battler& victim, pplmn::BattlePeoplemon& ppl,
1284 const bool isHost = victim.isHost();
1287 if (victim.getSubstate().substituteHp > 0) {
1289 victim.getSubstate().substituteHp -= dmg;
1290 if (victim.getSubstate().substituteHp <= 0) {
1292 victim.getSubstate().substituteHp = 0;
1298 if (teachThisTurn(victim) && ppl.base().currentHp() > 1 && dmg >= ppl.base().currentHp()) {
1299 dmg = ppl.base().currentHp() - 1;
1305 dmg >= ppl.base().currentHp()) {
1306 if (bl::util::Random::get<int>(0, 100) <= 10) {
1307 dmg = ppl.base().currentHp() - 1;
1312 ppl.applyDamage(dmg);
1313 victim.getSubstate().lastMoveHitWith = move;
1314 victim.getSubstate().lastDamageTaken = dmg;
1317 if (victim.getSubstate().enduringThisTurn && ppl.base().currentHp() == 0) {
1318 ppl.base().currentHp() = 1;
1323 void LocalBattleController::applyAilmentFromMove(Battler& owner, pplmn::BattlePeoplemon& victim,
1325 const bool isHost = owner.isHost();
1342 if (owner.getSubstate().substituteHp > 0) {
1348 if (owner.getSubstate().turnsGuarded > 0) {
1360 if (victim.giveAilment(ail)) {
1367 if (other.activePeoplemon().giveAilment(ail)) {
1389 void LocalBattleController::applyAilmentFromMove(Battler& owner, pplmn::BattlePeoplemon& victim,
1391 const bool isHost = owner.isHost();
1402 if (owner.getSubstate().substituteHp > 0) {
1409 if (owner.getSubstate().turnsGuarded > 0) {
1415 owner.getSubstate().giveAilment(ail);
1422 void LocalBattleController::doStatChange(pplmn::BattlePeoplemon& ppl,
pplmn::Stat stat,
int amt,
1426 if (ppl.statChange(stat, amt)) {
1439 else if (amt > -2) {
1459 void LocalBattleController::handleBattlerTurnStart(Battler& battler) {
1460 const bool isHost = battler.isHost();
1461 pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1475 if (battler.activePeoplemon().turnsUntilAwake() <= 0) {
1484 void LocalBattleController::handleBattlerRoundEnd(Battler& battler) {
1485 const bool isHost = battler.isHost();
1487 pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1488 battler.notifyTurnEnd();
1490 if (ppl.base().currentHp() == 0) {
1491 battler.getSubstate().ballBlocked =
false;
1492 battler.getSubstate().ballSet =
false;
1493 battler.getSubstate().ballSpiked =
false;
1494 battler.getSubstate().ballSwiped =
false;
1500 const bool ballHandled = battler.getSubstate().ballSet || battler.getSubstate().ballBumped;
1501 if (battler.getSubstate().ballUpTurns >= 0 && !ballHandled) {
1502 battler.activePeoplemon().base().currentHp() = 0;
1508 if (battler.getSubstate().ballUpTurns >= 0 && battler.getSubstate().noOneToGetBall) {
1509 battler.activePeoplemon().base().currentHp() = 0;
1515 if (battler.getSubstate().ballUpTurns >= 3) {
1516 battler.activePeoplemon().base().currentHp() = 0;
1522 if (other.getSubstate().ballUpTurns >= 0) {
1524 if (other.getSubstate().ballSwiped && battler.getSubstate().ballBlocked) {
1525 battler.activePeoplemon().base().currentHp() = 0;
1531 if (other.getSubstate().ballSpiked && !battler.getSubstate().ballBlocked) {
1532 battler.activePeoplemon().base().currentHp() = 0;
1539 if (battler.getSubstate().ballSpiked && other.getSubstate().ballBlocked) {
1540 battler.activePeoplemon().base().currentHp() = 0;
1546 if (battler.getSubstate().ballSwiped && !other.getSubstate().ballBlocked) {
1547 battler.activePeoplemon().base().currentHp() = 0;
1552 battler.getSubstate().ballBlocked =
false;
1553 battler.getSubstate().ballSet =
false;
1554 battler.getSubstate().ballSpiked =
false;
1555 battler.getSubstate().ballSwiped =
false;
1558 if (battler.getSubstate().healNext >= 2) {
1559 ppl.giveHealth(ppl.currentStats().hp / 2);
1562 battler.getSubstate().healNext = -1;
1567 const int dmg = battler.activePeoplemon().currentStats().hp / 16;
1568 other.activePeoplemon().giveHealth(dmg);
1569 ppl.applyDamage(dmg);
1572 if (ppl.base().currentHp() == 0) {
return; }
1577 ppl.applyDamage(ppl.currentStats().hp / 16);
1581 if (ppl.base().currentHp() == 0) {
return; }
1586 ppl.applyDamage(ppl.currentStats().hp / 16 * battler.getSubstate().turnsSticky);
1590 if (ppl.base().currentHp() == 0) {
return; }
1594 if (battler.getSubstate().deathCounter == 0) {
1595 battler.activePeoplemon().base().currentHp() = 0;
1602 if (ppl.base().currentHp() < ppl.currentStats().hp) {
1603 ppl.giveHealth(ppl.currentStats().hp / 16);
1611 ppl.base().currentHp() <= ppl.currentStats().hp / 2) {
1613 ppl.applyDamage(ppl.currentStats().hp / 16);
1620 ppl.base().currentHp() <= ppl.currentStats().hp / 2) {
1622 ppl.giveHealth(ppl.currentStats().hp / 8);
1637 ppl.base().currentHp() = 0;
1640 other.activePeoplemon().applyDamage(
1642 ppl.currentStats().atk,
1643 other.activePeoplemon().currentStats().def,
1644 ppl.base().currentLevel()));
1649 void LocalBattleController::doRoar(Battler& victim) {
1650 const bool isHost = victim.isHost();
1652 if (!victim.canSwitch()) {
1666 midturnSwitcher = &victim;
1669 void LocalBattleController::startSwitch(Battler& battler) {
1670 const bool isHost = battler.isHost();
1676 void LocalBattleController::doSwitch(Battler& battler,
unsigned int newPP) {
1677 const bool isHost = battler.isHost();
1679 pplmn::BattlePeoplemon& oldPpl = battler.activePeoplemon();
1680 const std::int16_t oldIndex = battler.outNowIndex();
1682 battler.setActivePeoplemon(newPP);
1691 if (oldPpl.base().currentHp() > 0 &&
1701 seenPeoplemon.find(&battler.activePeoplemon()) == seenPeoplemon.end()) {
1702 seenPeoplemon.emplace(&battler.activePeoplemon());
1708 void LocalBattleController::postSwitch(Battler& battler) {
1709 const bool isHost = battler.isHost();
1712 if (battler.getSubstate().koReviveHp > 0) {
1713 battler.activePeoplemon().giveHealth(battler.getSubstate().koReviveHp);
1719 if (battler.getSubstate().copyStatsFrom >= 0) {
1720 battler.activePeoplemon().copyStages(
1721 battler.peoplemon()[battler.getSubstate().copyStatsFrom]);
1722 battler.peoplemon()[battler.getSubstate().copyStatsFrom].resetStages();
1724 battler.getSubstate().copyStatsFrom,
1730 checkKlutz(battler);
1733 if (battler.getSubstate().totalMomIndex >= 0) {
1734 auto* moves = battler.activePeoplemon().base().knownMoves();
1735 for (
int i = 0; i < 4; ++i) {
1739 static_cast<std::int16_t
>(battler.getSubstate().totalMomIndex),
1744 battler.getSubstate().notifySwitch();
1747 bool LocalBattleController::isFainter(Battler& b)
const {
1748 if (b.activePeoplemon().base().currentHp() == 0) {
1749 return b.canFight() || !b.getSubstate().faintHandled;
1754 void LocalBattleController::preFaint(Battler& b) {
1755 const bool isHost = b.isHost();
1762 b.getSubstate().totalMomIndex = b.outNowIndex();
1766 bool LocalBattleController::tryMidturnSwitch(Battler& switcher) {
1767 if (!canSwitch(switcher)) {
return false; }
1769 midturnSwitcher = &switcher;
1770 switchAfterMove =
true;
1774 bool LocalBattleController::canSwitch(Battler& switcher) {
1775 const bool isHost = switcher.isHost();
1793 float LocalBattleController::getEffectivenessMultiplier(pplmn::BattlePeoplemon& attacker,
1794 pplmn::BattlePeoplemon& defender,
1809 if (victim.getSubstate().lastMoveSuperEffective == move && e > 1.1f) {
1817 if (e < 0.9f && e > 0.1f) {
1821 else if (e > 1.1f) {
1830 void LocalBattleController::checkKlutz(Battler& battler) {
1831 pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1834 if (bl::util::Random::get<int>(0, 100) <= 33) {
1835 const item::Id item = ppl.base().holdItem();
1845 bool LocalBattleController::teachThisTurn(Battler& battler) {
1846 return battler.chosenAction() ==
TurnAction::Fight && battler.chosenMove() >= 0 &&
1847 isTeachMove(battler.activePeoplemon().base().knownMoves()[battler.chosenMove()].id);
1850 int LocalBattleController::getPriority(Battler& battler) {
1851 const bool isHost = battler.isHost();
1856 battler.activePeoplemon().base().currentHp() <=
1857 battler.activePeoplemon().currentStats().hp / 5) {
1858 battler.getSubstate().gotBaked =
true;
1863 int base = battler.getPriority();
1876 teachThisTurn(battler)) {
1888 int LocalBattleController::getSpeed(Battler& battler) {
1889 const bool isHost = battler.isHost();
1890 pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1891 int spd = ppl.getSpeed();
1895 ppl.base().currentHp() <= ppl.currentStats().hp / 4) {
1897 spd = spd * 11 / 10;
1904 void LocalBattleController::checkAbilitiesAfterMove(Battler& user) {
1905 const bool isHost = user.isHost();
1908 pplmn::BattlePeoplemon& defender = victim.activePeoplemon();
1912 switch (defender.currentAbility()) {
1914 if (contact && damage > 0) {
1915 attacker.applyDamage(damage / 16);
1922 if (!contact && damage > 0) {
1923 attacker.applyDamage(damage / 16);
1930 if (damage > 0 && bl::util::Random::get<int>(0, 100) <= 10) {
1938 if (isTeachMove(usedMove) && bl::util::Random::get<int>(0, 100) <= 10) {
1947 if (!special && damage > 0 && bl::util::Random::get<int>(0, 100) <= 20) {
1954 if (special && damage > 0 && bl::util::Random::get<int>(0, 100) <= 20) {
1961 if (damage > 0 && bl::util::Random::get<int>(0, 100) <= 15) {
1969 pplmn::OwnedMove* move = attacker.base().findMove(usedMove);
1971 move->curPP = (move->curPP) / 2 + (move->curPP % 2);
1982 switch (attacker.currentAbility()) {
1984 if (isTeachMove(usedMove) && bl::util::Random::get<int>(0, 100) <= 15) {
1991 if (isTeachMove(usedMove) && bl::util::Random::get<int>(0, 100) <= 33) {
2003 if (bl::util::Random::get<int>(0, 100) <= 25) {
2014 void LocalBattleController::handleMoveEffect(Battler& user) {
2015 const bool isHost = user.isHost();
2018 pplmn::BattlePeoplemon& defender = victim.activePeoplemon();
2022 (chance > 0 && bl::util::Random::get<int>(0, 100) > chance))
2026 pplmn::BattlePeoplemon& affected = affectsSelf ? attacker : defender;
2027 Battler& affectedOwner = affectsSelf ? user : victim;
2028 Battler& other = affectsSelf ? victim : user;
2030 const bool forHost = affectedOwner.isHost();
2033 if (affected.base().currentHp() == 0 && !doEvenIfDead(effect))
return;
2037 affected.giveHealth(affected.currentStats().hp * intensity / 100);
2042 affected.giveHealth(damage * intensity / 100);
2094 user.getSubstate().isProtected =
true;
2100 if (affectedOwner.getSubstate().turnsGuarded == 0) {
2101 affectedOwner.getSubstate().turnsGuarded = 5;
2108 affected.applyDamage(affected.currentStats().hp / 4);
2109 if (affected.base().currentHp() == 0) {
2112 else if (affectedOwner.getSubstate().substituteHp > 0) {
2116 affectedOwner.getSubstate().substituteHp = affected.currentStats().hp / 4;
2122 bool healed =
false;
2123 for (pplmn::BattlePeoplemon& ppl : affectedOwner.peoplemon()) {
2124 if (ppl.clearAilments(&affectedOwner.getSubstate())) { healed =
true; }
2207 affected.applyDamage(damage * intensity / 100);
2212 affected.base().currentHp() = 0;
2217 if (isPeanutAllergic(affected.base().id())) {
2218 affected.base().currentHp() = 0;
2222 affected.giveHealth(1);
2243 if (affectedOwner.getSubstate().lastMoveIndex >= 0) {
2244 affectedOwner.getSubstate().encoreTurnsLeft = 5;
2245 affectedOwner.getSubstate().encoreMove = affectedOwner.getSubstate().lastMoveIndex;
2251 if (affectedOwner.canSwitch()) {
2252 if (tryMidturnSwitch(affectedOwner)) {
2253 affectedOwner.getSubstate().copyStatsFrom = affectedOwner.outNowIndex();
2261 bool marked =
false;
2278 if (affectedOwner.getSubstate().ballUpTurns >= 0) {
2280 affectedOwner.getSubstate().ballSet =
true;
2281 if (affectedOwner.canSwitch()) { tryMidturnSwitch(affectedOwner); }
2282 else { affectedOwner.getSubstate().noOneToGetBall =
true; }
2288 if (affectedOwner.getSubstate().ballUpTurns < 0) {
2290 affectedOwner.getSubstate().ballUpTurns = 0;
2291 affectedOwner.getSubstate().ballBumped =
true;
2295 affectedOwner.getSubstate().ballBumped =
true;
2297 if (affectedOwner.canSwitch()) { tryMidturnSwitch(affectedOwner); }
2298 else { affectedOwner.getSubstate().noOneToGetBall =
true; }
2302 if (affectedOwner.getSubstate().ballUpTurns >= 0) {
2304 affectedOwner.getSubstate().ballSpiked =
true;
2310 if (other.getSubstate().ballUpTurns >= 0) {
2312 other.getSubstate().ballBlocked =
true;
2318 if (affectedOwner.getSubstate().ballUpTurns >= 0) {
2320 affectedOwner.getSubstate().ballSwiped =
true;
2329 bool oneDead =
false;
2330 for (
const pplmn::BattlePeoplemon& p : affectedOwner.peoplemon()) {
2331 if (p.base().currentHp() == 0 && &p != &affected) {
2337 affectedOwner.getSubstate().koReviveHp = affected.base().currentHp();
2338 affected.base().currentHp() = 0;
2345 if (!affectedOwner.getSubstate().enduredLastTurn) {
2346 affectedOwner.getSubstate().enduringThisTurn =
true;
2364 if (affectedOwner.getSubstate().spikesOut < 3) {
2365 affectedOwner.getSubstate().spikesOut += 1;
2374 other.activePeoplemon().base().knownMoves()[other.getSubstate().lastMoveIndex];
2376 std::max(0,
static_cast<int>(move.curPP) - bl::util::Random::get<int>(2, 5));
2383 if (affectedOwner.getSubstate().healNext < 0) {
2384 affectedOwner.getSubstate().healNext = 0;
2391 doRoar(affectedOwner);
2396 other.getSubstate().spikesOut = 0;
2397 affectedOwner.getSubstate().ballUpTurns = -1;
2398 doRoar(affectedOwner);
2404 affected.clearAilments(&affectedOwner.getSubstate());
2405 affected.base().currentHp() = affected.currentStats().hp;
2412 affected.copyStages(other.activePeoplemon());
2413 other.activePeoplemon().resetStages();
2418 if (affectedOwner.canSwitch()) {
2419 if (tryMidturnSwitch(affectedOwner)) {
2441 BL_LOG_ERROR <<
"Unknown move effect: " << effect;
Id
Represents an item in its simplist form.
@ Peopleball
The item is used to catch peoplemon.
PassiveAilment
Represents a passive ailment a Peoplemon can have.
Stat
Represents a single stat. Used as an offset to access Stats as an array.
Ailment
Represents an ailment that a Peoplemon can have.
Type
Represents a type that a move or Peoplemon can be. Types may be combined.
MoveEffect
Represents an effect that a move can have. Copied verbatim from Peoplemon 2, may refactor later when ...
Core classes and functionality for both the editor and game.
const std::string location
void queueCommand(Command &&command, bool addWait=false)
Enqueues a command to manipulate battle state or the view.
void updateBattleState(bool viewSynced, bool queueEmpty)
Updates the battle state based on the previous state, view state, and queue state.
LocalBattleController()
Construct a new Local Battle Controller.
void startChooseToContinue()
Begins to decide whether or not to continue.
void pickPeoplemon(bool fromFaint, bool reviveOnly)
Initiates the process of selecting a replacement peoplemon if the current one faints.
bool actionSelected() const
Returns whether or not the battler has chosen what to do on this turn.
bool removeItem(item::Id item)
Removes the given item from the battler's inventory.
int getNextXpEarnerIndex(int ci)
Returns the index of the next peoplemon that should earn XP.
bool shouldContinue() const
Returns whether or not the player has chosen to continue.
BattlerSubstate & getSubstate()
Returns the state of this battler.
bool canFight() const
Returns true if the battler has at least one non-fainted peoplemon.
core::pplmn::BattlePeoplemon & activePeoplemon()
Returns the peoplemon that is currently out.
bool isHost() const
Returns whether or not this battler is the host.
TurnAction chosenAction() const
Returns the action the battler is using this turn.
int xpEarnerCount() const
Returns how many of the current peoplemon should earn exp.
std::uint8_t chosenPeoplemon() const
Returns the peoplemon the battler is switching to this turn.
unsigned int selectRandomPeoplemon() const
Returns the index of a random, living Peoplemon that is not currently out.
int prizeMoney() const
Returns the amount of prize money awarded for defeating this battler.
int chosenMove() const
Returns the move the battler is using this turn.
core::item::Id chosenItem() const
Returns the item the battler is using this turn.
int getFirstXpEarner() const
Returns the index of the first peoplemon that should earn XP.
void pickAction()
Initiates the process of selecting what to do on this turn.
void resetXpEarners()
Resets who earns XP.
std::vector< core::pplmn::BattlePeoplemon > & peoplemon()
Returns all the peoplemon held by this battler.
Battler & inactiveBattler()
Returns the battler who is currently not resolving their turn.
Stage currentStage() const
Returns the current stage the battle is in.
Battler & hostBattler()
Returns the battler that is the host.
Battler & enemy()
Returns the opponent Battler.
void setStage(Stage stage)
Sets the current stage of the battle.
void beginRound(bool playerIsFirst)
Begins the next round of turns and sets the battler order.
bool isFirstMover() const
Returns whether the current mover is the first peoplemon to go this round.
Battler & activeBattler()
Returns the battler that is current resolving their turn.
Battler & localPlayer()
Returns the local player Battler.
Stage nextTurn()
Moves on to the next battler's turn. Returns the stage to transition to.
Stage
Represents all possible states in a battle.
Battler & nonHostBattler()
Returns the battler who is not the host.
view::PlayerMenu & menu()
Access the player's menu.
bool playerChoseToSetName() const
Returns whether or not the player chose to set a nickname on a new Peoplemon.
bool playerChoseForgetMove() const
Returns whether or not the player chose to forget a move when prompted.
const std::string & chosenNickname() const
Returns the nickname the player entered.
@ ClassyFrustratedBlocked
@ AilmentSatPassiveAilmentBlocked
@ SubstitutePassiveAilmentBlocked
@ GainedAilmentSnackshare
@ ExperiencedTeachAbility
@ GuardBlockedPassiveAilment
@ SubstituteAilmentBlocked
@ SubstituteAlreadyExists
void chooseMoveToForget()
Initiates the process of choosing the move to forget.
int selectedMove() const
Returns the selected move index.
static Type getType(Id item)
Returns the type of the given item.
static void useOnPeoplemon(Id item, pplmn::OwnedPeoplemon &ppl, std::vector< pplmn::OwnedPeoplemon > *team=nullptr, std::vector< pplmn::BattlePeoplemon > *battleTeam=nullptr)
Applies the given item to the peoplemon.
static bool hasEffectOnPeoplemon(Id item, const pplmn::OwnedPeoplemon &ppl)
Returns whether or not the given item will affect the peoplemon.
OwnedPeoplemon & base()
Returns the wrapped peoplemon.
const Stats & currentStats() const
Returns the current stats of the peoplemon, including changes.
void notifyInBattle()
Lets the peoplemon know that they have seen battle.
SpecialAbility currentAbility() const
Returns the current ability of this peoplemon.
static float getAccuracyMultiplier(int adjustedStage)
Returns the accuracy multiplier for a move hitting.
static int damage(MoveId move)
Returns the damage of the given move.
static int accuracy(MoveId move)
Returns the accuracy of the given move.
static int priority(MoveId move)
Returns the priority of the given move.
static bool makesContact(MoveId move)
Returns whether or not the given move makes physical contact when used.
static int effectIntensity(MoveId move)
Returns the effect intensity of the given move.
static MoveEffect effect(MoveId move)
Returns the effect of the given move.
static MoveId getRandomMove(bool allowRandomEffect=false)
Returns a random, valid move. Optionally filters moves that have the Random effect.
static const std::string & name(MoveId move)
Returns the name of the given move.
static Type type(MoveId move)
Returns the type of the given move.
static int effectChance(MoveId move)
Returns the effect chance of the given move.
static bool isSpecial(MoveId move)
Returns whether or not the given move is special.
static bool affectsUser(MoveId move)
Returns whether or not the given move affects the user or the opponent.
void setCustomName(const std::string &name)
Sets a custom name for this peoplemon.
bool canClone() const
Returns whether or not this peoplemon can be cloned.
std::uint16_t & currentHp()
Access the current HP.
unsigned int currentLevel() const
Returns the current level of this peoplemon.
Id id() const
Returns the id of this peoplemon.
bool shakePasses(item::Id ball, int turnNumber, unsigned int opLevel)
Performs the check that a shake would occur with the given peopleball.
unsigned int xpYield(bool isTrainer) const
Returns the XP awarded by defeating this peoplemon.
static const Stats & evAward(Id id)
Returns the ev points awarded for defeating the given peoplemon.
static float getStab(Type moveType, Type userType)
Returns the STAB multiplier for a move.
static float getSuperMult(Type moveType, Type defenderType)
Returns the multiplier for if/when a move is super effective.
void addItem(item::Id item, unsigned int qty=1)
Adds the given item to the bag.
void registerSighting(pplmn::Id peoplemon, const std::string &location)
Registers a sighting of the given peoplemon at the given location.
std::vector< pplmn::OwnedPeoplemon > peoplemon
bool add(const pplmn::OwnedPeoplemon &ppl)
Adds the given peoplemon to storage in a free slot.
player::State & state()
Returns the state of the player.