Implements character management and update views.

This commit is contained in:
Johnny 2018-10-05 20:16:01 +00:00
parent 3e644c9984
commit 5b536899d5
3 changed files with 273 additions and 2 deletions

View file

@ -0,0 +1,146 @@
from django import forms
from django.conf import settings
from django.contrib.auth.forms import UserCreationForm, UsernameField
from evennia.utils import class_from_module
from random import choice, randint
class AccountForm(UserCreationForm):
class Meta:
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
fields = ("username", "email")
field_classes = {'username': UsernameField}
email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)
class CharacterForm(forms.Form):
name = forms.CharField(help_text="The name of your intended character.")
age = forms.IntegerField(min_value=3, max_value=99, help_text="How old your character should be once spawned.")
description = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), max_length=2048, min_length=160, required=False)
@classmethod
def assign_attributes(cls, attribute_list, points, min_points, max_points):
"""
Randomly distributes a number of points across the given attributes,
while also ensuring each attribute gets at least a certain amount
and at most a certain amount.
Args:
attribute_list (iterable): List or tuple of attribute names to assign
points to.
points (int): Starting number of points
min_points (int): Least amount of points each attribute should have
max_points (int): Most amount of points each attribute should have
Returns:
spread (dict): Dict of attributes and a point assignment.
"""
num_buckets = len(attribute_list)
point_spread = (x for x in self.random_distribution(points, num_buckets, min_points, max_points))
# For each field, get the point calculation for the next attribute value generated
return {attribute: next(point_spread) for k in attribute_list}
@classmethod
def random_distribution(cls, points, num_buckets, min_points, max_points):
"""
Distributes a set number of points randomly across a number of 'buckets'
while also attempting to ensure each bucket's value finishes within a
certain range.
If your math doesn't add up (you try to distribute 5 points across 100
buckets and insist each bucket has at least 20 points), the algorithm
will return the best spread it could achieve but will not raise an error
(so in this case, 5 random buckets would get 1 point each and that's all).
Args:
points (int): The number of points to distribute.
num_buckets (int): The number of 'buckets' (or stats, skills, etc)
you wish to distribute points to.
min_points (int): The least amount of points each bucket should have.
max_points (int): The most points each bucket should have.
Returns:
buckets (list): List of random point assignments.
"""
buckets = [0 for x in range(num_buckets)]
indices = [i for (i, value) in enumerate(buckets)]
# Do this while we have eligible buckets, points to assign and we haven't
# maxed out all the buckets.
while indices and points and sum(buckets) <= (max_points * num_buckets):
# Pick a random bucket index
index = choice(indices)
# Add to bucket
buckets[index] = buckets[index] + 1
points = points - 1
# Get the indices of eligible buckets
indices = [i for (i, value) in enumerate(buckets) if (value < min_points) or (value < max_points)]
return buckets
class CharacterUpdateForm(CharacterForm):
class Meta:
fields = ('description',)
class ExtendedCharacterForm(CharacterForm):
GENDERS = (
('male', 'Male'),
('female', 'Female'),
('androgynous', 'Androgynous'),
('special', 'Special')
)
RACES = (
('human', 'Human'),
('elf', 'Elf'),
('orc', 'Orc'),
)
CLASSES = (
('civilian', 'Civilian'),
('warrior', 'Warrior'),
('thief', 'Thief'),
('cleric', 'Cleric')
)
PERKS = (
('strong', 'Extra strength'),
('nimble', 'Quick on their toes'),
('diplomatic', 'Fast talker')
)
name = forms.CharField(help_text="The name of your intended character.")
age = forms.IntegerField(min_value=3, max_value=99, help_text="How old your character should be once spawned.")
gender = forms.ChoiceField(choices=GENDERS, help_text="Which end of the multidimensional spectrum does your character most closely align with, in terms of gender?")
race = forms.ChoiceField(choices=RACES, help_text="What race does your character belong to?")
job = forms.ChoiceField(choices=CLASSES, help_text="What profession or role does your character fulfill or is otherwise destined to?")
perks = forms.MultipleChoiceField(choices=PERKS, help_text="What extraordinary abilities does your character possess?")
description = forms.CharField(widget=forms.Textarea(attrs={'rows': 3}), max_length=2048, min_length=160, required=False)
strength = forms.IntegerField(min_value=1, max_value=10)
perception = forms.IntegerField(min_value=1, max_value=10)
intelligence = forms.IntegerField(min_value=1, max_value=10)
dexterity = forms.IntegerField(min_value=1, max_value=10)
charisma = forms.IntegerField(min_value=1, max_value=10)
vitality = forms.IntegerField(min_value=1, max_value=10)
magic = forms.IntegerField(min_value=1, max_value=10)
def __init__(self, *args, **kwargs):
# Do all the normal initizliation stuff that would otherwise be happening
super(ExtendedCharacterCreationForm, self).__init__(*args, **kwargs)
# Given a pool of points, let's randomly distribute them across attributes.
# First get a list of attributes
attributes = ('strength', 'perception', 'intelligence', 'dexterity', 'charisma', 'vitality', 'magic')
# Distribute a random number of points across them
attrs = self.assign_attributes(attributes, 50, 1, 10)
# Initialize the form with the results of the point distribution
for field in attrs.keys():
self.initial[field] = attrs[field]

View file

@ -13,8 +13,14 @@ urlpatterns = [
url(r'^tbi/', website_views.to_be_implemented, name='to_be_implemented'),
# User Authentication (makes login/logout url names available)
url(r'^authenticate/', include('django.contrib.auth.urls')),
url(r'^auth/', include('django.contrib.auth.urls')),
url(r'^auth/register', website_views.AccountCreationView.as_view(), name="register"),
# Character management
url(r'^characters/create/$', website_views.CharacterCreateView.as_view(), name="chargen"),
url(r'^characters/manage/$', website_views.CharacterManageView.as_view(), name="manage-characters"),
url(r'^characters/update/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$', website_views.CharacterUpdateView.as_view(), name="update-character"),
# Django original admin page. Make this URL is always available, whether
# we've chosen to use Evennia's custom admin or not.
url(r'django_admin/', website_views.admin_wrapper, name="django_admin"),

View file

@ -17,6 +17,7 @@ from evennia.accounts.models import AccountDB
from evennia.utils import logger
from django.contrib.auth import login
from django.utils.text import slugify
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
@ -134,3 +135,121 @@ def admin_wrapper(request):
Wrapper that allows us to properly use the base Django admin site, if needed.
"""
return staff_member_required(site.index)(request)
class ObjectDetailView(DetailView):
model = ObjectDB
def get_object(self, queryset=None):
obj = super(ObjectDetailView, self).get_object(queryset)
if not slugify(obj.name) == self.kwargs.get('slug'):
raise Http404(u"No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
class AccountCreationView(FormView):
form_class = AccountForm
template_name = 'website/registration/register.html'
success_url = reverse_lazy('login')
def form_valid(self, form):
# Check to make sure basics validated
valid = super(AccountCreationView, self).form_valid(form)
if not valid: return self.form_invalid(form)
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
email = form.cleaned_data.get('email', '')
# Create a fake session object to intercept calls to the terminal
from mock import Mock
session = self.request
session.address = self.request.META.get('REMOTE_ADDR', '')
session.msg = Mock()
# Create account
from evennia.commands.default.unloggedin import _create_account
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
account = _create_account(session, username, password, permissions)
# If unsuccessful, get messages passed to session.msg
if not account:
[messages.error(self.request, call) for call in session.msg.call_args_list]
return self.form_invalid(form)
# Append email address if given
account.email = email
account.save()
messages.success(self.request, "Your account '%s' was successfully created! You may log in using it now." % account.name)
return HttpResponseRedirect(self.success_url)
class CharacterManageView(LoginRequiredMixin, ListView):
model = ObjectDB
def get_queryset(self):
# Get IDs of characters owned by account
ids = [getattr(x, 'id') for x in self.request.user.db._playable_characters]
# Return a queryset consisting of those characters
return self.model.filter(id__in=ids)
class CharacterUpdateView(LoginRequiredMixin, FormView):
form_class = CharacterUpdateForm
template_name = 'website/generic_form.html'
success_url = '/'#reverse_lazy('character-manage')
class CharacterCreateView(LoginRequiredMixin, FormView):
form_class = CharacterForm
template_name = 'website/chargen_form.html'
success_url = '/'#reverse_lazy('character-manage')
def form_valid(self, form):
# Get account ref
account = self.request.user
character = None
# Get attributes from the form
self.attributes = {k: form.cleaned_data[k] for k in form.cleaned_data.keys()}
charname = self.attributes.pop('name')
description = self.attributes.pop('description')
# Create a character
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
from evennia.utils import create
try:
character = create.create_object(typeclass, key=charname, home=default_home, permissions=permissions)
# set playable character list
account.db._playable_characters.append(character)
# allow only the character itself and the account to puppet this character (and Developers).
character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
(character.id, account.id))
# If no description is set, set a default description
if not description:
character.db.desc = "This is a character."
else:
character.db.desc = description
# We need to set this to have @ic auto-connect to this character
account.db._last_puppet = character
# Assign attributes from form
[setattr(character.db, field, self.attributes[field]) for field in self.attributes.keys()]
character.db.creator_id = account.id
character.save()
except Exception as e:
messages.error(self.request, "There was an error creating your character. If this problem persists, contact an admin.")
logger.log_trace()
return self.form_invalid(form)
if character:
messages.success(self.request, "Your character '%s' was created!" % character.name)
return HttpResponseRedirect(self.success_url)
else:
messages.error(self.request, "Your character could not be created. Please contact an admin.")
return self.form_invalid(form)