app_lisium/main/models.py
2026-05-06 18:35:19 -05:00

632 lines
22 KiB
Python

from django.db import models
import uuid
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
"""
Custom user model that extends AbstractUser, adding a UUID field
for unique identification and linking to other tables.
"""
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
def __str__(self):
return self.username
def check_requirements(character, requirements):
for requirement in requirements:
char_prop = getattr(character, requirement['property'])
#print(char_prop, requirement['value'], requirement['condition'])
if requirement['condition'] == "==":
#print("==")
if requirement['value'] != char_prop:
return False
if requirement['condition'] == "!=":
#print("!=")
if requirement['value'] == char_prop:
return False
if requirement['condition'] == "<=":
#print("<=")
if requirement['value'] < char_prop:
return False
if requirement['condition'] == ">=":
#print(">=")
if requirement['value'] > char_prop:
return False
return True
def get_operations_for_hp_max(character, attr):
ops = []
for feature in character.hp_features:
if check_requirements(character, feature['feature_requirements']):
feature_ops = feature['feature_data'].get('operations', [])
for op in feature_ops:
if op['attr'] == attr:
ops.append(op)
return ops
def get_operations_for_attr(character, attr):
ops = []
for feature in character.features.all():
feature_ops = feature.feature_data.get('operations', [])
if check_requirements(character, feature.feature_requirements):
for op in feature_ops:
if op['attr'] == attr:
ops.append(op)
return ops
def apply_operations(base, operations, character):
"""Apply a list of operations (add, multiply, set, etc) in order."""
value = base
for op in operations:
if op['operation'] == 'add':
if isinstance(op['value'], int):
value += op['value']
if isinstance(op['value'], str):
value += getattr(character, op['value'])
elif op['operation'] == 'subtract':
if isinstance(op['value'], int):
value -= op['value']
if isinstance(op['value'], str):
value -= getattr(character, op['value'])
elif op['operation'] == 'multiply':
if isinstance(op['value'], int):
value *= op['value']
if isinstance(op['value'], str):
value *= getattr(character, op['value'])
elif op['operation'] == 'divide':
if isinstance(op['value'], int):
value /= op['value']
if isinstance(op['value'], str):
value /= getattr(character, op['value'])
elif op['operation'] == 'set':
if isinstance(op['value'], int):
value = op['value']
if isinstance(op['value'], str):
value = getattr(character, op['value'])
return value
# Character model based on character_template.json
class Character(models.Model):
owner = models.ForeignKey('CustomUser', on_delete=models.CASCADE, related_name='characters')
name = models.CharField(max_length=100)
level = models.IntegerField(default=0)
race = models.ForeignKey('Package', related_name='race_characters', null=True, blank=True, on_delete=models.SET_NULL)
subrace = models.ForeignKey('Package', related_name='subrace_characters', null=True, blank=True, on_delete=models.SET_NULL)
char_class = models.ForeignKey('Package', related_name='class_characters', null=True, blank=True, on_delete=models.SET_NULL)
subclass = models.ForeignKey('Package', related_name='subclass_characters', null=True, blank=True, on_delete=models.SET_NULL)
background = models.ForeignKey('Package', related_name='background_characters', null=True, blank=True, on_delete=models.SET_NULL)
alignment = models.CharField(max_length=50, blank=True)
size = models.CharField(max_length=20, blank=True)
age = models.IntegerField(default=0)
gender = models.CharField(max_length=30, blank=True)
height = models.CharField(max_length=20, blank=True)
weight = models.IntegerField(default=0)
deity = models.CharField(max_length=100, blank=True)
# Relationships to packages and features
packages = models.ManyToManyField('Package', blank=True, related_name='characters')
features = models.ManyToManyField('Feature', blank=True, related_name='characters')
# Base and current stats
strength_base = models.IntegerField(default=0)
dexterity_base = models.IntegerField(default=0)
constitution_base = models.IntegerField(default=0)
intelligence_base = models.IntegerField(default=0)
wisdom_base = models.IntegerField(default=0)
charisma_base = models.IntegerField(default=0)
armor_base = models.IntegerField(default=0)
inspiration = models.BooleanField(default=False)
experience = models.IntegerField(default=0)
# Proficiencies and languages
proficiencies_armor = models.JSONField(default=list, blank=True)
proficiencies_weapons = models.JSONField(default=list, blank=True)
proficiencies_tools = models.JSONField(default=list, blank=True)
languages = models.JSONField(default=list, blank=True)
# Saving throws
strength_save_prof = models.BooleanField(default=False)
dexterity_save_prof = models.BooleanField(default=False)
constitution_save_prof = models.BooleanField(default=False)
intelligence_save_prof = models.BooleanField(default=False)
wisdom_save_prof = models.BooleanField(default=False)
charisma_save_prof = models.BooleanField(default=False)
# HP and combat
hp = models.IntegerField(default=0)
#hp_max = models.IntegerField(default=0)
hp_temp = models.IntegerField(default=0)
hit_die = models.CharField(max_length=20, blank=True)
#initiative = models.IntegerField(default=0)
# Death saves
deathsaves_successes = models.IntegerField(default=0)
deathsaves_failures = models.IntegerField(default=0)
# Resistances
fire_resistance = models.BooleanField(default=False)
poison_resistance = models.BooleanField(default=False)
psychic_resistance = models.BooleanField(default=False)
cold_resistance = models.BooleanField(default=False)
thunder_resistance = models.BooleanField(default=False)
acid_resistance = models.BooleanField(default=False)
force_resistance = models.BooleanField(default=False)
radiant_resistance = models.BooleanField(default=False)
necrotic_resistance = models.BooleanField(default=False)
bludgeoning_resistance = models.BooleanField(default=False)
piercing_resistance = models.BooleanField(default=False)
slashing_resistance = models.BooleanField(default=False)
immunities = models.JSONField(default=list, blank=True)
vulnerabilities = models.JSONField(default=list, blank=True)
# Movement & vision
speed_base = models.IntegerField(default=0)
speed_type = models.JSONField(default=list, blank=True)
darkvision = models.IntegerField(default=0)
blindsight = models.IntegerField(default=0)
tremorsense = models.IntegerField(default=0)
truesight = models.IntegerField(default=0)
# Spellcasting
spellcasting_ability = models.CharField(max_length=50, blank=True)
spell_save_dc = models.IntegerField(default=0)
spell_attack_bonus = models.IntegerField(default=0)
known_spells = models.JSONField(default=list, blank=True)
prepared_spells = models.JSONField(default=list, blank=True)
spell_slots = models.JSONField(default=dict, blank=True)
cantrips = models.JSONField(default=list, blank=True)
spellcasting_class = models.CharField(max_length=100, blank=True)
# Exhaustion, conditions, notes, and attributes
exhaustion_level = models.IntegerField(default=0)
conditions_active = models.JSONField(default=list, blank=True)
cp = models.IntegerField(default=0)
sp = models.IntegerField(default=0)
ep = models.IntegerField(default=0)
gp = models.IntegerField(default=0)
pp = models.IntegerField(default=0)
equipment = models.JSONField(default=list, blank=True)
traits = models.JSONField(default=list, blank=True)
personality_traits = models.JSONField(default=list, blank=True)
ideals = models.JSONField(default=list, blank=True)
bonds = models.JSONField(default=list, blank=True)
flaws = models.JSONField(default=list, blank=True)
notes = models.TextField(blank=True)
hp_features = models.JSONField(default=list, blank=True)
custom_attributes = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.name} (Level {self.level})"
@classmethod
def from_db(cls, db, field_names, values):
instance = super().from_db(db, field_names, values)
# Here, do your consolidation logic:
instance.feature_operations = instance.build_feature_operations_dict()
return instance
def build_feature_operations_dict(self):
feature_dict = {}
#Build HP
for feature in self.hp_features:
feature_requirements = feature['feature_requirements']
if check_requirements(self, feature_requirements):
for operation in feature['feature_data'].get('operations', []):
if 'requirements' in operation:
if check_requirements(self, operation['requirements']):
feature_dict.setdefault(operation['attr'], []).append(operation)
else:
feature_dict.setdefault(operation['attr'], []).append(operation)
#Build features
for feature in self.features.all():
feature_requirements = feature.feature_requirements
if check_requirements(self, feature_requirements):
for operation in feature.feature_data.get('operations', []):
if 'requirements' in operation:
if check_requirements(self, operation['requirements']):
feature_dict.setdefault(operation['attr'], []).append(operation)
else:
feature_dict.setdefault(operation['attr'], []).append(operation)
print(self.packages.all())
# Build race
if self.race != None:
for feature in self.race.features.all():
feature_requirements = feature.feature_requirements
if check_requirements(self, feature_requirements):
for operation in feature.feature_data.get('operations', []):
if 'requirements' in operation:
if check_requirements(self, operation['requirements']):
feature_dict.setdefault(operation['attr'], []).append(operation)
else:
feature_dict.setdefault(operation['attr'], []).append(operation)
for feature in feature_dict:
print(feature)
return feature_dict
def stat_total(self, attr):
base = getattr(self, f"{attr}_base")
ops = self.feature_operations.get(attr, [])
return apply_operations(base, ops, self)
@property
def proficiency(self):
lvl = getattr(self, 'level')
return 2 + (lvl-1) // 4
@property
def strength(self):
return self.stat_total('strength')
@property
def strength_modifier(self):
return (getattr(self, 'strength')-10) // 2
@property
def strength_save(self):
sav = getattr(self, 'strength_modifier')
if getattr(self, 'strength_save_prof'):
return getattr(self, 'proficiency') + sav
return sav
@property
def dexterity(self):
return self.stat_total('dexterity')
@property
def dexterity_modifier(self):
return (getattr(self, 'dexterity')-10) //2
@property
def dexterity_save(self):
sav = getattr(self, 'dexterity_modifier')
if getattr(self, 'dexterity_save_prof'):
return getattr(self, 'proficiency') + sav
return sav
@property
def constitution(self):
return self.stat_total('constitution')
@property
def constitution_modifier(self):
return (getattr(self, 'constitution')-10) //2
@property
def constitution_save(self):
sav = getattr(self, 'constitution_modifier')
if getattr(self, 'constitution_save_prof'):
return getattr(self, 'proficiency') + sav
return sav
@property
def intelligence(self):
return self.stat_total('intelligence')
@property
def intelligence_modifier(self):
return (getattr(self, 'intelligence')-10) //2
@property
def intelligence_save(self):
sav = getattr(self, 'intelligence_modifier')
if getattr(self, 'intelligence_save_prof'):
return getattr(self, 'proficiency') + sav
return sav
@property
def wisdom(self):
return self.stat_total('wisdom')
@property
def wisdom_modifier(self):
return (getattr(self, 'wisdom')-10) //2
@property
def wisdom_save(self):
sav = getattr(self, 'wisdom_modifier')
if getattr(self, 'wisdom_save_prof'):
return getattr(self, 'proficiency') + sav
return sav
@property
def charisma(self):
return self.stat_total('charisma')
@property
def charisma_modifier(self):
return (getattr(self, 'charisma')-10) //2
@property
def charisma_save(self):
sav = getattr(self, 'charisma_modifier')
if getattr(self, 'charisma_save_prof'):
return getattr(self, 'proficiency') + sav
return sav
@property
def armor_class(self):
return self.stat_total('armor_class')
@property
def hp_max(self):
con = getattr(self, 'constitution_modifier')
level = getattr(self, 'level')
base = con * level
ops = self.feature_operations.get('hp_max', [])
print(ops, base)
bonus = apply_operations(base, ops, self)
return bonus
@property
def initiative(self):
return 0
@property
def athletics(self):
ops = get_operations_for_attr(self, 'athletics')
base = getattr(self, 'strength_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus + getattr(self, 'proficiency')
@property
def athletics_passive(self):
return getattr(self, 'athletics') + 10
@property
def acrobatics(self):
ops = get_operations_for_attr(self, 'acrobatics')
base = getattr(self, 'dexterity_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def acrobatics_passive(self):
return getattr(self, 'acrobatics') + 10
@property
def sleight_of_hand(self):
ops = get_operations_for_attr(self, 'sleight_of_hand')
base = getattr(self, 'dexterity_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def sleight_of_hand_passive(self):
return getattr(self, 'sleight_of_hand') + 10
@property
def stealth(self):
ops = get_operations_for_attr(self, 'stealth')
base = getattr(self, 'dexterity_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def stealth_passive(self):
return getattr(self, 'stealth') + 10
@property
def arcana(self):
ops = get_operations_for_attr(self, 'arcana')
base = getattr(self, 'intelligence_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def arcana_passive(self):
return getattr(self, 'arcana') + 10
@property
def history(self):
ops = get_operations_for_attr(self, 'history')
base = getattr(self, 'intelligence_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def history_passive(self):
return getattr(self, 'history') + 10
@property
def investigation(self):
ops = get_operations_for_attr(self, 'investigation')
base = getattr(self, 'intelligence_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def investigation_passive(self):
return getattr(self, 'investigation') + 10
@property
def nature(self):
ops = get_operations_for_attr(self, 'nature')
base = getattr(self, 'intelligence_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def nature_passive(self):
return getattr(self, 'nature') + 10
@property
def religion(self):
ops = get_operations_for_attr(self, 'religion')
base = getattr(self, 'intelligence_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def religion_passive(self):
return getattr(self, 'religion') + 10
@property
def animal_handling(self):
ops = get_operations_for_attr(self, 'animal_handling')
base = getattr(self, 'wisdom_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def animal_handling_passive(self):
return getattr(self, 'animal_handling') + 10
@property
def insight(self):
ops = get_operations_for_attr(self, 'insight')
base = getattr(self, 'wisdom_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def insight_passive(self):
return getattr(self, 'insight') + 10
@property
def medicine(self):
ops = get_operations_for_attr(self, 'medicine')
base = getattr(self, 'wisdom_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def medicine_passive(self):
return getattr(self, 'medicine') + 10
@property
def perception(self):
ops = get_operations_for_attr(self, 'perception')
base = getattr(self, 'wisdom_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def perception_passive(self):
return getattr(self, 'perception') + 10
@property
def survival(self):
ops = get_operations_for_attr(self, 'survival')
base = getattr(self, 'wisdom_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def survival_passive(self):
return getattr(self, 'survival') + 10
@property
def deception(self):
ops = get_operations_for_attr(self, 'deception')
base = getattr(self, 'charisma_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def deception_passive(self):
return getattr(self, 'deception') + 10
@property
def intimidation(self):
ops = get_operations_for_attr(self, 'intimidation')
base = getattr(self, 'charisma_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def intimidation_passive(self):
return getattr(self, 'intimidation') + 10
@property
def performance(self):
ops = get_operations_for_attr(self, 'performance')
base = getattr(self, 'charisma_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def performance_passive(self):
return getattr(self, 'performance') + 10
@property
def persuasion(self):
ops = get_operations_for_attr(self, 'persuasion')
base = getattr(self, 'charisma_modifier')
bonus = apply_operations(0, ops, self)
return base + bonus
@property
def persuasion_passive(self):
return getattr(self, 'persuasion') + 10
# Feature model based on feature_template.json
class Feature(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
feature_name = models.CharField(max_length=200)
feature_description = models.TextField(blank=True)
feature_requirements = models.JSONField(default=list, blank=True)
#feature requirements requires 3 keys, {"property", "value", "condition"}
feature_data = models.JSONField(default=dict, blank=True)
#feature_data holds a value of {"operations", "sources"}
# ----> operations is a list of dictionaries that have to have 6 values
# ----> {"attr", "value", "operation", "limits", "operation_requirements", "priority"}
def __str__(self):
return self.feature_name
# Package <-> Feature through model for priorities
class PackageFeature(models.Model):
package = models.ForeignKey('Package', on_delete=models.CASCADE)
feature = models.ForeignKey('Feature', on_delete=models.CASCADE)
priority = models.IntegerField(default=0)
requirements_override = models.JSONField(blank=True, null=True)
class Meta:
unique_together = ('package', 'feature')
ordering = ['priority']
def __str__(self):
return f"{self.package} - {self.feature} (priority {self.priority})"
# Package model based on package_template.json
class Package(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
package_name = models.CharField(max_length=200)
package_description = models.TextField(blank=True)
package_type = models.CharField(max_length=100, blank=True)
package_doc_md = models.TextField(blank=True)
features = models.ManyToManyField('Feature', through='PackageFeature', blank=True, related_name='packages')
def __str__(self):
return self.package_name
# Create your models here.
class Pin(models.Model):
label = models.CharField(max_length=100)
url = models.URLField(max_length=300)
x = models.FloatField(help_text="X position as percentage (0-100)")
y = models.FloatField(help_text="Y position as percentage (0-100)")
pin_type = models.CharField(max_length=100, default="general")
def as_dict(self):
return {
"label": self.label,
"url": self.url,
"x": self.x,
"y": self.y,
"pin_type": self.pin_type,
}
def __str__(self):
return f"{self.label} ({self.x:.2f}%, {self.y:.2f}%)"