Passed
Push — master ( f3e1d4...54fdf8 )
by Jakub
02:24
created

CombatBase::applyEffectProviders()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

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

712
    $this->log->log(/** @scrutinizer ignore-type */ $this->results);
Loading history...
713 1
    $this->results = NULL;
714 1
  }
715
  
716
  /**
717
   * Log dealt damage
718
   */
719
  public function logDamage(Character $attacker): void {
720 1
    $team = $this->team1->hasMember($attacker->id) ? 1 : 2;
721 1
    $this->damage[$team] += $this->results["amount"];
722 1
  }
723
  
724
  public function getLog(): CombatLogger {
725 1
    return $this->log;
726
  }
727
}
728
?>