charasheet/src/character/models/character.py

252 lines
7.7 KiB
Python
Raw Normal View History

2022-10-30 16:33:28 +01:00
import collections
2022-10-29 00:32:18 +02:00
from django.db import models
from django.db.models.functions import Lower
2022-10-30 10:12:49 +01:00
from django.urls import reverse
2022-10-29 00:32:18 +02:00
from django_extensions.db.models import TimeStampedModel
2022-10-30 16:33:28 +01:00
from character.models import Capability, Path
2022-10-29 00:32:18 +02:00
from character.models.dice import Dice
2022-10-30 09:44:28 +01:00
from common.models import DocumentedModel, UniquelyNamedModel
2022-10-29 00:32:18 +02:00
2022-10-30 09:44:28 +01:00
class Profile(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
2022-10-29 00:32:18 +02:00
class MagicalStrength(models.TextChoices):
2022-10-30 11:09:46 +01:00
NONE = "NON", "Aucun"
2022-10-29 00:32:18 +02:00
INTELLIGENCE = "INT", "Intelligence"
2022-10-30 11:09:46 +01:00
WISDOM = "SAG", "Sagesse"
CHARISMA = "CHA", "Charisme"
2022-10-29 00:32:18 +02:00
magical_strength = models.CharField(
2022-10-30 11:09:46 +01:00
max_length=3,
choices=MagicalStrength.choices,
default=MagicalStrength.NONE,
verbose_name="force magique",
2022-10-29 00:32:18 +02:00
)
2022-10-30 11:09:46 +01:00
life_dice = models.PositiveSmallIntegerField(
choices=Dice.choices, verbose_name="dé de vie"
)
notes = models.TextField(blank=True, verbose_name="notes")
class Meta:
verbose_name = "Profil"
verbose_name_plural = "Profils"
2022-10-29 00:32:18 +02:00
2022-10-30 09:44:28 +01:00
class Race(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
2022-10-30 11:09:46 +01:00
class Meta:
verbose_name = "Race"
verbose_name_plural = "Races"
2022-10-29 00:32:18 +02:00
def modifier(value: int) -> int:
if 1 < value < 10:
value -= 1
value -= 10
return int(value / 2)
class CharacterManager(models.Manager):
def get_by_natural_key(self, name: str, player_id: int):
return self.get(name=name, player_id=player_id)
class Character(models.Model):
class Gender(models.TextChoices):
2022-10-30 11:09:46 +01:00
MALE = "M", "Mâle"
FEMALE = "F", "Femelle"
OTHER = "O", "Autre"
2022-10-29 00:32:18 +02:00
2022-10-30 11:09:46 +01:00
name = models.CharField(max_length=100, verbose_name="nom")
2022-10-29 00:32:18 +02:00
player = models.ForeignKey(
2022-10-30 11:09:46 +01:00
"common.User",
on_delete=models.CASCADE,
related_name="characters",
verbose_name="joueur",
2022-10-29 00:32:18 +02:00
)
race = models.ForeignKey(
"character.Race",
on_delete=models.PROTECT,
related_name="characters",
2022-10-30 11:09:46 +01:00
verbose_name="race",
2022-10-29 00:32:18 +02:00
)
profile = models.ForeignKey(
"character.Profile",
on_delete=models.PROTECT,
related_name="characters",
2022-10-30 11:09:46 +01:00
verbose_name="profil",
2022-10-29 00:32:18 +02:00
)
2022-10-30 11:09:46 +01:00
level = models.PositiveSmallIntegerField(verbose_name="niveau")
2022-10-29 00:32:18 +02:00
gender = models.CharField(
2022-10-30 11:09:46 +01:00
max_length=1, choices=Gender.choices, default=Gender.OTHER, verbose_name="genre"
2022-10-29 00:32:18 +02:00
)
2022-10-30 11:09:46 +01:00
age = models.PositiveSmallIntegerField(verbose_name="âge")
height = models.PositiveSmallIntegerField(verbose_name="taille")
weight = models.PositiveSmallIntegerField(verbose_name="poids")
value_strength = models.PositiveSmallIntegerField(verbose_name="valeur force")
value_dexterity = models.PositiveSmallIntegerField(verbose_name="valeur dextérité")
value_constitution = models.PositiveSmallIntegerField(
verbose_name="valeur constitution"
)
value_intelligence = models.PositiveSmallIntegerField(
verbose_name="valeur intelligence"
)
value_wisdom = models.PositiveSmallIntegerField(verbose_name="valeur sagesse")
value_charisma = models.PositiveSmallIntegerField(verbose_name="valeur charisme")
2022-10-29 00:32:18 +02:00
2022-10-30 11:09:46 +01:00
health_max = models.PositiveSmallIntegerField(verbose_name="points de vie max")
health_remaining = models.PositiveSmallIntegerField(
verbose_name="points de vie restants"
)
2022-10-29 00:32:18 +02:00
racial_capability = models.ForeignKey(
"character.RacialCapability",
on_delete=models.PROTECT,
related_name="characters",
2022-10-30 11:09:46 +01:00
verbose_name="capacité raciale",
2022-10-29 00:32:18 +02:00
)
2022-10-30 11:09:46 +01:00
weapons = models.ManyToManyField(
"character.Weapon", blank=True, verbose_name="armes"
)
2022-10-29 00:32:18 +02:00
2022-10-30 11:09:46 +01:00
armor = models.PositiveSmallIntegerField(verbose_name="armure")
shield = models.PositiveSmallIntegerField(verbose_name="bouclier")
defense_misc = models.SmallIntegerField(verbose_name="divers défense")
2022-10-29 00:32:18 +02:00
2022-10-30 11:09:46 +01:00
capabilities = models.ManyToManyField(
"character.Capability", blank=True, verbose_name="capacités"
)
2022-10-29 00:32:18 +02:00
2022-10-30 11:09:46 +01:00
equipment = models.TextField(blank=True, verbose_name="équipement")
luck_points_max = models.PositiveSmallIntegerField(
verbose_name="points de chance max"
)
luck_points_remaining = models.PositiveSmallIntegerField(
verbose_name="points de chance restants"
)
2022-10-29 00:32:18 +02:00
2022-10-30 17:47:42 +01:00
mana_remaining = models.PositiveSmallIntegerField(
default=0, verbose_name="mana restant"
2022-10-30 11:09:46 +01:00
)
2022-10-29 00:32:18 +02:00
2022-10-30 16:51:17 +01:00
money_pp = models.PositiveSmallIntegerField(default=0, verbose_name="PP")
money_po = models.PositiveSmallIntegerField(default=0, verbose_name="PO")
money_pa = models.PositiveSmallIntegerField(default=0, verbose_name="PA")
money_pc = models.PositiveSmallIntegerField(default=0, verbose_name="PC")
2022-10-30 21:55:05 +01:00
recovery_points_remaining = models.PositiveSmallIntegerField(
default=5, verbose_name="points de récupération restants"
)
2022-10-30 11:09:46 +01:00
notes = models.TextField(blank=True, verbose_name="notes")
2022-10-29 00:32:18 +02:00
objects = CharacterManager()
class Meta:
2022-10-30 11:09:46 +01:00
verbose_name = "Personnage"
verbose_name_plural = "Personnages"
2022-10-29 00:32:18 +02:00
constraints = [
models.UniqueConstraint(
Lower("name"), "player", name="unique_character_player"
)
]
def __str__(self):
return self.name
def natural_key(self):
return (self.name, self.player_id)
2022-10-30 10:12:49 +01:00
def get_absolute_url(self):
return reverse("character:view", kwargs={"pk": self.pk})
2022-10-29 00:32:18 +02:00
@property
def modifier_strength(self) -> int:
return modifier(self.value_strength)
@property
def modifier_dexterity(self) -> int:
return modifier(self.value_dexterity)
@property
def modifier_constitution(self) -> int:
return modifier(self.value_constitution)
@property
def modifier_intelligence(self) -> int:
return modifier(self.value_intelligence)
@property
def modifier_wisdom(self) -> int:
return modifier(self.value_wisdom)
@property
def modifier_charisma(self) -> int:
return modifier(self.value_charisma)
@property
def initiative(self) -> int:
return self.value_dexterity
@property
def attack_melee(self) -> int:
return self.level + self.modifier_strength
@property
def attack_range(self) -> int:
return self.level + self.modifier_dexterity
@property
def attack_magic(self) -> int:
modifier_map = {
Profile.MagicalStrength.INTELLIGENCE: self.modifier_intelligence,
Profile.MagicalStrength.WISDOM: self.modifier_wisdom,
Profile.MagicalStrength.CHARISMA: self.modifier_charisma,
2022-10-30 09:30:54 +01:00
Profile.MagicalStrength.NONE: 0,
2022-10-29 00:32:18 +02:00
}
return self.level + modifier_map.get(
Profile.MagicalStrength(self.profile.magical_strength)
)
@property
def defense(self) -> int:
return (
10 + self.armor + self.shield + self.modifier_dexterity + self.defense_misc
)
@property
def mana_max(self) -> int:
return 2 * self.level + self.modifier_intelligence
2022-10-30 10:54:28 +01:00
@property
def height_m(self) -> float:
return round(self.height / 100, 2)
@property
def imc(self) -> float:
return self.weight / (self.height_m**2)
2022-10-30 16:33:28 +01:00
2022-10-30 21:55:05 +01:00
@property
def recovery_points_max(self) -> int:
return 5
2022-10-30 16:33:28 +01:00
def get_capabilities_by_path(self) -> dict[Path, list[Capability]]:
capabilities_by_path = collections.defaultdict(list)
for capability in self.capabilities.all():
capabilities_by_path[capability.path].append(capability)
return dict(
sorted(
(
(path, sorted(capabilities, key=lambda x: x.rank))
for path, capabilities in capabilities_by_path.items()
),
key=lambda x: x[0].name,
)
)