Compare commits

..

27 Commits

Author SHA1 Message Date
Zachary Watts
d1829184cd cleaning up code 2026-05-03 22:46:33 -04:00
Zachary Watts
fbe69be2ac cleaning up code 2026-05-03 22:32:50 -04:00
Zachary Watts
2173733463 cleaning up app and improving html 2026-05-02 00:41:49 -04:00
Zachary Watts
78ba21227a adding spell book fixes 2026-04-30 15:30:54 -04:00
Zachary Watts
2561b403bd checks for arguments 2026-04-30 15:18:38 -04:00
Zachary Watts
ef5d3474a7 fixing some bugs 2026-04-30 14:33:51 -04:00
Zachary Watts
0149035c72 cleaning up, adding party debug 2026-04-30 12:03:14 -04:00
Zachary Watts
1ddeec0b6d cleaning up, adding party debug 2026-04-30 12:01:32 -04:00
Zachary Watts
72a33f22de fixing git ignore file 2026-04-30 11:54:09 -04:00
Zachary Watts
8bcefa02a6 character sheet more editable 2026-04-30 11:49:11 -04:00
Zachary Watts
fbecd90c8d updates 2026-04-30 10:07:13 -04:00
Zachary Watts
972e094466 adding in a home page with a slide show 2026-04-30 02:40:07 -04:00
Zachary Watts
f089cf0d2c single character sheet improvements 2026-04-30 00:57:04 -04:00
Zachary Watts
0171a05c19 cleaning templates, getting levels are party counts cached properly 2026-04-29 21:32:06 -04:00
Zachary Watts
15342552f1 can now load a character from a cookie 2026-04-29 13:55:07 -04:00
Zachary Watts
e6d19b1f69 able to get compress JSON characters stored as cookies 2026-04-29 01:25:10 -04:00
Zachary Watts
dad1a4c588 making the ui nicer 2026-04-28 16:12:21 -04:00
Zachary Watts
0ca25f9f91 making the html and css better 2026-04-26 11:32:29 -04:00
Zachary Watts
8a7f605a5f cleaning up 2026-04-26 01:53:40 -04:00
Zachary Watts
b7ddafddd4 cleaned up main 2026-04-26 01:34:03 -04:00
Zachary Watts
f4d8511ab6 template improved, using multiple .py files now 2026-04-26 01:31:43 -04:00
zack
390f062e0a adding in flask 2026-04-23 18:35:11 -04:00
Zachary Watts
206a229895 adding armor 2026-04-19 14:18:38 -04:00
Zachary Watts
feacb20551 made the class selector more agnostic 2026-04-18 16:18:08 -04:00
Zachary Watts
c966c4f974 adding character sheet outputs 2026-04-18 16:11:53 -04:00
Zachary Watts
6b03659ec9 adding in better classes and methods 2026-04-18 12:25:45 -04:00
Zachary Watts
9323e7c5d5 adding classes, yay 2026-04-18 01:37:11 -04:00
12 changed files with 1163 additions and 73 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
__pycache__/
flask_session/

View File

@@ -1,3 +1,47 @@
# ose-character-gen # OSE Character Generator
This tool uses the Old School Essentials (OSE) ruleset for tabletop RPGs in order to randomly generate a character.
## Overview
### What is Dungeons & Dragons?
Dungeons & Dragons, also known as D&D, is a pen and paper table top game. One player is the story teller, the "dungeon master" and the others interact with the story using their "adventurers." An adventurer is a playable character, who has a mixture of abilities and powers, the details of which are often kept on piece of paper called a "character sheet."
### How does the game work?
Most settings are fantasy based, like game of thrones or lord of the rings. Players often pick up a quest in one of these fantasy worlds, guided by the "Dungeon Master" who narrates the world. The players will make decisions, take actions, and ultimate help write the story as it unfolds.</p>
### Why would I need a character generator?
It takes time and effort to create a new character; there are many rules, phrases, and terminology you'll need to know to create a character. Most rule books provide an easy to follow guide on making player characters or "adventurers." However, if you are short on time, a generator such as this will get you in the game right away!
### About This App
This app accomplishes a few things, but mainly it creates a character for you.
- It follows the rules precisely from the books to generate a legal character
- All stats are randomly rolled, but a class is chosen per your best stats
- Character equipment and spells are also randomly selected, within that character-class rules
- You can generate a whole party of characters
- Within that party, you can adjust their level, and the number of characters
- You can create a single character to edit
## Instructions
### Install
This is a flask web application, to install:
1. Clone this repo
2. Install requirements, `python3 pip install -r requirements.txt`
3. To run the webserver execute `flask run` while in your cloned repository's top folder.
### To Use
There are two main generators, one for a single character and another for a group, or a "party" of characters.
1. Character Generator
- You may select a class and a level
- The re-roll button always starts the character back to level 1
2. Party Generator
- You may also select the level for this group
- You may select the count, between 2 and 5.
- If you want to have the party persist across refreshes, level and size changes, keep "cached" on
- Otherwise, "not cached" will always generate a new party
- If you find a single character interesting, you may "select" them and it will load **this** character in the "charater generator."
Web app for old school D&D character generation

479
adventurers.py Executable file
View File

@@ -0,0 +1,479 @@
#!/usr/bin/python3
import json
import random
from equipment import *
from spells import *
# functions
def roll_dice(count, sides):
return sum(random.randint(1,sides) for _ in range(count))
# Player Character Classes
class Adventurer:
ability_score_modifiers = {
'strength' : { 3 : { 'melee' : -3, 'open doors': 1 }, 5 : { 'melee' : -2, 'open doors': 1 }, 8 : { 'melee' : -1, 'open doors': 1 },
12 : { 'melee' : 0, 'open doors': 2 }, 15 : { 'melee' : 1, 'open doors': 3 }, 17 : { 'melee' : 2, 'open doors': 4 },
18 : { 'melee' : 3, 'open doors': 5 } },
'intelligence' : { 3 : { 'languages' : 'native, broken speech', 'literacy': 'illiterate' }, 5 : { 'languages' : 'native', 'literacy': 'illiterate' },
8 : { 'languages' : 'native', 'literacy': 'basic' }, 12 : { 'languages' :'native', 'literacy': 'literate' },
15 : { 'languages' : 'native +1 additional', 'literacy': 'literate'}, 17 : { 'languages' : 'native +2 additional', 'literacy': 'literate' },
18 : { 'languages' : 'native +3 additional', 'literacy': 'literate' } },
'wisdom' : { 3 : { 'magic saves' : -3 }, 5 : { 'magic saves' : -2 }, 8 : { 'magic saves' : -1 }, 12 : { 'magic saves' : 0,},
15 : { 'magic saves' : 1 }, 17 : { 'magic saves' : 2 }, 18 : { 'magic saves' : 3 } },
'dexterity' : { 3 : { 'AC' : -3, 'missile': -3, 'initiative' : -2 }, 5 : { 'AC' : -2, 'missile': -2, 'initiative' : -1},
8 : { 'AC' : -1, 'missile': -1, 'initiative' : -1 }, 12 : { 'AC' : 0, 'missile': 0, 'initiative' : 0 },
15 : { 'AC' : 1, 'missile': 1, 'initiative' : 1 }, 17 : { 'AC' : 2, 'missile': 2, 'initiative' : 1 },
18 : { 'AC' : 3, 'missile': 3, 'initiative' : 2 } },
'constitution' : { 3 : { 'hit points' : -3 }, 5 : { 'hit points' : -2 }, 8 : { 'hit points' : -1 }, 12 : { 'hit points' : 0 },
15 : { 'hit points' : 1 }, 17 : { 'hit points' : 2 }, 18 : { 'hit points' : 3 } },
'charisma' : { 3 : { 'npc reactions' : -2, 'max retainers': 1, 'retainer loyalty' : 4 }, 5 : { 'npc reactions' : -2, 'max retainers': 2, 'retainer loyalty' : 5 },
8 : { 'npc reactions' : -1, 'max retainers': 3, 'retainer loyalty' : 6 }, 12 : { 'npc reactions' : 0, 'max retainers': 4, 'retainer loyalty' : 7 },
15 : { 'npc reactions' : 1, 'max retainers': 5, 'retainer loyalty' : 8 }, 17 : { 'npc reactions' : 1, 'max retainers': 6, 'retainer loyalty' : 9 },
18 : { 'npc reactions' : 2, 'max retainers': 7, 'retainer loyalty' : 10 } }
}
def __init__(self, c_id: str, level=1, attributes={}) -> None:
# using a get() method to pull an attribute else use a default value, https://python-academy.org/en/handbook/get
self.identifier = c_id
self.player_class = None
self.level = level
self.strength = attributes.get('strength', roll_dice(3,6))
self.intelligence = attributes.get('intelligence', roll_dice(3,6))
self.wisdom = attributes.get('wisdom', roll_dice(3,6))
self.dexterity = attributes.get('dexterity', roll_dice(3,6))
self.constitution = attributes.get('constitution', roll_dice(3,6))
self.charisma = attributes.get('charisma', roll_dice(3,6))
self.hp_rolls = []
self.hp = 1
self.atk = 0
self.gold= roll_dice(3,6)
self.torches = roll_dice(1,6)
self.rations = roll_dice(1,6)
self.equipment = self.set_equipment()
# all armor, individual classes may have overrides
self.possible_armor = list(armor.keys())
# all weapons, individual classes may have overrides
self.possible_weapons = weapons
self.possible_melee_weapons = list(filter(lambda d: 'melee' in d['traits'], weapons))
self.possible_missile_weapons = list(filter(lambda d: 'missile' in d['traits'], weapons))
# each character should get 1 melee and 1 random (missle or melee)
self.weapons = [ random.choice(self.possible_melee_weapons), random.choice(self.possible_weapons) ]
self.armor = random.choice(self.possible_armor)
# special abilities
self.spells = None
self.spell_book = None
self.thief_skills = None
self.turn_undead = None
def __str__(self) -> str:
return f"{self.player_class}"
def get_subclass_dict() -> dict:
subclasses = {}
for subclass in Adventurer.__subclasses__():
subclasses[subclass.adv_class] = subclass
return subclasses
def get_json(self) -> dict:
char_dict = self.__dict__
char_json = json.dumps(char_dict)
return char_dict
def vertical_sheet(self) -> list:
sheet = []
sheet.append('{0: <28}'.format(f"| {self.player_class.title()} - Level {self.level}"))
for key, val in self.get_attributes().items():
key_string = "| " + '{0:16}'.format(f"{key}").capitalize() + f" {val}"
sheet += ['{0: <28}'.format(key_string)]
sheet.append('| ------------------------- ')
sheet.append('{0: <28}'.format(f"| HP: {self.hp} AC: {self.ac}"))
sheet.append('{0: <28}'.format(f"| Torches: {self.torches}"))
sheet.append('{0: <28}'.format(f"| Rations: {self.rations}"))
sheet.append('{0: <28}'.format(f"| Armor: {self.armor}"))
sheet.append('{0: <28}'.format(f"| Weapons:"))
sheet.append('{0: <28}'.format(f"| {self.weapons[0]['name'].title()}"))
sheet.append('{0: <28}'.format(f"| {self.weapons[1]['name'].title()}"))
sheet.append('{0: <28}'.format(f"| Equipment:"))
for item in self.equipment:
item_string = "| " + '{0:22}'.format(f"{item}").title()
sheet += ['{0: <28}'.format(item_string)]
sheet.append('{0: <28}'.format(f"| "))
sheet.append('{0: <28}'.format(f"| Gold: {self.gold}"))
sheet.append('| ------------------------- ')
for key, val in self.progression[self.level]['saves'].items():
key_string = "| " + '{0:22}'.format(f"{key}").title() + f" {val}"
sheet += ['{0: <28}'.format(key_string)]
sheet.append('| ------------------------- ')
#if self.spells:
# sheet.append('{0: <28}'.format("| Spellbook: "))
# sheet += ['{0: <28}'.format(f"| {spell.title()}") for spell in self.spell_book ]
# append a | to each string, after the formatted whitespace
sheet = [ line + "|" for line in sheet ]
return sheet
def full_sheet(self) -> list:
sheet = { 1: [], 2 : [] }
sheet[1].append('{0: <60}'.format(f"| Character Name:"))
sheet[1].append('{0: <60}'.format(f"| Player Name :"))
sheet[1].append('{0: <60}'.format(f"| Class: {self.player_class.title()} Alignment:"))
sheet[1].append('{0: <60}'.format(f"| Title: Level {self.level}"))
sheet[1].append('| ------------------------------------------ ')
sheet[1].append('{0: <60}'.format(f"| ABILITY SCORES\t\t\tSAVING THROWS"))
sheet[1].append('{0: <60}'.format(f"| Strength:\t{self.strength} Death: {self.ac}"))
sheet[1].append('{0: <60}'.format(f"| Intelligence:{self.intelligence} Wands: {self.ac}"))
sheet[1].append('{0: <60}'.format(f"| Wisdom:\t {self.wisdom} Para: {self.ac}"))
sheet[1].append('{0: <60}'.format(f"| Dexterity:\t{self.dexterity} Breath: {self.ac}"))
sheet[1].append('{0: <60}'.format(f"| Constitution: {self.constitution} Spells: {self.ac}"))
sheet[1].append('{0: <60}'.format(f"| Charisma:\t{self.charisma} SpellMod: {self.ac}"))
return sheet
def get_attributes(self) -> dict:
attribute_list = ['strength', 'intelligence', 'wisdom', 'dexterity', 'constitution', 'charisma']
return {k: self.__dict__[k] for k in attribute_list}
def get_best_prime_attribute(self) -> str:
attribute_list = [ 'strength', 'intelligence', 'wisdom', 'dexterity' ]
prime_attributes = {k: self.__dict__[k] for k in attribute_list}
# return highest, found this at https://stackoverflow.com/a/280156
return max(prime_attributes, key=prime_attributes.get)
def set_equipment(self) -> list:
equipment = [ 'backpack', 'tinderbox', 'waterskin' ]
for i in range(2):
item = ""
while item not in equipment:
item = random.choice(adventuring_gear)
if item not in equipment:
equipment.append(item)
return equipment
def select_spells(self, spell_list: dict) -> list:
spell_book = []
for spell_level, count in self.spells.items():
if count != "-":
for i in range(count):
new_spell = ""
while new_spell not in spell_book:
new_spell = random.choice(spell_list[spell_level])
if new_spell not in spell_book:
spell_book.append(new_spell)
return spell_book
def roll_all_hit_points(self) -> list:
hp_list = []
prev_hit_dice = 0
for i in range(self.__class__.max_level):
new_hit_dice = self.__class__.progression[i]['hit-dice']
hp_mod = self.__class__.progression[i]['hp-mod']
hit_dice = new_hit_dice - prev_hit_dice
prev_hit_dice = new_hit_dice
# check for constitution bonus to hp
if hp_mod == 0 and self.constitution >= 13: hp_mod = 1
if hp_mod == 0 and self.constitution >= 16: hp_mod = 2
if hp_mod == 0 and self.constitution >= 18: hp_mod = 3
hp_roll = roll_dice(hit_dice,self.__class__.hit_die) + hp_mod
hp_list.append(hp_roll)
return hp_list
def get_class_progression_for_level(self) -> list:
return list(filter(lambda d: d['level'] == self.level, self.__class__.progression))[0]
def set_level(self, new_level: int) -> None:
self.level = new_level
self.hp = sum(self.hp_rolls[:self.level])
if self.player_class in [ "magic user", "elf" ]:
self.spells = MagicUser.spells[self.level]
self.spell_book = self.select_spells(magic_user_spells)
if self.player_class == "cleric":
self.spells = Cleric.spells[self.level]
self.spell_book = self.select_spells(cleric_spells)
self.turn_undead = Cleric.turn_undead[self.level]
def set_attack_bonus(self) -> int:
prog = self.get_class_progression_for_level()
thac0 = prog['thac0']
atk = 19 - thac0
return atk
class Fighter(Adventurer):
adv_class = "fighter"
requirements = None
prime_requisite = "strength"
hit_die = 8
max_level = 14
progression = [
{ "level" : 1, "xp" : 0, "hit-dice" : 1, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 14, "breath attack" : 15, "spells / rods / staves" : 16 }},
{ "level" : 2, "xp" : 2000, "hit-dice" : 2, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 14, "breath attack" : 15, "spells / rods / staves" : 16 }},
{ "level" : 3, "xp" : 4000, "hit-dice" : 3, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 14, "breath attack" : 15, "spells / rods / staves" : 16 }},
{ "level" : 4, "xp" : 8000, "hit-dice" : 4, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 12, "breath attack" : 13, "spells / rods / staves" : 14 }},
{ "level" : 5, "xp" : 16000, "hit-dice" : 5, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 12, "breath attack" : 13, "spells / rods / staves" : 14 }},
{ "level" : 6, "xp" : 32000, "hit-dice" : 6, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 12, "breath attack" : 13, "spells / rods / staves" : 14 }},
{ "level" : 7, "xp" : 64000, "hit-dice" : 7, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 10, "spells / rods / staves" : 12 }},
{ "level" : 8, "xp" : 120000, "hit-dice" : 8, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 10, "spells / rods / staves" : 12 }},
{ "level" : 9, "xp" : 240000, "hit-dice" : 9, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 10, "spells / rods / staves" : 12 }},
{ "level" : 10, "xp" : 360000, "hit-dice" : 9, "hp-mod" : 2, "thac0" : 12, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 10 }},
{ "level" : 11, "xp" : 480000, "hit-dice" : 9, "hp-mod" : 4, "thac0" : 12, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 10 }},
{ "level" : 12, "xp" : 600000, "hit-dice" : 9, "hp-mod" : 6, "thac0" : 12, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 10 }},
{ "level" : 13, "xp" : 720000, "hit-dice" : 9, "hp-mod" : 8, "thac0" : 10, "saves" : { "death / poison" : 4, "wands" : 5, "paralysis / petrify" : 6, "breath attack" : 5, "spells / rods / staves" : 8 }},
{ "level" : 14, "xp" : 840000, "hit-dice" : 9, "hp-mod" : 10, "thac0" : 10, "saves" : { "death / poison" : 4, "wands" : 5, "paralysis / petrify" : 6, "breath attack" : 5, "spells / rods / staves" : 8 }}
]
def __init__(self, c_id, level, attributes={}) -> None:
Adventurer.__init__(self, c_id, level, attributes)
self.player_class = Fighter.adv_class
self.progression = Fighter.progression
self.hp_rolls = self.roll_all_hit_points()
self.hp = sum(self.hp_rolls[:self.level])
self.ac = armor[self.armor]
self.atk = self.set_attack_bonus()
class MagicUser(Adventurer):
adv_class = "magic user"
requirements = None
prime_requisite = "intelligence"
hit_die = 4
max_level = 14
progression = [
{ "level" : 1, "xp" : 0, "hit-dice" : 1, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 2, "xp" : 2500, "hit-dice" : 2, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 3, "xp" : 5000, "hit-dice" : 3, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 4, "xp" : 10000, "hit-dice" : 4, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 5, "xp" : 20000, "hit-dice" : 5, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 6, "xp" : 40000, "hit-dice" : 6, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 11, "breath attack" : 11, "spells / rods / staves" : 12 }},
{ "level" : 7, "xp" : 80000, "hit-dice" : 7, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 11, "breath attack" : 11, "spells / rods / staves" : 12 }},
{ "level" : 8, "xp" : 150000, "hit-dice" : 8, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 11, "breath attack" : 11, "spells / rods / staves" : 12 }},
{ "level" : 9, "xp" : 300000, "hit-dice" : 9, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 11, "breath attack" : 11, "spells / rods / staves" : 12 }},
{ "level" : 10, "xp" : 450000, "hit-dice" : 9, "hp-mod" : 1, "thac0" : 17, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 11, "breath attack" : 11, "spells / rods / staves" : 12 }},
{ "level" : 11, "xp" : 600000, "hit-dice" : 9, "hp-mod" : 2, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 8 }},
{ "level" : 12, "xp" : 750000, "hit-dice" : 9, "hp-mod" : 3, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 8 }},
{ "level" : 13, "xp" : 900000, "hit-dice" : 9, "hp-mod" : 4, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 8 }},
{ "level" : 14, "xp" : 1050000, "hit-dice" : 9, "hp-mod" : 5, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 8 }}
]
spells = {
1: { 1: 1, 2: '-', 3: '-', 4: '-', 5: '-', 6: '-'},
2: { 1: 2, 2: '-', 3: '-', 4: '-', 5: '-', 6: '-'},
3: { 1: 2, 2: 1, 3: '-', 4: '-', 5: '-', 6: '-'},
4: { 1: 2, 2: 2, 3: '-', 4: '-', 5: '-', 6: '-'},
5: { 1: 2, 2: 2, 3: 1, 4: '-', 5: '-', 6: '-'},
6: { 1: 2, 2: 2, 3: 2, 4: '-', 5: '-', 6: '-'},
7: { 1: 3, 2: 2, 3: 2, 4: 1, 5: '-', 6: '-'},
8: { 1: 3, 2: 3, 3: 2, 4: 2, 5: '-', 6: '-'},
9: { 1: 3, 2: 3, 3: 3, 4: 2, 5: 1, 6: '-'},
10: { 1: 3, 2: 3, 3: 3, 4: 3, 5: 2, 6: '-'},
11: { 1: 4, 2: 3, 3: 3, 4: 3, 5: 2, 6: 1 },
12: { 1: 4, 2: 4, 3: 3, 4: 3, 5: 2, 6: 1 },
13: { 1: 4, 2: 4, 3: 4, 4: 3, 5: 3, 6: 3 },
14: { 1: 4, 2: 4, 3: 4, 4: 4, 5: 3, 6: 3 }
}
def __init__(self,c_id, level, attributes={}) -> None:
Adventurer.__init__(self, c_id, level, attributes)
self.player_class = MagicUser.adv_class
self.progression = MagicUser.progression
self.hp_rolls = self.roll_all_hit_points()
self.hp = sum(self.hp_rolls[:self.level])
self.armor = "None"
self.ac = armor[self.armor]
self.weapons = [ list(filter(lambda d: 'silver dagger' in d['name'],weapons))[0], { "name" : "" } ]
self.spells = MagicUser.spells[self.level]
self.spell_book = self.select_spells(magic_user_spells)
self.atk = self.set_attack_bonus()
class Cleric(Adventurer):
adv_class = "cleric"
requirements = None
prime_requisite = "wisdom"
hit_die = 6
max_level = 14
progression = [
{ "level" : 1, "xp" : 0, "hit-dice" : 1, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 14, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 2, "xp" : 1500, "hit-dice" : 2, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 14, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 3, "xp" : 3000, "hit-dice" : 3, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 14, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 4, "xp" : 6000, "hit-dice" : 4, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 11, "wands" : 12, "paralysis / petrify" : 14, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 5, "xp" : 12000, "hit-dice" : 5, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 9, "wands" : 10, "paralysis / petrify" : 12, "breath attack" : 14, "spells / rods / staves" : 12 }},
{ "level" : 6, "xp" : 25000, "hit-dice" : 6, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 9, "wands" : 10, "paralysis / petrify" : 12, "breath attack" : 14, "spells / rods / staves" : 12 }},
{ "level" : 7, "xp" : 50000, "hit-dice" : 7, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 9, "wands" : 10, "paralysis / petrify" : 12, "breath attack" : 14, "spells / rods / staves" : 12 }},
{ "level" : 8, "xp" : 100000, "hit-dice" : 8, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 9, "wands" : 10, "paralysis / petrify" : 12, "breath attack" : 14, "spells / rods / staves" : 12 }},
{ "level" : 9, "xp" : 200000, "hit-dice" : 9, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 9, "breath attack" : 14, "spells / rods / staves" : 9 }},
{ "level" : 10, "xp" : 300000, "hit-dice" : 9, "hp-mod" : 1, "thac0" : 14, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 9, "breath attack" : 11, "spells / rods / staves" : 9 }},
{ "level" : 11, "xp" : 400000, "hit-dice" : 9, "hp-mod" : 2, "thac0" : 14, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 9, "breath attack" : 11, "spells / rods / staves" : 9 }},
{ "level" : 12, "xp" : 500000, "hit-dice" : 9, "hp-mod" : 3, "thac0" : 14, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 9, "breath attack" : 11, "spells / rods / staves" : 9 }},
{ "level" : 13, "xp" : 600000, "hit-dice" : 9, "hp-mod" : 4, "thac0" : 12, "saves" : { "death / poison" : 3, "wands" : 5, "paralysis / petrify" : 7, "breath attack" : 8, "spells / rods / staves" : 7 }},
{ "level" : 14, "xp" : 700000, "hit-dice" : 9, "hp-mod" : 5, "thac0" : 12, "saves" : { "death / poison" : 3, "wands" : 5, "paralysis / petrify" : 7, "breath attack" : 8, "spells / rods / staves" : 7 }}
]
spells = {
1: { 1: '-', 2: '-', 3: '-', 4: '-', 5: '-'},
2: { 1: 1, 2: '-', 3: '-', 4: '-', 5: '-'},
3: { 1: 2, 2: '-', 3: '-', 4: '-', 5: '-'},
4: { 1: 2, 2: 1, 3: '-', 4: '-', 5: '-'},
5: { 1: 2, 2: 2, 3: '-', 4: '-', 5: '-'},
6: { 1: 2, 2: 2, 3: 1, 4: 1, 5: '-'},
7: { 1: 2, 2: 2, 3: 2, 4: 1, 5: 1 },
8: { 1: 3, 2: 3, 3: 2, 4: 2, 5: 1 },
9: { 1: 3, 2: 3, 3: 3, 4: 2, 5: 2 },
10: { 1: 4, 2: 4, 3: 3, 4: 3, 5: 2 },
11: { 1: 4, 2: 4, 3: 4, 4: 3, 5: 3 },
12: { 1: 5, 2: 5, 3: 4, 4: 4, 5: 3 },
13: { 1: 5, 2: 5, 3: 4, 4: 4, 5: 4 },
14: { 1: 6, 2: 5, 3: 5, 4: 5, 5: 4 }
}
turn_undead = {
1: { '1': '7', '2': '9', '2*': '11', 3: '-', 4: '-', 5: '-', '6' : '-', '7-9' : '-' },
2: { '1': 'T', '2': '7', '2*': '9', 3: '11', 4: '-', 5: '-', '6' : '-', '7-9' : '-' },
3: { '1': 'T', '2': 'T', '2*': '7', 3: '9', 4: '11', 5: '-', '6' : '-', '7-9' : '-' },
4: { '1': 'D', '2': 'T', '2*': 'T', 3: '7', 4: '9', 5: '11', '6' : '-', '7-9' : '-' },
5: { '1': 'D', '2': 'D', '2*': 'T', 3: 'T', 4: '7', 5: '9', '6' : '11', '7-9' : '-' },
6: { '1': 'D', '2': 'D', '2*': 'D', 3: 'T', 4: 'T', 5: '7', '6' : '9', '7-9' : '11'},
7: { '1': 'D', '2': 'D', '2*': 'D', 3: 'D', 4: 'T', 5: 'T', '6' : '7', '7-9' : '9' },
8: { '1': 'D', '2': 'D', '2*': 'D', 3: 'D', 4: 'D', 5: 'T', '6' : 'T', '7-9' : '7' },
9: { '1': 'D', '2': 'D', '2*': 'D', 3: 'D', 4: 'D', 5: 'D', '6' : 'D', '7-9' : 'T' },
11: { '1': 'D', '2': 'D', '2*': 'D', 3: 'D', 4: 'D', 5: 'D', '6' : 'D', '7-9' : 'T' },
12: { '1': 'D', '2': 'D', '2*': 'D', 3: 'D', 4: 'D', 5: 'D', '6' : 'D', '7-9' : 'D' },
13: { '1': 'D', '2': 'D', '2*': 'D', 3: 'D', 4: 'D', 5: 'D', '6' : 'D', '7-9' : 'D' },
14: { '1': 'D', '2': 'D', '2*': 'D', 3: 'D', 4: 'D', 5: 'D', '6' : 'D', '7-9' : 'D' }
}
def __init__(self,c_id, level, attributes={}) -> None:
Adventurer.__init__(self, c_id, level, attributes)
self.player_class = Cleric.adv_class
self.progression = Cleric.progression
self.hp_rolls = self.roll_all_hit_points()
self.hp = sum(self.hp_rolls[:self.level])
self.armor = random.choice(list(armor.keys()))
self.ac = armor[self.armor]
# clerics can only wield blunt weapons
self.possible_melee_weapons = list(filter(lambda d: 'blunt' in d['traits'] and 'melee' in d['traits'], weapons))
self.possible_weapons = list(filter(lambda d: 'blunt' in d['traits'], weapons))
self.weapons = [ random.choice(self.possible_melee_weapons), random.choice(self.possible_weapons) ]
self.spells = Cleric.spells[self.level]
self.spell_book = self.select_spells(cleric_spells)
self.atk = self.set_attack_bonus()
self.turn_undead = Cleric.turn_undead[self.level]
class Thief(Adventurer):
adv_class = "thief"
prime_requisite = "dexterity"
requirements = None
hit_die = 4
max_level = 14
progression = [
{ "level" : 1, "xp" : 0, "hit-dice" : 1, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 2, "xp" : 1200, "hit-dice" : 2, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 3, "xp" : 2400, "hit-dice" : 3, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 4, "xp" : 4800, "hit-dice" : 4, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 13, "wands" : 14, "paralysis / petrify" : 13, "breath attack" : 16, "spells / rods / staves" : 15 }},
{ "level" : 5, "xp" : 9600, "hit-dice" : 5, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 11, "breath attack" : 14, "spells / rods / staves" : 13 }},
{ "level" : 6, "xp" : 20000, "hit-dice" : 6, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 11, "breath attack" : 14, "spells / rods / staves" : 13 }},
{ "level" : 7, "xp" : 40000, "hit-dice" : 7, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 11, "breath attack" : 14, "spells / rods / staves" : 13 }},
{ "level" : 8, "xp" : 80000, "hit-dice" : 8, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 11, "breath attack" : 14, "spells / rods / staves" : 13 }},
{ "level" : 9, "xp" : 160000, "hit-dice" : 9, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 9, "breath attack" : 11, "spells / rods / staves" : 10 }},
{ "level" : 10, "xp" : 280000, "hit-dice" : 9, "hp-mod" : 2, "thac0" : 14, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 9, "breath attack" : 11, "spells / rods / staves" : 10 }},
{ "level" : 11, "xp" : 400000, "hit-dice" : 9, "hp-mod" : 4, "thac0" : 14, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 9, "breath attack" : 11, "spells / rods / staves" : 10 }},
{ "level" : 12, "xp" : 520000, "hit-dice" : 9, "hp-mod" : 6, "thac0" : 14, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 9, "breath attack" : 11, "spells / rods / staves" : 10 }},
{ "level" : 13, "xp" : 640000, "hit-dice" : 9, "hp-mod" : 8, "thac0" : 12, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 7, "breath attack" : 10, "spells / rods / staves" : 8 }},
{ "level" : 14, "xp" : 760000, "hit-dice" : 9, "hp-mod" : 10, "thac0" : 12, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 7, "breath attack" : 10, "spells / rods / staves" : 8 }}
]
def __init__(self,c_id, level, attributes={}) -> None:
Adventurer.__init__(self, c_id, level, attributes)
self.player_class = Thief.adv_class
self.progression = Thief.progression
self.hp_rolls = self.roll_all_hit_points()
self.hp = sum(self.hp_rolls[:self.level])
self.armor = random.choice(list(armor.keys()))
self.ac = armor[self.armor]
self.atk = self.set_attack_bonus()
class Dwarf(Adventurer):
adv_class = "dwarf"
requirements = {'constitution' : 9 }
prime_requisite = "strength"
hit_die = 8
max_level = 12
progression = [
{ "level" : 1, "xp" : 0, "hit-dice" : 1, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 2, "xp" : 2200, "hit-dice" : 2, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 3, "xp" : 4400, "hit-dice" : 3, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 4, "xp" : 8800, "hit-dice" : 4, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 5, "xp" : 17000, "hit-dice" : 5, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 6, "xp" : 35000, "hit-dice" : 6, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 7, "xp" : 70000, "hit-dice" : 7, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 4, "wands" : 5, "paralysis / petrify" : 6, "breath attack" : 7, "spells / rods / staves" : 8 }},
{ "level" : 8, "xp" : 140000, "hit-dice" : 8, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 4, "wands" : 5, "paralysis / petrify" : 6, "breath attack" : 7, "spells / rods / staves" : 8 }},
{ "level" : 9, "xp" : 270000, "hit-dice" : 9, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 4, "wands" : 5, "paralysis / petrify" : 6, "breath attack" : 7, "spells / rods / staves" : 8 }},
{ "level" : 10, "xp" : 400000, "hit-dice" : 9, "hp-mod" : 3, "thac0" : 12, "saves" : { "death / poison" : 2, "wands" : 3, "paralysis / petrify" : 4, "breath attack" : 4, "spells / rods / staves" : 6 }},
{ "level" : 11, "xp" : 530000, "hit-dice" : 9, "hp-mod" : 6, "thac0" : 12, "saves" : { "death / poison" : 2, "wands" : 3, "paralysis / petrify" : 4, "breath attack" : 4, "spells / rods / staves" : 6 }},
{ "level" : 12, "xp" : 660000, "hit-dice" : 9, "hp-mod" : 9, "thac0" : 12, "saves" : { "death / poison" : 2, "wands" : 3, "paralysis / petrify" : 4, "breath attack" : 4, "spells / rods / staves" : 6 }}
]
def __init__(self,c_id, level, attributes={}) -> None:
Adventurer.__init__(self, c_id, level, attributes)
self.player_class = Dwarf.adv_class
self.progression = Dwarf.progression
self.hp_rolls = self.roll_all_hit_points()
self.hp = sum(self.hp_rolls[:self.level])
self.armor = random.choice(list(armor.keys()))
self.ac = armor[self.armor]
self.atk = self.set_attack_bonus()
class Elf(Adventurer):
adv_class = "elf"
requirements = {'intelligence' : 9 }
prime_requisite = "intellgence"
hit_die = 6
max_level = 10
progression = [
{ "level" : 1, "xp" : 0, "hit-dice" : 1, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 13, "breath attack" : 15, "spells / rods / staves" : 15 }},
{ "level" : 2, "xp" : 4000, "hit-dice" : 2, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 13, "breath attack" : 15, "spells / rods / staves" : 15 }},
{ "level" : 3, "xp" : 8000, "hit-dice" : 3, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 12, "wands" : 13, "paralysis / petrify" : 13, "breath attack" : 15, "spells / rods / staves" : 15 }},
{ "level" : 4, "xp" : 16000, "hit-dice" : 4, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 11, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 5, "xp" : 32000, "hit-dice" : 5, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 11, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 6, "xp" : 64000, "hit-dice" : 6, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 10, "wands" : 11, "paralysis / petrify" : 11, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 7, "xp" : 120000, "hit-dice" : 7, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 9, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 8, "xp" : 250000, "hit-dice" : 8, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 9, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 9, "xp" : 400000, "hit-dice" : 9, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 9, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 10, "xp" : 600000, "hit-dice" : 9, "hp-mod" : 2, "thac0" : 12, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 8, "spells / rods / staves" : 8 }}
]
spells = {
1: { 1: 1, 2: '-', 3: '-', 4: '-', 5: '-', 6: '-'},
2: { 1: 2, 2: '-', 3: '-', 4: '-', 5: '-', 6: '-'},
3: { 1: 2, 2: 1, 3: '-', 4: '-', 5: '-', 6: '-'},
4: { 1: 2, 2: 2, 3: '-', 4: '-', 5: '-', 6: '-'},
5: { 1: 2, 2: 2, 3: 1, 4: '-', 5: '-', 6: '-'},
6: { 1: 2, 2: 2, 3: 2, 4: '-', 5: '-', 6: '-'},
7: { 1: 3, 2: 2, 3: 2, 4: 1, 5: '-', 6: '-'},
8: { 1: 3, 2: 3, 3: 2, 4: 2, 5: '-', 6: '-'},
9: { 1: 3, 2: 3, 3: 3, 4: 2, 5: 1, 6: '-'},
10: { 1: 3, 2: 3, 3: 3, 4: 3, 5: 2, 6: '-'}
}
def __init__(self,c_id, level, attributes={}) -> None:
Adventurer.__init__(self, c_id, level, attributes)
self.player_class = Elf.adv_class
self.progression = Elf.progression
self.hp_rolls = self.roll_all_hit_points()
self.hp = sum(self.hp_rolls[:self.level])
self.armor = random.choice(list(armor.keys()))
self.ac = armor[self.armor]
self.spells = Elf.spells[self.level]
self.spell_book = self.select_spells(magic_user_spells)
self.atk = self.set_attack_bonus()
class Halfling(Adventurer):
adv_class = "halfling"
requirements = {'constitution' : 9, 'dexterity' : 9 }
prime_requisite = "dexterity"
hit_die = 6
max_level = 8
progression = [
{ "level" : 1, "xp" : 0, "hit-dice" : 1, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 2, "xp" : 2000, "hit-dice" : 2, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 3, "xp" : 4000, "hit-dice" : 3, "hp-mod" : 0, "thac0" : 19, "saves" : { "death / poison" : 8, "wands" : 9, "paralysis / petrify" : 10, "breath attack" : 13, "spells / rods / staves" : 12 }},
{ "level" : 4, "xp" : 8000, "hit-dice" : 4, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 5, "xp" : 16000, "hit-dice" : 5, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 6, "xp" : 32000, "hit-dice" : 6, "hp-mod" : 0, "thac0" : 17, "saves" : { "death / poison" : 6, "wands" : 7, "paralysis / petrify" : 8, "breath attack" : 10, "spells / rods / staves" : 10 }},
{ "level" : 7, "xp" : 64000, "hit-dice" : 7, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 4, "wands" : 5, "paralysis / petrify" : 6, "breath attack" : 7, "spells / rods / staves" : 8 }},
{ "level" : 8, "xp" : 120000, "hit-dice" : 8, "hp-mod" : 0, "thac0" : 14, "saves" : { "death / poison" : 4, "wands" : 5, "paralysis / petrify" : 6, "breath attack" : 7, "spells / rods / staves" : 8 }},
]
def __init__(self,c_id, level, attributes={}) -> None:
Adventurer.__init__(self, c_id, level, attributes)
self.player_class = Halfling.adv_class
self.progression = Halfling.progression
self.hp_rolls = self.roll_all_hit_points()
self.hp = sum(self.hp_rolls[:self.level])
self.armor = random.choice(list(armor.keys()))
self.ac = armor[self.armor]
self.atk = self.set_attack_bonus()

118
app.py Normal file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/python3
import zlib
from main import *
from adventurers import *
from flask import Flask, render_template, redirect, request, make_response
from flask_session import Session
app = Flask(__name__)
# useful functions
def compress_character(adventurer):
cookie_string = adventurer.identifier
adventurer_json = json.dumps(adventurer.get_json())
compressed_data = zlib.compress(adventurer_json.encode())
cookie_data = base64.urlsafe_b64encode(compressed_data).decode()
return cookie_string, cookie_data
def return_character_from_cookie(cookie):
cookie_decompressed = base64.urlsafe_b64decode(cookie)
adventurer_dict = json.loads(zlib.decompress(cookie_decompressed).decode())
adventurer = AdventurerGen.create_from_dict(adventurer_dict)
return adventurer
@app.route('/')
def index():
tab = request.args.get("tab", default="what", type=str)
# control argument inputs to tabs available on index.html
if tab not in [ "what", "how", "why", "about" ]:
tab = "what"
return render_template("index.html", tab=tab)
@app.route('/party', methods = ["GET"])
def party():
# default values, modified if cookies exist
stored_adv_party = []
stored_count = None
stored_level = None
if request.method == "GET":
count = request.args.get("count", default=4, type=int)
level = request.args.get("level", default=1, type=int)
cache = request.args.get("cache", default='true', type=str)
# control inputs to prevent abuse
if cache not in [ "true", "false" ]:
cache = 'true'
if level < 1 or level > 5:
level = 1
if count < 2 or count > 5:
count = 4
# generate an adventuring party
adv_party = PartyGen.get_new_party(count, level)
# check for cookies present
if request.cookies:
stored_count = len(request.cookies)
for c in request.cookies:
if c.startswith('adv'):
new_char = return_character_from_cookie(request.cookies[c])
stored_level = new_char.level
stored_adv_party.append(new_char)
# if request arguments match the adventure party count & level stored in the cookies, just use the cookies
if cache == 'true':
if level != stored_level:
for adv in stored_adv_party:
adv.set_level(level)
if count == stored_count:
adv_party.set_party(stored_adv_party, count, level)
if count > stored_count:
extension = count - stored_count
more_party_members = PartyGen.get_new_party(extension,level).adventurers
extended_party = stored_adv_party + more_party_members
adv_party.set_party(extended_party, count, level)
if count < stored_count:
reduction = count - stored_count
reduced_party = stored_adv_party[:reduction]
adv_party.set_party(reduced_party, count, level)
# generate page
response = make_response(render_template("party.html", adv_party=adv_party, count=count, level=level, cache=cache))
# make a cookie for each character
for adv in adv_party.adventurers:
cookie_string, cookie_data = compress_character(adv)
response.set_cookie(cookie_string, cookie_data)
return response
@app.route('/character', methods = ["GET","POST"])
def character():
if request.method == "GET":
c_id = request.args.get("id", default="adv-1", type=str)
level = request.args.get("level", default=1, type=int)
role = request.args.get("role", default="fighter", type=str)
cache = request.args.get("cache", default='true', type=str)
# control inputs to prevent abuse
if cache not in [ "true", "false" ]:
cache = 'true'
if not c_id.startswith('adv-'):
c_id = 'adv-1'
if level < 1 or level > 5:
level = 1
if role not in [ 'fighter','magic-user','cleric', 'thief', 'dwarf', 'elf', 'halfling']:
role = 'fighter'
# check cookies for characters
if request.cookies and cache != "false":
character = return_character_from_cookie(request.cookies[c_id])
if character.level != level:
character.set_level(level)
# reroll until we get the class from the cookie, if it doesn't match
while character.player_class != role.replace("-"," "):
new_char = Adventurer(c_id, level)
selected_class = AdventurerGen(new_char).selection()
character = selected_class(new_char.identifier, new_char.level, new_char.get_attributes())
else:
new_char = Adventurer(c_id, level)
selected_class = AdventurerGen(new_char).selection()
character = selected_class(new_char.identifier, new_char.level, new_char.get_attributes())
role = character.player_class
level = character.level
response = make_response(render_template("character.html", character=character, level=level, cache=cache,role=role))
cookie_string, cookie_data = compress_character(character)
response.set_cookie(cookie_string, cookie_data)
return response

25
equipment.py Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/python3
armor= { 'None' : 9, 'Leather' : 7, 'Leather, Shield' : 6, 'Chain' : 5, 'Chain, Shield' : 4, 'Plate' : 3, 'Plate, Shield' : 2 }
weapons = [
{ 'name' : 'battle axe', 'damage-dice' : 8, 'traits' : [ 'melee', 'slow', 'two-handed' ] },
{ 'name' : 'club', 'damage-dice' : 4, 'traits' : [ 'melee', 'blunt' ] },
{ 'name' : 'crossbow', 'damage-dice' : 6, 'traits' : [ 'missile', 'reload','slow','two-handed'], 'ammo' : '20 bolts'},
{ 'name' : 'hand axe', 'damage-dice' : 6, 'traits' : [ 'melee', 'missile'] },
{ 'name' : 'mace', 'damage-dice' : 6, 'traits' : [ 'melee', 'blunt' ] },
{ 'name' : 'pole arm', 'damage-dice' : 10, 'traits' : [ 'melee', 'brace', 'slow', 'two-handed' ] },
{ 'name' : 'short bow', 'damage-dice' : 6, 'traits' : [ 'missile', 'two-handed' ], 'ammo' : '20 arrows'},
{ 'name' : 'short sword', 'damage-dice' : 6, 'traits' : [ 'melee' ] },
{ 'name' : 'silver dagger','damage-dice' : 4, 'traits' : [ 'melee', 'missile' ] },
{ 'name' : 'sling', 'damage-dice' : 4, 'traits' : [ 'missile', 'blunt' ], 'ammo' : '20 stones'},
{ 'name' : 'staff', 'damage-dice' : 4, 'traits' : [ 'melee', 'blunt', 'slow', 'two-handed' ] },
{ 'name' : 'spear', 'damage-dice' : 6, 'traits' : [ 'melee', 'missile', 'brace' ] },
{ 'name' : 'sword', 'damage-dice' : 8, 'traits' : [ 'melee' ] },
{ 'name' : 'war hammer', 'damage-dice' : 6, 'traits' : [ 'melee', 'blunt' ] }
]
adventuring_gear = [
"crowbar", "hammer (small) + 12 iron spikes", "holy water", "lantern + 3 flasks of oil", "mirror (small, steel)", "pole (10' long, wooden)",
"rope (50')", "rope (50') + grappling hook", "sack (large)", "sack (small)", "stakes (3) + mallet", "wolfsbane (1 bunch)"
]

184
main.py
View File

@@ -1,84 +1,128 @@
#!/usr/bin/python3 #!/usr/bin/python3
from adventurers import *
import base64
import json
import random import random
class PlayerCharacter: # Player Class Selector
def __init__(self, attributes={}) -> None: class AdventurerGen():
# using a get() method to pull an attribute else use a default value, https://python-academy.org/en/handbook/get def __init__(self, player: Adventurer) -> None:
self.strength = attributes.get('strength', roll_dice(3,6)) self.player = player
self.intelligence = attributes.get('intelligence', roll_dice(3,6)) # https://stackoverflow.com/questions/3862310/how-to-find-all-the-subclasses-of-a-class-given-its-name
self.wisdom = attributes.get('wisdom', roll_dice(3,6)) # pull classes that do not have requirements
self.dexterity = attributes.get('dexterity', roll_dice(3,6)) self.available_classes = [cls for cls in Adventurer.__subclasses__() if not cls.requirements]
self.constitution = attributes.get('constitution', roll_dice(3,6)) # pull classes that do have requirements
self.charisma = attributes.get('charisma', roll_dice(3,6)) self.classes_with_reqs = [cls for cls in Adventurer.__subclasses__() if cls.requirements]
# run function to randomly select an adventurer class
self.selected_class = self.selection()
def class_selector(self): def return_class_by_best_attribute(self) -> Adventurer:
# take the 4 primary attribtues # for adventurer classes in available classes, return the one where that classes' prime requisite is equal to the players best attribute
base_attr = self.__dict__.copy() return [adv_class for adv_class in self.available_classes if adv_class.prime_requisite == self.player.get_best_prime_attribute()][0]
del base_attr['constitution']
del base_attr['charisma'] def return_classes_with_requirements(self) -> list:
# of those grab the highest, found this at https://stackoverflow.com/a/280156 p_attrs = self.player.get_attributes()
max_base_attr = max(base_attr, key=base_attr.get) possible_classes = []
# use that to pick the best possible base class for c in self.classes_with_reqs:
best_class = { "strength" : "Fighter", "intelligence" : "Magic User", "wisdom" : "Cleric", "dexterity" : "Thief" } match_count = 0
best_base_class = best_class[max_base_attr] for r in c.requirements:
# create a list of possible classes if p_attrs[r] >= c.requirements[r]:
possible_classes = [ best_base_class ] match_count += 1
# check if player qualifies for other classes if match_count >= len(c.requirements):
if self.constitution >= 9 : possible_classes.append("Dwarf") possible_classes.append(c)
if self.intelligence >= 9 : possible_classes.append("Elf") return possible_classes
if self.constitution >= 9 and self.dexterity >= 9 : possible_classes.append("Halfling")
def selection(self) -> Adventurer:
best_prime_attribute = self.player.get_best_prime_attribute()
# create an array of possible player classes, add the best choice per player's best core attributes
possible_classes = [ self.return_class_by_best_attribute() ]
possible_classes += self.return_classes_with_requirements()
# randomly select class # randomly select class
selected_class = random.choice(possible_classes) selected_class = random.choice(possible_classes)
return f"{selected_class}" return selected_class
class Fighter(PlayerCharacter): def create_from_dict(character_dict) -> Adventurer:
prime_requisite = "strength" chosen_class = character_dict['player_class']
def __init__(self, attributes: dict) -> None: c_id = character_dict['identifier']
PlayerCharacter.__init__(self, attributes) level = character_dict['level']
self.player_class = "fighter" adv_dict = Adventurer.get_subclass_dict()
self.hp = roll_dice(1, 8) chosen_class = adv_dict[chosen_class]
new_char = chosen_class(c_id=c_id, level=level)
for k, v in character_dict.items():
setattr(new_char,k, v)
return new_char
class MagicUser(PlayerCharacter): class PartyGen():
prime_requisite = "intelligence" def __init__(self, party_size: int, party_level: int) -> None:
def __init__(self, attributes: dict) -> None: self.size = party_size
PlayerCharacter.__init__(self, name, attributes) self.level = party_level
self.player_class = "magic-user" self.adventurers = []
self.hp = roll_dice(1, 4) self.adventurer_types = []
class Cleric(PlayerCharacter): def gen_party(self) -> None:
prime_requisite = "wisdom" c_id = 1
def __init__(self, attributes: dict) -> None: while len(self.adventurers) < self.size:
PlayerCharacter.__init__(self, name, attributes) identifier = "adv-" + str(c_id)
self.player_class = "cleric" new_player = Adventurer(identifier, self.level)
self.hp = roll_dice(1, 6)
class Thief(PlayerCharacter):
prime_requisite = "dexterity"
def __init__(self, attributes: dict) -> None:
PlayerCharacter.__init__(self, name, attributes)
self.player_class = "thief"
self.hp = roll_dice(1, 6)
# functions
def roll_dice(count, sides):
return sum(random.randint(1,sides) for _ in range(count))
def main():
adventurer_party = []
expected_party_size = 4
while len(adventurer_party) < expected_party_size:
new_player = PlayerCharacter()
selected_class = None
attempts = 0 attempts = 0
while selected_class not in adventurer_party: while new_player.player_class not in self.adventurer_types:
attempts += 1 attempts += 1
selected_class = new_player.class_selector() selected_class = AdventurerGen(new_player).selection()
if selected_class not in adventurer_party: new_player = selected_class(new_player.identifier, new_player.level, new_player.get_attributes())
adventurer_party.append(selected_class)
# i couldnt randomly generate a scenario where a character couldn't be added, but it seems possible, so this is the hard cut off # i couldnt randomly generate a scenario where a character couldn't be added, but it seems possible, so this is the hard cut off
elif attempts > 10: if (new_player.player_class not in self.adventurer_types) or (attempts > 10):
adventurer_party.append(selected_class) c_id += 1
self.adventurers.append(new_player)
self.adventurer_types.append(new_player.player_class)
print(f"{adventurer_party}") def set_party(self, adventurers: list, count: int, level: int):
self.size = count
self.level = level
self.adventurers = adventurers
self.adventurer_types = []
main() def get_new_party(party_size: int, party_level: int):
# keep variables within expected ranges
if party_size <= 0 or party_size > 5:
party_size = 1
if party_level < 1 or party_level > 5:
party_level = 1
# generate an aventuring party per size and level
new_party = PartyGen(party_size, party_level)
# for adventurers select classes
new_party.gen_party()
# return the created adventurer party
return new_party
def get_character(self, identifer: str) -> Adventurer:
for adv in self.adventurers:
if adv.identifier == identifier:
return adv
def get_character_sheets(self) -> list:
sheet_string = ""
character_sheets = []
for c in self.adventurers:
character_sheets.append(c.vertical_sheet())
return character_sheets
def get_json(self) -> str:
party_list = []
for c in self.adventurers:
party_list.append(c.get_json())
party_json = json.dumps(party_list)
return party_json
def __str__(self) -> str:
return f"{self.adventurers}"
# used for local testing
def main():
adv_dict = Adventurer.get_subclass_dict()
test_class = adv_dict['cleric']
new_char = test_class(c_id="adv-1",level=2)
for line in new_char.vertical_sheet():
print(line)
if __name__ == "__main__":
main()

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
Flask==3.1.3

18
spells.py Normal file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/python3
magic_user_spells = {
1 : [ "charm person", "detect magic", "floating disc", "hold portal", "light (darkness)", "magic missile", "protection from evil", "read languages", "shield", "sleep", "ventriloquism" ],
2 : [ "continual light", "detect evil", "detect invisible", "ESP", "invisibility", "knock", "levitate", "locate object", "mirror image", "phantasmal force", "web", "wizard lock" ],
3 : [ "clairvoyance", "dispel magic", "fire ball", "fly", "haste", "hold person", "infravision", "invisibility 10'", "lightning bolt", "protection from evil 10'", "protection from normal missiles", "water breathing" ],
4 : [ "charm monster", "confusion", "dimension door", "growth of plants", "hallucinatory terrain", "massmorph", "polymorph others", "polymorph self", "remove curse", "wall of fire", "wall of ice", "wizard eye" ],
5 : [ "animate dead", "cloudkill", "conjure elemental", "contact higher plane", "feeblemind", "hold monster", "magic jar", "pass-wall", "telekinesis", "teleport", "transmute rock to mud", "wall of stone" ],
6 : [ "anti-magic shell", "control weather", "death spell", "disintegrate", "geas", "invisible stalker", "lower water", "move earth", "part water", "project image", "reincarnation", "stone to flesh" ]
}
cleric_spells = {
1 : [ "cure light wounds", "detect evil", "detect magic", "light (darkness)", "protection from evil", "purify food and water", "remove fear", "resist cold"],
2 : [ "bless", "find traps", "hold person", "know alignment", "resist fire", "silence 15'", "snake charm", "speak with animals" ],
3 : [ "continual light", "cure disease", "growth of animal", "locate object", "remove curse", "striking" ],
4 : [ "create water", "cure serious wounds", "neutralize poison", "protection from evil 10'", "speak with plants", "sticks to snakes" ],
5 : [ "commune", "create food", "dispel evil", "insect plague", "quest", "raise dead" ]
}

BIN
static/magic-user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

164
templates/character.html Normal file
View File

@@ -0,0 +1,164 @@
<!DOCTYPE html>
<html>
<head>
<title>D&D Characters</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"
>
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="/">Home</a>
<a class="navbar-item" href="character">Character Generator</a>
<a class="navbar-item" href="party?cache=true">Party Generator</a>
</div>
</div>
</nav>
<div class="card">
<div class="container">
<div class="content">
<div class="card-content">
<div class="content">
<div class="block">
<div class="block">
<h1>Character Generator</h1>
<p>The below lets you generate a character based on your preferences. If you change levels, there will be subtle changes, usually just the "saving throws" and "hp" will change. Some classes, like spellcasters, will gain new spells.</p>
<hr>
<div class="columns">
<div class="column" >
<p>Select a Class</p>
<div class="dropdown is-hoverable">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span>{{role.title()}}</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
{%for c in ['fighter','magic-user', 'cleric', 'thief', 'dwarf', 'elf', 'halfling'] %}
{% if role == c %}
<a href="{{ '/character?level={}&role={}&cache=true'.format(level,c) }}" class="dropdown-item is-active">{{c.title()}}</a>
{% else %}
<a href="{{ '/character?level={}&role={}&cache=true'.format(level,c) }}" class="dropdown-item">{{c.title()}}</a>
{% endif %}
{%endfor%}
</div>
</div>
</div>
</div>
<div class="column" >
<p>Character Level</p>
<div class="buttons">
{%for j in range(1,6)%}
{% if level|int() == j|int() %}
<a class="button is-primary" href="{{ '/character?&level={}&role={}&cache=true'.format(j,role) }}">{{j}}</a>
{% else %}
<a class="button" href="{{ '/character?&level={}&role={}&cache=true'.format(j,role) }}">{{j}}</a>
{% endif %}
{%endfor%}
</div>
</div>
<div class="column" >
<p>Generate new character sheet</p>
<div class="buttons">
<a class="button is-danger is-outlined" href="{{ '/character?&level={}&role={}&cache=false'.format(j,role,cache) }}">Re-Roll Character</a>
</div>
</div>
</div>
<br>
<div class="card">
<h3>{{character.adv_class.title()}} - Level {{character.level}}</h3>
<div class="columns">
<div class="column">
<table class="table">
<thead><tr><th>Ability Scores</th><th></th><th>Saving Throws</th><th></th></tr></thead>
<tbody><tr><th>Strength</th><td>{{character.strength}}</td><th>Death / Poison</th><td>{{character.progression[character.level]['saves']['death / poison']}}</tr></tbody>
<tbody><tr><th>Intelligence</th><td>{{character.intelligence}}</td><th>Magic Wands</th><td>{{character.progression[character.level]['saves']['wands']}}</tr></tbody>
<tbody><tr><th>Wisdom</th><td>{{character.wisdom}}</td><th>Paralysis / Petrification</th><td>{{character.progression[character.level]['saves']['paralysis / petrify']}}</tr></tbody>
<tbody><tr><th>Dexterity</th><td>{{character.dexterity}}</td><th>Breath Attacks</th><td>{{character.progression[character.level]['saves']['breath attack']}}</tr></tbody>
<tbody><tr><th>Constitution</th><td>{{character.constitution}}</td><th>Spells, Rods, Staves</th><td>{{character.progression[character.level]['saves']['spells / rods / staves']}}</tr></tbody>
<tbody><tr><th>Charisma</th><td>{{character.charisma}}</td><th>Wisdom Mod. to Saves v. Magic</th><td>+1</tr></tbody>
</table>
</div>
<div class="column">
<table class="table">
<thead><tr><th>Combat</th><th></th><th>Dungeon Gear</th><th></th></thead>
<tbody><tr><th>Hit Points</th><td>{{character.hp}}</td><th>Torches</th><td>{{character.torches}}</td></tr></tbody>
<tbody><tr><th>Armor Class</th><td>{{character.ac}}</td><th>Rations</th><td>{{character.rations}}</td></tr></tbody>
<tbody><tr><th>Attack Bonus</th><td>{{character.atk}}</td><th>Gold</th><td>{{character.gold}}</td></tbody>
</table>
</div>
</div>
<div class="columns">
<div class="column">
<table class="table">
<thead><tr><th>Equipment</th><th></th><th></th><th></th></tr></thead>
{% for e in character.equipment %}
<tbody><tr><th>{{e}}</th></tr></tbody>
{% endfor %}
</table>
</div>
{% if character.spells %}
<div class="column">
<table class="table">
<thead><tr><th>Spellbook</th><th></th><th></th><th></th></thead>
{%for spell in character.spell_book %}
<tbody><tr><th>{{spell}}</th><td></td><th></th><td></td></tr></tbody>
{%endfor%}
</table>
</div>
{% endif %}
{% if character.turn_undead %}
<div class="column">
<h5>Turn Undead</h5>
<table class="table">
<thead><tr><th>Monster Hit Die</th><th>Roll to Turn</th><th></th><th></th></thead>
{%for k,v in character.turn_undead.items() %}
<tbody><tr><th>{{k}} Hit Die</th><td>{{v}}</td><th></th><td></td></tr></tbody>
{%endfor%}
</table>
</div>
{% endif %}
{% if character.thief_skills %}
<div class="column">
<h5>Thief Skills</h5>
<table class="table">
<thead><tr><th>Thief Skills</th><th></th><th></th><th></th></thead>
{%for spell in character.spell_book %}
<tbody><tr><th>{{spell}}</th><td></td><th></th><td></td></tr></tbody>
{%endfor%}
</table>
</div>
{% endif %}
</div>
</div>
</div>
<br>
</div>
</div>
</div>
</div>
</body>
</html>

94
templates/index.html Normal file
View File

@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html>
<head>
<title>D&D Characters</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"
>
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="/">Home</a>
<a class="navbar-item" href="character">Character Generator</a>
<a class="navbar-item" href="party?cache=true">Party Generator</a>
</div>
</div>
</nav>
<div class="card">
<div class="container">
<div class="content">
<div class="card-content">
<div class="content">
<div class="block">
<h1>A Dungeons & Dragons Character Generator</h1>
<div class="tabs"><ul>
{% for t in ["what","how","why", "about"] %}
{%if t == tab %}
<li class="is-active"><a href="{{ '/?tab={}'.format(t) }}">{{t.title()}}</a></li>
{%else%}
<li><a href="{{ '/?tab={}'.format(t) }}">{{t.title()}}</a></li>
{%endif%}
{%endfor%}
</ul></div>
{%if tab == "what" %}
<h3>What is Dungeons & Dragons?</h3>
<p>Dungeons & Dragons, also known as D&D, is a pen and paper table top game. One player is the story teller, the "dungeon master" and the others interact with the story using their "adventurers." An adventurer is a playable character, who has a mixture of abilities and powers, the details of which are often kept on piece of paper called a "character sheet."</p>
<br>
<img src="https://static0.colliderimages.com/wordpress/wp-content/uploads/2024/06/dungeons-and-dragons-in-stranger-things.jpg">
{%endif%}
{%if tab == "how" %}
<h3>How does the game work?</h3>
<p>Most settings are fantasy based, like game of thrones or lord of the rings. Players often pick up a quest in one of these fantasy worlds, guided by the "Dungeon Master" who narrates the world. The players will make decisions, take actions, and ultimate help write the story as it unfolds.</p>
<br>
<img src="https://www.baltimoremagazine.com/wp-content/uploads/2023/03/dungeons-and-dragons-honor-among-thieves-movie-review-2023.jpeg">
{%endif%}
{%if tab == "why" %}
<h3>Why would I need a character generator?</h3>
<p>It takes time and effort to create a new character; there are many rules, phrases, and terminology you'll need to know to create a character. Most rule books provide an easy to follow guide on making player characters or "adventurers." However, if you are short on time, a generator such as this will get you in the game right away!</p>
<br>
<img src="https://assetsio.reedpopcdn.com/dnd-character-sheet-dice-pencil.jpeg">
{%endif%}
{%if tab == "about" %}
<h3>About This App</h3>
<p>This app accomplishes a few things, but mainly it creates a character for you.</p>
<ul>
<li>It follows the rules precisely from the books to generate a legal character</li>
<li>All stats are randomly rolled, but a class is chosen per your best stats</li>
<li>Character equipment and spells are also randomly selected, within that character-class rules</li>
<li>You can generate a whole party of characters</li>
<li>Within that party, you can adjust their level, and the number of characters</li>
<li>You can create a single character to edit</li>
<br>
<img src="static/magic-user.png">
</il>
{%endif%}
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

101
templates/party.html Normal file
View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<title>D&D Characters</title>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"
>
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="/">Home</a>
<a class="navbar-item" href="character">Character Generator</a>
<a class="navbar-item" href="party?cache=true">Party Generator</a>
</div>
</div>
</nav>
<div class="card">
<div class="container">
<div class="content">
<div class="card-content">
<div class="content">
<h2>Welcome to the Dungeon</h2>
<div class="block">
<div class="columns">
<div class="column" >
<p>How many party members?</p>
<div class="buttons">
{%for i in range(2,6)%}
{% if count|int() == i|int() %}
<a class="button is-primary" href="{{ '/party?count={}&level={}&cache={}'.format(i,level,cache) }}">{{i}}</a>
{% else %}
<a class="button" href="{{ '/party?count={}&level={}&cache={}'.format(i,level,cache) }}">{{i}}</a>
{% endif %}
{%endfor%}
</div>
</div>
<div class="column" >
<p>What level for the party?</p>
<div class="buttons">
{%for j in range(1,6)%}
{% if level|int() == j|int() %}
<a class="button is-primary" href="{{ '/party?count={}&level={}&cache={}'.format(count,j,cache) }}">{{j}}</a>
{% else %}
<a class="button" href="{{ '/party?count={}&level={}&cache={}'.format(count,j,cache) }}">{{j}}</a>
{% endif %}
{%endfor%}
</div>
</div>
<div class="column" >
<p>Preserve adventurers?</p>
<div class="buttons">
{% if cache == 'true' %}
<a class="button is-primary">cached</a>
<a class="button is-danger is-outlined" href="{{ '/party?count={}&level={}&cache=false'.format(count,level) }}">not cached</a>
{% else %}
<a class="button is-primary is-outlined" href="{{ '/party?count={}&level={}&cache=true'.format(count,level) }}">are cached</a>
<a class="button is-danger">not cached</a>
{% endif %}
</div>
</div>
</div>
</div>
<div class="block has-text-centered">
<div class="columns">
{%for character in adv_party.adventurers%}
<div class="column" >
<pre>{{character.vertical_sheet() | join("\n")}}</pre>
<div class="block has-text-centered">
<a class="button is-focused" href="{{ '/character?id={}&level={}&role={}&cache=true'.format(character.identifier,character.level,character.player_class)}}">Select this {{character.player_class.title()}}</a>
</div>
</div>
{%endfor%}
</div>
</div>
</div>
<br>
</div>
</div>
</div>
</div>
</body>
</html>