diff --git a/game_data/migrations/0006_playerprofile_class_name.py b/game_data/migrations/0006_playerprofile_class_name.py new file mode 100644 index 0000000..5c0a961 --- /dev/null +++ b/game_data/migrations/0006_playerprofile_class_name.py @@ -0,0 +1,25 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('game_data', '0005_merge_20260505_1640'), + ] + + operations = [ + migrations.AddField( + model_name='playerprofile', + name='class_name', + field=models.CharField( + choices=[ + ('adventurer', 'Adventurer'), + ('mage', 'Mage'), + ('archer', 'Archer'), + ('warrior', 'Warrior'), + ], + default='adventurer', + max_length=16, + ), + ), + ] diff --git a/game_data/models.py b/game_data/models.py index ef5da95..cd9338c 100644 --- a/game_data/models.py +++ b/game_data/models.py @@ -8,6 +8,19 @@ class PlayerProfile(models.Model): + + CLASS_ADVENTURER = 'adventurer' + CLASS_MAGE = 'mage' + CLASS_ARCHER = 'archer' + CLASS_WARRIOR = 'warrior' + + CLASS_CHOICES = [ + (CLASS_ADVENTURER, 'Adventurer'), + (CLASS_MAGE, 'Mage'), + (CLASS_ARCHER, 'Archer'), + (CLASS_WARRIOR, 'Warrior'), + ] + user = models.OneToOneField( User, on_delete=models.CASCADE, @@ -19,6 +32,13 @@ class PlayerProfile(models.Model): hp_current = models.PositiveIntegerField(default=100) gold = models.PositiveIntegerField(default=100) + # Player class — determines available skills and sprite set. + class_name = models.CharField( + max_length=16, + choices=CLASS_CHOICES, + default=CLASS_ADVENTURER, + ) + # Currently equipped items keyed by slot (head, body, arms, legs, shoes, weapon). equipped_items = models.JSONField(default=dict, blank=True) @@ -34,7 +54,7 @@ class Meta: ordering = ['user_id'] def __str__(self) -> str: - return f"{self.user.username} L{self.level} HP {self.hp_current}/{self.hp_max}" + return f"{self.user.username} L{self.level} HP {self.hp_current}/{self.hp_max} [{self.class_name}]" class PlayerSkill(models.Model): diff --git a/game_data/serializers.py b/game_data/serializers.py index 65877bd..5cfea35 100644 --- a/game_data/serializers.py +++ b/game_data/serializers.py @@ -71,6 +71,7 @@ class Meta: 'hp_max', 'hp_current', 'gold', + 'class_name', 'inventory_items', 'discarded_items', 'equipped_items', @@ -93,6 +94,10 @@ class PlayerProfileUpdateSerializer(serializers.Serializer): hp_max = serializers.IntegerField(required=False, min_value=1) hp_current = serializers.IntegerField(required=False, min_value=0) gold = serializers.IntegerField(required=False, min_value=0) + class_name = serializers.ChoiceField( + choices=[c[0] for c in PlayerProfile.CLASS_CHOICES], + required=False, + ) skills = PlayerSkillSerializer(many=True, required=False) inventory_items = serializers.ListField( child=serializers.DictField(), diff --git a/game_data/views.py b/game_data/views.py index 8d5bd29..bd92e63 100644 --- a/game_data/views.py +++ b/game_data/views.py @@ -109,36 +109,19 @@ def patch(self, request: Request) -> Response: with transaction.atomic(): session_id = validated.get('session_id') - sequence = validated.get('sequence') - - if session_id: - if profile.active_session_id != session_id: - profile.active_session_id = session_id - profile.last_sequence = 0 - - for field in ( - 'level', - 'experience', - 'hp_max', - 'hp_current', - 'gold', - 'inventory_items', - 'discarded_items', - 'equipped_items', - ): - if field in validated: - setattr(profile, field, validated[field]) - - for field in ( - 'level', - 'experience', - 'hp_max', - 'hp_current', - 'gold', - ): + sequence = validated.get('sequence') + + # Reset sequence counter when a new session starts. + if session_id and profile.active_session_id != session_id: + profile.active_session_id = session_id + profile.last_sequence = 0 + + # Apply simple scalar fields directly onto the profile. + for field in ('level', 'experience', 'hp_max', 'hp_current', 'gold', 'class_name', 'equipped_items'): if field in validated: setattr(profile, field, validated[field]) + # Sync inventory and discarded items. if 'inventory_items' in validated or 'discarded_items' in validated: _sync_profile_items( profile, @@ -146,6 +129,7 @@ def patch(self, request: Request) -> Response: discarded_items=validated.get('discarded_items'), ) + # Replace skills list. if 'skills' in validated: PlayerSkill.objects.filter(player_profile=profile).delete() for index, skill in enumerate(validated['skills']): @@ -157,6 +141,7 @@ def patch(self, request: Request) -> Response: display_order=skill.get('display_order', index), ) + # Guard: hp_current must never exceed hp_max. if profile.hp_current > profile.hp_max: profile.hp_current = profile.hp_max @@ -169,6 +154,7 @@ def patch(self, request: Request) -> Response: output['ignored'] = False return Response(output, status=status.HTTP_200_OK) + @staticmethod def _get_or_create_profile(user): profile, created = PlayerProfile.objects.get_or_create(user=user)