ColonyTick   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 327
Duplicated Lines 0 %

Test Coverage

Coverage 1.28%

Importance

Changes 0
Metric Value
eloc 130
dl 0
loc 327
ccs 2
cts 156
cp 0.0128
rs 9.2
c 0
b 0
f 0
wmc 40

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
A work() 0 11 1
A getBuildingToDeactivateByLivingSpace() 0 12 2
A mainLoop() 0 37 5
B checkStorage() 0 35 7
A mergeProduction() 0 16 4
A deactivateBuilding() 0 21 3
A proceedModules() 0 26 5
A getBuildingToDeactivateByEpsUsage() 0 17 2
A checkEnergyProduction() 0 17 3
A checkLivingSpace() 0 16 3
A sendMessages() 0 21 2
A getBuildingToDeactivateByCommodity() 0 19 2

How to fix   Complexity   

Complex Class

Complex classes like ColonyTick 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 ColonyTick, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Stu\Module\Tick\Colony;
4
5
use Doctrine\Common\Collections\Collection;
6
use InvalidArgumentException;
7
use Override;
0 ignored issues
show
Bug introduced by
The type Override was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use RuntimeException;
9
use Stu\Component\Building\BuildingFunctionEnum;
10
use Stu\Component\Building\BuildingManagerInterface;
11
use Stu\Component\Colony\ColonyFunctionManagerInterface;
12
use Stu\Lib\Transfer\Storage\StorageManagerInterface;
13
use Stu\Lib\ColonyProduction\ColonyProduction;
14
use Stu\Lib\Information\InformationFactoryInterface;
15
use Stu\Lib\Information\InformationWrapper;
16
use Stu\Module\Colony\Lib\ColonyLibFactoryInterface;
17
use Stu\Module\Commodity\Lib\CommodityCacheInterface;
18
use Stu\Module\Message\Lib\PrivateMessageFolderTypeEnum;
19
use Stu\Module\Message\Lib\PrivateMessageSenderInterface;
20
use Stu\Module\PlayerSetting\Lib\UserConstants;
0 ignored issues
show
Bug introduced by
The type Stu\Module\PlayerSetting\Lib\UserConstants was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
21
use Stu\Module\Tick\Colony\Component\ColonyTickComponentInterface;
22
use Stu\Orm\Entity\BuildingCommodity;
23
use Stu\Orm\Entity\Colony;
24
use Stu\Orm\Entity\Commodity;
25
use Stu\Orm\Entity\PlanetField;
26
use Stu\Orm\Repository\ColonyDepositMiningRepositoryInterface;
27
use Stu\Orm\Repository\ColonyRepositoryInterface;
28
use Stu\Orm\Repository\ModuleQueueRepositoryInterface;
29
use Stu\Orm\Repository\PlanetFieldRepositoryInterface;
30
31
final class ColonyTick implements ColonyTickInterface
32
{
33
    private InformationWrapper $information;
34
35
    /** @param array<ColonyTickComponentInterface> $components */
36 1
    public function __construct(
37
        private readonly ModuleQueueRepositoryInterface $moduleQueueRepository,
38
        private readonly PlanetFieldRepositoryInterface $planetFieldRepository,
39
        private readonly ColonyRepositoryInterface $colonyRepository,
40
        private readonly ColonyDepositMiningRepositoryInterface $colonyDepositMiningRepository,
41
        private readonly PrivateMessageSenderInterface $privateMessageSender,
42
        private readonly StorageManagerInterface $storageManager,
43
        private readonly BuildingManagerInterface $buildingManager,
44
        private readonly ColonyLibFactoryInterface $colonyLibFactory,
45
        private readonly ColonyFunctionManagerInterface $colonyFunctionManager,
46
        private readonly CommodityCacheInterface $commodityCache,
47
        private readonly InformationFactoryInterface $informationFactory,
48
        private readonly array $components
49 1
    ) {}
50
51
    #[Override]
52
    public function work(Colony $colony): void
53
    {
54
        $this->information = $this->informationFactory->createInformationWrapper();
55
56
        $deactivatedFields = $this->mainLoop($colony);
57
58
        $this->colonyRepository->save($colony);
59
60
        $this->proceedModules($colony, $deactivatedFields);
61
        $this->sendMessages($colony);
62
    }
63
64
    /**
65
     * @return array<int>
66
     */
67
    private function mainLoop(Colony $colony): array
68
    {
69
        $i = 1;
70
        $production = $this->colonyLibFactory->createColonyCommodityProduction($colony)->getProduction();
71
72
        $deactivatedFields = [-1];
73
74
        while (true) {
75
76
            $rewind = $this->checkStorage($colony, $production, $deactivatedFields);
77
            $rewind |= $this->checkLivingSpace($colony, $production, $deactivatedFields);
78
            $rewind |= $this->checkEnergyProduction($colony, $production, $deactivatedFields);
79
80
            if ($rewind !== 0) {
81
                $i++;
82
                if ($i == 100) {
83
                    // SECURITY
84
                    //echo "HIT SECURITY BREAK\n";
85
                    break;
86
                }
87
                continue;
88
            }
89
            break;
90
        }
91
        $changeable = $colony->getChangeable();
92
        $changeable->setEps(
93
            min(
94
                $colony->getMaxEps(),
95
                $changeable->getEps() + $this->planetFieldRepository->getEnergyProductionByHost($colony, $deactivatedFields)
96
            )
97
        );
98
99
        foreach ($this->components as $component) {
100
            $component->work($colony, $production, $this->information);
101
        }
102
103
        return $deactivatedFields;
104
    }
105
106
    /**
107
     * @param array<int, ColonyProduction> $production
108
     * @param array<int> $deactivatedFields
109
     */
110
    private function checkStorage(
111
        Colony $colony,
112
        array &$production,
113
        array &$deactivatedFields
114
    ): bool {
115
116
        $result = false;
117
118
        foreach ($production as $pro) {
119
            if ($pro->getProduction() >= 0) {
120
                continue;
121
            }
122
123
            $commodityId = $pro->getCommodityId();
124
125
            $depositMining = $this->colonyDepositMiningRepository->getCurrentUserDepositMinings($colony)[$commodityId] ?? null;
126
            if ($depositMining !== null && $depositMining->isEnoughLeft(abs($pro->getProduction()))) {
0 ignored issues
show
Bug introduced by
It seems like abs($pro->getProduction()) can also be of type double; however, parameter $neededAmount of Stu\Orm\Entity\ColonyDepositMining::isEnoughLeft() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

126
            if ($depositMining !== null && $depositMining->isEnoughLeft(/** @scrutinizer ignore-type */ abs($pro->getProduction()))) {
Loading history...
127
                continue;
128
            }
129
130
            $storage = $colony->getStorage();
131
            $storageItem = $storage[$commodityId] ?? null;
132
            if ($storageItem !== null && $storageItem->getAmount() + $pro->getProduction() >= 0) {
133
                continue;
134
            }
135
            //echo "coloId:" . $colony->getId() . ", production:" . $pro->getProduction() . ", commodityId:" . $commodityId . ", commodity:" . $this->commodityCache->get($commodityId)->getName() . "\n";
136
            $field = $this->getBuildingToDeactivateByCommodity($colony, $commodityId, $deactivatedFields);
137
            // echo $i." hit by commodity ".$field->getFieldId()." - produce ".$pro->getProduction()." MT ".microtime()."\n";
138
            $this->deactivateBuilding($field, $production, $this->commodityCache->get($commodityId));
139
            $deactivatedFields[] = $field->getFieldId();
140
141
            $result = true;
142
        }
143
144
        return $result;
145
    }
146
147
    /**
148
     * @param array<int, ColonyProduction> $production
149
     * @param array<int> $deactivatedFields
150
     */
151
    private function checkLivingSpace(
152
        Colony $colony,
153
        array &$production,
154
        array &$deactivatedFields
155
    ): bool {
156
        if ($colony->getWorkers() > $colony->getChangeable()->getMaxBev()) {
157
            $field = $this->getBuildingToDeactivateByLivingSpace($colony, $deactivatedFields);
158
            if ($field !== null) {
159
                $this->deactivateBuilding($field, $production, 'Wohnraum');
160
                $deactivatedFields[] = $field->getFieldId();
161
162
                return true;
163
            }
164
        }
165
166
        return false;
167
    }
168
169
    /**
170
     * @param array<int, ColonyProduction> $production
171
     * @param array<int> $deactivatedFields
172
     */
173
    private function checkEnergyProduction(
174
        Colony $colony,
175
        array &$production,
176
        array &$deactivatedFields
177
    ): bool {
178
        $energyProduction = $this->planetFieldRepository->getEnergyProductionByHost($colony, $deactivatedFields);
179
180
        if ($energyProduction < 0 && $colony->getChangeable()->getEps() + $energyProduction < 0) {
181
            $field = $this->getBuildingToDeactivateByEpsUsage($colony, $deactivatedFields);
182
            //echo $i . " hit by eps " . $field->getFieldId() . " - complete usage " . $colony->getEpsProduction() . " - usage " . $field->getBuilding()->getEpsProduction() . " MT " . microtime() . "\n";
183
            $this->deactivateBuilding($field, $production, 'Energie');
184
            $deactivatedFields[] = $field->getFieldId();
185
186
            return true;
187
        }
188
189
        return false;
190
    }
191
192
    /**
193
     * @param array<int, ColonyProduction> $production
194
     */
195
    private function deactivateBuilding(
196
        PlanetField $field,
197
        array &$production,
198
        Commodity|string $cause
199
    ): void {
200
        $ext = $cause instanceof Commodity ? $cause->getName() : $cause;
201
        $building = $field->getBuilding();
202
203
        if ($building === null) {
204
            throw new InvalidArgumentException('can not deactivate field without building');
205
        }
206
207
        $this->buildingManager->deactivate($field);
208
209
        $this->mergeProduction($building->getCommodities(), $production);
210
211
        $this->information->addInformationf(
212
            "%s auf Feld %d deaktiviert (Mangel an %s)",
213
            $building->getName(),
214
            $field->getFieldId(),
215
            $ext
216
        );
217
    }
218
219
    /**
220
     * @param array<int> $deactivatedFields
221
     */
222
    private function getBuildingToDeactivateByCommodity(
223
        Colony $colony,
224
        int $commodityId,
225
        array $deactivatedFields
226
    ): PlanetField {
227
        $fields = $this->planetFieldRepository->getCommodityConsumingByHostAndCommodity(
228
            $colony,
229
            $commodityId,
230
            [1],
231
            1,
232
            $deactivatedFields
233
        );
234
235
        $result = current($fields);
236
        if (!$result) {
237
            throw new RuntimeException('no building found');
238
        }
239
240
        return $result;
241
    }
242
243
    /**
244
     * @param array<int> $deactivatedFields
245
     */
246
    private function getBuildingToDeactivateByEpsUsage(
247
        Colony $colony,
248
        array $deactivatedFields
249
    ): PlanetField {
250
        $fields = $this->planetFieldRepository->getEnergyConsumingByHost(
251
            $colony,
252
            [1],
253
            1,
254
            $deactivatedFields
255
        );
256
257
        $result = current($fields);
258
        if (!$result) {
259
            throw new RuntimeException('no building found');
260
        }
261
262
        return $result;
263
    }
264
265
    /**
266
     * @param array<int> $deactivatedFields
267
     */
268
    private function getBuildingToDeactivateByLivingSpace(
269
        Colony $colony,
270
        array $deactivatedFields
271
    ): ?PlanetField {
272
        $fields = $this->planetFieldRepository->getWorkerConsumingByColonyAndState(
273
            $colony->getId(),
274
            [1],
275
            1,
276
            $deactivatedFields
277
        );
278
279
        return $fields === [] ? null : current($fields);
280
    }
281
282
    /**
283
     * @param array<int> $deactivatedFields
284
     */
285
    private function proceedModules(Colony $colony, array $deactivatedFields): void
286
    {
287
        foreach ($this->moduleQueueRepository->getByColony($colony->getId()) as $queue) {
288
            $buildingFunction = $queue->getBuildingFunction();
289
290
            //spare parts and system components are generated by spacecraft tick manager, to avoid dead locks
291
            if (
292
                $buildingFunction === BuildingFunctionEnum::FABRICATION_HALL ||
293
                $buildingFunction === BuildingFunctionEnum::TECH_CENTER
294
            ) {
295
                continue;
296
            }
297
298
            if ($this->colonyFunctionManager->hasActiveFunction($colony, $buildingFunction, false, $deactivatedFields)) {
299
                $this->storageManager->upperStorage(
300
                    $colony,
301
                    $queue->getModule()->getCommodity(),
302
                    $queue->getAmount()
303
                );
304
305
                $this->information->addInformationf(
306
                    _('Es wurden %d %s hergestellt'),
307
                    $queue->getAmount(),
308
                    $queue->getModule()->getName()
309
                );
310
                $this->moduleQueueRepository->delete($queue);
311
            }
312
        }
313
    }
314
315
    private function sendMessages(Colony $colony): void
316
    {
317
        if ($this->information->isEmpty()) {
318
            return;
319
        }
320
321
        $text = sprintf(
322
            "Tickreport der Kolonie %s\n%s",
323
            $colony->getName(),
324
            $this->information->getInformationsAsString()
325
        );
326
327
        $this->privateMessageSender->send(
328
            UserConstants::USER_NOONE,
329
            $colony->getUserId(),
330
            $text,
331
            PrivateMessageFolderTypeEnum::SPECIAL_COLONY,
332
            $colony
333
        );
334
335
        $this->information = $this->informationFactory->createInformationWrapper();
336
    }
337
338
    /**
339
     * @param Collection<int, BuildingCommodity> $buildingProduction
340
     * @param array<int, ColonyProduction> $production
341
     */
342
    private function mergeProduction(
343
        Collection $buildingProduction,
344
        array &$production
345
    ): void {
346
        foreach ($buildingProduction as $obj) {
347
            $commodityId = $obj->getCommodityId();
348
            if (!array_key_exists($commodityId, $production)) {
349
                $data = $this->colonyLibFactory->createColonyProduction(
350
                    $obj->getCommodity(),
351
                    $obj->getAmount() * -1
352
                );
353
                $production[$commodityId] = $data;
354
            } elseif ($obj->getAmount() < 0) {
355
                $production[$commodityId]->upperProduction(abs($obj->getAmount()));
0 ignored issues
show
Bug introduced by
It seems like abs($obj->getAmount()) can also be of type double; however, parameter $value of Stu\Lib\ColonyProduction...tion::upperProduction() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

355
                $production[$commodityId]->upperProduction(/** @scrutinizer ignore-type */ abs($obj->getAmount()));
Loading history...
356
            } else {
357
                $production[$commodityId]->lowerProduction($obj->getAmount());
358
            }
359
        }
360
    }
361
}
362