From 843f8507c2a75e2821040462dbd52d1fe1e5c09a Mon Sep 17 00:00:00 2001 From: TimeTGame Date: Fri, 28 Nov 2025 01:31:12 +0300 Subject: [PATCH] feat: Created game prototype. Added change location and battle system. --- .gitignore | 1 - NoobRPG/NoobRPG/settings.py | 1 + NoobRPG/NoobRPG/urls.py | 1 + NoobRPG/entities/models.py | 32 +++- NoobRPG/game/__init__.py | 0 NoobRPG/game/apps.py | 8 + NoobRPG/game/migrations/__init__.py | 0 NoobRPG/game/urls.py | 21 +++ NoobRPG/game/views.py | 263 ++++++++++++++++++++++++++++ 9 files changed, 321 insertions(+), 6 deletions(-) create mode 100644 NoobRPG/game/__init__.py create mode 100644 NoobRPG/game/apps.py create mode 100644 NoobRPG/game/migrations/__init__.py create mode 100644 NoobRPG/game/urls.py create mode 100644 NoobRPG/game/views.py diff --git a/.gitignore b/.gitignore index 4e91397..ab991c7 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,6 @@ local_settings.py db.sqlite3 db.sqlite3-journal fixtures/ -game/ # Flask stuff: instance/ diff --git a/NoobRPG/NoobRPG/settings.py b/NoobRPG/NoobRPG/settings.py index 2a7f0d0..eac375f 100644 --- a/NoobRPG/NoobRPG/settings.py +++ b/NoobRPG/NoobRPG/settings.py @@ -46,6 +46,7 @@ def load_bool_from_env(name: str, default: bool): 'locations.apps.LocationsConfig', 'rarity.apps.RarityConfig', 'sellers_offers.apps.SellersOffersConfig', + 'game.apps.GameConfig', ] MIDDLEWARE = [ diff --git a/NoobRPG/NoobRPG/urls.py b/NoobRPG/NoobRPG/urls.py index 929220e..39f1ff7 100644 --- a/NoobRPG/NoobRPG/urls.py +++ b/NoobRPG/NoobRPG/urls.py @@ -8,6 +8,7 @@ path('api/v1/auth/', include('djoser.urls')), re_path(r'^auth/', include('djoser.urls.authtoken')), path('api/v1/entities/', include('entities.urls')), + path('api/v1/game/', include('game.urls')), path('api/v1/items/', include('items.urls')), path('api/v1/locations/', include('locations.urls')), path('api/v1/rarity/', include('rarity.urls')), diff --git a/NoobRPG/entities/models.py b/NoobRPG/entities/models.py index e8a09a5..0a4abec 100644 --- a/NoobRPG/entities/models.py +++ b/NoobRPG/entities/models.py @@ -85,12 +85,27 @@ def taking_damage(self, damage: int) -> str | None: self.hp = 0 self.save() if self.hp == 0: - return self.drop_items() + self.is_in_battle = False + self.save() + return f'{self.drop_items()}' return None def self_damage(self) -> int: return self.base_damage + def attack(self, player: models.QuerySet) -> str: + damage = self.self_damage() + player_return = player.taking_damage(damage, 0) + if player_return is None: + self.is_in_battle = False + self.save() + return ( + 'Enemy attacked. You have lost. ' + f'You have been sent to the {player.start_location} location.' + ) + + return player_return + class PlayerManager(models.Manager): def all_fields(self) -> models.QuerySet: @@ -163,17 +178,19 @@ def equip_weapon(self, item: models.QuerySet) -> str: return f'Equipped weapon: {self.weapon}.' def taking_damage(self, damage_hp: int, damage_mana: int) -> str: - if self.hp >= damage_hp or self.mana >= damage_mana: + message = 'Enemy attacked. ' + if self.hp >= damage_hp and self.mana >= damage_mana: self.hp -= damage_hp self.mana -= damage_mana - message = ( + message += ( f'Your health is now {self.hp} and your mana is {self.mana}.' ) else: self.hp = self.max_hp self.mana = self.max_mana + self.is_in_battle = False self.to_start_location() - message = ( + message += ( 'You have lost. ' f'You have been sent to the {self.start_location} location.' ) @@ -206,5 +223,10 @@ def self_damage(self) -> int: def attack(self, enemy: models.QuerySet) -> str: damage = self.self_damage() - enemy.taking_damage(damage) + enemy_return = enemy.taking_damage(damage) + if enemy_return is not None: + self.is_in_battle = False + self.save() + return f'You won. Drop from target: {enemy_return}' + return f'You dealt {damage} damage to an enemy.' diff --git a/NoobRPG/game/__init__.py b/NoobRPG/game/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/NoobRPG/game/apps.py b/NoobRPG/game/apps.py new file mode 100644 index 0000000..b137017 --- /dev/null +++ b/NoobRPG/game/apps.py @@ -0,0 +1,8 @@ +__all__ = () + +from django.apps import AppConfig + + +class GameConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'game' diff --git a/NoobRPG/game/migrations/__init__.py b/NoobRPG/game/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/NoobRPG/game/urls.py b/NoobRPG/game/urls.py new file mode 100644 index 0000000..a725541 --- /dev/null +++ b/NoobRPG/game/urls.py @@ -0,0 +1,21 @@ +from django.urls import path +from game import views + + +urlpatterns = [ + path( + 'change-location/', + views.ChangeLocationView.as_view(), + name='change-location', + ), + path( + 'start-battle/', + views.StartBattleView.as_view(), + name='start-battle', + ), + path( + 'attack/', + views.AttackView.as_view(), + name='attack', + ), +] diff --git a/NoobRPG/game/views.py b/NoobRPG/game/views.py new file mode 100644 index 0000000..3d797e8 --- /dev/null +++ b/NoobRPG/game/views.py @@ -0,0 +1,263 @@ +__all__ = () + +from entities.models import NonPlayerCharacter as NPCModel +from entities.models import Player +from locations.models import Location +from rest_framework import permissions +from rest_framework.response import Response +from rest_framework.views import APIView + + +class ChangeLocationView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def post(self, request): + data = request.data + + location_id = data.get('location_id') + player_id = data.get('player_id') + + if not location_id: + return Response({'error': 'Location ID is required'}, status=400) + + try: + location_id = int(location_id) + if location_id <= 0: + return Response( + {'error': 'Location ID must be a positive integer'}, + status=400, + ) + except (ValueError, TypeError): + return Response( + {'error': 'Location ID must be a valid integer'}, + status=400, + ) + + if player_id: + try: + player_id = int(player_id) + if player_id <= 0: + return Response( + {'error': 'Player ID must be a positive integer'}, + status=400, + ) + except (ValueError, TypeError): + return Response( + {'error': 'Player ID must be a valid integer'}, + status=400, + ) + + try: + if player_id: + player = Player.objects.get(id=player_id, user=request.user) + else: + player = Player.objects.get(user=request.user) + + location = Location.objects.get(id=location_id) + + player.current_location = location + player.save() + + return Response( + { + 'message': ( + f'Location updated successfully to {location.name}' + ), + 'current_location': location.name, + 'location_id': location.id, + 'player_id': player.id, + }, + ) + + except Player.DoesNotExist: + return Response( + { + 'error': ( + 'Player not found. Either the player does not exist ' + 'or you do not have permission to update this player.', + ), + }, + status=404, + ) + except Location.DoesNotExist: + return Response({'error': 'Location not found'}, status=404) + except Exception as e: + print(f'Error updating player location: {e}') + return Response( + {'error': 'An error occurred while updating the location'}, + status=500, + ) + + +class StartBattleView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def post(self, request): + data = request.data + + npc_id = data.get('npc_id') + player_id = data.get('player_id') + + if not npc_id: + return Response({'error': 'NPC ID is required'}, status=400) + + try: + npc_id = int(npc_id) + if npc_id <= 0: + return Response( + {'error': 'NPC ID must be a positive integer'}, + status=400, + ) + except (ValueError, TypeError): + return Response( + {'error': 'NPC ID must be a valid integer'}, + status=400, + ) + + if player_id: + try: + player_id = int(player_id) + if player_id <= 0: + return Response( + {'error': 'Player ID must be a positive integer'}, + status=400, + ) + except (ValueError, TypeError): + return Response( + {'error': 'Player ID must be a valid integer'}, + status=400, + ) + + try: + if player_id: + player = Player.objects.get(id=player_id, user=request.user) + else: + player = Player.objects.get(user=request.user) + + npc = NPCModel.objects.get(id=npc_id) + + player.is_in_battle = True + npc.is_in_battle = True + player.save() + npc.save() + + return Response( + { + 'message': f'You are now in battle with {npc.name}', + 'player is in battle': player.is_in_battle, + 'NPC is in battle': npc.is_in_battle, + 'npc_id': npc.id, + 'player_id': player.id, + }, + ) + + except Player.DoesNotExist: + return Response( + { + 'error': ( + 'Player not found. Either the player does not exist ' + 'or you do not have permission to update this player.', + ), + }, + status=404, + ) + except NPCModel.DoesNotExist: + return Response({'error': 'NPC not found'}, status=404) + except Exception as e: + print(f'Error updating player is_in_battle: {e}') + return Response( + {'error': 'An error occurred while starting battle'}, + status=500, + ) + + +class AttackView(APIView): + permission_classes = [permissions.IsAuthenticated] + + def post(self, request): + data = request.data + + npc_id = data.get('npc_id') + player_id = data.get('player_id') + + if not npc_id: + return Response({'error': 'NPC ID is required'}, status=400) + + try: + npc_id = int(npc_id) + if npc_id <= 0: + return Response( + {'error': 'NPC ID must be a positive integer'}, + status=400, + ) + except (ValueError, TypeError): + return Response( + {'error': 'NPC ID must be a valid integer'}, + status=400, + ) + + if player_id: + try: + player_id = int(player_id) + if player_id <= 0: + return Response( + {'error': 'Player ID must be a positive integer'}, + status=400, + ) + except (ValueError, TypeError): + return Response( + {'error': 'Player ID must be a valid integer'}, + status=400, + ) + + try: + if player_id: + player = Player.objects.get(id=player_id, user=request.user) + else: + player = Player.objects.get(user=request.user) + + if player.is_in_battle is False: + return Response( + {'error': 'First we need to start the battle'}, + status=400, + ) + + npc = NPCModel.objects.get(id=npc_id) + + if npc.is_in_battle is False: + return Response( + {'error': 'NPC is_in_battle must be True'}, + status=400, + ) + + player_attack_res = player.attack(npc) + npc_attack_res = npc.attack(player) + + return Response( + { + 'message': f'{player_attack_res} {npc_attack_res}', + 'player is in battle': player.is_in_battle, + 'NPC is in battle': npc.is_in_battle, + 'npc_id': npc.id, + 'player_id': player.id, + }, + ) + + except Player.DoesNotExist: + return Response( + { + 'error': ( + 'Player not found. Either the player does not exist ' + 'or you do not have permission to update this player.', + ), + }, + status=404, + ) + except NPCModel.DoesNotExist: + return Response({'error': 'NPC not found'}, status=404) + except Exception as e: + print(f'Error updating player is_in_battle: {e}') + return Response( + {'error': 'An error occurred while starting battle'}, + status=500, + )