Completed
Push — master ( 6fb0e0...4a52e8 )
by Timur
01:18
created

AchievementsManager::updateAchievementCriterias()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 44
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 21
nc 11
nop 3
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);
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
            /**
96
             * @var AchievementCriteria $criteria
97
             */
98
99
            if ($criteria->completed()) {
100
                continue;
101
            }
102
103
            $achievement = $this->storage->getAchievementForCriteria($criteria, $achievements);
104
105
            if (is_null($achievement)) {
106
                continue;
107
            }
108
109
            $change = $this->getCriteriaChange($owner, $criteria, $achievement, $data);
110
111
            if (is_null($change)) {
112
                continue;
113
            }
114
115
            $this->setCriteriaProgress($owner, $criteria, $achievement, $change);
116
            $updatedCriteriasCount++;
117
        }
118
119
        if (count($this->achievementsToCheck) > 0) {
120
            $this->checkCompletedAchievements($owner);
121
        }
122
123
        return $updatedCriteriasCount;
124
    }
125
126
    /**
127
     * Requests new progress value for given owner & criteria.
128
     *
129
     * @param mixed               $owner
130
     * @param AchievementCriteria $criteria
131
     * @param Achievement         $achievement
132
     * @param mixed               $data = null
133
     *
134
     * @return AchievementCriteriaChange|null
135
     */
136
    public function getCriteriaChange($owner, AchievementCriteria $criteria, Achievement $achievement, $data = null)
137
    {
138
        $handler = static::$handlers[$criteria->type()] ?? null;
139
140
        if (!is_callable($handler)) {
141
            return null;
142
        }
143
144
        $result = call_user_func_array($handler, [
145
            $owner, $criteria, $achievement, $data,
146
        ]);
147
148
        if ($result instanceof AchievementCriteriaChange) {
149
            return $result;
150
        } elseif (is_array($result) && isset($result['value'])) {
151
            return new AchievementCriteriaChange($result['value'], $result['progress'] ?? $result['progress_type'] ?? null);
152
        }
153
154
        return null;
155
    }
156
157
    /**
158
     * Updates criteria progress & saves achievement for completeness check (if eligible for).
159
     *
160
     * @param mixed               $owner
161
     * @param AchievementCriteria $criteria
162
     * @param Achievement         $achievement
163
     * @param AchievementCriteriaChange $change
164
     *
165
     * @return bool
166
     */
167
    protected function setCriteriaProgress($owner, AchievementCriteria $criteria, Achievement $achievement, AchievementCriteriaChange $change)
168
    {
169
        $maxValue = $criteria->maxValue();
170
        $changeValue = $change->value;
171
172
        if ($maxValue > 0 && $changeValue > $maxValue) {
173
            $changeValue = $maxValue;
174
        }
175
176
        if (!$criteria->hasProgress()) {
177
            $newValue = $changeValue;
178
            $progress = new AchievementCriteriaProgress(0, false);
179
        } else {
180
            $progress = $criteria->progress();
181
            $oldValue = $progress->value;
182
            $newValue = $progress->getNewValue($maxValue, $changeValue, $change->progressType);
183
184
            if ($oldValue === $newValue) {
185
                return false;
186
            }
187
        }
188
189
        $progress->value = $newValue;
190
        $progress->changed = true;
191
        $progress->data = $change->progressData;
192
193
        $this->storage->setCriteriaProgressUpdated($owner, $criteria, $achievement, $progress);
194
195
        if ($this->isCompletedCriteria($criteria, $progress)) {
196
            $this->completedCriteriaFor($achievement);
197
        }
198
199
        return true;
200
    }
201
202
    /**
203
     * Saves achievement to completeness check list.
204
     *
205
     * @param Achievement $achievement
206
     */
207
    protected function completedCriteriaFor(Achievement $achievement)
208
    {
209
        $this->achievementsToCheck[] = $achievement->id();
210
    }
211
212
    /**
213
     * Checks all saved achievements for completeness state.
214
     *
215
     * Returns number of completed achievements.
216
     *
217
     * @param mixed $owner
218
     *
219
     * @return int
220
     */
221
    protected function checkCompletedAchievements($owner): int
222
    {
223
        $achievements = $this->storage->getAchievementsWithProgressFor($owner, $this->achievementsToCheck);
224
225
        if (!count($achievements)) {
226
            $this->achievementsToCheck = [];
227
228
            return 0;
229
        }
230
231
        $completedAchievements = [];
232
233
        foreach ($achievements as $achievement) {
234
            /**
235
             * @var Achievement $achievement
236
             */
237
238
            if ($achievement->completed()) {
239
                continue;
240
            }
241
242
            if ($this->isCompletedAchievement($achievement)) {
243
                $completedAchievements[] = $achievement;
244
            }
245
        }
246
247
        if (count($completedAchievements) > 0) {
248
            $this->storage->setAchievementsCompleted($owner, $completedAchievements);
249
        }
250
251
        $this->achievementsToCheck = [];
252
253
        return count($completedAchievements);
254
    }
255
256
    /**
257
     * Determines if given criteria was completed.
258
     *
259
     * @param AchievementCriteria         $criteria
260
     * @param AchievementCriteriaProgress $progress
261
     *
262
     * @return bool
263
     */
264
    protected function isCompletedCriteria(AchievementCriteria $criteria, AchievementCriteriaProgress $progress): bool
265
    {
266
        $progress->completed = false;
267
268
        if ($progress->value >= $criteria->maxValue()) {
269
            $progress->completed = true;
270
        }
271
272
        return $progress->completed;
273
    }
274
275
    /**
276
     * Determines if given achievement was completed.
277
     *
278
     * @param Achievement $achievement
279
     *
280
     * @return bool
281
     */
282
    protected function isCompletedAchievement(Achievement $achievement): bool
283
    {
284
        $allCompleted = true;
285
        $count = 0;
286
        $criteriasCount = count($achievement->criterias());
287
288
        foreach ($achievement->criterias() as $criteria) {
289
            /**
290
             * @var AchievementCriteria $criteria
291
             */
292
293
            if ($this->isCompletedCriteria($criteria, $criteria->progress())) {
294
                ++$count;
295
            } else {
296
                $allCompleted = false;
297
                break;
298
            }
299
300
            if ($count >= $criteriasCount) {
301
                return true;
302
            }
303
        }
304
305
        return $allCompleted;
306
    }
307
}
308