Completed
Push — master ( f29677...f615bc )
by Jakub
04:44
created

CombatBase::removeCombatEffects()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 5
nc 4
nop 1
crap 5
1
<?php
2
declare(strict_types=1);
3
4
namespace HeroesofAbenez\Combat;
5
6
use Nexendrie\Utils\Numbers,
7
    Nexendrie\Utils\Constants;
8
9
/**
10
 * Handles combat
11
 * 
12
 * @author Jakub Konečný
13
 * @property-read CombatLogger $log Log from the combat
14
 * @property-read int $winner Team which won the combat/0 if there is no winner yet
15
 * @property-read int $round Number of current round
16
 * @property-read int $roundLimit
17
 * @property-read Team $team1
18
 * @property-read Team $team2
19
 * @property callable $victoryCondition To evaluate the winner of combat. Gets combat as parameter, should return winning team (1/2) or 0 if there is not winner (yet)
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 166 characters
Loading history...
20
 * @property callable $healers To determine characters that are supposed to heal their team. Gets team1 and team2 as parameters, should return Team
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 147 characters
Loading history...
21
 * @method void onCombatStart(CombatBase $combat)
22
 * @method void onCombatEnd(CombatBase $combat)
23
 * @method void onRoundStart(CombatBase $combat)
24
 * @method void onRound(CombatBase $combat)
25
 * @method void onRoundEnd(CombatBase $combat)
26
 * @method void onAttack(Character $attacker, Character $defender)
27
 * @method void onSkillAttack(Character $attacker, Character $defender, CharacterAttackSkill $skill)
28
 * @method void onSkillSpecial(Character $character1, Character $target, CharacterSpecialSkill $skill)
29
 * @method void onHeal(Character $healer, Character $patient)
30
 */
31 1
class CombatBase {
32 1
  use \Nette\SmartObject;
33
  
34
  protected const LOWEST_HP_THRESHOLD = 0.5;
35
  
36
  /** @var Team First team */
37
  protected $team1;
38
  /** @var Team Second team */
39
  protected $team2;
40
  /** @var CombatLogger */
41
  protected $log;
42
  /** @var int Number of current round */
43
  protected $round = 0;
44
  /** @var int Round limit */
45
  protected $roundLimit = 30;
46
  /** @var array Dealt damage by team */
47
  protected $damage = [1 => 0, 2 => 0];
48
  /** @var callable[] */
49
  public $onCombatStart = [];
50
  /** @var callable[] */
51
  public $onCombatEnd = [];
52
  /** @var callable[] */
53
  public $onRoundStart = [];
54
  /** @var callable[] */
55
  public $onRound = [];
56
  /** @var callable[] */
57
  public $onRoundEnd = [];
58
  /** @var callable[] */
59
  public $onAttack = [];
60
  /** @var callable[] */
61
  public $onSkillAttack = [];
62
  /** @var callable[] */
63
  public $onSkillSpecial = [];
64
  /** @var callable[] */
65
  public $onHeal = [];
66
  /** @var array|NULL Temporary variable for results of an action */
67
  protected $results;
68
  /** @var callable */
69
  protected $victoryCondition;
70
  /** @var callable */
71
  protected $healers;
72
  
73
  public function __construct(CombatLogger $logger) {
74 1
    $this->log = $logger;
75 1
    $this->onCombatStart[] = [$this, "deployPets"];
76 1
    $this->onCombatStart[] = [$this, "equipItems"];
77 1
    $this->onCombatStart[] = [$this, "setSkillsCooldowns"];
78 1
    $this->onCombatEnd[] = [$this, "removeCombatEffects"];
79 1
    $this->onCombatEnd[] = [$this, "logCombatResult"];
80 1
    $this->onCombatEnd[] = [$this, "resetInitiative"];
81 1
    $this->onRoundStart[] = [$this ,"recalculateStats"];
82 1
    $this->onRoundStart[] = [$this, "logRoundNumber"];
83 1
    $this->onRoundStart[] = [$this, "applyPoison"];
84 1
    $this->onRound[] = [$this, "mainStage"];
85 1
    $this->onRoundEnd[] = [$this, "decreaseSkillsCooldowns"];
86 1
    $this->onRoundEnd[] = [$this, "resetInitiative"];
87 1
    $this->onAttack[] = [$this, "attackHarm"];
88 1
    $this->onAttack[] = [$this, "logDamage"];
89 1
    $this->onAttack[] = [$this, "logResults"];
90 1
    $this->onSkillAttack[] = [$this, "useAttackSkill"];
91 1
    $this->onSkillAttack[] = [$this, "logDamage"];
92 1
    $this->onSkillAttack[] = [$this, "logResults"];
93 1
    $this->onSkillSpecial[] = [$this, "useSpecialSkill"];
94 1
    $this->onSkillSpecial[] = [$this, "logResults"];
95 1
    $this->onHeal[] = [$this, "heal"];
96 1
    $this->onHeal[] = [$this, "logResults"];
97 1
    $this->victoryCondition = [$this, "victoryConditionMoreDamage"];
98 1
    $this->healers = function(): Team {
99
      return new Team("healers");
100
    };
101 1
  }
102
  
103
  public function getRound(): int {
104 1
    return $this->round;
105
  }
106
  
107
  public function getRoundLimit(): int {
108
    return $this->roundLimit;
109
  }
110
  
111
  /**
112
   * Set teams
113
   */
114
  public function setTeams(Team $team1, Team $team2): void {
115 1
    if(isset($this->team1)) {
116 1
      throw new ImmutableException("Teams has already been set.");
117
    }
118 1
    $this->team1 = & $team1;
119 1
    $this->team2 = & $team2;
120 1
    $this->log->setTeams($team1, $team2);
121 1
  }
122
  
123
  /**
124
   * Set participants for duel
125
   * Creates teams named after the member
126
   */
127
  public function setDuelParticipants(Character $player, Character $opponent): void {
128
    $team1 = new Team($player->name);
129
    $team1[] = $player;
130
    $team2 = new Team($opponent->name);
131
    $team2[] = $opponent;
132
    $this->setTeams($team1, $team2);
133
  }
134
  
135
  public function getTeam1(): Team {
136
    return $this->team1;
137
  }
138
  
139
  public function getTeam2(): Team {
140
    return $this->team2;
141
  }
142
  
143
  public function getVictoryCondition(): callable {
144
    return $this->victoryCondition;
145
  }
146
  
147
  public function setVictoryCondition(callable $victoryCondition) {
148
    $this->victoryCondition = $victoryCondition;
149
  }
150
  
151
  public function getHealers(): callable {
152
    return $this->healers;
153
  }
154
  
155
  public function setHealers(callable $healers) {
156 1
    $this->healers = $healers;
157 1
  }
158
  
159
  /**
160
   * Evaluate winner of combat
161
   * The team which dealt more damage after round limit, wins
162
   * If all members of one team are eliminated before that, the other team wins
163
   */
164
  public function victoryConditionMoreDamage(CombatBase $combat): int {
165 1
    $result = 0;
166 1
    if($combat->round <= $combat->roundLimit) {
167 1
      if(!$combat->team1->hasAliveMembers()) {
168
        $result = 2;
169 1
      } elseif(!$combat->team2->hasAliveMembers()) {
170 1
        $result = 1;
171
      }
172 1
    } elseif($combat->round > $combat->roundLimit) {
173 1
      $result = ($combat->damage[1] > $combat->damage[2]) ? 1 : 2;
174
    }
175 1
    return $result;
176
  }
177
  
178
  /**
179
   * Evaluate winner of combat
180
   * Team 1 wins only if they eliminate all opponents before round limit
181
   */
182
  public function victoryConditionEliminateSecondTeam(CombatBase $combat): int {
183
    $result = 0;
184
    if($combat->round <= $combat->roundLimit) {
185
      if(!$combat->team1->hasAliveMembers()) {
186
        $result = 2;
187
      } elseif(!$combat->team2->hasAliveMembers()) {
188
        $result = 1;
189
      }
190
    } elseif($combat->round > $combat->roundLimit) {
191
      $result = (!$combat->team2->hasAliveMembers()) ? 1 : 2;
192
    }
193
    return $result;
194
  }
195
  
196
  /**
197
   * Evaluate winner of combat
198
   * Team 1 wins if at least 1 of its members is alive after round limit
199
   */
200
  public function victoryConditionFirstTeamSurvives(CombatBase $combat): int {
201
    $result = 0;
202
    if($combat->round <= $combat->roundLimit) {
203
      if(!$combat->team1->hasAliveMembers()) {
204
        $result = 2;
205
      } elseif(!$combat->team2->hasAliveMembers()) {
206
        $result = 1;
207
      }
208
    } elseif($combat->round > $combat->roundLimit) {
209
      $result = ($combat->team1->hasAliveMembers()) ? 1 : 2;
210
    }
211
    return $result;
212
  }
213
  
214
  /**
215
   * Get winner of combat
216
   * 
217
   * @staticvar int $result
218
   * @return int Winning team/0
219
   */
220
  public function getWinner(): int {
221 1
    static $result = 0;
222 1
    if($result === 0) {
223 1
      $result = call_user_func($this->victoryCondition, $this);
224 1
      $result = Numbers::range($result, 0, 2);
225
    }
226 1
    return $result;
227
  }
228
  
229
  protected function getTeam(Character $character): Team {
230 1
    return $this->team1->hasMember($character->id) ? $this->team1 : $this->team2;
231
  }
232
  
233
  protected function getEnemyTeam(Character $character): Team {
234 1
    return $this->team1->hasMember($character->id) ? $this->team2 : $this->team1;
235
  }
236
  
237
  /**
238
   * Apply pet's effects to character at the start of the combat
239
   */
240
  public function deployPets(CombatBase $combat): void {
241
    /** @var Character[] $characters */
242 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
243 1
    foreach($characters as $character) {
244 1
      if(!is_null($character->activePet)) {
245 1
        $effect = $character->getPet($character->activePet)->deployParams;
246 1
        $character->addEffect(new CharacterEffect($effect));
247
      }
248
    }
249 1
  }
250
  
251
  /**
252
   * Apply effects from worn items
253
   */
254
  public function equipItems(CombatBase $combat): void {
255
    /** @var Character[] $characters */
256 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
257 1
    foreach($characters as $character) {
258 1
      foreach($character->equipment as $item) {
259 1
        if($item->worn) {
260 1
          $character->equipItem($item->id);
261
        }
262
      }
263
    }
264 1
  }
265
  
266
  /**
267
   * Set skills' cooldowns
268
   */
269
  public function setSkillsCooldowns(CombatBase $combat): void {
270
    /** @var Character[] $characters */
271 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
272 1
    foreach($characters as $character) {
273 1
      foreach($character->skills as $skill) {
274 1
        $skill->resetCooldown();
275
      }
276
    }
277 1
  }
278
  
279
  /**
280
   * Decrease skills' cooldowns
281
   */
282
  public function decreaseSkillsCooldowns(CombatBase $combat): void {
283
    /** @var Character[] $characters */
284 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
285 1
    foreach($characters as $character) {
286 1
      foreach($character->skills as $skill) {
287 1
        $skill->decreaseCooldown();
288
      }
289
    }
290 1
  }
291
  
292
  /**
293
   * Remove combat effects from character at the end of the combat
294
   */
295
  public function removeCombatEffects(CombatBase $combat): void {
296
    /** @var Character[] $characters */
297 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
298 1
    foreach($characters as $character) {
299 1
      foreach($character->effects as $effect) {
300 1
        if($effect->duration === CharacterEffect::DURATION_COMBAT OR is_int($effect->duration)) {
301 1
          $character->removeEffect($effect->id);
302
        }
303
      }
304
    }
305 1
  }
306
  
307
  /**
308
   * Add winner to the log
309
   */
310
  public function logCombatResult(CombatBase $combat): void {
311 1
    $combat->log->round = 5000;
312
    $params = [
313 1
      "team1name" => $combat->team1->name, "team1damage" => $combat->damage[1],
314 1
      "team2name" => $combat->team2->name, "team2damage" => $combat->damage[2],
315
    ];
316 1
    if($combat->winner === 1) {
317 1
      $params["winner"] = $combat->team1->name;
318
    } else {
319
      $params["winner"] = $combat->team2->name;
320
    }
321 1
    $combat->log->logText("combat.log.combatEnd", $params);
322 1
  }
323
  
324
  /**
325
   * Log start of a round
326
   */
327
  public function logRoundNumber(CombatBase $combat): void {
328 1
    $combat->log->round = ++$this->round;
329 1
  }
330
  
331
  /**
332
   * Decrease duration of effects and recalculate stats
333
   */
334
  public function recalculateStats(CombatBase $combat): void {
335
    /** @var Character[] $characters */
336 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
337 1
    foreach($characters as $character) {
338 1
      $character->recalculateStats();
339 1
      if($character->hitpoints > 0) {
340 1
        $character->calculateInitiative();
341
      }
342
    }
343 1
  }
344
  
345
  /**
346
   * Reset characters' initiative
347
   */
348
  public function resetInitiative(CombatBase $combat): void {
349
    /** @var Character[] $characters */
350 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
351 1
    foreach($characters as $character) {
352 1
      $character->resetInitiative();
353
    }
354 1
  }
355
  
356
  /**
357
   * Select random character from the team
358
   */
359
  protected function selectRandomCharacter(Team $team): ?Character {
360 1
    if(count($team->aliveMembers) === 0) {
361
      return NULL;
362 1
    } elseif(count($team) === 1) {
363 1
      return $team[0];
364
    }
365
    $roll = rand(0, count($team->aliveMembers) - 1);
366
    return $team->aliveMembers[$roll];
367
  }
368
  
369
  /**
370
   * Select target for attack
371
   */
372
  protected function selectAttackTarget(Character $attacker): ?Character {
373 1
    $enemyTeam = $this->getEnemyTeam($attacker);
374 1
    $target = $this->findLowestHpCharacter($enemyTeam);
375 1
    if(!is_null($target)) {
376 1
      return $target;
377
    }
378 1
    return $this->selectRandomCharacter($enemyTeam);
379
  }
380
  
381
  /**
382
   * Find character with lowest hp in the team
383
   */
384
  protected function findLowestHpCharacter(Team $team, int $threshold = NULL): ?Character {
385 1
    $lowestHp = 9999;
386 1
    $lowestIndex = -1;
387 1
    if(is_null($threshold)) {
388 1
      $threshold = static::LOWEST_HP_THRESHOLD;
389
    }
390 1
    foreach($team->aliveMembers as $index => $member) {
391 1
      if($member->hitpoints <= $member->maxHitpoints * $threshold AND $member->hitpoints < $lowestHp) {
392 1
        $lowestHp = $member->hitpoints;
393 1
        $lowestIndex = $index;
394
      }
395
    }
396 1
    if($lowestIndex === -1) {
397 1
      return NULL;
398
    }
399 1
    return $team->aliveMembers[$lowestIndex];
400
  }
401
  
402
  /**
403
   * Select target for healing
404
   */
405
  protected function selectHealingTarget(Character $healer): ?Character {
406 1
    return $this->findLowestHpCharacter($this->getTeam($healer));
407
  }
408
  
409
  protected function findHealers(): Team {
410 1
    $healers = call_user_func($this->healers, $this->team1, $this->team2);
411 1
    if($healers instanceof Team) {
412 1
      return $healers;
413
    }
414
    return new Team("healers");
415
  }
416
  
417
  protected function doSpecialSkill(Character $character1, Character $character2, CharacterSpecialSkill $skill): void {
418 1
    switch($skill->skill->target) {
419 1
      case SkillSpecial::TARGET_ENEMY:
420
        $this->onSkillSpecial($character1, $character2, $skill);
421
        break;
422 1
      case SkillSpecial::TARGET_SELF:
423 1
        $this->onSkillSpecial($character1, $character1, $skill);
424 1
        break;
425
      case SkillSpecial::TARGET_PARTY:
426
        $team = $this->getTeam($character1);
427
        foreach($team as $target) {
428
          $this->onSkillSpecial($character1, $target, $skill);
429
        }
430
        break;
431
      case SkillSpecial::TARGET_ENEMY_PARTY:
432
        $team = $this->getEnemyTeam($character1);
433
        foreach($team as $target) {
434
          $this->onSkillSpecial($character1, $target, $skill);
435
        }
436
        break;
437
    }
438 1
  }
439
  
440
  protected function chooseAction(CombatBase $combat, Character $character): ?string {
441 1
    if($character->hitpoints < 1) {
442
      return NULL;
443 1
    } elseif(in_array($character, $combat->findHealers()->items, true) AND !is_null($combat->selectHealingTarget($character))) {
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 128 characters
Loading history...
444 1
      return CombatAction::ACTION_HEALING;
445
    }
446 1
    $attackTarget = $combat->selectAttackTarget($character);
447 1
    if(is_null($attackTarget)) {
448
      return NULL;
449
    }
450 1
    if(count($character->usableSkills) > 0) {
451 1
      $skill = $character->usableSkills[0];
452 1
      if($skill instanceof CharacterAttackSkill) {
453 1
        return CombatAction::ACTION_SKILL_ATTACK;
454 1
      } elseif($skill instanceof  CharacterSpecialSkill) {
455 1
        return CombatAction::ACTION_SKILL_SPECIAL;
456
      }
457
    }
458 1
    return CombatAction::ACTION_ATTACK;
459
  }
460
  
461
  protected function getAllowedActions(): array {
462 1
    $allowedActions = Constants::getConstantsValues(CombatAction::class, "ACTION_");
463 1
    return array_values(array_filter($allowedActions, function(string $value) {
464 1
      return ($value !== CombatAction::ACTION_POISON);
465 1
    }));
466
  }
467
  
468
  /**
469
   * Main stage of a round
470
   */
471
  public function mainStage(CombatBase $combat): void {
472
    /** @var Character[] $characters */
473 1
    $characters = array_merge($combat->team1->usableMembers, $combat->team2->usableMembers);
474 1
    usort($characters, function(Character $a, Character $b) {
475 1
      return -1 * strcmp((string) $a->initiative, (string) $b->initiative);
476 1
    });
477 1
    foreach($characters as $character) {
478 1
      $action = $combat->chooseAction($combat, $character);
479 1
      if(!in_array($action, $this->getAllowedActions(), true)) {
480
        continue;
481
      }
482
      switch($action) {
483 1
        case CombatAction::ACTION_ATTACK:
484 1
          $combat->onAttack($character, $combat->selectAttackTarget($character));
485 1
          break;
486 1
        case CombatAction::ACTION_SKILL_ATTACK:
487
          /** @var CharacterAttackSkill $skill */
488 1
          $skill = $character->usableSkills[0];
489 1
          $combat->onSkillAttack($character, $combat->selectAttackTarget($character), $skill);
490 1
          break;
491 1
        case CombatAction::ACTION_SKILL_SPECIAL:
492
          /** @var CharacterSpecialSkill $skill */
493 1
          $skill = $character->usableSkills[0];
494 1
          $combat->doSpecialSkill($character, $combat->selectAttackTarget($character), $skill);
495 1
          break;
496 1
        case CombatAction::ACTION_HEALING:
497 1
          $combat->onHeal($character, $combat->selectHealingTarget($character));
498 1
          break;
499
      }
500
    }
501 1
  }
502
  
503
  /**
504
   * Start next round
505
   * 
506
   * @return int Winning team/0
507
   */
508
  protected function startRound(): int {
509 1
    $this->onRoundStart($this);
510 1
    return $this->getWinner();
511
  }
512
  
513
  /**
514
   * Do a round
515
   */
516
  protected function doRound(): void {
517 1
    $this->onRound($this);
518 1
  }
519
  
520
  /**
521
   * End round
522
   * 
523
   * @return int Winning team/0
524
   */
525
  protected function endRound(): int {
526 1
    $this->onRoundEnd($this);
527 1
    return $this->getWinner();
528
  }
529
  
530
  /**
531
   * Executes the combat
532
   * 
533
   * @return int Winning team
534
   */
535
  public function execute(): int {
536 1
    if(!isset($this->team1)) {
537 1
      throw new InvalidStateException("Teams are not set.");
538
    }
539 1
    $this->onCombatStart($this);
540 1
    while($this->round <= $this->roundLimit) {
541 1
      if($this->startRound() > 0) {
542 1
        break;
543
      }
544 1
      $this->doRound();
545 1
      if($this->endRound() > 0) {
546
        break;
547
      }
548
    }
549 1
    $this->onCombatEnd($this);
550 1
    return $this->getWinner();
551
  }
552
  
553
  /**
554
   * Calculate hit chance for attack/skill attack
555
   */
556
  protected function calculateHitChance(Character $character1, Character $character2, CharacterAttackSkill $skill = NULL): int {
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 128 characters
Loading history...
557 1
    $hitRate = $character1->hit;
558 1
    $dodgeRate = $character2->dodge;
559 1
    if(!is_null($skill)) {
560 1
      $hitRate = $hitRate / 100 * $skill->hitRate;
561
    }
562 1
    return Numbers::range((int) ($hitRate - $dodgeRate), 15, 100);
563
  }
564
  
565
  /**
566
   * Check whether action succeeded
567
   */
568
  protected function hasHit(int $hitChance): bool {
569 1
    $roll = rand(0, 100);
570 1
    return ($roll <= $hitChance);
571
  }
572
  
573
  /**
574
   * Do an attack
575
   * Hit chance = Attacker's hit - Defender's dodge, but at least 15%
576
   * Damage = Attacker's damage - defender's defense
577
   */
578
  public function attackHarm(Character $attacker, Character $defender): void {
579 1
    $result = [];
580 1
    $hitChance = $this->calculateHitChance($attacker, $defender);
581 1
    $result["result"] = $this->hasHit($hitChance);
582 1
    $result["amount"] = 0;
583 1
    if($result["result"]) {
584 1
      $amount = $attacker->damage - $defender->defense;
585 1
      $result["amount"] = Numbers::range($amount, 0, $defender->hitpoints);
586
    }
587 1
    if($result["amount"] > 0) {
588 1
      $defender->harm($result["amount"]);
589
    }
590 1
    $result["action"] = CombatAction::ACTION_ATTACK;
591 1
    $result["name"] = "";
592 1
    $result["character1"] = $attacker;
593 1
    $result["character2"] = $defender;
594 1
    $this->results = $result;
595 1
  }
596
  
597
  /**
598
   * Use an attack skill
599
   */
600
  public function useAttackSkill(Character $attacker, Character $defender, CharacterAttackSkill $skill): void {
601 1
    $result = [];
602 1
    $hitChance = $this->calculateHitChance($attacker, $defender, $skill);
603 1
    $result["result"] = $this->hasHit($hitChance);
604 1
    $result["amount"] = 0;
605 1
    if($result["result"]) {
606 1
      $amount = (int) ($attacker->damage - $defender->defense / 100 * $skill->damage);
607 1
      $result["amount"] = Numbers::range($amount, 0, $defender->hitpoints);
608
    }
609 1
    if($result["amount"]) {
610 1
      $defender->harm($result["amount"]);
611
    }
612 1
    $result["action"] = CombatAction::ACTION_SKILL_ATTACK;
613 1
    $result["name"] = $skill->skill->name;
614 1
    $result["character1"] = $attacker;
615 1
    $result["character2"] = $defender;
616 1
    $this->results = $result;
617 1
    $skill->resetCooldown();
618 1
  }
619
  
620
  /**
621
   * Use a special skill
622
   */
623
  public function useSpecialSkill(Character $character1, Character $target, CharacterSpecialSkill $skill): void {
624
    $result = [
625 1
      "result" => true, "amount" => 0, "action" => CombatAction::ACTION_SKILL_SPECIAL, "name" => $skill->skill->name,
626 1
      "character1" => $character1, "character2" => $target,
627
    ];
628 1
    $this->results = $result;
629
    $effect = [
630 1
      "id" => "skill{$skill->skill->id}Effect",
631 1
      "type" => $skill->skill->type,
632 1
      "stat" => ((in_array($skill->skill->type, SkillSpecial::NO_STAT_TYPES, true)) ? NULL : $skill->skill->stat),
633 1
      "value" => $skill->value,
634 1
      "source" => CharacterEffect::SOURCE_SKILL,
635 1
      "duration" => $skill->skill->duration
636
    ];
637 1
    $target->addEffect(new CharacterEffect($effect));
638 1
    $skill->resetCooldown();
639 1
  }
640
  
641
  /**
642
   * Calculate success chance of healing
643
   */
644
  protected function calculateHealingSuccessChance(Character $healer): int {
645 1
    return $healer->intelligence * (int) round($healer->level / 5) + 30;
646
  }
647
  
648
  /**
649
   * Heal a character
650
   */
651
  public function heal(Character $healer, Character $patient): void {
652 1
    $result = [];
653 1
    $hitChance = $this->calculateHealingSuccessChance($healer);
654 1
    $result["result"] = $this->hasHit($hitChance);
655 1
    $amount = ($result["result"]) ? $healer->intelligence / 2 : 0;
656 1
    if($amount + $patient->hitpoints > $patient->maxHitpoints) {
657
      $amount = $patient->maxHitpoints - $patient->hitpoints;
658
    }
659 1
    $result["amount"] = (int) $amount;
660 1
    if($result["amount"]) {
661
      $patient->heal($result["amount"]);
662
    }
663 1
    $result["action"] = CombatAction::ACTION_HEALING;
664 1
    $result["name"] = "";
665 1
    $result["character1"] = $healer;
666 1
    $result["character2"] = $patient;
667 1
    $this->results = $result;
668 1
  }
669
  
670
  /**
671
   * Harm poisoned characters at start of round
672
   */
673
  public function applyPoison(CombatBase $combat): void {
674
    /** @var Character[] $characters */
675 1
    $characters = array_merge($combat->team1->aliveMembers, $combat->team2->aliveMembers);
676 1
    foreach($characters as $character) {
677 1
      foreach($character->effects as $effect) {
678 1
        if($effect->type === SkillSpecial::TYPE_POISON) {
679
          $character->harm($effect->value);
680
          $action = [
681
            "action" => CombatAction::ACTION_POISON, "result" => true, "amount" => $effect->value,
682
            "character1" => $character, "character2" => $character,
683
          ];
684 1
          $combat->log->log($action);
685
        }
686
      }
687
    }
688 1
  }
689
  
690
  /**
691
   * Log results of an action
692
   */
693
  public function logResults(): void {
694 1
    $this->log->log($this->results);
0 ignored issues
show
Bug introduced by
It seems like $this->results can also be of type null; however, parameter $action of HeroesofAbenez\Combat\CombatLogger::log() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

694
    $this->log->log(/** @scrutinizer ignore-type */ $this->results);
Loading history...
695 1
    $this->results = NULL;
696 1
  }
697
  
698
  /**
699
   * Log dealt damage
700
   */
701
  public function logDamage(Character $attacker): void {
702 1
    $team = $this->team1->hasMember($attacker->id) ? 1 : 2;
703 1
    $this->damage[$team] += $this->results["amount"];
704 1
  }
705
  
706
  public function getLog(): CombatLogger {
707 1
    return $this->log;
708
  }
709
}
710
?>