Passed
Push — dev ( 766009...bcc49c )
by Janko
09:42
created

ColonyTick   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 507
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
eloc 252
dl 0
loc 507
ccs 0
cts 261
cp 0
rs 2.32
c 3
b 1
f 0
wmc 76

16 Methods

Rating   Name   Duplication   Size   Complexity  
A proceedEmigration() 0 6 2
A mergeProduction() 0 18 4
A proceedImmigration() 0 8 1
A proceedModules() 0 26 5
A sendMessages() 0 21 3
A getBuildingToDeactivateByLivingSpace() 0 5 2
B mainLoop() 0 41 6
B checkStorage() 0 35 7
A __construct() 0 30 1
A deactivateBuilding() 0 22 3
A work() 0 17 3
A getBuildingToDeactivateByEpsUsage() 0 10 2
A checkEnergyProduction() 0 13 3
A checkLivingSpace() 0 12 3
A getBuildingToDeactivateByCommodity() 0 14 2
F proceedStorage() 0 136 29

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 Doctrine\ORM\EntityManagerInterface;
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Stu\Component\Building\BuildingEnum;
10
use Stu\Component\Building\BuildingManagerInterface;
11
use Stu\Component\Colony\ColonyFunctionManagerInterface;
12
use Stu\Component\Colony\Storage\ColonyStorageManagerInterface;
13
use Stu\Lib\ColonyProduction\ColonyProduction;
14
use Stu\Module\Colony\Lib\ColonyLibFactoryInterface;
15
use Stu\Module\Commodity\Lib\CommodityCacheInterface;
16
use Stu\Module\Logging\LoggerUtilFactoryInterface;
17
use Stu\Module\Logging\LoggerUtilInterface;
18
use Stu\Module\Message\Lib\PrivateMessageFolderSpecialEnum;
19
use Stu\Module\Message\Lib\PrivateMessageSenderInterface;
20
use Stu\Module\PlayerSetting\Lib\UserEnum;
21
use Stu\Module\Research\ResearchStateFactoryInterface;
22
use Stu\Orm\Entity\BuildingCommodityInterface;
23
use Stu\Orm\Entity\ColonyInterface;
24
use Stu\Orm\Entity\CommodityInterface;
25
use Stu\Orm\Entity\PlanetFieldInterface;
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
use Stu\Orm\Repository\ResearchedRepositoryInterface;
31
32
final class ColonyTick implements ColonyTickInterface
33
{
34
    private ResearchedRepositoryInterface $researchedRepository;
35
36
    private ModuleQueueRepositoryInterface $moduleQueueRepository;
37
38
    private PlanetFieldRepositoryInterface $planetFieldRepository;
39
40
    private PrivateMessageSenderInterface $privateMessageSender;
41
42
    private ColonyStorageManagerInterface $colonyStorageManager;
43
44
    private ColonyRepositoryInterface $colonyRepository;
45
46
    private BuildingManagerInterface $buildingManager;
47
48
    private ColonyDepositMiningRepositoryInterface $colonyDepositMiningRepository;
49
50
    private EntityManagerInterface $entityManager;
51
52
    private ColonyLibFactoryInterface $colonyLibFactory;
53
54
    private ColonyFunctionManagerInterface $colonyFunctionManager;
55
56
    private ResearchStateFactoryInterface $researchStateFactory;
57
58
    private CommodityCacheInterface $commodityCache;
59
60
    private LoggerUtilInterface $loggerUtil;
61
62
    /**
63
     * @var array<string>
64
     */
65
    private array $msg = [];
66
67
    public function __construct(
68
        ResearchedRepositoryInterface $researchedRepository,
69
        ModuleQueueRepositoryInterface $moduleQueueRepository,
70
        PlanetFieldRepositoryInterface $planetFieldRepository,
71
        PrivateMessageSenderInterface $privateMessageSender,
72
        ColonyStorageManagerInterface $colonyStorageManager,
73
        ColonyRepositoryInterface $colonyRepository,
74
        BuildingManagerInterface $buildingManager,
75
        ColonyDepositMiningRepositoryInterface $colonyDepositMiningRepository,
76
        EntityManagerInterface $entityManager,
77
        ColonyLibFactoryInterface $colonyLibFactory,
78
        ColonyFunctionManagerInterface $colonyFunctionManager,
79
        ResearchStateFactoryInterface $researchStateFactory,
80
        CommodityCacheInterface $commodityCache,
81
        LoggerUtilFactoryInterface $loggerUtilFactory
82
    ) {
83
        $this->researchedRepository = $researchedRepository;
84
        $this->moduleQueueRepository = $moduleQueueRepository;
85
        $this->planetFieldRepository = $planetFieldRepository;
86
        $this->privateMessageSender = $privateMessageSender;
87
        $this->colonyStorageManager = $colonyStorageManager;
88
        $this->colonyRepository = $colonyRepository;
89
        $this->buildingManager = $buildingManager;
90
        $this->colonyDepositMiningRepository = $colonyDepositMiningRepository;
91
        $this->entityManager = $entityManager;
92
        $this->colonyLibFactory = $colonyLibFactory;
93
        $this->colonyFunctionManager = $colonyFunctionManager;
94
        $this->researchStateFactory = $researchStateFactory;
95
        $this->commodityCache = $commodityCache;
96
        $this->loggerUtil = $loggerUtilFactory->getLoggerUtil();
97
    }
98
99
    public function work(ColonyInterface $colony): void
100
    {
101
        $doLog = $this->loggerUtil->doLog();
102
        if ($doLog) {
103
            $startTime = microtime(true);
104
        }
105
106
        $this->mainLoop($colony);
107
108
        $this->colonyRepository->save($colony);
109
110
        $this->proceedModules($colony);
111
        $this->sendMessages($colony);
112
113
        if ($doLog) {
114
            $endTime = microtime(true);
115
            $this->loggerUtil->log(sprintf("Colony-Id: %6d, seconds: %F", $colony->getId(), $endTime - $startTime));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $startTime does not seem to be defined for all execution paths leading up to this point.
Loading history...
116
        }
117
    }
118
119
    private function mainLoop(ColonyInterface $colony): void
120
    {
121
        $doLog = $this->loggerUtil->doLog();
122
123
        if ($doLog) {
124
            $startTime = microtime(true);
125
        }
126
127
        $i = 1;
128
        $production = $this->colonyLibFactory->createColonyCommodityProduction($colony)->getProduction();
129
130
        while (true) {
131
132
            $rewind = $this->checkStorage($colony, $production);
133
            $rewind |= $this->checkLivingSpace($colony, $production);
134
            $rewind |= $this->checkEnergyProduction($colony, $production);
135
136
            if ($rewind) {
137
                $i++;
138
                if ($i == 100) {
139
                    // SECURITY
140
                    //echo "HIT SECURITY BREAK\n";
141
                    break;
142
                }
143
                continue;
144
            }
145
            break;
146
        }
147
        $colony->setEps(
148
            min(
149
                $colony->getMaxEps(),
150
                $colony->getEps() + $this->planetFieldRepository->getEnergyProductionByColony($colony->getId())
151
            )
152
        );
153
154
        if ($doLog) {
155
            $endTime = microtime(true);
156
            $this->loggerUtil->log(sprintf("\tmainLoop, seconds: %F", $endTime - $startTime));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $startTime does not seem to be defined for all execution paths leading up to this point.
Loading history...
157
        }
158
159
        $this->proceedStorage($colony, $production);
160
    }
161
162
    /**
163
     * @param array<int, ColonyProduction> $production
164
     */
165
    private function checkStorage(
166
        ColonyInterface $colony,
167
        array &$production
168
    ): bool {
169
170
        $result = false;
171
172
        foreach ($production as $pro) {
173
            if ($pro->getProduction() >= 0) {
174
                continue;
175
            }
176
177
            $commodityId = $pro->getCommodityId();
178
179
            $depositMining = $colony->getUserDepositMinings()[$commodityId] ?? null;
180
            if ($depositMining !== null) {
181
                if ($depositMining->isEnoughLeft((int) abs($pro->getProduction()))) {
182
                    continue;
183
                }
184
            }
185
186
            $storage = $colony->getStorage();
187
            $storageItem = $storage[$commodityId] ?? null;
188
            if ($storageItem !== null && $storageItem->getAmount() + $pro->getProduction() >= 0) {
189
                continue;
190
            }
191
            //echo "coloId:" . $colony->getId() . ", production:" . $pro->getProduction() . ", commodityId:" . $commodityId . ", commodity:" . $this->commodityCache->get($commodityId)->getName() . "\n";
192
            $field = $this->getBuildingToDeactivateByCommodity($colony, $commodityId);
193
            // echo $i." hit by commodity ".$field->getFieldId()." - produce ".$pro->getProduction()." MT ".microtime()."\n";
194
            $this->deactivateBuilding($field, $production, $this->commodityCache->get($commodityId));
195
196
            $result = true;
197
        }
198
199
        return $result;
200
    }
201
202
    /**
203
     * @param array<int, ColonyProduction> $production
204
     */
205
    private function checkLivingSpace(ColonyInterface $colony, array &$production): bool
206
    {
207
        if ($colony->getWorkers() > $colony->getMaxBev()) {
208
            $field = $this->getBuildingToDeactivateByLivingSpace($colony);
209
            if ($field !== null) {
210
                $this->deactivateBuilding($field, $production, 'Wohnraum');
211
212
                return true;
213
            }
214
        }
215
216
        return false;
217
    }
218
219
    /**
220
     * @param array<int, ColonyProduction> $production
221
     */
222
    private function checkEnergyProduction(ColonyInterface $colony, array &$production): bool
223
    {
224
        $energyProduction = $this->planetFieldRepository->getEnergyProductionByColony($colony->getId());
225
226
        if ($energyProduction < 0 && $colony->getEps() + $energyProduction < 0) {
227
            $field = $this->getBuildingToDeactivateByEpsUsage($colony);
228
            //echo $i . " hit by eps " . $field->getFieldId() . " - complete usage " . $colony->getEpsProduction() . " - usage " . $field->getBuilding()->getEpsProduction() . " MT " . microtime() . "\n";
229
            $this->deactivateBuilding($field, $production, 'Energie');
230
231
            return true;
232
        }
233
234
        return false;
235
    }
236
237
    /**
238
     * @param array<ColonyProduction> $production
239
     */
240
    private function deactivateBuilding(
241
        PlanetFieldInterface $field,
242
        array &$production,
243
        CommodityInterface|string $cause
244
    ): void {
245
        if ($cause instanceof CommodityInterface) {
246
            $ext = $cause->getName();
247
        } else {
248
            $ext = $cause;
249
        }
250
        $building = $field->getBuilding();
251
252
        if ($building === null) {
253
            throw new InvalidArgumentException('can not deactivate field without building');
254
        }
255
256
        $this->buildingManager->deactivate($field);
257
        $this->entityManager->flush();
258
259
        $this->mergeProduction($building->getCommodities(), $production);
260
261
        $this->msg[] = $building->getName() . " auf Feld " . $field->getFieldId() . " deaktiviert (Mangel an " . $ext . ")";
262
    }
263
264
    private function getBuildingToDeactivateByCommodity(ColonyInterface $colony, int $commodityId): PlanetFieldInterface
265
    {
266
        $fields = $this->planetFieldRepository->getCommodityConsumingByColonyAndCommodity(
267
            $colony->getId(),
268
            $commodityId,
269
            [1]
270
        );
271
272
        $result = current($fields);
0 ignored issues
show
Bug introduced by
$fields of type iterable is incompatible with the type array|object expected by parameter $array of current(). ( Ignorable by Annotation )

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

272
        $result = current(/** @scrutinizer ignore-type */ $fields);
Loading history...
273
        if (!$result) {
274
            throw new RuntimeException('no building found');
275
        }
276
277
        return $result;
278
    }
279
280
    private function getBuildingToDeactivateByEpsUsage(ColonyInterface $colony): PlanetFieldInterface
281
    {
282
        $fields = $this->planetFieldRepository->getEnergyConsumingByColony($colony->getId(), [1], 1);
283
284
        $result = current($fields);
0 ignored issues
show
Bug introduced by
$fields of type iterable is incompatible with the type array|object expected by parameter $array of current(). ( Ignorable by Annotation )

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

284
        $result = current(/** @scrutinizer ignore-type */ $fields);
Loading history...
285
        if (!$result) {
286
            throw new RuntimeException('no building found');
287
        }
288
289
        return $result;
290
    }
291
292
    private function getBuildingToDeactivateByLivingSpace(ColonyInterface $colony): ?PlanetFieldInterface
293
    {
294
        $fields = $this->planetFieldRepository->getWorkerConsumingByColonyAndState($colony->getId(), [1], 1);
295
296
        return empty($fields) ? null : current($fields);
0 ignored issues
show
Bug introduced by
$fields of type iterable is incompatible with the type array|object expected by parameter $array of current(). ( Ignorable by Annotation )

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

296
        return empty($fields) ? null : current(/** @scrutinizer ignore-type */ $fields);
Loading history...
297
    }
298
299
    /**
300
     * @param array<ColonyProduction> $production
301
     */
302
    private function proceedStorage(
303
        ColonyInterface $colony,
304
        array $production
305
    ): void {
306
        $doLog = $this->loggerUtil->doLog();
307
        if ($doLog) {
308
            $startTime = microtime(true);
309
        }
310
311
        $sum = $colony->getStorageSum();
312
313
        if ($doLog) {
314
            $startTime = microtime(true);
315
        }
316
317
        //DECREASE
318
        foreach ($production as $commodityId => $obj) {
319
            $amount = $obj->getProduction();
320
            $commodity = $this->commodityCache->get($commodityId);
321
322
            if ($amount < 0) {
323
                $amount = (int) abs($amount);
324
325
                if ($commodity->isSaveable()) {
326
                    // STANDARD
327
                    $this->colonyStorageManager->lowerStorage(
328
                        $colony,
329
                        $this->commodityCache->get($commodityId),
330
                        $amount
331
                    );
332
                    $sum -= $amount;
333
                } else {
334
                    // EFFECTS
335
                    $depositMining = $colony->getUserDepositMinings()[$commodityId];
336
337
                    $depositMining->setAmountLeft($depositMining->getAmountLeft() - $amount);
338
                    $this->colonyDepositMiningRepository->save($depositMining);
339
                }
340
            }
341
        }
342
        if ($doLog) {
343
            $endTime = microtime(true);
344
            $this->loggerUtil->log(sprintf("\tforeach1, seconds: %F", $endTime - $startTime));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $startTime does not seem to be defined for all execution paths leading up to this point.
Loading history...
345
        }
346
347
        if ($doLog) {
348
            $startTime = microtime(true);
349
        }
350
        foreach ($production as $commodityId => $obj) {
351
            $startTimeC = microtime(true);
352
353
            $commodity = $this->commodityCache->get($commodityId);
354
            if ($obj->getProduction() <= 0 || !$commodity->isSaveable()) {
355
                continue;
356
            }
357
            if ($sum >= $colony->getMaxStorage()) {
358
                if ($colony->getUser()->isStorageNotification()) {
359
                    $this->msg[] = _('Das Lager der Kolonie ist voll');
360
                }
361
                break;
362
            }
363
            if ($sum + $obj->getProduction() > $colony->getMaxStorage()) {
364
                $this->colonyStorageManager->upperStorage(
365
                    $colony,
366
                    $commodity,
367
                    $colony->getMaxStorage() - $sum
368
                );
369
                if ($colony->getUser()->isStorageNotification()) {
370
                    $this->msg[] = _('Das Lager der Kolonie ist voll');
371
                }
372
                break;
373
            }
374
            $startTimeM = microtime(true);
375
            $this->colonyStorageManager->upperStorage(
376
                $colony,
377
                $commodity,
378
                $obj->getProduction()
379
            );
380
            if ($doLog) {
381
                $endTimeM = microtime(true);
382
                $this->loggerUtil->log(sprintf("\t\t\tupper, seconds: %F", $endTimeM - $startTimeM));
383
            }
384
            $sum += $obj->getProduction();
385
            if ($doLog) {
386
                $endTimeC = microtime(true);
387
                $this->loggerUtil->log(sprintf("\t\tcommodity: %s, seconds: %F", $commodity->getName(), $endTimeC - $startTimeC));
388
            }
389
        }
390
        if ($doLog) {
391
            $endTime = microtime(true);
392
            $this->loggerUtil->log(sprintf("\tforeach2, seconds: %F", $endTime - $startTime));
393
        }
394
395
        if ($doLog) {
396
            $startTime = microtime(true);
397
        }
398
        $current_research = $this->researchedRepository->getCurrentResearch($colony->getUser());
399
400
        if ($current_research && $current_research->getActive()) {
401
            if (isset($production[$current_research->getResearch()->getCommodityId()])) {
402
                $this->researchStateFactory->createResearchState()->advance(
403
                    $current_research,
404
                    $production[$current_research->getResearch()->getCommodityId()]->getProduction()
405
                );
406
            }
407
        }
408
        if ($doLog) {
409
            $endTime = microtime(true);
410
            $this->loggerUtil->log(sprintf("\tresearch, seconds: %F", $endTime - $startTime));
411
        }
412
413
        if ($colony->getPopulation() > $colony->getMaxBev()) {
414
            $this->proceedEmigration($colony);
415
            return;
416
        }
417
418
        if ($colony->getPopulationLimit() > 0 && $colony->getPopulation() > $colony->getPopulationLimit() && $colony->getWorkless()) {
419
            if (($free = ($colony->getPopulationLimit() - $colony->getWorkers())) > 0) {
420
                $this->msg[] = sprintf(
421
                    _('Es sind %d Arbeitslose ausgewandert'),
422
                    ($colony->getWorkless() - $free)
423
                );
424
                $colony->setWorkless($free);
425
            } else {
426
                $this->msg[] = _('Es sind alle Arbeitslosen ausgewandert');
427
                $colony->setWorkless(0);
428
            }
429
        }
430
        $this->proceedImmigration(
431
            $colony,
432
            $production
433
        );
434
435
        if ($doLog) {
436
            $endTime = microtime(true);
437
            $this->loggerUtil->log(sprintf("\tstorage, seconds: %F", $endTime - $startTime));
438
        }
439
    }
440
441
    private function proceedModules(ColonyInterface $colony): void
442
    {
443
        foreach ($this->moduleQueueRepository->getByColony((int) $colony->getId()) as $queue) {
444
            $buildingFunction = $queue->getBuildingFunction();
445
446
            //spare parts and system components are generated by ship tick manager, to avoid dead locks
447
            if (
448
                $buildingFunction === BuildingEnum::BUILDING_FUNCTION_FABRICATION_HALL ||
449
                $buildingFunction === BuildingEnum::BUILDING_FUNCTION_TECH_CENTER
450
            ) {
451
                continue;
452
            }
453
454
            if ($this->colonyFunctionManager->hasActiveFunction($colony, $buildingFunction, false)) {
455
                $this->colonyStorageManager->upperStorage(
456
                    $colony,
457
                    $queue->getModule()->getCommodity(),
458
                    $queue->getAmount()
459
                );
460
461
                $this->msg[] = sprintf(
462
                    _('Es wurden %d %s hergestellt'),
463
                    $queue->getAmount(),
464
                    $queue->getModule()->getName()
465
                );
466
                $this->moduleQueueRepository->delete($queue);
467
            }
468
        }
469
    }
470
471
    /**
472
     * @param array<int, ColonyProduction> $production
473
     */
474
    private function proceedImmigration(
475
        ColonyInterface $colony,
476
        array $production
477
    ): void {
478
        // @todo
479
        $colony->setWorkless(
480
            $colony->getWorkless() +
481
                $this->colonyLibFactory->createColonyPopulationCalculator($colony, $production)->getGrowth()
482
        );
483
    }
484
485
    private function proceedEmigration(ColonyInterface $colony): void
486
    {
487
        if ($colony->getWorkless()) {
488
            $bev = rand(1, $colony->getWorkless());
489
            $colony->setWorkless($colony->getWorkless() - $bev);
490
            $this->msg[] = $bev . " Einwohner sind ausgewandert";
491
        }
492
    }
493
494
    private function sendMessages(ColonyInterface $colony): void
495
    {
496
        if ($this->msg === []) {
497
            return;
498
        }
499
        $text = "Tickreport der Kolonie " . $colony->getName() . "\n";
500
        foreach ($this->msg as $msg) {
501
            $text .= $msg . "\n";
502
        }
503
504
        $href = sprintf(_('colony.php?SHOW_COLONY=1&id=%d'), $colony->getId());
505
506
        $this->privateMessageSender->send(
507
            UserEnum::USER_NOONE,
508
            (int) $colony->getUserId(),
509
            $text,
510
            PrivateMessageFolderSpecialEnum::PM_SPECIAL_COLONY,
511
            $href
512
        );
513
514
        $this->msg = [];
515
    }
516
517
    /**
518
     * @param Collection<int, BuildingCommodityInterface> $buildingProduction
519
     * @param array<ColonyProduction> $production
520
     */
521
    private function mergeProduction(
522
        Collection $buildingProduction,
523
        array &$production
524
    ): void {
525
        foreach ($buildingProduction as $obj) {
526
            $commodityId = $obj->getCommodityId();
527
            if (!array_key_exists($commodityId, $production)) {
528
                $data = $this->colonyLibFactory->createColonyProduction(
529
                    $obj->getCommodity(),
530
                    $obj->getAmount() * -1
531
                );
532
533
                $production[$commodityId] = $data;
534
            } else {
535
                if ($obj->getAmount() < 0) {
536
                    $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

536
                    $production[$commodityId]->upperProduction(/** @scrutinizer ignore-type */ abs($obj->getAmount()));
Loading history...
537
                } else {
538
                    $production[$commodityId]->lowerProduction($obj->getAmount());
539
                }
540
            }
541
        }
542
    }
543
}
544