Completed
Push — master ( 2f7ee5...bb528d )
by Timur
02:04
created

AchievementsManager::checkCompletedAchievements()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 3
nop 1
1
<?php
2
3
namespace Zurbaev\Achievements;
4
5
use Zurbaev\Achievements\Contracts\AchievementsStorageInterface;
6
7
class AchievementsManager
8
{
9
    /**
10
     * @var AchievementsStorageInterface
11
     */
12
    protected $storage;
13
14
    /**
15
     * Contains list of achievement IDs that
16
     * should be checked for completeness
17
     * in current criterias update.
18
     *
19
     * @var array
20
     */
21
    protected $achievementsToCheck = [];
22
23
    /**
24
     * Criteria handlers.
25
     *
26
     * @var array
27
     */
28
    protected static $handlers = [];
29
30
    /**
31
     * AchievementsManager constructor.
32
     *
33
     * @param AchievementsStorageInterface $storage
34
     */
35
    public function __construct(AchievementsStorageInterface $storage)
36
    {
37
        $this->storage = $storage;
38
    }
39
40
    /**
41
     * Registers new criteria handler by given type.
42
     *
43
     * @param string   $type
44
     * @param callable $handler
45
     */
46
    public static function registerHandler(string $type, callable $handler)
47
    {
48
        static::$handlers[$type] = $handler;
49
    }
50
51
    /**
52
     * Unregisters previously registered criteria handler by given type.
53
     *
54
     * @param string $type
55
     */
56
    public static function unregisterHandler(string $type)
57
    {
58
        unset(static::$handlers[$type]);
59
    }
60
61
    /**
62
     * Unregisters all previously registered criteria handlers.
63
     */
64
    public static function unregisterAllHandlers()
65
    {
66
        static::$handlers = [];
67
    }
68
69
    /**
70
     * Updates criterias progress by given type & checks
71
     * referred achievements for completeness state.
72
     *
73
     * Returns number of updated criterias.
74
     *
75
     * @param mixed  $owner
76
     * @param string $type
77
     * @param mixed  $data = null
78
     *
79
     * @return int
80
     */
81
    public function updateAchievementCriterias($owner, string $type, $data = null): int
82
    {
83
        $criterias = $this->storage->getOwnerCriteriasByType($owner, $type, $data);
84
85
        if (!count($criterias)) {
86
            return 0;
87
        }
88
89
        $this->achievementsToCheck = [];
90
91
        $achievements = $this->storage->getAchievementsByCriterias($criterias);
92
        $updatedCriteriasCount = 0;
93
94
        foreach ($criterias as $criteria) {
95
            /** @var AchievementCriteria $criteria*/
96
97
            if ($criteria->completed()) {
98
                continue;
99
            }
100
101
            $achievement = $this->storage->getAchievementForCriteria($criteria, $achievements);
102
103
            if (is_null($achievement)) {
104
                continue;
105
            }
106
107
            $change = $this->getCriteriaChange($owner, $criteria, $achievement, $data);
108
109
            if (is_null($change)) {
110
                continue;
111
            }
112
113
            $this->setCriteriaProgress($owner, $criteria, $achievement, $change);
114
            $updatedCriteriasCount++;
115
        }
116
117
        if (count($this->achievementsToCheck) > 0) {
118
            $this->checkCompletedAchievements($owner);
119
        }
120
121
        return $updatedCriteriasCount;
122
    }
123
124
    /**
125
     * Requests new progress value for given owner & criteria.
126
     *
127
     * @param mixed               $owner
128
     * @param AchievementCriteria $criteria
129
     * @param Achievement         $achievement
130
     * @param mixed               $data = null
131
     *
132
     * @return AchievementCriteriaChange|null
133
     */
134
    public function getCriteriaChange($owner, AchievementCriteria $criteria, Achievement $achievement, $data = null)
135
    {
136
        $handler = static::$handlers[$criteria->type()] ?? null;
137
138
        if (!is_callable($handler)) {
139
            return null;
140
        }
141
142
        $result = call_user_func_array($handler, [
143
            $owner, $criteria, $achievement, $data,
144
        ]);
145
146
        if ($result instanceof AchievementCriteriaChange) {
147
            return $result;
148
        } elseif (is_array($result) && isset($result['value'])) {
149
            return new AchievementCriteriaChange(
150
                $result['value'],
151
                $result['progress'] ?? $result['progress_type'] ?? null,
152
                $result['data'] ?? $result['progress_data'] ?? []
153
            );
154
        }
155
156
        return null;
157
    }
158
159
    /**
160
     * Updates criteria progress & saves achievement for completeness check (if eligible for).
161
     *
162
     * @param mixed               $owner
163
     * @param AchievementCriteria $criteria
164
     * @param Achievement         $achievement
165
     * @param AchievementCriteriaChange $change
166
     *
167
     * @return bool
168
     */
169
    protected function setCriteriaProgress($owner, AchievementCriteria $criteria, Achievement $achievement, AchievementCriteriaChange $change)
170
    {
171
        $maxValue = $criteria->maxValue();
172
        $changeValue = $change->value;
173
        $oldValue = null;
174
        $newValue = $changeValue;
175
        $progress = new AchievementCriteriaProgress(0, false);
176
177
        if ($maxValue > 0 && $changeValue > $maxValue) {
178
            $changeValue = $maxValue;
179
        }
180
181
        if ($criteria->hasProgress()) {
182
            $progress = $criteria->progress();
183
            $oldValue = $progress->value;
184
            $newValue = $progress->getNewValue($maxValue, $changeValue, $change->progressType);
185
        }
186
187
        if ($oldValue === $newValue) {
188
            return false;
189
        }
190
191
        $progress->value = $newValue;
192
        $progress->changed = true;
193
        $progress->data = $change->progressData;
194
195
        $this->storage->setCriteriaProgressUpdated($owner, $criteria, $achievement, $progress);
196
197
        if ($this->isCompletedCriteria($criteria, $progress)) {
198
            $this->completedCriteriaFor($achievement);
199
        }
200
201
        return true;
202
    }
203
204
    /**
205
     * Saves achievement to completeness check list.
206
     *
207
     * @param Achievement $achievement
208
     */
209
    protected function completedCriteriaFor(Achievement $achievement)
210
    {
211
        $this->achievementsToCheck[] = $achievement->id();
212
    }
213
214
    /**
215
     * Checks all saved achievements for completeness state.
216
     *
217
     * Returns number of completed achievements.
218
     *
219
     * @param mixed $owner
220
     *
221
     * @return int
222
     */
223
    protected function checkCompletedAchievements($owner): int
224
    {
225
        $achievements = $this->storage->getAchievementsWithProgressFor($owner, $this->achievementsToCheck);
226
        $this->achievementsToCheck = [];
227
228
        if (!count($achievements)) {
229
            return 0;
230
        }
231
232
        $completedAchievements = array_filter($achievements, function (Achievement $achievement) {
233
            return !$achievement->completed() && $this->isCompletedAchievement($achievement);
234
        });
235
236
        if (count($completedAchievements) > 0) {
237
            $this->storage->setAchievementsCompleted($owner, $completedAchievements);
238
        }
239
240
        return count($completedAchievements);
241
    }
242
243
    /**
244
     * Determines if given criteria was completed.
245
     *
246
     * @param AchievementCriteria         $criteria
247
     * @param AchievementCriteriaProgress $progress
248
     *
249
     * @return bool
250
     */
251
    protected function isCompletedCriteria(AchievementCriteria $criteria, AchievementCriteriaProgress $progress): bool
252
    {
253
        $progress->completed = $progress->value >= $criteria->maxValue();
254
255
        return $progress->completed;
256
    }
257
258
    /**
259
     * Determines if given achievement was completed.
260
     *
261
     * @param Achievement $achievement
262
     *
263
     * @return bool
264
     */
265
    protected function isCompletedAchievement(Achievement $achievement): bool
266
    {
267
        $completedCriterias = array_filter($achievement->criterias(), function (AchievementCriteria $criteria) {
268
            return $this->isCompletedCriteria($criteria, $criteria->progress());
269
        });
270
271
        return count($achievement->criterias()) === count($completedCriterias);
272
    }
273
}
274