Passed
Push — master ( aa3b71...5e2219 )
by Nico
55:07 queued 26:20
created

FightLib   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 251
Duplicated Lines 0 %

Test Coverage

Coverage 90.08%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 119
c 1
b 1
f 0
dl 0
loc 251
ccs 109
cts 121
cp 0.9008
rs 5.5199
wmc 56

12 Methods

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