Passed
Push — dev ( 7b03b8...7ed3e6 )
by Nico
07:15 queued 15s
created

ColonyTick::proceedModules()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 16
nc 4
nop 1
dl 0
loc 26
ccs 0
cts 18
cp 0
crap 30
rs 9.4222
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
                if ($field !== null) {
192
                    $name = 'Wohnraum';
193
                    $this->deactivateBuilding($field, $production, null, $name);
194
                    $rewind = 1;
195
                }
196
            }
197
198
            $energyProduction = $this->planetFieldRepository->getEnergyProductionByColony($colony->getId());
199
200
            if ($rewind == 0 && $energyProduction < 0 && $colony->getEps() + $energyProduction < 0) {
201
                $field = $this->getBuildingToDeactivateByEpsUsage($colony);
202
                $name = 'Energie';
203
                //echo $i . " hit by eps " . $field->getFieldId() . " - complete usage " . $colony->getEpsProduction() . " - usage " . $field->getBuilding()->getEpsProduction() . " MT " . microtime() . "\n";
204
                $this->deactivateBuilding($field, $production, null, $name);
205
                $rewind = 1;
206
            }
207
            if ($rewind == 1) {
208
                $i++;
209
                if ($i == 100) {
210
                    // SECURITY
211
                    //echo "HIT SECURITY BREAK\n";
212
                    break;
213
                }
214
                continue;
215
            }
216
            break;
217
        }
218
        $colony->setEps(
219
            min(
220
                $colony->getMaxEps(),
221
                $colony->getEps() + $this->planetFieldRepository->getEnergyProductionByColony($colony->getId())
222
            )
223
        );
224
225
        if ($doLog) {
226
            $endTime = microtime(true);
227
            $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...
228
        }
229
230
        $this->proceedStorage($colony, $userDepositMinings, $production);
231
    }
232
233
    /**
234
     * @param array<ColonyProduction> $production
235
     */
236
    private function deactivateBuilding(
237
        PlanetFieldInterface $field,
238
        array &$production,
239
        CommodityInterface $commodity = null,
240
        string $name
241
    ): void {
242
        if ($name != '') {
243
            $ext = $name;
244
        } else {
245
            $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

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

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

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

279
        return empty($fields) ? null : current(/** @scrutinizer ignore-type */ $fields);
Loading history...
Bug Best Practice introduced by
The expression return empty($fields) ? null : current($fields) could return the type null which is incompatible with the type-hinted return Stu\Orm\Entity\PlanetFieldInterface. Consider adding an additional type-check to rule them out.
Loading history...
280
    }
281
282
    /**
283
     * @param ColonyDepositMiningInterface[] $userDepositMinings
284
     * @param array<ColonyProduction> $production
285
     */
286
    private function proceedStorage(
287
        ColonyInterface $colony,
288
        array $userDepositMinings,
289
        array $production
290
    ): void {
291
        $doLog = $this->loggerUtil->doLog();
292
        if ($doLog) {
293
            $startTime = microtime(true);
294
        }
295
296
        $sum = $colony->getStorageSum();
297
298
        if ($doLog) {
299
            $startTime = microtime(true);
300
        }
301
302
        //DECREASE
303
        foreach ($production as $commodityId => $obj) {
304
            $amount = $obj->getProduction();
305
            $commodity = $this->commodityArray[$commodityId];
306
307
            if ($amount < 0) {
308
                $amount = (int) abs($amount);
309
310
                if ($commodity->isSaveable()) {
311
                    // STANDARD
312
                    $this->colonyStorageManager->lowerStorage(
313
                        $colony,
314
                        $this->commodityArray[$commodityId],
315
                        $amount
316
                    );
317
                    $sum -= $amount;
318
                } else {
319
                    // EFFECTS
320
                    $depositMining = $userDepositMinings[$commodityId];
321
322
                    $depositMining->setAmountLeft($depositMining->getAmountLeft() - $amount);
323
                    $this->colonyDepositMiningRepository->save($depositMining);
324
                }
325
            }
326
        }
327
        if ($doLog) {
328
            $endTime = microtime(true);
329
            $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...
330
        }
331
332
        if ($doLog) {
333
            $startTime = microtime(true);
334
        }
335
        foreach ($production as $commodityId => $obj) {
336
            if ($doLog) {
337
                $startTimeC = microtime(true);
338
            }
339
340
            $commodity = $this->commodityArray[$commodityId];
341
            if ($obj->getProduction() <= 0 || !$commodity->isSaveable()) {
342
                continue;
343
            }
344
            if ($sum >= $colony->getMaxStorage()) {
345
                if ($colony->getUser()->isStorageNotification()) {
346
                    $this->msg[] = _('Das Lager der Kolonie ist voll');
347
                }
348
                break;
349
            }
350
            if ($sum + $obj->getProduction() > $colony->getMaxStorage()) {
351
                $this->colonyStorageManager->upperStorage(
352
                    $colony,
353
                    $commodity,
354
                    $colony->getMaxStorage() - $sum
355
                );
356
                if ($colony->getUser()->isStorageNotification()) {
357
                    $this->msg[] = _('Das Lager der Kolonie ist voll');
358
                }
359
                break;
360
            }
361
            if ($doLog) {
362
                $startTimeM = microtime(true);
363
            }
364
            $this->colonyStorageManager->upperStorage(
365
                $colony,
366
                $commodity,
367
                $obj->getProduction()
368
            );
369
            if ($doLog) {
370
                $endTimeM = microtime(true);
371
                $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...
372
            }
373
            $sum += $obj->getProduction();
374
            if ($doLog) {
375
                $endTimeC = microtime(true);
376
                $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...
377
            }
378
        }
379
        if ($doLog) {
380
            $endTime = microtime(true);
381
            $this->loggerUtil->log(sprintf("\tforeach2, seconds: %F", $endTime - $startTime));
382
        }
383
384
        if ($doLog) {
385
            $startTime = microtime(true);
386
        }
387
        $current_research = $this->researchedRepository->getCurrentResearch($colony->getUser());
388
389
        if ($current_research && $current_research->getActive()) {
390
            if (isset($production[$current_research->getResearch()->getCommodityId()])) {
391
                (new ResearchState(
392
                    $this->researchedRepository,
393
                    $this->shipRumpUserRepository,
394
                    $this->privateMessageSender,
395
                    $this->createDatabaseEntry,
396
                    $this->crewCreator,
397
                    $this->shipCreator,
398
                    $this->shipRepository,
399
                    $this->shipSystemManager,
400
                    $this->createUserAward,
401
                    $this->entityManager,
402
                ))->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, 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)
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