Lines 98.75% 79 / 80
Functions and Methods 80.00% 4 / 5
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
PlayerInventoryLoadedEvent 98.75% 79 / 80 80.00% 4 / 5 51 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 from 100.00% 40 / 40 100.00% 1 / 1 11
 detail 100.00% 28 / 28 100.00% 1 / 1 28
 slot 100.00% 10 / 10 100.00% 1 / 1 10
 summary 0.00% 0 / 1 0.00% 0 / 1 2
1<?php
2
3declare(strict_types=1);
4
5namespace Lorekeeper\Server\Event\Inventory;
6
7use Lorekeeper\Core\Asset\Gamefile;
8use Lorekeeper\Core\Asset\Linkage;
9use Lorekeeper\Core\Currency\AdventureCoins;
10use Lorekeeper\Core\Currency\Gold;
11use Lorekeeper\Core\Item\EquipmentSlot;
12use Lorekeeper\Core\Item\ItemDescription;
13use Lorekeeper\Core\Item\ItemId;
14use Lorekeeper\Core\Item\ItemName;
15use Lorekeeper\Core\Item\ItemPrice;
16use Lorekeeper\Core\Item\Type;
17use Lorekeeper\Server\Event\Event;
18use Lorekeeper\Server\Event\NamedAfterClass;
19use Lorekeeper\Server\Exception\MalformedEventException;
20use Lorekeeper\Server\Message\JsonMessage;
21use Lorekeeper\Server\Message\Message;
22use Override;
23use Psl\Option;
24use Psl\Type as Coerce;
25use Psl\Type\Exception\CoercionException;
26
27/** @mago-expect lint:cyclomatic-complexity */
28final readonly class PlayerInventoryLoadedEvent implements Event
29{
30    use NamedAfterClass;
31
32    /** @param list<InventoryItem> $items */
33    public function __construct(
34        public array $items,
35    ) {}
36
37    /** @return Option\Option<static> */
38    #[Override]
39    public static function from(Message $message): Option\Option
40    {
41        if (!$message instanceof JsonMessage || ($message->data['cmd'] ?? null) !== 'loadInventoryBig') {
42            return Option\none();
43        }
44
45        try {
46            $items = Coerce\vec(Coerce\shape([
47                'ItemID' => Coerce\positive_int(),
48                'sName' => Coerce\non_empty_string(),
49                'sType' => Coerce\string(),
50                'sES' => Coerce\string(),
51                'sDesc' => Coerce\optional(Coerce\string()),
52                'iCost' => Coerce\optional(Coerce\uint()),
53                'bCoins' => Coerce\optional(Coerce\bool()),
54                'sFile' => Coerce\optional(Coerce\nullable(Coerce\string())),
55                'sLink' => Coerce\optional(Coerce\string()),
56                'bUpg' => Coerce\optional(Coerce\bool()),
57            ], allowUnknownFields: true))
58                ->coerce($message->data['items'] ?? null);
59        } catch (CoercionException $exception) {
60            throw new MalformedEventException(static::class, 'items are missing or invalid', $exception);
61        }
62
63        $inventory = [];
64        foreach ($items as $item) {
65            $description = trim($item['sDesc'] ?? '');
66            $cost = $item['iCost'] ?? 0;
67            $file = $item['sFile'] ?? '';
68            $link = $item['sLink'] ?? '';
69            $hasFile = str_ends_with($file, '.swf');
70
71            $inventory[] = new InventoryItem(
72                new ItemId($item['ItemID']),
73                new ItemName($item['sName']),
74                self::detail($item['sType']),
75                $description === '' ? Option\none() : Option\some(new ItemDescription($description)),
76                $cost === 0
77                    ? Option\none()
78                    : Option\some(
79                        new ItemPrice($item['bCoins'] ?? false ? new AdventureCoins($cost) : new Gold($cost)),
80                    ),
81                $hasFile ? Option\some(new Gamefile($file)) : Option\none(),
82                $hasFile && $link !== '' ? Option\some(new Linkage($link)) : Option\none(),
83                self::slot($item['sES']),
84                $item['bUpg'] ?? false,
85            );
86        }
87
88        return Option\some(new static($inventory));
89    }
90
91    /** @throws MalformedEventException */
92    private static function detail(string $type): Type\Equipment|Type\Weapon|Type\House|Type\Misc
93    {
94        return match ($type) {
95            'Armor' => Type\Equipment::Armor,
96            'Cape' => Type\Equipment::Cape,
97            'Class' => Type\Equipment::CharacterClass,
98            'Misc' => Type\Equipment::Ground,
99            'Helm' => Type\Equipment::Helm,
100            'Necklace' => Type\Equipment::Necklace,
101            'Pet' => Type\Equipment::Pet,
102            'Axe' => Type\Weapon::Axe,
103            'Bow' => Type\Weapon::Bow,
104            'Dagger' => Type\Weapon::Dagger,
105            'Gauntlet' => Type\Weapon::Gauntlet,
106            'Gun' => Type\Weapon::Gun,
107            'HandGun' => Type\Weapon::HandGun,
108            'Mace' => Type\Weapon::Mace,
109            'Polearm' => Type\Weapon::Polearm,
110            'Rifle' => Type\Weapon::Rifle,
111            'Staff' => Type\Weapon::Staff,
112            'Sword' => Type\Weapon::Sword,
113            'Wand' => Type\Weapon::Wand,
114            'Whip' => Type\Weapon::Whip,
115            'Floor Item' => Type\House::Floor,
116            'House' => Type\House::House,
117            'Wall Item' => Type\House::Wall,
118            'ServerUse' => Type\Misc::Boost,
119            'Enhancement' => Type\Misc::Enhancement,
120            'Item', 'Note', 'Quest Item', 'Resource' => Type\Misc::Generic,
121            default => throw new MalformedEventException(static::class, "unknown item type '{$type}'"),
122        };
123    }
124
125    /**
126     * @throws MalformedEventException
127     *
128     * @return Option\Option<EquipmentSlot>
129     */
130    private static function slot(string $code): Option\Option
131    {
132        return match ($code) {
133            'None' => Option\none(),
134            'Weapon' => Option\some(EquipmentSlot::Weapon),
135            'ar', 'co' => Option\some(EquipmentSlot::Armor),
136            'he' => Option\some(EquipmentSlot::Helm),
137            'ba' => Option\some(EquipmentSlot::Cape),
138            'pe' => Option\some(EquipmentSlot::Pet),
139            'mi' => Option\some(EquipmentSlot::Ground),
140            'am' => Option\some(EquipmentSlot::Necklace),
141            default => throw new MalformedEventException(static::class, "unknown equipment slot '{$code}'"),
142        };
143    }
144
145    #[Override]
146    public function summary(): string
147    {
148        return 'Inventory loaded: ' . count($this->items) . ' items';
149    }
150}