Peoplemon  0.1.0
Peoplemon 3 game source documentation
LocalBattleController.cpp
Go to the documentation of this file.
2 
3 #include <BLIB/Util/Random.hpp>
8 #include <Core/Items/Item.hpp>
10 
11 namespace core
12 {
13 namespace battle
14 {
15 namespace
16 {
17 int critChance(int stage) {
18  switch (stage) {
19  case 0:
20  return 7;
21  case 1:
22  return 13;
23  case 2:
24  return 25;
25  case 3:
26  return 33;
27  default:
28  return 50;
29  }
30 }
31 
32 bool isPeanutAllergic(pplmn::Id ppl) {
33  using Id = pplmn::Id;
34  switch (ppl) {
35  case Id::EmanualA:
36  case Id::EmanualB:
37  case Id::EmanualC:
38  case Id::QuinnA:
39  case Id::QuinnB:
40  case Id::QuinnC:
41  case Id::MattA:
42  case Id::MattB:
43  case Id::MattC:
44  case Id::JudasA:
45  case Id::JudasB:
46  return true;
47  default:
48  return false;
49  }
50 }
51 
52 bool doEvenIfDead(pplmn::MoveEffect effect) {
53  using E = pplmn::MoveEffect;
54 
55  switch (effect) {
56  case E::WakeBoth:
57  return true;
58  default:
59  return false;
60  }
61 }
62 
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);
67 }
68 
69 bool isTeachMove(pplmn::MoveId move) {
70  switch (move) {
75  return true;
76  default:
77  return false;
78  }
79 }
80 
81 bool isJokeMove(pplmn::MoveId move) {
82  switch (move) {
85  return true;
86  default:
87  return false;
88  }
89 }
90 
91 class BattlerAttackFinalizer {
92 public:
93  BattlerAttackFinalizer(Battler& b, pplmn::MoveId move)
94  : battler(b)
95  , move(move) {}
96 
97  ~BattlerAttackFinalizer() { battler.getSubstate().lastMoveUsed = move; }
98 
99 private:
100  Battler& battler;
101  const pplmn::MoveId move;
102 };
103 
104 } // namespace
105 
107 : currentStageInitialized(false)
108 , finalEffectsApplied(false)
109 , currentFainter(nullptr)
110 , midturnSwitcher(nullptr)
111 , xpAward(0)
112 , xpAwardRemaining(0)
113 , xpAwardIndex(-1)
114 , learnMove(pplmn::MoveId::Unknown)
115 , firstTurn(true)
116 , runCount(0)
117 , switchAfterMove(false)
118 , turnCounter(0)
119 , curShake(0) {}
120 
121 void LocalBattleController::updateBattleState(bool viewSynced, bool queueEmpty) {
122  if (!currentStageInitialized) {
123  if (viewSynced && queueEmpty) {
124  initCurrentStage();
125  currentStageInitialized = true;
126  }
127  }
128  else { checkCurrentStage(viewSynced, queueEmpty); }
129 }
130 
131 void LocalBattleController::initCurrentStage() {
132  using Stage = BattleState::Stage;
133 
134  switch (state->currentStage()) {
135  case Stage::WildIntro:
137  queueCommand(
138  {cmd::Animation(state->enemy().isHost(), cmd::Animation::Type::MakeWildVisible)});
139  queueCommand({cmd::Message(cmd::Message::Type::WildIntro)}, true);
140  break;
141 
142  case Stage::TrainerIntro:
143  // TODO - show image somehow
144  queueCommand({cmd::Message(cmd::Message::Type::TrainerIntro)}, true);
145  break;
146 
147  case Stage::NetworkIntro:
148  // TODO - show image?
149  queueCommand({cmd::Message(cmd::Message::Type::NetworkIntro)}, true);
150  break;
151 
152  case Stage::IntroSendInSelf:
156  checkKlutz(state->localPlayer());
157  break;
158 
159  case Stage::IntroSendInOpponent:
163  checkKlutz(state->enemy());
164  break;
165 
166  case Stage::WaitingChoices:
167  finalEffectsApplied = false;
169  state->enemy().pickAction();
170  break;
171 
172  case Stage::TurnStart:
173  ++turnCounter;
174  handleBattlerTurnStart(state->activeBattler());
175  break;
176 
177  case Stage::PreUseItem:
180  state->activeBattler().isHost())},
181  true);
182  break;
183 
184  case Stage::UsingItem:
188  state->activeBattler())) {
192  state->activeBattler().isHost())});
196  state->activeBattler());
199  }
200  else { queueCommand({cmd::Message(cmd::Message::Type::ItemNoEffect)}, true); }
201  break;
202 
203  case Stage::PeopleballThrown:
205  queueCommand({cmd::Animation(!state->activeBattler().isHost(),
208  true);
209  break;
210 
211  case Stage::CloneBallThrown:
213  queueCommand({cmd::Animation(!state->activeBattler().isHost(),
216  true);
217  break;
218 
219  case Stage::PeopleballRocking:
220  curShake = 0;
221  break;
222 
223  case Stage::PeopleballStealFailed:
225  break;
226 
227  case Stage::CloneBallFailed:
228  queueCommand(
230  true);
231  break;
232 
233  case Stage::PeopleballBrokeout:
234  queueCommand({cmd::Animation(!state->activeBattler().isHost(),
238  !state->activeBattler().isHost())},
239  true);
240  break;
241 
242  case Stage::BeforeSwitch:
243  if (canSwitch(state->activeBattler())) { startSwitch(state->activeBattler()); }
244  else { setBattleState(Stage::NextBattler); }
245  break;
246 
247  case Stage::Switching:
249  break;
250 
251  case Stage::AfterSwitch:
252  postSwitch(state->activeBattler());
253  break;
254 
255  case Stage::Running:
256  queueCommand({cmd::Message(cmd::Message::Type::RunAway)}, true);
257  break;
258 
259  case Stage::RunFailed:
261  queueCommand({cmd::Message(cmd::Message::Type::RunFailedNotWild)}, true);
262  }
263  else { queueCommand({cmd::Message(cmd::Message::Type::RunFailed)}, true); }
264  break;
265 
266  case Stage::Attacking:
267  startUseMove(state->activeBattler(), state->activeBattler().chosenMove());
268  break;
269 
270  case Stage::ResolveAfterAttackAbilities:
271  checkAbilitiesAfterMove(state->activeBattler());
272  break;
273 
274  case Stage::ResolveAttackEffect:
275  handleMoveEffect(state->activeBattler());
276  break;
277 
278  case Stage::WaitingMidTurnSwitch:
279  midturnSwitcher->pickPeoplemon(false, false);
280  break;
281 
282  case Stage::BeforeMidTurnSwitch:
283  startSwitch(*midturnSwitcher);
284  break;
285 
286  case Stage::MidTurnSwitching:
287  doSwitch(*midturnSwitcher, midturnSwitcher->chosenPeoplemon());
288  break;
289 
290  case Stage::AfterMidTurnSwitch:
291  postSwitch(*midturnSwitcher);
292  break;
293 
294  case Stage::RoarSwitching:
295  doSwitch(*midturnSwitcher, midturnSwitcher->selectRandomPeoplemon());
296  break;
297 
298  case Stage::AfterRoarSwitch:
299  postSwitch(*midturnSwitcher);
300  break;
301 
302  case Stage::Fainting:
303  if (isFainter(state->inactiveBattler())) { currentFainter = &state->inactiveBattler(); }
304  else if (isFainter(state->activeBattler())) { currentFainter = &state->activeBattler(); }
305  else {
306  BL_LOG_CRITICAL << "In faint state but neither battler has fainted";
307  setBattleState(Stage::Completed);
308  }
309  if (currentFainter != nullptr) {
310  currentFainter->getSubstate().faintHandled = true;
311  preFaint(*currentFainter);
312  }
313  break;
314 
315  case Stage::XpAwardPeoplemonBegin:
316  state->localPlayer().peoplemon()[xpAwardIndex].base().awardEVs(
317  pplmn::Peoplemon::evAward(currentFainter->activePeoplemon().base().id()));
319  xpAwardIndex,
320  xpAwardRemaining,
321  state->localPlayer().isHost())},
322  true);
323  break;
324 
325  case Stage::XpAwarding:
326  xpAwardRemaining =
327  state->localPlayer().peoplemon()[xpAwardIndex].base().awardXP(xpAwardRemaining);
329  break;
330 
331  case Stage::LevelingUp: {
332  auto& ppl = state->localPlayer().peoplemon()[xpAwardIndex].base();
333  learnMove = ppl.levelUp();
334  if (ppl.currentLevel() >= ppl.evolveLevel()) { ppl.pendingEvolution() = true; }
335  queueCommand({cmd::Message(
336  cmd::Message::Type::LevelUp, xpAwardIndex, state->localPlayer().isHost())},
337  true);
339  // TODO - do we want to show stat increase box?
340  } break;
341 
342  case Stage::WaitingLearnMoveChoice:
344  learnMove,
345  state->localPlayer().isHost())},
346  true);
347  break;
348 
349  case Stage::WaitingForgetMoveChoice:
351  break;
352 
353  case Stage::WaitingFaintSwitch:
354  currentFainter->pickPeoplemon(true, currentFainter->getSubstate().koReviveHp > 0);
355  break;
356 
357  case Stage::FaintSwitching:
358  doSwitch(*currentFainter, currentFainter->chosenPeoplemon());
359  break;
360 
361  case Stage::AfterFaintSwitch:
362  postSwitch(*currentFainter);
363  currentFainter = nullptr;
364  break;
365 
366  case Stage::RoundFinalEffectsPlayer:
367  handleBattlerRoundEnd(state->localPlayer());
368  break;
369 
370  case Stage::RoundFinalEffectsEnemy:
371  handleBattlerRoundEnd(state->enemy());
372  break;
373 
374  case Stage::TrainerDefeated:
375  battle->result.localPlayerWon = true;
377  queueCommand({cmd::Message(cmd::Message::Type::TrainerLost)}, true);
378  queueCommand(
379  {cmd::Message(cmd::Message::Type::WonMoney, state->enemy().prizeMoney(), false)}, true);
380  break;
381 
382  case Stage::PeoplemonCaught:
383  battle->result.localPlayerWon = true;
385  state->inactiveBattler().isHost())});
386  queueCommand({cmd::Animation(state->inactiveBattler().isHost(),
388  true);
389  queueCommand(
391  true);
392  break;
393 
394  case Stage::PeoplemonCloned:
395  queueCommand(
397  true);
398  queueCommand(
400  true);
401  break;
402 
403  case Stage::ChoosingNickname:
404  queueCommand(
406  true);
407  break;
408 
409  case Stage::SavingPeoplemon: {
410  pplmn::OwnedPeoplemon caught = state->inactiveBattler().activePeoplemon().base();
412  if (!battle->view.chosenNickname().empty()) {
414  }
415  }
416  if (battle->player.state().peoplemon.size() < 6) {
417  battle->player.state().peoplemon.emplace_back(std::move(caught));
418  }
419  else {
420  if (battle->player.state().storage.add(caught)) {
423  true);
424  }
425  else { queueCommand({cmd::Message(cmd::Message::Type::StorageFailed)}, true); }
426  }
427  } break;
428 
429  case Stage::NetworkDefeated:
430  battle->result.localPlayerWon = true;
431  [[fallthrough]];
432 
433  case Stage::NetworkLost:
434  queueCommand({cmd::Message(cmd::Message::Type::NetworkWinLose)}, true);
435  break;
436 
437  case Stage::Whiteout:
438  queueCommand({cmd::Message(cmd::Message::Type::WhiteoutA)}, true);
439  queueCommand({cmd::Message(cmd::Message::Type::WhiteoutB)}, true);
440  break;
441 
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:
452  default:
453  BL_LOG_CRITICAL << "Invalid battle stage: " << state->currentStage();
454  setBattleState(Stage::Completed);
455  break;
456  }
457 }
458 
459 void LocalBattleController::checkCurrentStage(bool viewSynced, bool queueEmpty) {
460  using Stage = BattleState::Stage;
461 
462  if (viewSynced && queueEmpty) {
463  switch (state->currentStage()) {
464  case Stage::WildIntro:
465  setBattleState(Stage::IntroSendInSelf);
466  break;
467 
468  case Stage::TrainerIntro:
469  setBattleState(Stage::IntroSendInOpponent);
470  break;
471 
472  case Stage::NetworkIntro:
473  setBattleState(Stage::IntroSendInOpponent);
474  break;
475 
476  case Stage::IntroSendInSelf:
477  setBattleState(Stage::WaitingChoices);
478  break;
479 
480  case Stage::IntroSendInOpponent:
481  setBattleState(Stage::IntroSendInSelf);
482  break;
483 
484  case Stage::WaitingChoices:
486  setBattleState(Stage::RoundStart);
487  }
488  break;
489 
490  case Stage::TurnStart:
491  switch (state->activeBattler().chosenAction()) {
492  case TurnAction::Fight:
493  setBattleState(Stage::Attacking);
494  break;
495  case TurnAction::Item:
496  setBattleState(Stage::PreUseItem);
497  break;
498  case TurnAction::Run:
499  setBattleState(Stage::BeforeRun);
500  break;
501  case TurnAction::Switch:
502  setBattleState(Stage::BeforeSwitch);
503  break;
504  default:
505  BL_LOG_ERROR << "Unknown turn action: " << state->activeBattler().chosenAction();
506  setBattleState(Stage::Completed);
507  break;
508  }
509  break;
510 
511  case Stage::PreUseItem:
512  setBattleState(Stage::UsingItem);
513  break;
514 
515  case Stage::UsingItem:
516  setBattleState(Stage::NextBattler);
517  break;
518 
519  case Stage::PeopleballThrown:
520  setBattleState(Stage::PeopleballRocking);
521  break;
522 
523  case Stage::PeopleballRocking:
524  if (curShake == 4) { setBattleState(Stage::PeoplemonCaught); }
525  else {
528  turnCounter,
530  ++curShake;
531  queueCommand({cmd::Animation(state->inactiveBattler().isHost(),
534  true);
535  }
536  else { setBattleState(Stage::PeopleballBrokeout); }
537  }
538  break;
539 
540  case Stage::CloneBallThrown:
541  setBattleState(Stage::PeoplemonCloned);
542  break;
543 
544  case Stage::PeopleballBrokeout:
545  case Stage::PeopleballStealFailed:
546  case Stage::CloneBallFailed:
547  setBattleState(Stage::NextBattler);
548  break;
549 
550  case Stage::BeforeSwitch:
551  setBattleState(Stage::Switching);
552  break;
553 
554  case Stage::Switching:
555  setBattleState(Stage::AfterSwitch);
556  break;
557 
558  case Stage::AfterSwitch:
559  setBattleState(Stage::NextBattler);
560  break;
561 
562  case Stage::Running:
563  setBattleState(Stage::Completed);
564  break;
565 
566  case Stage::RunFailed:
567  setBattleState(Stage::NextBattler);
568  break;
569 
570  case Stage::Attacking:
571  setBattleState(Stage::ResolveAfterAttackAbilities);
572  break;
573 
574  case Stage::ResolveAfterAttackAbilities:
576  setBattleState(Stage::ResolveAttackEffect);
577  break;
578 
579  case Stage::ResolveAttackEffect:
581  if (!switchAfterMove) { setBattleState(Stage::NextBattler); }
582  else { setBattleState(BattleState::Stage::WaitingMidTurnSwitch); }
583  break;
584 
585  case Stage::WaitingMidTurnSwitch:
586  if (midturnSwitcher->actionSelected()) { setBattleState(Stage::BeforeMidTurnSwitch); }
587  break;
588 
589  case Stage::BeforeMidTurnSwitch:
590  setBattleState(Stage::MidTurnSwitching);
591  break;
592 
593  case Stage::MidTurnSwitching:
594  setBattleState(Stage::AfterMidTurnSwitch);
595  break;
596 
597  case Stage::AfterMidTurnSwitch:
598  setBattleState(Stage::NextBattler);
599  break;
600 
601  case Stage::RoarSwitching:
602  setBattleState(Stage::AfterRoarSwitch);
603  break;
604 
605  case Stage::AfterRoarSwitch:
606  setBattleState(Stage::NextBattler);
607  break;
608 
609  case Stage::RoundFinalEffectsPlayer:
610  setBattleState(Stage::RoundFinalEffectsEnemy);
611  break;
612 
613  case Stage::RoundFinalEffectsEnemy:
614  setBattleState(Stage::RoundEnd);
615  break;
616 
617  case Stage::Fainting:
618  if (currentFainter != &state->localPlayer() && battle->type != Battle::Type::Online &&
620  setBattleState(Stage::XpAwardBegin);
621  }
622  else { setBattleState(Stage::CheckPlayerContinue); }
623  break;
624 
625  case Stage::XpAwardPeoplemonBegin:
626  setBattleState(Stage::XpAwarding);
627  break;
628 
629  case Stage::XpAwarding:
630  if (xpAwardRemaining > 0) { setBattleState(Stage::LevelingUp); }
631  else {
632  xpAwardIndex = state->localPlayer().getNextXpEarnerIndex(xpAwardIndex);
633  if (xpAwardIndex >= 0) {
634  xpAwardRemaining = xpAward;
635  if (state->localPlayer().peoplemon()[xpAwardIndex].base().hasExpShare()) {
636  xpAwardRemaining *= 2;
637  }
638  setBattleState(Stage::XpAwardPeoplemonBegin);
639  }
640  else { setBattleState(Stage::CheckFaint); }
641  }
642  break;
643 
644  case Stage::LevelingUp:
645  if (learnMove != pplmn::MoveId::Unknown) {
646  if (state->localPlayer().peoplemon()[xpAwardIndex].base().gainMove(learnMove)) {
648  xpAwardIndex,
649  learnMove,
650  state->localPlayer().isHost())},
651  true);
652  setBattleState(Stage::XpAwarding);
653  }
654  else {
656  xpAwardIndex,
657  learnMove,
658  state->localPlayer().isHost())},
659  true);
660  setBattleState(Stage::WaitingLearnMoveChoice);
661  }
662  }
663  else { setBattleState(Stage::XpAwarding); }
664  break;
665 
666  case Stage::WaitingLearnMoveChoice:
668  setBattleState(Stage::WaitingForgetMoveChoice);
669  }
670  else {
672  xpAwardIndex,
673  learnMove,
674  state->localPlayer().isHost())},
675  true);
676  setBattleState(Stage::XpAwarding);
677  }
678  break;
679 
680  case Stage::WaitingForgetMoveChoice:
681  if (battle->view.menu().selectedMove() >= 0) {
682  pplmn::BattlePeoplemon& ppl = state->localPlayer().peoplemon()[xpAwardIndex];
683  pplmn::OwnedMove& m = ppl.base().knownMoves()[battle->view.menu().selectedMove()];
685  xpAwardIndex,
686  m.id,
687  state->localPlayer().isHost())},
688  true);
689  ppl.base().learnMove(learnMove, battle->view.menu().selectedMove());
691  xpAwardIndex,
692  learnMove,
693  state->localPlayer().isHost())},
694  false);
695  }
696  else {
698  xpAwardIndex,
699  learnMove,
700  state->localPlayer().isHost())},
701  true);
702  }
703  setBattleState(Stage::XpAwarding);
704  break;
705 
706  case Stage::WaitingPlayerContinue:
707  if (state->localPlayer().actionSelected()) {
708  if (state->localPlayer().shouldContinue()) { setBattleState(Stage::CheckFaint); }
709  else { setBattleState(Stage::Completed); }
710  }
711  break;
712 
713  case Stage::WaitingFaintSwitch:
714  if (currentFainter->actionSelected()) { setBattleState(Stage::FaintSwitching); }
715  break;
716 
717  case Stage::FaintSwitching:
718  setBattleState(Stage::AfterFaintSwitch);
719  break;
720 
721  case Stage::AfterFaintSwitch:
722  setBattleState(Stage::RoundEnd);
723  break;
724 
725  case Stage::PeoplemonCaught:
726  case Stage::PeoplemonCloned:
727  if (battle->view.playerChoseToSetName()) { setBattleState(Stage::ChoosingNickname); }
728  else { setBattleState(Stage::SavingPeoplemon); }
729  break;
730 
731  case Stage::ChoosingNickname:
732  setBattleState(Stage::SavingPeoplemon);
733  break;
734 
735  case Stage::SavingPeoplemon:
736  setBattleState(state->activeBattler().chosenItem() != item::Id::CloneBall ?
737  Stage::Completed :
738  Stage::NextBattler);
739  break;
740 
741  case Stage::TrainerDefeated:
742  case Stage::NetworkDefeated:
743  case Stage::NetworkLost:
744  case Stage::Whiteout:
745  setBattleState(Stage::Completed);
746  break;
747 
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:
757  // do nothing, these are intermediate states
758  break;
759 
760  default:
761  BL_LOG_CRITICAL << "Invalid battle stage: " << state->currentStage();
762  setBattleState(Stage::Completed);
763  break;
764  }
765  }
766 }
767 
768 void LocalBattleController::setBattleState(BattleState::Stage ns) {
769  using Stage = BattleState::Stage;
770 
771  Stage nns = getNextStage(ns);
772  while (nns != ns) {
773  ns = nns;
774  nns = getNextStage(nns);
775  }
776 
777  state->setStage(ns);
778  currentStageInitialized = false;
779 }
780 
781 BattleState::Stage LocalBattleController::getNextStage(BattleState::Stage ns) {
782  using Stage = BattleState::Stage;
783 
784  switch (ns) {
785  case Stage::NextBattler:
786  if (state->inactiveBattler().activePeoplemon().base().currentHp() == 0 ||
788  return Stage::Fainting;
789  }
790  else {
791  const BattleState::Stage next = state->nextTurn();
793  return next;
794  }
795 
796  case Stage::RoundStart: {
798  int pp = getPriority(state->localPlayer());
799  int op = getPriority(state->enemy());
800  firstTurn = false;
801 
802  bool pfirst = true;
803  if (pp < op) { pfirst = false; }
804  else if (pp == op && state->localPlayer().chosenAction() == TurnAction::Fight) {
805  const int lps = getSpeed(state->localPlayer());
806  const int eps = getSpeed(state->enemy());
807  if (eps > lps) { pfirst = false; }
808  else if (lps == eps) { pfirst = bl::util::Random::get<int>(0, 100) <= 50; }
809  }
810 
811  state->beginRound(pfirst);
815  return Stage::TurnStart;
816  }
817 
818  case Stage::RoundEnd:
819  if (state->inactiveBattler().activePeoplemon().base().currentHp() == 0 ||
821  return Stage::Fainting;
822  }
823  else {
825  return Stage::WaitingChoices;
826  }
827 
828  case Stage::CheckPlayerContinue:
829  if (currentFainter == &state->localPlayer() &&
832  return Stage::WaitingPlayerContinue;
833  }
834  return Stage::CheckFaint;
835 
836  case Stage::CheckFaint:
837  if (currentFainter->canFight()) { return Stage::WaitingFaintSwitch; }
838  else {
839  if (!state->enemy().canFight() && state->localPlayer().canFight()) { // player won
840  battle->result.localPlayerWon = true;
842  switch (battle->type) {
844  return Stage::TrainerDefeated;
846  return Stage::NetworkDefeated;
848  default:
849  return Stage::Completed;
850  }
851  }
852  else { // player lost
854  return Stage::Whiteout;
855  }
856  }
857  break;
858 
859  case Stage::XpAwardBegin:
860  xpAward =
861  currentFainter->activePeoplemon().base().xpYield(battle->type == Battle::Type::Trainer);
862  xpAward /= std::max(state->localPlayer().xpEarnerCount(), 1);
863  xpAwardRemaining = xpAward;
864  xpAwardIndex = state->localPlayer().getFirstXpEarner();
865  return xpAwardIndex >= 0 ? Stage::XpAwardPeoplemonBegin : Stage::CheckFaint;
866 
867  case Stage::BeforeRun: {
868  // TODO - should we allow "running" from online battles?
869  if (battle->type != Battle::Type::WildPeoplemon) { return Stage::RunFailed; }
870  ++runCount;
871  const int ps = state->localPlayer().activePeoplemon().currentStats().spd;
872  const int os = state->enemy().activePeoplemon().currentStats().spd;
873  const int odds = ((ps * 128 / os) + 30 * runCount) % 256;
874 
875 #ifdef PEOPLEMON_DEBUG
876  const bool alwaysRun = debug::DebugOverrides::get().alwaysRun ||
879 #else
880  const bool alwaysRun = state->localPlayer().activePeoplemon().currentAbility() ==
882 #endif
883 
884  if (bl::util::Random::get<int>(0, 255) < odds || alwaysRun) { return Stage::Running; }
885  else { return Stage::RunFailed; }
886  } break;
887 
888  case Stage::UsingItem:
893  return Stage::CloneBallThrown;
894  }
895  else { return Stage::CloneBallFailed; }
896  }
897 
898  if (battle->type == Battle::Type::WildPeoplemon) { return Stage::PeopleballThrown; }
899  else { return Stage::PeopleballStealFailed; }
900  }
901  return Stage::UsingItem;
902 
903  default:
904  return ns;
905  }
906 }
907 
908 void LocalBattleController::onCommandQueued(const Command&) {}
909 
910 void LocalBattleController::onCommandProcessed(const Command&) {}
911 
912 void LocalBattleController::onUpdate(bool vs, bool qe) { updateBattleState(vs, qe); }
913 
914 void LocalBattleController::startUseMove(Battler& user, int index) {
915  static pplmn::OwnedMove skimpOut(pplmn::MoveId::SkimpOut);
916  switchAfterMove = false;
917 
918  Battler& victim = &user == &state->localPlayer() ? state->enemy() : state->localPlayer();
919  pplmn::BattlePeoplemon& attacker = user.activePeoplemon();
920  pplmn::BattlePeoplemon& defender = victim.activePeoplemon();
921  const bool userIsHost = state->activeBattler().isHost();
922  user.getSubstate().lastMoveIndex = index;
923 
924  // check if frozen
925  if (attacker.hasAilment(pplmn::Ailment::Frozen)) {
926  if (bl::util::Random::get<int>(0, 100) <= 20) {
927  attacker.clearAilments(nullptr);
929  queueCommand({cmd::Animation(userIsHost, pplmn::Ailment::Frozen)});
930  queueCommand({cmd::Message(cmd::Message::Type::ThawedOut, userIsHost)}, true);
931  }
932  else {
933  queueCommand({cmd::Message(cmd::Message::Type::FrozenAilment, userIsHost)}, true);
934  return;
935  }
936  }
937 
938  // check if enough pp
939  if (!user.getSubstate().gotBaked) {
940  if (index < 0 || user.activePeoplemon().base().knownMoves()[index].curPP == 0) {
941  user.activePeoplemon().base().currentHp() = 0;
943  queueCommand({cmd::Message(cmd::Message::Type::NoPPDeath, userIsHost)}, true);
944  return;
945  }
946  }
947 
948  // get move, handle charge, and deduct pp
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; }
954 
955  // determine actual move to use based on effects
956  usedMove = move.id;
957  const pplmn::MoveEffect preEffect = pplmn::Move::effect(move.id);
958  if (preEffect == pplmn::MoveEffect::RandomMove) {
959  usedMove = pplmn::Move::getRandomMove(false);
960  }
961 
962  BattlerAttackFinalizer _finalizer(user, usedMove);
963 
964  // determine if hit
965  int acc = pplmn::Move::accuracy(usedMove);
966  if (attacker.base().holdItem() == item::Id::Glasses) {
967  acc = acc * 11 / 10;
968  queueCommand({cmd::Message(cmd::Message::Type::GlassesAcc, userIsHost)}, true);
969  }
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);
973  const int hitChance = acc * pplmn::BattleStats::getAccuracyMultiplier(stage);
974  bool hit = bl::util::Random::get<int>(0, 100) <= hitChance || acc == 0;
975  effect = pplmn::Move::effect(usedMove);
976 
977  const auto printAttackPrequel = [this, &move, isChargeSecondTurn, userIsHost]() {
978  // print used move
979  if (!isChargeSecondTurn) { queueCommand({cmd::Message(move.id, userIsHost)}, true); }
980  else {
981  queueCommand({cmd::Message(cmd::Message::Type::ChargeUnleashed, userIsHost)}, true);
982  }
983 
984  // random move
985  if (move.id != usedMove) {
986  queueCommand({cmd::Message(cmd::Message::Type::RandomMove, move.id, usedMove)}, true);
987  }
988  };
989 
990  // volleyball moves guaranteed to hit if ball is set
991  if (user.getSubstate().ballSet &&
993  hit = true;
994  }
995 
996  // bail out if we missed
997  if (!hit) {
998  if (attacker.currentAbility() == pplmn::SpecialAbility::FakeStudy) {
999  queueCommand({cmd::Message(cmd::Message::Type::FakeStudyAbility, userIsHost)}, true);
1000  }
1001  else {
1002  printAttackPrequel();
1004  }
1005  setBattleState(BattleState::Stage::NextBattler);
1006  return;
1007  }
1008  printAttackPrequel();
1009 
1010  // mark this move as hitting
1011  if (usedMove == pplmn::MoveId::Kick) { victim.getSubstate().move64Hit = true; }
1012 
1013  // determine if attacking move
1014  const pplmn::Type moveType = pplmn::Move::type(usedMove);
1015  const bool special = pplmn::Move::isSpecial(usedMove);
1016  int pwr = pplmn::Move::damage(usedMove);
1017 
1018  // check if abilities or substate canceles the move
1019  if (checkMoveCancelled(
1020  user, victim, index, usedMove, pwr, moveType, effect, isChargeSecondTurn)) {
1021  return;
1022  }
1023 
1024  // check if move has Gamble
1025  if (effect == pplmn::MoveEffect::Gamble) {
1026  const std::int16_t r = bl::util::Random::get<std::int16_t>(1, 20);
1027  if (r == 1) {
1028  user.activePeoplemon().base().currentHp() = 1;
1029  queueCommand({cmd::Message(cmd::Message::Type::GambleOne, userIsHost)}, true);
1030  }
1031  else if (r == 20) {
1032  pwr = 200;
1033  queueCommand({cmd::Message(cmd::Message::Type::GambleTwenty, userIsHost)}, true);
1034  }
1035  else {
1036  pwr = r * 5;
1037  queueCommand({cmd::Message(cmd::Message::Type::GambleMiddle, r, userIsHost)}, true);
1038  }
1039  }
1040 
1041  // check if move has DoubleFamily
1042  if (effect == pplmn::MoveEffect::DoubleFamily) {
1043  const int bc = user.getBroCount();
1044  pwr = static_cast<float>(pwr) * (static_cast<float>(bc) * 1.5f);
1045  queueCommand({cmd::Message(cmd::Message::Type::DoubleBroPower, bc, userIsHost)}, true);
1046  }
1047 
1048  // check if user has BeefedUp or Reserved abilities
1049  if (attacker.base().currentHp() <= attacker.currentStats().hp / 4 && pwr > 0) {
1050  if (attacker.currentAbility() == pplmn::SpecialAbility::BeefedUp &&
1051  moveType == pplmn::Type::Athletic) {
1052  pwr = pwr * 5 / 4;
1053  queueCommand({cmd::Message(cmd::Message::Type::BeefyPower, userIsHost)}, true);
1054  }
1055  if (attacker.currentAbility() == pplmn::SpecialAbility::Reserved &&
1056  moveType == pplmn::Type::Quiet) {
1057  pwr = pwr * 5 / 4;
1058  queueCommand({cmd::Message(cmd::Message::Type::ReservedQuietPower, userIsHost)}, true);
1059  }
1060  }
1061 
1062  // Check DukeOfJokes ability
1063  if (attacker.currentAbility() == pplmn::SpecialAbility::DukeOfJokes && isJokeMove(usedMove)) {
1064  pwr = pwr * 3 / 2;
1065  queueCommand({cmd::Message(cmd::Message::Type::DukeOfJokes, userIsHost)}, true);
1066  }
1067 
1068  // begin determining how much damage to deal
1069  damage = 0;
1070 
1071  // check counter and mirror coat
1072  if (effect == pplmn::MoveEffect::Counter) {
1073  if (!pplmn::Move::isSpecial(user.getSubstate().lastMoveHitWith) &&
1074  user.getSubstate().lastDamageTaken > 0) {
1075  damage = user.getSubstate().lastDamageTaken * 2;
1076  }
1077  else {
1078  queueCommand({cmd::Message(cmd::Message::Type::GenericMoveFailed, userIsHost)}, true);
1079  return;
1080  }
1081  }
1082  if (effect == pplmn::MoveEffect::MirrorCoat) {
1083  if (pplmn::Move::isSpecial(user.getSubstate().lastMoveHitWith) &&
1084  user.getSubstate().lastDamageTaken > 0) {
1085  damage = user.getSubstate().lastDamageTaken * 2;
1086  }
1087  else {
1088  queueCommand({cmd::Message(cmd::Message::Type::GenericMoveFailed, userIsHost)}, true);
1089  return;
1090  }
1091  }
1092 
1093  // finally play the animation
1094  queueCommand({cmd::Animation(!userIsHost, usedMove)}, true);
1095 
1096  // move does damage
1097  if (pwr > 0) {
1098  queueCommand({cmd::Animation(!userIsHost, cmd::Animation::Type::ShakeAndFlash)});
1100 
1101  int atk = special ? attacker.currentStats().spatk : attacker.currentStats().atk;
1102  int def = special ? defender.currentStats().spdef : defender.currentStats().def;
1103  const float stab = pplmn::TypeUtil::getStab(moveType, attacker.base().type());
1104  float effective = getEffectivenessMultiplier(attacker, defender, usedMove, moveType);
1105  bool crit = bl::util::Random::get<int>(0, 100) <= critChance(attacker.battleStages().crit);
1106  if (damage == 0) {
1107  // Check chillaxed ability
1108  if (crit && defender.currentAbility() == pplmn::SpecialAbility::Chillaxed) {
1109  crit = false;
1110  queueCommand({cmd::Message(cmd::Message::Type::ChillaxCritBlocked, userIsHost)},
1111  true);
1112  }
1113 
1114  // check backwards hoody hold item
1115  if (attacker.base().holdItem() == item::Id::BackwardsHoody) {
1116  atk = atk * 3 / 2;
1117  queueCommand({cmd::Message(cmd::Message::Type::BackwardsHoodyStatDown, userIsHost)},
1118  true);
1119  }
1120 
1121  // check spoon hold item
1122  if (attacker.base().holdItem() == item::Id::Spoon && special) {
1123  atk = atk * 11 / 10;
1124  queueCommand({cmd::Message(cmd::Message::Type::SpoonDamage, userIsHost)}, true);
1125  }
1126 
1127  // check slapping glove hold item
1128  if (attacker.base().holdItem() == item::Id::SlappingGlove && !special) {
1129  atk = atk * 11 / 10;
1130  queueCommand({cmd::Message(cmd::Message::Type::SlappingGloveDamage, userIsHost)},
1131  true);
1132  }
1133 
1134  // check power juice
1135  if (attacker.base().currentHp() <= attacker.currentStats().hp / 4) {
1136  if (attacker.base().holdItem() == item::Id::PowerJuice) {
1137  attacker.base().holdItem() = item::Id::None;
1138  atk = atk * 11 / 10;
1139  queueCommand({cmd::Message(cmd::Message::Type::PowerJuice, userIsHost)}, true);
1140  }
1141  }
1142 
1143  // check iced tea hold item
1144  if (defender.base().currentHp() <= defender.currentStats().hp / 4) {
1145  if (defender.base().holdItem() == item::Id::IcedTea) {
1146  defender.base().holdItem() = item::Id::None;
1147  def = def * 11 / 10;
1148  queueCommand({cmd::Message(cmd::Message::Type::IcedTea, !userIsHost)}, true);
1149  }
1150  }
1151 
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; }
1155  }
1156 
1157  // reduce damage if Frustrated
1158  if (attacker.hasAilment(pplmn::Ailment::Frustrated) && !special) { damage /= 2; }
1159 
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);
1163 
1164  if (crit) { queueCommand({cmd::Message(cmd::Message::Type::CriticalHit)}, true); }
1165  if (effective > 1.1f) {
1166  victim.getSubstate().lastMoveSuperEffective = usedMove;
1167  victim.getSubstate().preserveLastSuper = true;
1168  queueCommand({cmd::Message(cmd::Message::Type::SuperEffective)}, true);
1169  }
1170  else if (effective < 0.9f) {
1171  queueCommand({cmd::Message(cmd::Message::Type::NotEffective)}, true);
1172  }
1173 
1175  }
1176 
1177  // ensure that everything is reflected and wait for the view
1179 }
1180 
1181 bool LocalBattleController::checkMoveCancelled(Battler& user, Battler& victim, int i,
1182  pplmn::MoveId move, int pwr, pplmn::Type moveType,
1183  pplmn::MoveEffect effect, bool isChargeSecondTurn) {
1184  const bool targetsVictim = pwr > 0 || (!pplmn::Move::affectsUser(move) &&
1186  const bool userIsHost = user.isHost();
1187  const bool victimIsHost = !userIsHost;
1188 
1189  // Check if sleeping
1190  if (user.activePeoplemon().hasAilment(pplmn::Ailment::Sleep)) {
1191  // waking up is handled at turn start
1192  queueCommand({cmd::Animation(userIsHost, pplmn::Ailment::Sleep)});
1193  queueCommand({cmd::Message(cmd::Message::Type::SleepingAilment, userIsHost)}, true);
1194  return true;
1195  }
1196 
1197  // Check if distracted
1198  if (user.getSubstate().hasAilment(pplmn::PassiveAilment::Distracted)) {
1199  queueCommand({cmd::Message(cmd::Message::Type::DistractedAilment, userIsHost)}, true);
1200  return true;
1201  }
1202 
1203  // Check if no joke teach ability
1204  if (victim.activePeoplemon().currentAbility() == pplmn::SpecialAbility::NoJokeTeach) {
1205  if (isJokeMove(move) && teachThisTurn(victim)) {
1206  queueCommand({cmd::Message(cmd::Message::Type::NoJokeTeachAbility, userIsHost)}, true);
1207  return true;
1208  }
1209  }
1210 
1211  // Check if confused
1212  if (user.getSubstate().hasAilment(pplmn::PassiveAilment::Confused)) {
1213  if (user.getSubstate().turnsConfused <= 0) {
1214  user.getSubstate().clearAilment(pplmn::PassiveAilment::Confused);
1215  queueCommand({cmd::Message(cmd::Message::Type::SnappedConfusion, userIsHost)}, true);
1216  }
1217  else {
1218  queueCommand({cmd::Animation(userIsHost, pplmn::PassiveAilment::Confused)});
1219  queueCommand({cmd::Message(cmd::Message::Type::IsConfused, userIsHost)}, true);
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);
1227  queueCommand({cmd::Message(cmd::Message::Type::HurtConfusion, userIsHost)}, true);
1228  return true;
1229  }
1230  }
1231  }
1232 
1233  // Check if annoyed
1234  if (user.activePeoplemon().hasAilment(pplmn::Ailment::Annoyed)) {
1235  if (bl::util::Random::get<int>(0, 100) <= 25) {
1236  queueCommand({cmd::Animation(userIsHost, pplmn::Ailment::Annoyed)});
1237  queueCommand({cmd::Message(cmd::Message::Type::AnnoyAilment, userIsHost)}, true);
1238  return true;
1239  }
1240  }
1241 
1242  // check if move does not affect the victim
1243  if (targetsVictim) {
1244  const float effective =
1245  pplmn::TypeUtil::getSuperMult(moveType, victim.activePeoplemon().base().type());
1246  if (effective < 0.1f) {
1247  queueCommand({cmd::Message(cmd::Message::Type::IsNotAffected, userIsHost)}, true);
1248  return true;
1249  }
1250  }
1251 
1252  // check if victim is protected
1253  if (targetsVictim && victim.getSubstate().isProtected) {
1254  queueCommand({cmd::Message(cmd::Message::Type::WasProtected, victimIsHost)}, true);
1255  return true;
1256  }
1257 
1258  // stop here if this is first move of a charging move
1259  if (effect == pplmn::MoveEffect::Charge && !isChargeSecondTurn) {
1260  user.getSubstate().chargingMove = i;
1261  queueCommand({cmd::Message(cmd::Message::Type::ChargeStarted, userIsHost)}, true);
1262  return true;
1263  }
1264 
1265  // check OnlySleeping effect
1266  if (effect == pplmn::MoveEffect::OnlySleeping &&
1267  victim.activePeoplemon().base().currentAilment() != pplmn::Ailment::Sleep) {
1268  queueCommand({cmd::Message(cmd::Message::Type::OnlySleepAffected, userIsHost)}, true);
1269  return true;
1270  }
1271 
1272  // fail on 64
1273  if (effect == pplmn::MoveEffect::FailOnMove64 && user.getSubstate().move64Hit) {
1274  queueCommand({cmd::Message(cmd::Message::Type::Move64Cancel, move, userIsHost)}, true);
1275  return true;
1276  }
1277 
1278  // move goes ahead normally
1279  return false;
1280 }
1281 
1282 void LocalBattleController::applyDamageWithChecks(Battler& victim, pplmn::BattlePeoplemon& ppl,
1283  pplmn::MoveId move, int dmg) {
1284  const bool isHost = victim.isHost();
1285 
1286  // check if victim has a Substitute
1287  if (victim.getSubstate().substituteHp > 0) {
1288  queueCommand({cmd::Message(cmd::Message::Type::SubstituteTookDamage, isHost)}, true);
1289  victim.getSubstate().substituteHp -= dmg;
1290  if (victim.getSubstate().substituteHp <= 0) {
1291  queueCommand({cmd::Message(cmd::Message::Type::SubstituteDied, isHost)}, true);
1292  victim.getSubstate().substituteHp = 0;
1293  }
1294  return; // no damage is dealt
1295  }
1296 
1297  // check Experienced Teacher ability
1298  if (teachThisTurn(victim) && ppl.base().currentHp() > 1 && dmg >= ppl.base().currentHp()) {
1299  dmg = ppl.base().currentHp() - 1;
1300  queueCommand({cmd::Message(cmd::Message::Type::ExperiencedTeachAbility, isHost)}, true);
1301  }
1302 
1303  // undying faith ability
1304  if (ppl.currentAbility() == pplmn::SpecialAbility::UndyingFaith &&
1305  dmg >= ppl.base().currentHp()) {
1306  if (bl::util::Random::get<int>(0, 100) <= 10) {
1307  dmg = ppl.base().currentHp() - 1;
1308  queueCommand({cmd::Message(cmd::Message::Type::UndyingFaithAbility, isHost)}, true);
1309  }
1310  }
1311 
1312  ppl.applyDamage(dmg);
1313  victim.getSubstate().lastMoveHitWith = move;
1314  victim.getSubstate().lastDamageTaken = dmg;
1315 
1316  // check if we Endure
1317  if (victim.getSubstate().enduringThisTurn && ppl.base().currentHp() == 0) {
1318  ppl.base().currentHp() = 1;
1319  queueCommand({cmd::Message(cmd::Message::Type::Endured, isHost)}, true);
1320  }
1321 }
1322 
1323 void LocalBattleController::applyAilmentFromMove(Battler& owner, pplmn::BattlePeoplemon& victim,
1324  pplmn::Ailment ail) {
1325  const bool isHost = owner.isHost();
1326 
1327  // check too cool ability
1328  if (victim.currentAbility() == pplmn::SpecialAbility::TooCool &&
1329  ail == pplmn::Ailment::Annoyed) {
1330  queueCommand({cmd::Message(cmd::Message::Type::TooCoolAbility, isHost)}, true);
1331  return;
1332  }
1333 
1334  // all nighter ability
1335  if (victim.currentAbility() == pplmn::SpecialAbility::AllNighter &&
1336  ail == pplmn::Ailment::Sleep) {
1337  queueCommand({cmd::Message(cmd::Message::Type::AllNighterAbility, isHost)}, true);
1338  return;
1339  }
1340 
1341  // Substitute blocks ailments
1342  if (owner.getSubstate().substituteHp > 0) {
1343  queueCommand({cmd::Message(cmd::Message::Type::SubstituteAilmentBlocked, ail, isHost)});
1344  return;
1345  }
1346 
1347  // Guard blocks ailments
1348  if (owner.getSubstate().turnsGuarded > 0) {
1349  queueCommand({cmd::Message(cmd::Message::Type::GuardBlockedAilment, ail, isHost)});
1350  return;
1351  }
1352 
1353  // Classy blocks frustrated
1354  if (ail == pplmn::Ailment::Frustrated &&
1355  owner.activePeoplemon().currentAbility() == pplmn::SpecialAbility::Classy) {
1356  queueCommand({cmd::Message(cmd::Message::Type::ClassyFrustratedBlocked, isHost)}, true);
1357  return;
1358  }
1359 
1360  if (victim.giveAilment(ail)) {
1361  queueCommand({cmd::Animation(isHost, ail)});
1362  queueCommand({cmd::Message(cmd::Message::Type::GainedAilment, ail, isHost)}, true);
1363 
1364  // Check SnackShare ailment give back ability
1365  if (victim.currentAbility() == pplmn::SpecialAbility::SnackShare) {
1366  Battler& other = isHost ? state->nonHostBattler() : state->hostBattler();
1367  if (other.activePeoplemon().giveAilment(ail)) {
1368  queueCommand({cmd::Animation(!isHost, ail)});
1369  queueCommand(
1370  {cmd::Message(cmd::Message::Type::GainedAilmentSnackshare, ail, !isHost)},
1371  true);
1372  }
1373  else {
1374  // determine if blocked by too cool
1375  if (ail == pplmn::Ailment::Annoyed &&
1376  other.activePeoplemon().currentAbility() == pplmn::SpecialAbility::TooCool) {
1377  queueCommand({cmd::Message(cmd::Message::Type::TooCoolAbility, !isHost)}, true);
1378  }
1379  else {
1380  queueCommand({cmd::Message(cmd::Message::Type::AilmentGiveFail, ail, !isHost)},
1381  true);
1382  }
1383  }
1384  }
1385  }
1386  else { queueCommand({cmd::Message(cmd::Message::Type::AilmentGiveFail, ail, isHost)}, true); }
1387 }
1388 
1389 void LocalBattleController::applyAilmentFromMove(Battler& owner, pplmn::BattlePeoplemon& victim,
1390  pplmn::PassiveAilment ail) {
1391  const bool isHost = owner.isHost();
1392 
1393  // prevent more than one ailment if ailment saturated
1394  if (victim.currentAbility() == pplmn::SpecialAbility::AilmentSaturated &&
1395  owner.getSubstate().ailments != pplmn::PassiveAilment::None) {
1396  queueCommand(
1397  {cmd::Message(cmd::Message::Type::AilmentSatPassiveAilmentBlocked, ail, isHost)}, true);
1398  return;
1399  }
1400 
1401  // Substitute blocks ailments
1402  if (owner.getSubstate().substituteHp > 0) {
1403  queueCommand(
1404  {cmd::Message(cmd::Message::Type::SubstitutePassiveAilmentBlocked, ail, isHost)}, true);
1405  return;
1406  }
1407 
1408  // Guard blocks ailments
1409  if (owner.getSubstate().turnsGuarded > 0) {
1410  queueCommand({cmd::Message(cmd::Message::Type::GuardBlockedPassiveAilment, ail, isHost)},
1411  true);
1412  return;
1413  }
1414 
1415  owner.getSubstate().giveAilment(ail);
1416  queueCommand({cmd::Animation(isHost, ail)});
1417  queueCommand({cmd::Message(cmd::Message::Type::GainedPassiveAilment, ail, isHost)}, true);
1418 
1419  // TODO - reflect some passive ailments?
1420 }
1421 
1422 void LocalBattleController::doStatChange(pplmn::BattlePeoplemon& ppl, pplmn::Stat stat, int amt,
1423  bool anim) {
1424  const bool isHost = &ppl == &state->hostBattler().activePeoplemon();
1425 
1426  if (ppl.statChange(stat, amt)) {
1427  if (anim) {
1428  const auto at =
1430  queueCommand({cmd::Animation(isHost, at, stat)});
1431  }
1432  if (amt > 1) {
1433  queueCommand({cmd::Message(cmd::Message::Type::StatIncreasedSharply, stat, isHost)},
1434  true);
1435  }
1436  else if (amt > 0) {
1437  queueCommand({cmd::Message(cmd::Message::Type::StatIncreased, stat, isHost)}, true);
1438  }
1439  else if (amt > -2) {
1440  queueCommand({cmd::Message(cmd::Message::Type::StatDecreased, stat, isHost)}, true);
1441  }
1442  else {
1443  queueCommand({cmd::Message(cmd::Message::Type::StatDecreasedSharply, stat, isHost)},
1444  true);
1445  }
1446  }
1447  else {
1448  if (amt > 0) {
1449  queueCommand({cmd::Message(cmd::Message::Type::StatIncreaseFailed, stat, isHost)},
1450  true);
1451  }
1452  else {
1453  queueCommand({cmd::Message(cmd::Message::Type::StatDecreaseFailed, stat, isHost)},
1454  true);
1455  }
1456  }
1457 }
1458 
1459 void LocalBattleController::handleBattlerTurnStart(Battler& battler) {
1460  const bool isHost = battler.isHost();
1461  pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1462 
1463  // alcoholic ability
1464  if (ppl.currentAbility() == pplmn::SpecialAbility::Alcoholic) {
1465  // TODO - replace with correct item id
1466  if (battler.removeItem(item::Id::IcedTea)) {
1467  queueCommand({cmd::Message(cmd::Message::Type::AlcoholicAbility, isHost)}, true);
1468  }
1469  else {
1470  queueCommand({cmd::Message(cmd::Message::Type::AlcoholicAbilityFailed, isHost)}, true);
1471  }
1472  }
1473 
1474  // check if Sleep is done
1475  if (battler.activePeoplemon().turnsUntilAwake() <= 0) {
1476  if (ppl.base().currentAilment() == pplmn::Ailment::Sleep) {
1477  ppl.base().currentAilment() = pplmn::Ailment::None;
1479  queueCommand({cmd::Message(cmd::Message::Type::WokeUp, isHost)}, true);
1480  }
1481  }
1482 }
1483 
1484 void LocalBattleController::handleBattlerRoundEnd(Battler& battler) {
1485  const bool isHost = battler.isHost();
1486  Battler& other = isHost ? state->nonHostBattler() : state->hostBattler();
1487  pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1488  battler.notifyTurnEnd();
1489 
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;
1495  return;
1496  }
1497 
1498  // check volleyball stuff
1499  // check if too many turns and we died
1500  const bool ballHandled = battler.getSubstate().ballSet || battler.getSubstate().ballBumped;
1501  if (battler.getSubstate().ballUpTurns >= 0 && !ballHandled) {
1502  battler.activePeoplemon().base().currentHp() = 0;
1504  queueCommand({cmd::Message(cmd::Message::Type::BallKillSelf, isHost)}, true);
1505  return;
1506  }
1507  // check if died from no switch
1508  if (battler.getSubstate().ballUpTurns >= 0 && battler.getSubstate().noOneToGetBall) {
1509  battler.activePeoplemon().base().currentHp() = 0;
1511  queueCommand({cmd::Message(cmd::Message::Type::BallNoSwitchSuicide, isHost)}, true);
1512  return;
1513  }
1514  // check if up too long
1515  if (battler.getSubstate().ballUpTurns >= 3) {
1516  battler.activePeoplemon().base().currentHp() = 0;
1518  queueCommand({cmd::Message(cmd::Message::Type::BallKillTimeout, isHost)}, true);
1519  return;
1520  }
1521  // check if killed by opponent
1522  if (other.getSubstate().ballUpTurns >= 0) {
1523  // check if killed by swipe
1524  if (other.getSubstate().ballSwiped && battler.getSubstate().ballBlocked) {
1525  battler.activePeoplemon().base().currentHp() = 0;
1527  queueCommand({cmd::Message(cmd::Message::Type::BallKillSwipe, isHost)}, true);
1528  return;
1529  }
1530  // check if killed by spike
1531  if (other.getSubstate().ballSpiked && !battler.getSubstate().ballBlocked) {
1532  battler.activePeoplemon().base().currentHp() = 0;
1534  queueCommand({cmd::Message(cmd::Message::Type::BallKillSpike, isHost)}, true);
1535  return;
1536  }
1537  }
1538  // check if killed by block
1539  if (battler.getSubstate().ballSpiked && other.getSubstate().ballBlocked) {
1540  battler.activePeoplemon().base().currentHp() = 0;
1542  queueCommand({cmd::Message(cmd::Message::Type::BallSpikeBlocked, isHost)}, true);
1543  return;
1544  }
1545  // check if killed by swipe no block
1546  if (battler.getSubstate().ballSwiped && !other.getSubstate().ballBlocked) {
1547  battler.activePeoplemon().base().currentHp() = 0;
1549  queueCommand({cmd::Message(cmd::Message::Type::BallSwipeBlocked, isHost)}, true);
1550  return;
1551  }
1552  battler.getSubstate().ballBlocked = false;
1553  battler.getSubstate().ballSet = false;
1554  battler.getSubstate().ballSpiked = false;
1555  battler.getSubstate().ballSwiped = false;
1556 
1557  // check for HealNext
1558  if (battler.getSubstate().healNext >= 2) {
1559  ppl.giveHealth(ppl.currentStats().hp / 2);
1561  queueCommand({cmd::Message(cmd::Message::Type::HealNextHealed, isHost)}, true);
1562  battler.getSubstate().healNext = -1;
1563  }
1564 
1565  // check Stolen ailment
1566  if (battler.getSubstate().hasAilment(pplmn::PassiveAilment::Stolen)) {
1567  const int dmg = battler.activePeoplemon().currentStats().hp / 16;
1568  other.activePeoplemon().giveHealth(dmg);
1569  ppl.applyDamage(dmg);
1571  queueCommand({cmd::Message(cmd::Message::Type::StolenAilment, isHost)}, true);
1572  if (ppl.base().currentHp() == 0) { return; }
1573  }
1574 
1575  // check Frustrated ailment
1576  if (ppl.hasAilment(pplmn::Ailment::Frustrated)) {
1577  ppl.applyDamage(ppl.currentStats().hp / 16);
1579  queueCommand({cmd::Animation(isHost, pplmn::Ailment::Frustrated)});
1580  queueCommand({cmd::Message(cmd::Message::Type::FrustratedAilment, isHost)}, true);
1581  if (ppl.base().currentHp() == 0) { return; }
1582  }
1583 
1584  // Check if sticky
1585  if (ppl.hasAilment(pplmn::Ailment::Sticky)) {
1586  ppl.applyDamage(ppl.currentStats().hp / 16 * battler.getSubstate().turnsSticky);
1588  queueCommand({cmd::Animation(isHost, pplmn::Ailment::Sticky)});
1589  queueCommand({cmd::Message(cmd::Message::Type::StickyAilment, isHost)}, true);
1590  if (ppl.base().currentHp() == 0) { return; }
1591  }
1592 
1593  // check for death counter (DieIn3Turns)
1594  if (battler.getSubstate().deathCounter == 0) {
1595  battler.activePeoplemon().base().currentHp() = 0;
1597  queueCommand({cmd::Message(cmd::Message::Type::DeathFromCountdown, isHost)}, true);
1598  }
1599 
1600  // check bag of goldfish hold item
1601  if (ppl.base().holdItem() == item::Id::BagOfGoldFish) {
1602  if (ppl.base().currentHp() < ppl.currentStats().hp) {
1603  ppl.giveHealth(ppl.currentStats().hp / 16);
1605  queueCommand({cmd::Message(cmd::Message::Type::BagOfGoldfish, isHost)}, true);
1606  }
1607  }
1608 
1609  // sketchy sack hold item
1610  if (ppl.base().holdItem() == item::Id::SketchySack &&
1611  ppl.base().currentHp() <= ppl.currentStats().hp / 2) {
1612  ppl.base().holdItem() = item::Id::None;
1613  ppl.applyDamage(ppl.currentStats().hp / 16);
1615  queueCommand({cmd::Message(cmd::Message::Type::SketchySack, isHost)}, true);
1616  }
1617 
1618  // check goldfish cracker hold item
1619  if (ppl.base().holdItem() == item::Id::GoldfishCracker &&
1620  ppl.base().currentHp() <= ppl.currentStats().hp / 2) {
1621  ppl.base().holdItem() = item::Id::None;
1622  ppl.giveHealth(ppl.currentStats().hp / 8);
1624  queueCommand({cmd::Message(cmd::Message::Type::GoldfishCracker, isHost)}, true);
1625  }
1626 
1627  // check wakeupbelle hold item
1628  if (ppl.base().holdItem() == item::Id::WakeUpBelle &&
1629  ppl.base().currentAilment() == pplmn::Ailment::Sleep) {
1630  ppl.base().currentAilment() = pplmn::Ailment::None;
1632  queueCommand({cmd::Message(cmd::Message::Type::WakeUpBelle, isHost)}, true);
1633  }
1634 
1635  // check super tiny mini fridge
1636  if (ppl.base().holdItem() == item::Id::SuperTinyMiniFridge && ppl.base().currentHp() > 0) {
1637  ppl.base().currentHp() = 0;
1638  queueCommand({cmd::Message(cmd::Message::Type::SuperTinyMiniFridge, isHost)}, true);
1639  // TODO - play explosion animation
1640  other.activePeoplemon().applyDamage(
1641  computeDamage(100,
1642  ppl.currentStats().atk,
1643  other.activePeoplemon().currentStats().def,
1644  ppl.base().currentLevel()));
1646  }
1647 }
1648 
1649 void LocalBattleController::doRoar(Battler& victim) {
1650  const bool isHost = victim.isHost();
1651 
1652  if (!victim.canSwitch()) {
1653  queueCommand({cmd::Message(cmd::Message::Type::RoarFailedNoSwitch, isHost)}, true);
1654  return;
1655  }
1656 
1657  // adament prevents roar
1658  if (victim.activePeoplemon().currentAbility() == pplmn::SpecialAbility::Adament) {
1659  queueCommand({cmd::Message(cmd::Message::Type::AdamentAbility, isHost)}, true);
1660  return;
1661  }
1662 
1663  queueCommand({cmd::Message(cmd::Message::Type::Roar, isHost)});
1664  queueCommand({cmd::Animation(isHost, cmd::Animation::Type::SlideOut)}, true);
1665  setBattleState(BattleState::Stage::RoarSwitching);
1666  midturnSwitcher = &victim;
1667 }
1668 
1669 void LocalBattleController::startSwitch(Battler& battler) {
1670  const bool isHost = battler.isHost();
1671 
1672  queueCommand({cmd::Message(cmd::Message::Type::Callback, isHost)});
1673  queueCommand({cmd::Animation(isHost, cmd::Animation::Type::ComeBack)}, true);
1674 }
1675 
1676 void LocalBattleController::doSwitch(Battler& battler, unsigned int newPP) {
1677  const bool isHost = battler.isHost();
1678 
1679  pplmn::BattlePeoplemon& oldPpl = battler.activePeoplemon();
1680  const std::int16_t oldIndex = battler.outNowIndex();
1681 
1682  battler.setActivePeoplemon(newPP);
1683  queueCommand({cmd::Message(cmd::Message::Type::SendOut, isHost)}, true);
1685  queueCommand({cmd::Animation(isHost, cmd::Animation::Type::SendOut)}, true);
1686 
1687  Battler& other = isHost ? state->nonHostBattler() : state->hostBattler();
1688  other.resetXpEarners();
1689 
1690  // check always friendly ability
1691  if (oldPpl.base().currentHp() > 0 &&
1692  oldPpl.currentAbility() == pplmn::SpecialAbility::AlwaysFriendly &&
1693  oldPpl.base().currentAilment() != pplmn::Ailment::None) {
1694  oldPpl.base().currentAilment() = pplmn::Ailment::None;
1695  queueCommand({cmd::Message(cmd::Message::Type::FriendlyAilmentHeal, oldIndex, isHost)},
1696  true);
1697  }
1698 
1699  // register sighting
1700  if (&battler != &state->localPlayer() &&
1701  seenPeoplemon.find(&battler.activePeoplemon()) == seenPeoplemon.end()) {
1702  seenPeoplemon.emplace(&battler.activePeoplemon());
1703  battle->player.state().peopledex.registerSighting(battler.activePeoplemon().base().id(),
1704  battle->location);
1705  }
1706 }
1707 
1708 void LocalBattleController::postSwitch(Battler& battler) {
1709  const bool isHost = battler.isHost();
1710 
1711  // check DeathSwap
1712  if (battler.getSubstate().koReviveHp > 0) {
1713  battler.activePeoplemon().giveHealth(battler.getSubstate().koReviveHp);
1715  queueCommand({cmd::Message(cmd::Message::Type::DeathSwapRevived, isHost)}, true);
1716  }
1717 
1718  // Check baton pass stats
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,
1725  isHost)},
1726  true);
1727  }
1728 
1729  // check klutz ability
1730  checkKlutz(battler);
1731 
1732  // check total mom ability
1733  if (battler.getSubstate().totalMomIndex >= 0) {
1734  auto* moves = battler.activePeoplemon().base().knownMoves();
1735  for (int i = 0; i < 4; ++i) {
1736  if (moves[i].id != pplmn::MoveId::Unknown) { moves[i].restorePP(3); }
1737  }
1739  static_cast<std::int16_t>(battler.getSubstate().totalMomIndex),
1740  isHost)},
1741  true);
1742  }
1743 
1744  battler.getSubstate().notifySwitch();
1745 }
1746 
1747 bool LocalBattleController::isFainter(Battler& b) const {
1748  if (b.activePeoplemon().base().currentHp() == 0) {
1749  return b.canFight() || !b.getSubstate().faintHandled;
1750  }
1751  return false;
1752 }
1753 
1754 void LocalBattleController::preFaint(Battler& b) {
1755  const bool isHost = b.isHost();
1756 
1757  queueCommand({cmd::Message(cmd::Message::Type::Fainted, isHost)}, true);
1758  queueCommand({cmd::Animation(isHost, cmd::Animation::Type::SlideDown)}, true);
1759 
1760  // total mom
1761  if (b.activePeoplemon().currentAbility() == pplmn::SpecialAbility::TotalMom) {
1762  b.getSubstate().totalMomIndex = b.outNowIndex();
1763  }
1764 }
1765 
1766 bool LocalBattleController::tryMidturnSwitch(Battler& switcher) {
1767  if (!canSwitch(switcher)) { return false; }
1768 
1769  midturnSwitcher = &switcher;
1770  switchAfterMove = true;
1771  return true;
1772 }
1773 
1774 bool LocalBattleController::canSwitch(Battler& switcher) {
1775  const bool isHost = switcher.isHost();
1776  Battler& other = isHost ? state->nonHostBattler() : state->hostBattler();
1777 
1778  // check trapped ailment
1779  if (switcher.getSubstate().hasAilment(pplmn::PassiveAilment::Trapped)) {
1780  queueCommand({cmd::Message(cmd::Message::Type::TrappedAilment, isHost)}, true);
1781  return false;
1782  }
1783 
1784  // check for board game master ability
1785  if (other.activePeoplemon().currentAbility() == pplmn::SpecialAbility::BoardGameMaster) {
1786  queueCommand({cmd::Message(cmd::Message::Type::BoardGameSwitchBlocked, isHost)}, true);
1787  return false;
1788  }
1789 
1790  return true;
1791 }
1792 
1793 float LocalBattleController::getEffectivenessMultiplier(pplmn::BattlePeoplemon& attacker,
1794  pplmn::BattlePeoplemon& defender,
1795  pplmn::MoveId move, pplmn::Type moveType) {
1796  const bool isHost = &attacker == &state->hostBattler().activePeoplemon();
1797 
1798  float e = pplmn::TypeUtil::getSuperMult(moveType, defender.base().type());
1799 
1800  // check engaging ability
1801  if (e < 0.1f && attacker.currentAbility() == pplmn::SpecialAbility::Engaging) {
1802  e = 0.5f;
1803  queueCommand({cmd::Message(cmd::Message::Type::EngagingAbility, move, isHost)}, true);
1804  }
1805 
1806  // check snapshot ability
1807  if (defender.currentAbility() == pplmn::SpecialAbility::Snapshot) {
1808  Battler& victim = isHost ? state->nonHostBattler() : state->hostBattler();
1809  if (victim.getSubstate().lastMoveSuperEffective == move && e > 1.1f) {
1810  e = 1.f;
1811  queueCommand({cmd::Message(cmd::Message::Type::SnapshotAbility, !isHost)}, true);
1812  }
1813  }
1814 
1815  // check absolute pitch ability
1816  if (attacker.currentAbility() == pplmn::SpecialAbility::AbsolutePitch) {
1817  if (e < 0.9f && e > 0.1f) {
1818  e *= 0.85f;
1819  queueCommand({cmd::Message(cmd::Message::Type::AbsPitchNotEffective, isHost)}, true);
1820  }
1821  else if (e > 1.1f) {
1822  e *= 1.15f;
1823  queueCommand({cmd::Message(cmd::Message::Type::AbsPitchSuperEffective, isHost)}, true);
1824  }
1825  }
1826 
1827  return e;
1828 }
1829 
1830 void LocalBattleController::checkKlutz(Battler& battler) {
1831  pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1832 
1833  if (ppl.currentAbility() == pplmn::SpecialAbility::Klutz) {
1834  if (bl::util::Random::get<int>(0, 100) <= 33) {
1835  const item::Id item = ppl.base().holdItem();
1836  ppl.base().holdItem() = item::Id::None;
1837  // TODO - how to restore items to network player?
1838  if (&battler == &state->localPlayer()) { battle->player.state().bag.addItem(item); }
1839  queueCommand({cmd::Message(cmd::Message::Type::KlutzDrop, item, battler.isHost())},
1840  true);
1841  }
1842  }
1843 }
1844 
1845 bool LocalBattleController::teachThisTurn(Battler& battler) {
1846  return battler.chosenAction() == TurnAction::Fight && battler.chosenMove() >= 0 &&
1847  isTeachMove(battler.activePeoplemon().base().knownMoves()[battler.chosenMove()].id);
1848 }
1849 
1850 int LocalBattleController::getPriority(Battler& battler) {
1851  const bool isHost = battler.isHost();
1852  const Battler& otherBattler = isHost ? state->nonHostBattler() : state->hostBattler();
1853 
1854  // check get baked ability
1855  if (battler.activePeoplemon().currentAbility() == pplmn::SpecialAbility::GetBaked &&
1856  battler.activePeoplemon().base().currentHp() <=
1857  battler.activePeoplemon().currentStats().hp / 5) {
1858  battler.getSubstate().gotBaked = true;
1859  queueCommand({cmd::Message(cmd::Message::Type::GetBakedAbility, isHost)}, true);
1861  }
1862 
1863  int base = battler.getPriority();
1864 
1865  if (firstTurn) {
1866  // Check quick draw ability
1867  if (battler.activePeoplemon().currentAbility() == pplmn::SpecialAbility::QuickDraw &&
1868  battler.chosenAction() == TurnAction::Fight &&
1869  otherBattler.chosenAction() == TurnAction::Fight) {
1870  queueCommand({cmd::Message(cmd::Message::Type::QuickDrawFirst, isHost)}, true);
1871  return 1000000;
1872  }
1873 
1874  // Check new teach ability
1875  if (battler.activePeoplemon().currentAbility() == pplmn::SpecialAbility::NewTeach &&
1876  teachThisTurn(battler)) {
1877  ++base;
1878  Battler& other = isHost ? state->nonHostBattler() : state->hostBattler();
1879  if (other.chosenAction() == TurnAction::Fight) {
1880  queueCommand({cmd::Message(cmd::Message::Type::NewTeachAbility, isHost)}, true);
1881  }
1882  }
1883  }
1884 
1885  return base;
1886 }
1887 
1888 int LocalBattleController::getSpeed(Battler& battler) {
1889  const bool isHost = battler.isHost();
1890  pplmn::BattlePeoplemon& ppl = battler.activePeoplemon();
1891  int spd = ppl.getSpeed();
1892 
1893  // check speed juice hold item
1894  if (ppl.base().holdItem() == item::Id::SpeedJuice &&
1895  ppl.base().currentHp() <= ppl.currentStats().hp / 4) {
1896  ppl.base().holdItem() = item::Id::None;
1897  spd = spd * 11 / 10;
1898  queueCommand({cmd::Message(cmd::Message::Type::SpeedJuice, isHost)}, true);
1899  }
1900 
1901  return spd;
1902 }
1903 
1904 void LocalBattleController::checkAbilitiesAfterMove(Battler& user) {
1905  const bool isHost = user.isHost();
1906  Battler& victim = isHost ? state->nonHostBattler() : state->hostBattler();
1907  pplmn::BattlePeoplemon& attacker = user.activePeoplemon();
1908  pplmn::BattlePeoplemon& defender = victim.activePeoplemon();
1909  const bool contact = pplmn::Move::makesContact(usedMove);
1910  const bool special = pplmn::Move::isSpecial(usedMove);
1911 
1912  switch (defender.currentAbility()) {
1914  if (contact && damage > 0) {
1915  attacker.applyDamage(damage / 16);
1917  queueCommand({cmd::Message(cmd::Message::Type::GoonDamage, isHost)}, true);
1918  }
1919  break;
1920 
1922  if (!contact && damage > 0) {
1923  attacker.applyDamage(damage / 16);
1925  queueCommand({cmd::Message(cmd::Message::Type::SassyDamage, isHost)}, true);
1926  }
1927  break;
1928 
1930  if (damage > 0 && bl::util::Random::get<int>(0, 100) <= 10) {
1931  user.getSubstate().giveAilment(pplmn::PassiveAilment::Confused);
1932  queueCommand({cmd::Animation(isHost, pplmn::PassiveAilment::Confused)});
1933  queueCommand({cmd::Message(cmd::Message::Type::DerpDerpConfuse, isHost)}, true);
1934  }
1935  break;
1936 
1938  if (isTeachMove(usedMove) && bl::util::Random::get<int>(0, 100) <= 10) {
1939  if (defender.giveAilment(pplmn::Ailment::Sleep)) {
1940  queueCommand({cmd::Animation(!isHost, pplmn::Ailment::Sleep)});
1941  queueCommand({cmd::Message(cmd::Message::Type::DozeOffAbility, isHost)}, true);
1942  }
1943  }
1944  break;
1945 
1947  if (!special && damage > 0 && bl::util::Random::get<int>(0, 100) <= 20) {
1948  queueCommand({cmd::Message(cmd::Message::Type::DouseFlamesAbility, isHost)}, true);
1949  doStatChange(attacker, pplmn::Stat::Attack, -1);
1950  }
1951  break;
1952 
1954  if (special && damage > 0 && bl::util::Random::get<int>(0, 100) <= 20) {
1955  queueCommand({cmd::Message(cmd::Message::Type::FlirtyAbility, isHost)}, true);
1956  doStatChange(attacker, pplmn::Stat::SpecialAttack, -1);
1957  }
1958  break;
1959 
1961  if (damage > 0 && bl::util::Random::get<int>(0, 100) <= 15) {
1962  queueCommand({cmd::Message(cmd::Message::Type::CantSwimAbility, isHost)}, true);
1963  doStatChange(attacker, pplmn::Stat::Accuracy, -1);
1964  }
1965  break;
1966 
1968  if (damage > 0) {
1969  pplmn::OwnedMove* move = attacker.base().findMove(usedMove);
1970  if (move) {
1971  move->curPP = (move->curPP) / 2 + (move->curPP % 2);
1973  true);
1974  }
1975  }
1976  break;
1977 
1978  default:
1979  break;
1980  }
1981 
1982  switch (attacker.currentAbility()) {
1984  if (isTeachMove(usedMove) && bl::util::Random::get<int>(0, 100) <= 15) {
1985  victim.getSubstate().giveAilment(pplmn::PassiveAilment::Distracted);
1986  queueCommand({cmd::Message(cmd::Message::Type::SidetrackDistract, isHost)}, true);
1987  }
1988  break;
1989 
1991  if (isTeachMove(usedMove) && bl::util::Random::get<int>(0, 100) <= 33) {
1992  queueCommand({cmd::Message(cmd::Message::Type::FieryTeachAbility, isHost)}, true);
1993  doStatChange(attacker, pplmn::Stat::SpecialAttack, 1);
1994  }
1995  break;
1996 
1997  default:
1998  break;
1999  }
2000 
2001  // also check backwards hoodie confuse here
2002  if (defender.base().holdItem() == item::Id::BackwardsHoody && damage > 0) {
2003  if (bl::util::Random::get<int>(0, 100) <= 25) {
2004  if (!user.getSubstate().hasAilment(pplmn::PassiveAilment::Confused)) {
2005  user.getSubstate().giveAilment(pplmn::PassiveAilment::Confused);
2006  queueCommand({cmd::Animation(isHost, pplmn::PassiveAilment::Confused)});
2008  true);
2009  }
2010  }
2011  }
2012 }
2013 
2014 void LocalBattleController::handleMoveEffect(Battler& user) {
2015  const bool isHost = user.isHost();
2016  Battler& victim = isHost ? state->nonHostBattler() : state->hostBattler();
2017  pplmn::BattlePeoplemon& attacker = user.activePeoplemon();
2018  pplmn::BattlePeoplemon& defender = victim.activePeoplemon();
2019 
2020  const int chance = pplmn::Move::effectChance(usedMove);
2021  if (effect == pplmn::MoveEffect::None ||
2022  (chance > 0 && bl::util::Random::get<int>(0, 100) > chance))
2023  return;
2024 
2025  const bool affectsSelf = pplmn::Move::affectsUser(usedMove);
2026  pplmn::BattlePeoplemon& affected = affectsSelf ? attacker : defender;
2027  Battler& affectedOwner = affectsSelf ? user : victim;
2028  Battler& other = affectsSelf ? victim : user;
2029  const int intensity = pplmn::Move::effectIntensity(usedMove);
2030  const bool forHost = affectedOwner.isHost();
2031 
2032  // bail early if dead and the effect doesn't always apply
2033  if (affected.base().currentHp() == 0 && !doEvenIfDead(effect)) return;
2034 
2035  switch (effect) {
2037  affected.giveHealth(affected.currentStats().hp * intensity / 100);
2038  queueCommand({cmd::Message(cmd::Message::Type::AttackRestoredHp, forHost)});
2039  break;
2040 
2042  affected.giveHealth(damage * intensity / 100);
2043  queueCommand({cmd::Message(cmd::Message::Type::Absorb, forHost)});
2044  break;
2045 
2047  applyAilmentFromMove(affectedOwner, affected, pplmn::Ailment::Sticky);
2048  break;
2049 
2051  applyAilmentFromMove(affectedOwner, affected, pplmn::Ailment::Frustrated);
2052  break;
2053 
2055  applyAilmentFromMove(affectedOwner, affected, pplmn::Ailment::Annoyed);
2056  break;
2057 
2059  applyAilmentFromMove(affectedOwner, affected, pplmn::Ailment::Frozen);
2060  break;
2061 
2063  applyAilmentFromMove(affectedOwner, affected, pplmn::PassiveAilment::Confused);
2064  break;
2065 
2067  applyAilmentFromMove(affectedOwner, affected, pplmn::PassiveAilment::Stolen);
2068  break;
2069 
2071  if (state->isFirstMover()) {
2072  affectedOwner.getSubstate().giveAilment(pplmn::PassiveAilment::Distracted);
2075  forHost)});
2076  }
2077  else {
2080  forHost)});
2081  }
2082  break;
2083 
2085  applyAilmentFromMove(affectedOwner, affected, pplmn::PassiveAilment::Trapped);
2086  break;
2087 
2089  applyAilmentFromMove(affectedOwner, affected, pplmn::Ailment::Sleep);
2090  break;
2091 
2093  if (state->isFirstMover() && user.getSubstate().lastMoveUsed != usedMove) {
2094  user.getSubstate().isProtected = true;
2095  }
2096  else { queueCommand({cmd::Message(cmd::Message::Type::GenericMoveFailed)}); }
2097  break;
2098 
2100  if (affectedOwner.getSubstate().turnsGuarded == 0) {
2101  affectedOwner.getSubstate().turnsGuarded = 5;
2102  queueCommand({cmd::Message(cmd::Message::Type::Guarded, forHost)});
2103  }
2104  else { queueCommand({cmd::Message(cmd::Message::Type::GuardFailed, forHost)}); }
2105  break;
2106 
2108  affected.applyDamage(affected.currentStats().hp / 4);
2109  if (affected.base().currentHp() == 0) {
2110  queueCommand({cmd::Message(cmd::Message::Type::SubstituteSuicide, forHost)});
2111  }
2112  else if (affectedOwner.getSubstate().substituteHp > 0) {
2114  }
2115  else {
2116  affectedOwner.getSubstate().substituteHp = affected.currentStats().hp / 4;
2117  queueCommand({cmd::Message(cmd::Message::Type::SubstituteCreated, forHost)});
2118  }
2119  break;
2120 
2122  bool healed = false;
2123  for (pplmn::BattlePeoplemon& ppl : affectedOwner.peoplemon()) {
2124  if (ppl.clearAilments(&affectedOwner.getSubstate())) { healed = true; }
2125  }
2126  if (healed) { queueCommand({cmd::Message(cmd::Message::Type::HealBellHealed, forHost)}); }
2127  else { queueCommand({cmd::Message(cmd::Message::Type::HealBellAlreadyHealthy, forHost)}); }
2128  } break;
2129 
2131  doStatChange(affected, pplmn::Stat::Critical, intensity);
2132  break;
2133 
2135  doStatChange(affected, pplmn::Stat::Attack, intensity);
2136  break;
2137 
2139  doStatChange(affected, pplmn::Stat::Defense, intensity);
2140  break;
2141 
2143  doStatChange(affected, pplmn::Stat::Accuracy, intensity);
2144  break;
2145 
2147  doStatChange(affected, pplmn::Stat::Evasion, intensity);
2148  break;
2149 
2151  doStatChange(affected, pplmn::Stat::Speed, intensity);
2152  break;
2153 
2155  doStatChange(affected, pplmn::Stat::SpecialAttack, intensity);
2156  break;
2157 
2159  doStatChange(affected, pplmn::Stat::SpecialDefense, intensity);
2160  break;
2161 
2163  doStatChange(affected, pplmn::Stat::Critical, -intensity);
2164  break;
2165 
2167  doStatChange(affected, pplmn::Stat::Attack, -intensity);
2168  break;
2169 
2171  doStatChange(affected, pplmn::Stat::Defense, -intensity);
2172  break;
2173 
2175  doStatChange(affected, pplmn::Stat::Accuracy, -intensity);
2176  break;
2177 
2179  doStatChange(affected, pplmn::Stat::Evasion, -intensity);
2180  break;
2181 
2183  doStatChange(affected, pplmn::Stat::Speed, -intensity);
2184  break;
2185 
2187  doStatChange(affected, pplmn::Stat::SpecialAttack, -intensity);
2188  break;
2189 
2191  doStatChange(affected, pplmn::Stat::SpecialDefense, -intensity);
2192  break;
2193 
2195  queueCommand({cmd::Animation(forHost, cmd::Animation::Type::MultipleStateIncrease)}, true);
2196  doStatChange(affected, pplmn::Stat::Evasion, intensity, false);
2197  doStatChange(affected, pplmn::Stat::Critical, intensity, false);
2198  break;
2199 
2201  queueCommand({cmd::Animation(forHost, cmd::Animation::Type::MultipleStateIncrease)}, true);
2202  doStatChange(affected, pplmn::Stat::Attack, intensity, false);
2203  doStatChange(affected, pplmn::Stat::Speed, intensity, false);
2204  break;
2205 
2207  affected.applyDamage(damage * intensity / 100);
2208  queueCommand({cmd::Message(cmd::Message::Type::RecoilDamage, forHost)});
2209  break;
2210 
2212  affected.base().currentHp() = 0;
2213  queueCommand({cmd::Message(cmd::Message::Type::SuicideEffect, forHost)});
2214  break;
2215 
2217  if (isPeanutAllergic(affected.base().id())) {
2218  affected.base().currentHp() = 0;
2219  queueCommand({cmd::Message(cmd::Message::Type::PeanutAllergic, forHost)});
2220  }
2221  else {
2222  affected.giveHealth(1);
2223  queueCommand({cmd::Message(cmd::Message::Type::PeanutAte, forHost)});
2224  }
2225  break;
2226 
2228  for (pplmn::BattlePeoplemon& ppl : state->localPlayer().peoplemon()) {
2229  if (ppl.base().currentAilment() == pplmn::Ailment::Sleep) {
2230  ppl.base().currentAilment() = pplmn::Ailment::None;
2231  }
2232  }
2233  for (pplmn::BattlePeoplemon& ppl : state->enemy().peoplemon()) {
2234  if (ppl.base().currentAilment() == pplmn::Ailment::Sleep) {
2235  ppl.base().currentAilment() = pplmn::Ailment::None;
2236  }
2237  }
2239  break;
2240 
2242  queueCommand({cmd::Message(cmd::Message::Type::EncoreStart, forHost)}, true);
2243  if (affectedOwner.getSubstate().lastMoveIndex >= 0) {
2244  affectedOwner.getSubstate().encoreTurnsLeft = 5;
2245  affectedOwner.getSubstate().encoreMove = affectedOwner.getSubstate().lastMoveIndex;
2246  }
2247  else { queueCommand({cmd::Message(cmd::Message::Type::EncoreFailed, forHost)}, true); }
2248  break;
2249 
2251  if (affectedOwner.canSwitch()) {
2252  if (tryMidturnSwitch(affectedOwner)) {
2253  affectedOwner.getSubstate().copyStatsFrom = affectedOwner.outNowIndex();
2254  queueCommand({cmd::Message(cmd::Message::Type::BatonPassStart, forHost)}, true);
2255  }
2256  }
2257  else { queueCommand({cmd::Message(cmd::Message::Type::BatonPassFailed, forHost)}); }
2258  break;
2259 
2261  bool marked = false;
2263  queueCommand({cmd::Message(cmd::Message::Type::DeathCountDown, true)}, true);
2265  marked = true;
2266  }
2268  queueCommand({cmd::Message(cmd::Message::Type::DeathCountDown, false)}, true);
2270  marked = true;
2271  }
2272  if (!marked) {
2273  queueCommand({cmd::Message(cmd::Message::Type::DeathCountDownFailed, isHost)});
2274  }
2275  } break;
2276 
2278  if (affectedOwner.getSubstate().ballUpTurns >= 0) {
2279  queueCommand({cmd::Message(cmd::Message::Type::BallSet, forHost)}, true);
2280  affectedOwner.getSubstate().ballSet = true;
2281  if (affectedOwner.canSwitch()) { tryMidturnSwitch(affectedOwner); }
2282  else { affectedOwner.getSubstate().noOneToGetBall = true; }
2283  }
2284  else { queueCommand({cmd::Message(cmd::Message::Type::BallSetFail, forHost)}); }
2285  break;
2286 
2288  if (affectedOwner.getSubstate().ballUpTurns < 0) {
2289  queueCommand({cmd::Message(cmd::Message::Type::BallServed, forHost)}, true);
2290  affectedOwner.getSubstate().ballUpTurns = 0;
2291  affectedOwner.getSubstate().ballBumped = true;
2292  }
2293  else {
2294  queueCommand({cmd::Message(cmd::Message::Type::BallBumped, forHost)}, true);
2295  affectedOwner.getSubstate().ballBumped = true;
2296  }
2297  if (affectedOwner.canSwitch()) { tryMidturnSwitch(affectedOwner); }
2298  else { affectedOwner.getSubstate().noOneToGetBall = true; }
2299  break;
2300 
2302  if (affectedOwner.getSubstate().ballUpTurns >= 0) {
2303  queueCommand({cmd::Message(cmd::Message::Type::BallSpiked, forHost)});
2304  affectedOwner.getSubstate().ballSpiked = true;
2305  }
2306  else { queueCommand({cmd::Message(cmd::Message::Type::BallSpikeFail, forHost)}); }
2307  break;
2308 
2310  if (other.getSubstate().ballUpTurns >= 0) {
2311  queueCommand({cmd::Message(cmd::Message::Type::BallBlocked, forHost)});
2312  other.getSubstate().ballBlocked = true;
2313  }
2314  else { queueCommand({cmd::Message(cmd::Message::Type::BallBlockFail, forHost)}); }
2315  break;
2316 
2318  if (affectedOwner.getSubstate().ballUpTurns >= 0) {
2319  queueCommand({cmd::Message(cmd::Message::Type::BallSwiped, forHost)});
2320  affectedOwner.getSubstate().ballSwiped = true;
2321  }
2322  else {
2323  // TODO - this message is printing wrong, move db error?
2324  queueCommand({cmd::Message(cmd::Message::Type::BallSwipeFail, forHost)});
2325  }
2326  break;
2327 
2329  bool oneDead = false;
2330  for (const pplmn::BattlePeoplemon& p : affectedOwner.peoplemon()) {
2331  if (p.base().currentHp() == 0 && &p != &affected) {
2332  oneDead = true;
2333  break;
2334  }
2335  }
2336  if (oneDead) {
2337  affectedOwner.getSubstate().koReviveHp = affected.base().currentHp();
2338  affected.base().currentHp() = 0;
2339  queueCommand({cmd::Message(cmd::Message::Type::DeathSwapSac, forHost)});
2340  }
2341  else { queueCommand({cmd::Message(cmd::Message::Type::DeathSwapFailed, forHost)}); }
2342  } break;
2343 
2345  if (!affectedOwner.getSubstate().enduredLastTurn) {
2346  affectedOwner.getSubstate().enduringThisTurn = true;
2347  queueCommand({cmd::Message(cmd::Message::Type::EndureStart, forHost)});
2348  }
2349  else { queueCommand({cmd::Message(cmd::Message::Type::EndureFail, forHost)}); }
2350  break;
2351 
2353  affected.statChange(pplmn::Stat::Attack, 12);
2354  affected.statChange(pplmn::Stat::Accuracy, -12);
2355  queueCommand({cmd::Message(cmd::Message::Type::MaxAtkMinAcc, forHost)});
2356  break;
2357 
2359  applyAilmentFromMove(affectedOwner, affected, pplmn::Ailment::Frustrated);
2360  applyAilmentFromMove(affectedOwner, affected, pplmn::PassiveAilment::Confused);
2361  break;
2362 
2364  if (affectedOwner.getSubstate().spikesOut < 3) {
2365  affectedOwner.getSubstate().spikesOut += 1;
2366  queueCommand({cmd::Message(cmd::Message::Type::SpikesApplied, forHost)});
2367  }
2368  else { queueCommand({cmd::Message(cmd::Message::Type::SpikesFailed, forHost)}); }
2369  break;
2370 
2372  if (!state->isFirstMover() && other.getSubstate().lastMoveIndex >= 0) {
2373  auto& move =
2374  other.activePeoplemon().base().knownMoves()[other.getSubstate().lastMoveIndex];
2375  move.curPP =
2376  std::max(0, static_cast<int>(move.curPP) - bl::util::Random::get<int>(2, 5));
2377  queueCommand({cmd::Message(cmd::Message::Type::PPLowered, forHost)});
2378  }
2379  else { queueCommand({cmd::Message(cmd::Message::Type::PPLowerFail, forHost)}, true); }
2380  break;
2381 
2383  if (affectedOwner.getSubstate().healNext < 0) {
2384  affectedOwner.getSubstate().healNext = 0;
2385  queueCommand({cmd::Message(cmd::Message::Type::HealNextStart, forHost)});
2386  }
2387  else { queueCommand({cmd::Message(cmd::Message::Type::HealNextFail, forHost)}); }
2388  break;
2389 
2391  doRoar(affectedOwner);
2392  break;
2393 
2395  queueCommand({cmd::Message(cmd::Message::Type::RoarClearedArea, !forHost)}, true);
2396  other.getSubstate().spikesOut = 0;
2397  affectedOwner.getSubstate().ballUpTurns = -1;
2398  doRoar(affectedOwner);
2399  break;
2400 
2402  if (affected.base().currentAilment() == pplmn::Ailment::None) {
2403  affected.base().currentAilment() = pplmn::Ailment::Sleep;
2404  affected.clearAilments(&affectedOwner.getSubstate());
2405  affected.base().currentHp() = affected.currentStats().hp;
2406  queueCommand({cmd::Message(cmd::Message::Type::SleepHealed, forHost)});
2407  }
2408  else { queueCommand({cmd::Message(cmd::Message::Type::SleepHealFailed, forHost)}); }
2409  break;
2410 
2412  affected.copyStages(other.activePeoplemon());
2413  other.activePeoplemon().resetStages();
2414  queueCommand({cmd::Message(cmd::Message::Type::StatsStolen, forHost)});
2415  break;
2416 
2418  if (affectedOwner.canSwitch()) {
2419  if (tryMidturnSwitch(affectedOwner)) {
2420  queueCommand({cmd::Message(cmd::Message::Type::AttackThenSwitched, forHost)}, true);
2421  }
2422  }
2423  else { queueCommand({cmd::Message(cmd::Message::Type::AttackSwitchFailed, forHost)}); }
2424  break;
2425 
2429  // handled in checkMoveCancelled
2430  break;
2431 
2437  // handled up top
2438  break;
2439 
2440  default:
2441  BL_LOG_ERROR << "Unknown move effect: " << effect;
2442  break;
2443  }
2444 
2446 }
2447 
2448 } // namespace battle
2449 } // namespace core
std::uint32_t base
Definition: WildIntro.cpp:26
Id
Represents an item in its simplist form.
Definition: Id.hpp:24
@ 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.
Definition: Stat.hpp:16
Ailment
Represents an ailment that a Peoplemon can have.
Definition: Ailment.hpp:16
Type
Represents a type that a move or Peoplemon can be. Types may be combined.
Definition: Type.hpp:18
MoveId
The id of a move.
Definition: MoveId.hpp:16
MoveEffect
Represents an effect that a move can have. Copied verbatim from Peoplemon 2, may refactor later when ...
Definition: MoveEffect.hpp:17
Id
The id of a peoplemon.
Definition: Id.hpp:16
Core classes and functionality for both the editor and game.
const Type type
Definition: Battle.hpp:48
system::Player & player
Definition: Battle.hpp:47
BattleView view
Definition: Battle.hpp:51
const std::string location
Definition: Battle.hpp:46
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.
Definition: Battler.cpp:116
void pickPeoplemon(bool fromFaint, bool reviveOnly)
Initiates the process of selecting a replacement peoplemon if the current one faints.
Definition: Battler.cpp:37
bool actionSelected() const
Returns whether or not the battler has chosen what to do on this turn.
Definition: Battler.cpp:25
bool removeItem(item::Id item)
Removes the given item from the battler's inventory.
Definition: Battler.cpp:162
int getNextXpEarnerIndex(int ci)
Returns the index of the next peoplemon that should earn XP.
Definition: Battler.cpp:145
bool shouldContinue() const
Returns whether or not the player has chosen to continue.
Definition: Battler.cpp:114
BattlerSubstate & getSubstate()
Returns the state of this battler.
Definition: Battler.cpp:100
bool canFight() const
Returns true if the battler has at least one non-fainted peoplemon.
Definition: Battler.cpp:84
core::pplmn::BattlePeoplemon & activePeoplemon()
Returns the peoplemon that is currently out.
Definition: Battler.cpp:59
bool isHost() const
Returns whether or not this battler is the host.
Definition: Battler.cpp:164
TurnAction chosenAction() const
Returns the action the battler is using this turn.
Definition: Battler.cpp:39
int xpEarnerCount() const
Returns how many of the current peoplemon should earn exp.
Definition: Battler.cpp:123
std::uint8_t chosenPeoplemon() const
Returns the peoplemon the battler is switching to this turn.
Definition: Battler.cpp:55
unsigned int selectRandomPeoplemon() const
Returns the index of a random, living Peoplemon that is not currently out.
Definition: Battler.cpp:104
int prizeMoney() const
Returns the amount of prize money awarded for defeating this battler.
Definition: Battler.cpp:118
int chosenMove() const
Returns the move the battler is using this turn.
Definition: Battler.cpp:46
core::item::Id chosenItem() const
Returns the item the battler is using this turn.
Definition: Battler.cpp:53
int getFirstXpEarner() const
Returns the index of the first peoplemon that should earn XP.
Definition: Battler.cpp:136
void pickAction()
Initiates the process of selecting what to do on this turn.
Definition: Battler.cpp:31
void resetXpEarners()
Resets who earns XP.
Definition: Battler.cpp:132
std::vector< core::pplmn::BattlePeoplemon > & peoplemon()
Returns all the peoplemon held by this battler.
Definition: Battler.cpp:57
Battler & inactiveBattler()
Returns the battler who is currently not resolving their turn.
Definition: BattleState.cpp:34
Stage currentStage() const
Returns the current stage the battle is in.
Definition: BattleState.cpp:42
Battler & hostBattler()
Returns the battler that is the host.
Definition: BattleState.cpp:38
Battler & enemy()
Returns the opponent Battler.
Definition: BattleState.cpp:28
void setStage(Stage stage)
Sets the current stage of the battle.
Definition: BattleState.cpp:44
void beginRound(bool playerIsFirst)
Begins the next round of turns and sets the battler order.
Definition: BattleState.cpp:14
bool isFirstMover() const
Returns whether the current mover is the first peoplemon to go this round.
Definition: BattleState.cpp:46
Battler & activeBattler()
Returns the battler that is current resolving their turn.
Definition: BattleState.cpp:30
Battler & localPlayer()
Returns the local player Battler.
Definition: BattleState.cpp:26
Stage nextTurn()
Moves on to the next battler's turn. Returns the stage to transition to.
Definition: BattleState.cpp:21
Stage
Represents all possible states in a battle.
Definition: BattleState.hpp:24
Battler & nonHostBattler()
Returns the battler who is not the host.
Definition: BattleState.cpp:40
view::PlayerMenu & menu()
Access the player's menu.
Definition: BattleView.cpp:110
bool playerChoseToSetName() const
Returns whether or not the player chose to set a nickname on a new Peoplemon.
Definition: BattleView.cpp:114
bool playerChoseForgetMove() const
Returns whether or not the player chose to forget a move when prompted.
Definition: BattleView.cpp:112
const std::string & chosenNickname() const
Returns the nickname the player entered.
Definition: BattleView.cpp:116
void chooseMoveToForget()
Initiates the process of choosing the move to forget.
Definition: PlayerMenu.cpp:183
int selectedMove() const
Returns the selected move index.
Definition: PlayerMenu.cpp:192
static Type getType(Id item)
Returns the type of the given item.
Definition: Item.cpp:76
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.
Definition: Item.cpp:222
static bool hasEffectOnPeoplemon(Id item, const pplmn::OwnedPeoplemon &ppl)
Returns whether or not the given item will affect the peoplemon.
Definition: Item.cpp:144
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.
Definition: BattleStats.cpp:12
static int damage(MoveId move)
Returns the damage of the given move.
Definition: Move.cpp:86
static int accuracy(MoveId move)
Returns the accuracy of the given move.
Definition: Move.cpp:91
static int priority(MoveId move)
Returns the priority of the given move.
Definition: Move.cpp:96
static bool makesContact(MoveId move)
Returns whether or not the given move makes physical contact when used.
Definition: Move.cpp:106
static int effectIntensity(MoveId move)
Returns the effect intensity of the given move.
Definition: Move.cpp:126
static MoveEffect effect(MoveId move)
Returns the effect of the given move.
Definition: Move.cpp:116
static MoveId getRandomMove(bool allowRandomEffect=false)
Returns a random, valid move. Optionally filters moves that have the Random effect.
Definition: Move.cpp:160
static const std::string & name(MoveId move)
Returns the name of the given move.
Definition: Move.cpp:71
static Type type(MoveId move)
Returns the type of the given move.
Definition: Move.cpp:81
static int effectChance(MoveId move)
Returns the effect chance of the given move.
Definition: Move.cpp:121
static bool isSpecial(MoveId move)
Returns whether or not the given move is special.
Definition: Move.cpp:111
static bool affectsUser(MoveId move)
Returns whether or not the given move affects the user or the opponent.
Definition: Move.cpp:131
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.
Definition: Peoplemon.cpp:176
static float getStab(Type moveType, Type userType)
Returns the STAB multiplier for a move.
Definition: Type.cpp:112
static float getSuperMult(Type moveType, Type defenderType)
Returns the multiplier for if/when a move is super effective.
Definition: Type.cpp:114
void addItem(item::Id item, unsigned int qty=1)
Adds the given item to the bag.
Definition: Bag.cpp:44
void registerSighting(pplmn::Id peoplemon, const std::string &location)
Registers a sighting of the given peoplemon at the given location.
Definition: Peopledex.cpp:49
StorageSystem storage
Definition: State.hpp:47
std::vector< pplmn::OwnedPeoplemon > peoplemon
Definition: State.hpp:46
Peopledex peopledex
Definition: State.hpp:49
player::Bag bag
Definition: State.hpp:44
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.
Definition: Player.cpp:154