diff --git a/AGENTS.md b/AGENTS.md index 626dab9..82f3707 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,28 +1,25 @@ -# AGENTS.md: Guidelines for Agent Coders (2025) +# AGENTS.md: Agent Coding Guidelines (2025) -## Build/Lint/Test -- Main entry: `python main.py` -- No test suite found; to run a single test (if using pytest): - - `pytest test_file.py::test_func` -- Lint (if installed): `flake8 .` or `pylint main.py` -- Add to `requirements.txt` if you introduce dependencies. -- No shell/env scripts found by default. +## Build, Lint, and Test Commands +- Main entry point: `python main.py` +- Start dev server: `python manage.py runserver` +- Run all tests: `pytest main/tests.py` +- Run a single test: `pytest main/tests.py::test_func` +- Lint code (if installed): `flake8 .` or `pylint main.py` +- Add new dependencies: update `requirements.txt` -## Python Code Style -- Follow [PEP8](https://pep8.org/) (4 spaces, ≤79 chars/line) -- Import order: stdlib, third-party, project/local -- Never use wildcard imports; always explicit -- Naming: snake_case for vars/functions, PascalCase for classes, UPPER_SNAKE_CASE for constants -- Add type annotations and docstrings to all public APIs/classes -- Handle errors with try/except; log or re-raise as needed -- One statement per line; trim trailing whitespace -- Avoid global state. Organize code into functions/classes. -- Use `if __name__ == '__main__'` to guard scripts. +## Python & Django Style Guide +- Follow [PEP8](https://pep8.org/): 4 spaces/indent, ≤79 chars/line +- Import order: stdlib, third-party, then project/local modules +- Use explicit imports; do NOT use wildcard imports +- Naming: snake_case (vars/functions), PascalCase (classes), UPPER_SNAKE_CASE (constants) +- All public classes/APIs require type annotations and docstrings +- Django models/views should have descriptive docstrings +- Handle errors with try/except; log, re-raise, or message as appropriate +- One statement per line, trim trailing whitespace +- Avoid global state; use functions/classes, avoid module-level code +- Guard standalone scripts with `if __name__ == '__main__':` +- No Cursor or Copilot rules present as of 2025 +- Reference `.github/instructions/memory.instruction.md` for user customizations -- No Cursor/.Copilot rules found (Dec 2025) - -(Update as project conventions evolve) - - -## Custom Rules - - Ensure that you guide me to code myself and not write code that I should learn to do myself. \ No newline at end of file +(Update and evolve as practices change) diff --git a/character_template.json b/character_template.json new file mode 100644 index 0000000..647ece7 --- /dev/null +++ b/character_template.json @@ -0,0 +1,157 @@ +{ + "name": "", + "level": 0, + "race": "", + "subrace": "", + "class": "", + "subclass": "", + "background": "", + "alignment": "", + "size": "", + "age": 0, + "gender": "", + "height": "", + "weight": 0, + "deity": "", + + "strength_base": 0, + "dexterity_base": 0, + "constitution_base": 0, + "intelligence_base": 0, + "wisdom_base": 0, + "charisma_base": 0, + "armor_base": 0, + + "strength": 0, + "dexterity": 0, + "constitution": 0, + "intelligence": 0, + "wisdom": 0, + "charisma": 0, + "armor": 0, + + "strength_modifier": 0, + "dexterity_modifier": 0, + "constitution_modifier": 0, + "intelligence_modifier": 0, + "wisdom_modifier": 0, + "charisma_modifier": 0, + + "proficiency": 0, + "inspiration": false, + "experience": 0, + + "proficiencies_armor": [], + "proficiencies_weapons": [], + "proficiencies_tools": [], + "languages": [], + + "strength_save": 0, + "dexterity_save": 0, + "constitution_save": 0, + "intelligence_save": 0, + "wisdom_save": 0, + "charisma_save": 0, + + "hp": 0, + "hp_max": 0, + "hp_temp": 0, + "hit_die": "", + "initiative": 0, + + "deathsaves_successes": 0, + "deathsaves_failures": 0, + + "fire_resistance": false, + "poison_resistance": false, + "psychic_resistance": false, + "cold_resistance": false, + "thunder_resistance": false, + "acid_resistance": false, + "force_resistance": false, + "radiant_resistance": false, + "necrotic_resistance": false, + "bludgeoning_resistance": false, + "piercing_resistance": false, + "slashing_resistance": false, + "immunities": [], + "vulnerabilities": [], + + "speed_base": 0, + "speed_type": [], + "darkvision": 0, + "blindsight": 0, + "tremorsense": 0, + "truesight": 0, + + "athletics": 0, + "acrobatics": 0, + "sleight_of_hand": 0, + "stealth": 0, + "arcana": 0, + "history": 0, + "investigation": 0, + "nature": 0, + "religion": 0, + "animal_handling": 0, + "insight": 0, + "medicine": 0, + "perception": 0, + "survival": 0, + "deception": 0, + "intimidation": 0, + "performance": 0, + "persuasion": 0, + + "athletics_passive": 0, + "acrobatics_passive": 0, + "sleight_of_hand_passive": 0, + "stealth_passive": 0, + "arcana_passive": 0, + "history_passive": 0, + "investigation_passive": 0, + "nature_passive": 0, + "religion_passive": 0, + "animal_handling_passive": 0, + "insight_passive": 0, + "medicine_passive": 0, + "perception_passive": 0, + "survival_passive": 0, + "deception_passive": 0, + "intimidation_passive": 0, + "performance_passive": 0, + "persuasion_passive": 0, + "passive_perception": 0, + "passive_investigation": 0, + "passive_insight": 0, + + "spellcasting_ability": "", + "spell_save_dc": 0, + "spell_attack_bonus": 0, + "known_spells": [], + "prepared_spells": [], + "spell_slots": {}, + "cantrips": [], + "spellcasting_class": "", + + "exhaustion_level": 0, + "conditions_active": [], + + "cp": 0, + "sp": 0, + "ep": 0, + "gp": 0, + "pp": 0, + "equipment": [], + + "packages": [], + "features": [], + "traits": [], + "personality_traits": [], + "ideals": [], + "bonds": [], + "flaws": [], + + "notes": "", + "custom_attributes": {} +} diff --git a/db.sqlite3 b/db.sqlite3 index e69de29..e5b772a 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/feature_template.json b/feature_template.json new file mode 100644 index 0000000..a1d26d8 --- /dev/null +++ b/feature_template.json @@ -0,0 +1,7 @@ +{ + "feature_name": "", + "feature_description": "", + "feature_data": { + "effects": [] + } +} \ No newline at end of file diff --git a/lisiumsite/__pycache__/__init__.cpython-314.pyc b/lisiumsite/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..bb77469 Binary files /dev/null and b/lisiumsite/__pycache__/__init__.cpython-314.pyc differ diff --git a/lisiumsite/__pycache__/settings.cpython-313.pyc b/lisiumsite/__pycache__/settings.cpython-313.pyc index 6931bad..460a789 100644 Binary files a/lisiumsite/__pycache__/settings.cpython-313.pyc and b/lisiumsite/__pycache__/settings.cpython-313.pyc differ diff --git a/lisiumsite/__pycache__/settings.cpython-314.pyc b/lisiumsite/__pycache__/settings.cpython-314.pyc new file mode 100644 index 0000000..bb99e14 Binary files /dev/null and b/lisiumsite/__pycache__/settings.cpython-314.pyc differ diff --git a/lisiumsite/__pycache__/urls.cpython-314.pyc b/lisiumsite/__pycache__/urls.cpython-314.pyc new file mode 100644 index 0000000..0ee577c Binary files /dev/null and b/lisiumsite/__pycache__/urls.cpython-314.pyc differ diff --git a/lisiumsite/__pycache__/wsgi.cpython-314.pyc b/lisiumsite/__pycache__/wsgi.cpython-314.pyc new file mode 100644 index 0000000..66498e3 Binary files /dev/null and b/lisiumsite/__pycache__/wsgi.cpython-314.pyc differ diff --git a/lisiumsite/settings.py b/lisiumsite/settings.py index 9fb89f3..5cb7fc9 100644 --- a/lisiumsite/settings.py +++ b/lisiumsite/settings.py @@ -25,7 +25,7 @@ SECRET_KEY = 'django-insecure-c@*)_9zq1%@%nv+q18ji6k^ixf(^!7@**di7_r-s5q33m@&w)q # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['192.168.1.45', '127.0.0.1'] # Application definition @@ -38,6 +38,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'main', + 'rest_framework', ] MIDDLEWARE = [ @@ -81,6 +82,9 @@ DATABASES = { } +# Use custom user model with uuid field +AUTH_USER_MODEL = 'main.CustomUser' + # Password validation # https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators @@ -115,4 +119,7 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/6.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = '/static/' +STATICFILES_DIRS = [ + BASE_DIR / "static", # or os.path.join(BASE_DIR, "static") for Django <4 +] diff --git a/main/__pycache__/__init__.cpython-314.pyc b/main/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..1b5a88a Binary files /dev/null and b/main/__pycache__/__init__.cpython-314.pyc differ diff --git a/main/__pycache__/admin.cpython-313.pyc b/main/__pycache__/admin.cpython-313.pyc index 9729497..dd3c31a 100644 Binary files a/main/__pycache__/admin.cpython-313.pyc and b/main/__pycache__/admin.cpython-313.pyc differ diff --git a/main/__pycache__/admin.cpython-314.pyc b/main/__pycache__/admin.cpython-314.pyc new file mode 100644 index 0000000..6ed249c Binary files /dev/null and b/main/__pycache__/admin.cpython-314.pyc differ diff --git a/main/__pycache__/apps.cpython-314.pyc b/main/__pycache__/apps.cpython-314.pyc new file mode 100644 index 0000000..e7a4cd8 Binary files /dev/null and b/main/__pycache__/apps.cpython-314.pyc differ diff --git a/main/__pycache__/models.cpython-313.pyc b/main/__pycache__/models.cpython-313.pyc index 63a8b93..1c0cdd8 100644 Binary files a/main/__pycache__/models.cpython-313.pyc and b/main/__pycache__/models.cpython-313.pyc differ diff --git a/main/__pycache__/models.cpython-314.pyc b/main/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000..5c6715c Binary files /dev/null and b/main/__pycache__/models.cpython-314.pyc differ diff --git a/main/__pycache__/serializers.cpython-313.pyc b/main/__pycache__/serializers.cpython-313.pyc new file mode 100644 index 0000000..9510f7d Binary files /dev/null and b/main/__pycache__/serializers.cpython-313.pyc differ diff --git a/main/__pycache__/serializers.cpython-314.pyc b/main/__pycache__/serializers.cpython-314.pyc new file mode 100644 index 0000000..8d8538a Binary files /dev/null and b/main/__pycache__/serializers.cpython-314.pyc differ diff --git a/main/__pycache__/urls.cpython-313.pyc b/main/__pycache__/urls.cpython-313.pyc index 971a9fa..e3d9c64 100644 Binary files a/main/__pycache__/urls.cpython-313.pyc and b/main/__pycache__/urls.cpython-313.pyc differ diff --git a/main/__pycache__/urls.cpython-314.pyc b/main/__pycache__/urls.cpython-314.pyc new file mode 100644 index 0000000..c394f83 Binary files /dev/null and b/main/__pycache__/urls.cpython-314.pyc differ diff --git a/main/__pycache__/views.cpython-313.pyc b/main/__pycache__/views.cpython-313.pyc index c781ed7..6fa4acc 100644 Binary files a/main/__pycache__/views.cpython-313.pyc and b/main/__pycache__/views.cpython-313.pyc differ diff --git a/main/__pycache__/views.cpython-314.pyc b/main/__pycache__/views.cpython-314.pyc new file mode 100644 index 0000000..58cbd29 Binary files /dev/null and b/main/__pycache__/views.cpython-314.pyc differ diff --git a/main/admin.py b/main/admin.py index 8c38f3f..107cafd 100644 --- a/main/admin.py +++ b/main/admin.py @@ -1,3 +1,42 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import CustomUser, Character, Feature, Package, PackageFeature, Pin -# Register your models here. +class CustomUserAdmin(UserAdmin): + list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'uuid') + readonly_fields = ('uuid',) + fieldsets = UserAdmin.fieldsets + ( + (None, {'fields': ('uuid',)}), + ) + +admin.site.register(CustomUser, CustomUserAdmin) +admin.site.register(Character) + +class PackageFeatureInline(admin.TabularInline): + model = PackageFeature + extra = 1 + autocomplete_fields = ['feature'] + fields = ['feature', 'priority'] + ordering = ['priority'] + +@admin.register(Package) +class PackageAdmin(admin.ModelAdmin): + inlines = [PackageFeatureInline] + list_display = ['package_name', 'package_type'] + search_fields = ['package_name', 'package_type'] + +@admin.register(Feature) +class FeatureAdmin(admin.ModelAdmin): + list_display = ['feature_name'] + search_fields = ['feature_name'] + +@admin.register(PackageFeature) +class PackageFeatureAdmin(admin.ModelAdmin): + list_display = ['package', 'feature', 'priority'] + list_filter = ['package'] + search_fields = ['package__package_name', 'feature__feature_name'] + +@admin.register(Pin) +class PinAdmin(admin.ModelAdmin): + list_display = ('label', 'url', 'x', 'y') + search_fields = ('label', 'url') diff --git a/main/migrations/0001_initial.py b/main/migrations/0001_initial.py new file mode 100644 index 0000000..c998229 --- /dev/null +++ b/main/migrations/0001_initial.py @@ -0,0 +1,231 @@ +# Generated by Django 6.0 on 2025-12-14 02:30 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.db.models.deletion +import django.utils.timezone +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Feature', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('feature_name', models.CharField(max_length=200)), + ('feature_description', models.TextField(blank=True)), + ('feature_data', models.JSONField(blank=True, default=dict)), + ], + ), + migrations.CreateModel( + name='Package', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('package_name', models.CharField(max_length=200)), + ('package_description', models.TextField(blank=True)), + ('package_type', models.CharField(blank=True, max_length=100)), + ('package_doc_md', models.TextField(blank=True)), + ], + ), + migrations.CreateModel( + name='CustomUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name='Character', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('level', models.IntegerField(default=0)), + ('alignment', models.CharField(blank=True, max_length=50)), + ('size', models.CharField(blank=True, max_length=20)), + ('age', models.IntegerField(default=0)), + ('gender', models.CharField(blank=True, max_length=30)), + ('height', models.CharField(blank=True, max_length=20)), + ('weight', models.IntegerField(default=0)), + ('deity', models.CharField(blank=True, max_length=100)), + ('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)), + ('strength', models.IntegerField(default=0)), + ('dexterity', models.IntegerField(default=0)), + ('constitution', models.IntegerField(default=0)), + ('intelligence', models.IntegerField(default=0)), + ('wisdom', models.IntegerField(default=0)), + ('charisma', models.IntegerField(default=0)), + ('armor', models.IntegerField(default=0)), + ('strength_modifier', models.IntegerField(default=0)), + ('dexterity_modifier', models.IntegerField(default=0)), + ('constitution_modifier', models.IntegerField(default=0)), + ('intelligence_modifier', models.IntegerField(default=0)), + ('wisdom_modifier', models.IntegerField(default=0)), + ('charisma_modifier', models.IntegerField(default=0)), + ('proficiency', models.IntegerField(default=0)), + ('inspiration', models.BooleanField(default=False)), + ('experience', models.IntegerField(default=0)), + ('proficiencies_armor', models.JSONField(blank=True, default=list)), + ('proficiencies_weapons', models.JSONField(blank=True, default=list)), + ('proficiencies_tools', models.JSONField(blank=True, default=list)), + ('languages', models.JSONField(blank=True, default=list)), + ('strength_save', models.IntegerField(default=0)), + ('dexterity_save', models.IntegerField(default=0)), + ('constitution_save', models.IntegerField(default=0)), + ('intelligence_save', models.IntegerField(default=0)), + ('wisdom_save', models.IntegerField(default=0)), + ('charisma_save', models.IntegerField(default=0)), + ('hp', models.IntegerField(default=0)), + ('hp_max', models.IntegerField(default=0)), + ('hp_temp', models.IntegerField(default=0)), + ('hit_die', models.CharField(blank=True, max_length=20)), + ('initiative', models.IntegerField(default=0)), + ('deathsaves_successes', models.IntegerField(default=0)), + ('deathsaves_failures', models.IntegerField(default=0)), + ('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(blank=True, default=list)), + ('vulnerabilities', models.JSONField(blank=True, default=list)), + ('speed_base', models.IntegerField(default=0)), + ('speed_type', models.JSONField(blank=True, default=list)), + ('darkvision', models.IntegerField(default=0)), + ('blindsight', models.IntegerField(default=0)), + ('tremorsense', models.IntegerField(default=0)), + ('truesight', models.IntegerField(default=0)), + ('athletics', models.IntegerField(default=0)), + ('acrobatics', models.IntegerField(default=0)), + ('sleight_of_hand', models.IntegerField(default=0)), + ('stealth', models.IntegerField(default=0)), + ('arcana', models.IntegerField(default=0)), + ('history', models.IntegerField(default=0)), + ('investigation', models.IntegerField(default=0)), + ('nature', models.IntegerField(default=0)), + ('religion', models.IntegerField(default=0)), + ('animal_handling', models.IntegerField(default=0)), + ('insight', models.IntegerField(default=0)), + ('medicine', models.IntegerField(default=0)), + ('perception', models.IntegerField(default=0)), + ('survival', models.IntegerField(default=0)), + ('deception', models.IntegerField(default=0)), + ('intimidation', models.IntegerField(default=0)), + ('performance', models.IntegerField(default=0)), + ('persuasion', models.IntegerField(default=0)), + ('athletics_passive', models.IntegerField(default=0)), + ('acrobatics_passive', models.IntegerField(default=0)), + ('sleight_of_hand_passive', models.IntegerField(default=0)), + ('stealth_passive', models.IntegerField(default=0)), + ('arcana_passive', models.IntegerField(default=0)), + ('history_passive', models.IntegerField(default=0)), + ('investigation_passive', models.IntegerField(default=0)), + ('nature_passive', models.IntegerField(default=0)), + ('religion_passive', models.IntegerField(default=0)), + ('animal_handling_passive', models.IntegerField(default=0)), + ('insight_passive', models.IntegerField(default=0)), + ('medicine_passive', models.IntegerField(default=0)), + ('perception_passive', models.IntegerField(default=0)), + ('survival_passive', models.IntegerField(default=0)), + ('deception_passive', models.IntegerField(default=0)), + ('intimidation_passive', models.IntegerField(default=0)), + ('performance_passive', models.IntegerField(default=0)), + ('persuasion_passive', models.IntegerField(default=0)), + ('passive_perception', models.IntegerField(default=0)), + ('passive_investigation', models.IntegerField(default=0)), + ('passive_insight', models.IntegerField(default=0)), + ('spellcasting_ability', models.CharField(blank=True, max_length=50)), + ('spell_save_dc', models.IntegerField(default=0)), + ('spell_attack_bonus', models.IntegerField(default=0)), + ('known_spells', models.JSONField(blank=True, default=list)), + ('prepared_spells', models.JSONField(blank=True, default=list)), + ('spell_slots', models.JSONField(blank=True, default=dict)), + ('cantrips', models.JSONField(blank=True, default=list)), + ('spellcasting_class', models.CharField(blank=True, max_length=100)), + ('exhaustion_level', models.IntegerField(default=0)), + ('conditions_active', models.JSONField(blank=True, default=list)), + ('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(blank=True, default=list)), + ('features', models.JSONField(blank=True, default=list)), + ('traits', models.JSONField(blank=True, default=list)), + ('personality_traits', models.JSONField(blank=True, default=list)), + ('ideals', models.JSONField(blank=True, default=list)), + ('bonds', models.JSONField(blank=True, default=list)), + ('flaws', models.JSONField(blank=True, default=list)), + ('notes', models.TextField(blank=True)), + ('custom_attributes', models.JSONField(blank=True, default=dict)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='characters', to=settings.AUTH_USER_MODEL)), + ('background', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='background_characters', to='main.package')), + ('char_class', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='class_characters', to='main.package')), + ('packages', models.ManyToManyField(blank=True, related_name='characters', to='main.package')), + ('race', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='race_characters', to='main.package')), + ('subclass', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subclass_characters', to='main.package')), + ('subrace', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subrace_characters', to='main.package')), + ], + ), + migrations.CreateModel( + name='PackageFeature', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('priority', models.IntegerField(default=0)), + ('feature', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.feature')), + ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.package')), + ], + options={ + 'ordering': ['priority'], + 'unique_together': {('package', 'feature')}, + }, + ), + migrations.AddField( + model_name='package', + name='features', + field=models.ManyToManyField(blank=True, related_name='packages', through='main.PackageFeature', to='main.feature'), + ), + ] diff --git a/main/migrations/0002_pin.py b/main/migrations/0002_pin.py new file mode 100644 index 0000000..0c97808 --- /dev/null +++ b/main/migrations/0002_pin.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0 on 2025-12-14 16:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Pin', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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)')), + ], + ), + ] diff --git a/main/migrations/0003_pin_pin_type.py b/main/migrations/0003_pin_pin_type.py new file mode 100644 index 0000000..93e6f15 --- /dev/null +++ b/main/migrations/0003_pin_pin_type.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0 on 2025-12-14 18:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0002_pin'), + ] + + operations = [ + migrations.AddField( + model_name='pin', + name='pin_type', + field=models.CharField(default='general', max_length=100), + ), + ] diff --git a/main/migrations/0004_remove_character_features_character_features.py b/main/migrations/0004_remove_character_features_character_features.py new file mode 100644 index 0000000..2b28ea0 --- /dev/null +++ b/main/migrations/0004_remove_character_features_character_features.py @@ -0,0 +1,22 @@ +# Generated by Django 6.0 on 2025-12-14 21:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0003_pin_pin_type'), + ] + + operations = [ + migrations.RemoveField( + model_name='character', + name='features', + ), + migrations.AddField( + model_name='character', + name='features', + field=models.ManyToManyField(blank=True, related_name='characters', to='main.feature'), + ), + ] diff --git a/main/migrations/0005_remove_character_armor_remove_character_charisma_and_more.py b/main/migrations/0005_remove_character_armor_remove_character_charisma_and_more.py new file mode 100644 index 0000000..b78653d --- /dev/null +++ b/main/migrations/0005_remove_character_armor_remove_character_charisma_and_more.py @@ -0,0 +1,123 @@ +# Generated by Django 6.0 on 2025-12-14 21:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0004_remove_character_features_character_features'), + ] + + operations = [ + migrations.RemoveField( + model_name='character', + name='armor', + ), + migrations.RemoveField( + model_name='character', + name='charisma', + ), + migrations.RemoveField( + model_name='character', + name='charisma_modifier', + ), + migrations.RemoveField( + model_name='character', + name='charisma_save', + ), + migrations.RemoveField( + model_name='character', + name='constitution', + ), + migrations.RemoveField( + model_name='character', + name='constitution_modifier', + ), + migrations.RemoveField( + model_name='character', + name='constitution_save', + ), + migrations.RemoveField( + model_name='character', + name='dexterity', + ), + migrations.RemoveField( + model_name='character', + name='dexterity_modifier', + ), + migrations.RemoveField( + model_name='character', + name='dexterity_save', + ), + migrations.RemoveField( + model_name='character', + name='intelligence', + ), + migrations.RemoveField( + model_name='character', + name='intelligence_modifier', + ), + migrations.RemoveField( + model_name='character', + name='intelligence_save', + ), + migrations.RemoveField( + model_name='character', + name='proficiency', + ), + migrations.RemoveField( + model_name='character', + name='strength', + ), + migrations.RemoveField( + model_name='character', + name='strength_modifier', + ), + migrations.RemoveField( + model_name='character', + name='strength_save', + ), + migrations.RemoveField( + model_name='character', + name='wisdom', + ), + migrations.RemoveField( + model_name='character', + name='wisdom_modifier', + ), + migrations.RemoveField( + model_name='character', + name='wisdom_save', + ), + migrations.AddField( + model_name='character', + name='charisma_save_prof', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='character', + name='constitution_save_prof', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='character', + name='dexterity_save_prof', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='character', + name='intelligence_save_prof', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='character', + name='strength_save_prof', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='character', + name='wisdom_save_prof', + field=models.BooleanField(default=False), + ), + ] diff --git a/main/migrations/0006_remove_character_athletics.py b/main/migrations/0006_remove_character_athletics.py new file mode 100644 index 0000000..c23223a --- /dev/null +++ b/main/migrations/0006_remove_character_athletics.py @@ -0,0 +1,17 @@ +# Generated by Django 6.0 on 2025-12-14 21:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0005_remove_character_armor_remove_character_charisma_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='character', + name='athletics', + ), + ] diff --git a/main/migrations/0007_remove_character_acrobatics_and_more.py b/main/migrations/0007_remove_character_acrobatics_and_more.py new file mode 100644 index 0000000..cf88660 --- /dev/null +++ b/main/migrations/0007_remove_character_acrobatics_and_more.py @@ -0,0 +1,165 @@ +# Generated by Django 6.0 on 2025-12-14 22:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0006_remove_character_athletics'), + ] + + operations = [ + migrations.RemoveField( + model_name='character', + name='acrobatics', + ), + migrations.RemoveField( + model_name='character', + name='acrobatics_passive', + ), + migrations.RemoveField( + model_name='character', + name='animal_handling', + ), + migrations.RemoveField( + model_name='character', + name='animal_handling_passive', + ), + migrations.RemoveField( + model_name='character', + name='arcana', + ), + migrations.RemoveField( + model_name='character', + name='arcana_passive', + ), + migrations.RemoveField( + model_name='character', + name='athletics_passive', + ), + migrations.RemoveField( + model_name='character', + name='deception', + ), + migrations.RemoveField( + model_name='character', + name='deception_passive', + ), + migrations.RemoveField( + model_name='character', + name='history', + ), + migrations.RemoveField( + model_name='character', + name='history_passive', + ), + migrations.RemoveField( + model_name='character', + name='insight', + ), + migrations.RemoveField( + model_name='character', + name='insight_passive', + ), + migrations.RemoveField( + model_name='character', + name='intimidation', + ), + migrations.RemoveField( + model_name='character', + name='intimidation_passive', + ), + migrations.RemoveField( + model_name='character', + name='investigation', + ), + migrations.RemoveField( + model_name='character', + name='investigation_passive', + ), + migrations.RemoveField( + model_name='character', + name='medicine', + ), + migrations.RemoveField( + model_name='character', + name='medicine_passive', + ), + migrations.RemoveField( + model_name='character', + name='nature', + ), + migrations.RemoveField( + model_name='character', + name='nature_passive', + ), + migrations.RemoveField( + model_name='character', + name='passive_insight', + ), + migrations.RemoveField( + model_name='character', + name='passive_investigation', + ), + migrations.RemoveField( + model_name='character', + name='passive_perception', + ), + migrations.RemoveField( + model_name='character', + name='perception', + ), + migrations.RemoveField( + model_name='character', + name='perception_passive', + ), + migrations.RemoveField( + model_name='character', + name='performance', + ), + migrations.RemoveField( + model_name='character', + name='performance_passive', + ), + migrations.RemoveField( + model_name='character', + name='persuasion', + ), + migrations.RemoveField( + model_name='character', + name='persuasion_passive', + ), + migrations.RemoveField( + model_name='character', + name='religion', + ), + migrations.RemoveField( + model_name='character', + name='religion_passive', + ), + migrations.RemoveField( + model_name='character', + name='sleight_of_hand', + ), + migrations.RemoveField( + model_name='character', + name='sleight_of_hand_passive', + ), + migrations.RemoveField( + model_name='character', + name='stealth', + ), + migrations.RemoveField( + model_name='character', + name='stealth_passive', + ), + migrations.RemoveField( + model_name='character', + name='survival', + ), + migrations.RemoveField( + model_name='character', + name='survival_passive', + ), + ] diff --git a/main/migrations/0008_remove_character_hp_max_remove_character_initiative.py b/main/migrations/0008_remove_character_hp_max_remove_character_initiative.py new file mode 100644 index 0000000..94de5d5 --- /dev/null +++ b/main/migrations/0008_remove_character_hp_max_remove_character_initiative.py @@ -0,0 +1,21 @@ +# Generated by Django 6.0 on 2025-12-14 23:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0007_remove_character_acrobatics_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='character', + name='hp_max', + ), + migrations.RemoveField( + model_name='character', + name='initiative', + ), + ] diff --git a/main/migrations/0009_character_hp_features.py b/main/migrations/0009_character_hp_features.py new file mode 100644 index 0000000..2725650 --- /dev/null +++ b/main/migrations/0009_character_hp_features.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0 on 2025-12-14 23:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0008_remove_character_hp_max_remove_character_initiative'), + ] + + operations = [ + migrations.AddField( + model_name='character', + name='hp_features', + field=models.JSONField(blank=True, default=dict), + ), + ] diff --git a/main/migrations/0010_alter_character_hp_features.py b/main/migrations/0010_alter_character_hp_features.py new file mode 100644 index 0000000..673e514 --- /dev/null +++ b/main/migrations/0010_alter_character_hp_features.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0 on 2025-12-14 23:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0009_character_hp_features'), + ] + + operations = [ + migrations.AlterField( + model_name='character', + name='hp_features', + field=models.JSONField(blank=True, default=list), + ), + ] diff --git a/main/migrations/0011_feature_feature_requirements.py b/main/migrations/0011_feature_feature_requirements.py new file mode 100644 index 0000000..928a659 --- /dev/null +++ b/main/migrations/0011_feature_feature_requirements.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0 on 2025-12-15 00:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0010_alter_character_hp_features'), + ] + + operations = [ + migrations.AddField( + model_name='feature', + name='feature_requirements', + field=models.JSONField(default=dict), + ), + ] diff --git a/main/migrations/0012_packagefeature_requirements_override.py b/main/migrations/0012_packagefeature_requirements_override.py new file mode 100644 index 0000000..c1b761e --- /dev/null +++ b/main/migrations/0012_packagefeature_requirements_override.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0 on 2025-12-15 00:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0011_feature_feature_requirements'), + ] + + operations = [ + migrations.AddField( + model_name='packagefeature', + name='requirements_override', + field=models.JSONField(blank=True, null=True), + ), + ] diff --git a/main/migrations/0013_alter_feature_feature_requirements.py b/main/migrations/0013_alter_feature_feature_requirements.py new file mode 100644 index 0000000..f5f3212 --- /dev/null +++ b/main/migrations/0013_alter_feature_feature_requirements.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0 on 2025-12-15 01:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0012_packagefeature_requirements_override'), + ] + + operations = [ + migrations.AlterField( + model_name='feature', + name='feature_requirements', + field=models.JSONField(default=list), + ), + ] diff --git a/main/migrations/0014_alter_character_features_alter_character_packages.py b/main/migrations/0014_alter_character_features_alter_character_packages.py new file mode 100644 index 0000000..920457b --- /dev/null +++ b/main/migrations/0014_alter_character_features_alter_character_packages.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.5 on 2026-05-06 22:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0013_alter_feature_feature_requirements'), + ] + + operations = [ + migrations.AlterField( + model_name='character', + name='features', + field=models.ManyToManyField(blank=True, related_name='+', to='main.feature'), + ), + migrations.AlterField( + model_name='character', + name='packages', + field=models.ManyToManyField(blank=True, related_name='+', to='main.package'), + ), + ] diff --git a/main/migrations/0015_alter_character_features_alter_character_packages.py b/main/migrations/0015_alter_character_features_alter_character_packages.py new file mode 100644 index 0000000..fb1df3a --- /dev/null +++ b/main/migrations/0015_alter_character_features_alter_character_packages.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.5 on 2026-05-06 22:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0014_alter_character_features_alter_character_packages'), + ] + + operations = [ + migrations.AlterField( + model_name='character', + name='features', + field=models.ManyToManyField(blank=True, related_name='characters', to='main.feature'), + ), + migrations.AlterField( + model_name='character', + name='packages', + field=models.ManyToManyField(blank=True, related_name='characters', to='main.package'), + ), + ] diff --git a/main/migrations/0016_alter_feature_feature_requirements.py b/main/migrations/0016_alter_feature_feature_requirements.py new file mode 100644 index 0000000..7561c71 --- /dev/null +++ b/main/migrations/0016_alter_feature_feature_requirements.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.5 on 2026-05-06 23:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0015_alter_character_features_alter_character_packages'), + ] + + operations = [ + migrations.AlterField( + model_name='feature', + name='feature_requirements', + field=models.JSONField(blank=True, default=list), + ), + ] diff --git a/main/migrations/__pycache__/0001_initial.cpython-313.pyc b/main/migrations/__pycache__/0001_initial.cpython-313.pyc new file mode 100644 index 0000000..b26e466 Binary files /dev/null and b/main/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0001_initial.cpython-314.pyc b/main/migrations/__pycache__/0001_initial.cpython-314.pyc new file mode 100644 index 0000000..c35b894 Binary files /dev/null and b/main/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0002_character.cpython-313.pyc b/main/migrations/__pycache__/0002_character.cpython-313.pyc new file mode 100644 index 0000000..591079a Binary files /dev/null and b/main/migrations/__pycache__/0002_character.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0002_pin.cpython-313.pyc b/main/migrations/__pycache__/0002_pin.cpython-313.pyc new file mode 100644 index 0000000..8ff9fa1 Binary files /dev/null and b/main/migrations/__pycache__/0002_pin.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0002_pin.cpython-314.pyc b/main/migrations/__pycache__/0002_pin.cpython-314.pyc new file mode 100644 index 0000000..1b905a5 Binary files /dev/null and b/main/migrations/__pycache__/0002_pin.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0003_feature_package_character_packages.cpython-313.pyc b/main/migrations/__pycache__/0003_feature_package_character_packages.cpython-313.pyc new file mode 100644 index 0000000..9e168a7 Binary files /dev/null and b/main/migrations/__pycache__/0003_feature_package_character_packages.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0003_pin_pin_type.cpython-313.pyc b/main/migrations/__pycache__/0003_pin_pin_type.cpython-313.pyc new file mode 100644 index 0000000..55d4e80 Binary files /dev/null and b/main/migrations/__pycache__/0003_pin_pin_type.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0003_pin_pin_type.cpython-314.pyc b/main/migrations/__pycache__/0003_pin_pin_type.cpython-314.pyc new file mode 100644 index 0000000..2b7334c Binary files /dev/null and b/main/migrations/__pycache__/0003_pin_pin_type.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0004_alter_character_background_and_more.cpython-313.pyc b/main/migrations/__pycache__/0004_alter_character_background_and_more.cpython-313.pyc new file mode 100644 index 0000000..1feb260 Binary files /dev/null and b/main/migrations/__pycache__/0004_alter_character_background_and_more.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0004_remove_character_features_character_features.cpython-313.pyc b/main/migrations/__pycache__/0004_remove_character_features_character_features.cpython-313.pyc new file mode 100644 index 0000000..e28ea1b Binary files /dev/null and b/main/migrations/__pycache__/0004_remove_character_features_character_features.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0004_remove_character_features_character_features.cpython-314.pyc b/main/migrations/__pycache__/0004_remove_character_features_character_features.cpython-314.pyc new file mode 100644 index 0000000..56ad837 Binary files /dev/null and b/main/migrations/__pycache__/0004_remove_character_features_character_features.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0005_packagefeature_alter_package_features.cpython-313.pyc b/main/migrations/__pycache__/0005_packagefeature_alter_package_features.cpython-313.pyc new file mode 100644 index 0000000..dd362e1 Binary files /dev/null and b/main/migrations/__pycache__/0005_packagefeature_alter_package_features.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0005_remove_character_armor_remove_character_charisma_and_more.cpython-313.pyc b/main/migrations/__pycache__/0005_remove_character_armor_remove_character_charisma_and_more.cpython-313.pyc new file mode 100644 index 0000000..f9162b3 Binary files /dev/null and b/main/migrations/__pycache__/0005_remove_character_armor_remove_character_charisma_and_more.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0005_remove_character_armor_remove_character_charisma_and_more.cpython-314.pyc b/main/migrations/__pycache__/0005_remove_character_armor_remove_character_charisma_and_more.cpython-314.pyc new file mode 100644 index 0000000..fc6f141 Binary files /dev/null and b/main/migrations/__pycache__/0005_remove_character_armor_remove_character_charisma_and_more.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0006_remove_character_athletics.cpython-313.pyc b/main/migrations/__pycache__/0006_remove_character_athletics.cpython-313.pyc new file mode 100644 index 0000000..d3b5868 Binary files /dev/null and b/main/migrations/__pycache__/0006_remove_character_athletics.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0006_remove_character_athletics.cpython-314.pyc b/main/migrations/__pycache__/0006_remove_character_athletics.cpython-314.pyc new file mode 100644 index 0000000..e73d37b Binary files /dev/null and b/main/migrations/__pycache__/0006_remove_character_athletics.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0007_remove_character_acrobatics_and_more.cpython-313.pyc b/main/migrations/__pycache__/0007_remove_character_acrobatics_and_more.cpython-313.pyc new file mode 100644 index 0000000..af51717 Binary files /dev/null and b/main/migrations/__pycache__/0007_remove_character_acrobatics_and_more.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0007_remove_character_acrobatics_and_more.cpython-314.pyc b/main/migrations/__pycache__/0007_remove_character_acrobatics_and_more.cpython-314.pyc new file mode 100644 index 0000000..ca348e9 Binary files /dev/null and b/main/migrations/__pycache__/0007_remove_character_acrobatics_and_more.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0008_remove_character_hp_max_remove_character_initiative.cpython-313.pyc b/main/migrations/__pycache__/0008_remove_character_hp_max_remove_character_initiative.cpython-313.pyc new file mode 100644 index 0000000..be31a6b Binary files /dev/null and b/main/migrations/__pycache__/0008_remove_character_hp_max_remove_character_initiative.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0008_remove_character_hp_max_remove_character_initiative.cpython-314.pyc b/main/migrations/__pycache__/0008_remove_character_hp_max_remove_character_initiative.cpython-314.pyc new file mode 100644 index 0000000..f8a0a99 Binary files /dev/null and b/main/migrations/__pycache__/0008_remove_character_hp_max_remove_character_initiative.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0009_character_hp_features.cpython-313.pyc b/main/migrations/__pycache__/0009_character_hp_features.cpython-313.pyc new file mode 100644 index 0000000..bfd0461 Binary files /dev/null and b/main/migrations/__pycache__/0009_character_hp_features.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0009_character_hp_features.cpython-314.pyc b/main/migrations/__pycache__/0009_character_hp_features.cpython-314.pyc new file mode 100644 index 0000000..a0f3e8f Binary files /dev/null and b/main/migrations/__pycache__/0009_character_hp_features.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0010_alter_character_hp_features.cpython-313.pyc b/main/migrations/__pycache__/0010_alter_character_hp_features.cpython-313.pyc new file mode 100644 index 0000000..c4e0cfa Binary files /dev/null and b/main/migrations/__pycache__/0010_alter_character_hp_features.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0010_alter_character_hp_features.cpython-314.pyc b/main/migrations/__pycache__/0010_alter_character_hp_features.cpython-314.pyc new file mode 100644 index 0000000..971becc Binary files /dev/null and b/main/migrations/__pycache__/0010_alter_character_hp_features.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0011_feature_feature_requirements.cpython-313.pyc b/main/migrations/__pycache__/0011_feature_feature_requirements.cpython-313.pyc new file mode 100644 index 0000000..db27538 Binary files /dev/null and b/main/migrations/__pycache__/0011_feature_feature_requirements.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0011_feature_feature_requirements.cpython-314.pyc b/main/migrations/__pycache__/0011_feature_feature_requirements.cpython-314.pyc new file mode 100644 index 0000000..b975fc0 Binary files /dev/null and b/main/migrations/__pycache__/0011_feature_feature_requirements.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0012_packagefeature_requirements_override.cpython-313.pyc b/main/migrations/__pycache__/0012_packagefeature_requirements_override.cpython-313.pyc new file mode 100644 index 0000000..5e0c431 Binary files /dev/null and b/main/migrations/__pycache__/0012_packagefeature_requirements_override.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0012_packagefeature_requirements_override.cpython-314.pyc b/main/migrations/__pycache__/0012_packagefeature_requirements_override.cpython-314.pyc new file mode 100644 index 0000000..3f3e9a5 Binary files /dev/null and b/main/migrations/__pycache__/0012_packagefeature_requirements_override.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0013_alter_feature_feature_requirements.cpython-313.pyc b/main/migrations/__pycache__/0013_alter_feature_feature_requirements.cpython-313.pyc new file mode 100644 index 0000000..0bd58e5 Binary files /dev/null and b/main/migrations/__pycache__/0013_alter_feature_feature_requirements.cpython-313.pyc differ diff --git a/main/migrations/__pycache__/0013_alter_feature_feature_requirements.cpython-314.pyc b/main/migrations/__pycache__/0013_alter_feature_feature_requirements.cpython-314.pyc new file mode 100644 index 0000000..f7b49b0 Binary files /dev/null and b/main/migrations/__pycache__/0013_alter_feature_feature_requirements.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0014_alter_character_features_alter_character_packages.cpython-314.pyc b/main/migrations/__pycache__/0014_alter_character_features_alter_character_packages.cpython-314.pyc new file mode 100644 index 0000000..1c3e2b3 Binary files /dev/null and b/main/migrations/__pycache__/0014_alter_character_features_alter_character_packages.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0015_alter_character_features_alter_character_packages.cpython-314.pyc b/main/migrations/__pycache__/0015_alter_character_features_alter_character_packages.cpython-314.pyc new file mode 100644 index 0000000..5481d89 Binary files /dev/null and b/main/migrations/__pycache__/0015_alter_character_features_alter_character_packages.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0016_alter_feature_feature_requirements.cpython-314.pyc b/main/migrations/__pycache__/0016_alter_feature_feature_requirements.cpython-314.pyc new file mode 100644 index 0000000..d162bc7 Binary files /dev/null and b/main/migrations/__pycache__/0016_alter_feature_feature_requirements.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/__init__.cpython-314.pyc b/main/migrations/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..bd19688 Binary files /dev/null and b/main/migrations/__pycache__/__init__.cpython-314.pyc differ diff --git a/main/models.py b/main/models.py index 71a8362..3fa0963 100644 --- a/main/models.py +++ b/main/models.py @@ -1,3 +1,632 @@ 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}%)" \ No newline at end of file diff --git a/main/serializers.py b/main/serializers.py new file mode 100644 index 0000000..3c1941e --- /dev/null +++ b/main/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from .models import Pin + +class PinSerializer(serializers.ModelSerializer): + class Meta: + model = Pin + fields = ['id', 'label', 'url', 'x', 'y', 'pin_type'] diff --git a/main/templates/main/character_sheet.html b/main/templates/main/character_sheet.html new file mode 100644 index 0000000..d07c512 --- /dev/null +++ b/main/templates/main/character_sheet.html @@ -0,0 +1,290 @@ +{% load static %} + + +
+| Score | Modifier | Save | |
|---|---|---|---|
| STR | {{ character.strength }} | {{ character.strength_modifier }} | {{ character.strength_save }} |
| DEX | {{ character.dexterity }} | {{ character.dexterity_modifier }} | {{ character.dexterity_save }} |
| CON | {{ character.constitution }} | {{ character.constitution_modifier }} | {{ character.constitution_save }} |
| INT | {{ character.intelligence }} | {{ character.intelligence_modifier }} | {{ character.intelligence_save }} |
| WIS | {{ character.wisdom }} | {{ character.wisdom_modifier }} | {{ character.wisdom_save }} |
| CHA | {{ character.charisma }} | {{ character.charisma_modifier }} | {{ character.charisma_save }} |
| Armor | {{ character.armor_base }} | - | - |
| Skill | Bonus | Passive |
|---|---|---|
| Athletics | {{ character.athletics }} | {{ character.athletics_passive }} |
| Acrobatics | {{ character.acrobatics }} | {{ character.acrobatics_passive }} |
| Sleight of Hand | {{ character.sleight_of_hand }} | {{ character.sleight_of_hand_passive }} |
| Stealth | {{ character.stealth }} | {{ character.stealth_passive }} |
| Arcana | {{ character.arcana }} | {{ character.arcana_passive }} |
| History | {{ character.history }} | {{ character.history_passive }} |
| Investigation | {{ character.investigation }} | {{ character.investigation_passive }} |
| Nature | {{ character.nature }} | {{ character.nature_passive }} |
| Religion | {{ character.religion }} | {{ character.religion_passive }} |
| Animal Handling | {{ character.animal_handling }} | {{ character.animal_handling_passive }} |
| Insight | {{ character.insight }} | {{ character.insight_passive }} |
| Medicine | {{ character.medicine }} | {{ character.medicine_passive }} |
| Perception | {{ character.perception }} | {{ character.perception_passive }} |
| Survival | {{ character.survival }} | {{ character.survival_passive }} |
| Deception | {{ character.deception }} | {{ character.deception_passive }} |
| Intimidation | {{ character.intimidation }} | {{ character.intimidation_passive }} |
| Performance | {{ character.performance }} | {{ character.performance_passive }} |
| Persuasion | {{ character.persuasion }} | {{ character.persuasion_passive }} |
{{ val.feature.feature_description|safe }}
+By adding an operation to your feature you can have the feature modifier and manipulate + a characters properties at runtime. +
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+- This is being rendered from a django template. -
- - \ No newline at end of file diff --git a/main/templates/main/map.html b/main/templates/main/map.html new file mode 100644 index 0000000..f8eea19 --- /dev/null +++ b/main/templates/main/map.html @@ -0,0 +1,88 @@ + +{% load static %} +{% csrf_token %} + + + + +
+
+ | # | +Label | +URL | +X (%) | +Y (%) | +Actions | +
|---|
';
+ }
+ if (pin.pin_type == "dungeon"){
+ el.innerHTML = '
';
+ }
+ const label = document.createElement('div');
+ label.className = 'pin-label';
+ label.innerText = pin.label;
+
+ el.appendChild(label);
+
+ pinsDiv.appendChild(el);
+ });
+
+ renderPinsTable(pins)
+
+}
+
+function fetchPins() {
+ fetch("/api/pins/")
+ .then(r => {
+ if (!r.ok) throw new Error("Pin fetch failed");
+ return r.json();
+ })
+ .then(renderPins)
+ .catch(() => {
+ // For demo/development: Show example
+ renderPins([
+ {x:15, y:30, label:'Demo Pin', url:'/'}
+ ]);
+ });
+}
+
+function renderPinsTable(pins) {
+ const tbody = document.querySelector('#pins-table tbody');
+ tbody.innerHTML = '';
+ pins.forEach((pin, idx) => {
+ const tr = document.createElement('tr');
+ tr.innerHTML = `
+