Lines 98.00% 49 / 50
Functions and Methods 94.11% 16 / 17
Classes and Traits 0.00% 0 / 1
Name Lines Functions and Methods CRAP Classes and Traits
Character 98.00% 49 / 50 94.11% 16 / 17 32 0.00% 0 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 name 100.00% 1 / 1 100.00% 1 / 1 1
 socket 100.00% 1 / 1 100.00% 1 / 1 1
 position 100.00% 1 / 1 100.00% 1 / 1 1
 level 100.00% 1 / 1 100.00% 1 / 1 1
 skills 100.00% 1 / 1 100.00% 1 / 1 1
 mana 100.00% 1 / 1 100.00% 1 / 1 1
 alive 100.00% 1 / 1 100.00% 1 / 1 1
 map 100.00% 1 / 1 100.00% 1 / 1 1
 entered 100.00% 3 / 3 100.00% 1 / 1 1
 hear 100.00% 5 / 5 100.00% 1 / 1 2
 learn 100.00% 13 / 13 100.00% 1 / 1 11
 state 100.00% 9 / 9 100.00% 1 / 1 1
 arrived 100.00% 2 / 2 100.00% 1 / 1 1
 seeLevel 100.00% 4 / 4 100.00% 1 / 1 3
 seeMana 75.00% 3 / 4 0.00% 0 / 1 3.14
 is 100.00% 1 / 1 100.00% 1 / 1 1
1<?php
2
3declare(strict_types=1);
4
5namespace Lorekeeper\Client;
6
7use LogicException;
8use Lorekeeper\Client\Bot\MapLayout;
9use Lorekeeper\Client\Bot\Position;
10use Lorekeeper\Client\Script\Game\InGame;
11use Lorekeeper\Core\Attribute\Mana;
12use Lorekeeper\Core\CharacterClass\Skill\Skill;
13use Lorekeeper\Core\Player\PlayerLevel;
14use Lorekeeper\Core\Player\PlayerName;
15use Lorekeeper\Server\Event\Combat\PlayerDiedEvent;
16use Lorekeeper\Server\Event\Combat\PlayersManaChangedEvent;
17use Lorekeeper\Server\Event\Combat\ResurrectedEvent;
18use Lorekeeper\Server\Event\Combat\SkillsLoadedEvent;
19use Lorekeeper\Server\Event\Event;
20use Lorekeeper\Server\Event\Map\MonstersDescribedEvent;
21use Lorekeeper\Server\Event\Map\MonstersPlacedEvent;
22use Lorekeeper\Server\Event\Map\MovedToMapEvent;
23use Lorekeeper\Server\Event\Map\PlayersSeenEvent;
24use Lorekeeper\Server\Event\Progression\LeveledUpEvent;
25use Lorekeeper\Server\SocketId;
26use Psl\Observer\SubjectInterface;
27
28/** @mago-expect lint:too-many-methods */
29final class Character implements SubjectInterface
30{
31    use Observable;
32
33    private ?SocketId $socket = null;
34
35    private ?Position $position = null;
36
37    private ?PlayerLevel $level = null;
38
39    /** @var list<Skill> */
40    private array $skills = [];
41
42    private ?Mana $mana = null;
43
44    private bool $alive = true;
45
46    private MapLayout $map;
47
48    public function __construct(
49        private readonly PlayerName $name,
50    ) {
51        $this->map = new MapLayout();
52    }
53
54    public function name(): PlayerName
55    {
56        return $this->name;
57    }
58
59    /** @throws LogicException */
60    public function socket(): SocketId
61    {
62        return $this->socket ?? throw new LogicException("{$this->name->value} has not entered the game yet.");
63    }
64
65    /** @throws LogicException */
66    public function position(): Position
67    {
68        return $this->position ?? throw new LogicException("{$this->name->value} is in no map yet.");
69    }
70
71    /** @throws LogicException */
72    public function level(): PlayerLevel
73    {
74        return $this->level ?? throw new LogicException("The level of {$this->name->value} is not known yet.");
75    }
76
77    /** @return list<Skill> */
78    public function skills(): array
79    {
80        return $this->skills;
81    }
82
83    public function mana(): ?Mana
84    {
85        return $this->mana;
86    }
87
88    public function alive(): bool
89    {
90        return $this->alive;
91    }
92
93    public function map(): MapLayout
94    {
95        return $this->map;
96    }
97
98    public function entered(InGame $game): void
99    {
100        $this->socket = $game->socketId;
101        $this->position = new Position($game->map->map, $game->map->areaId);
102        $this->notify();
103    }
104
105    public function hear(Event $event): void
106    {
107        $before = $this->state();
108        $this->learn($event);
109
110        if ($this->state() === $before) {
111            return;
112        }
113
114        $this->notify();
115    }
116
117    private function learn(Event $event): void
118    {
119        match (true) {
120            $event instanceof MovedToMapEvent => $this->arrived($event),
121            $event instanceof MonstersDescribedEvent => $this->map = $this->map->described($event->monsters),
122            $event instanceof MonstersPlacedEvent => $this->map = $this->map->placed($event->placements),
123            $event instanceof PlayersSeenEvent => $this->seeLevel($event),
124            $event instanceof LeveledUpEvent => $this->level = $event->level,
125            $event instanceof SkillsLoadedEvent => $this->skills = array_values(array_filter(
126                $event->skills,
127                static fn(Skill $skill): bool => in_array($skill->slot, $event->usable, strict: true),
128            )),
129            $event instanceof PlayersManaChangedEvent => $this->seeMana($event),
130            $event instanceof PlayerDiedEvent => $this->alive = false,
131            $event instanceof ResurrectedEvent => $this->alive = true,
132            default => null,
133        };
134    }
135
136    /** @return list<mixed> */
137    private function state(): array
138    {
139        return [
140            $this->position?->map->value,
141            $this->position?->area->value,
142            $this->level?->value,
143            $this->mana?->value,
144            $this->alive,
145            array_map(static fn(Skill $skill): string => $skill->slot->name, $this->skills),
146            $this->map,
147        ];
148    }
149
150    private function arrived(MovedToMapEvent $event): void
151    {
152        $this->position = new Position($event->map, $event->areaId);
153        $this->map = new MapLayout();
154    }
155
156    private function seeLevel(PlayersSeenEvent $event): void
157    {
158        foreach ($event->players as $player) {
159            if (!$this->is($player->name)) {
160                continue;
161            }
162
163            $this->level = $player->level;
164        }
165    }
166
167    private function seeMana(PlayersManaChangedEvent $event): void
168    {
169        foreach ($event->players as $player) {
170            if (!$this->is($player->player)) {
171                continue;
172            }
173
174            $this->mana = $player->mana;
175        }
176    }
177
178    private function is(PlayerName $name): bool
179    {
180        return strcasecmp($name->value, $this->name->value) === 0;
181    }
182}