Lines 96.29% 26 / 27
Functions and Methods 75.00% 3 / 4
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
ClassChangedEvent 96.29% 26 / 27 75.00% 3 / 4 11 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 from 100.00% 21 / 21 100.00% 1 / 1 5
 category 100.00% 4 / 4 100.00% 1 / 1 4
 summary 0.00% 0 / 1 0.00% 0 / 1 2
1<?php
2
3declare(strict_types=1);
4
5namespace Lorekeeper\Server\Event\CharacterClass;
6
7use Lorekeeper\Core\CharacterClass\ClassCategory;
8use Lorekeeper\Core\CharacterClass\ClassDescription;
9use Lorekeeper\Core\CharacterClass\ClassName;
10use Lorekeeper\Core\Progression\ClassPoints;
11use Lorekeeper\Server\Event\Event;
12use Lorekeeper\Server\Event\NamedAfterClass;
13use Lorekeeper\Server\Exception\MalformedEventException;
14use Lorekeeper\Server\Message\JsonMessage;
15use Lorekeeper\Server\Message\Message;
16use Lorekeeper\Server\SocketId;
17use Override;
18use Psl\Option;
19use Psl\Type;
20use Psl\Type\Exception\CoercionException;
21
22final readonly class ClassChangedEvent implements Event
23{
24    use NamedAfterClass;
25
26    /** @param Option\Option<ClassDescription> $description */
27    public function __construct(
28        public SocketId $player,
29        public ClassName $className,
30        public ClassCategory $category,
31        public ClassPoints $points,
32        public Option\Option $description,
33    ) {}
34
35    /** @return Option\Option<static> */
36    #[Override]
37    public static function from(Message $message): Option\Option
38    {
39        if (!$message instanceof JsonMessage || ($message->data['cmd'] ?? null) !== 'updateClass') {
40            return Option\none();
41        }
42
43        try {
44            $data = Type\shape([
45                'uid' => Type\positive_int(),
46                'sClassName' => Type\non_empty_string(),
47                'sClassCat' => Type\string(),
48                'iCP' => Type\uint(),
49                'sDesc' => Type\optional(Type\string()),
50            ], allowUnknownFields: true)->coerce($message->data);
51        } catch (CoercionException $exception) {
52            throw new MalformedEventException(static::class, 'class is missing or invalid', $exception);
53        }
54
55        $description = trim($data['sDesc'] ?? '');
56
57        return Option\some(
58            new static(
59                new SocketId($data['uid']),
60                new ClassName($data['sClassName']),
61                self::category($data['sClassCat']),
62                new ClassPoints($data['iCP']),
63                $description === '' ? Option\none() : Option\some(new ClassDescription($description)),
64            ),
65        );
66    }
67
68    /** @throws MalformedEventException */
69    private static function category(string $code): ClassCategory
70    {
71        return match (substr($code, offset: 0, length: 1)) {
72            'M' => ClassCategory::Melee,
73            'C' => ClassCategory::Caster,
74            default => throw new MalformedEventException(static::class, "unknown class category '{$code}'"),
75        };
76    }
77
78    #[Override]
79    public function summary(): string
80    {
81        return "Socket {$this->player->value} is {$this->className->value} ({$this->category->name}, {$this->points->value} CP)";
82    }
83}