Completed
Push — master ( 76fd65...fe9b11 )
by Jakub
02:06
created

CombatBase::__construct()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 26
cts 27
cp 0.963
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 27
nc 1
nop 2
crap 1
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
  protected const LOWEST_HP_THRESHOLD = 0.5;
38
  
39
  /** @var Team First team */
40
  protected $team1;
41
  /** @var Team Second team */
42
  protected $team2;
43
  /** @var CombatLogger */
44
  protected $log;
45
  /** @var int Number of current round */
46
  protected $round = 0;
47
  /** @var int Round limit */
48
  protected $roundLimit = 30;
49
  /** @var array Dealt damage by team */
50
  protected $damage = [1 => 0, 2 => 0];
51
  /** @var callable[] */
52
  public $onCombatStart = [];
53
  /** @var callable[] */
54
  public $onCombatEnd = [];
55
  /** @var callable[] */
56
  public $onRoundStart = [];
57
  /** @var callable[] */
58
  public $onRound = [];
59
  /** @var callable[] */
60
  public $onRoundEnd = [];
61
  /** @var callable[] */
62
  public $onAttack = [];
63
  /** @var callable[] */
64
  public $onSkillAttack = [];
65
  /** @var callable[] */
66
  public $onSkillSpecial = [];
67
  /** @var callable[] */
68
  public $onHeal = [];
69
  /** @var array|NULL Temporary variable for results of an action */
70
  protected $results;
71
  /** @var callable */
72
  protected $victoryCondition;
73
  /** @var callable */
74
  protected $healers;
75
  /** @var ISuccessCalculator */
76
  protected $successCalculator;
77
  
78
  public function __construct(CombatLogger $logger, ?ISuccessCalculator $successCalculator = NULL) {
79 1
    $this->log = $logger;
80 1
    $this->onCombatStart[] = [$this, "applyEffectProviders"];
81 1
    $this->onCombatStart[] = [$this, "setSkillsCooldowns"];
82 1
    $this->onCombatStart[] = [$this, "assignPositions"];
83 1
    $this->onCombatEnd[] = [$this, "removeCombatEffects"];
84 1
    $this->onCombatEnd[] = [$this, "logCombatResult"];
85 1
    $this->onCombatEnd[] = [$this, "resetInitiative"];
86 1
    $this->onRoundStart[] = [$this ,"recalculateStats"];
87 1
    $this->onRoundStart[] = [$this, "logRoundNumber"];
88 1
    $this->onRoundStart[] = [$this, "applyPoison"];
89 1
    $this->onRound[] = [$this, "mainStage"];
90 1
    $this->onRoundEnd[] = [$this, "decreaseSkillsCooldowns"];
91 1
    $this->onRoundEnd[] = [$this, "resetInitiative"];
92 1
    $this->onAttack[] = [$this, "attackHarm"];
93 1
    $this->onAttack[] = [$this, "logDamage"];
94 1
    $this->onAttack[] = [$this, "logResults"];
95 1
    $this->onSkillAttack[] = [$this, "useAttackSkill"];
96 1
    $this->onSkillAttack[] = [$this, "logDamage"];
97 1
    $this->onSkillAttack[] = [$this, "logResults"];
98 1
    $this->onSkillSpecial[] = [$this, "useSpecialSkill"];
99 1
    $this->onSkillSpecial[] = [$this, "logResults"];
100 1
    $this->onHeal[] = [$this, "heal"];
101 1
    $this->onHeal[] = [$this, "logResults"];
102 1
    $this->victoryCondition = [VictoryConditions::class, "moreDamage"];
103 1
    $this->successCalculator = $successCalculator ?? new RandomSuccessCalculator();
104 1
    $this->healers = function(): Team {
105
      return new Team("healers");
106
    };
107 1
  }
108
  
109
  public function getRound(): int {
110 1
    return $this->round;
111
  }
112
  
113
  public function getRoundLimit(): int {
114 1
    return $this->roundLimit;
115
  }
116
  
117
  /**
118
   * Set teams
119
   */
120
  public function setTeams(Team $team1, Team $team2): void {
121 1
    if(isset($this->team1)) {
122 1
      throw new ImmutableException("Teams has already been set.");
123
    }
124 1
    $this->team1 = & $team1;
125 1
    $this->team2 = & $team2;
126 1
    $this->log->setTeams($team1, $team2);
127 1
  }
128
  
129
  /**
130
   * Set participants for duel
131
   * Creates teams named after the member
132
   */
133
  public function setDuelParticipants(Character $player, Character $opponent): void {
134 1
    $team1 = new Team($player->name);
135 1
    $team1[] = $player;
136 1
    $team2 = new Team($opponent->name);
137 1
    $team2[] = $opponent;
138 1
    $this->setTeams($team1, $team2);
139 1
  }
140
  
141
  public function getTeam1(): Team {
142 1
    return $this->team1;
143
  }
144
  
145
  public function getTeam2(): Team {
146 1
    return $this->team2;
147
  }
148
  
149
  public function getVictoryCondition(): callable {
150 1
    return $this->victoryCondition;
151
  }
152
  
153
  public function setVictoryCondition(callable $victoryCondition) {
154 1
    $this->victoryCondition = $victoryCondition;
155 1
  }
156
  
157
  public function getHealers(): callable {
158
    return $this->healers;
159
  }
160
  
161
  public function setHealers(callable $healers) {
162 1
    $this->healers = $healers;
163 1
  }
164
  
165
  public function getTeam1Damage(): int {
166 1
    return $this->damage[1];
167
  }
168
  
169
  public function getTeam2Damage(): int {
170 1
    return $this->damage[2];
171
  }
172
  
173
  public function getSuccessCalculator(): ISuccessCalculator {
174 1
    return $this->successCalculator;
175
  }
176
  
177
  public function setSuccessCalculator(ISuccessCalculator $successCalculator): void {
178 1
    $this->successCalculator = $successCalculator;
179 1
  }
180
  
181
  /**
182
   * Get winner of combat
183
   * 
184
   * @staticvar int $result
185
   * @return int Winning team/0
186
   */
187
  public function getWinner(): int {
188 1
    static $result = 0;
189 1
    if($result === 0) {
190 1
      $result = call_user_func($this->victoryCondition, $this);
191 1
      $result = Numbers::range($result, 0, 2);
192
    }
193 1
    return $result;
194
  }
195
  
196
  protected function getTeam(Character $character): Team {
197 1
    return $this->team1->hasMembers(["id" => $character->id]) ? $this->team1 : $this->team2;
198
  }
199
  
200
  protected function getEnemyTeam(Character $character): Team {
201 1
    return $this->team1->hasMembers(["id" => $character->id]) ? $this->team2 : $this->team1;
202
  }
203
  
204
  public function applyEffectProviders(CombatBase $combat): void {
205
    /** @var Character[] $characters */
206 1
    $characters = array_merge($combat->team1->toArray(), $combat->team2->toArray());
207 1
    foreach($characters as $character) {
208 1
      foreach($character->effectProviders as $item) {
209 1
        $effect = $item->toCombatEffect();
210 1
        if(is_null($effect)) {
211
          continue;
212
        }
213 1
        $character->addEffect($effect);
214
      }
215
    }
216 1
  }
217
  
218
  /**
219
   * Set skills' cooldowns
220
   */
221
  public function setSkillsCooldowns(CombatBase $combat): void {
222
    /** @var Character[] $characters */
223 1
    $characters = array_merge($combat->team1->toArray(), $combat->team2->toArray());
224 1
    foreach($characters as $character) {
225 1
      foreach($character->skills as $skill) {
226 1
        $skill->resetCooldown();
227
      }
228
    }
229 1
  }
230
  
231
  public function assignPositions(CombatBase $combat): void {
232 1
    $assignPositions = function(Team $team) {
233 1
      $row = 1;
234 1
      $column = 0;
235
      /** @var Character $character */
236 1
      foreach($team as $character) {
237
        try {
238 1
          $column++;
239 1
          if($character->positionRow > 0 AND $character->positionColumn > 0) {
240 1
            continue;
241
          }
242
          setPosition:
0 ignored issues
show
introduced by
Use of the GOTO language construct is discouraged
Loading history...
243 1
          $team->setCharacterPosition($character->id, $row, $column);
244 1
        } catch(InvalidCharacterPositionException $e) {
245 1
          if($e->getCode() === InvalidCharacterPositionException::ROW_FULL) {
246 1
            $row++;
247 1
            $column = 1;
248
          } elseif($e->getCode() === InvalidCharacterPositionException::POSITION_OCCUPIED) {
249
            $column++;
250
          } else {
251
            throw $e;
252
          }
253 1
          goto setPosition;
0 ignored issues
show
introduced by
Use of the GOTO language construct is discouraged
Loading history...
254
        }
255
      }
256 1
    };
257 1
    $assignPositions($combat->team1);
258 1
    $assignPositions($combat->team2);
259 1
  }
260
  
261
  /**
262
   * Decrease skills' cooldowns
263
   */
264
  public function decreaseSkillsCooldowns(CombatBase $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(CombatBase $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(CombatBase $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
      $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(CombatBase $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(CombatBase $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(CombatBase $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 random character from the team
337
   */
338
  protected function selectRandomCharacter(Team $team): ?Character {
339 1
    if(count($team->aliveMembers) === 0) {
340
      return NULL;
341 1
    } elseif(count($team) === 1) {
342 1
      return $team[0];
343
    }
344
    $roll = rand(0, count($team->aliveMembers) - 1);
345
    return $team->aliveMembers[$roll];
346
  }
347
  
348
  /**
349
   * Select target for attack
350
   */
351
  protected function selectAttackTarget(Character $attacker): ?Character {
352 1
    $enemyTeam = $this->getEnemyTeam($attacker);
353 1
    $target = $this->findLowestHpCharacter($enemyTeam);
354 1
    if(!is_null($target)) {
355 1
      return $target;
356
    }
357 1
    return $this->selectRandomCharacter($enemyTeam);
358
  }
359
  
360
  /**
361
   * Find character with lowest hp in the team
362
   */
363
  protected function findLowestHpCharacter(Team $team, int $threshold = NULL): ?Character {
364 1
    $lowestHp = PHP_INT_MAX;
365 1
    $lowestIndex = PHP_INT_MIN;
366 1
    if(is_null($threshold)) {
367 1
      $threshold = static::LOWEST_HP_THRESHOLD;
368
    }
369 1
    foreach($team->aliveMembers as $index => $member) {
370 1
      if($member->hitpoints <= $member->maxHitpoints * $threshold AND $member->hitpoints < $lowestHp) {
371 1
        $lowestHp = $member->hitpoints;
372 1
        $lowestIndex = $index;
373
      }
374
    }
375 1
    if($lowestIndex === PHP_INT_MIN) {
376 1
      return NULL;
377
    }
378 1
    return $team->aliveMembers[$lowestIndex];
379
  }
380
  
381
  /**
382
   * Select target for healing
383
   */
384
  protected function selectHealingTarget(Character $healer): ?Character {
385 1
    return $this->findLowestHpCharacter($this->getTeam($healer));
386
  }
387
  
388
  protected function findHealers(): Team {
389 1
    $healers = call_user_func($this->healers, $this->team1, $this->team2);
390 1
    if($healers instanceof Team) {
391 1
      return $healers;
392
    }
393
    return new Team("healers");
394
  }
395
  
396
  protected function doAttackSkill(Character $character, CharacterAttackSkill $skill): void {
397 1
    $targets = [];
398 1
    switch($skill->skill->target) {
399 1
      case SkillAttack::TARGET_SINGLE:
400 1
        $targets[] = $this->selectAttackTarget($character);
401 1
        break;
402
      case SkillAttack::TARGET_ROW:
403
        /** @var Character $primaryTarget */
404
        $primaryTarget = $this->selectAttackTarget($character);
405
        $targets = $this->getTeam($primaryTarget)->getMembers(["positionRow" => $primaryTarget->positionRow]);
406
        break;
407
      case SkillAttack::TARGET_COLUMN:
408
        /** @var Character $primaryTarget */
409
        $primaryTarget = $this->selectAttackTarget($character);
410
        $targets = $this->getTeam($primaryTarget)->getMembers(["positionColumn" => $primaryTarget->positionColumn]);
411
        break;
412
      default:
413
        throw new NotImplementedException("Target $skill->skill->target for attack skills is not implemented yet.");
414
    }
415 1
    foreach($targets as $target) {
416 1
      for($i = 1; $i <= $skill->skill->strikes; $i++) {
417 1
        $this->onSkillAttack($character, $target, $skill);
418
      }
419
    }
420 1
  }
421
  
422
  protected function doSpecialSkill(Character $character, CharacterSpecialSkill $skill): void {
423 1
    $targets = [];
424 1
    switch($skill->skill->target) {
425 1
      case SkillSpecial::TARGET_ENEMY:
426
        $targets[] = $this->selectAttackTarget($character);
427
        break;
428 1
      case SkillSpecial::TARGET_SELF:
429 1
        $targets[] = $character;
430 1
        break;
431
      case SkillSpecial::TARGET_PARTY:
432
        $targets = $this->getTeam($character)->toArray();
433
        break;
434
      case SkillSpecial::TARGET_ENEMY_PARTY:
435
        $targets = $this->getEnemyTeam($character)->toArray();
436
        break;
437
      default:
438
        throw new NotImplementedException("Target $skill->skill->target for special skills is not implemented.");
439
    }
440 1
    foreach($targets as $target) {
441 1
      $this->onSkillSpecial($character, $target, $skill);
442
    }
443 1
  }
444
  
445
  protected function chooseAction(CombatBase $combat, Character $character): ?string {
446 1
    if($character->hitpoints < 1) {
447
      return NULL;
448 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...
449 1
      return CombatAction::ACTION_HEALING;
450
    }
451 1
    $attackTarget = $combat->selectAttackTarget($character);
452 1
    if(is_null($attackTarget)) {
453
      return NULL;
454
    }
455 1
    if(count($character->usableSkills) > 0) {
456 1
      $skill = $character->usableSkills[0];
457 1
      if($skill instanceof CharacterAttackSkill) {
458 1
        return CombatAction::ACTION_SKILL_ATTACK;
459 1
      } elseif($skill instanceof  CharacterSpecialSkill) {
460 1
        return CombatAction::ACTION_SKILL_SPECIAL;
461
      }
462
    }
463 1
    return CombatAction::ACTION_ATTACK;
464
  }
465
  
466
  protected function getAllowedActions(): array {
467 1
    $allowedActions = Constants::getConstantsValues(CombatAction::class, "ACTION_");
468 1
    return array_values(array_filter($allowedActions, function(string $value) {
469 1
      return ($value !== CombatAction::ACTION_POISON);
470 1
    }));
471
  }
472
  
473
  /**
474
   * Main stage of a round
475
   */
476
  public function mainStage(CombatBase $combat): void {
477
    /** @var Character[] $characters */
478 1
    $characters = array_merge($combat->team1->usableMembers, $combat->team2->usableMembers);
479 1
    usort($characters, function(Character $a, Character $b) {
480 1
      return -1 * strcmp((string) $a->initiative, (string) $b->initiative);
481 1
    });
482 1
    foreach($characters as $character) {
483 1
      $action = $combat->chooseAction($combat, $character);
484 1
      if(!in_array($action, $this->getAllowedActions(), true)) {
485
        continue;
486
      }
487
      switch($action) {
488 1
        case CombatAction::ACTION_ATTACK:
489 1
          $combat->onAttack($character, $combat->selectAttackTarget($character));
490 1
          break;
491 1
        case CombatAction::ACTION_SKILL_ATTACK:
492
          /** @var CharacterAttackSkill $skill */
493 1
          $skill = $character->usableSkills[0];
494 1
          $combat->doAttackSkill($character, $skill);
495 1
          break;
496 1
        case CombatAction::ACTION_SKILL_SPECIAL:
497
          /** @var CharacterSpecialSkill $skill */
498 1
          $skill = $character->usableSkills[0];
499 1
          $combat->doSpecialSkill($character, $skill);
500 1
          break;
501 1
        case CombatAction::ACTION_HEALING:
502 1
          $combat->onHeal($character, $combat->selectHealingTarget($character));
503 1
          break;
504
      }
505
    }
506 1
  }
507
  
508
  /**
509
   * Start next round
510
   * 
511
   * @return int Winning team/0
512
   */
513
  protected function startRound(): int {
514 1
    $this->onRoundStart($this);
515 1
    return $this->getWinner();
516
  }
517
  
518
  /**
519
   * Do a round
520
   */
521
  protected function doRound(): void {
522 1
    $this->onRound($this);
523 1
  }
524
  
525
  /**
526
   * End round
527
   * 
528
   * @return int Winning team/0
529
   */
530
  protected function endRound(): int {
531 1
    $this->onRoundEnd($this);
532 1
    return $this->getWinner();
533
  }
534
  
535
  /**
536
   * Executes the combat
537
   * 
538
   * @return int Winning team
539
   */
540
  public function execute(): int {
541 1
    if(!isset($this->team1)) {
542 1
      throw new InvalidStateException("Teams are not set.");
543
    }
544 1
    $this->onCombatStart($this);
545 1
    while($this->round <= $this->roundLimit) {
546 1
      if($this->startRound() > 0) {
547 1
        break;
548
      }
549 1
      $this->doRound();
550 1
      if($this->endRound() > 0) {
551
        break;
552
      }
553
    }
554 1
    $this->onCombatEnd($this);
555 1
    return $this->getWinner();
556
  }
557
  
558
  /**
559
   * Calculate hit chance for attack/skill attack
560
   */
561
  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...
562 1
    $hitChance = $this->successCalculator->calculateHitChance($character1, $character2, $skill);
563 1
    return Numbers::range($hitChance, ISuccessCalculator::MIN_HIT_CHANCE, ISuccessCalculator::MAX_HIT_CHANCE);
564
  }
565
  
566
  /**
567
   * Check whether action succeeded
568
   */
569
  protected function hasHit(int $hitChance): bool {
570 1
    return $this->successCalculator->hasHit($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 1
    $effect = new CharacterEffect([
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($effect);
638 1
    $skill->resetCooldown();
639 1
  }
640
  
641
  /**
642
   * Heal a character
643
   */
644
  public function heal(Character $healer, Character $patient): void {
645 1
    $result = [];
646 1
    $hitChance = $this->successCalculator->calculateHealingSuccessChance($healer);
647 1
    $hitChance = Numbers::range($hitChance, 0, 100);
648 1
    $result["result"] = $this->successCalculator->hasHit($hitChance);
649 1
    $amount = ($result["result"]) ? (int) ($healer->intelligence / 2) : 0;
650 1
    $result["amount"] = Numbers::range($amount, 0, $patient->maxHitpoints - $patient->hitpoints);
651 1
    if($result["amount"]) {
652 1
      $patient->heal($result["amount"]);
653
    }
654 1
    $result["action"] = CombatAction::ACTION_HEALING;
655 1
    $result["name"] = "";
656 1
    $result["character1"] = $healer;
657 1
    $result["character2"] = $patient;
658 1
    $this->results = $result;
659 1
  }
660
  
661
  /**
662
   * Harm poisoned characters at start of round
663
   */
664
  public function applyPoison(CombatBase $combat): void {
665
    /** @var Character[] $characters */
666 1
    $characters = array_merge($combat->team1->aliveMembers, $combat->team2->aliveMembers);
667 1
    foreach($characters as $character) {
668 1
      foreach($character->effects as $effect) {
669 1
        if($effect->type === SkillSpecial::TYPE_POISON) {
670
          $character->harm($effect->value);
671
          $action = [
672
            "action" => CombatAction::ACTION_POISON, "result" => true, "amount" => $effect->value,
673
            "character1" => $character, "character2" => $character,
674
          ];
675 1
          $combat->log->log($action);
676
        }
677
      }
678
    }
679 1
  }
680
  
681
  /**
682
   * Log results of an action
683
   */
684
  public function logResults(): void {
685 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

685
    $this->log->log(/** @scrutinizer ignore-type */ $this->results);
Loading history...
686 1
    $this->results = NULL;
687 1
  }
688
  
689
  /**
690
   * Log dealt damage
691
   */
692
  public function logDamage(Character $attacker): void {
693 1
    $team = $this->team1->hasMembers(["id" => $attacker->id]) ? 1 : 2;
694 1
    $this->damage[$team] += $this->results["amount"];
695 1
  }
696
  
697
  public function getLog(): CombatLogger {
698 1
    return $this->log;
699
  }
700
}
701
?>