Lines
99.13%
114 / 115
Methods
88.88%
8 / 9
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| mentions | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| from | 100.00% | 35 / 35 | 100.00% | 1 / 1 | 4 | ||
| item | 100.00% | 25 / 25 | 100.00% | 1 / 1 | 7 | ||
| materials | 100.00% | 8 / 8 | 100.00% | 1 / 1 | 3 | ||
| 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 | ||
| [Lorekeeper\Server\Event\NamedAfterClass] name | 100.00% | 6 / 6 | 100.00% | 1 / 1 | 1 | ||
| 37 | final readonly class ShopLoadedEvent implements Event, Mentions | |
| 38 | { | |
| 39 | use NamedAfterClass; | |
| 40 | ||
| 41 | /** | |
| 42 | * @param list<ShopLoadedItem> $items | |
| 43 | * @param list<Mention> $mentions | |
| 44 | */ | |
| 45 | public function __construct( | |
| 46 | public ShopId $id, | |
| 47 | public ShopName $name, | |
| 48 | public bool $memberOnly, | |
| 49 | public array $items, | |
| 50 | private array $mentions, | |
| 51 | ) {} | |
| 52 | ||
| 53 | /** @return list<Mention> */ | |
| 54 | #[Override] | |
| 55 | public function mentions(): array | |
| 56 | { | |
| 57 | return $this->mentions; | |
| 58 | } | |
| 59 | ||
| 60 | /** @return Option\Option<static> */ | |
| 61 | #[Override] | |
| 62 | public static function from(Message $message): Option\Option | |
| 63 | { | |
| 64 | if (!$message instanceof JsonMessage || ($message->data['cmd'] ?? null) !== 'loadShop') { | |
| 65 | return Option\none(); | |
| 66 | } | |
| 67 | ||
| 68 | try { | |
| 69 | $shop = Coerce\shape([ | |
| 70 | 'ShopID' => Coerce\positive_int(), | |
| 71 | 'sName' => Coerce\non_empty_string(), | |
| 72 | 'bUpgrd' => Coerce\optional(Coerce\bool()), | |
| 73 | 'items' => Coerce\vec(Coerce\shape([ | |
| 74 | 'ItemID' => Coerce\positive_int(), | |
| 75 | 'sName' => Coerce\non_empty_string(), | |
| 76 | 'sType' => Coerce\string(), | |
| 77 | 'sES' => Coerce\string(), | |
| 78 | 'sDesc' => Coerce\optional(Coerce\string()), | |
| 79 | 'iCost' => Coerce\optional(Coerce\uint()), | |
| 80 | 'bCoins' => Coerce\optional(Coerce\bool()), | |
| 81 | 'sFile' => Coerce\optional(Coerce\nullable(Coerce\string())), | |
| 82 | 'sLink' => Coerce\optional(Coerce\string()), | |
| 83 | 'bUpg' => Coerce\optional(Coerce\bool()), | |
| 84 | 'turnin' => Coerce\optional(Coerce\vec(Coerce\shape([ | |
| 85 | 'ItemID' => Coerce\positive_int(), | |
| 86 | 'sName' => Coerce\non_empty_string(), | |
| 87 | 'iQty' => Coerce\positive_int(), | |
| 88 | ], allowUnknownFields: true))), | |
| 89 | ], allowUnknownFields: true)), | |
| 90 | ], allowUnknownFields: true)->coerce($message->data['shopinfo'] ?? null); | |
| 91 | } catch (CoercionException $exception) { | |
| 92 | throw new MalformedEventException(static::class, 'shopinfo is missing or invalid', $exception); | |
| 93 | } | |
| 94 | ||
| 95 | return Option\some( | |
| 96 | new static( | |
| 97 | new ShopId($shop['ShopID']), | |
| 98 | new ShopName($shop['sName']), | |
| 99 | $shop['bUpgrd'] ?? false, | |
| 100 | array_map(self::item(...), $shop['items']), | |
| 101 | self::materials($shop['items']), | |
| 102 | ), | |
| 103 | ); | |
| 104 | } | |
| 105 | ||
| 106 | /** @param Item $item */ | |
| 107 | private static function item(array $item): ShopLoadedItem | |
| 108 | { | |
| 109 | $description = trim($item['sDesc'] ?? ''); | |
| 110 | $cost = $item['iCost'] ?? 0; | |
| 111 | $file = $item['sFile'] ?? ''; | |
| 112 | $link = $item['sLink'] ?? ''; | |
| 113 | $hasFile = str_ends_with($file, '.swf'); | |
| 114 | ||
| 115 | return new ShopLoadedItem( | |
| 116 | new ItemId($item['ItemID']), | |
| 117 | new ItemName($item['sName']), | |
| 118 | self::detail($item['sType']), | |
| 119 | $description === '' ? Option\none() : Option\some(new ItemDescription($description)), | |
| 120 | $cost === 0 | |
| 121 | ? Option\none() | |
| 122 | : Option\some(new ItemPrice($item['bCoins'] ?? false ? new AdventureCoins($cost) : new Gold($cost))), | |
| 123 | $hasFile ? Option\some(new Gamefile($file)) : Option\none(), | |
| 124 | $hasFile && $link !== '' ? Option\some(new Linkage($link)) : Option\none(), | |
| 125 | self::slot($item['sES']), | |
| 126 | $item['bUpg'] ?? false, | |
| 127 | array_map( | |
| 128 | static fn(array $material): MergeCost => new MergeCost( | |
| 129 | new ItemId($material['ItemID']), | |
| 130 | $material['iQty'], | |
| 131 | ), | |
| 132 | $item['turnin'] ?? [], | |
| 133 | ), | |
| 134 | ); | |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * @param list<Item> $items | |
| 139 | * | |
| 140 | * @return list<Mention<ItemId, ItemName>> | |
| 141 | */ | |
| 142 | private static function materials(array $items): array | |
| 143 | { | |
| 144 | $mentions = []; | |
| 145 | foreach ($items as $item) { | |
| 146 | foreach ($item['turnin'] ?? [] as $material) { | |
| 147 | $mentions[$material['ItemID']] ??= new Mention( | |
| 148 | new ItemId($material['ItemID']), | |
| 149 | new ItemName($material['sName']), | |
| 150 | ); | |
| 151 | } | |
| 152 | } | |
| 153 | ||
| 154 | return array_values($mentions); | |
| 155 | } | |
| 156 | ||
| 157 | /** @throws MalformedEventException */ | |
| 158 | private static function detail(string $type): Type\Equipment|Type\Weapon|Type\House|Type\Misc | |
| 159 | { | |
| 160 | return match ($type) { | |
| 161 | 'Armor' => Type\Equipment::Armor, | |
| 162 | 'Cape' => Type\Equipment::Cape, | |
| 163 | 'Class' => Type\Equipment::CharacterClass, | |
| 164 | 'Misc' => Type\Equipment::Ground, | |
| 165 | 'Helm' => Type\Equipment::Helm, | |
| 166 | 'Necklace' => Type\Equipment::Necklace, | |
| 167 | 'Pet' => Type\Equipment::Pet, | |
| 168 | 'Axe' => Type\Weapon::Axe, | |
| 169 | 'Bow' => Type\Weapon::Bow, | |
| 170 | 'Dagger' => Type\Weapon::Dagger, | |
| 171 | 'Gauntlet' => Type\Weapon::Gauntlet, | |
| 172 | 'Gun' => Type\Weapon::Gun, | |
| 173 | 'HandGun' => Type\Weapon::HandGun, | |
| 174 | 'Mace' => Type\Weapon::Mace, | |
| 175 | 'Polearm' => Type\Weapon::Polearm, | |
| 176 | 'Rifle' => Type\Weapon::Rifle, | |
| 177 | 'Staff' => Type\Weapon::Staff, | |
| 178 | 'Sword' => Type\Weapon::Sword, | |
| 179 | 'Wand' => Type\Weapon::Wand, | |
| 180 | 'Whip' => Type\Weapon::Whip, | |
| 181 | 'Floor Item' => Type\House::Floor, | |
| 182 | 'House' => Type\House::House, | |
| 183 | 'Wall Item' => Type\House::Wall, | |
| 184 | 'ServerUse' => Type\Misc::Boost, | |
| 185 | 'Enhancement' => Type\Misc::Enhancement, | |
| 186 | 'Item', 'Note', 'Quest Item', 'Resource' => Type\Misc::Generic, | |
| 187 | default => throw new MalformedEventException(static::class, "unknown item type '{$type}'"), | |
| 188 | }; | |
| 189 | } | |
| 190 | ||
| 191 | /** | |
| 192 | * @throws MalformedEventException | |
| 193 | * | |
| 194 | * @return Option\Option<EquipmentSlot> | |
| 195 | */ | |
| 196 | private static function slot(string $code): Option\Option | |
| 197 | { | |
| 198 | return match ($code) { | |
| 199 | 'None' => Option\none(), | |
| 200 | 'Weapon' => Option\some(EquipmentSlot::Weapon), | |
| 201 | 'ar', 'co' => Option\some(EquipmentSlot::Armor), | |
| 202 | 'he' => Option\some(EquipmentSlot::Helm), | |
| 203 | 'ba' => Option\some(EquipmentSlot::Cape), | |
| 204 | 'pe' => Option\some(EquipmentSlot::Pet), | |
| 205 | 'mi' => Option\some(EquipmentSlot::Ground), | |
| 206 | 'am' => Option\some(EquipmentSlot::Necklace), | |
| 207 | default => throw new MalformedEventException(static::class, "unknown equipment slot '{$code}'"), | |
| 208 | }; | |
| 209 | } | |
| 210 | ||
| 211 | #[Override] | |
| 212 | public function summary(): string | |
| 213 | { | |
| 214 | return "{$this->name->value}: " . count($this->items) . ' items'; | |
| 215 | } | |
| 216 | } |
From Lorekeeper\Server\Event\NamedAfterClass
| 7 | trait NamedAfterClass | |
| 8 | { | |
| 9 | public function name(): string | |
| 10 | { | |
| 11 | $class = static::class; | |
| 12 | ||
| 13 | return (string) preg_replace( | |
| 14 | '/Event$/', | |
| 15 | replacement: '', | |
| 16 | subject: substr($class, (int) strrpos($class, needle: '\\') + 1), | |
| 17 | ); | |
| 18 | } | |
| 19 | } |