From 7b609e3b5cdf2723288e8e088f091f6c73a96671 Mon Sep 17 00:00:00 2001 From: Basilius Sauter Date: Sat, 4 Jun 2016 23:12:27 +0200 Subject: [PATCH] Add attack/defense/damage modifiers and invuln. Adds attack, defense and damage modifiers for both "goodguy" (self) and "badguy" (target) as well as a handler for goodguy/badguy invulnurability. Modified the battle calculation to not recalculate if noone does damage as long as at least 1 buff is active. This prevets infinite loops. --- src/Battle.php | 191 +++++++++++++++++++-- src/BuffList.php | 301 ++++++++++++++++++++++++++++++---- src/Models/Buff.php | 32 ++-- tests/BattleTest.php | 337 +++++++++++++++++++++++++++++++++++++- tests/datasets/battle.yml | 35 +++- 5 files changed, 828 insertions(+), 68 deletions(-) diff --git a/src/Battle.php b/src/Battle.php index ede2ce2..f3bf293 100644 --- a/src/Battle.php +++ b/src/Battle.php @@ -12,11 +12,12 @@ use LotGD\Core\{ Exceptions\BattleNotOverException, Models\FighterInterface }; -use LotGD\Core\Models\BattleEvents\{ - BuffMessageEvent, - CriticalHitEvent, - DamageEvent, - DeathEvent +use LotGD\Core\Models\{ + Buff, + BattleEvents\BuffMessageEvent, + BattleEvents\CriticalHitEvent, + BattleEvents\DamageEvent, + BattleEvents\DeathEvent }; /** @@ -41,6 +42,15 @@ class Battle protected $result = 0; protected $round = 0; + /** + * Battle Configuration + * @var type + */ + protected $configuration = [ + "riposteEnabled" => true, + "levelAdjustementEnabled" => true, + ]; + public function __construct(Game $game, FighterInterface $player, FighterInterface $monster) { $this->game = $game; @@ -65,14 +75,72 @@ class Battle } /** - * Returns true if the battle is over. - * @return type + * Disables ripostes */ - public function isOver() + public function disableRiposte() + { + $this->configuration["riposteEnabled"] = false; + } + + /** + * Enables ripostes + */ + public function enableRiposte() + { + $this->configuration["riposteEnabled"] = true; + } + + /** + * Returns true if ripostes are enabled + * @return bool + */ + public function isRiposteEnabled(): bool + { + return $this->configuration["riposteEnabled"]; + } + + public function enableLevelAdjustement() + { + $this->configuration["levelAdjustementEnabled"] = true; + } + + public function disableLevelAdjustement() + { + $this->configuration["levelAdjustementEnabled"] = false; + } + + public function isLevelAdjustementEnabled(): bool + { + return $this->configuration["levelAdjustementEnabled"]; + } + + /** + * Returns true if the battle is over. + * @return bool + */ + public function isOver(): bool { return $this->result !== self::RESULT_UNDECIDED; } + /** + * Returns the player instance + * @return FighterInterface + */ + public function getPlayer(): FighterInterface + { + return $this->player; + } + + /** + * Returns the montser instance + * @return FighterInterface + */ + public function getMonster(): FighterInterface + { + return $this->monster; + } + /** * Returns the winner of this fight * @return FighterInterface @@ -87,7 +155,7 @@ class Battle } /** - * Returns the looser of this fight + * Returns the loser of this fight * @return FighterInterface */ public function getLoser(): FighterInterface @@ -136,15 +204,23 @@ class Battle { $damageHasBeenDone = false; - $playerBuffStartEvents = $this->player->getBuffs()->activate(); - $monsterBuffStartEvents = $this->monster->getBuffs()->activate(); + $this->player->getBuffs()->resetBuffUsage(); + $this->monster->getBuffs()->resetBuffUsage(); + + $playerBuffStartEvents = $this->player->getBuffs()->activate(Buff::ACTIVATE_ROUNDSTART); + $monsterBuffStartEvents = $this->monster->getBuffs()->activate(Buff::ACTIVATE_ROUNDSTART); do { $offenseTurnEvents = $firstDamageRound & self::DAMAGEROUND_PLAYER ? $this->turn($this->player, $this->monster) : new ArrayCollection(); $defenseTurnEvents = $firstDamageRound & self::DAMAGEROUND_MONSTER ? $this->turn($this->monster, $this->player) : new ArrayCollection(); - + $events = new ArrayCollection(array_merge($offenseTurnEvents->toArray(), $defenseTurnEvents->toArray())); $eventsToAdd = new ArrayCollection(); + + if ($this->player->getBuffs()->hasBuffsInUse() || $this->monster->getBuffs()->hasBuffsInUse()) { + // If there are active buffs, we still need to count the round even if there has not been any damage done. + $damageHasBeenDone = true; + } foreach($events as $event) { $event->apply(); @@ -199,9 +275,42 @@ class Battle $attackersBuffs = $attacker->getBuffs(); $defendersBuffs = $defender->getBuffs(); - $attackersAttack = $attacker->getAttack($this->game); - $defendersDefense = $defender->getDefense($this->game); + // Adjustement makes fights versus monsters with lower level easier, + // and more difficult if the monster has a higher level by adjusting + // the monster's defense value. + // For example, if a level 10 player attacks a level 9 monster, the + // defenseAdjustement value for the monster is 0.81, reducing the monster's + // defense by 20% and making it more likely for the player to land a hit. + // On the other hand, the player's defense is increased by ~ 10%, making it + // less likely for the enemy to hit the player. + $adjustement = 1.0; + $defenseAdjustement = 1.0; + if ($attacker === $this->player && $this->isLevelAdjustementEnabled()) { + if ($attacker->getLevel() > 1 && $defender->getLevel() > 1) { + $adjustement = $attacker->getLevel() / $defender->getLevel(); + $defenseAdjustement = 1. / ($adjustement * $adjustement); + } + } + elseif ($defender === $this->player && $this->isLevelAdjustementEnabled()) { + if ($attacker->getLevel() > 1 && $defender->getLevel() > 1) { + $adjustement = $defender->getLevel() / $attacker->getLevel(); + $defenseAdjustement = $adjustement; + } + } + // Apply buff scaling for the attacker's attack - this needs to take into + // account the attacker's goodguyAttackModifier and the defenders badguyAttackModifier + $attackersAttack = $attacker->getAttack($this->game) + * $attackersBuffs->getGoodguyAttackModifier() + * $defendersBuffs->getBadguyAttackModifier(); + // It's the opposite for the defender's defense - it needs to take into account the + // defender's goodguyDefenseModifier as well as the attacker's badguyDefenseModifier. + $defendersDefense = $defender->getDefense($this->game) + * $defendersBuffs->getGoodguyDefenseModifier() + * $attackersBuffs->getBadguyDefenseModifier() + * $defenseAdjustement; + + // If the player is the attacker, we enable critical hits with a chance of 25%. if ($attacker === $this->game->getCharacter()) { // Players can land critical hits if ($this->game->getDiceBag()->chance(0.25)) { @@ -209,21 +318,67 @@ class Battle } } + // Conversion from float to int, since the random number generator takes int values. + $attackersAttack = (int) round($attackersAttack, 0); + $defendersDefense = (int) round($defendersDefense, 0); + + // Lets roll the $attackersAtkRoll = $this->game->getDiceBag()->normal(0, $attackersAttack); $defendersDefRoll = $this->game->getDiceBag()->normal(0, $defendersDefense); $damage = $attackersAtkRoll - $defendersDefRoll; - if ($attackersAttack > $attacker->getAttack($this->game, true)) { + // If the attacker's attack after modification is bigger than before, + // we call it a critical hit and apply the CriticalHitEvent. + if ($attackersAttack > $attacker->getAttack($this->game)) { $events->add(new CriticalHitEvent($attacker, $attackersAttack)); } - if ($damage < 0) { - // RIPOSTE are only half as damaging than normal attacks - $damage /= 2; + // Set damage to 0 if riposte has been disabled + if ($this->isRiposteEnabled() === false && $damage < 0) { + $damage = 0; } + // Here, we take invulnurable buffs into account. There are 4 possible values coming from the + // 2 buff lists, so we must take care a bit. + $attackerIsInvulnurable = $attackersBuffs->goodguyIsInvulnurable() || $defendersBuffs->badguyIsInvulnurable(); + $defenderIsInvulnurable = $defendersBuffs->goodguyIsInvulnurable() || $attackersBuffs->badguyIsInvulnurable(); + + if ($attackerIsInvulnurable && $defenderIsInvulnurable) { + // Both are invulnurable, damage is 0. + $damage = 0; + } + elseif ($attackerIsInvulnurable) { + // Attaker is invulnurable, damage is always > 0 (there is no riposte) + $damage = abs($damage); + } + elseif ($defenderIsInvulnurable) { + // Defender is invulnurable, damage is always < 0 (defender always ripostes) + $damage = - abs($damage); + } + + if ($damage < 0) { + // If the damage is less then 0, it's a RIPOSTE. They are only half + // as damaging than normal attacks. + $damage /= 2; + + // Apply damage modification. It's a RIPOSTE, so the defenders makes the + // damage. Therefore, we take defender's goodguyDamageModifier into account, + // and the attacker's badguyDamageModifier. + $damage *= $defendersBuffs->getGoodguyDamageModifier() + * $attackersBuffs->getBadguyDamageModifier(); + } + else { + // Apply damage modification. It's a normal attack - meaning the attacker does + // the damage. Therefore, we take the attacker's goodguyDamageModifier and + // the defender's badguyDamageModifier into account. + $damage *= $attackersBuffs->getGoodguyDamageModifier() + * $defendersBuffs->getBadguyDamageModifier(); + } + + // Round the damage value and convert to int. $damage = (int)round($damage, 0); + // Add the damage event $events->add(new DamageEvent($attacker, $defender, $damage)); return $events; diff --git a/src/BuffList.php b/src/BuffList.php index e400c82..1154f4a 100644 --- a/src/BuffList.php +++ b/src/BuffList.php @@ -8,12 +8,16 @@ use Doctrine\Common\Collections\{ Collection }; +use LotGD\Core\Exceptions\{ + ArgumentException +}; use LotGD\Core\Models\{ Buff, Character, BattleEvents\BuffMessageEvent }; + /** * Description of BuffList */ @@ -21,27 +25,46 @@ class BuffList { protected $buffs; protected $buffsBySlot; - protected $activeBuffs; + protected $activeBuffs = []; + /** @var Doctrine\Common\Collections\ArrayCollection */ + protected $usedBuffs; - protected $activated = false; + /** @var boolean True of the modifiers have already been calculated */ + protected $modifiersCalculated = false; + /** @var boolean True if the badguy is invulnurable */ protected $badguyInvulnurable = false; - protected $badguyDamageModifier = 1; - protected $badguyAttackModifier = 1; - protected $badguyDefenseModifier = 1; + /** @var float */ + protected $badguyDamageModifier = 1.; + /** @var float */ + protected $badguyAttackModifier = 1.; + /** @var float */ + protected $badguyDefenseModifier = 1.; + /** @var boolean True if the goodguy is invulnurable */ protected $goodguyInvulnurable = false; - protected $goodguyDamageModifier = 1; - protected $goodguyAttackModifier = 1; - protected $goodguyDefenseModifier = 1; + /** @var float */ + protected $goodguyDamageModifier = 1.; + /** @var float */ + protected $goodguyAttackModifier = 1.; + /** @var float */ + protected $goodguyDefenseModifier = 1.; protected $events; protected $loaded = false; + /** + * Initiates some variables + * @param Collection $buffs + */ public function __construct(Collection $buffs) { $this->buffs = $buffs; $this->events = new ArrayCollection(); + $this->usedBuffs = new ArrayCollection(); } + /** + * Loads all buffs (since it's a lazy correlation) + */ public function loadBuffs() { if ($this->loaded === false) { @@ -51,50 +74,126 @@ class BuffList } } - public function activate(): Collection + /** + * Returns true if the given buff has already been used this round. + * @param Buff $buff + * @return bool + */ + protected function hasBuffBeenUsed(Buff $buff): bool { - if ($this->activated === true) { - throw new BuffListAlreadyActivatedException("You can activate the buff list only once."); + if ($this->usedBuffs->contains($buff)) { + $used = true; + } + else { + $used = false; } - $this->activeBuffs = new ArrayCollection(); + return $used; + } + + /** + * Marks the given buff as used + * @param Buff $buff + */ + protected function useBuff(Buff $buff) + { + $this->usedBuffs->add($buff); + } + + /** + * Returns the buff's start or round message + * @param Buff $buff + * @return string + */ + protected function getBuffMessage(Buff $buff): string + { + $return = ""; + $used = $this->hasBuffBeenUsed($buff); + if ($buff->hasBeenStarted() === false && $used === false) { + $return = $buff->getStartMessage(); + $buff->setHasBeenStarted(); + } + elseif($used === false) { + $return = $buff->getRoundMessage(); + } + + return $return; + } + + /** + * Resets the buff usage for a new round + */ + public function resetBuffUsage() + { + $this->activeBuffs = []; + $this->usedBuffs = new ArrayCollection(); + $this->modifiersCalculated = false; + } + + public function hasBuffsInUse(): bool + { + return count($this->usedBuffs) > 0 ? true : false; + } + + /** + * Activates all buffs that activate upon the given activation parameter. + * @param int $activation + * @return Collection + * @throws ArgumentException + * @throws BuffListAlreadyActivatedException + */ + public function activate(int $activation): Collection + { + if ($activation%2 !== 0 && $activation !== 1) { + throw new ArgumentException("You can only activate one activation type at a time."); + } + + if (!empty($this->activeBuffs[$activation])) { + throw new BuffListAlreadyActivatedException("You can activate the buff list for the given activation step only once."); + } + + $this->activeBuffs[$activation] = new ArrayCollection(); $activationEvents = new ArrayCollection(); - foreach ($this->buffs as $buff) { - // Only look at buffs that are activated in battle. - if ($buff->getsActivatedAt(Buff::ACTIVATE_NONE)) { + foreach ($this->iterateBuffList() as $buff) { + // Continue to next buff if the activation is not in this round. + if ($buff->getsActivatedAt($activation) === false) { continue; } - $this->activeBuffs->add($buff); - - if ($buff->hasBeenStarted() === false) { - $activationMessage = $buff->getStartMessage(); - if ($activationMessage !== "") { - $activationEvents->add(new BuffMessageEvent($activationMessage)); - } - $buff->setHasBeenStarted(); + $this->activeBuffs[$activation]->add($buff); + + // Returns start or roundMessage if the buff has not been used yet. + $buffMessage = $this->getBuffMessage($buff); + if ($buffMessage !== "") { + $activationEvents->add(new BuffMessageEvent($buffMessage)); } - else { - $roundMessage = $buff->getRoundMessage(); - if ($roundMessage !== "") { - $activationEvents->add(new BuffMessageEvent($roundMessage)); - } + + // Needs to come at the end + if ($this->hasBuffBeenUsed($buff) === false) { + $this->useBuff($buff); } } return $activationEvents; } + /** + * Decreases the rounds left on all used buffs + * @return Collection A Collection containing expire messages (if there are any) + */ public function expireOneRound(): Collection { + /* @var $endEvents Collection */ $endEvents = new ArrayCollection(); - foreach($this->activeBuffs as $buff) { + foreach($this->usedBuffs as $buff) { + /* @var $roundsLeft int */ $roundsLeft = $buff->getRounds() - 1; $buff->setRounds($roundsLeft); if ($roundsLeft === 0) { + /* @var $endMessage string */ $endMessage = $buff->getEndMessage(); if ($endMessage !== "") { @@ -108,13 +207,22 @@ class BuffList return $endEvents; } + /** + * Removes a buff from the buff list. + * @param Buff $buff + */ public function remove(Buff $buff) { unset($this->buffsBySlot[$buff->getSlot()]); $this->buffs->removeElement($buff); - $this->activeBuffs->removeElement($buff); + $this->usedBuffs->removeElement($buff); } + /** + * Adds a buff to the buff list, occupying the slot. + * @param Buff $buff + * @throws BuffSlotOccupiedException if the slot is already occupied. Use renew instead. + */ public function add(Buff $buff) { $this->loadBuffs(); @@ -128,6 +236,10 @@ class BuffList $this->buffsBySlot[$buff->getSlot()] = $buff; } + /** + * Renews a buff. + * @param Buff $buff + */ public function renew(Buff $buff) { $this->loadBuffs(); @@ -140,4 +252,133 @@ class BuffList $this->buffs->add($buff); $this->buffsBySlot[$buff->getSlot()] = $buff; } + + /** + * Calculates all total modifiers + * @return type + */ + protected function calculateModifiers() + { + if ($this->modifiersCalculated === true) { + return; + } + + $this->badguyAttackModifier = 1.; + $this->badguyDamageModifier = 1.; + $this->badguyDefenseModifier = 1.; + $this->badguyInvulnurable = false; + $this->goodguyAttackModifier = 1.; + $this->goodguyDamageModifier = 1.; + $this->goodguyDefenseModifier = 1.; + $this->goodguyInvulnurable = false; + + /* @var $buff \LotGD\Core\Model\Buff */ + foreach ($this->iterateBuffList() as $buff) { + $this->badguyAttackModifier *= $buff->getBadguyAttackModifier(); + $this->badguyDefenseModifier *= $buff->getBadguyDefenseModifier(); + $this->badguyDamageModifier *= $buff->getBadguyDamageModifier(); + $this->badguyInvulnurable = $this->badguyInvulnurable || $buff->badguyIsInvulnurable(); + $this->goodguyAttackModifier *= $buff->getGoodguyAttackModifier(); + $this->goodguyDefenseModifier *= $buff->getGoodguyDefenseModifier(); + $this->goodguyDamageModifier *= $buff->getGoodguyDamageModifier(); + $this->goodguyInvulnurable = $this->goodguyInvulnurable || $buff->goodguyIsInvulnurable(); + } + } + + /** + * Iterates over every buff that gets activated at one point during a round. + * @return Generator|\LotGD\Core\Model\Buff[] + */ + protected function iterateBuffList() + { + foreach ($this->buffs as $buff) { + // Only look at buffs that are activated in battle. + if ($buff->getsActivatedAt(Buff::ACTIVATE_NONE)) { + continue; + } + else { + yield $buff; + } + } + } + + /** + * Returns the badguy attack modifier calculated over the whole bufflist + * @return float + */ + public function getBadguyAttackModifier(): float + { + $this->calculateModifiers(); + return $this->badguyAttackModifier; + } + + /** + * Returns the badguy defense modifier calculated over the whole bufflist + * @return float + */ + public function getBadguyDefenseModifier(): float + { + $this->calculateModifiers(); + return $this->badguyDefenseModifier; + } + + /** + * Returns the badguy damage modifier calculated over the whole bufflist + * @return float + */ + public function getBadguyDamageModifier(): float + { + $this->calculateModifiers(); + return $this->badguyDamageModifier; + } + + /** + * Returns true if the badguy is invulnurable + * @return bool + */ + public function badguyIsInvulnurable(): bool + { + $this->calculateModifiers(); + return $this->badguyInvulnurable; + } + + /** + * Returns the badguy attack modifier calculated over the whole bufflist + * @return float + */ + public function getGoodguyAttackModifier(): float + { + $this->calculateModifiers(); + return $this->goodguyAttackModifier; + } + + /** + * Returns the badguy defense modifier calculated over the whole bufflist + * @return float + */ + public function getGoodguyDefenseModifier(): float + { + $this->calculateModifiers(); + return $this->goodguyDefenseModifier; + } + + /** + * Returns the badguy damage modifier calculated over the whole bufflist + * @return float + */ + public function getGoodguyDamageModifier(): float + { + $this->calculateModifiers(); + return $this->goodguyDamageModifier; + } + + /** + * Returns true if the goodguy is invulnurable + * @return bool + */ + public function goodguyIsInvulnurable(): bool + { + $this->calculateModifiers(); + return $this->goodguyInvulnurable; + } } diff --git a/src/Models/Buff.php b/src/Models/Buff.php index ba2d650..4625e95 100644 --- a/src/Models/Buff.php +++ b/src/Models/Buff.php @@ -43,49 +43,49 @@ class Buff * @var string * @Column(type="text") */ - private $startMessage; + private $startMessage = ""; /** * The message given every round * @var string * @Column(type="text") */ - private $roundMessage; + private $roundMessage = ""; /** * The message given if the buff ends * @var string * @Column(type="text") */ - private $endMessage; + private $endMessage = ""; /** * The message given if the effect has success * @var string * @Column(type="text") */ - private $effectSucceedsMessage; + private $effectSucceedsMessage = ""; /** * The message given if the effect fails * @var string * @Column(type="text") */ - private $effectFailsMessage; + private $effectFailsMessage = ""; /** * The message given if the effect has no effect * @var string * @Column(type="text") */ - private $noEffectMessage; + private $noEffectMessage = ""; /** * Message that gets displayed every new day. * @var string * @Column(type="text") */ - private $newDayMessage; + private $newDayMessage = ""; /** * A value determining when the buffs activates * @var int * @Column(type="integer") */ - private $activateAt; + private $activateAt = self::ACTIVATE_NONE; /** * True if the buff survives a new day * @var bool @@ -293,7 +293,12 @@ class Buff case "float": if (is_float($value) === false) { - throw new ArgumentException("{$attribute} needs to be a float."); + // Convert to float if it is an integer. + if (is_int($value) === false) { + throw new ArgumentException("{$attribute} needs to be a float."); + } + + $value = (float)$value; } break; @@ -444,7 +449,12 @@ class Buff */ public function getsActivatedAt(int $flag): bool { - return ($flag === self::ACTIVATE_NONE ? $this->activateAt === self::ACTIVATE_NONE : $this->activateAt & $flag); + if ($flag === self::ACTIVATE_NONE) { + return $this->activateAt == self::ACTIVATE_NONE ? true : false; + } + else { + return ($this->activateAt & $flag > 0) === 1; + } } /** @@ -662,7 +672,7 @@ class Buff * Returns true if the goodguy is invulnurable * @return bool */ - public function getGoodguyIsInvulnurable(): bool + public function goodguyIsInvulnurable(): bool { return $this->goodguyInvulnurable; } diff --git a/tests/BattleTest.php b/tests/BattleTest.php index af86132..2b997f4 100644 --- a/tests/BattleTest.php +++ b/tests/BattleTest.php @@ -121,9 +121,9 @@ class BattleTest extends ModelTestCase } /** - * Tests a fight which the player has to loose (lvl 1 vs lvl 100) + * Tests a fight which the player has to lose (lvl 1 vs lvl 100) */ - public function testPlayerLooseBattle() + public function testPlayerLoseBattle() { $em = $this->getEntityManager(); @@ -171,7 +171,7 @@ class BattleTest extends ModelTestCase /** * @expectedException LotGD\Core\Exceptions\BattleNotOverException */ - public function testBattleNotOverExceptionFromLooser() + public function testBattleNotOverExceptionFromLoser() { $em = $this->getEntityManager(); @@ -196,25 +196,54 @@ class BattleTest extends ModelTestCase $battle = new Battle($this->getMockGame($character), $character, $monster); - // Fighting for 99 rounds should be enough for determining a looser - and to + // Fighting for 99 rounds should be enough for determining a loser - and to // throw the exception. for ($n = 0; $n < 99; $n++) { $battle->fightNRounds(1); } } - private function provideBuffBattleParticipants(Buff $buff): Battle + private function provideBuffBattleParticipants(Buff $buff, int $participantsType): Battle { $em = $this->getEntityManager(); + $em->clear(); - $character = $em->getRepository(Character::class)->find(4); - $monster = $em->getRepository(Monster::class)->find(3); + switch ($participantsType) { + default: + case 0: + // Fair Battle + $character = $em->getRepository(Character::class)->find(1); + $monster = $em->getRepository(Monster::class)->find(1); + break; + case 1: + // very long battle + $character = $em->getRepository(Character::class)->find(4); + $monster = $em->getRepository(Monster::class)->find(3); + break; + case 2: + // player should win battle + $character = $em->getRepository(Character::class)->find(13); + $monster = $em->getRepository(Monster::class)->find(11); + break; + case 3: + // player should lose battle + $character = $em->getRepository(Character::class)->find(11); + $monster = $em->getRepository(Monster::class)->find(13); + break; + } $character->addBuff($buff); return new Battle($this->getMockGame($character), $character, $monster); } + /** + * Asserts that a certain BuffMessageEvent with a specific text is contained in the lst of events + * @param Collection $events The list of events + * @param string $battleEventText The text to test for + * @param int $timesAtLeast Mininum number of times the message is expected to be in the event list + * @param int? $timesAtMax Maximum number of times the message is expected to be in the event list, or $timesAtLeast if null. + */ protected function assertBuffEventMessageExists( Collection $events, string $battleEventText, @@ -238,6 +267,10 @@ class BattleTest extends ModelTestCase $this->assertLessThanOrEqual($timesAtMax, $eventCounter); } + /** + * Tests normal buff messages - message upon start of the buff, message every + * round (except when it's started), and the message displayed if the buff expires. + */ public function testBattleBuffMessages() { $battle = $this->provideBuffBattleParticipants(new Buff([ @@ -247,8 +280,9 @@ class BattleTest extends ModelTestCase "roundMessage" => "The buff is still activate", "endMessage" => "The buff is ending.", "activateAt" => Buff::ACTIVATE_ROUNDSTART, - ])); + ]), 1); + // We fight for 5 rounds - this ensures that the buff is started and expired. $battle->fightNRounds(5); $this->assertBuffEventMessageExists($battle->getEvents(), "And this buff starts!", 1); @@ -278,4 +312,291 @@ class BattleTest extends ModelTestCase } } + public function testBattleBuffPlayerGoodguyModifier() + { + // Get a battle ready + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "goodguyAttackModifier" => 0.0, + "goodguyDefenseModifier" => 0.0, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 2); + + $rounds = $battle->fightNRounds(99); + + $this->assertTrue($battle->isOver()); + $this->assertSame($battle->getPlayer(), $battle->getLoser()); + + // Get a battle that the player should lose and apply a buff that the player forces to win + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "goodguyAttackModifier" => 2, + "goodguyDefenseModifier" => 2, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 3); + + $battle->fightNRounds(99); + + $this->assertTrue($battle->isOver()); + $this->assertSame($battle->getPlayer(), $battle->getWinner()); + } + + public function testBattleBuffPlayerBadguyModifier() + { + // Get a battle that the player should win and apply a buff that the player forces to lose. + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "badguyAttackModifier" => 10, + "badguyDefenseModifier" => 10, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 2); + + $rounds = $battle->fightNRounds(99); + + $this->assertTrue($battle->isOver()); + $this->assertSame($battle->getPlayer(), $battle->getLoser()); + + // Get a battle that the player should lose and apply a buff that the player forces to win + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "badguyAttackModifier" => 0, + "badguyDefenseModifier" => 0, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 3); + + $battle->fightNRounds(99); + + $this->assertTrue($battle->isOver()); + $this->assertSame($battle->getPlayer(), $battle->getWinner()); + } + + public function testBattleBuffPlayerDamageModifier() + { + // Get a battle that the player should win and apply a buff that the player forces to lose + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "goodguyDamageModifier" => 0.0, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 0); + + $rounds = $battle->fightNRounds(10); + + $this->assertSame($battle->getMonster()->getMaxHealth(), $battle->getMonster()->getHealth()); + + // Get a battle that the player should lose and apply a buff that the player forces to win + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "badguyDamageModifier" => 0.0, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 0); + + $battle->fightNRounds(10); + + $this->assertSame($battle->getPlayer()->getMaxHealth(), $battle->getPlayer()->getHealth()); + } + + public function testBattleBuffPlayerInvulnurability() + { + // Get a battle that the player should win and apply a buff that the player forces to lose + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "badguyInvulnurable" => true, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 0); + + $rounds = $battle->fightNRounds(99); + + $this->assertSame($battle->getMonster()->getMaxHealth(), $battle->getMonster()->getHealth()); + $this->assertTrue($battle->isOver()); + $this->assertSame($battle->getMonster(), $battle->getWinner()); + + // Get a battle that the player should lose and apply a buff that the player forces to win + $battle = $this->provideBuffBattleParticipants(new Buff([ + "slot" => "test", + "rounds" => 99, + "goodguyInvulnurable" => true, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ]), 0); + + $rounds = $battle->fightNRounds(99); + + $this->assertSame($battle->getPlayer()->getMaxHealth(), $battle->getPlayer()->getHealth()); + $this->assertTrue($battle->isOver()); + $this->assertSame($battle->getPlayer(), $battle->getWinner()); + } + + public function testBufflistGoodguyAttackModifier() + { + $em = $this->getEntityManager(); + $player = $em->getRepository(Character::class)->find(1); + $game = $this->getMockGame($player); + + $player->addBuff(new Buff([ + "slot" => "test1", + "rounds" => 1, + "goodguyAttackModifier" => 1.23, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test2", + "rounds" => 1, + "goodguyAttackModifier" => 0.126, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test3", + "rounds" => 1, + "goodguyAttackModifier" => 13.4, + ])); + + $modifier = $player->getBuffs()->getGoodguyAttackModifier(); + $this->assertEquals(0.15498, $modifier, '', 0.001); + } + + public function testBufflistGoodguyDefenseModifier() + { + $em = $this->getEntityManager(); + $player = $em->getRepository(Character::class)->find(1); + $game = $this->getMockGame($player); + + $player->addBuff(new Buff([ + "slot" => "test1", + "rounds" => 1, + "goodguyDefenseModifier" => 1.293, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test2", + "rounds" => 1, + "goodguyDefenseModifier" => 5.6, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test3", + "rounds" => 1, + "goodguyDefenseModifier" => 0, + ])); + + $modifier = $player->getBuffs()->getGoodguyDefenseModifier(); + $this->assertEquals(7.2408, $modifier, '', 0.001); + } + + public function testBufflistGoodguyDamageModifier() + { + $em = $this->getEntityManager(); + $player = $em->getRepository(Character::class)->find(1); + $game = $this->getMockGame($player); + + $player->addBuff(new Buff([ + "slot" => "test1", + "rounds" => 1, + "goodguyDamageModifier" => 10, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test2", + "rounds" => 1, + "goodguyDamageModifier" => 0.25, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test3", + "rounds" => 1, + "goodguyDamageModifier" => 3.5, + ])); + + $modifier = $player->getBuffs()->getGoodguyDamageModifier(); + $this->assertEquals(2.5, $modifier, '', 0.001); + } + + public function testBufflistBadguyAttackModifier() + { + $em = $this->getEntityManager(); + $player = $em->getRepository(Character::class)->find(1); + $game = $this->getMockGame($player); + + $player->addBuff(new Buff([ + "slot" => "test1", + "rounds" => 1, + "badguyAttackModifier" => 1.23, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test2", + "rounds" => 1, + "badguyAttackModifier" => 0.126, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test3", + "rounds" => 1, + "badguyAttackModifier" => 13.4, + ])); + + $modifier = $player->getBuffs()->getBadguyAttackModifier(); + $this->assertEquals(0.15498, $modifier, '', 0.001); + } + + public function testBufflistBadguyDefenseModifier() + { + $em = $this->getEntityManager(); + $player = $em->getRepository(Character::class)->find(1); + $game = $this->getMockGame($player); + + $player->addBuff(new Buff([ + "slot" => "test1", + "rounds" => 1, + "badguyDefenseModifier" => 1.293, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test2", + "rounds" => 1, + "badguyDefenseModifier" => 5.6, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test3", + "rounds" => 1, + "badguyDefenseModifier" => 0, + ])); + + $modifier = $player->getBuffs()->getBadguyDefenseModifier(); + $this->assertEquals(7.2408, $modifier, '', 0.001); + } + + public function testBufflistBadguyDamageModifier() + { + $em = $this->getEntityManager(); + $player = $em->getRepository(Character::class)->find(1); + $game = $this->getMockGame($player); + + $player->addBuff(new Buff([ + "slot" => "test1", + "rounds" => 1, + "badguyDamageModifier" => 10, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test2", + "rounds" => 1, + "badguyDamageModifier" => 0.25, + "activateAt" => Buff::ACTIVATE_ROUNDSTART, + ])); + $player->addBuff(new Buff([ + "slot" => "test3", + "rounds" => 1, + "badguyDamageModifier" => 3.5, + ])); + + $modifier = $player->getBuffs()->getBadguyDamageModifier(); + $this->assertEquals(2.5, $modifier, '', 0.001); + } } diff --git a/tests/datasets/battle.yml b/tests/datasets/battle.yml index 94b4306..3117125 100644 --- a/tests/datasets/battle.yml +++ b/tests/datasets/battle.yml @@ -27,6 +27,27 @@ characters: health: 500 maxhealth: 500 level: 0 + - + id: 10 + name: "Level 10 Character" + displayName: "Level 10 Character" + health: 100 + maxhealth: 100 + level: 10 + - + id: 11 + name: "Level 11 Character" + displayName: "Level 11 Character" + health: 110 + maxhealth: 110 + level: 11 + - + id: 13 + name: "Level 13 Character" + displayName: "Level 13 Character" + health: 130 + maxhealth: 130 + level: 13 monsters: - id: 1 @@ -39,4 +60,16 @@ monsters: - id: 3 name: "Stone" - level: 1 \ No newline at end of file + level: 1 + - + id: 10 + name: "Level 10 Monster" + level: 10 + - + id: 11 + name: "Level 11 Monster" + level: 11 + - + id: 13 + name: "Level 13 Monster" + level: 13 \ No newline at end of file