Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[flake8]
import-order-style = google
exclude = .git,__pycache__,migrations,.dev-venv,.prod-venv,.venv
max-line-length = 79
max-line-length = 79
ignore = FNE003,FNE008
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ local_settings.py
db.sqlite3
db.sqlite3-journal
fixtures/
game/

# Flask stuff:
instance/
Expand Down
11 changes: 11 additions & 0 deletions NoobRPG/NoobRPG/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def load_bool_from_env(name: str, default: bool):
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'djoser',
'entities.apps.EntitiesConfig',
'items.apps.ItemsConfig',
'locations.apps.LocationsConfig',
Expand Down Expand Up @@ -124,3 +126,12 @@ def load_bool_from_env(name: str, default: bool):


DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
5 changes: 4 additions & 1 deletion NoobRPG/NoobRPG/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from django.contrib import admin
from django.urls import include, path
from django.urls import include, path, re_path


urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/drf-auth/', include('rest_framework.urls')),
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/items/', include('items.urls')),
path('api/v1/locations/', include('locations.urls')),
Expand Down
3 changes: 3 additions & 0 deletions NoobRPG/entities/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@
class EntitiesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'entities'

def ready(self):
import entities.signals # noqa
28 changes: 28 additions & 0 deletions NoobRPG/entities/migrations/0002_player_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 5.2.8 on 2025-11-27 11:15

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('entities', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AddField(
model_name='player',
name='user',
field=models.OneToOneField(
blank=True,
help_text='The user account associated with this player',
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='player_profile',
to=settings.AUTH_USER_MODEL,
),
),
]
37 changes: 29 additions & 8 deletions NoobRPG/entities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import random

from core.models import EntityBaseModel
from django.contrib.auth.models import User
from django.db import models
from items.models import Items
from locations.models import Location
Expand Down Expand Up @@ -72,17 +73,22 @@ def __str__(self) -> str:
return f'{self.name}'

def drop_items(self) -> models.QuerySet:
return random.choice(self.items_to_drop)
items = list(self.items_to_drop.all())
if items:
return random.choice(items)
return None

def taking_damage(self, damage: int) -> str | None:
if self.hp > damage:
self.hp -= damage
else:
self.hp = 0
self.save()
if self.hp == 0:
return self.drop_items()
return None

def dealing_damage(self) -> int:
def self_damage(self) -> int:
return self.base_damage


Expand All @@ -106,6 +112,14 @@ def all_fields(self) -> models.QuerySet:

class Player(EntityBaseModel):
objects = PlayerManager()
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='player_profile',
help_text='The user account associated with this player',
)
inventory = models.ManyToManyField(
Items,
blank=True,
Expand Down Expand Up @@ -133,18 +147,20 @@ class Meta:
def __str__(self):
return f'{self.name}'

def to_start_location(self) -> None:
self.location = self.start_location
def to_start_location(self) -> str:
self.current_location = self.start_location
self.save()
return f'You are now in start location: {self.current_location}'

def equip_weapon(self, item: models.QuerySet) -> None:
def equip_weapon(self, item: models.QuerySet) -> str:
if item is None:
self.weapon = None
return
return 'Cannot equip weapon: Inventory is empty.'
if item not in self.inventory.all():
raise ValueError('Cannot equip weapon: item not in inventory.')
return 'Cannot equip weapon: Item not in inventory.'
self.weapon = item
self.save()
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:
Expand Down Expand Up @@ -183,7 +199,12 @@ def healing(self, hp_heal_amount: int, mana_heal_amount: int) -> str:
)
return f'Your health is now {self.hp} and your mana is {self.mana}.'

def dealing_damage(self) -> int:
def self_damage(self) -> int:
if self.weapon:
return self.base_damage + self.weapon.damage
return self.base_damage

def attack(self, enemy: models.QuerySet) -> str:
damage = self.self_damage()
enemy.taking_damage(damage)
return f'You dealt {damage} damage to an enemy.'
2 changes: 2 additions & 0 deletions NoobRPG/entities/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def build_field(self, field_name, info, model_class, nested_depth):


class PlayerSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.StringRelatedField()
inventory = serializers.StringRelatedField(many=True, read_only=True)
weapon = serializers.StringRelatedField(read_only=True)

Expand All @@ -81,6 +82,7 @@ class Meta:
Player.base_damage.field.name,
Player.is_in_battle.field.name,
Player.current_location.field.name,
Player.user.field.name,
Player.inventory.field.name,
Player.weapon.field.name,
Player.start_location.field.name,
Expand Down
51 changes: 51 additions & 0 deletions NoobRPG/entities/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
__all__ = ()

from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from entities.models import Player
from locations.models import Location


@receiver(post_save, sender=User)
def create_player_profile(sender, instance, created, **kwargs):
if created:
if not hasattr(instance, 'player_profile'):
default_location = Location.objects.first()

if default_location:
Player.objects.create(
user=instance,
name=instance.username,
start_location=default_location,
hp=100,
max_hp=100,
mana=20,
max_mana=20,
base_damage=10,
current_location=default_location,
is_in_battle=False,
)
else:
default_location = Location.objects.create(
name='Default Location',
slug='default-location',
)
Player.objects.create(
user=instance,
name=instance.username,
start_location=default_location,
hp=100,
max_hp=100,
mana=20,
max_mana=20,
base_damage=10,
current_location=default_location,
is_in_battle=False,
)


@receiver(post_save, sender=User)
def save_player_profile(sender, instance, **kwargs):
if hasattr(instance, 'player_profile'):
instance.player_profile.save()
61 changes: 0 additions & 61 deletions NoobRPG/entities/tests.py

This file was deleted.

7 changes: 0 additions & 7 deletions NoobRPG/entities/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,4 @@

urlpatterns = [
path('', include(router.urls)),
path(
'api-auth/',
include(
'rest_framework.urls',
namespace='rest_framework',
),
),
]
45 changes: 45 additions & 0 deletions NoobRPG/entities/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
__all__ = ()

from entities.models import Player
from locations.models import Location


def create_player_for_user(user, player_name=None, location=None):
try:
player = user.player_profile
return player
except AttributeError:
pass

name = player_name or user.username

start_location = location
if not start_location:
start_location = Location.objects.first()

if not start_location:
raise ValueError('No location available to assign to the new player.')

player = Player.objects.create(
user=user,
name=name,
start_location=start_location,
hp=100,
max_hp=100,
mana=20,
max_mana=20,
base_damage=10,
current_location=start_location,
is_in_battle=False,
)

return player


def get_or_create_player_for_user(user, player_name=None, location=None):
try:
player = user.player_profile
return player, False
except AttributeError:
player = create_player_for_user(user, player_name, location)
return player, True
2 changes: 1 addition & 1 deletion NoobRPG/items/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
router.register(r'', views.ItemViewSet, basename='items')

urlpatterns = [
path('items/', include(router.urls)),
path('', include(router.urls)),
]
2 changes: 1 addition & 1 deletion NoobRPG/locations/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ def build_field(self, field_name, info, model_class, nested_depth):
)

if field_name == 'url':
field_kwargs['view_name'] = 'locations-detail'
field_kwargs['view_name'] = 'location-detail'

return field_class, field_kwargs
Empty file removed NoobRPG/users/__init__.py
Empty file.
8 changes: 0 additions & 8 deletions NoobRPG/users/apps.py

This file was deleted.

Empty file.
1 change: 1 addition & 0 deletions requirements/prod.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
djangorestframework==3.16.1
djoser==2.3.3
python-dotenv==1.2.1