Passed
Push — dev ( 66fe9c...34655c )
by Nico
18:31
created

ColonyTick::getBuildingToDeactivateByCommodity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 9
ccs 0
cts 7
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 Doctrine\ORM\EntityManagerInterface;
7
use Stu\Component\Building\BuildingEnum;
8
use Stu\Component\Building\BuildingManagerInterface;
9
use Stu\Component\Colony\ColonyFunctionManagerInterface;
10
use Stu\Component\Colony\Storage\ColonyStorageManagerInterface;
11
use Stu\Component\Ship\System\ShipSystemManagerInterface;
12
use Stu\Lib\ColonyProduction\ColonyProduction;
13
use Stu\Module\Award\Lib\CreateUserAwardInterface;
14
use Stu\Module\Colony\Lib\ColonyLibFactoryInterface;
15
use Stu\Module\Crew\Lib\CrewCreatorInterface;
16
use Stu\Module\Database\Lib\CreateDatabaseEntryInterface;
17
use Stu\Module\Logging\LoggerUtilFactoryInterface;
18
use Stu\Module\Logging\LoggerUtilInterface;
19
use Stu\Module\Message\Lib\PrivateMessageFolderSpecialEnum;
20
use Stu\Module\Message\Lib\PrivateMessageSenderInterface;
21
use Stu\Module\PlayerSetting\Lib\UserEnum;
22
use Stu\Module\Research\ResearchState;
23
use Stu\Module\Ship\Lib\ShipCreatorInterface;
24
use Stu\Orm\Entity\BuildingCommodityInterface;
25
use Stu\Orm\Entity\ColonyDepositMiningInterface;
26
use Stu\Orm\Entity\ColonyInterface;
27
use Stu\Orm\Entity\CommodityInterface;
28
use Stu\Orm\Entity\PlanetFieldInterface;
29
use Stu\Orm\Repository\ColonyDepositMiningRepositoryInterface;
30
use Stu\Orm\Repository\ColonyRepositoryInterface;
31
use Stu\Orm\Repository\ModuleQueueRepositoryInterface;
32
use Stu\Orm\Repository\PlanetFieldRepositoryInterface;
33
use Stu\Orm\Repository\ResearchedRepositoryInterface;
34
use Stu\Orm\Repository\ShipRepositoryInterface;
35
use Stu\Orm\Repository\ShipRumpUserRepositoryInterface;
36
37
final class ColonyTick implements ColonyTickInterface
38
{
39
    private ResearchedRepositoryInterface $researchedRepository;
40
41
    private ShipRumpUserRepositoryInterface $shipRumpUserRepository;
42
43
    private ModuleQueueRepositoryInterface $moduleQueueRepository;
44
45
    private PlanetFieldRepositoryInterface $planetFieldRepository;
46
47
    private PrivateMessageSenderInterface $privateMessageSender;
48
49
    private ColonyStorageManagerInterface $colonyStorageManager;
50
51
    private ColonyRepositoryInterface $colonyRepository;
52
53
    private CreateDatabaseEntryInterface $createDatabaseEntry;
54
55
    private BuildingManagerInterface $buildingManager;
56
57
    private CrewCreatorInterface $crewCreator;
58
59
    private ShipCreatorInterface $shipCreator;
60
61
    private ShipRepositoryInterface $shipRepository;
62
63
    private ShipSystemManagerInterface $shipSystemManager;
64
65
    private CreateUserAwardInterface $createUserAward;
66
67
    private ColonyDepositMiningRepositoryInterface $colonyDepositMiningRepository;
68
69
    private EntityManagerInterface $entityManager;
70
71
    private LoggerUtilInterface $loggerUtil;
72
73
    private ColonyLibFactoryInterface $colonyLibFactory;
74
75
    private ColonyFunctionManagerInterface $colonyFunctionManager;
76
77
    private array $commodityArray;
78
79
    private array $msg = [];
80
81
    public function __construct(
82
        ResearchedRepositoryInterface $researchedRepository,
83
        ShipRumpUserRepositoryInterface $shipRumpUserRepository,
84
        ModuleQueueRepositoryInterface $moduleQueueRepository,
85
        PlanetFieldRepositoryInterface $planetFieldRepository,
86
        PrivateMessageSenderInterface $privateMessageSender,
87
        ColonyStorageManagerInterface $colonyStorageManager,
88
        ColonyRepositoryInterface $colonyRepository,
89
        CreateDatabaseEntryInterface $createDatabaseEntry,
90
        BuildingManagerInterface $buildingManager,
91
        CrewCreatorInterface $crewCreator,
92
        ShipCreatorInterface $shipCreator,
93
        ShipRepositoryInterface $shipRepository,
94
        ShipSystemManagerInterface $shipSystemManager,
95
        CreateUserAwardInterface $createUserAward,
96
        ColonyDepositMiningRepositoryInterface $colonyDepositMiningRepository,
97
        EntityManagerInterface $entityManager,
98
        ColonyLibFactoryInterface $colonyLibFactory,
99
        ColonyFunctionManagerInterface $colonyFunctionManager,
100
        LoggerUtilFactoryInterface $loggerUtilFactory
101
    ) {
102
        $this->researchedRepository = $researchedRepository;
103
        $this->shipRumpUserRepository = $shipRumpUserRepository;
104
        $this->moduleQueueRepository = $moduleQueueRepository;
105
        $this->planetFieldRepository = $planetFieldRepository;
106
        $this->privateMessageSender = $privateMessageSender;
107
        $this->colonyStorageManager = $colonyStorageManager;
108
        $this->colonyRepository = $colonyRepository;
109
        $this->createDatabaseEntry = $createDatabaseEntry;
110
        $this->buildingManager = $buildingManager;
111
        $this->crewCreator = $crewCreator;
112
        $this->shipCreator = $shipCreator;
113
        $this->shipRepository = $shipRepository;
114
        $this->shipSystemManager = $shipSystemManager;
115
        $this->createUserAward = $createUserAward;
116
        $this->colonyDepositMiningRepository = $colonyDepositMiningRepository;
117
        $this->entityManager = $entityManager;
118
        $this->loggerUtil = $loggerUtilFactory->getLoggerUtil();
119
        $this->colonyLibFactory = $colonyLibFactory;
120
        $this->colonyFunctionManager = $colonyFunctionManager;
121
    }
122
123
    public function work(ColonyInterface $colony, array $commodityArray): void
124
    {
125
        $doLog = $this->loggerUtil->doLog();
126
        if ($doLog) {
127
            $startTime = microtime(true);
128
        }
129
130
        $this->commodityArray = $commodityArray;
131
132
        $userDepositMinings = $colony->getUserDepositMinings();
133
134
        $this->mainLoop($colony, $userDepositMinings);
135
136
        $this->colonyRepository->save($colony);
137
138
        $this->proceedModules($colony);
139
        $this->sendMessages($colony);
140
141
        if ($doLog) {
142
            $endTime = microtime(true);
143
            $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...
144
        }
145
    }
146
147
    /**
148
     * @param ColonyDepositMiningInterface[] $userDepositMinings
149
     */
150
    private function mainLoop(ColonyInterface $colony, array $userDepositMinings)
151
    {
152
        $doLog = $this->loggerUtil->doLog();
153
154
        if ($doLog) {
155
            $startTime = microtime(true);
156
        }
157
158
        $i = 1;
159
        $storage = $colony->getStorage();
160
161
        $production = $this->colonyLibFactory->createColonyCommodityProduction($colony)->getProduction();
162
163
        while (true) {
164
            $rewind = 0;
165
            foreach ($production as $commodityId => $pro) {
166
                if ($pro->getProduction() >= 0) {
167
                    continue;
168
                }
169
170
                $depositMining = $userDepositMinings[$commodityId] ?? null;
171
                if ($depositMining !== null) {
172
                    if ($depositMining->isEnoughLeft((int) abs($pro->getProduction()))) {
173
                        continue;
174
                    }
175
                }
176
177
                $storageItem = $storage[$pro->getCommodityId()] ?? null;
178
                if ($storageItem !== null && $storageItem->getAmount() + $pro->getProduction() >= 0) {
179
                    continue;
180
                }
181
                //echo "coloId:" . $colony->getId() . ", production:" . $pro->getProduction() . ", commodityId:" . $commodityId . ", commodity:" . $this->commodityArray[$commodityId]->getName() . "\n";
182
                $field = $this->getBuildingToDeactivateByCommodity($colony, $commodityId);
183
                $name = '';
184
                // echo $i." hit by commodity ".$field->getFieldId()." - produce ".$pro->getProduction()." MT ".microtime()."\n";
185
                $this->deactivateBuilding($field, $production, $this->commodityArray[$commodityId], $name);
186
                $rewind = 1;
187
            }
188
189
            if ($rewind == 0 && $colony->getWorkers() > $colony->getMaxBev()) {
190
                $field = $this->getBuildingToDeactivateByLivingSpace($colony);
191
                $name = 'Wohnraum';
192
                $this->deactivateBuilding($field, $production, null, $name);
193
                $rewind = 1;
194
            }
195
196
            $energyProduction = $this->planetFieldRepository->getEnergyProductionByColony($colony->getId());
197
198
            if ($rewind == 0 && $energyProduction < 0 && $colony->getEps() + $energyProduction < 0) {
199
                $field = $this->getBuildingToDeactivateByEpsUsage($colony);
200
                $name = 'Energie';
201
                //echo $i . " hit by eps " . $field->getFieldId() . " - complete usage " . $colony->getEpsProduction() . " - usage " . $field->getBuilding()->getEpsProduction() . " MT " . microtime() . "\n";
202
                $this->deactivateBuilding($field, $production, null, $name);
203
                $rewind = 1;
204
            }
205
            if ($rewind == 1) {
206
                $i++;
207
                if ($i == 100) {
208
                    // SECURITY
209
                    //echo "HIT SECURITY BREAK\n";
210
                    break;
211
                }
212
                continue;
213
            }
214
            break;
215
        }
216
        $colony->setEps(
217
            min(
218
                $colony->getMaxEps(),
219
                $colony->getEps() + $this->planetFieldRepository->getEnergyProductionByColony($colony->getId())
220
            )
221
        );
222
223
        if ($doLog) {
224
            $endTime = microtime(true);
225
            $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...
226
        }
227
228
        $this->proceedStorage($colony, $userDepositMinings, $production);
229
    }
230
231
    /**
232
     * @param array<ColonyProduction> $production
233
     */
234
    private function deactivateBuilding(
235
        PlanetFieldInterface $field,
236
        array &$production,
237
        CommodityInterface $commodity = null,
238
        string $name
239
    ): void {
240
        if ($name != '') {
241
            $ext = $name;
242
        } else {
243
            $ext = $commodity->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on null. ( Ignorable by Annotation )

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

243
            /** @scrutinizer ignore-call */ 
244
            $ext = $commodity->getName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
244
        }
245
        $building = $field->getBuilding();
246
247
        $this->buildingManager->deactivate($field);
248
        $this->entityManager->flush();
249
250
        $this->mergeProduction($building->getCommodities(), $production);
251
252
        $this->msg[] = $building->getName() . " auf Feld " . $field->getFieldId() . " deaktiviert (Mangel an " . $ext . ")";
253
    }
254
255
    private function getBuildingToDeactivateByCommodity(ColonyInterface $colony, int $commodityId): PlanetFieldInterface
256
    {
257
        $fields = $this->planetFieldRepository->getCommodityConsumingByColonyAndCommodity(
258
            $colony->getId(),
259
            $commodityId,
260
            [1]
261
        );
262
263
        return 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

263
        return current(/** @scrutinizer ignore-type */ $fields);
Loading history...
264
    }
265
266
    private function getBuildingToDeactivateByEpsUsage(ColonyInterface $colony): PlanetFieldInterface
267
    {
268
        $fields = $this->planetFieldRepository->getEnergyConsumingByColony($colony->getId(), [1], 1);
269
270
        return 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

270
        return current(/** @scrutinizer ignore-type */ $fields);
Loading history...
271
    }
272
273
    private function getBuildingToDeactivateByLivingSpace(ColonyInterface $colony): PlanetFieldInterface
274
    {
275
        $fields = $this->planetFieldRepository->getWorkerConsumingByColonyAndState($colony->getId(), [1], 1);
276
277
        return 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

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

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