Completed
Push — master ( 7c8b7e...f3e1d4 )
by Jakub
02:23
created

CombatBase::selectRandomCharacter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4.125

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 3
cts 6
cp 0.5
c 0
b 0
f 0
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
crap 4.125
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 = new CharacterEffect($character->getPet($character->activePet)->deployParams);
246 1
        $character->addEffect($effect);
247 1
        $effect->onApply($character, $effect);
248
      }
249
    }
250 1
  }
251
  
252
  /**
253
   * Apply effects from worn items
254
   */
255
  public function equipItems(CombatBase $combat): void {
256
    /** @var Character[] $characters */
257 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
258 1
    foreach($characters as $character) {
259 1
      foreach($character->equipment as $item) {
260 1
        if($item->worn) {
261 1
          $character->equipItem($item->id);
262
        }
263
      }
264
    }
265 1
  }
266
  
267
  /**
268
   * Set skills' cooldowns
269
   */
270
  public function setSkillsCooldowns(CombatBase $combat): void {
271
    /** @var Character[] $characters */
272 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
273 1
    foreach($characters as $character) {
274 1
      foreach($character->skills as $skill) {
275 1
        $skill->resetCooldown();
276
      }
277
    }
278 1
  }
279
  
280
  /**
281
   * Decrease skills' cooldowns
282
   */
283
  public function decreaseSkillsCooldowns(CombatBase $combat): void {
284
    /** @var Character[] $characters */
285 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
286 1
    foreach($characters as $character) {
287 1
      foreach($character->skills as $skill) {
288 1
        $skill->decreaseCooldown();
289
      }
290
    }
291 1
  }
292
  
293
  /**
294
   * Remove combat effects from character at the end of the combat
295
   */
296
  public function removeCombatEffects(CombatBase $combat): void {
297
    /** @var Character[] $characters */
298 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
299 1
    foreach($characters as $character) {
300 1
      foreach($character->effects as $effect) {
301 1
        if($effect->duration === CharacterEffect::DURATION_COMBAT OR is_int($effect->duration)) {
302 1
          $character->removeEffect($effect->id);
303
        }
304
      }
305
    }
306 1
  }
307
  
308
  /**
309
   * Add winner to the log
310
   */
311
  public function logCombatResult(CombatBase $combat): void {
312 1
    $combat->log->round = 5000;
313
    $params = [
314 1
      "team1name" => $combat->team1->name, "team1damage" => $combat->damage[1],
315 1
      "team2name" => $combat->team2->name, "team2damage" => $combat->damage[2],
316
    ];
317 1
    if($combat->winner === 1) {
318 1
      $params["winner"] = $combat->team1->name;
319
    } else {
320
      $params["winner"] = $combat->team2->name;
321
    }
322 1
    $combat->log->logText("combat.log.combatEnd", $params);
323 1
  }
324
  
325
  /**
326
   * Log start of a round
327
   */
328
  public function logRoundNumber(CombatBase $combat): void {
329 1
    $combat->log->round = ++$this->round;
330 1
  }
331
  
332
  /**
333
   * Decrease duration of effects and recalculate stats
334
   */
335
  public function recalculateStats(CombatBase $combat): void {
336
    /** @var Character[] $characters */
337 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
338 1
    foreach($characters as $character) {
339 1
      $character->recalculateStats();
340 1
      if($character->hitpoints > 0) {
341 1
        $character->calculateInitiative();
342
      }
343
    }
344 1
  }
345
  
346
  /**
347
   * Reset characters' initiative
348
   */
349
  public function resetInitiative(CombatBase $combat): void {
350
    /** @var Character[] $characters */
351 1
    $characters = array_merge($combat->team1->items, $combat->team2->items);
352 1
    foreach($characters as $character) {
353 1
      $character->resetInitiative();
354
    }
355 1
  }
356
  
357
  /**
358
   * Select random character from the team
359
   */
360
  protected function selectRandomCharacter(Team $team): ?Character {
361 1
    if(count($team->aliveMembers) === 0) {
362
      return NULL;
363 1
    } elseif(count($team) === 1) {
364 1
      return $team[0];
365
    }
366
    $roll = rand(0, count($team->aliveMembers) - 1);
367
    return $team->aliveMembers[$roll];
368
  }
369
  
370
  /**
371
   * Select target for attack
372
   */
373
  protected function selectAttackTarget(Character $attacker): ?Character {
374 1
    $enemyTeam = $this->getEnemyTeam($attacker);
375 1
    $target = $this->findLowestHpCharacter($enemyTeam);
376 1
    if(!is_null($target)) {
377 1
      return $target;
378
    }
379 1
    return $this->selectRandomCharacter($enemyTeam);
380
  }
381
  
382
  /**
383
   * Find character with lowest hp in the team
384
   */
385
  protected function findLowestHpCharacter(Team $team, int $threshold = NULL): ?Character {
386 1
    $lowestHp = 9999;
387 1
    $lowestIndex = -1;
388 1
    if(is_null($threshold)) {
389 1
      $threshold = static::LOWEST_HP_THRESHOLD;
390
    }
391 1
    foreach($team->aliveMembers as $index => $member) {
392 1
      if($member->hitpoints <= $member->maxHitpoints * $threshold AND $member->hitpoints < $lowestHp) {
393 1
        $lowestHp = $member->hitpoints;
394 1
        $lowestIndex = $index;
395
      }
396
    }
397 1
    if($lowestIndex === -1) {
398 1
      return NULL;
399
    }
400 1
    return $team->aliveMembers[$lowestIndex];
401
  }
402
  
403
  /**
404
   * Select target for healing
405
   */
406
  protected function selectHealingTarget(Character $healer): ?Character {
407 1
    return $this->findLowestHpCharacter($this->getTeam($healer));
408
  }
409
  
410
  protected function findHealers(): Team {
411 1
    $healers = call_user_func($this->healers, $this->team1, $this->team2);
412 1
    if($healers instanceof Team) {
413 1
      return $healers;
414
    }
415
    return new Team("healers");
416
  }
417
  
418
  protected function doSpecialSkill(Character $character1, Character $character2, CharacterSpecialSkill $skill): void {
419 1
    switch($skill->skill->target) {
420 1
      case SkillSpecial::TARGET_ENEMY:
421
        $this->onSkillSpecial($character1, $character2, $skill);
422
        break;
423 1
      case SkillSpecial::TARGET_SELF:
424 1
        $this->onSkillSpecial($character1, $character1, $skill);
425 1
        break;
426
      case SkillSpecial::TARGET_PARTY:
427
        $team = $this->getTeam($character1);
428
        foreach($team as $target) {
429
          $this->onSkillSpecial($character1, $target, $skill);
430
        }
431
        break;
432
      case SkillSpecial::TARGET_ENEMY_PARTY:
433
        $team = $this->getEnemyTeam($character1);
434
        foreach($team as $target) {
435
          $this->onSkillSpecial($character1, $target, $skill);
436
        }
437
        break;
438
    }
439 1
  }
440
  
441
  protected function chooseAction(CombatBase $combat, Character $character): ?string {
442 1
    if($character->hitpoints < 1) {
443
      return NULL;
444 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...
445 1
      return CombatAction::ACTION_HEALING;
446
    }
447 1
    $attackTarget = $combat->selectAttackTarget($character);
448 1
    if(is_null($attackTarget)) {
449
      return NULL;
450
    }
451 1
    if(count($character->usableSkills) > 0) {
452 1
      $skill = $character->usableSkills[0];
453 1
      if($skill instanceof CharacterAttackSkill) {
454 1
        return CombatAction::ACTION_SKILL_ATTACK;
455 1
      } elseif($skill instanceof  CharacterSpecialSkill) {
456 1
        return CombatAction::ACTION_SKILL_SPECIAL;
457
      }
458
    }
459 1
    return CombatAction::ACTION_ATTACK;
460
  }
461
  
462
  protected function getAllowedActions(): array {
463 1
    $allowedActions = Constants::getConstantsValues(CombatAction::class, "ACTION_");
464 1
    return array_values(array_filter($allowedActions, function(string $value) {
465 1
      return ($value !== CombatAction::ACTION_POISON);
466 1
    }));
467
  }
468
  
469
  /**
470
   * Main stage of a round
471
   */
472
  public function mainStage(CombatBase $combat): void {
473
    /** @var Character[] $characters */
474 1
    $characters = array_merge($combat->team1->usableMembers, $combat->team2->usableMembers);
475 1
    usort($characters, function(Character $a, Character $b) {
476 1
      return -1 * strcmp((string) $a->initiative, (string) $b->initiative);
477 1
    });
478 1
    foreach($characters as $character) {
479 1
      $action = $combat->chooseAction($combat, $character);
480 1
      if(!in_array($action, $this->getAllowedActions(), true)) {
481
        continue;
482
      }
483
      switch($action) {
484 1
        case CombatAction::ACTION_ATTACK:
485 1
          $combat->onAttack($character, $combat->selectAttackTarget($character));
486 1
          break;
487 1
        case CombatAction::ACTION_SKILL_ATTACK:
488
          /** @var CharacterAttackSkill $skill */
489 1
          $skill = $character->usableSkills[0];
490 1
          $combat->onSkillAttack($character, $combat->selectAttackTarget($character), $skill);
491 1
          break;
492 1
        case CombatAction::ACTION_SKILL_SPECIAL:
493
          /** @var CharacterSpecialSkill $skill */
494 1
          $skill = $character->usableSkills[0];
495 1
          $combat->doSpecialSkill($character, $combat->selectAttackTarget($character), $skill);
496 1
          break;
497 1
        case CombatAction::ACTION_HEALING:
498 1
          $combat->onHeal($character, $combat->selectHealingTarget($character));
499 1
          break;
500
      }
501
    }
502 1
  }
503
  
504
  /**
505
   * Start next round
506
   * 
507
   * @return int Winning team/0
508
   */
509
  protected function startRound(): int {
510 1
    $this->onRoundStart($this);
511 1
    return $this->getWinner();
512
  }
513
  
514
  /**
515
   * Do a round
516
   */
517
  protected function doRound(): void {
518 1
    $this->onRound($this);
519 1
  }
520
  
521
  /**
522
   * End round
523
   * 
524
   * @return int Winning team/0
525
   */
526
  protected function endRound(): int {
527 1
    $this->onRoundEnd($this);
528 1
    return $this->getWinner();
529
  }
530
  
531
  /**
532
   * Executes the combat
533
   * 
534
   * @return int Winning team
535
   */
536
  public function execute(): int {
537 1
    if(!isset($this->team1)) {
538 1
      throw new InvalidStateException("Teams are not set.");
539
    }
540 1
    $this->onCombatStart($this);
541 1
    while($this->round <= $this->roundLimit) {
542 1
      if($this->startRound() > 0) {
543 1
        break;
544
      }
545 1
      $this->doRound();
546 1
      if($this->endRound() > 0) {
547
        break;
548
      }
549
    }
550 1
    $this->onCombatEnd($this);
551 1
    return $this->getWinner();
552
  }
553
  
554
  /**
555
   * Calculate hit chance for attack/skill attack
556
   */
557
  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...
558 1
    $hitRate = $character1->hit;
559 1
    $dodgeRate = $character2->dodge;
560 1
    if(!is_null($skill)) {
561 1
      $hitRate = $hitRate / 100 * $skill->hitRate;
562
    }
563 1
    return Numbers::range((int) ($hitRate - $dodgeRate), 15, 100);
564
  }
565
  
566
  /**
567
   * Check whether action succeeded
568
   */
569
  protected function hasHit(int $hitChance): bool {
570 1
    $roll = rand(0, 100);
571 1
    return ($roll <= $hitChance);
572
  }
573
  
574
  /**
575
   * Do an attack
576
   * Hit chance = Attacker's hit - Defender's dodge, but at least 15%
577
   * Damage = Attacker's damage - defender's defense
578
   */
579
  public function attackHarm(Character $attacker, Character $defender): void {
580 1
    $result = [];
581 1
    $hitChance = $this->calculateHitChance($attacker, $defender);
582 1
    $result["result"] = $this->hasHit($hitChance);
583 1
    $result["amount"] = 0;
584 1
    if($result["result"]) {
585 1
      $amount = $attacker->damage - $defender->defense;
586 1
      $result["amount"] = Numbers::range($amount, 0, $defender->hitpoints);
587
    }
588 1
    if($result["amount"] > 0) {
589 1
      $defender->harm($result["amount"]);
590
    }
591 1
    $result["action"] = CombatAction::ACTION_ATTACK;
592 1
    $result["name"] = "";
593 1
    $result["character1"] = $attacker;
594 1
    $result["character2"] = $defender;
595 1
    $this->results = $result;
596 1
  }
597
  
598
  /**
599
   * Use an attack skill
600
   */
601
  public function useAttackSkill(Character $attacker, Character $defender, CharacterAttackSkill $skill): void {
602 1
    $result = [];
603 1
    $hitChance = $this->calculateHitChance($attacker, $defender, $skill);
604 1
    $result["result"] = $this->hasHit($hitChance);
605 1
    $result["amount"] = 0;
606 1
    if($result["result"]) {
607 1
      $amount = (int) ($attacker->damage - $defender->defense / 100 * $skill->damage);
608 1
      $result["amount"] = Numbers::range($amount, 0, $defender->hitpoints);
609
    }
610 1
    if($result["amount"]) {
611 1
      $defender->harm($result["amount"]);
612
    }
613 1
    $result["action"] = CombatAction::ACTION_SKILL_ATTACK;
614 1
    $result["name"] = $skill->skill->name;
615 1
    $result["character1"] = $attacker;
616 1
    $result["character2"] = $defender;
617 1
    $this->results = $result;
618 1
    $skill->resetCooldown();
619 1
  }
620
  
621
  /**
622
   * Use a special skill
623
   */
624
  public function useSpecialSkill(Character $character1, Character $target, CharacterSpecialSkill $skill): void {
625
    $result = [
626 1
      "result" => true, "amount" => 0, "action" => CombatAction::ACTION_SKILL_SPECIAL, "name" => $skill->skill->name,
627 1
      "character1" => $character1, "character2" => $target,
628
    ];
629 1
    $this->results = $result;
630 1
    $effect = new CharacterEffect([
631 1
      "id" => "skill{$skill->skill->id}Effect",
632 1
      "type" => $skill->skill->type,
633 1
      "stat" => ((in_array($skill->skill->type, SkillSpecial::NO_STAT_TYPES, true)) ? NULL : $skill->skill->stat),
634 1
      "value" => $skill->value,
635 1
      "source" => CharacterEffect::SOURCE_SKILL,
636 1
      "duration" => $skill->skill->duration
637
    ]);
638 1
    $target->addEffect($effect);
639 1
    $effect->onApply($target, $effect);
640 1
    $skill->resetCooldown();
641 1
  }
642
  
643
  /**
644
   * Calculate success chance of healing
645
   */
646
  protected function calculateHealingSuccessChance(Character $healer): int {
647 1
    return $healer->intelligence * (int) round($healer->level / 5) + 30;
648
  }
649
  
650
  /**
651
   * Heal a character
652
   */
653
  public function heal(Character $healer, Character $patient): void {
654 1
    $result = [];
655 1
    $hitChance = $this->calculateHealingSuccessChance($healer);
656 1
    $result["result"] = $this->hasHit($hitChance);
657 1
    $amount = ($result["result"]) ? $healer->intelligence / 2 : 0;
658 1
    if($amount + $patient->hitpoints > $patient->maxHitpoints) {
659
      $amount = $patient->maxHitpoints - $patient->hitpoints;
660
    }
661 1
    $result["amount"] = (int) $amount;
662 1
    if($result["amount"]) {
663 1
      $patient->heal($result["amount"]);
664
    }
665 1
    $result["action"] = CombatAction::ACTION_HEALING;
666 1
    $result["name"] = "";
667 1
    $result["character1"] = $healer;
668 1
    $result["character2"] = $patient;
669 1
    $this->results = $result;
670 1
  }
671
  
672
  /**
673
   * Harm poisoned characters at start of round
674
   */
675
  public function applyPoison(CombatBase $combat): void {
676
    /** @var Character[] $characters */
677 1
    $characters = array_merge($combat->team1->aliveMembers, $combat->team2->aliveMembers);
678 1
    foreach($characters as $character) {
679 1
      foreach($character->effects as $effect) {
680 1
        if($effect->type === SkillSpecial::TYPE_POISON) {
681
          $character->harm($effect->value);
682
          $action = [
683
            "action" => CombatAction::ACTION_POISON, "result" => true, "amount" => $effect->value,
684
            "character1" => $character, "character2" => $character,
685
          ];
686 1
          $combat->log->log($action);
687
        }
688
      }
689
    }
690 1
  }
691
  
692
  /**
693
   * Log results of an action
694
   */
695
  public function logResults(): void {
696 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

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