Passed
Push — master ( 91454b...2408fc )
by Nico
22:07 queued 10:43
created

ColonyTick::proceedImmigration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 8
ccs 0
cts 4
cp 0
crap 2
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
        $this->mainLoop($colony);
67
68
        $this->colonyRepository->save($colony);
69
70
        $this->proceedModules($colony);
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
    private function mainLoop(ColonyInterface $colony): void
80
    {
81
        $doLog = $this->loggerUtil->doLog();
82
83
        if ($doLog) {
84
            $startTime = microtime(true);
85
        }
86
87
        $i = 1;
88
        $production = $this->colonyLibFactory->createColonyCommodityProduction($colony)->getProduction();
89
90
        $deactivatedFields = [-1];
91
92
        while (true) {
93
94
            $rewind = $this->checkStorage($colony, $production, $deactivatedFields);
95
            $rewind |= $this->checkLivingSpace($colony, $production, $deactivatedFields);
96
            $rewind |= $this->checkEnergyProduction($colony, $production, $deactivatedFields);
97
98
            if ($rewind !== 0) {
99
                $i++;
100
                if ($i == 100) {
101
                    // SECURITY
102
                    //echo "HIT SECURITY BREAK\n";
103
                    break;
104
                }
105
                continue;
106
            }
107
            break;
108
        }
109
        $colony->setEps(
110
            min(
111
                $colony->getMaxEps(),
112
                $colony->getEps() + $this->planetFieldRepository->getEnergyProductionByHost($colony, $deactivatedFields)
113
            )
114
        );
115
116
        if ($doLog) {
117
            $endTime = microtime(true);
118
            $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...
119
        }
120
121
        foreach ($this->components as $component) {
122
            $component->work($colony, $production);
123
        }
124
125
        $this->proceedStorage($colony, $production);
126
    }
127
128
    /**
129
     * @param array<int, ColonyProduction> $production
130
     * @param array<int> $deactivatedFields
131
     */
132
    private function checkStorage(
133
        ColonyInterface $colony,
134
        array &$production,
135
        array &$deactivatedFields
136
    ): bool {
137
138
        $result = false;
139
140
        foreach ($production as $pro) {
141
            if ($pro->getProduction() >= 0) {
142
                continue;
143
            }
144
145
            $commodityId = $pro->getCommodityId();
146
147
            $depositMining = $colony->getUserDepositMinings()[$commodityId] ?? null;
148
            if ($depositMining !== null && $depositMining->isEnoughLeft((int) abs($pro->getProduction()))) {
149
                continue;
150
            }
151
152
            $storage = $colony->getStorage();
153
            $storageItem = $storage[$commodityId] ?? null;
154
            if ($storageItem !== null && $storageItem->getAmount() + $pro->getProduction() >= 0) {
155
                continue;
156
            }
157
            //echo "coloId:" . $colony->getId() . ", production:" . $pro->getProduction() . ", commodityId:" . $commodityId . ", commodity:" . $this->commodityCache->get($commodityId)->getName() . "\n";
158
            $field = $this->getBuildingToDeactivateByCommodity($colony, $commodityId, $deactivatedFields);
159
            // echo $i." hit by commodity ".$field->getFieldId()." - produce ".$pro->getProduction()." MT ".microtime()."\n";
160
            $this->deactivateBuilding($field, $production, $this->commodityCache->get($commodityId));
161
            $deactivatedFields[] = $field->getFieldId();
162
163
            $result = true;
164
        }
165
166
        return $result;
167
    }
168
169
    /**
170
     * @param array<int, ColonyProduction> $production
171
     * @param array<int> $deactivatedFields
172
     */
173
    private function checkLivingSpace(
174
        ColonyInterface $colony,
175
        array &$production,
176
        array &$deactivatedFields
177
    ): bool {
178
        if ($colony->getWorkers() > $colony->getMaxBev()) {
179
            $field = $this->getBuildingToDeactivateByLivingSpace($colony, $deactivatedFields);
180
            if ($field !== null) {
181
                $this->deactivateBuilding($field, $production, 'Wohnraum');
182
                $deactivatedFields[] = $field->getFieldId();
183
184
                return true;
185
            }
186
        }
187
188
        return false;
189
    }
190
191
    /**
192
     * @param array<int, ColonyProduction> $production
193
     * @param array<int> $deactivatedFields
194
     */
195
    private function checkEnergyProduction(
196
        ColonyInterface $colony,
197
        array &$production,
198
        array &$deactivatedFields
199
    ): bool {
200
        $energyProduction = $this->planetFieldRepository->getEnergyProductionByHost($colony, $deactivatedFields);
201
202
        if ($energyProduction < 0 && $colony->getEps() + $energyProduction < 0) {
203
            $field = $this->getBuildingToDeactivateByEpsUsage($colony, $deactivatedFields);
204
            //echo $i . " hit by eps " . $field->getFieldId() . " - complete usage " . $colony->getEpsProduction() . " - usage " . $field->getBuilding()->getEpsProduction() . " MT " . microtime() . "\n";
205
            $this->deactivateBuilding($field, $production, 'Energie');
206
            $deactivatedFields[] = $field->getFieldId();
207
208
            return true;
209
        }
210
211
        return false;
212
    }
213
214
    /**
215
     * @param array<int, ColonyProduction> $production
216
     */
217
    private function deactivateBuilding(
218
        PlanetFieldInterface $field,
219
        array &$production,
220
        CommodityInterface|string $cause
221
    ): void {
222
        $ext = $cause instanceof CommodityInterface ? $cause->getName() : $cause;
223
        $building = $field->getBuilding();
224
225
        if ($building === null) {
226
            throw new InvalidArgumentException('can not deactivate field without building');
227
        }
228
229
        $this->buildingManager->deactivate($field);
230
231
        $this->mergeProduction($building->getCommodities(), $production);
232
233
        $this->msg[] = $building->getName() . " auf Feld " . $field->getFieldId() . " deaktiviert (Mangel an " . $ext . ")";
234
    }
235
236
    /**
237
     * @param array<int> $deactivatedFields
238
     */
239
    private function getBuildingToDeactivateByCommodity(
240
        ColonyInterface $colony,
241
        int $commodityId,
242
        array $deactivatedFields
243
    ): PlanetFieldInterface {
244
        $fields = $this->planetFieldRepository->getCommodityConsumingByHostAndCommodity(
245
            $colony,
246
            $commodityId,
247
            [1],
248
            1,
249
            $deactivatedFields
250
        );
251
252
        $result = current($fields);
253
        if (!$result) {
254
            throw new RuntimeException('no building found');
255
        }
256
257
        return $result;
258
    }
259
260
    /**
261
     * @param array<int> $deactivatedFields
262
     */
263
    private function getBuildingToDeactivateByEpsUsage(
264
        ColonyInterface $colony,
265
        array $deactivatedFields
266
    ): PlanetFieldInterface {
267
        $fields = $this->planetFieldRepository->getEnergyConsumingByHost(
268
            $colony,
269
            [1],
270
            1,
271
            $deactivatedFields
272
        );
273
274
        $result = current($fields);
275
        if (!$result) {
276
            throw new RuntimeException('no building found');
277
        }
278
279
        return $result;
280
    }
281
282
    /**
283
     * @param array<int> $deactivatedFields
284
     */
285
    private function getBuildingToDeactivateByLivingSpace(
286
        ColonyInterface $colony,
287
        array $deactivatedFields
288
    ): ?PlanetFieldInterface {
289
        $fields = $this->planetFieldRepository->getWorkerConsumingByColonyAndState(
290
            $colony->getId(),
291
            [1],
292
            1,
293
            $deactivatedFields
294
        );
295
296
        return $fields === [] ? null : current($fields);
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 = abs($amount);
324
325
                if ($commodity->isSaveable()) {
326
                    // STANDARD
327
                    $this->storageManager->lowerStorage(
328
                        $colony,
329
                        $this->commodityCache->get($commodityId),
330
                        $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

330
                        /** @scrutinizer ignore-type */ $amount
Loading history...
331
                    );
332
                    $sum -= $amount;
333
                } else {
334
                    // EFFECTS
335
                    $depositMining = $colony->getUserDepositMinings()[$commodityId];
336
337
                    $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

337
                    $depositMining->setAmountLeft(/** @scrutinizer ignore-type */ $depositMining->getAmountLeft() - $amount);
Loading history...
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->storageManager->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->storageManager->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
399
        if ($doLog) {
400
            $endTime = microtime(true);
401
            $this->loggerUtil->log(sprintf("\tresearch, seconds: %F", $endTime - $startTime));
402
        }
403
404
        if ($colony->getPopulation() > $colony->getMaxBev()) {
405
            $this->proceedEmigration($colony);
406
            return;
407
        }
408
409
        if ($colony->getPopulationLimit() > 0 && $colony->getPopulation() > $colony->getPopulationLimit() && $colony->getWorkless()) {
410
            if (($free = ($colony->getPopulationLimit() - $colony->getWorkers())) > 0) {
411
                $this->msg[] = sprintf(
412
                    _('Es sind %d Arbeitslose ausgewandert'),
413
                    ($colony->getWorkless() - $free)
414
                );
415
                $colony->setWorkless($free);
416
            } else {
417
                $this->msg[] = _('Es sind alle Arbeitslosen ausgewandert');
418
                $colony->setWorkless(0);
419
            }
420
        }
421
        $this->proceedImmigration(
422
            $colony,
423
            $production
424
        );
425
426
        if ($doLog) {
427
            $endTime = microtime(true);
428
            $this->loggerUtil->log(sprintf("\tstorage, seconds: %F", $endTime - $startTime));
429
        }
430
    }
431
432
    private function proceedModules(ColonyInterface $colony): void
433
    {
434
        foreach ($this->moduleQueueRepository->getByColony($colony->getId()) as $queue) {
435
            $buildingFunction = $queue->getBuildingFunction();
436
437
            //spare parts and system components are generated by ship tick manager, to avoid dead locks
438
            if (
439
                $buildingFunction === BuildingFunctionEnum::FABRICATION_HALL ||
440
                $buildingFunction === BuildingFunctionEnum::TECH_CENTER
441
            ) {
442
                continue;
443
            }
444
445
            if ($this->colonyFunctionManager->hasActiveFunction($colony, $buildingFunction, false)) {
446
                $this->storageManager->upperStorage(
447
                    $colony,
448
                    $queue->getModule()->getCommodity(),
449
                    $queue->getAmount()
450
                );
451
452
                $this->msg[] = sprintf(
453
                    _('Es wurden %d %s hergestellt'),
454
                    $queue->getAmount(),
455
                    $queue->getModule()->getName()
456
                );
457
                $this->moduleQueueRepository->delete($queue);
458
            }
459
        }
460
    }
461
462
    /**
463
     * @param array<int, ColonyProduction> $production
464
     */
465
    private function proceedImmigration(
466
        ColonyInterface $colony,
467
        array $production
468
    ): void {
469
        // @todo
470
        $colony->setWorkless(
471
            $colony->getWorkless() +
472
                $this->colonyLibFactory->createColonyPopulationCalculator($colony, $production)->getGrowth()
473
        );
474
    }
475
476
    private function proceedEmigration(ColonyInterface $colony): void
477
    {
478
        if ($colony->getWorkless() !== 0) {
479
            $bev = random_int(1, $colony->getWorkless());
480
            $colony->setWorkless($colony->getWorkless() - $bev);
481
            $this->msg[] = $bev . " Einwohner sind ausgewandert";
482
        }
483
    }
484
485
    private function sendMessages(ColonyInterface $colony): void
486
    {
487
        if ($this->msg === []) {
488
            return;
489
        }
490
        $text = "Tickreport der Kolonie " . $colony->getName() . "\n";
491
        foreach ($this->msg as $msg) {
492
            $text .= $msg . "\n";
493
        }
494
495
        $this->privateMessageSender->send(
496
            UserEnum::USER_NOONE,
497
            $colony->getUserId(),
498
            $text,
499
            PrivateMessageFolderTypeEnum::SPECIAL_COLONY,
500
            $colony
501
        );
502
503
        $this->msg = [];
504
    }
505
506
    /**
507
     * @param Collection<int, BuildingCommodityInterface> $buildingProduction
508
     * @param array<int, ColonyProduction> $production
509
     */
510
    private function mergeProduction(
511
        Collection $buildingProduction,
512
        array &$production
513
    ): void {
514
        foreach ($buildingProduction as $obj) {
515
            $commodityId = $obj->getCommodityId();
516
            if (!array_key_exists($commodityId, $production)) {
517
                $data = $this->colonyLibFactory->createColonyProduction(
518
                    $obj->getCommodity(),
519
                    $obj->getAmount() * -1
520
                );
521
                $production[$commodityId] = $data;
522
            } elseif ($obj->getAmount() < 0) {
523
                $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

523
                $production[$commodityId]->upperProduction(/** @scrutinizer ignore-type */ abs($obj->getAmount()));
Loading history...
524
            } else {
525
                $production[$commodityId]->lowerProduction($obj->getAmount());
526
            }
527
        }
528
    }
529
}
530