Passed
Push — master ( 767dcd...21977d )
by Nico
22:25 queued 10:21
created

ColonyTick::getBuildingToDeactivateByEpsUsage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 2
dl 0
loc 17
ccs 0
cts 11
cp 0
crap 6
rs 10
c 0
b 0
f 0
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\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\PrivateMessageFolderTypeEnum;
19
use Stu\Module\Message\Lib\PrivateMessageSenderInterface;
20
use Stu\Module\PlayerSetting\Lib\UserEnum;
0 ignored issues
show
Bug introduced by
The type Stu\Module\PlayerSetting\Lib\UserEnum 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\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
31
final class ColonyTick implements ColonyTickInterface
32
{
33
    private LoggerUtilInterface $loggerUtil;
34
35
    /**
36
     * @var array<string>
37
     */
38
    private array $msg = [];
39
40
    /** @param array<ColonyTickComponentInterface> $components */
41 1
    public function __construct(
42
        private ModuleQueueRepositoryInterface $moduleQueueRepository,
43
        private PlanetFieldRepositoryInterface $planetFieldRepository,
44
        private PrivateMessageSenderInterface $privateMessageSender,
45
        private StorageManagerInterface $storageManager,
46
        private ColonyRepositoryInterface $colonyRepository,
47
        private BuildingManagerInterface $buildingManager,
48
        private ColonyDepositMiningRepositoryInterface $colonyDepositMiningRepository,
49
        private ColonyLibFactoryInterface $colonyLibFactory,
50
        private ColonyFunctionManagerInterface $colonyFunctionManager,
51
        private CommodityCacheInterface $commodityCache,
52
        LoggerUtilFactoryInterface $loggerUtilFactory,
53
        private array $components
54
    ) {
55 1
        $this->loggerUtil = $loggerUtilFactory->getLoggerUtil();
56
    }
57
58
    #[Override]
59
    public function work(ColonyInterface $colony): void
60
    {
61
        $doLog = $this->loggerUtil->doLog();
62
        if ($doLog) {
63
            $startTime = microtime(true);
64
        }
65
66
        $deactivatedFields = $this->mainLoop($colony);
67
68
        $this->colonyRepository->save($colony);
69
70
        $this->proceedModules($colony, $deactivatedFields);
71
        $this->sendMessages($colony);
72
73
        if ($doLog) {
74
            $endTime = microtime(true);
75
            $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...
76
        }
77
    }
78
79
    /**
80
     * @return array<int>
81
     */
82
    private function mainLoop(ColonyInterface $colony): array
83
    {
84
        $doLog = $this->loggerUtil->doLog();
85
86
        if ($doLog) {
87
            $startTime = microtime(true);
88
        }
89
90
        $i = 1;
91
        $production = $this->colonyLibFactory->createColonyCommodityProduction($colony)->getProduction();
92
93
        $deactivatedFields = [-1];
94
95
        while (true) {
96
97
            $rewind = $this->checkStorage($colony, $production, $deactivatedFields);
98
            $rewind |= $this->checkLivingSpace($colony, $production, $deactivatedFields);
99
            $rewind |= $this->checkEnergyProduction($colony, $production, $deactivatedFields);
100
101
            if ($rewind !== 0) {
102
                $i++;
103
                if ($i == 100) {
104
                    // SECURITY
105
                    //echo "HIT SECURITY BREAK\n";
106
                    break;
107
                }
108
                continue;
109
            }
110
            break;
111
        }
112
        $colony->setEps(
113
            min(
114
                $colony->getMaxEps(),
115
                $colony->getEps() + $this->planetFieldRepository->getEnergyProductionByHost($colony, $deactivatedFields)
116
            )
117
        );
118
119
        if ($doLog) {
120
            $endTime = microtime(true);
121
            $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...
122
        }
123
124
        foreach ($this->components as $component) {
125
            $component->work($colony, $production);
126
        }
127
128
        $this->proceedStorage($colony, $production);
129
130
        return $deactivatedFields;
131
    }
132
133
    /**
134
     * @param array<int, ColonyProduction> $production
135
     * @param array<int> $deactivatedFields
136
     */
137
    private function checkStorage(
138
        ColonyInterface $colony,
139
        array &$production,
140
        array &$deactivatedFields
141
    ): bool {
142
143
        $result = false;
144
145
        foreach ($production as $pro) {
146
            if ($pro->getProduction() >= 0) {
147
                continue;
148
            }
149
150
            $commodityId = $pro->getCommodityId();
151
152
            $depositMining = $colony->getUserDepositMinings()[$commodityId] ?? null;
153
            if ($depositMining !== null && $depositMining->isEnoughLeft((int) abs($pro->getProduction()))) {
154
                continue;
155
            }
156
157
            $storage = $colony->getStorage();
158
            $storageItem = $storage[$commodityId] ?? null;
159
            if ($storageItem !== null && $storageItem->getAmount() + $pro->getProduction() >= 0) {
160
                continue;
161
            }
162
            //echo "coloId:" . $colony->getId() . ", production:" . $pro->getProduction() . ", commodityId:" . $commodityId . ", commodity:" . $this->commodityCache->get($commodityId)->getName() . "\n";
163
            $field = $this->getBuildingToDeactivateByCommodity($colony, $commodityId, $deactivatedFields);
164
            // echo $i." hit by commodity ".$field->getFieldId()." - produce ".$pro->getProduction()." MT ".microtime()."\n";
165
            $this->deactivateBuilding($field, $production, $this->commodityCache->get($commodityId));
166
            $deactivatedFields[] = $field->getFieldId();
167
168
            $result = true;
169
        }
170
171
        return $result;
172
    }
173
174
    /**
175
     * @param array<int, ColonyProduction> $production
176
     * @param array<int> $deactivatedFields
177
     */
178
    private function checkLivingSpace(
179
        ColonyInterface $colony,
180
        array &$production,
181
        array &$deactivatedFields
182
    ): bool {
183
        if ($colony->getWorkers() > $colony->getMaxBev()) {
184
            $field = $this->getBuildingToDeactivateByLivingSpace($colony, $deactivatedFields);
185
            if ($field !== null) {
186
                $this->deactivateBuilding($field, $production, 'Wohnraum');
187
                $deactivatedFields[] = $field->getFieldId();
188
189
                return true;
190
            }
191
        }
192
193
        return false;
194
    }
195
196
    /**
197
     * @param array<int, ColonyProduction> $production
198
     * @param array<int> $deactivatedFields
199
     */
200
    private function checkEnergyProduction(
201
        ColonyInterface $colony,
202
        array &$production,
203
        array &$deactivatedFields
204
    ): bool {
205
        $energyProduction = $this->planetFieldRepository->getEnergyProductionByHost($colony, $deactivatedFields);
206
207
        if ($energyProduction < 0 && $colony->getEps() + $energyProduction < 0) {
208
            $field = $this->getBuildingToDeactivateByEpsUsage($colony, $deactivatedFields);
209
            //echo $i . " hit by eps " . $field->getFieldId() . " - complete usage " . $colony->getEpsProduction() . " - usage " . $field->getBuilding()->getEpsProduction() . " MT " . microtime() . "\n";
210
            $this->deactivateBuilding($field, $production, 'Energie');
211
            $deactivatedFields[] = $field->getFieldId();
212
213
            return true;
214
        }
215
216
        return false;
217
    }
218
219
    /**
220
     * @param array<int, ColonyProduction> $production
221
     */
222
    private function deactivateBuilding(
223
        PlanetFieldInterface $field,
224
        array &$production,
225
        CommodityInterface|string $cause
226
    ): void {
227
        $ext = $cause instanceof CommodityInterface ? $cause->getName() : $cause;
228
        $building = $field->getBuilding();
229
230
        if ($building === null) {
231
            throw new InvalidArgumentException('can not deactivate field without building');
232
        }
233
234
        $this->buildingManager->deactivate($field);
235
236
        $this->mergeProduction($building->getCommodities(), $production);
237
238
        $this->msg[] = $building->getName() . " auf Feld " . $field->getFieldId() . " deaktiviert (Mangel an " . $ext . ")";
239
    }
240
241
    /**
242
     * @param array<int> $deactivatedFields
243
     */
244
    private function getBuildingToDeactivateByCommodity(
245
        ColonyInterface $colony,
246
        int $commodityId,
247
        array $deactivatedFields
248
    ): PlanetFieldInterface {
249
        $fields = $this->planetFieldRepository->getCommodityConsumingByHostAndCommodity(
250
            $colony,
251
            $commodityId,
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 getBuildingToDeactivateByEpsUsage(
269
        ColonyInterface $colony,
270
        array $deactivatedFields
271
    ): PlanetFieldInterface {
272
        $fields = $this->planetFieldRepository->getEnergyConsumingByHost(
273
            $colony,
274
            [1],
275
            1,
276
            $deactivatedFields
277
        );
278
279
        $result = current($fields);
280
        if (!$result) {
281
            throw new RuntimeException('no building found');
282
        }
283
284
        return $result;
285
    }
286
287
    /**
288
     * @param array<int> $deactivatedFields
289
     */
290
    private function getBuildingToDeactivateByLivingSpace(
291
        ColonyInterface $colony,
292
        array $deactivatedFields
293
    ): ?PlanetFieldInterface {
294
        $fields = $this->planetFieldRepository->getWorkerConsumingByColonyAndState(
295
            $colony->getId(),
296
            [1],
297
            1,
298
            $deactivatedFields
299
        );
300
301
        return $fields === [] ? null : current($fields);
302
    }
303
304
    /**
305
     * @param array<ColonyProduction> $production
306
     */
307
    private function proceedStorage(
308
        ColonyInterface $colony,
309
        array $production
310
    ): void {
311
        $doLog = $this->loggerUtil->doLog();
312
        if ($doLog) {
313
            $startTime = microtime(true);
314
        }
315
316
        $sum = $colony->getStorageSum();
317
318
        if ($doLog) {
319
            $startTime = microtime(true);
320
        }
321
322
        //DECREASE
323
        foreach ($production as $commodityId => $obj) {
324
            $amount = $obj->getProduction();
325
            $commodity = $this->commodityCache->get($commodityId);
326
327
            if ($amount < 0) {
328
                $amount = abs($amount);
329
330
                if ($commodity->isSaveable()) {
331
                    // STANDARD
332
                    $this->storageManager->lowerStorage(
333
                        $colony,
334
                        $this->commodityCache->get($commodityId),
335
                        $amount
0 ignored issues
show
Bug introduced by
It seems like $amount can also be of type double; however, parameter $amount of Stu\Lib\Transfer\Storage...terface::lowerStorage() 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

335
                        /** @scrutinizer ignore-type */ $amount
Loading history...
336
                    );
337
                    $sum -= $amount;
338
                } else {
339
                    // EFFECTS
340
                    $depositMining = $colony->getUserDepositMinings()[$commodityId];
341
342
                    $depositMining->setAmountLeft($depositMining->getAmountLeft() - $amount);
0 ignored issues
show
Bug introduced by
$depositMining->getAmountLeft() - $amount of type double is incompatible with the type integer expected by parameter $amountLeft of Stu\Orm\Entity\ColonyDep...erface::setAmountLeft(). ( Ignorable by Annotation )

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

342
                    $depositMining->setAmountLeft(/** @scrutinizer ignore-type */ $depositMining->getAmountLeft() - $amount);
Loading history...
343
                    $this->colonyDepositMiningRepository->save($depositMining);
344
                }
345
            }
346
        }
347
        if ($doLog) {
348
            $endTime = microtime(true);
349
            $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...
350
        }
351
352
        if ($doLog) {
353
            $startTime = microtime(true);
354
        }
355
        foreach ($production as $commodityId => $obj) {
356
            $startTimeC = microtime(true);
357
358
            $commodity = $this->commodityCache->get($commodityId);
359
            if ($obj->getProduction() <= 0 || !$commodity->isSaveable()) {
360
                continue;
361
            }
362
            if ($sum >= $colony->getMaxStorage()) {
363
                if ($colony->getUser()->isStorageNotification()) {
364
                    $this->msg[] = _('Das Lager der Kolonie ist voll');
365
                }
366
                break;
367
            }
368
            if ($sum + $obj->getProduction() > $colony->getMaxStorage()) {
369
                $this->storageManager->upperStorage(
370
                    $colony,
371
                    $commodity,
372
                    $colony->getMaxStorage() - $sum
373
                );
374
                if ($colony->getUser()->isStorageNotification()) {
375
                    $this->msg[] = _('Das Lager der Kolonie ist voll');
376
                }
377
                break;
378
            }
379
            $startTimeM = microtime(true);
380
            $this->storageManager->upperStorage(
381
                $colony,
382
                $commodity,
383
                $obj->getProduction()
384
            );
385
            if ($doLog) {
386
                $endTimeM = microtime(true);
387
                $this->loggerUtil->log(sprintf("\t\t\tupper, seconds: %F", $endTimeM - $startTimeM));
388
            }
389
            $sum += $obj->getProduction();
390
            if ($doLog) {
391
                $endTimeC = microtime(true);
392
                $this->loggerUtil->log(sprintf("\t\tcommodity: %s, seconds: %F", $commodity->getName(), $endTimeC - $startTimeC));
393
            }
394
        }
395
        if ($doLog) {
396
            $endTime = microtime(true);
397
            $this->loggerUtil->log(sprintf("\tforeach2, seconds: %F", $endTime - $startTime));
398
        }
399
400
        if ($doLog) {
401
            $startTime = microtime(true);
402
        }
403
404
        if ($doLog) {
405
            $endTime = microtime(true);
406
            $this->loggerUtil->log(sprintf("\tresearch, seconds: %F", $endTime - $startTime));
407
        }
408
409
        if ($colony->getPopulation() > $colony->getMaxBev()) {
410
            $this->proceedEmigration($colony);
411
            return;
412
        }
413
414
        if ($colony->getPopulationLimit() > 0 && $colony->getPopulation() > $colony->getPopulationLimit() && $colony->getWorkless()) {
415
            if (($free = ($colony->getPopulationLimit() - $colony->getWorkers())) > 0) {
416
                $this->msg[] = sprintf(
417
                    _('Es sind %d Arbeitslose ausgewandert'),
418
                    ($colony->getWorkless() - $free)
419
                );
420
                $colony->setWorkless($free);
421
            } else {
422
                $this->msg[] = _('Es sind alle Arbeitslosen ausgewandert');
423
                $colony->setWorkless(0);
424
            }
425
        }
426
        $this->proceedImmigration(
427
            $colony,
428
            $production
429
        );
430
431
        if ($doLog) {
432
            $endTime = microtime(true);
433
            $this->loggerUtil->log(sprintf("\tstorage, seconds: %F", $endTime - $startTime));
434
        }
435
    }
436
437
    /**
438
     * @param array<int> $deactivatedFields
439
     */
440
    private function proceedModules(ColonyInterface $colony, array $deactivatedFields): void
441
    {
442
        foreach ($this->moduleQueueRepository->getByColony($colony->getId()) as $queue) {
443
            $buildingFunction = $queue->getBuildingFunction();
444
445
            //spare parts and system components are generated by spacecraft tick manager, to avoid dead locks
446
            if (
447
                $buildingFunction === BuildingFunctionEnum::FABRICATION_HALL ||
448
                $buildingFunction === BuildingFunctionEnum::TECH_CENTER
449
            ) {
450
                continue;
451
            }
452
453
            if ($this->colonyFunctionManager->hasActiveFunction($colony, $buildingFunction, false, $deactivatedFields)) {
454
                $this->storageManager->upperStorage(
455
                    $colony,
456
                    $queue->getModule()->getCommodity(),
457
                    $queue->getAmount()
458
                );
459
460
                $this->msg[] = sprintf(
461
                    _('Es wurden %d %s hergestellt'),
462
                    $queue->getAmount(),
463
                    $queue->getModule()->getName()
464
                );
465
                $this->moduleQueueRepository->delete($queue);
466
            }
467
        }
468
    }
469
470
    /**
471
     * @param array<int, ColonyProduction> $production
472
     */
473
    private function proceedImmigration(
474
        ColonyInterface $colony,
475
        array $production
476
    ): void {
477
        // @todo
478
        $colony->setWorkless(
479
            $colony->getWorkless() +
480
                $this->colonyLibFactory->createColonyPopulationCalculator($colony, $production)->getGrowth()
481
        );
482
    }
483
484
    private function proceedEmigration(ColonyInterface $colony): void
485
    {
486
        if ($colony->getWorkless() !== 0) {
487
            $bev = random_int(1, $colony->getWorkless());
488
            $colony->setWorkless($colony->getWorkless() - $bev);
489
            $this->msg[] = $bev . " Einwohner sind ausgewandert";
490
        }
491
    }
492
493
    private function sendMessages(ColonyInterface $colony): void
494
    {
495
        if ($this->msg === []) {
496
            return;
497
        }
498
        $text = "Tickreport der Kolonie " . $colony->getName() . "\n";
499
        foreach ($this->msg as $msg) {
500
            $text .= $msg . "\n";
501
        }
502
503
        $this->privateMessageSender->send(
504
            UserEnum::USER_NOONE,
505
            $colony->getUserId(),
506
            $text,
507
            PrivateMessageFolderTypeEnum::SPECIAL_COLONY,
508
            $colony
509
        );
510
511
        $this->msg = [];
512
    }
513
514
    /**
515
     * @param Collection<int, BuildingCommodityInterface> $buildingProduction
516
     * @param array<int, ColonyProduction> $production
517
     */
518
    private function mergeProduction(
519
        Collection $buildingProduction,
520
        array &$production
521
    ): void {
522
        foreach ($buildingProduction as $obj) {
523
            $commodityId = $obj->getCommodityId();
524
            if (!array_key_exists($commodityId, $production)) {
525
                $data = $this->colonyLibFactory->createColonyProduction(
526
                    $obj->getCommodity(),
527
                    $obj->getAmount() * -1
528
                );
529
                $production[$commodityId] = $data;
530
            } elseif ($obj->getAmount() < 0) {
531
                $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

531
                $production[$commodityId]->upperProduction(/** @scrutinizer ignore-type */ abs($obj->getAmount()));
Loading history...
532
            } else {
533
                $production[$commodityId]->lowerProduction($obj->getAmount());
534
            }
535
        }
536
    }
537
}
538