240 lines
8.3 KiB
Python
240 lines
8.3 KiB
Python
import random
|
||
|
||
from django.shortcuts import get_object_or_404, redirect, render
|
||
|
||
from .forms import CharacterCreateForm, ChoiceForm
|
||
from .models import Character, Choice, InventoryItem, Item, Quest, Scene, StoryEntry
|
||
|
||
|
||
BACKGROUND_STATS = {
|
||
Character.BACKGROUND_WARDEN: {"vigor": 5, "focus": 3, "alchemy": 2},
|
||
Character.BACKGROUND_SCOUR: {"vigor": 4, "focus": 4, "alchemy": 2},
|
||
Character.BACKGROUND_ALCHEMIST: {"vigor": 2, "focus": 4, "alchemy": 5},
|
||
}
|
||
|
||
|
||
def get_active_character(request):
|
||
character_id = request.session.get("active_character_id")
|
||
if character_id:
|
||
try:
|
||
return Character.objects.get(id=character_id)
|
||
except Character.DoesNotExist:
|
||
request.session.pop("active_character_id", None)
|
||
return None
|
||
|
||
|
||
def home(request):
|
||
character = get_active_character(request)
|
||
quests = character.quests.all()[:3] if character else []
|
||
inventory = character.inventory.select_related("item")[:3] if character else []
|
||
|
||
context = {
|
||
"page_title": "Темная тропа — интерактивная RPG",
|
||
"page_description": "Мрачное текстовое RPG-приключение в духе ведьмачьих саг: выбор, проверки навыков, журнал заданий и лут.",
|
||
"character": character,
|
||
"quests": quests,
|
||
"inventory": inventory,
|
||
}
|
||
return render(request, "core/index.html", context)
|
||
|
||
|
||
def character_create(request):
|
||
if request.method == "POST":
|
||
form = CharacterCreateForm(request.POST)
|
||
if form.is_valid():
|
||
background = form.cleaned_data["background"]
|
||
stats = BACKGROUND_STATS.get(background, {"vigor": 3, "focus": 3, "alchemy": 3})
|
||
starting_scene = Scene.objects.first()
|
||
character = Character.objects.create(
|
||
name=form.cleaned_data["name"],
|
||
background=background,
|
||
vigor=stats["vigor"],
|
||
focus=stats["focus"],
|
||
alchemy=stats["alchemy"],
|
||
current_scene=starting_scene,
|
||
)
|
||
request.session["active_character_id"] = character.id
|
||
starter_item = Item.objects.first()
|
||
if starter_item:
|
||
InventoryItem.objects.create(
|
||
character=character, item=starter_item, equipped=True
|
||
)
|
||
Quest.objects.create(
|
||
character=character,
|
||
title="Шепот Чёрной Трясины",
|
||
summary="Разузнать, кто тревожит болота и почему местные боятся ночи.",
|
||
)
|
||
return redirect("character_detail", pk=character.id)
|
||
else:
|
||
form = CharacterCreateForm()
|
||
|
||
return render(
|
||
request,
|
||
"core/character_create.html",
|
||
{
|
||
"form": form,
|
||
"page_title": "Создать героя",
|
||
"page_description": "Соберите персонажа и начните мрачное приключение.",
|
||
},
|
||
)
|
||
|
||
|
||
def character_detail(request, pk):
|
||
character = get_object_or_404(Character, pk=pk)
|
||
request.session["active_character_id"] = character.id
|
||
equipped = character.inventory.select_related("item").filter(equipped=True)
|
||
recent_entries = character.story_entries.select_related("scene")[:4]
|
||
|
||
return render(
|
||
request,
|
||
"core/character_detail.html",
|
||
{
|
||
"character": character,
|
||
"equipped": equipped,
|
||
"recent_entries": recent_entries,
|
||
"page_title": f"{character.name} — профиль героя",
|
||
"page_description": "Профиль персонажа, характеристики и недавние выборы.",
|
||
},
|
||
)
|
||
|
||
|
||
def story_view(request):
|
||
character = get_active_character(request)
|
||
if not character:
|
||
return redirect("character_create")
|
||
|
||
scene = character.current_scene or Scene.objects.first()
|
||
if not scene:
|
||
return render(
|
||
request,
|
||
"core/story.html",
|
||
{
|
||
"character": character,
|
||
"scene": None,
|
||
"form": None,
|
||
"page_title": "Сюжет",
|
||
"page_description": "Пока нет сцен. Добавьте их через админку.",
|
||
},
|
||
)
|
||
|
||
last_entry_id = request.session.pop("last_entry_id", None)
|
||
last_entry = None
|
||
if last_entry_id:
|
||
last_entry = StoryEntry.objects.filter(
|
||
id=last_entry_id, character=character
|
||
).select_related("scene").first()
|
||
|
||
if request.method == "POST":
|
||
form = ChoiceForm(request.POST, choice_queryset=scene.choices.all())
|
||
if form.is_valid():
|
||
choice = form.cleaned_data["choice"]
|
||
outcome = StoryEntry.OUTCOME_NEUTRAL
|
||
roll = None
|
||
total = None
|
||
next_scene = choice.next_scene or scene
|
||
|
||
if choice.required_skill and choice.difficulty:
|
||
roll = random.randint(1, 20)
|
||
skill_value = getattr(character, choice.required_skill, 0)
|
||
total = roll + skill_value
|
||
success = total >= choice.difficulty
|
||
outcome = StoryEntry.OUTCOME_SUCCESS if success else StoryEntry.OUTCOME_FAIL
|
||
next_scene = choice.success_scene if success else choice.fail_scene
|
||
next_scene = next_scene or choice.next_scene or scene
|
||
|
||
entry = StoryEntry.objects.create(
|
||
character=character,
|
||
scene=scene,
|
||
choice_text=choice.text,
|
||
outcome=outcome,
|
||
roll=roll,
|
||
total=total,
|
||
)
|
||
|
||
if choice.reward_item and outcome != StoryEntry.OUTCOME_FAIL:
|
||
InventoryItem.objects.get_or_create(
|
||
character=character, item=choice.reward_item
|
||
)
|
||
|
||
character.current_scene = next_scene
|
||
character.save(update_fields=["current_scene"])
|
||
request.session["last_entry_id"] = entry.id
|
||
return redirect("story")
|
||
else:
|
||
form = ChoiceForm(choice_queryset=scene.choices.all())
|
||
|
||
return render(
|
||
request,
|
||
"core/story.html",
|
||
{
|
||
"character": character,
|
||
"scene": scene,
|
||
"form": form,
|
||
"last_entry": last_entry,
|
||
"page_title": "Сюжет",
|
||
"page_description": "Выбирайте действия и наблюдайте последствия.",
|
||
},
|
||
)
|
||
|
||
|
||
def quest_list(request):
|
||
character = get_active_character(request)
|
||
quests = character.quests.all() if character else []
|
||
return render(
|
||
request,
|
||
"core/quest_list.html",
|
||
{
|
||
"character": character,
|
||
"quests": quests,
|
||
"page_title": "Журнал заданий",
|
||
"page_description": "Список текущих и завершённых заданий.",
|
||
},
|
||
)
|
||
|
||
|
||
def quest_detail(request, pk):
|
||
character = get_active_character(request)
|
||
quest = get_object_or_404(Quest, pk=pk)
|
||
return render(
|
||
request,
|
||
"core/quest_detail.html",
|
||
{
|
||
"character": character,
|
||
"quest": quest,
|
||
"page_title": quest.title,
|
||
"page_description": "Детали задания и его статус.",
|
||
},
|
||
)
|
||
|
||
|
||
def inventory_view(request):
|
||
character = get_active_character(request)
|
||
items = (
|
||
character.inventory.select_related("item") if character else InventoryItem.objects.none()
|
||
)
|
||
return render(
|
||
request,
|
||
"core/inventory.html",
|
||
{
|
||
"character": character,
|
||
"items": items,
|
||
"page_title": "Инвентарь",
|
||
"page_description": "Лут, экипировка и свойства предметов.",
|
||
},
|
||
)
|
||
|
||
|
||
def item_detail(request, pk):
|
||
character = get_active_character(request)
|
||
inventory_item = get_object_or_404(InventoryItem, pk=pk)
|
||
return render(
|
||
request,
|
||
"core/item_detail.html",
|
||
{
|
||
"character": character,
|
||
"inventory_item": inventory_item,
|
||
"page_title": inventory_item.item.name,
|
||
"page_description": "Подробности предмета и его эффектов.",
|
||
},
|
||
)
|