Completed
Push — master ( fde887...02d242 )
by Petr
03:30
created

UserService::getEntityManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Rottenwood\KingdomBundle\Service;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\Common\Collections\Criteria;
7
use Doctrine\ORM\EntityManager;
8
use Monolog\Logger;
9
use Rottenwood\KingdomBundle\Entity\Human;
10
use Rottenwood\KingdomBundle\Entity\Infrastructure\InventoryItemRepository;
11
use Rottenwood\KingdomBundle\Entity\Infrastructure\Item;
12
use Rottenwood\KingdomBundle\Entity\Infrastructure\ItemRepository;
13
use Rottenwood\KingdomBundle\Entity\Infrastructure\RoomRepository;
14
use Rottenwood\KingdomBundle\Entity\InventoryItem;
15
use Rottenwood\KingdomBundle\Entity\Room;
16
use Rottenwood\KingdomBundle\Entity\Infrastructure\User;
17
use Rottenwood\KingdomBundle\Entity\Infrastructure\HumanRepository;
18
use Rottenwood\KingdomBundle\Exception\ItemNotFound;
19
use Rottenwood\KingdomBundle\Exception\NotEnoughItems;
20
use Rottenwood\KingdomBundle\Exception\RoomNotFound;
21
use Rottenwood\KingdomBundle\Redis\RedisClientInterface;
22
use Predis\Client as RedisClient;
23
use Symfony\Component\Finder\Finder;
24
use Symfony\Component\Finder\SplFileInfo;
25
use Symfony\Component\HttpKernel\KernelInterface;
26
27
class UserService
28
{
29
30
    /** @var KernelInterface */
31
    private $kernel;
32
    /** @var RedisClient */
33
    private $redis;
34
    /** @var HumanRepository */
35
    private $humanRepository;
36
    /** @var InventoryItemRepository */
37
    private $inventoryItemRepository;
38
    /** @var Logger */
39
    private $logger;
40
    /** @var RoomRepository */
41
    private $roomRepository;
42
    /** @var ItemRepository */
43
    private $itemRepository;
44
45
    /**
46
     * @param KernelInterface         $kernel
47
     * @param RedisClient             $redis
48
     * @param Logger                  $logger
49
     * @param HumanRepository         $humanRepository
50
     * @param InventoryItemRepository $inventoryItemRepository
51
     * @param RoomRepository          $roomRepository
52
     * @param ItemRepository          $itemRepository
53
     */
54
    public function __construct(
55
        KernelInterface $kernel,
56
        RedisClient $redis,
57
        Logger $logger,
58
        HumanRepository $humanRepository,
59
        InventoryItemRepository $inventoryItemRepository,
60
        RoomRepository $roomRepository,
61
        ItemRepository $itemRepository
62
    ) {
63
        $this->redis = $redis;
64
        $this->logger = $logger;
65
        $this->humanRepository = $humanRepository;
66
        $this->inventoryItemRepository = $inventoryItemRepository;
67
        $this->kernel = $kernel;
68
        $this->roomRepository = $roomRepository;
69
        $this->itemRepository = $itemRepository;
70
    }
71
72
    /**
73
     * Запрос ID всех онлайн игроков в комнате
74
     * @param Room  $room
75
     * @param int|int[] $excludePlayerIds
76
     * @return int[]
77
     */
78
    public function getOnlineUsersIdsInRoom(Room $room, $excludePlayerIds = []): array
79
    {
80
        return array_map(
81
            function (User $user) {
82
                return $user->getId();
83
            },
84
            $this->getOnlineHumansInRoom($room, $excludePlayerIds)
85
        );
86
    }
87
88
    /**
89
     * Запрос всех онлайн игроков в комнате
90
     * @param Room      $room
91
     * @param int|array $excludePlayerIds
92
     * @return Human[]
93
     */
94
    public function getOnlineHumansInRoom(Room $room, $excludePlayerIds = []): array
95
    {
96
        if (!is_array($excludePlayerIds)) {
97
            $excludePlayerIds = [$excludePlayerIds];
98
        }
99
100
        return $this->humanRepository->findOnlineByRoom($room, $this->getOnlineUsersIds(), $excludePlayerIds);
101
    }
102
103
    /**
104
     * Запрос id всех игроков онлайн из redis
105
     * @return int[]
106
     */
107
    public function getOnlineUsersIds(): array
108
    {
109
        return $this->redis->smembers(RedisClientInterface::ONLINE_LIST);
110
    }
111
112
    /**
113
     * @param array $userIds
114
     * @return array
115
     */
116
    public function getSessionsByUserIds(array $userIds): array
117
    {
118
        return array_values($this->redis->hmget(RedisClientInterface::ID_SESSION_HASH, $userIds));
119
    }
120
121
    /**
122
     * Передать один или несколько предметов другому персонажу
123
     * @param User $userFrom
124
     * @param User $userTo
125
     * @param Item|Item[] $items
126
     * @param int  $quantityToGive Сколько предметов передать
127
     * @return bool
128
     * @throws \Exception
129
     */
130
    public function giveItems(User $userFrom, User $userTo, $items, int $quantityToGive = 1): bool
131
    {
132
        $items = $this->prepareItemsArray($items);
133
134
        try {
135
            $this->dropItems($userFrom, $items, $quantityToGive);
136
        } catch (\Exception $exception) {
137
            if ($exception instanceof ItemNotFound || $exception instanceof NotEnoughItems) {
138
                return false;
139
            } else {
140
                throw $exception;
141
            }
142
        }
143
144
        $this->takeItems($userTo, $items, $quantityToGive);
145
146
        $this->logGivenItems($userFrom, $userTo, $items, $quantityToGive);
147
148
        return true;
149
    }
150
151
    /**
152
     * Выбросить один или несколько предметов
153
     * @param User        $user
154
     * @param Item|Item[] $items
155
     * @param int         $quantityToDrop Сколько предметов выбросить
156
     * @return bool
157
     * @throws ItemNotFound
158
     * @throws NotEnoughItems
159
     */
160
    public function dropItems(User $user, $items, int $quantityToDrop): bool
161
    {
162
        $items = $this->prepareItemsArray($items);
163
164
        $inventoryItems = $this->inventoryItemRepository->findByUser($user);
165
        $inventoryItemCollection = new ArrayCollection($inventoryItems);
166
167
        foreach ($items as $item) {
168
            $criteria = Criteria::create();
169
            $criteria->where(Criteria::expr()->eq('item', $item));
170
171
            $collectedInventoryItem = $inventoryItemCollection->matching($criteria);
172
173
            if ($collectedInventoryItem->isEmpty()) {
174
                throw new ItemNotFound;
175
            }
176
177
            $inventoryItem = $collectedInventoryItem->first();
178
            $itemQuantity = $inventoryItem->getQuantity();
179
            $itemQuantityAfterDrop = $itemQuantity - $quantityToDrop;
180
181
            if ($itemQuantityAfterDrop == 0) {
182
                $this->inventoryItemRepository->remove($inventoryItem);
183
            } elseif ($itemQuantityAfterDrop > 0) {
184
                $inventoryItem->setQuantity($itemQuantityAfterDrop);
185
            } else {
186
                throw new NotEnoughItems;
187
            }
188
        }
189
190
        $this->inventoryItemRepository->flush();
191
192
        $this->logDroppedItems($user, $items, $quantityToDrop);
193
194
        return true;
195
    }
196
197
    /**
198
     * Взять один или несколько предметов
199
     * @param User $user
200
     * @param Item|Item[] $items
201
     * @param int  $quantityToTake Сколько предметов взять
202
     */
203
    public function takeItems(User $user, $items, int $quantityToTake = 1)
204
    {
205
        $itemsToTake = $this->prepareItemsArray($items);
206
207
        $inventoryItems = $this->inventoryItemRepository->findByUser($user);
208
        $inventoryItemCollection = new ArrayCollection($inventoryItems);
209
210
        foreach ($itemsToTake as $itemToTake) {
211
            $criteria = Criteria::create();
212
            $criteria->where(Criteria::expr()->eq('item', $itemToTake));
213
214
            $collectedInventoryItem = $inventoryItemCollection->matching($criteria);
215
216
            if ($collectedInventoryItem->count() === 1) {
217
                $inventoryItem = $collectedInventoryItem->first();
218
                $quantity = $inventoryItem->getQuantity() + $quantityToTake;
219
                $inventoryItem->setQuantity($quantity);
220
            } elseif ($collectedInventoryItem->count() === 0) {
221
                $inventoryItem = new InventoryItem($user, $itemToTake, $quantityToTake);
222
                $this->inventoryItemRepository->persist($inventoryItem);
223
            } else {
224
                throw new \RuntimeException('Найдено более одного предмета');
225
            }
226
        }
227
228
        $this->inventoryItemRepository->flush();
229
230
        $this->logObtainedItems(
231
            $user,
232
            $itemsToTake,
233
            $quantityToTake
234
        );
235
    }
236
237
    /**
238
     * Установка рэндомного аватара
239
     * @return string
240
     */
241
    public function pickAvatar(): string
242
    {
243
        $finder = new Finder();
244
245
        $prefix = 'male';
246
        $avatarPath = $this->kernel->getRootDir() . '/../web/img/avatars/' . $prefix;
247
248
        $files = $finder->files()->in($avatarPath);
249
250
        $avatars = [];
251
        /** @var SplFileInfo $file */
252
        foreach ($files as $file) {
253
            $avatars[] = $file->getBasename('.jpg');
254
        }
255
256
        $avatar = $prefix . '/' . $avatars[array_rand($avatars)];
257
258
        return $avatar;
259
    }
260
261
    /**
262
     * Транслитерация и конвертация строки, удаление цифр
263
     * @param string $string
264
     * @return string
265
     */
266
    public function transliterate(string $string): string
267
    {
268
        $englishLetters = implode('', array_keys($this->getAlphabet()));
269
        $cyrillicLetters = 'абвгдеёжзиклмнопрстуфхцчшщьыъэюяАБВГДЕЁЖЗИКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ';
270
        $pattern = '[^' . preg_quote($englishLetters . $cyrillicLetters, '/') . ']';
271
272
        $stringWithoutSpecialChars = mb_ereg_replace($pattern, '', $string);
273
274
        $cyrillicString = mb_convert_case(
275
            strtr($stringWithoutSpecialChars, $this->getAlphabet()),
276
            MB_CASE_TITLE,
277
            'UTF-8'
278
        );
279
280
        return $cyrillicString;
281
    }
282
283
    /**
284
     * Массив соответствия русских букв латинским
285
     * @return string[]
286
     */
287
    private function getAlphabet(): array
288
    {
289
        return [
290
            'a' => 'а', 'b' => 'б', 'c' => 'ц', 'd' => 'д', 'e' => 'е',
291
            'f' => 'ф', 'g' => 'г', 'h' => 'х', 'i' => 'ай', 'j' => 'дж',
292
            'k' => 'к', 'l' => 'л', 'm' => 'м', 'n' => 'н', 'o' => 'о',
293
            'p' => 'п', 'q' => 'к', 'r' => 'р', 's' => 'с', 't' => 'т',
294
            'u' => 'у', 'v' => 'в', 'w' => 'в', 'x' => 'кс', 'y' => 'й',
295
            'z' => 'з', 'A' => 'А', 'B' => 'Б', 'C' => 'Ц', 'D' => 'Д',
296
            'E' => 'Е', 'F' => 'Ф', 'G' => 'Г', 'H' => 'Х', 'I' => 'Ай',
297
            'J' => 'Дж', 'K' => 'К', 'L' => 'Л', 'M' => 'М', 'N' => 'Н',
298
            'O' => 'О', 'P' => 'П', 'Q' => 'К', 'R' => 'Р', 'S' => 'С',
299
            'T' => 'Т', 'U' => 'Ю', 'V' => 'В', 'W' => 'В', 'X' => 'Кс',
300
            'Y' => 'Й', 'Z' => 'З',
301
        ];
302
    }
303
304
    /**
305
     * Стартовая комната при создании персонажа
306
     * @return Room
307
     * @throws RoomNotFound
308
     */
309
    public function getStartRoom(): Room
310
    {
311
        $startRoom = $this->roomRepository->findOneByXandY(0, 0);
312
313
        if (!$startRoom) {
314
            throw new RoomNotFound();
315
        }
316
317
        return $startRoom;
318
    }
319
320
    /**
321
     * Стартовые предметы при создании персонажа
322
     * @param User $user
323
     */
324
    public function giveStarterItems(User $user)
325
    {
326
        $starterItemsIds = [
327
            'newbie-boots',
328
            'newbie-legs',
329
            'newbie-shirt',
330
            'tester-sword',
331
        ];
332
333
        $items = $this->itemRepository->findSeveralByIds($starterItemsIds);
334
335
        $this->takeItems($user, $items);
336
    }
337
338
    /**
339
     * Подготовка массива предметов
340
     * @param Item|Item[] $items
341
     * @return Item[]
342
     * @throws NotEnoughItems
343
     */
344
    private function prepareItemsArray($items): array
345
    {
346
        $preparedItems = [];
347
        if (is_array($items) && current($items) instanceof Item) {
348
            $preparedItems = $items;
349
        } elseif ($items instanceof Item) {
350
            $preparedItems[] = $items;
351
        } else {
352
            throw new \RuntimeException('$items must be Item or array of Items entity');
353
        }
354
355
        if (empty($preparedItems)) {
356
            throw new NotEnoughItems('Не передано ни одного предмета для действия');
357
        }
358
359
        return $preparedItems;
360
    }
361
362
    /**
363
     * @param User   $user
364
     * @param Item[] $itemsToTake
365
     * @param int    $quantityToTake
366
     */
367
    private function logObtainedItems(User $user, array $itemsToTake, int $quantityToTake)
368
    {
369
        /** @var Item $item */
370
        foreach ($itemsToTake as $item) {
371
            $this->logger->info(
372
                sprintf(
373
                    '[%d]%s взял предмет: [%d]%s x %d шт.',
374
                    $user->getId(),
375
                    $user->getName(),
376
                    $item->getId(),
377
                    $item->getName(),
378
                    $quantityToTake
379
                )
380
            );
381
        }
382
    }
383
384
    /**
385
     * @param User   $userFrom
386
     * @param User   $userTo
387
     * @param Item[] $items
388
     * @param int    $quantityToGive
389
     */
390
    private function logGivenItems(User $userFrom, User $userTo, array $items, int $quantityToGive)
391
    {
392
        /** @var Item $item */
393
        foreach ($items as $item) {
394
            $this->logger->info(
395
                sprintf(
396
                    '[%d]%s передал [%d]%s предмет: [%d]%s x %d шт.',
397
                    $userFrom->getId(),
398
                    $userFrom->getName(),
399
                    $userTo->getId(),
400
                    $userTo->getName(),
401
                    $item->getId(),
402
                    $item->getName(),
403
                    $quantityToGive
404
                )
405
            );
406
        }
407
    }
408
409
    /**
410
     * @param User   $user
411
     * @param Item[] $items
412
     * @param int    $quantityToDrop
413
     */
414
    private function logDroppedItems($user, array $items, int $quantityToDrop)
415
    {
416
        foreach ($items as $item) {
417
            $this->logger->info(
418
                sprintf(
419
                    '[%d]%s выбросил предмет: [%d]%s x %d шт.',
420
                    $user->getId(),
421
                    $user->getName(),
422
                    $item->getId(),
423
                    $item->getName(),
424
                    $quantityToDrop
425
                )
426
            );
427
        }
428
    }
429
430
    /**
431
     * Назначение вейтстейта юзеру
432
     * @param int $waitState
433
     * @return bool
434
     */
435
    public function addWaitstate(User $user, int $waitState): bool {
436
        $user->addWaitstate($waitState);
437
        $this->getEntityManager()->flush($user);
438
439
        return true;
440
    }
441
442
    /**
443
     * @return EntityManager
444
     */
445
    private function getEntityManager(): EntityManager {
446
        return $this->humanRepository->getEntityManager();
447
    }
448
449
    /**
450
     * @param User $user
451
     */
452
    public function dropWaitState(User $user)
453
    {
454
        $user->dropWaitState();
455
        $this->getEntityManager()->flush($user);
456
    }
457
}
458