Passed
Push — master ( db7b85...c86096 )
by Nico
49:13 queued 24:57
created

FightLib   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Test Coverage

Coverage 89.83%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 116
dl 0
loc 243
ccs 106
cts 118
cp 0.8983
rs 6.96
c 1
b 1
f 0
wmc 53

12 Methods

Rating   Name   Duplication   Size   Complexity  
A filterInactiveShips() 0 5 2
B ready() 0 42 9
A canFire() 0 12 4
A __construct() 0 8 1
A addDockedToAsDefender() 0 9 4
A calculateHealthPercentage() 0 17 3
A isBoardingPossible() 0 8 6
A getAttackersAndDefenders() 0 9 1
A getDefenders() 0 30 5
A getAttackers() 0 12 3
C canAttackTarget() 0 47 12
A isTargetOutsideFinishedTholianWeb() 0 8 3

How to fix   Complexity   

Complex Class

Complex classes like FightLib often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FightLib, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stu\Module\Ship\Lib\Battle;
6
7
use Stu\Component\Ship\Repair\CancelRepairInterface;
8
use Stu\Component\Ship\System\Exception\ShipSystemException;
9
use Stu\Component\Ship\System\ShipSystemManagerInterface;
10
use Stu\Component\Ship\System\ShipSystemTypeEnum;
11
use Stu\Lib\Information\InformationWrapper;
12
use Stu\Module\Ship\Lib\ShipNfsItem;
13
use Stu\Module\Ship\Lib\ShipWrapperInterface;
14
use Stu\Orm\Entity\ShipInterface;
15
use Stu\Orm\Entity\User;
16
17
final class FightLib implements FightLibInterface
18
{
19
    private ShipSystemManagerInterface $shipSystemManager;
20
21
    private CancelRepairInterface $cancelRepair;
22
23
    private AlertLevelBasedReactionInterface $alertLevelBasedReaction;
24
25 46
    public function __construct(
26
        ShipSystemManagerInterface $shipSystemManager,
27
        CancelRepairInterface $cancelRepair,
28
        AlertLevelBasedReactionInterface $alertLevelBasedReaction
29
    ) {
30 46
        $this->shipSystemManager = $shipSystemManager;
31 46
        $this->cancelRepair = $cancelRepair;
32 46
        $this->alertLevelBasedReaction = $alertLevelBasedReaction;
33
    }
34
35 6
    public function ready(ShipWrapperInterface $wrapper): InformationWrapper
36
    {
37 6
        $ship = $wrapper->get();
38
39 6
        $informations = new InformationWrapper();
40
41
        if (
42 6
            $ship->isDestroyed()
43 6
            || $ship->getRump()->isEscapePods()
44
        ) {
45 2
            return $informations;
46
        }
47 4
        if ($ship->getBuildplan() === null) {
48 1
            return $informations;
49
        }
50 3
        if (!$ship->hasEnoughCrew()) {
51 1
            return $informations;
52
        }
53
54 2
        if ($ship->getDockedTo() !== null) {
55 1
            $ship->setDockedTo(null);
56 1
            $informations->addInformation("- Das Schiff hat abgedockt");
57
        }
58
59
        try {
60 2
            $this->shipSystemManager->deactivate($wrapper, ShipSystemTypeEnum::SYSTEM_WARPDRIVE);
61 1
        } catch (ShipSystemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
62
        }
63
        try {
64 2
            $this->shipSystemManager->deactivate($wrapper, ShipSystemTypeEnum::SYSTEM_CLOAK);
65 1
        } catch (ShipSystemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
66
        }
67
68 2
        $this->cancelRepair->cancelRepair($ship);
69
70 2
        $informations->addInformationWrapper($this->alertLevelBasedReaction->react($wrapper));
71
72 2
        if (!$informations->isEmpty()) {
73 1
            $informations->addInformationArray([sprintf(_('Aktionen der %s'), $ship->getName())], true);
74
        }
75
76 2
        return $informations;
77
    }
78
79 1
    public function filterInactiveShips(array $base): array
80
    {
81 1
        return array_filter(
82 1
            $base,
83 1
            fn (ShipWrapperInterface $wrapper): bool => !$wrapper->get()->isDestroyed() && !$wrapper->get()->isDisabled()
84 1
        );
85
    }
86
87 4
    public function canFire(ShipWrapperInterface $wrapper): bool
88
    {
89 4
        $ship = $wrapper->get();
90 4
        if (!$ship->getNbs()) {
91 1
            return false;
92
        }
93 3
        if (!$ship->hasActiveWeapon()) {
94 1
            return false;
95
        }
96
97 2
        $epsSystem = $wrapper->getEpsSystemData();
98 2
        return $epsSystem !== null && $epsSystem->getEps() !== 0;
99
    }
100
101 13
    public function canAttackTarget(
102
        ShipInterface $ship,
103
        ShipInterface|ShipNfsItem $target,
104
        bool $checkActiveWeapons = true,
105
        bool $checkActiveWarpdrive = true
106
    ): bool {
107 13
        if ($checkActiveWeapons && !$ship->hasActiveWeapon()) {
108 1
            return false;
109
        }
110
111
        //can't attack itself
112 12
        if ($target === $ship) {
113 1
            return false;
114
        }
115
116
        //can't attack trumfields
117 11
        if ($target->isTrumfield()) {
118 1
            return false;
119
        }
120
121
        //if tractored, can only attack tractoring ship
122 10
        $tractoringShip = $ship->getTractoringShip();
123 10
        if ($tractoringShip !== null) {
124 3
            return $target->getId() === $tractoringShip->getId();
125
        }
126
127
        //can't attack target under warp
128 7
        if ($checkActiveWarpdrive && $target->getWarpState()) {
129 1
            return false;
130
        }
131
132
        //can't attack own target under cloak
133
        if (
134 6
            $target->getUserId() === $ship->getUserId()
135 6
            && $target->getCloakState()
136
        ) {
137 1
            return false;
138
        }
139
140
        //can't attack same fleet
141 5
        $ownFleetId = $ship->getFleetId();
142 5
        $targetFleetId = $target->getFleetId();
143 5
        if ($ownFleetId === null || $targetFleetId === null) {
144 2
            return true;
145
        }
146
147 3
        return $ownFleetId !== $targetFleetId;
148
    }
149
150 8
    public function getAttackersAndDefenders(ShipWrapperInterface $wrapper, ShipWrapperInterface $targetWrapper): array
151
    {
152 8
        $attackers = $this->getAttackers($wrapper);
153 8
        $defenders = $this->getDefenders($targetWrapper);
154
155 8
        return [
156 8
            $attackers,
157 8
            $defenders,
158 8
            count($attackers) + count($defenders) > 2
159 8
        ];
160
    }
161
162
    /** @return array<int, ShipWrapperInterface> */
163 8
    public function getAttackers(ShipWrapperInterface $wrapper): array
164
    {
165 8
        $ship = $wrapper->get();
166 8
        $fleet = $wrapper->getFleetWrapper();
167
168 8
        if ($ship->isFleetLeader() && $fleet !== null) {
169 1
            $attackers = $fleet->getShipWrappers();
170
        } else {
171 7
            $attackers = [$ship->getId() => $wrapper];
172
        }
173
174 8
        return $attackers;
175
    }
176
177
    /** @return array<int, ShipWrapperInterface> */
178 8
    private function getDefenders(ShipWrapperInterface $targetWrapper): array
179
    {
180 8
        $target = $targetWrapper->get();
181 8
        $targetFleet = $targetWrapper->getFleetWrapper();
182
183 8
        if ($targetFleet !== null) {
184 3
            $defenders = [];
185
186
            // only uncloaked defenders fight
187 3
            foreach ($targetFleet->getShipWrappers() as $shipId => $defWrapper) {
188
189 3
                $defShip = $defWrapper->get();
190 3
                if (!$defShip->getCloakState()) {
191 3
                    $defenders[$shipId] = $defWrapper;
192
193 3
                    $this->addDockedToAsDefender($targetWrapper, $defenders);
194
                }
195
            }
196
197
            // if all defenders were cloaked, they obviously were scanned and enter the fight as a whole fleet
198 3
            if ($defenders === []) {
199
                $defenders = $targetFleet->getShipWrappers();
200
            }
201
        } else {
202 5
            $defenders = [$target->getId() => $targetWrapper];
203
204 5
            $this->addDockedToAsDefender($targetWrapper, $defenders);
205
        }
206
207 8
        return $defenders;
208
    }
209
210
    /** @param array<int, ShipWrapperInterface> $defenders */
211 8
    private function addDockedToAsDefender(ShipWrapperInterface $targetWrapper, array &$defenders): void
212
    {
213 8
        $dockedToWrapper = $targetWrapper->getDockedToShipWrapper();
214
        if (
215 8
            $dockedToWrapper !== null
216 8
            && !$dockedToWrapper->get()->getUser()->isNpc()
217 8
            && $dockedToWrapper->get()->hasActiveWeapon()
218
        ) {
219 2
            $defenders[$dockedToWrapper->get()->getId()] = $dockedToWrapper;
220
        }
221
    }
222
223 4
    public function isTargetOutsideFinishedTholianWeb(ShipInterface $ship, ShipInterface $target): bool
224
    {
225 4
        $web = $ship->getHoldingWeb();
226 4
        if ($web === null) {
227 1
            return false;
228
        }
229
230 3
        return $web->isFinished() && ($target->getHoldingWeb() !== $web);
231
    }
232
233 6
    public static function isBoardingPossible(ShipInterface|ShipNfsItem $ship): bool
234
    {
235 6
        return !(User::isUserNpc($ship->getUserId())
236 6
            || $ship->isBase()
237 6
            || $ship->isTrumfield()
238 6
            || $ship->getCloakState()
239 6
            || $ship->getShieldState()
240 6
            || $ship->getWarpState());
241
    }
242
243
    public function calculateHealthPercentage(ShipInterface $target): int
244
    {
245
        $shipCount = 0;
246
        $healthSum = 0;
247
248
        $fleet = $target->getFleet();
249
        if ($fleet !== null) {
250
            foreach ($fleet->getShips() as $ship) {
251
                $shipCount++;
252
                $healthSum += $ship->getHealthPercentage();
253
            }
254
        } else {
255
            $shipCount++;
256
            $healthSum += $target->getHealthPercentage();
257
        }
258
259
        return (int)($healthSum / $shipCount);
260
    }
261
}
262