Completed
Push — master ( 3d8890...b672a0 )
by Jakub
02:05
created

Character::getInitiativeFormula()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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