Completed
Push — master ( 54fdf8...606b80 )
by Jakub
02:17
created

CombatBase   F

Complexity

Total Complexity 112

Size/Duplication

Total Lines 616
Duplicated Lines 0 %

Test Coverage

Coverage 89.35%

Importance

Changes 0
Metric Value
wmc 112
dl 0
loc 616
ccs 260
cts 291
cp 0.8935
rs 1.5284
c 0
b 0
f 0

48 Methods

Rating   Name   Duplication   Size   Complexity  
A getVictoryCondition() 0 2 1
A getRound() 0 2 1
A setDuelParticipants() 0 6 1
A setVictoryCondition() 0 2 1
A setTeams() 0 7 2
A setHealers() 0 2 1
A getHealers() 0 2 1
A resetInitiative() 0 5 2
C mainStage() 0 28 7
A getTeam2() 0 2 1
A getWinner() 0 7 2
A logRoundNumber() 0 2 1
A attackHarm() 0 17 3
A setSkillsCooldowns() 0 6 3
A startRound() 0 3 1
A applyEffectProviders() 0 11 4
B doSpecialSkill() 0 20 7
A getTeam() 0 2 2
A calculateHitChance() 0 7 2
A getRoundLimit() 0 2 1
A logCombatResult() 0 12 2
B removeCombatEffects() 0 7 5
A useSpecialSkill() 0 17 2
A logResults() 0 3 1
A selectRandomCharacter() 0 8 3
A getEnemyTeam() 0 2 2
A getAllowedActions() 0 4 1
B findLowestHpCharacter() 0 16 6
A getLog() 0 2 1
B chooseAction() 0 19 8
A selectAttackTarget() 0 7 2
A selectHealingTarget() 0 2 1
A recalculateStats() 0 7 3
A doRound() 0 2 1
A useAttackSkill() 0 18 3
B execute() 0 16 5
A endRound() 0 3 1
A logDamage() 0 3 2
A getTeam1() 0 2 1
A findHealers() 0 6 2
B __construct() 0 26 1
A heal() 0 17 4
A hasHit() 0 3 1
A getTeam2Damage() 0 2 1
A getTeam1Damage() 0 2 1
A applyPoison() 0 12 4
A decreaseSkillsCooldowns() 0 6 3
A calculateHealingSuccessChance() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like CombatBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CombatBase, and based on these observations, apply Extract Interface, too.

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-read int $team1Damage
20
 * @property-read int $team2Damage
21
 * @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...
22
 * @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...
23
 * @method void onCombatStart(CombatBase $combat)
24
 * @method void onCombatEnd(CombatBase $combat)
25
 * @method void onRoundStart(CombatBase $combat)
26
 * @method void onRound(CombatBase $combat)
27
 * @method void onRoundEnd(CombatBase $combat)
28
 * @method void onAttack(Character $attacker, Character $defender)
29
 * @method void onSkillAttack(Character $attacker, Character $defender, CharacterAttackSkill $skill)
30
 * @method void onSkillSpecial(Character $character1, Character $target, CharacterSpecialSkill $skill)
31
 * @method void onHeal(Character $healer, Character $patient)
32
 */
33 1
class CombatBase {
34 1
  use \Nette\SmartObject;
35
  
36
  protected const LOWEST_HP_THRESHOLD = 0.5;
37
  
38
  /** @var Team First team */
39
  protected $team1;
40
  /** @var Team Second team */
41
  protected $team2;
42
  /** @var CombatLogger */
43
  protected $log;
44
  /** @var int Number of current round */
45
  protected $round = 0;
46
  /** @var int Round limit */
47
  protected $roundLimit = 30;
48
  /** @var array Dealt damage by team */
49
  protected $damage = [1 => 0, 2 => 0];
50
  /** @var callable[] */
51
  public $onCombatStart = [];
52
  /** @var callable[] */
53
  public $onCombatEnd = [];
54
  /** @var callable[] */
55
  public $onRoundStart = [];
56
  /** @var callable[] */
57
  public $onRound = [];
58
  /** @var callable[] */
59
  public $onRoundEnd = [];
60
  /** @var callable[] */
61
  public $onAttack = [];
62
  /** @var callable[] */
63
  public $onSkillAttack = [];
64
  /** @var callable[] */
65
  public $onSkillSpecial = [];
66
  /** @var callable[] */
67
  public $onHeal = [];
68
  /** @var array|NULL Temporary variable for results of an action */
69
  protected $results;
70
  /** @var callable */
71
  protected $victoryCondition;
72
  /** @var callable */
73
  protected $healers;
74
  
75
  public function __construct(CombatLogger $logger) {
76 1
    $this->log = $logger;
77 1
    $this->onCombatStart[] = [$this, "applyEffectProviders"];
78 1
    $this->onCombatStart[] = [$this, "setSkillsCooldowns"];
79 1
    $this->onCombatEnd[] = [$this, "removeCombatEffects"];
80 1
    $this->onCombatEnd[] = [$this, "logCombatResult"];
81 1
    $this->onCombatEnd[] = [$this, "resetInitiative"];
82 1
    $this->onRoundStart[] = [$this ,"recalculateStats"];
83 1
    $this->onRoundStart[] = [$this, "logRoundNumber"];
84 1
    $this->onRoundStart[] = [$this, "applyPoison"];
85 1
    $this->onRound[] = [$this, "mainStage"];
86 1
    $this->onRoundEnd[] = [$this, "decreaseSkillsCooldowns"];
87 1
    $this->onRoundEnd[] = [$this, "resetInitiative"];
88 1
    $this->onAttack[] = [$this, "attackHarm"];
89 1
    $this->onAttack[] = [$this, "logDamage"];
90 1
    $this->onAttack[] = [$this, "logResults"];
91 1
    $this->onSkillAttack[] = [$this, "useAttackSkill"];
92 1
    $this->onSkillAttack[] = [$this, "logDamage"];
93 1
    $this->onSkillAttack[] = [$this, "logResults"];
94 1
    $this->onSkillSpecial[] = [$this, "useSpecialSkill"];
95 1
    $this->onSkillSpecial[] = [$this, "logResults"];
96 1
    $this->onHeal[] = [$this, "heal"];
97 1
    $this->onHeal[] = [$this, "logResults"];
98 1
    $this->victoryCondition = [VictoryConditions::class, "moreDamage"];
1 ignored issue
show
Documentation Bug introduced by
It seems like array(HeroesofAbenez\Com...s::class, 'moreDamage') of type array<integer,string> is incompatible with the declared type callable of property $victoryCondition.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
99 1
    $this->healers = function(): Team {
100
      return new Team("healers");
101
    };
102 1
  }
103
  
104
  public function getRound(): int {
105 1
    return $this->round;
106
  }
107
  
108
  public function getRoundLimit(): int {
109 1
    return $this->roundLimit;
110
  }
111
  
112
  /**
113
   * Set teams
114
   */
115
  public function setTeams(Team $team1, Team $team2): void {
116 1
    if(isset($this->team1)) {
117 1
      throw new ImmutableException("Teams has already been set.");
118
    }
119 1
    $this->team1 = & $team1;
120 1
    $this->team2 = & $team2;
121 1
    $this->log->setTeams($team1, $team2);
122 1
  }
123
  
124
  /**
125
   * Set participants for duel
126
   * Creates teams named after the member
127
   */
128
  public function setDuelParticipants(Character $player, Character $opponent): void {
129 1
    $team1 = new Team($player->name);
130 1
    $team1[] = $player;
131 1
    $team2 = new Team($opponent->name);
132 1
    $team2[] = $opponent;
133 1
    $this->setTeams($team1, $team2);
134 1
  }
135
  
136
  public function getTeam1(): Team {
137 1
    return $this->team1;
138
  }
139
  
140
  public function getTeam2(): Team {
141 1
    return $this->team2;
142
  }
143
  
144
  public function getVictoryCondition(): callable {
145
    return $this->victoryCondition;
146
  }
147
  
148
  public function setVictoryCondition(callable $victoryCondition) {
149
    $this->victoryCondition = $victoryCondition;
150
  }
151
  
152
  public function getHealers(): callable {
153
    return $this->healers;
154
  }
155
  
156
  public function setHealers(callable $healers) {
157 1
    $this->healers = $healers;
158 1
  }
159
  
160
  public function getTeam1Damage(): int {
161 1
    return $this->damage[1];
162
  }
163
  
164
  public function getTeam2Damage(): int {
165 1
    return $this->damage[2];
166
  }
167
  
168
  /**
169
   * Get winner of combat
170
   * 
171
   * @staticvar int $result
172
   * @return int Winning team/0
173
   */
174
  public function getWinner(): int {
175 1
    static $result = 0;
176 1
    if($result === 0) {
177 1
      $result = call_user_func($this->victoryCondition, $this);
178 1
      $result = Numbers::range($result, 0, 2);
179
    }
180 1
    return $result;
181
  }
182
  
183
  protected function getTeam(Character $character): Team {
184 1
    return $this->team1->hasMember($character->id) ? $this->team1 : $this->team2;
185
  }
186
  
187
  protected function getEnemyTeam(Character $character): Team {
188 1
    return $this->team1->hasMember($character->id) ? $this->team2 : $this->team1;
189
  }
190
  
191
  public function applyEffectProviders(CombatBase $combat): void {
192
    /** @var Character[] $characters */
193 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
194 1
    foreach($characters as $character) {
195 1
      foreach($character->effectProviders as $item) {
196 1
        $effect = $item->toCombatEffect();
197 1
        if(is_null($effect)) {
198
          continue;
199
        }
200 1
        $character->addEffect($effect);
201 1
        $effect->onApply($character, $effect);
202
      }
203
    }
204 1
  }
205
  
206
  /**
207
   * Set skills' cooldowns
208
   */
209
  public function setSkillsCooldowns(CombatBase $combat): void {
210
    /** @var Character[] $characters */
211 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
212 1
    foreach($characters as $character) {
213 1
      foreach($character->skills as $skill) {
214 1
        $skill->resetCooldown();
215
      }
216
    }
217 1
  }
218
  
219
  /**
220
   * Decrease skills' cooldowns
221
   */
222
  public function decreaseSkillsCooldowns(CombatBase $combat): void {
223
    /** @var Character[] $characters */
224 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
225 1
    foreach($characters as $character) {
226 1
      foreach($character->skills as $skill) {
227 1
        $skill->decreaseCooldown();
228
      }
229
    }
230 1
  }
231
  
232
  /**
233
   * Remove combat effects from character at the end of the combat
234
   */
235
  public function removeCombatEffects(CombatBase $combat): void {
236
    /** @var Character[] $characters */
237 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
238 1
    foreach($characters as $character) {
239 1
      foreach($character->effects as $effect) {
240 1
        if($effect->duration === CharacterEffect::DURATION_COMBAT OR is_int($effect->duration)) {
241 1
          $character->removeEffect($effect->id);
242
        }
243
      }
244
    }
245 1
  }
246
  
247
  /**
248
   * Add winner to the log
249
   */
250
  public function logCombatResult(CombatBase $combat): void {
251 1
    $combat->log->round = 5000;
252
    $params = [
253 1
      "team1name" => $combat->team1->name, "team1damage" => $combat->damage[1],
254 1
      "team2name" => $combat->team2->name, "team2damage" => $combat->damage[2],
255
    ];
256 1
    if($combat->winner === 1) {
257
      $params["winner"] = $combat->team1->name;
258
    } else {
259 1
      $params["winner"] = $combat->team2->name;
260
    }
261 1
    $combat->log->logText("combat.log.combatEnd", $params);
262 1
  }
263
  
264
  /**
265
   * Log start of a round
266
   */
267
  public function logRoundNumber(CombatBase $combat): void {
268 1
    $combat->log->round = ++$this->round;
269 1
  }
270
  
271
  /**
272
   * Decrease duration of effects and recalculate stats
273
   */
274
  public function recalculateStats(CombatBase $combat): void {
275
    /** @var Character[] $characters */
276 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
277 1
    foreach($characters as $character) {
278 1
      $character->recalculateStats();
279 1
      if($character->hitpoints > 0) {
280 1
        $character->calculateInitiative();
281
      }
282
    }
283 1
  }
284
  
285
  /**
286
   * Reset characters' initiative
287
   */
288
  public function resetInitiative(CombatBase $combat): void {
289
    /** @var Character[] $characters */
290 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
291 1
    foreach($characters as $character) {
292 1
      $character->resetInitiative();
293
    }
294 1
  }
295
  
296
  /**
297
   * Select random character from the team
298
   */
299
  protected function selectRandomCharacter(Team $team): ?Character {
300 1
    if(count($team->aliveMembers) === 0) {
301
      return NULL;
302 1
    } elseif(count($team) === 1) {
303 1
      return $team[0];
304
    }
305
    $roll = rand(0, count($team->aliveMembers) - 1);
306
    return $team->aliveMembers[$roll];
307
  }
308
  
309
  /**
310
   * Select target for attack
311
   */
312
  protected function selectAttackTarget(Character $attacker): ?Character {
313 1
    $enemyTeam = $this->getEnemyTeam($attacker);
314 1
    $target = $this->findLowestHpCharacter($enemyTeam);
315 1
    if(!is_null($target)) {
316 1
      return $target;
317
    }
318 1
    return $this->selectRandomCharacter($enemyTeam);
319
  }
320
  
321
  /**
322
   * Find character with lowest hp in the team
323
   */
324
  protected function findLowestHpCharacter(Team $team, int $threshold = NULL): ?Character {
325 1
    $lowestHp = 9999;
326 1
    $lowestIndex = -1;
327 1
    if(is_null($threshold)) {
328 1
      $threshold = static::LOWEST_HP_THRESHOLD;
329
    }
330 1
    foreach($team->aliveMembers as $index => $member) {
331 1
      if($member->hitpoints <= $member->maxHitpoints * $threshold AND $member->hitpoints < $lowestHp) {
332 1
        $lowestHp = $member->hitpoints;
333 1
        $lowestIndex = $index;
334
      }
335
    }
336 1
    if($lowestIndex === -1) {
337 1
      return NULL;
338
    }
339 1
    return $team->aliveMembers[$lowestIndex];
340
  }
341
  
342
  /**
343
   * Select target for healing
344
   */
345
  protected function selectHealingTarget(Character $healer): ?Character {
346 1
    return $this->findLowestHpCharacter($this->getTeam($healer));
347
  }
348
  
349
  protected function findHealers(): Team {
350 1
    $healers = call_user_func($this->healers, $this->team1, $this->team2);
351 1
    if($healers instanceof Team) {
352 1
      return $healers;
353
    }
354
    return new Team("healers");
355
  }
356
  
357
  protected function doSpecialSkill(Character $character1, Character $character2, CharacterSpecialSkill $skill): void {
358 1
    switch($skill->skill->target) {
359 1
      case SkillSpecial::TARGET_ENEMY:
360
        $this->onSkillSpecial($character1, $character2, $skill);
361
        break;
362 1
      case SkillSpecial::TARGET_SELF:
363 1
        $this->onSkillSpecial($character1, $character1, $skill);
364 1
        break;
365
      case SkillSpecial::TARGET_PARTY:
366
        $team = $this->getTeam($character1);
367
        foreach($team as $target) {
368
          $this->onSkillSpecial($character1, $target, $skill);
369
        }
370
        break;
371
      case SkillSpecial::TARGET_ENEMY_PARTY:
372
        $team = $this->getEnemyTeam($character1);
373
        foreach($team as $target) {
374
          $this->onSkillSpecial($character1, $target, $skill);
375
        }
376
        break;
377
    }
378 1
  }
379
  
380
  protected function chooseAction(CombatBase $combat, Character $character): ?string {
381 1
    if($character->hitpoints < 1) {
382
      return NULL;
383 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...
384 1
      return CombatAction::ACTION_HEALING;
385
    }
386 1
    $attackTarget = $combat->selectAttackTarget($character);
387 1
    if(is_null($attackTarget)) {
388
      return NULL;
389
    }
390 1
    if(count($character->usableSkills) > 0) {
391 1
      $skill = $character->usableSkills[0];
392 1
      if($skill instanceof CharacterAttackSkill) {
393 1
        return CombatAction::ACTION_SKILL_ATTACK;
394 1
      } elseif($skill instanceof  CharacterSpecialSkill) {
395 1
        return CombatAction::ACTION_SKILL_SPECIAL;
396
      }
397
    }
398 1
    return CombatAction::ACTION_ATTACK;
399
  }
400
  
401
  protected function getAllowedActions(): array {
402 1
    $allowedActions = Constants::getConstantsValues(CombatAction::class, "ACTION_");
403 1
    return array_values(array_filter($allowedActions, function(string $value) {
404 1
      return ($value !== CombatAction::ACTION_POISON);
405 1
    }));
406
  }
407
  
408
  /**
409
   * Main stage of a round
410
   */
411
  public function mainStage(CombatBase $combat): void {
412
    /** @var Character[] $characters */
413 1
    $characters = array_merge($combat->team1->usableMembers, $combat->team2->usableMembers);
414 1
    usort($characters, function(Character $a, Character $b) {
415 1
      return -1 * strcmp((string) $a->initiative, (string) $b->initiative);
416 1
    });
417 1
    foreach($characters as $character) {
418 1
      $action = $combat->chooseAction($combat, $character);
419 1
      if(!in_array($action, $this->getAllowedActions(), true)) {
420
        continue;
421
      }
422
      switch($action) {
423 1
        case CombatAction::ACTION_ATTACK:
424 1
          $combat->onAttack($character, $combat->selectAttackTarget($character));
425 1
          break;
426 1
        case CombatAction::ACTION_SKILL_ATTACK:
427
          /** @var CharacterAttackSkill $skill */
428 1
          $skill = $character->usableSkills[0];
429 1
          $combat->onSkillAttack($character, $combat->selectAttackTarget($character), $skill);
430 1
          break;
431 1
        case CombatAction::ACTION_SKILL_SPECIAL:
432
          /** @var CharacterSpecialSkill $skill */
433 1
          $skill = $character->usableSkills[0];
434 1
          $combat->doSpecialSkill($character, $combat->selectAttackTarget($character), $skill);
435 1
          break;
436 1
        case CombatAction::ACTION_HEALING:
437 1
          $combat->onHeal($character, $combat->selectHealingTarget($character));
438 1
          break;
439
      }
440
    }
441 1
  }
442
  
443
  /**
444
   * Start next round
445
   * 
446
   * @return int Winning team/0
447
   */
448
  protected function startRound(): int {
449 1
    $this->onRoundStart($this);
450 1
    return $this->getWinner();
451
  }
452
  
453
  /**
454
   * Do a round
455
   */
456
  protected function doRound(): void {
457 1
    $this->onRound($this);
458 1
  }
459
  
460
  /**
461
   * End round
462
   * 
463
   * @return int Winning team/0
464
   */
465
  protected function endRound(): int {
466 1
    $this->onRoundEnd($this);
467 1
    return $this->getWinner();
468
  }
469
  
470
  /**
471
   * Executes the combat
472
   * 
473
   * @return int Winning team
474
   */
475
  public function execute(): int {
476 1
    if(!isset($this->team1)) {
477 1
      throw new InvalidStateException("Teams are not set.");
478
    }
479 1
    $this->onCombatStart($this);
480 1
    while($this->round <= $this->roundLimit) {
481 1
      if($this->startRound() > 0) {
482 1
        break;
483
      }
484 1
      $this->doRound();
485 1
      if($this->endRound() > 0) {
486
        break;
487
      }
488
    }
489 1
    $this->onCombatEnd($this);
490 1
    return $this->getWinner();
491
  }
492
  
493
  /**
494
   * Calculate hit chance for attack/skill attack
495
   */
496
  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...
497 1
    $hitRate = $character1->hit;
498 1
    $dodgeRate = $character2->dodge;
499 1
    if(!is_null($skill)) {
500 1
      $hitRate = $hitRate / 100 * $skill->hitRate;
501
    }
502 1
    return Numbers::range((int) ($hitRate - $dodgeRate), 15, 100);
503
  }
504
  
505
  /**
506
   * Check whether action succeeded
507
   */
508
  protected function hasHit(int $hitChance): bool {
509 1
    $roll = rand(0, 100);
510 1
    return ($roll <= $hitChance);
511
  }
512
  
513
  /**
514
   * Do an attack
515
   * Hit chance = Attacker's hit - Defender's dodge, but at least 15%
516
   * Damage = Attacker's damage - defender's defense
517
   */
518
  public function attackHarm(Character $attacker, Character $defender): void {
519 1
    $result = [];
520 1
    $hitChance = $this->calculateHitChance($attacker, $defender);
521 1
    $result["result"] = $this->hasHit($hitChance);
522 1
    $result["amount"] = 0;
523 1
    if($result["result"]) {
524 1
      $amount = $attacker->damage - $defender->defense;
525 1
      $result["amount"] = Numbers::range($amount, 0, $defender->hitpoints);
526
    }
527 1
    if($result["amount"] > 0) {
528 1
      $defender->harm($result["amount"]);
529
    }
530 1
    $result["action"] = CombatAction::ACTION_ATTACK;
531 1
    $result["name"] = "";
532 1
    $result["character1"] = $attacker;
533 1
    $result["character2"] = $defender;
534 1
    $this->results = $result;
535 1
  }
536
  
537
  /**
538
   * Use an attack skill
539
   */
540
  public function useAttackSkill(Character $attacker, Character $defender, CharacterAttackSkill $skill): void {
541 1
    $result = [];
542 1
    $hitChance = $this->calculateHitChance($attacker, $defender, $skill);
543 1
    $result["result"] = $this->hasHit($hitChance);
544 1
    $result["amount"] = 0;
545 1
    if($result["result"]) {
546 1
      $amount = (int) ($attacker->damage - $defender->defense / 100 * $skill->damage);
547 1
      $result["amount"] = Numbers::range($amount, 0, $defender->hitpoints);
548
    }
549 1
    if($result["amount"]) {
550 1
      $defender->harm($result["amount"]);
551
    }
552 1
    $result["action"] = CombatAction::ACTION_SKILL_ATTACK;
553 1
    $result["name"] = $skill->skill->name;
554 1
    $result["character1"] = $attacker;
555 1
    $result["character2"] = $defender;
556 1
    $this->results = $result;
557 1
    $skill->resetCooldown();
558 1
  }
559
  
560
  /**
561
   * Use a special skill
562
   */
563
  public function useSpecialSkill(Character $character1, Character $target, CharacterSpecialSkill $skill): void {
564
    $result = [
565 1
      "result" => true, "amount" => 0, "action" => CombatAction::ACTION_SKILL_SPECIAL, "name" => $skill->skill->name,
566 1
      "character1" => $character1, "character2" => $target,
567
    ];
568 1
    $this->results = $result;
569 1
    $effect = new CharacterEffect([
570 1
      "id" => "skill{$skill->skill->id}Effect",
571 1
      "type" => $skill->skill->type,
572 1
      "stat" => ((in_array($skill->skill->type, SkillSpecial::NO_STAT_TYPES, true)) ? NULL : $skill->skill->stat),
573 1
      "value" => $skill->value,
574 1
      "source" => CharacterEffect::SOURCE_SKILL,
575 1
      "duration" => $skill->skill->duration
576
    ]);
577 1
    $target->addEffect($effect);
578 1
    $effect->onApply($target, $effect);
579 1
    $skill->resetCooldown();
580 1
  }
581
  
582
  /**
583
   * Calculate success chance of healing
584
   */
585
  protected function calculateHealingSuccessChance(Character $healer): int {
586 1
    return $healer->intelligence * (int) round($healer->level / 5) + 30;
587
  }
588
  
589
  /**
590
   * Heal a character
591
   */
592
  public function heal(Character $healer, Character $patient): void {
593 1
    $result = [];
594 1
    $hitChance = $this->calculateHealingSuccessChance($healer);
595 1
    $result["result"] = $this->hasHit($hitChance);
596 1
    $amount = ($result["result"]) ? $healer->intelligence / 2 : 0;
597 1
    if($amount + $patient->hitpoints > $patient->maxHitpoints) {
598
      $amount = $patient->maxHitpoints - $patient->hitpoints;
599
    }
600 1
    $result["amount"] = (int) $amount;
601 1
    if($result["amount"]) {
602 1
      $patient->heal($result["amount"]);
603
    }
604 1
    $result["action"] = CombatAction::ACTION_HEALING;
605 1
    $result["name"] = "";
606 1
    $result["character1"] = $healer;
607 1
    $result["character2"] = $patient;
608 1
    $this->results = $result;
609 1
  }
610
  
611
  /**
612
   * Harm poisoned characters at start of round
613
   */
614
  public function applyPoison(CombatBase $combat): void {
615
    /** @var Character[] $characters */
616 1
    $characters = array_merge($combat->team1->aliveMembers, $combat->team2->aliveMembers);
617 1
    foreach($characters as $character) {
618 1
      foreach($character->effects as $effect) {
619 1
        if($effect->type === SkillSpecial::TYPE_POISON) {
620
          $character->harm($effect->value);
621
          $action = [
622
            "action" => CombatAction::ACTION_POISON, "result" => true, "amount" => $effect->value,
623
            "character1" => $character, "character2" => $character,
624
          ];
625 1
          $combat->log->log($action);
626
        }
627
      }
628
    }
629 1
  }
630
  
631
  /**
632
   * Log results of an action
633
   */
634
  public function logResults(): void {
635 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

635
    $this->log->log(/** @scrutinizer ignore-type */ $this->results);
Loading history...
636 1
    $this->results = NULL;
637 1
  }
638
  
639
  /**
640
   * Log dealt damage
641
   */
642
  public function logDamage(Character $attacker): void {
643 1
    $team = $this->team1->hasMember($attacker->id) ? 1 : 2;
644 1
    $this->damage[$team] += $this->results["amount"];
645 1
  }
646
  
647
  public function getLog(): CombatLogger {
648 1
    return $this->log;
649
  }
650
}
651
?>