Completed
Push — master ( f29677...f615bc )
by Jakub
04:44
created

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