Completed
Push — master ( 663f01...49bfb0 )
by Petr
02:49
created

UserService::prepareItemsArray()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 8.8571
cc 5
eloc 11
nc 5
nop 1
1
<?php
2
3
namespace Rottenwood\KingdomBundle\Service;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\Common\Collections\Criteria;
7
use Monolog\Logger;
8
use Rottenwood\KingdomBundle\Entity\Human;
9
use Rottenwood\KingdomBundle\Entity\Infrastructure\InventoryItemRepository;
10
use Rottenwood\KingdomBundle\Entity\Infrastructure\Item;
11
use Rottenwood\KingdomBundle\Entity\Infrastructure\ItemRepository;
12
use Rottenwood\KingdomBundle\Entity\Infrastructure\RoomRepository;
13
use Rottenwood\KingdomBundle\Entity\InventoryItem;
14
use Rottenwood\KingdomBundle\Entity\Room;
15
use Rottenwood\KingdomBundle\Entity\Infrastructure\User;
16
use Rottenwood\KingdomBundle\Entity\Infrastructure\HumanRepository;
17
use Rottenwood\KingdomBundle\Exception\ItemNotFound;
18
use Rottenwood\KingdomBundle\Exception\NotEnoughItems;
19
use Rottenwood\KingdomBundle\Exception\RoomNotFound;
20
use Rottenwood\KingdomBundle\Redis\RedisClientInterface;
21
use Snc\RedisBundle\Client\Phpredis\Client;
22
use Symfony\Component\Finder\Finder;
23
use Symfony\Component\Finder\SplFileInfo;
24
use Symfony\Component\HttpKernel\KernelInterface;
25
26
class UserService
27
{
28
29
    /** @var KernelInterface */
30
    private $kernel;
31
    /** @var \Redis */
32
    private $redis;
33
    /** @var HumanRepository */
34
    private $humanRepository;
35
    /** @var InventoryItemRepository */
36
    private $inventoryItemRepository;
37
    /** @var Logger */
38
    private $logger;
39
    /** @var RoomRepository */
40
    private $roomRepository;
41
    /** @var ItemRepository */
42
    private $itemRepository;
43
44
    /**
45
     * @param KernelInterface         $kernel
46
     * @param Client                  $redis
47
     * @param Logger                  $logger
48
     * @param HumanRepository          $humanRepository
49
     * @param InventoryItemRepository $inventoryItemRepository
50
     * @param RoomRepository          $roomRepository
51
     * @param ItemRepository          $itemRepository
52
     */
53
    public function __construct(
54
        KernelInterface $kernel,
55
        Client $redis,
56
        Logger $logger,
57
        HumanRepository $humanRepository,
58
        InventoryItemRepository $inventoryItemRepository,
59
        RoomRepository $roomRepository,
60
        ItemRepository $itemRepository
61
    ) {
62
        $this->redis = $redis;
0 ignored issues
show
Documentation Bug introduced by
It seems like $redis of type object<Snc\RedisBundle\Client\Phpredis\Client> is incompatible with the declared type object<Redis> of property $redis.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

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