cleaning up code
This commit is contained in:
25
README.md
25
README.md
@@ -2,6 +2,8 @@
|
||||
|
||||
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."
|
||||
|
||||
@@ -20,3 +22,26 @@ This app accomplishes a few things, but mainly it creates a character for you.
|
||||
- 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."
|
||||
|
||||
|
||||
@@ -10,6 +10,28 @@ def roll_dice(count, sides):
|
||||
|
||||
# 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
|
||||
@@ -27,7 +49,7 @@ class Adventurer:
|
||||
self.gold= roll_dice(3,6)
|
||||
self.torches = roll_dice(1,6)
|
||||
self.rations = roll_dice(1,6)
|
||||
self.equipment = self.roll_equipment()
|
||||
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
|
||||
@@ -43,21 +65,21 @@ class Adventurer:
|
||||
self.thief_skills = None
|
||||
self.turn_undead = None
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.player_class}"
|
||||
|
||||
def get_subclass_dict():
|
||||
def get_subclass_dict() -> dict:
|
||||
subclasses = {}
|
||||
for subclass in Adventurer.__subclasses__():
|
||||
subclasses[subclass.adv_class] = subclass
|
||||
return subclasses
|
||||
|
||||
def get_json(self):
|
||||
def get_json(self) -> dict:
|
||||
char_dict = self.__dict__
|
||||
char_json = json.dumps(char_dict)
|
||||
return char_dict
|
||||
|
||||
def vertical_sheet(self):
|
||||
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():
|
||||
@@ -89,7 +111,7 @@ class Adventurer:
|
||||
sheet = [ line + "|" for line in sheet ]
|
||||
return sheet
|
||||
|
||||
def full_sheet(self):
|
||||
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 :"))
|
||||
@@ -105,26 +127,27 @@ class Adventurer:
|
||||
sheet[1].append('{0: <60}'.format(f"| Charisma:\t{self.charisma} SpellMod: {self.ac}"))
|
||||
return sheet
|
||||
|
||||
def get_attributes(self):
|
||||
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):
|
||||
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 roll_equipment(self):
|
||||
def set_equipment(self):
|
||||
equipment = [ 'backpack', 'tinderbox', 'waterskin' ]
|
||||
random_items = [ random.choice(adventuring_gear) for i in range(2) ]
|
||||
for item in random_items:
|
||||
equipment += item.split("+")
|
||||
while len(equipment) < 7:
|
||||
equipment.append("")
|
||||
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):
|
||||
def select_spells(self, spell_list) -> list:
|
||||
spell_book = []
|
||||
for spell_level, count in self.spells.items():
|
||||
if count != "-":
|
||||
@@ -132,10 +155,11 @@ class Adventurer:
|
||||
new_spell = ""
|
||||
while new_spell not in spell_book:
|
||||
new_spell = random.choice(spell_list[spell_level])
|
||||
spell_book.append(new_spell)
|
||||
if new_spell not in spell_book:
|
||||
spell_book.append(new_spell)
|
||||
return spell_book
|
||||
|
||||
def roll_all_hit_points(self):
|
||||
def roll_all_hit_points(self) -> list:
|
||||
hp_list = []
|
||||
prev_hit_dice = 0
|
||||
for i in range(self.__class__.max_level):
|
||||
@@ -151,10 +175,10 @@ class Adventurer:
|
||||
hp_list.append(hp_roll)
|
||||
return hp_list
|
||||
|
||||
def get_class_progression_for_level(self):
|
||||
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):
|
||||
def set_level(self, new_level) -> None:
|
||||
self.level = new_level
|
||||
self.hp = sum(self.hp_rolls[:self.level])
|
||||
if self.player_class in [ "magic user", "elf" ]:
|
||||
@@ -163,8 +187,9 @@ class Adventurer:
|
||||
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):
|
||||
def set_attack_bonus(self) -> int:
|
||||
prog = self.get_class_progression_for_level()
|
||||
thac0 = prog['thac0']
|
||||
atk = 19 - thac0
|
||||
@@ -321,7 +346,7 @@ class Cleric(Adventurer):
|
||||
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.turn_undead = Cleric.turn_undead[self.level]
|
||||
|
||||
class Thief(Adventurer):
|
||||
adv_class = "thief"
|
||||
|
||||
13
app.py
13
app.py
@@ -18,7 +18,7 @@ def compress_character(adventurer):
|
||||
def return_character_from_cookie(cookie):
|
||||
cookie_decompressed = base64.urlsafe_b64decode(cookie)
|
||||
adventurer_dict = json.loads(zlib.decompress(cookie_decompressed).decode())
|
||||
adventurer = createCharacterWithDict(adventurer_dict)
|
||||
adventurer = AdventurerGen.create_from_dict(adventurer_dict)
|
||||
return adventurer
|
||||
|
||||
@app.route('/')
|
||||
@@ -47,7 +47,7 @@ def party():
|
||||
if count < 2 or count > 5:
|
||||
count = 4
|
||||
# generate an adventuring party
|
||||
adv_party = returnParty(count, level)
|
||||
adv_party = PartyGen.get_new_party(count, level)
|
||||
# check for cookies present
|
||||
if request.cookies:
|
||||
stored_count = len(request.cookies)
|
||||
@@ -65,7 +65,7 @@ def party():
|
||||
adv_party.set_party(stored_adv_party, count, level)
|
||||
if count > stored_count:
|
||||
extension = count - stored_count
|
||||
more_party_members = returnParty(extension,level).adventurers
|
||||
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:
|
||||
@@ -103,17 +103,14 @@ def character():
|
||||
# reroll until we get our class
|
||||
while character.player_class != role.replace("-"," "):
|
||||
new_char = Adventurer(c_id, level)
|
||||
selected_class = ClassSelector(new_char).selection()
|
||||
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 = ClassSelector(new_char).selection()
|
||||
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
|
||||
# bug fix for cleric issues
|
||||
if character.player_class == "cleric":
|
||||
character.turn_undead = { int(k) : v for k,v in character.turn_undead.items() }
|
||||
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)
|
||||
|
||||
@@ -20,6 +20,6 @@ weapons = [
|
||||
]
|
||||
|
||||
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)"
|
||||
"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)"
|
||||
]
|
||||
|
||||
77
main.py
77
main.py
@@ -5,7 +5,7 @@ import json
|
||||
import random
|
||||
|
||||
# Player Class Selector
|
||||
class ClassSelector():
|
||||
class AdventurerGen():
|
||||
def __init__(self, player: Adventurer) -> None:
|
||||
self.player = player
|
||||
# https://stackoverflow.com/questions/3862310/how-to-find-all-the-subclasses-of-a-class-given-its-name
|
||||
@@ -16,11 +16,11 @@ class ClassSelector():
|
||||
# run function to randomly select an adventurer class
|
||||
self.selected_class = self.selection()
|
||||
|
||||
def return_class_by_best_attribute(self):
|
||||
def return_class_by_best_attribute(self) -> Adventurer:
|
||||
# for adventurer classes in available classes, return the one where that classes' prime requisite is equal to the players best attribute
|
||||
return [adv_class for adv_class in self.available_classes if adv_class.prime_requisite == self.player.get_best_prime_attribute()][0]
|
||||
|
||||
def return_classes_with_requirements(self):
|
||||
def return_classes_with_requirements(self) -> list:
|
||||
p_attrs = self.player.get_attributes()
|
||||
possible_classes = []
|
||||
for c in self.classes_with_reqs:
|
||||
@@ -32,7 +32,7 @@ class ClassSelector():
|
||||
possible_classes.append(c)
|
||||
return possible_classes
|
||||
|
||||
def selection(self):
|
||||
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() ]
|
||||
@@ -41,7 +41,18 @@ class ClassSelector():
|
||||
selected_class = random.choice(possible_classes)
|
||||
return selected_class
|
||||
|
||||
class PartyGenerator():
|
||||
def create_from_dict(character_dict) -> Adventurer:
|
||||
chosen_class = character_dict['player_class']
|
||||
c_id = character_dict['identifier']
|
||||
level = character_dict['level']
|
||||
adv_dict = Adventurer.get_subclass_dict()
|
||||
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 PartyGen():
|
||||
def __init__(self, party_size: int, party_level: int) -> None:
|
||||
self.size = party_size
|
||||
self.level = party_level
|
||||
@@ -56,7 +67,7 @@ class PartyGenerator():
|
||||
attempts = 0
|
||||
while new_player.player_class not in self.adventurer_types:
|
||||
attempts += 1
|
||||
selected_class = ClassSelector(new_player).selection()
|
||||
selected_class = AdventurerGen(new_player).selection()
|
||||
new_player = selected_class(new_player.identifier, new_player.level, new_player.get_attributes())
|
||||
# i couldnt randomly generate a scenario where a character couldn't be added, but it seems possible, so this is the hard cut off
|
||||
if (new_player.player_class not in self.adventurer_types) or (attempts > 10):
|
||||
@@ -70,6 +81,24 @@ class PartyGenerator():
|
||||
self.adventurers = adventurers
|
||||
self.adventurer_types = []
|
||||
|
||||
def get_new_party(party_size, party_level):
|
||||
# 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):
|
||||
for adv in self.adventurers:
|
||||
if adv.identifier == identifier:
|
||||
return adv
|
||||
|
||||
def get_character_sheets(self):
|
||||
sheet_string = ""
|
||||
character_sheets = []
|
||||
@@ -87,47 +116,15 @@ class PartyGenerator():
|
||||
def __str__(self):
|
||||
return f"{self.adventurers}"
|
||||
|
||||
def returnParty(party_size, party_level):
|
||||
# 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 = PartyGenerator(party_size, party_level)
|
||||
# for adventurers select classes
|
||||
new_party.gen_party()
|
||||
# return the created adventurer party
|
||||
return new_party
|
||||
|
||||
def returnCharacter(identifer):
|
||||
for adv in new_party.adventurers:
|
||||
if adv.identifier == identifier:
|
||||
return adv
|
||||
|
||||
def createCharacterWithDict(character_dict) -> Adventurer:
|
||||
chosen_class = character_dict['player_class']
|
||||
c_id = character_dict['identifier']
|
||||
level = character_dict['level']
|
||||
adv_dict = Adventurer.get_subclass_dict()
|
||||
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
|
||||
|
||||
# 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)
|
||||
print(new_char.adv_class)
|
||||
print(new_char.level)
|
||||
print(new_char.hp_rolls)
|
||||
print(new_char.hp)
|
||||
print(new_char.atk)
|
||||
for k,v in new_char.turn_undead[new_char.level].items():
|
||||
print(k, v)
|
||||
for line in new_char.vertical_sheet():
|
||||
print(line)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
flask
|
||||
flask-session
|
||||
Flask==3.1.3
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
<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[character.level].items() %}
|
||||
{%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>
|
||||
|
||||
Reference in New Issue
Block a user