add gamification characters (Duolingo-style mascot catalog)
New progress.Character model: an admin-managed catalog (same pattern as subscription.Plan) of selectable mascots, each unlocked once the user's XP crosses unlock_xp. No separate ownership table yet — unlocking is a pure function of UserStats.xp, kept intentionally minimal since this is the first of an incremental rollout the senior asked for. - GET /api/characters — catalog with per-user unlocked/selected flags - POST /api/characters/select — picks an active character, rejecting ones the user hasn't unlocked yet - UserStats.selected_character_id tracks the pick - seed_test_data now seeds 4 sample characters (owl/fox/turtle/lion) for local/staging testing Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,10 @@ class UserStats(models.Model):
|
||||
xp = models.PositiveIntegerField(default=0)
|
||||
coins = models.PositiveIntegerField(default=0)
|
||||
progress_percent = models.FloatField(default=0)
|
||||
# ارجاع سبک به Character؛ کاراکترِ انتخابیِ کاربر (مثلِ ماسکاتِ Duolingo).
|
||||
# میتونه به کاراکترِ هنوز بازنشده اشاره نکنه — CharacterSelectView قبل از
|
||||
# ستکردن چک میکنه unlock_xp <= xp.
|
||||
selected_character_id = models.IntegerField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"stats-user{self.user_id}"
|
||||
@@ -92,3 +96,27 @@ class UserHearts(models.Model):
|
||||
self.current_hearts -= 1
|
||||
if self.next_refill_at is None and self.current_hearts < self.max_hearts:
|
||||
self.next_refill_at = timezone.now() + HEART_REFILL_INTERVAL
|
||||
|
||||
|
||||
class Character(models.Model):
|
||||
"""
|
||||
کاراکترهای گیمیفیکیشن (مثلِ ماسکاتهای Duolingo) — یک کاتالوگِ ثابت که از
|
||||
پنلِ ادمین مدیریت میشه (دقیقاً مثلِ subscription.Plan)، نه یک چیزی که
|
||||
کاربر خودش میسازه. فعلاً باز/بستهبودن فقط تابعی از XP کاربره (بدونِ
|
||||
جدولِ جداگانهی مالکیت)؛ اگه بعداً «خریدن با coin» یا شرطِ دیگهای لازم
|
||||
شد، همینجا اضافه میشه.
|
||||
"""
|
||||
slug = models.SlugField(max_length=50, unique=True)
|
||||
name = models.CharField(max_length=50)
|
||||
# کلیدی که فرانت به تصویر/انیمیشنِ واقعی نگاشت میده — همون الگویِ
|
||||
# content.Lesson.mascot_animation و accounts.User.avatar؛ فعلاً آپلودِ
|
||||
# asset واقعی نداریم.
|
||||
icon = models.CharField(max_length=100)
|
||||
unlock_xp = models.PositiveIntegerField(default=0)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order', 'unlock_xp']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
Reference in New Issue
Block a user