Add json import/export
This commit is contained in:
+4
-4
@@ -80,11 +80,11 @@ def serve(
|
||||
"""
|
||||
|
||||
# delay loading the app until we have configured our environment
|
||||
from ttfrog.db.bootstrap import bootstrap
|
||||
from ttfrog.db.bootstrap import loader
|
||||
from ttfrog.webserver import application
|
||||
|
||||
print("Starting TableTop Frog server...")
|
||||
bootstrap()
|
||||
loader.load()
|
||||
application.start(host=host, port=port, debug=debug)
|
||||
|
||||
|
||||
@@ -93,13 +93,13 @@ def setup(context: typer.Context):
|
||||
"""
|
||||
(Re)Initialize TableTop Frog. Idempotent; will preserve any existing configuration.
|
||||
"""
|
||||
from ttfrog.db.bootstrap import bootstrap
|
||||
from ttfrog.db.bootstrap import loader
|
||||
|
||||
if not os.path.exists(app_state["env"]):
|
||||
app_state["env"].parent.mkdir(parents=True, exist_ok=True)
|
||||
app_state["env"].write_text(dedent(SETUP_HELP))
|
||||
print(f"Wrote defaults file {app_state['env']}.")
|
||||
bootstrap()
|
||||
loader.load()
|
||||
|
||||
|
||||
@db_app.command()
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from ttfrog.db import schema
|
||||
from ttfrog.db.manager import db
|
||||
|
||||
|
||||
def bootstrap():
|
||||
db.metadata.drop_all(bind=db.engine)
|
||||
db.init()
|
||||
with db.transaction():
|
||||
# ancestries
|
||||
human = schema.Ancestry("human")
|
||||
tiefling = schema.Ancestry("tiefling")
|
||||
tiefling.add_modifier(schema.Modifier("Ability Score Increase", target="intelligence", relative_value=1))
|
||||
tiefling.add_modifier(schema.Modifier("Ability Score Increase", target="charisma", relative_value=2))
|
||||
darkvision = schema.AncestryTrait(
|
||||
"Darkvision",
|
||||
description=(
|
||||
"You can see in dim light within 60 feet of you as if it were bright light, and in darkness as if it "
|
||||
"were dim light. You can’t discern color in darkness, only shades of gray."
|
||||
),
|
||||
)
|
||||
darkvision.add_modifier(schema.Modifier("Darkvision", target="vision_in_darkness", absolute_value=120))
|
||||
tiefling.add_trait(darkvision)
|
||||
|
||||
# classes
|
||||
fighter = schema.CharacterClass("fighter", hit_dice="1d10", hit_dice_stat="CON")
|
||||
rogue = schema.CharacterClass("rogue", hit_dice="1d8", hit_dice_stat="DEX")
|
||||
|
||||
# characters
|
||||
sabetha = schema.Character("Sabetha", ancestry=tiefling, _intelligence=14)
|
||||
sabetha.add_class(fighter, level=2)
|
||||
sabetha.add_class(rogue, level=3)
|
||||
|
||||
bob = schema.Character("Bob", ancestry=human)
|
||||
|
||||
# persist all the records we've created
|
||||
db.add_or_update([sabetha, bob])
|
||||
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"ancestry": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "human",
|
||||
"creature_type": "humanoid",
|
||||
"size": "medium",
|
||||
"speed": 30,
|
||||
"_fly_speed": null,
|
||||
"_climb_speed": null,
|
||||
"_swim_speed": null
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "tiefling",
|
||||
"creature_type": "humanoid",
|
||||
"size": "medium",
|
||||
"speed": 30,
|
||||
"_fly_speed": null,
|
||||
"_climb_speed": null,
|
||||
"_swim_speed": null
|
||||
}
|
||||
],
|
||||
"ancestry_trait": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Darkvision",
|
||||
"description": "You can see in dim light within 60 feet of you as if it were bright light, and in darkness as if it were dim light. You can\u2019t discern color in darkness, only shades of gray."
|
||||
}
|
||||
],
|
||||
"character_class": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "fighter",
|
||||
"hit_die_name": "1d10",
|
||||
"hit_die_stat_name": "constitution",
|
||||
"starting_skills": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "rogue",
|
||||
"hit_die_name": "1d8",
|
||||
"hit_die_stat_name": "dexterity",
|
||||
"starting_skills": 0
|
||||
}
|
||||
],
|
||||
"class_attribute": [],
|
||||
"modifier": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Darkvision",
|
||||
"target": "vision_in_darkness",
|
||||
"stacks": false,
|
||||
"absolute_value": 120,
|
||||
"multiply_value": null,
|
||||
"multiply_attribute": null,
|
||||
"relative_value": null,
|
||||
"relative_attribute": null,
|
||||
"new_value": null,
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Ability Score Increase",
|
||||
"target": "intelligence",
|
||||
"stacks": false,
|
||||
"absolute_value": null,
|
||||
"multiply_value": null,
|
||||
"multiply_attribute": null,
|
||||
"relative_value": 1,
|
||||
"relative_attribute": null,
|
||||
"new_value": null,
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Ability Score Increase",
|
||||
"target": "charisma",
|
||||
"stacks": false,
|
||||
"absolute_value": null,
|
||||
"multiply_value": null,
|
||||
"multiply_attribute": null,
|
||||
"relative_value": 2,
|
||||
"relative_attribute": null,
|
||||
"new_value": null,
|
||||
"description": ""
|
||||
}
|
||||
],
|
||||
"skill": [],
|
||||
"transaction_log": [],
|
||||
"character": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Sabetha",
|
||||
"hit_points": 10,
|
||||
"temp_hit_points": 0,
|
||||
"_max_hit_points": 10,
|
||||
"_armor_class": 10,
|
||||
"_strength": 10,
|
||||
"_dexterity": 10,
|
||||
"_constitution": 10,
|
||||
"_intelligence": 14,
|
||||
"_wisdom": 10,
|
||||
"_charisma": 10,
|
||||
"_vision": null,
|
||||
"exhaustion": 0,
|
||||
"ancestry_id": 2,
|
||||
"slug": "nMnWu"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Bob",
|
||||
"hit_points": 10,
|
||||
"temp_hit_points": 0,
|
||||
"_max_hit_points": 10,
|
||||
"_armor_class": 10,
|
||||
"_strength": 10,
|
||||
"_dexterity": 10,
|
||||
"_constitution": 10,
|
||||
"_intelligence": 10,
|
||||
"_wisdom": 10,
|
||||
"_charisma": 10,
|
||||
"_vision": null,
|
||||
"exhaustion": 0,
|
||||
"ancestry_id": 1,
|
||||
"slug": "PjPdM"
|
||||
}
|
||||
],
|
||||
"class_attribute_map": [],
|
||||
"class_attribute_option": [],
|
||||
"class_skill_map": [],
|
||||
"modifier_map": [
|
||||
{
|
||||
"id": 1,
|
||||
"modifier_id": 1,
|
||||
"primary_table_name": "ancestry_trait",
|
||||
"primary_table_id": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"modifier_id": 2,
|
||||
"primary_table_name": "ancestry",
|
||||
"primary_table_id": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"modifier_id": 3,
|
||||
"primary_table_name": "ancestry",
|
||||
"primary_table_id": 2
|
||||
}
|
||||
],
|
||||
"trait_map": [
|
||||
{
|
||||
"id": 1,
|
||||
"ancestry_id": 2,
|
||||
"ancestry_trait_id": 1,
|
||||
"level": 1
|
||||
}
|
||||
],
|
||||
"character_class_attribute_map": [],
|
||||
"character_skill_map": [],
|
||||
"class_map": [
|
||||
{
|
||||
"id": 1,
|
||||
"character_id": 1,
|
||||
"character_class_id": 1,
|
||||
"level": 2
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"character_id": 1,
|
||||
"character_class_id": 2,
|
||||
"level": 3
|
||||
}
|
||||
],
|
||||
"hit_die": [
|
||||
{
|
||||
"id": 1,
|
||||
"character_id": 1,
|
||||
"character_class_id": 1,
|
||||
"spent": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"character_id": 1,
|
||||
"character_class_id": 1,
|
||||
"spent": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"character_id": 1,
|
||||
"character_class_id": 2,
|
||||
"spent": false
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"character_id": 1,
|
||||
"character_class_id": 2,
|
||||
"spent": false
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"character_id": 1,
|
||||
"character_class_id": 2,
|
||||
"spent": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from ttfrog.db import schema
|
||||
from ttfrog.db.manager import db
|
||||
|
||||
DATA_PATH = Path(__file__).parent
|
||||
|
||||
|
||||
def load(data: str = ""):
|
||||
db.metadata.drop_all(bind=db.engine)
|
||||
db.init()
|
||||
with db.transaction():
|
||||
if not data:
|
||||
data = (DATA_PATH / "bootstrap.json").read_text()
|
||||
db.load(json.loads(data))
|
||||
tiefling = db.Ancestry.filter_by(name="tiefling").one()
|
||||
human = db.Ancestry.filter_by(name="human").one()
|
||||
fighter = db.CharacterClass.filter_by(name="fighter").one()
|
||||
rogue = db.CharacterClass.filter_by(name="rogue").one()
|
||||
|
||||
sabetha = schema.Character("Sabetha", ancestry=tiefling, _intelligence=14)
|
||||
sabetha.add_class(fighter, level=2)
|
||||
sabetha.add_class(rogue, level=3)
|
||||
|
||||
bob = schema.Character("Bob", ancestry=human)
|
||||
|
||||
# persist all the records we've created
|
||||
db.add_or_update([sabetha, bob])
|
||||
@@ -4,16 +4,15 @@ import json
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from functools import cached_property
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
import transaction
|
||||
from pyramid_sqlalchemy.meta import Session
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy import create_engine, event, insert
|
||||
|
||||
import ttfrog.db.schema
|
||||
from ttfrog.db import schema
|
||||
from ttfrog.path import database
|
||||
|
||||
assert ttfrog.db.schema
|
||||
|
||||
|
||||
class AlchemyEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
@@ -42,7 +41,7 @@ class SQLDatabaseManager:
|
||||
|
||||
@cached_property
|
||||
def metadata(self):
|
||||
return ttfrog.db.schema.BaseObject.metadata
|
||||
return schema.BaseObject.metadata
|
||||
|
||||
@cached_property
|
||||
def tables(self):
|
||||
@@ -87,8 +86,18 @@ class SQLDatabaseManager:
|
||||
results[table_name] = [dict(row._mapping) for row in self.query(table).all()]
|
||||
return json.dumps(results, indent=2, cls=AlchemyEncoder)
|
||||
|
||||
def load(self, data: dict):
|
||||
for table_name, rows in data.items():
|
||||
table = self.tables.get(table_name, None)
|
||||
if table is None:
|
||||
raise IntegrityError(f"Table {table_name} not found in database.")
|
||||
if not rows:
|
||||
continue
|
||||
query = insert(table), rows
|
||||
self.session.execute(*query)
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
return self.query(getattr(ttfrog.db.schema, name))
|
||||
return self.query(getattr(schema, name))
|
||||
|
||||
|
||||
db = SQLDatabaseManager()
|
||||
|
||||
@@ -391,7 +391,7 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||
self.add_skill(skill, proficient=True, character_class=newclass)
|
||||
|
||||
# add hit dice
|
||||
existing = len(self.hit_dice[newclass.name])
|
||||
existing = len([die for die in self._hit_dice if die.character_class_id == newclass.id])
|
||||
for lvl in range(level - existing):
|
||||
self._hit_dice.append(HitDie(character_id=self.id, character_class_id=newclass.id))
|
||||
|
||||
@@ -431,8 +431,8 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||
return True
|
||||
|
||||
def add_skill(self, skill, proficient=False, expert=False, character_class=None):
|
||||
if not self.id:
|
||||
raise Exception("Cannot add a skill before the character has been persisted.")
|
||||
# if not self.id:
|
||||
# raise Exception("Cannot add a skill before the character has been persisted.")
|
||||
skillmap = None
|
||||
exists = False
|
||||
if skill in self.skills:
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
from enum import StrEnum, auto
|
||||
|
||||
|
||||
class Conditions(StrEnum):
|
||||
blinded = auto()
|
||||
charmed = auto()
|
||||
deafened = auto()
|
||||
frightened = auto()
|
||||
grappled = auto()
|
||||
incapacitated = auto()
|
||||
invisible = auto()
|
||||
paralyzed = auto()
|
||||
petrified = auto()
|
||||
poisoned = auto()
|
||||
prone = auto()
|
||||
restrained = auto()
|
||||
stunned = auto()
|
||||
unconscious = auto()
|
||||
dead = auto()
|
||||
|
||||
|
||||
class DamageType(StrEnum):
|
||||
piercing = auto()
|
||||
slashing = auto()
|
||||
bludgeoning = auto()
|
||||
fire = auto()
|
||||
cold = auto()
|
||||
lightning = auto()
|
||||
thunder = auto()
|
||||
acid = auto()
|
||||
poison = auto()
|
||||
radiant = auto()
|
||||
necrotic = auto()
|
||||
psychic = auto()
|
||||
force = auto()
|
||||
magical = auto()
|
||||
magical_piercing = auto()
|
||||
magical_slashing = auto()
|
||||
magical_bludgeoning = auto()
|
||||
silvered_piercing = auto()
|
||||
silvered_slashing = auto()
|
||||
silvered_bludgeoning = auto()
|
||||
adamantium_piercing = auto()
|
||||
adamantium_slashing = auto()
|
||||
adamantium_bludgeoning = auto()
|
||||
|
||||
|
||||
class Defenses(StrEnum):
|
||||
vulnerable = auto()
|
||||
resistant = auto()
|
||||
immune = auto()
|
||||
absorbs = auto()
|
||||
Reference in New Issue
Block a user