jaroslavtyc/drd-plus-health

View on GitHub
Health/Health.php

Summary

Maintainability
D
2 days
Test Coverage
<?php declare(strict_types=1);

namespace DrdPlus\Health;

use DrdPlus\BaseProperties\Will;
use DrdPlus\Codes\Body\SeriousWoundOriginCode;
use DrdPlus\Health\Afflictions\Affliction;
use DrdPlus\Health\Afflictions\AfflictionByWound;
use DrdPlus\Health\Afflictions\SpecificAfflictions\Pain;
use DrdPlus\Health\Inflictions\Glared;
use DrdPlus\Lighting\Glare;
use DrdPlus\Properties\Derived\Toughness;
use DrdPlus\Properties\Derived\WoundBoundary;
use DrdPlus\RollsOn\Traps\RollOnWillAgainstMalus;
use DrdPlus\RollsOn\Traps\RollOnWill;
use DrdPlus\Tables\Tables;
use Granam\DiceRolls\Templates\Rolls\Roll2d6DrdPlus;
use Granam\Strict\Object\StrictObject;

class Health extends StrictObject
{
    /**
     * @var array|Wound[]
     */
    private $wounds = [];
    /**
     * @var array|Affliction[]
     */
    private $afflictions = [];
    /**
     * Separates new and old (or serious) injuries.
     *
     * @var TreatmentBoundary
     */
    private $treatmentBoundary;
    /**
     * @var MalusFromWounds
     */
    private $malusFromWounds;
    /**
     * @var ReasonToRollAgainstMalusFromWounds|null
     */
    private $reasonToRollAgainstMalusFromWounds;
    /**
     * @var GridOfWounds|null is just a helper, does not need to be persisted
     */
    private $gridOfWounds;
    /**
     * @var bool helper to avoid side-adding of new wounds (Those created on their own and linked by Doctrine relation
     *     instead of directly here).
     */
    private $openForNewWound = false;
    /**
     * @var Glared
     */
    private $glared;

    public function __construct()
    {
        $this->treatmentBoundary = TreatmentBoundary::getIt(0);
        $this->malusFromWounds = MalusFromWounds::getIt(0);
        $this->glared = Glared::createWithoutGlare($this);
    }

    /**
     * @param WoundSize $woundSize
     * @param SeriousWoundOriginCode $seriousWoundOriginCode Beware that if the wound size is considered as NOT serious then
     *     OrdinaryWoundOrigin will be used instead (as the only possible for @see OrdinaryWound)
     * @param WoundBoundary $woundBoundary
     * @return OrdinaryWound|SeriousWound|Wound
     * @throws \DrdPlus\Health\Exceptions\NeedsToRollAgainstMalusFromWoundsFirst
     */
    public function addWound(
        WoundSize $woundSize,
        SeriousWoundOriginCode $seriousWoundOriginCode,
        WoundBoundary $woundBoundary
    ): Wound
    {
        $this->checkIfNeedsToRollAgainstMalusFirst();
        $this->openForNewWound = true;
        $wound = $this->isSeriousInjury($woundSize, $woundBoundary)
            ? new SeriousWound($this, $woundSize, $seriousWoundOriginCode)
            : new OrdinaryWound($this, $woundSize);
        $this->openForNewWound = false;
        $this->wounds[] = $wound;
        if ($wound->isSerious()) {
            $this->treatmentBoundary = TreatmentBoundary::getIt($this->getTreatmentBoundary()->getValue() + $wound->getValue());
        }
        $this->resolveMalusAfterWound($wound->getValue(), $woundBoundary);
        return $wound;
    }

    /**
     * @throws \DrdPlus\Health\Exceptions\NeedsToRollAgainstMalusFromWoundsFirst
     */
    private function checkIfNeedsToRollAgainstMalusFirst(): void
    {
        if ($this->needsToRollAgainstMalusFromWounds()) {
            throw new Exceptions\NeedsToRollAgainstMalusFromWoundsFirst(
                'Need to roll on will against malus caused by wounds because of previous '
                . $this->reasonToRollAgainstMalusFromWounds
            );
        }
    }

    /**
     * A lock of current health instance to ensure that new wound is created by that health,
     * @see \DrdPlus\Health\Wound::checkIfCreatedByGivenHealth
     * @return bool
     */
    public function isOpenForNewWound(): bool
    {
        return $this->openForNewWound;
    }

    private function isSeriousInjury(WoundSize $woundSize, WoundBoundary $woundBoundary): bool
    {
        return $this->getGridOfWounds()->calculateFilledHalfRowsFor($woundSize, $woundBoundary) > 0;
    }

    private function maySufferFromPain(WoundBoundary $woundBoundary): bool
    {
        // if the being became unconscious than the roll against pain malus is not re-rolled
        return
            $this->getGridOfWounds()->getNumberOfFilledRows($woundBoundary) >= GridOfWounds::PAIN_NUMBER_OF_ROWS
            && $this->isConscious($woundBoundary);
    }

    public function isConscious(WoundBoundary $woundBoundary): bool
    {
        return $this->getGridOfWounds()->getNumberOfFilledRows($woundBoundary) < GridOfWounds::UNCONSCIOUS_NUMBER_OF_ROWS;
    }

    /**
     * @param int $woundAmount
     * @param WoundBoundary $woundBoundary
     */
    private function resolveMalusAfterWound(int $woundAmount, WoundBoundary $woundBoundary)
    {
        if ($woundAmount === 0) {
            return;
        }
        if ($this->maySufferFromPain($woundBoundary)) {
            $this->reasonToRollAgainstMalusFromWounds = ReasonToRollAgainstMalusFromWounds::getWoundReason();
        } elseif ($this->isConscious($woundBoundary)) {
            $this->malusFromWounds = MalusFromWounds::getIt(0);
        } // otherwise left the previous malus - creature will suffer by it when comes conscious again
    }

    /**
     * Every serious injury SHOULD has at least one accompanying affliction (but it is PJ privilege to say it has not).
     *
     * @param Affliction $affliction
     * @throws \DrdPlus\Health\Exceptions\UnknownAfflictionOriginatingWound
     * @throws \DrdPlus\Health\Exceptions\AfflictionIsAlreadyRegistered
     */
    public function addAffliction(Affliction $affliction): void
    {
        if ($affliction instanceof AfflictionByWound && !$this->doesHaveThatWound($affliction->getSeriousWound())) {
            throw new Exceptions\UnknownAfflictionOriginatingWound(
                "Given affliction '{$affliction->getName()}' to add comes from unknown wound"
                . " of value {$affliction->getSeriousWound()} and origin '{$affliction->getSeriousWound()->getWoundOriginCode()}'."
                . ' Have you created that wound by current health?'
            );
        }
        if ($this->doesHaveThatAffliction($affliction)) {
            throw new Exceptions\AfflictionIsAlreadyRegistered(
                "Given instance of affliction '{$affliction->getName()}' is already added."
            );
        }
        $this->afflictions[] = $affliction;
    }

    /**
     * @param Wound $givenWound
     * @return bool
     */
    private function doesHaveThatWound(Wound $givenWound): bool
    {
        if ($givenWound->getHealth() !== $this) {
            return false; // easiest test - the wound belongs to different health
        }
        foreach ($this->wounds as $registeredWound) {
            if ($givenWound === $registeredWound) {
                return true; // this health recognizes that wound
            }
        }
        return false; // the wound know this health, but this health does not know that wound
    }

    /**
     * @param Affliction $givenAffliction
     * @return bool
     */
    private function doesHaveThatAffliction(Affliction $givenAffliction): bool
    {
        foreach ($this->afflictions as $registeredAffliction) {
            if ($givenAffliction === $registeredAffliction) {
                return true;
            }
        }
        return false;
    }

    /**
     * Also sets treatment boundary to unhealed wounds after. Even if the heal itself heals nothing!
     *
     * @param HealingPower $healingPower
     * @param WoundBoundary $woundBoundary
     * @return int amount of actually healed points of wounds
     * @throws \DrdPlus\Health\Exceptions\NeedsToRollAgainstMalusFromWoundsFirst
     */
    public function healFreshOrdinaryWounds(HealingPower $healingPower, WoundBoundary $woundBoundary): int
    {
        $this->checkIfNeedsToRollAgainstMalusFirst();
        // can heal new and ordinary wounds only, up to limit by current treatment boundary
        $healedAmount = 0;
        $remainingHealUpToWounds = $healingPower->getHealUpToWounds();
        foreach ($this->getUnhealedFreshOrdinaryWounds() as $newOrdinaryWound) {
            // wound is set as old internally, ALL OF THEM, even if no healing left
            $currentlyRegenerated = $newOrdinaryWound->heal($remainingHealUpToWounds);
            $remainingHealUpToWounds -= $currentlyRegenerated;
            $healedAmount += $currentlyRegenerated;
        }
        $this->treatmentBoundary = TreatmentBoundary::getIt($this->getUnhealedWoundsSum());
        $this->resolveMalusAfterHeal($healedAmount, $woundBoundary);

        return $healedAmount;
    }

    /**
     * @return OrdinaryWound[]|array
     */
    private function getUnhealedFreshOrdinaryWounds(): array
    {
        return \array_filter(
            $this->wounds,
            function (Wound $wound) {
                return !$wound->isHealed() && !$wound->isSerious() && !$wound->isOld();
            }
        );
    }

    /**
     * @return OrdinaryWound[]|array
     */
    private function getUnhealedFreshSeriousWounds(): array
    {
        return \array_filter(
            $this->wounds,
            function (Wound $wound) {
                return !$wound->isHealed() && $wound->isSerious() && !$wound->isOld();
            }
        );
    }

    /**
     * @param int $healedAmount
     * @param WoundBoundary $woundBoundary
     */
    private function resolveMalusAfterHeal(int $healedAmount, WoundBoundary $woundBoundary): void
    {
        if ($healedAmount === 0) { // both wounds remain the same and pain remains the same
            return;
        }
        if ($this->maySufferFromPain($woundBoundary)) {
            $this->reasonToRollAgainstMalusFromWounds = ReasonToRollAgainstMalusFromWounds::getHealReason();
        } elseif ($this->isConscious($woundBoundary)) {
            $this->malusFromWounds = MalusFromWounds::getIt(0); // pain is gone and creature feel it - lets remove the malus
        } // otherwise left the previous malus - creature will suffer by it when comes conscious again
    }

    /**
     * @param SeriousWound $seriousWound
     * @param HealingPower $healingPower
     * @param Toughness $toughness
     * @param Tables $tables
     * @return int amount of healed points of wounds
     * @throws \DrdPlus\Health\Exceptions\UnknownSeriousWoundToHeal
     * @throws \DrdPlus\Health\Exceptions\ExpectedFreshWoundToHeal
     * @throws \DrdPlus\Health\Exceptions\NeedsToRollAgainstMalusFromWoundsFirst
     */
    public function healFreshSeriousWound(
        SeriousWound $seriousWound,
        HealingPower $healingPower,
        Toughness $toughness,
        Tables $tables
    ): int
    {
        $this->checkIfNeedsToRollAgainstMalusFirst();
        if (!$this->doesHaveThatWound($seriousWound)) {
            throw new Exceptions\UnknownSeriousWoundToHeal(
                "Given serious wound of value {$seriousWound->getValue()} and origin"
                . " {$seriousWound->getWoundOriginCode()} to heal does not belongs to this health"
            );
        }
        if ($seriousWound->isOld()) {
            throw new Exceptions\ExpectedFreshWoundToHeal(
                "Given serious wound of value {$seriousWound->getValue()} and origin"
                . " {$seriousWound->getWoundOriginCode()} should not be old to be healed."
            );
        }
        $healedAmount = $seriousWound->heal($healingPower->getHealUpToWounds());
        // treatment boundary is taken with wounds down together
        $this->treatmentBoundary = TreatmentBoundary::getIt($this->treatmentBoundary->getValue() - $healedAmount);
        $this->resolveMalusAfterHeal($healedAmount, WoundBoundary::getIt($toughness, $tables));
        return $healedAmount;
    }

    /**
     * Regenerate any wound, both ordinary and serious, both new and old, by natural or unnatural way.
     *
     * @param HealingPower $healingPower
     * @param WoundBoundary $woundBoundary
     * @return int actually regenerated amount
     * @throws \DrdPlus\Health\Exceptions\NeedsToRollAgainstMalusFromWoundsFirst
     */
    public function regenerate(HealingPower $healingPower, WoundBoundary $woundBoundary): int
    {
        $this->checkIfNeedsToRollAgainstMalusFirst();
        // every wound becomes old after this
        $regeneratedAmount = 0;
        $remainingHealUpToWounds = $healingPower->getHealUpToWounds();
        foreach ($this->getUnhealedWounds() as $unhealedWound) {
            // wound is set as old internally, ALL OF THEM, even if no healing left
            $currentlyRegenerated = $unhealedWound->heal($remainingHealUpToWounds);
            $remainingHealUpToWounds -= $currentlyRegenerated;
            $regeneratedAmount += $currentlyRegenerated;
        }
        $this->treatmentBoundary = TreatmentBoundary::getIt($this->getUnhealedWoundsSum());
        $this->resolveMalusAfterHeal($regeneratedAmount, $woundBoundary);
        return $regeneratedAmount;
    }

    /**
     * Usable for info about amount of wounds which can be healed by basic healing
     *
     * @return int
     */
    public function getUnhealedFreshOrdinaryWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (OrdinaryWound $ordinaryWound) {
                    return $ordinaryWound->getValue();
                },
                $this->getUnhealedFreshOrdinaryWounds()
            )
        );
    }

    /**
     * Usable for info about amount of wounds which can be healed by treatment
     *
     * @return int
     */
    public function getUnhealedFreshSeriousWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (SeriousWound $seriousWound) {
                    return $seriousWound->getValue();
                },
                $this->getUnhealedFreshSeriousWounds()
            )
        );
    }

    public function getUnhealedOrdinaryWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (OrdinaryWound $seriousWound) {
                    return $seriousWound->getValue();
                },
                $this->getUnhealedOrdinaryWounds()
            )
        );
    }

    public function getUnhealedSeriousWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (SeriousWound $seriousWound) {
                    return $seriousWound->getValue();
                },
                $this->getUnhealedSeriousWounds()
            )
        );
    }

    /**
     * @return SeriousWound[]|array
     */
    private function getUnhealedOrdinaryWounds(): array
    {
        return \array_filter(
            $this->getUnhealedWounds(),
            function (Wound $wound) {
                return !$wound->isSerious() && !$wound->isHealed();
            }
        );
    }

    /**
     * @return SeriousWound[]|array
     */
    private function getUnhealedSeriousWounds(): array
    {
        return \array_filter(
            $this->getUnhealedWounds(),
            function (Wound $wound) {
                return $wound->isSerious() && !$wound->isHealed();
            }
        );
    }

    public function getUnhealedWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (Wound $unhealedWound) {
                    return $unhealedWound->getValue();
                },
                $this->getUnhealedWounds()
            )
        );
    }

    public function getUnhealedFreshWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (Wound $unhealedWound) {
                    return $unhealedWound->getValue();
                },
                $this->getUnhealedFreshWounds()
            )
        );
    }

    public function getUnhealedOldWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (Wound $unhealedWound) {
                    return $unhealedWound->getValue();
                },
                $this->getUnhealedOldWounds()
            )
        );
    }

    public function getUnhealedOldSeriousWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (Wound $unhealedWound) {
                    return $unhealedWound->getValue();
                },
                $this->getUnhealedOldSeriousWounds()
            )
        );
    }

    private function getUnhealedOldSeriousWounds(): array
    {
        return \array_filter(
            $this->getUnhealedOldWounds(),
            function (Wound $wound) {
                return $wound->isSerious() && $wound->isOld() && !$wound->isHealed();
            }
        );
    }

    public function getUnhealedOldOrdinaryWoundsSum(): int
    {
        return \array_sum(
            \array_map(
                function (Wound $unhealedWound) {
                    return $unhealedWound->getValue();
                },
                $this->getUnhealedOldOrdinaryWounds()
            )
        );
    }

    /**
     * @return array|Wound[]
     */
    private function getUnhealedOldOrdinaryWounds(): array
    {
        return \array_filter(
            $this->getUnhealedOldWounds(),
            function (Wound $wound) {
                return !$wound->isSerious() && $wound->isOld() && !$wound->isHealed();
            }
        );
    }

    /**
     * Can be healed only by regeneration.
     * @return array|Wound[]
     */
    public function getUnhealedOldWounds(): array
    {
        return \array_filter(
            $this->getUnhealedWounds(),
            function (Wound $wound) {
                return $wound->isOld();
            }
        );
    }

    /**
     * Can be healed by treatment.
     * @return array|Wound[]
     */
    public function getUnhealedFreshWounds(): array
    {
        return \array_filter(
            $this->getUnhealedWounds(),
            function (Wound $wound) {
                return !$wound->isOld();
            }
        );
    }

    /**
     * @param WoundBoundary $woundBoundary
     * @return int
     */
    public function getHealthMaximum(WoundBoundary $woundBoundary): int
    {
        return $woundBoundary->getValue() * GridOfWounds::TOTAL_NUMBER_OF_ROWS;
    }

    /**
     * @param WoundBoundary $woundBoundary
     * @return int
     */
    public function getRemainingHealthAmount(WoundBoundary $woundBoundary): int
    {
        return \max(0, $this->getHealthMaximum($woundBoundary) - $this->getUnhealedWoundsSum());
    }

    /**
     * Gives both fresh and old wounds
     *
     * @return array|Wound[]
     */
    public function getUnhealedWounds(): array
    {
        // results into different instance of array which avoids external change of the original
        return array_filter(
            $this->wounds,
            function (Wound $wound) {
                return !$wound->isHealed();
            }
        );
    }

    public function getGridOfWounds(): GridOfWounds
    {
        if ($this->gridOfWounds === null) {
            $this->gridOfWounds = new GridOfWounds($this);
        }
        return $this->gridOfWounds;
    }

    /**
     * Looking for a setter? Sorry but affliction can be caused only by a new wound.
     *
     * @return array|AfflictionByWound[]
     */
    public function getAfflictions(): array
    {
        return $this->afflictions;
    }

    public function getStrengthMalusFromAfflictions(): int
    {
        $strengthMalus = 0;
        foreach ($this->getAfflictions() as $afflictionByWound) {
            $strengthMalus += $afflictionByWound->getStrengthMalus();
        }
        return $strengthMalus;
    }

    public function getAgilityMalusFromAfflictions(): int
    {
        $agilityMalus = 0;
        foreach ($this->getAfflictions() as $afflictionByWound) {
            $agilityMalus += $afflictionByWound->getAgilityMalus();
        }
        return $agilityMalus;
    }

    public function getKnackMalusFromAfflictions(): int
    {
        $knackMalus = 0;
        foreach ($this->getAfflictions() as $afflictionByWound) {
            $knackMalus += $afflictionByWound->getKnackMalus();
        }
        return $knackMalus;
    }

    public function getWillMalusFromAfflictions(): int
    {
        $willMalus = 0;
        foreach ($this->getAfflictions() as $afflictionByWound) {
            $willMalus += $afflictionByWound->getWillMalus();
        }
        return $willMalus;
    }

    public function getIntelligenceMalusFromAfflictions(): int
    {
        $intelligenceMalus = 0;
        foreach ($this->getAfflictions() as $afflictionByWound) {
            $intelligenceMalus += $afflictionByWound->getIntelligenceMalus();
        }
        return $intelligenceMalus;
    }

    public function getCharismaMalusFromAfflictions(): int
    {
        $charismaMalus = 0;
        foreach ($this->getAfflictions() as $afflictionByWound) {
            $charismaMalus += $afflictionByWound->getCharismaMalus();
        }
        return $charismaMalus;
    }

    /**
     * Treatment boundary is set automatically on any heal (lowering wounds) or new serious injury
     *
     * @return TreatmentBoundary
     */
    public function getTreatmentBoundary(): TreatmentBoundary
    {
        return $this->treatmentBoundary;
    }

    public function getNumberOfSeriousInjuries(): int
    {
        return \count($this->getUnhealedSeriousWounds());
    }

    private const DEADLY_NUMBER_OF_SERIOUS_INJURIES = 6;

    /**
     * @param WoundBoundary $woundBoundary
     * @return bool
     */
    public function isAlive(WoundBoundary $woundBoundary): bool
    {
        return $this->getRemainingHealthAmount($woundBoundary) > 0
            && $this->getNumberOfSeriousInjuries() < self::DEADLY_NUMBER_OF_SERIOUS_INJURIES;
    }

    /**
     * Dominant, applied malus from wounds (pains respectively)
     * @param WoundBoundary $woundBoundary
     * @return int
     * @throws \DrdPlus\Health\Exceptions\NeedsToRollAgainstMalusFromWoundsFirst
     */
    public function getSignificantMalusFromPains(WoundBoundary $woundBoundary): int
    {
        $maluses = [$this->getMalusFromWoundsValue($woundBoundary)];
        foreach ($this->getPains() as $pain) {
            // for Pain see PPH page 79, left column
            $maluses[] = $pain->getMalusToActivities();
        }

        return \min($maluses); // the most significant malus (always lesser than zero), therefore the lowest value
    }

    /**
     * @param WoundBoundary $woundBoundary
     * @return int
     * @throws \DrdPlus\Health\Exceptions\NeedsToRollAgainstMalusFromWoundsFirst
     */
    private function getMalusFromWoundsValue(WoundBoundary $woundBoundary): int
    {
        $this->checkIfNeedsToRollAgainstMalusFirst();
        if (!$this->mayHaveMalusFromWounds($woundBoundary)) {
            return 0;
        }

        /**
         * note: Can grow only on new wound when reach second row in grid of wounds.
         * Can decrease only on heal of any wound when on second row in grid of wounds.
         * Is removed when first row of grid of wounds is not filled.
         * Even unconscious can has a malus (but would be wrong if applied).
         * See PPH page 75 right column
         */
        return $this->malusFromWounds->getValue();
    }

    /**
     * @return bool
     */
    public function needsToRollAgainstMalusFromWounds(): bool
    {
        return $this->reasonToRollAgainstMalusFromWounds !== null;
    }

    /**
     * @return ReasonToRollAgainstMalusFromWounds|null
     */
    public function getReasonToRollAgainstMalusFromWounds(): ?ReasonToRollAgainstMalusFromWounds
    {
        return $this->reasonToRollAgainstMalusFromWounds;
    }

    /**
     * @param Will $will
     * @param Roll2d6DrdPlus $roll2D6DrdPlus
     * @param WoundBoundary $woundBoundary
     * @return int resulted malus
     * @throws \DrdPlus\Health\Exceptions\UselessRollAgainstMalus
     */
    public function rollAgainstMalusFromWounds(
        Will $will,
        Roll2d6DrdPlus $roll2D6DrdPlus,
        WoundBoundary $woundBoundary
    ): int
    {
        if (!$this->needsToRollAgainstMalusFromWounds()) {
            throw new Exceptions\UselessRollAgainstMalus(
                'There is no need to roll against malus from wounds'
                . ($this->isConscious($woundBoundary) ? '' : ' (being is unconscious)')
            );
        }

        $malusValue = $this->reasonToRollAgainstMalusFromWounds->becauseOfHeal()
            ? $this->rollAgainstMalusOnHeal($will, $roll2D6DrdPlus)
            : $this->rollAgainstMalusOnWound($will, $roll2D6DrdPlus);

        $this->reasonToRollAgainstMalusFromWounds = null;

        return $malusValue;
    }

    private function rollAgainstMalusOnHeal(Will $will, Roll2d6DrdPlus $roll2D6DrdPlus): int
    {
        if ($this->malusFromWounds->getValue() === 0) {
            return $this->malusFromWounds->getValue(); // on heal can be the malus only lowered - there is nothing to lower
        }
        $newRoll = $this->createRollOnWillAgainstMalus($will, $roll2D6DrdPlus);
        // lesser (or same of course) malus remains; can not be increased on healing
        if ($this->malusFromWounds->getValue() >= $newRoll->getMalusValue()) { // greater in mathematical meaning (malus is negative)
            return $this->malusFromWounds->getValue(); // lesser malus remains
        }
        $malusFromWounds = $this->setMalusFromWounds($newRoll);

        return $malusFromWounds->getValue();
    }

    private function createRollOnWillAgainstMalus(Will $will, Roll2d6DrdPlus $roll2D6DrdPlus): RollOnWillAgainstMalus
    {
        return new RollOnWillAgainstMalus(new RollOnWill($will, $roll2D6DrdPlus));
    }

    private function setMalusFromWounds(RollOnWillAgainstMalus $rollOnWillAgainstMalus): MalusFromWounds
    {
        return $this->malusFromWounds = MalusFromWounds::getIt($rollOnWillAgainstMalus->getMalusValue());
    }

    private function rollAgainstMalusOnWound(Will $will, Roll2d6DrdPlus $roll2D6DrdPlus): int
    {
        if ($this->malusFromWounds->getValue() === MalusFromWounds::MOST) {
            return $this->malusFromWounds->getValue();
        }
        $newRoll = $this->createRollOnWillAgainstMalus($will, $roll2D6DrdPlus);
        // bigger (or same of course) malus remains; can not be decreased on new wounds
        if ($this->malusFromWounds->getValue() <= $newRoll->getMalusValue() // lesser in mathematical meaning (malus is negative)
        ) {
            return $this->malusFromWounds->getValue(); // greater malus remains
        }
        return $this->setMalusFromWounds($newRoll)->getValue();
    }

    /**
     * @return array|Pain[]
     */
    public function getPains(): array
    {
        return \array_filter(
            $this->getAfflictions(),
            function (Affliction $affliction) {
                return $affliction instanceof Pain;
            }
        );
    }

    public function inflictByGlare(Glare $glare)
    {
        $this->glared = Glared::createFromGlare($glare, $this);
    }

    public function getGlared(): Glared
    {
        return $this->glared;
    }

    public function hasFreshWounds(): bool
    {
        foreach ($this->getUnhealedWounds() as $unhealedWound) {
            if (!$unhealedWound->isOld()) {
                return true;
            }
        }
        return false;
    }

    public function maySufferFromWounds(WoundBoundary $woundBoundary): bool
    {
        // if you became unconscious than the roll against pain malus is not re-rolled
        return $this->mayHaveMalusFromWounds($woundBoundary) && $this->isConscious($woundBoundary);
    }

    public function mayHaveMalusFromWounds(WoundBoundary $woundBoundary): bool
    {
        return $this->getGridOfWounds()->getNumberOfFilledRows($woundBoundary) >= GridOfWounds::PAIN_NUMBER_OF_ROWS;
    }
}