Passed
Push — master ( 98f021...0d9edd )
by Jakub
01:48
created

Character::getDexterityBase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 2
ccs 0
cts 1
cp 0
c 0
b 0
f 0
rs 10
cc 1
eloc 1
nc 1
nop 0
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace HeroesofAbenez\Combat;
5
6
use Nexendrie\Utils\Numbers;
7
use Symfony\Component\OptionsResolver\OptionsResolver;
8
9
/**
10
 * Structure for single character
11
 *
12
 * @author Jakub Konečný
13
 * @property-read int|string $id
14
 * @property-read string $name
15
 * @property-read string $gender
16
 * @property-read string $race
17
 * @property-read string $occupation
18
 * @property-read int $level
19
 * @property-read int $experience
20
 * @property-read int $strength
21
 * @property-read int $strengthBase
22
 * @property-read int $dexterity
23
 * @property-read int $dexterityBase
24
 * @property-read int $constitution
25
 * @property-read int $constitutionBase
26
 * @property-read int $intelligence
27
 * @property-read int $intelligenceBase
28
 * @property-read int $charisma
29
 * @property-read int $charismaBase
30
 * @property-read int $maxHitpoints
31
 * @property-read int $maxHitpointsBase
32
 * @property-read int $hitpoints
33
 * @property-read int $damage
34
 * @property-read int $damageBase
35
 * @property-read int $hit
36
 * @property-read int $hitBase
37
 * @property-read int $dodge
38
 * @property-read int $dodgeBase
39
 * @property-read int $initiative
40
 * @property-read int $initiativeBase
41
 * @property-read string $initiativeFormula
42
 * @property-read int $defense
43
 * @property-read int $defenseBase
44
 * @property-read Equipment[] $equipment
45
 * @property-read Pet[] $pets
46
 * @property-read BaseCharacterSkill[] $skills
47
 * @property-read int|null $activePet
48
 * @property-read CharacterEffect[] $effects
49
 * @property-read ICharacterEffectsProvider[] $effectProviders
50
 * @property-read bool $stunned
51
 * @property-read BaseCharacterSkill[] $usableSkills
52
 * @property IInitiativeFormulaParser $initiativeFormulaParser
53
 * @property int $positionRow
54
 * @property int $positionColumn
55
 */
56 1
class Character {
57 1
  use \Nette\SmartObject;
58
  
59
  public const HITPOINTS_PER_CONSTITUTION = 5;
60
  public const STAT_STRENGTH = "strength";
61
  public const STAT_DEXTERITY = "dexterity";
62
  public const STAT_CONSTITUTION = "constitution";
63
  public const STAT_INTELLIGENCE = "intelligence";
64
  public const STAT_CHARISMA = "charisma";
65
  public const STAT_MAX_HITPOINTS = "maxHitpoints";
66
  public const STAT_DAMAGE = "damage";
67
  public const STAT_DEFENSE = "defense";
68
  public const STAT_HIT = "hit";
69
  public const STAT_DODGE = "dodge";
70
  public const STAT_INITIATIVE = "initiative";
71
  public const BASE_STATS = [
72
    self::STAT_STRENGTH, self::STAT_DEXTERITY, self::STAT_CONSTITUTION, self::STAT_INTELLIGENCE, self::STAT_CHARISMA,
73
  ];
74
  public const SECONDARY_STATS = [
75
    self::STAT_MAX_HITPOINTS, self::STAT_DAMAGE, self::STAT_DEFENSE, self::STAT_HIT, self::STAT_DODGE, self::STAT_INITIATIVE,
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 125 characters
Loading history...
76
  ];
77
  
78
  /** @var int|string */
79
  protected $id;
80
  /** @var string */
81
  protected $name;
82
  /** @var string */
83
  protected $gender = "male";
84
  /** @var string */
85
  protected $race;
86
  /** @var string */
87
  protected $occupation;
88
  /** @var string */
89
  protected $specialization;
90
  /** @var int */
91
  protected $level;
92
  /** @var int */
93
  protected $experience = 0;
94
  /** @var int */
95
  protected $strength;
96
  /** @var int */
97
  protected $strengthBase;
98
  /** @var int */
99
  protected $dexterity;
100
  /** @var int */
101
  protected $dexterityBase;
102
  /** @var int */
103
  protected $constitution;
104
  /** @var int */
105
  protected $constitutionBase;
106
  /** @var int */
107
  protected $intelligence;
108
  /** @var int */
109
  protected $intelligenceBase;
110
  /** @var int */
111
  protected $charisma;
112
  /** @var int */
113
  protected $charismaBase;
114
  /** @var int */
115
  protected $maxHitpoints;
116
  /** @var int */
117
  protected $maxHitpointsBase;
118
  /** @var int */
119
  protected $hitpoints;
120
  /** @var int */
121
  protected $damage = 0;
122
  /** @var int */
123
  protected $damageBase = 0;
124
  /** @var int */
125
  protected $hit = 0;
126
  /** @var int */
127
  protected $hitBase = 0;
128
  /** @var int */
129
  protected $dodge = 0;
130
  /** @var int */
131
  protected $dodgeBase = 0;
132
  /** @var int */
133
  protected $initiative = 0;
134
  /** @var int */
135
  protected $initiativeBase = 0;
136
  /** @var string */
137
  protected $initiativeFormula;
138
  /** @var IInitiativeFormulaParser */
139
  protected $initiativeFormulaParser;
140
  /** @var float */
141
  protected $defense = 0;
142
  /** @var float */
143
  protected $defenseBase = 0;
144
  /** @var Equipment[] Character's equipment */
145
  protected $equipment = [];
146
  /** @var Pet[] Character's pets */
147
  protected $pets = [];
148
  /** @var BaseCharacterSkill[] Character's skills */
149
  protected $skills = [];
150
  /** @var int|null */
151
  protected $activePet = null;
152
  /** @var CharacterEffect[] Active effects */
153
  protected $effects = [];
154
  /** @var ICharacterEffectsProvider[] */
155
  protected $effectProviders = [];
156
  /** @var bool */
157
  protected $stunned = false;
158
  /** @var int */
159
  protected $positionRow = 0;
160
  /** @var int */
161
  protected $positionColumn = 0;
162
  
163
  /**
164
   *
165
   * @param array $stats Stats of the character
166
   * @param Equipment[] $equipment Equipment of the character
167
   * @param Pet[] $pets Pets owned by the character
168
   * @param BaseCharacterSkill[] $skills Skills acquired by the character
169
   */
170
  public function __construct(array $stats, array $equipment = [], array $pets = [], array $skills = [], IInitiativeFormulaParser $initiativeFormulaParser = null) {
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 164 characters
Loading history...
171 1
    $this->initiativeFormulaParser = $initiativeFormulaParser ?? new InitiativeFormulaParser();
172 1
    $this->setStats($stats);
173 1
    foreach($equipment as $eq) {
174 1
      if($eq instanceof Equipment) {
175 1
        $this->equipment[] = $eq;
176 1
        $this->addEffectProvider($eq);
177
      }
178
    }
179 1
    foreach($pets as $pet) {
180 1
      if($pet instanceof Pet) {
181 1
        $this->pets[] = $pet;
182 1
        $this->addEffectProvider($pet);
183
      }
184
    }
185 1
    foreach($skills as $skill) {
186 1
      if($skill instanceof BaseCharacterSkill) {
187 1
        $this->skills[] = $skill;
188
      }
189
    }
190 1
  }
191
  
192
  protected function setStats(array $stats): void {
193 1
    $requiredStats = array_merge(["id", "name", "level", "initiativeFormula",], static::BASE_STATS);
194 1
    $allStats = array_merge($requiredStats, ["occupation", "race", "specialization", "gender", "experience",]);
195 1
    $numberStats = static::BASE_STATS;
196 1
    $textStats = ["name", "race", "occupation", "initiativeFormula",];
197 1
    $resolver = new OptionsResolver();
198 1
    $resolver->setDefined($allStats);
199 1
    $resolver->setAllowedTypes("id", ["integer", "string"]);
200 1
    $resolver->setAllowedTypes("experience", "integer");
201 1
    foreach($numberStats as $stat) {
202 1
      $resolver->setAllowedTypes($stat, ["integer", "float"]);
203 1
      $resolver->setNormalizer($stat, function(OptionsResolver $resolver, $value) {
204 1
        return (int) $value;
205 1
      });
206
    }
207 1
    foreach($textStats as $stat) {
208 1
      $resolver->setNormalizer($stat, function(OptionsResolver $resolver, $value) {
209 1
        return (string) $value;
210 1
      });
211
    }
212 1
    $resolver->setRequired($requiredStats);
213 1
    $stats = array_filter($stats, function($key) use($allStats) {
214 1
      return in_array($key, $allStats, true);
215 1
    }, ARRAY_FILTER_USE_KEY);
216 1
    $stats = $resolver->resolve($stats);
217 1
    foreach($stats as $key => $value) {
218 1
      if(in_array($key, $numberStats, true)) {
219 1
        $this->$key = $value;
220 1
        $this->{$key . "Base"} = $value;
221
      } else {
222 1
        $this->$key = $value;
223
      }
224
    }
225 1
    $this->hitpoints = $this->maxHitpoints = $this->maxHitpointsBase = $this->constitution * static::HITPOINTS_PER_CONSTITUTION;
0 ignored issues
show
introduced by
Line exceeds 120 characters; contains 128 characters
Loading history...
226 1
    $this->recalculateSecondaryStats();
227 1
    $this->hitBase = $this->hit;
228 1
    $this->dodgeBase = $this->dodge;
229 1
  }
230
  
231
  /**
232
   * @return int|string
233
   */
234
  public function getId() {
235 1
    return $this->id;
236
  }
237
  
238
  public function getName(): string {
239 1
    return $this->name;
240
  }
241
  
242
  public function getGender(): string {
243
    return $this->gender;
244
  }
245
  
246
  public function getRace(): string {
247
    return $this->race;
248
  }
249
  
250
  public function getOccupation(): string {
251
    return $this->occupation;
252
  }
253
  
254
  public function getLevel(): int {
255 1
    return $this->level;
256
  }
257
  
258
  public function getExperience(): int {
259
    return $this->experience;
260
  }
261
  
262
  public function getStrength(): int {
263 1
    return $this->strength;
264
  }
265
  
266
  public function getStrengthBase(): int {
267
    return $this->strengthBase;
268
  }
269
  
270
  public function getDexterity(): int {
271 1
    return $this->dexterity;
272
  }
273
  
274
  public function getDexterityBase(): int {
275
    return $this->dexterityBase;
276
  }
277
  
278
  public function getConstitution(): int {
279 1
    return $this->constitution;
280
  }
281
  
282
  public function getConstitutionBase(): int {
283
    return $this->constitutionBase;
284
  }
285
  
286
  public function getCharisma(): int {
287 1
    return $this->charisma;
288
  }
289
  
290
  public function getCharismaBase(): int {
291
    return $this->charismaBase;
292
  }
293
  
294
  public function getMaxHitpoints(): int {
295 1
    return $this->maxHitpoints;
296
  }
297
  
298
  public function getMaxHitpointsBase(): int {
299 1
    return $this->maxHitpointsBase;
300
  }
301
  
302
  public function getHitpoints(): int {
303 1
    return $this->hitpoints;
304
  }
305
  
306
  public function getDamage(): int {
307 1
    return $this->damage;
308
  }
309
  
310
  public function getDamageBase(): int {
311
    return $this->damageBase;
312
  }
313
  
314
  public function getHit(): int {
315 1
    return $this->hit;
316
  }
317
  
318
  public function getHitBase(): int {
319
    return $this->hitBase;
320
  }
321
  
322
  public function getDodge(): int {
323 1
    return $this->dodge;
324
  }
325
  
326
  public function getDodgeBase(): int {
327
    return $this->dodgeBase;
328
  }
329
  
330
  public function getInitiative(): int {
331 1
    return $this->initiative;
332
  }
333
  
334
  public function getInitiativeBase(): int {
335 1
    return $this->initiativeBase;
336
  }
337
  
338
  public function getInitiativeFormula(): string {
339 1
    return $this->initiativeFormula;
340
  }
341
  
342
  public function getDefense(): int {
343 1
    return (int) $this->defense;
344
  }
345
  
346
  public function getDefenseBase(): int {
347
    return (int) $this->defenseBase;
348
  }
349
  
350
  /**
351
   * @return Equipment[]
352
   */
353
  public function getEquipment(): array {
354 1
    return $this->equipment;
355
  }
356
  
357
  /**
358
   * @return Pet[]
359
   */
360
  public function getPets(): array {
361
    return $this->pets;
362
  }
363
  
364
  /**
365
   * @return BaseCharacterSkill[]
366
   */
367
  public function getSkills(): array {
368 1
    return $this->skills;
369
  }
370
  
371
  public function getActivePet(): ?int {
372 1
    foreach($this->pets as $pet) {
373 1
      if($pet->deployed) {
374 1
        return $pet->id;
375
      }
376
    }
377 1
    return null;
378
  }
379
  
380
  /**
381
   * @return CharacterEffect[]
382
   */
383
  public function getEffects(): array {
384 1
    return $this->effects;
385
  }
386
  
387
  /**
388
   * @return ICharacterEffectsProvider[]
389
   */
390
  public function getEffectProviders(): array {
391
    return $this->effectProviders;
392
  }
393
  
394
  public function isStunned(): bool {
395 1
    return $this->stunned;
396
  }
397
  
398
  public function getSpecialization(): string {
399
    return $this->specialization;
400
  }
401
  
402
  public function getIntelligence(): int {
403 1
    return $this->intelligence;
404
  }
405
  
406
  public function getIntelligenceBase(): int {
407
    return $this->intelligenceBase;
408
  }
409
  
410
  public function getInitiativeFormulaParser(): IInitiativeFormulaParser {
411 1
    return $this->initiativeFormulaParser;
412
  }
413
  
414
  public function setInitiativeFormulaParser(IInitiativeFormulaParser $initiativeFormulaParser): void {
415 1
    $oldParser = $this->initiativeFormulaParser;
416 1
    $this->initiativeFormulaParser = $initiativeFormulaParser;
417 1
    if($oldParser !== $initiativeFormulaParser) {
418 1
      $this->recalculateStats();
419
    }
420 1
  }
421
  
422
  public function getPositionRow(): int {
423 1
    return $this->positionRow;
424
  }
425
  
426
  public function setPositionRow(int $positionRow): void {
427 1
    $this->positionRow = Numbers::range($positionRow, 1, PHP_INT_MAX);
428 1
  }
429
  
430
  public function getPositionColumn(): int {
431 1
    return $this->positionColumn;
432
  }
433
  
434
  public function setPositionColumn(int $positionColumn): void {
435 1
    $this->positionColumn = Numbers::range($positionColumn, 1, PHP_INT_MAX);
436 1
  }
437
  
438
  /**
439
   * @internal
440
   */
441
  public function applyEffectProviders(): void {
442 1
    foreach($this->effectProviders as $item) {
443 1
      $effects = $item->getCombatEffects();
444 1
      array_walk($effects, function(CharacterEffect $effect) {
445 1
        $this->addEffect($effect);
446 1
      });
447
    }
448 1
  }
449
  
450
  /**
451
   * Applies new effect on the character
452
   */
453
  public function addEffect(CharacterEffect $effect): void {
454 1
    $this->effects[] = $effect;
455 1
    $effect->onApply($this, $effect);
456 1
  }
457
  
458
  public function addEffectProvider(ICharacterEffectsProvider $provider): void {
459 1
    $this->effectProviders[] = $provider;
460 1
  }
461
  
462
  /**
463
   * Removes specified effect from the character
464
   *
465
   * @throws \OutOfBoundsException
466
   */
467
  public function removeEffect(string $effectId): void {
468 1
    foreach($this->effects as $i => $effect) {
469 1
      if($effect->id == $effectId) {
470 1
        unset($this->effects[$i]);
471 1
        $effect->onRemove($this, $effect);
472 1
        return;
473
      }
474
    }
475 1
    throw new \OutOfBoundsException("Effect to remove was not found.");
476
  }
477
  
478
  /**
479
   * Get specified equipment of the character
480
   *
481
   * @throws \OutOfBoundsException
482
   */
483
  public function getItem(int $itemId): Equipment {
484 1
    foreach($this->equipment as $equipment) {
485 1
      if($equipment->id === $itemId) {
486 1
        return $equipment;
487
      }
488
    }
489 1
    throw new \OutOfBoundsException("Item was not found.");
490
  }
491
  
492
  /**
493
   * Get specified pet
494
   *
495
   * @throws \OutOfBoundsException
496
   */
497
  public function getPet(int $petId): Pet {
498 1
    foreach($this->pets as $pet) {
499 1
      if($pet->id === $petId) {
500 1
        return $pet;
501
      }
502
    }
503 1
    throw new \OutOfBoundsException("Pet was not found.");
504
  }
505
  
506
  /**
507
   * @return BaseCharacterSkill[]
508
   */
509
  public function getUsableSkills(): array {
510 1
    return array_values(array_filter($this->skills, function(BaseCharacterSkill $skill) {
511 1
      return $skill->canUse();
512 1
    }));
513
  }
514
  
515
  /**
516
   * Harm the character
517
   */
518
  public function harm(int $amount): void {
519 1
    $this->hitpoints -= Numbers::range($amount, 0, $this->hitpoints);
520 1
  }
521
  
522
  /**
523
   * Heal the character
524
   */
525
  public function heal(int $amount): void {
526 1
    $this->hitpoints += Numbers::range($amount, 0, $this->maxHitpoints - $this->hitpoints);
527 1
  }
528
  
529
  /**
530
   * Determine which (primary) stat should be used to calculate damage
531
   */
532
  public function damageStat(): string {
533 1
    foreach($this->equipment as $item) {
534 1
      if(!$item->worn OR !$item instanceof Weapon) {
535
        continue;
536
      }
537 1
      return $item->damageStat;
538
    }
539 1
    return static::STAT_STRENGTH;
540
  }
541
  
542
  /**
543
   * Recalculate secondary stats from the the primary ones
544
   */
545
  public function recalculateSecondaryStats(): void {
546
    $stats = [
547 1
      static::STAT_DAMAGE => $this->damageStat(), static::STAT_HIT => static::STAT_DEXTERITY,
548 1
      static::STAT_DODGE => static::STAT_DEXTERITY, static::STAT_MAX_HITPOINTS => static::STAT_CONSTITUTION,
549 1
      static::STAT_INITIATIVE => "",
550
    ];
551 1
    foreach($stats as $secondary => $primary) {
552 1
      $gain = $this->$secondary - $this->{$secondary . "Base"};
553 1
      if($secondary === static::STAT_DAMAGE) {
554 1
        $base = (int) round($this->$primary / 2);
555 1
      } elseif($secondary === static::STAT_MAX_HITPOINTS) {
556 1
        $base = $this->$primary * static::HITPOINTS_PER_CONSTITUTION;
557 1
      } elseif($secondary === static::STAT_INITIATIVE) {
558 1
        $base = $this->initiativeFormulaParser->calculateInitiative($this);
559
      } else {
560 1
        $base = $this->$primary * 3;
561
      }
562 1
      $this->{$secondary . "Base"} = $base;
563 1
      $this->$secondary = $base + $gain;
564
    }
565 1
  }
566
  
567
  /**
568
   * Recalculates stats of the character (mostly used during combat)
569
   */
570
  public function recalculateStats(): void {
0 ignored issues
show
introduced by
Function's cyclomatic complexity (14) exceeds 10; consider refactoring the function
Loading history...
571 1
    $stats = array_merge(static::BASE_STATS, static::SECONDARY_STATS);
572 1
    $stunned = false;
573 1
    $debuffs = [];
574 1
    foreach($stats as $stat) {
575 1
      $$stat = $this->{$stat . "Base"};
576 1
      $debuffs[$stat] = 0;
577
    }
578 1
    foreach($this->effects as $i => $effect) {
579 1
      $stat = $effect->stat;
580 1
      $type = $effect->type;
581 1
      $duration = $effect->duration;
582 1
      if(is_int($duration) AND $duration < 1) {
583 1
        $this->removeEffect($effect->id);
584 1
        continue;
585
      }
586 1
      switch($effect->source) {
587 1
        case CharacterEffect::SOURCE_PET:
588 1
        case CharacterEffect::SOURCE_SKILL:
589 1
          if(!in_array($type, SkillSpecial::NO_STAT_TYPES, true)) {
590 1
            $bonus_value = $$stat / 100 * $effect->value;
591
          }
592 1
          break;
593 1
        case CharacterEffect::SOURCE_EQUIPMENT:
594 1
          if(!in_array($type, SkillSpecial::NO_STAT_TYPES, true)) {
595 1
            $bonus_value = $effect->value;
596
          }
597 1
          break;
598
      }
599 1
      if($type == SkillSpecial::TYPE_BUFF) {
600 1
        $$stat += $bonus_value;
601 1
      } elseif($type == SkillSpecial::TYPE_DEBUFF) {
602 1
        $debuffs[$stat] += $bonus_value;
603 1
      } elseif($type == SkillSpecial::TYPE_STUN) {
604 1
        $stunned = true;
605
      }
606 1
      unset($stat, $type, $duration, $bonus_value);
607
    }
608 1
    foreach($debuffs as $stat => $value) {
609 1
      $value = min($value, 80);
610 1
      $bonus_value = $$stat / 100 * $value;
611 1
      $$stat -= $bonus_value;
612
    }
613 1
    foreach($stats as $stat) {
614 1
      $this->$stat = (int) round($$stat);
615
    }
616 1
    $this->recalculateSecondaryStats();
617 1
    $this->stunned = $stunned;
618 1
  }
619
  
620
  /**
621
   * Reset character's initiative
622
   */
623
  public function resetInitiative(): void {
624 1
    $this->initiative = $this->initiativeBase = 0;
625 1
  }
626
}
627
?>