Browse Source

Armour/Wep in database

master
Brandon Cornejo 4 years ago
parent
commit
9811a6abf9
  1. 14
      Makefile
  2. 4
      acks/commands.py
  3. 39
      acks/npc/commands.py
  4. 12
      acks/npc/default_classes.csv
  5. 54
      acks/npc/models.py
  6. 275
      acks/npc/npc_party.py
  7. 85
      alembic.ini
  8. 1
      alembic/README
  9. 86
      alembic/env.py
  10. 24
      alembic/script.py.mako
  11. 24
      alembic/versions/7287451b8bce_create_characterclass_table.py

14
Makefile

@ -1,4 +1,4 @@
.PHONY: run debug clean
.PHONY: run debug clean shell
bin/activate: requirements.txt bin/activate: requirements.txt
test -f bin/activate || $(shell which python3) -m venv . test -f bin/activate || $(shell which python3) -m venv .
@ -13,3 +13,15 @@ debug: bin/activate
clean: clean:
rm -rf bin/ include/ lib/ lib64/ __pycache__/ share/; rm pyvenv.cfg rm -rf bin/ include/ lib/ lib64/ __pycache__/ share/; rm pyvenv.cfg
shell: bin/activate
. bin/activate; FLASK_SETTINGS_FILE=../config/dev.cfg FLASK_APP=start.py flask shell
cli: bin/activate
. bin/activate; FLASK_SETTINGS_FILE=../config/dev.cfg FLASK_APP=start.py flask ${COMMAND}
recreatedb: bin/activate
. bin/activate; FLASK_SETTINGS_FILE=../config/dev.cfg FLASK_APP=start.py flask acks dropdb
. bin/activate; FLASK_SETTINGS_FILE=../config/dev.cfg FLASK_APP=start.py flask acks initdb
. bin/activate; FLASK_SETTINGS_FILE=../config/dev.cfg FLASK_APP=start.py flask npc populate

4
acks/commands.py

@ -11,3 +11,7 @@ def initialize_database():
from acks.npc import models from acks.npc import models
db.create_all() db.create_all()
@default_cli.command('dropdb')
def destroy_database():
db.drop_all()
db.session.commit()

39
acks/npc/commands.py

@ -6,16 +6,37 @@ from ..models import db
npc_cli = AppGroup('npc') npc_cli = AppGroup('npc')
@npc_cli.command('create classes')
def create_class():
@npc_cli.command('populate')
def populate_npc_database():
import csv import csv
from .models import CharacterClass
from .models import (
CharacterClass,
EquipmentArmour,
EquipmentRangedWeapon,
EquipmentMeleeWeapon
)
classes = []
def load_csv_data(file_name, cls):
rows = []
with open('acks/npc/data/{}'.format(file_name), newline='') as data:
reader = csv.DictReader(data)
for row in reader:
rows.append(cls(**row))
return rows
# Character Classes
db.session.bulk_save_objects(load_csv_data('default_classes.csv', CharacterClass))
# Equipment Armour
db.session.bulk_save_objects(load_csv_data('default_armours.csv', EquipmentArmour))
# Ranged Weapons
db.session.bulk_save_objects(load_csv_data('default_ranged.csv', EquipmentRangedWeapon))
# Melee Weapons
melee_weps = load_csv_data('default_melee.csv', EquipmentMeleeWeapon)
for wep in melee_weps:
wep.two_handed = (wep.two_handed == 'True')
db.session.bulk_save_objects(melee_weps)
with open('acks/npc/default_classes.csv', newline='') as data:
reader = csv.DictReader(data)
for row in reader:
classes.append(CharacterClass(**row))
db.session.bulk_save_objects(classes)
db.session.commit() db.session.commit()

12
acks/npc/default_classes.csv

@ -1,12 +0,0 @@
name,bucket,prime_requisite,hit_die_size,maximum_level,armour_modifier,melee_light,melee_medium,melee_heavy,ranged_light,ranged_heavy
Elven Nightblade,Demi-Human,D+I,6,11,-4,1,2,0,0,0
Elven Spellsword,Demi-Human,S+I,6,10,-4,0,1,0,0,0
Explorer,Campaign,S+D,6,14,-3,1,1,0,0,1
Bladedancer,Campaign,W+D,6,14,-2,0,1,0,0,0
Cleric,Core,WIS,6,14,0,1,3,1,1,3
Fighter,Core,STR,8,14,1,1,4,1,1,1
Thief,Core,DEX,4,14,-6,1,1,0,1,0
Mage,Core,INT,4,14,-8,1,0,0,0,0
Assassin,Campaign,S+D,6,14,-6,1,2,0,1,0
Bard,Campaign,D+H,6,14,-3,1,2,0,1,0
Dwarven Vaultguard,Demi-Human,STR,8,13,-1,0,3,1,1,1

54
acks/npc/models.py

@ -7,6 +7,7 @@ class CharacterClass(BaseModel):
name = db.Column(db.String(50), unique=True, nullable=False) name = db.Column(db.String(50), unique=True, nullable=False)
bucket = db.Column(db.String(50)) bucket = db.Column(db.String(50))
frequency_modifier = db.Column(db.Integer, default=1)
prime_requisite = db.Column(db.String(3)) prime_requisite = db.Column(db.String(3))
hit_die_size = db.Column(db.Integer) hit_die_size = db.Column(db.Integer)
@ -22,5 +23,56 @@ class CharacterClass(BaseModel):
def __repr__(self): def __repr__(self):
return '<CharacterClass {0}>'.format(self.name) return '<CharacterClass {0}>'.format(self.name)
class EquipmentArmour(BaseModel):
__tablename__ = 'eq_armour'
admin_models = [CharacterClass]
name = db.Column(db.String(50), unique=True, nullable=False)
gp_value = db.Column(db.Integer, nullable=False)
ac_mod = db.Column(db.Integer, nullable=False)
def __repr__(self):
return '<EquipmentArmour {0}>'.format(self.name)
class EquipmentRangedWeapon(BaseModel):
__tablename__ = 'eq_ranged_wep'
name = db.Column(db.String(50), unique=True, nullable=False)
bucket = db.Column(db.String(20), nullable=False)
gp_value = db.Column(db.Integer, nullable=False)
damage_die = db.Column(db.String(10), nullable=False)
def __repr__(self):
return '<EquipmentRangedWeapon {0}>'.format(self.name)
class EquipmentMeleeWeapon(BaseModel):
__tablename__ = 'eq_melee_wep'
name = db.Column(db.String(50), unique=False, nullable=False)
bucket = db.Column(db.String(20), nullable=False)
gp_value = db.Column(db.Integer, nullable=False)
damage_die = db.Column(db.String(10), nullable=False)
two_handed = db.Column(db.Boolean, nullable=False)
def __repr__(self):
return '<EquipmentMeleeWeapon {0}>'.format(self.name)
class CharacterNPC(BaseModel):
__tablename__ = 'npcs'
name = db.Column(db.String(50), unique=False, nullable=True)
level = db.Column(db.Integer, nullable=False)
hit_points = db.Column(db.Integer, nullable=False)
guild_id = db.Column(db.Integer, db.ForeignKey('guild.id'), nullable=False)
guild = db.relationship('CharacterClass', backref=db.backref('npcs', lazy=True))
melee_id = db.Column(db.Integer, db.ForeignKey('melee.id'), nullable=False)
melee = db.relationship('EquipmentMeleeWeapon')
ranged_id = db.Column(db.Integer, db.ForeignKey('ranged.id'), nullable=False)
ranged = db.relationship('EquipmentRangedWeapon')
def __repr__(self):
return '<CharacterNPC {0}>'.format(self.name)
admin_models = [CharacterClass, EquipmentArmour, EquipmentRangedWeapon, EquipmentMeleeWeapon, CharacterNPC]

275
acks/npc/npc_party.py

@ -1,35 +1,20 @@
from random import randint
from random import randint, choice
from .models import (
CharacterClass,
EquipmentArmour,
EquipmentRangedWeapon,
EquipmentMeleeWeapon,
CharacterNPC
)
def number_encountered(): def number_encountered():
return (randint(1, 4) + 2) return (randint(1, 4) + 2)
def npc_class(): def npc_class():
roll = (randint(1, 6) + randint(1, 6) + randint(1, 6))
if roll == 3 or roll == 4:
return 'Elven Nightblade'
elif roll == 5:
return 'Elven Spellsword'
elif roll == 6:
return 'Explorer'
elif roll == 7:
return 'Bladedancer'
elif roll == 8:
return 'Cleric'
elif roll == 9 or roll == 10 or roll == 11:
return 'Fighter'
elif roll == 12:
return 'Thief'
elif roll == 13:
return 'Mage'
elif roll == 14:
return 'Assassin'
elif roll == 15:
return 'Bard'
elif roll == 16:
return 'Dwarven Vaultguard'
elif roll == 17 or roll == 18:
return 'Dwarven Craftpriest'
classes = [c for cls in CharacterClass.query.all() for c in ([cls] * cls.frequency_modifier)]
return choice(classes)
def npc_alignment(): def npc_alignment():
roll = randint(1, 6) roll = randint(1, 6)
@ -61,7 +46,9 @@ def npc_baselevel(base_level):
return base_level return base_level
def npc_abilities(): def npc_abilities():
return [(randint(1, 6) + randint(1, 6) + randint(1, 6)) for x in range(0,6)]
ability_list = ['str', 'int', 'wis', 'dex', 'con', 'chr']
abilities = [(randint(1, 6) + randint(1, 6) + randint(1, 6)) for x in range(0,6)]
return zip(ability_list, abilities)
def attribute_mod(atr): def attribute_mod(atr):
mod = 0 mod = 0
@ -76,214 +63,78 @@ def attribute_mod(atr):
return mod return mod
npc_class_data = {
'Elven Nightblade': {
'hd': [1, 6],
'armour': -4,
'melee': [0, 2, 1],
'ranged': [0, 0, 1]
},
'Elven Spellsword': {
'hd': [1, 6],
'armour': -4,
'melee': [0, 1, 0],
'ranged': [0, 0, 1]
},
'Explorer': {
'hd': [1, 6],
'armour': -3,
'melee': [0, 1, 1],
'ranged': [0, 1, 2]
},
'Bladedancer': {
'hd': [1, 6],
'armour': -2,
'melee': [0, 1, 0],
'ranged': [0, 0, 1]
},
'Cleric': {
'hd': [1, 6],
'armour': 0,
'melee': [1, 3, 1],
'ranged': [1, 3, 4]
},
'Fighter': {
'hd': [1, 8],
'armour': 1,
'melee': [1, 4, 1],
'ranged': [1, 1, 4],
},
'Thief': {
'hd': [1, 4],
'armour': -6,
'melee': [0, 1, 1],
'ranged': [1, 0, 6]
},
'Mage': {
'hd': [1, 4],
'armour': -8,
'melee': [0, 0, 1],
'ranged': [0, 0, 1]
},
'Assassin': {
'hd': [1, 6],
'armour': -6,
'melee': [0, 2, 1],
'ranged': [1, 0, 5]
},
'Bard': {
'hd': [1, 6],
'armour': -3,
'melee': [0, 2, 1],
'ranged': [1, 0, 4]
},
'Dwarven Vaultguard': {
'hd': [1, 8],
'armour': -1,
'melee': [1, 3, 0],
'ranged': [1, 1, 6]
},
'Dwarven Craftpriest': {
'hd': [1, 6],
'armour': -2,
'melee': [0, 1, 0],
'ranged': [1, 0, 5]
}
}
npc_armour_list = [
['Clothing Only', 0, 0],
['Hide and Fur Armour', 10, 1],
['Leather Armour', 20, 2],
['Ring Mail Armour', 30, 3],
['Scale Mail Armour', 30, 3],
['Chain Mail Armour', 40, 4],
['Banded Plate', 50, 5],
['Lamellar Armour', 50, 5],
['Plate Armour', 60, 6],
]
npc_melee_weapons_data = {
'light': [
['Club', 1, '1d4'],
['Dagger', 3, '1d4'],
['Sap', 1, '1d4'],
['Staff (1h)', 1, '1d4'],
['Staff (2h)', 1, '1d6'],
['Whip', 5, '1d2']
],
'medium': [
['Battle Axe (1h)', 7, '1d6'],
['Battle Axe (2h)', 7, '1d8'],
['Hand Axe', 4, '1d6'],
['Flail (1h)', 5, '1d6'],
['Flail (2h)', 5, '1d8'],
['Mace (1h)', 5, '1d6'],
['Mace (2h)', 5, '1d8'],
['War Hammer (1h)', 5, '1d6'],
['War Hammer (2h)', 5, '1d8'],
['Spear (1h)', 3, '1d6'],
['Spear (2h)', 3, '1d8'],
['Short Sword', 7, '1d6'],
['Sword (1h)', 10, '1d6'],
['Sword (2h)', 10, '1d8']
],
'heavy': [
['Great Axe (2h)', 10, '1d10'],
['Morning Star (2h)', 10, '1d10'],
['Pole Arm (2h)', 7, '1d10'],
['Two-Handed Sword (2h)', 15, '1d10']
]
}
npc_ranged_weapons_data = {
'light': [
['Crossbow', 30, '1d6'],
['Shortbow', 3, '1d6'],
['Javelin', 1, '1d6'],
],
'heavy': [
['Arbalest', 50, '1d8'],
['Composite Bow', 40, '1d6'],
['Longbow', 7, '1d6'],
]
}
def select_melee_weapon(guild):
melee = npc_class_data[guild]['melee']
def select_melee_weapon(guild, data):
weapons = [] weapons = []
for x in range(0, melee[0]):
weapons.extend(npc_melee_weapons_data['heavy'])
for x in range(0, melee[1]):
weapons.extend(npc_melee_weapons_data['medium'])
for x in range(0, melee[2]):
weapons.extend(npc_melee_weapons_data['light'])
for x in range(0, guild.melee_heavy):
weapons.extend(data['melee']['heavy'])
for x in range(0, guild.melee_medium):
weapons.extend(data['melee']['medium'])
for x in range(0, guild.melee_light):
weapons.extend(data['melee']['light'])
selection = randint(0, len(weapons) - 1) selection = randint(0, len(weapons) - 1)
return weapons[selection] return weapons[selection]
def select_ranged_weapon(guild):
ranged = npc_class_data[guild]['ranged']
def select_ranged_weapon(guild, data):
weapons = [] weapons = []
for x in range(0, ranged[0]):
weapons.extend(npc_ranged_weapons_data['light'])
for x in range(0, ranged[1]):
weapons.extend(npc_ranged_weapons_data['heavy'])
for x in range(0, ranged[2]):
weapons.append(None)
for x in range(0, guild.ranged_light):
weapons.extend(data['ranged']['light'])
for x in range(0, guild.ranged_heavy):
weapons.extend(data['ranged']['heavy'])
if not weapons:
return None
selection = randint(0, len(weapons) - 1) selection = randint(0, len(weapons) - 1)
return weapons[selection] return weapons[selection]
def generate_npc(base_level):
attributes = npc_abilities()
def generate_npc(base_level, data):
guild = npc_class()
npc = {} npc = {}
npc['guild'] = npc_class()
npc['guild'] = guild.name
npc['level'] = npc_baselevel(base_level) npc['level'] = npc_baselevel(base_level)
npc['str'] = attributes[0]
npc['int'] = attributes[1]
npc['wis'] = attributes[2]
npc['dex'] = attributes[3]
npc['con'] = attributes[4]
npc['chr'] = attributes[5]
npc.update(npc_abilities())
npc['hp'] = 0 npc['hp'] = 0
conmod = attribute_mod(npc['con']) conmod = attribute_mod(npc['con'])
for x in range(0, npc['level']):
hitdice = [randint(1, npc_class_data[npc['guild']]['hd'][1]) for x in range(0, npc_class_data[npc['guild']]['hd'][0])]
for die in hitdice:
die += conmod
if die < 1:
die = 1
npc['hp'] += die
armourval = randint(0, len(npc_armour_list) - 1) + npc_class_data[npc['guild']]['armour']
hitdice = [randint(1, guild.hit_die_size) for x in range(0, npc['level'])]
for die in hitdice:
die += conmod
if die < 1:
die = 1
npc['hp'] += die
armourval = randint(0, len(data['armours']) - 1) + guild.armour_modifier
if armourval < 0: if armourval < 0:
armourval = 0 armourval = 0
if armourval > len(npc_armour_list) - 1:
armourval = len(npc_armour_list) - 1
npc['armour'] = npc_armour_list[armourval]
npc['melee'] = select_melee_weapon(npc['guild'])
npc['ranged'] = select_ranged_weapon(npc['guild'])
if armourval > len(data['armours']) - 1:
armourval = len(data['armours']) - 1
armour = data['armours'][armourval]
npc['armour'] = [armour.name, armour.gp_value, armour.ac_mod]
melee = select_melee_weapon(guild, data)
npc['melee'] = [melee.name, melee.gp_value, melee.damage_die] if melee else None
ranged = select_ranged_weapon(guild, data)
npc['ranged'] = [ranged.name, ranged.gp_value, ranged.damage_die] if ranged else None
return npc return npc
def create_party(base_level): def create_party(base_level):
data = {
'armours': EquipmentArmour.query.all(),
'ranged': {
'light': EquipmentRangedWeapon.query.filter_by(bucket='Light').all(),
'heavy': EquipmentRangedWeapon.query.filter_by(bucket='Heavy').all()
},
'melee': {
'light': EquipmentMeleeWeapon.query.filter_by(bucket='Light').all(),
'medium': EquipmentMeleeWeapon.query.filter_by(bucket='Medium').all(),
'heavy': EquipmentMeleeWeapon.query.filter_by(bucket='Heavy').all()
},
}
party_size = number_encountered() party_size = number_encountered()
return [generate_npc(base_level) for x in range(0, party_size)]
return [generate_npc(base_level, data) for x in range(0, party_size)]
def print_party(party): def print_party(party):
def print_npc(npc): def print_npc(npc):

85
alembic.ini

@ -0,0 +1,85 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks=black
# black.type=console_scripts
# black.entrypoint=black
# black.options=-l 79
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

1
alembic/README

@ -0,0 +1 @@
Generic single-database configuration.

86
alembic/env.py

@ -0,0 +1,86 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from acks import app
from acks.models import db
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# set the sqlalchemy url to the one defined for flask-sqlalchemy
config.set_main_option('sqlalchemy.url', app.config['SQLALCHEMY_DATABASE_URI'])
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from acks.models import BaseModel
target_metadata = BaseModel.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
alembic/script.py.mako

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

24
alembic/versions/7287451b8bce_create_characterclass_table.py

@ -0,0 +1,24 @@
"""create CharacterClass table
Revision ID: 7287451b8bce
Revises:
Create Date: 2019-12-02 23:01:13.613091
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7287451b8bce'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass
Loading…
Cancel
Save