Completed
Push — master ( 6e62ff...a5d74c )
by Jakub
02:45
created

CombatBase::useSpecialSkill()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

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