Editorial Workflows

NPC

public workflow

Install Workflow...

This workflow contains at least one Python script. Only use it if you trust the person who shared this with you, and if you know exactly what it does.

I understand, install the workflow!

This is a workflow for Editorial, a Markdown and plain text editor for iOS. To download it, you need to view this page on a device that has the app installed.

Description: Randomly generates an NPC of a selected level and class for Adventurer Conqueror King (basically any OSR D&D clone. Requires aspects and traits data files in a directory called ACKS data. Dropbox link for data files: https://www.dropbox.com/sh/ghopk6jhloiqs5y/rV7ggkYpdr

Shared by: Chris Allison

Comments: Comment Feed (RSS)

There are no comments yet.

+ Add Comment

Workflow Preview
Request Text Input ?
Title
NPC name?
Initial Text
  • Single Line
  • Multiple Lines
Keyboard Options:
Set Variable ?
Variable Name
name
Value
Input
Choose Class ?
Title
Choose a class:
List (Lines)
Fighter Cleric Thief Mage Assassin Barbarian Bard Explorer Shaman Elven Spellsword Elven Nightblade Dwarven Vaultguard Dwarven Craftpriest Witch Warlock Specialist Normal Man Man at Arms
Multiple Selection
OFF
Show in Popover
OFF
Change Case ?
  • UPPER CASE
  • lower case
  • Title Case
Set Variable ?
Variable Name
profession
Value
Input
Request Text Input ?
Title
NPC level
Initial Text
  • Single Line
  • Multiple Lines
Keyboard Options:
Set Variable ?
Variable Name
level
Value
Input
Get File Contents ?
File Name
ACKS data/traits.txt
In Dropbox
OFF
If File Does Not Exist
  • Empty Output
  • Stop Workflow
Set Variable ?
Variable Name
traits
Value
Input
Get File Contents ?
File Name
ACKS data/class_proficiencies.txt
In Dropbox
OFF
If File Does Not Exist
  • Empty Output
  • Stop Workflow
Set Variable ?
Variable Name
class_profs
Value
Input
Get File Contents ?
File Name
ACKS data/aspect_list.txt
In Dropbox
OFF
If File Does Not Exist
  • Empty Output
  • Stop Workflow
Set Variable ?
Variable Name
aspects
Value
Input
Get File Contents ?
File Name
ACKS data/spells.txt
In Dropbox
OFF
If File Does Not Exist
  • Empty Output
  • Stop Workflow
Set Variable ?
Variable Name
spells
Value
Input
Select from List ?
Title
rank
List (Lines)
Minor Character Adventurer Major Character
Multiple Selection
OFF
Show in Popover
OFF
Set Variable ?
Variable Name
rank
Value
Input
Run Python Script ?
Source Code
#coding: utf-8
import workflow, random, fractions, pickle, clipboard, editor, ast

# Get the custom parameters as a dictionary (titles are keys):
params = workflow.get_parameters()
# Get the action's input (a string):
action_in = workflow.get_input()

name = workflow.get_variable('name')
level = workflow.get_variable('level')
profession = workflow.get_variable('profession')
trait_list = []
trait_list = ast.literal_eval(workflow.get_variable('traits'))
class_profs = {}
class_profs = ast.literal_eval(workflow.get_variable('class_profs'))
aspects = []
aspects = ast.literal_eval(workflow.get_variable('aspects'))
rank = workflow.get_variable('rank')
slist = {}
slist = ast.literal_eval(workflow.get_variable('spells'))

statlist = ['strength','intelligence','wisdom','dexterity','constitution','charisma']
	
def roll_em(dice,pips,mod):
	total = 0
	for i in range(1,dice+1):
		total += random.randint(1,pips)
	total += mod
	return total

attrib_mods = {3 : -3,4 : -2,5 :  -2,6 : -1,7 : -1,8 : -1,9 : 0,10 : 0,11 : 0,12 : 0,13 : 1,14 : 1,15 : 1,16 : 2,17 : 2,18 : 3}

fighter_db = [0,1,1,2,2,2,3,3,3,4,4,4,5,5,5]

spellsbylevel=[\
  [0, 0, 0, 0, 0, 0],\
  [1, 0, 0, 0, 0, 0],\
  [2, 0, 0, 0, 0, 0],\
  [2, 1, 0, 0, 0, 0],\
  [2, 2, 0, 0, 0, 0],\
  [2, 2, 1, 0, 0, 0],\
  [2, 2, 2, 0, 0, 0],\
  [3, 2, 2, 1, 0, 0],\
  [3, 3, 2, 2, 0, 0],\
  [3, 3, 3, 2, 1, 0],\
  [3, 3, 3, 3, 2, 0],\
  [4, 3, 3, 3, 2, 1],\
  [4, 4, 3, 3, 3, 2],\
  [4, 4, 4, 3, 3, 2],\
  [4, 4, 4, 4, 3, 3]]
  
divinespellsbylevel = [[0,0,0,0,0],[1,0,0,0,0],[2,0,0,0,0],[2,1,0,0,0],[2,2,0,0,0],[2,2,1,1,0],[2,2,2,1,1],[3,3,2,2,1],[3,3,3,2,2],[4,4,3,3,2],[4,4,4,3,3],[5,5,4,4,3],[5,5,5,4,3],[6,5,5,5,4]]

witchspellsbylevel = [[0,0,0,0,0],[1,0,0,0,0],[2,0,0,0,0],[3,0,0,0,0],[3,2,0,0,0],[3,3,0,0,0],[3,3,2,2,0],[3, 3, 3, 2, 2], [5, 5, 3, 3, 2], [5, 5, 5, 3, 3], [6, 6, 5, 5, 3], [6, 6, 6, 5, 5], [8, 8, 6, 6, 5], [8, 8, 8, 6, 5], [9, 8, 8, 8, 6]]

thief_skills = [[18,18,17,17,6,19,14,2],[17,17,16,16,5,18,13,2],[16,16,15,15,5,17,12,2],[15,15,14,14,4,16,11,2],[14,14,13,13,4,15,10,3],[12,13,12,12,4,14,9,3],[10,11,10,10,3,12,8,3],[8,9,8,8,3,10,7,3],[6,7,6,6,3,8,6,4],[4,5,4,4,3,6,5,4], [3,3,2,2,2,4,4,4], [2,2,-1,2,2,3,3,4], [1,2,-3,1,1,2,2,5], [1,1,-5,1,1,1,1,5]]

weapon_list = {
    'dagger' : (4,False,3,0,0,'none',20),
    'dart' : (4,False,3,0,0,'none',20),
    'whip' : (4,False,3,0,0,'none',0),
    'club' : (6,False,1,0,0,'none',0),
    'short sword' :(6,False,6,0,0,'none',0),
    'mace' : (6,False,5,0,0,'none',0),
    'hand axe' : (6,False,5,0,0,'none',0),
    'morningstar' : (6,False,8,0,0,'none',0),
    'longsword' : (8,False,15,0,0,'none',0),
    'spear' : (8,False,10,0,0,'none',0),
    'great sword' : (10,True,30,0,0,'none',0),
    'great axe' : (10,True,10,0,0,'none',0),
    'battle axe' : (6,False,5,0,0,'none',0),
    'warhammer' : (6,True,5,0,0,'none',0),
    'javelin' : (6,True,3,0,0,'none',20),
    'pole arm' : (10,True,7,0,0,'none',0),
    'staff' : (6,True,5,0,0,'none',0),
    'long bow' : (6,True,30,0,0,'none',125),
		'bow' : (6,True,30,0,0,'none',125),
    'short bow' : (6,True,30,0,0,'none',125),
    'crossbow' : (6,True,30,0,0,'none',125)}

armour_list = {
    'clothing' : (0,10),
    'hide and fur' : (1,10),
    'ring mail' : (3,30),
    'banded plate' : (5,50),
    'leather armour' : (2,20),
    'chain mail' : (4,40),
    'plate armour' : (6,60)}

magic_types = ['Potions','Rings','Scrolls', "Rods, Staffs and Wands","Miscellaneous Magic", 'Swords','Miscellaneous Weapon','Armor']

professions = {
    'specialist':[4,0,1000,['clothing','leather armour'],['dagger'],False,'',{1:['x4 Proficiencies']}],
    'normal man':[4,0,1000,['clothing','leather armour'],['dagger'],False,'',{1:['None']}],
    'man at arms':[6,0,1000,['clothing','leather armour','hide and fur','ring mail'],['dagger','short sword','mace','spear','club'],True,'',{1:['None']}],
    'fighter':[8,fractions.Fraction(2,3),2000,['leather armour','hide and fur','ring mail','chain mail','plate armour','banded plate'],['club','morningstar','dagger','short sword','mace','longsword','short bow','long bow','spear','great sword','battle axe', 'great axe','pole arm'],True,'',{1:['Battlefield Prowess','Damage Bonus'],9:['Leader of Men']}],
    'barbarian':[8,fractions.Fraction(2,3),2600,['leather armour','hide and fur','ring mail','chain mail','plate armour'],['club','morningstar','dagger','short sword','mace','longsword','spear','great sword'],True,'',{1:['Animal Reflexes','Stealthy','Savage Resilience'],5:['Animal Magnetism']}],
    'cleric':[6,fractions.Fraction(2,4),1500,['leather armour','hide and fur','chain mail','ring mail','plate armour','banded plate'],['club','mace','morningstar'],True,'cleric',{1:['Turn Undead'],2:['Divine Spells ({})'.format(divinespellsbylevel[int(level)]).strip('[],')],5:['research spells, scribe scrolls, and brew potions'],9:['craft magic','fortified church'],11:['ritual magic','greater crafting']}],
    'shaman' : [6,fractions.Fraction(2,4),1500,['clothing','hide and fur','leather armour'],['club','dagger','hand axe','short sword','spear'],True,'shaman',{1:['Commune','Totem'],2:['Divine Spells ({})'.format(divinespellsbylevel[int(level)]).strip('[],')],3:['Sp. Ritual'],5:['Shapechange'],7:['Spiritwalk'],9:['Medicine Lodge']}],
    'thief':[4,fractions.Fraction(2,4),1250,['hide and fur','leather armour'],['dagger','short sword'],False,'',{1:['Open Locks {}+, Find and Remove Traps {}+, Pick Pockets {}+, Move Silently {}+, Climb Walls {}+, Hide in Shadows {}+, Hear Noise {}+, Backstab x{}'.format(thief_skills[int(level)-1][0],thief_skills[int(level)-1][1],thief_skills[int(level)-1][2],thief_skills[int(level)-1][3],thief_skills[int(level)-1][4],thief_skills[int(level)-1][5],thief_skills[int(level)-1][6],thief_skills[int(level)-1][7])],9:['hideout']}],
    'mage':[4,fractions.Fraction(2,6),2500,['clothing'],['dagger','staff','dart'],False,'wizard',{1:['Arcane Spells ({})'.format(spellsbylevel[int(level)]).strip('[],'),"Spellbook"],5:['research spells, scribe scrolls, and brew potions'],9:['craft magic','sanctum'],11:['ritual magic','greater crafting']}],
    'elven spellsword':[6,fractions.Fraction(2,3),1750,['leather armour','hide and fur','ring mail','chain mail','plate armour','banded plate'],['club','morningstar','dagger','short sword','mace','longsword','short bow','long bow','spear','great sword','battle axe', 'great axe','pole arm'],True,'wizard',{1:['Arcane Spells ({})'.format(spellsbylevel[int(level)]).strip('[],'),'Attuned to Nature','Keen Eyes (8+/14+)','Connection to Nature'],5:['research spells, scribe scrolls, and brew potions'],9:['craft magic','fastness']}],
    'dwarven vaultguard':[8,fractions.Fraction(2,3),2200,['leather armour','ring mail','chain mail','plate armour','banded plate'],['club','dagger','short sword','mace','morningstar','longsword','spear','great sword'],True,'',{1:['Sensitivity to Rock and Stone','Detect Traps','Hardy People']}],
    'assassin':[6,fractions.Fraction(2,3),1700,['leather armour'],['dagger','short sword'],True,'',{1:['Move Silently {}+, Hide in Shadows {}+, Backstab {}x'.format(thief_skills[int(level)-1][3],thief_skills[int(level)-1][5],thief_skills[int(level)-1][7])]}],
    'explorer':[6,fractions.Fraction(2,3),2000,['leather armour','ring mail','chain mail'],['dagger','short sword','longsword','short bow','long bow','crossbow'],True,'',{1:['Accuracy Bonus +1','Animal Reflexes','Difficult to Spot 3+/14+'],5:['Experience and Hardiness'],9:['Border Fort']}],
    'bard':[6,fractions.Fraction(2,4),1400,['leather armour'],['dagger','short sword','mace','hand axe','longsword','short bow','long bow','crossbow'],True,'',{1:['Arcane Dabbling {}+, Loremastery {}+'.format(20-int(level*2),19-int(level)),'Inspire Courage'],5:['Chronicles of Battle'],9:['hall'],10:['read and cast magic from arcane scrolls']}],
    'dwarven craftpriest':[6,fractions.Fraction(2,4),2400,['leather armour','ring mail','chain mail','plate armour','banded plate'],['hand axe','battle axe','great axe','flail','mace','morningstar','warhammer'],True,'cleric',{1:['Turn Undead','Journeyman','Attention to Detail','Sensitivity to Rock and Stone','Detect Traps','Hardy People'],2:['Divine Spells ({})'.format(divinespellsbylevel[int(level)]).strip('[],')],5:['research spells, scribe scrolls, and brew potions'],9:['craft magic','vault']}],
    'elven nightblade':[6,fractions.Fraction(2,4),2775,['leather armour'],['dagger','short sword','bow','crossbow'],True,'wizard',{1:['Move Silently {}+, Hide in Shadows {}+, Climb Walls {}+, Backstab {}x, Acrobatics {}+'.format(thief_skills[int(level)-1][3],thief_skills[int(level)-1][5],thief_skills[int(level)-1][4],thief_skills[int(level)-1][7],21-int(level)),'Attuned to Nature','Keen Eyes (8+/14+)','Connection to Nature'],2:['Arcane Spells ({})'.format(spellsbylevel[int(level)/2]).strip('[],')],9:['Hideout'],10:['research spells, scribe magical scrolls, and brew potions']}],
    'warlock': [4,fractions.Fraction(2,6),2075,['clothing'],['dagger','dart','whip','staff'],False,'wizard',{1:['Arcane Spells ({})'.format(spellsbylevel[int(round(int(level)*.66))]).strip('[],'),'Familiar'],2:['Control Undead','Dark Arts'],4:['Curses'],6:['Contact Dark Powers'],7:['Magical Research'],8:['Alter Shape'],9:['Establish Coterie'],10:['Necromancy','Ritual Magic'],11:['Forbidden Spells']}],
    'witch':[4,fractions.Fraction(2,6),2000,['clothing'],['dagger','staff','club','dart'],False,'cleric',{1:['Divine Spells ({})'.format(witchspellsbylevel[int(level)]).strip('[],'),'Tradition'],3:['Brew Potion'],5:['Research Magic'],7:['Scribe Scrolls'],9:['Craft Magic','Coven'],11:['Ritual Magic']}]}

motivation = ['Association','Goal','Glory','Greed','Knowledge','Political-Revolutionary/Patriot','Psychopath','Redemption','Responsibility','Teacher/Mentor','Thrillseeker','Upholding the Good','Unwanted Involvement','Vengeance','Wanderer']

gen_prof = ['Alchemy','Animal Husbandry','Animal Training','Art','Bargaining','Caving','Collegiate Wizardry','Craft','Diplomacy','Disguise','Endurance','Engineering','Gambling','Healing','Intimidation','Knowledge','Labor','Language','Leadership','Lip Reading','Manual of Arms','Mapping','Military Strategy','Mimicry','Naturalism','Navigation','Performance','Profession','Riding','Seafaring','Seduction','Siege Engineering','Signaling','Survival','Theology','Tracking','Trapping']

def genSpells(level, intel, excess):
	out = []
	for i in range(0,6):
		tmp = []
		numspells = spellsbylevel[level][i]
		if numspells > 0:
			numspells += intel
			if excess:
				numspells += random.randint(0,2)
		for n in range(1,numspells+1):
			x = random.choice(slist[i+1])
			tmp.append(x)
		if tmp != []:
			out.append(tmp)
	return out

def printSpells(known):
	out = ''
	for j in range(0,len(known)):
		out += "{}: {}; ".format(str(j+1),', '.join(known[j]))
	return out

def pc_gen(name,level,profession):

	pc = {}
	pc_text = []
	
	pc['name'] = name
	pc['level'] = int(level)
	pc['profession'] = profession
	
	pc['alignment'] = random.choice(['Lawful','Lawful','Neutral','Neutral','Neutral','Chaotic'])

	if rank == 'Minor Character':
		rank_min = 55
	elif rank == 'Adventurer':
		rank_min = 65
	elif rank == 'Major Character':
		rank_min = 75

	total = 0
	while total < rank_min:
		total = 0
		for stat in statlist:
			pc[stat] = roll_em(3,6,0)
			total += pc[stat]
	
	if pc['profession'] in ['fighter','barbarian','dwarven vaultguard']:
		if pc['strength'] < 12:
			pc['strength'] = 12
	elif pc['profession'] in ['thief','assassin','explorer','elven spellsword','elven nightblade']:
		if pc['dexterity'] < 12:
			pc['dexterity'] = 12
	elif pc['profession'] in ['bard','shaman','elven spellsword']:
		if pc['charisma'] < 12:
			pc['charisma'] = 12
	elif pc['profession'] in ['explorer','barbarian','dwarven vaultguard','explorer','fighter','dwarven craftpriest']:
		if pc['constitution'] < 12:
			pc['constitution'] = 12
	elif pc['profession'] in ['shaman','cleric','dwarven craftpriest']:
		if pc['wisdom'] < 12:
			pc['wisdom'] = 12
	elif pc['profession'] in ['mage','witch','warlock','elven spellsword','elven nightblade','bard']:
		if pc['intelligence'] < 12:
			pc['intelligence'] = 12
			
	hp = 0
	hp = roll_em(pc['level'],professions[pc['profession']][0],int(attrib_mods.get(pc['constitution'])))
		
	pc['hp'] = hp
	pc['armour'] = str(random.choice(professions[pc['profession']][3])).rstrip(',')
	pc['weapon'] = str(random.choice(professions[pc['profession']][4])).rstrip(',')
	if professions[pc['profession']][5] == True and weapon_list[pc['weapon']][1] == False:
		pc['shield'] = 1
		pc['full_armour'] = pc['armour'] + ' & shield'
	else: 
		pc['shield'] = 0
		pc['full_armour'] = pc['armour']

	pc['atk_throw'] = 10 - int(attrib_mods.get(pc['strength']) + (int(pc['level']) * professions[pc['profession']][1]))
		
	pc['motivation'] = random.choice(motivation)
	t1 = random.choice(trait_list)
	t2 = random.choice(trait_list)
	pc['traits'] = str(t1+', '+t2)
	a1 = random.choice(aspects)
	a2 = random.choice(aspects)
	pc['aspects'] = str(a1+', '+a2)
		
	special = ""
	for lvl in range(1,pc['level']+1):
		try:
			special += str(professions[pc['profession']][7][lvl])+', '
		except KeyError:
			pass 
	pc['special'] = special
	
	pc['char_profs'] = []
		
	gen_profs = 1
	if pc['level'] < 5:
		gen_profs = 1
	elif pc['level'] < 9:
		gen_profs = 2
	elif pc['level'] > 13:
		gen_profs = 3
	else:
		gen_profs = 4
	gen_profs += attrib_mods.get(int(pc['intelligence']))
		
	# Chooses general proficiencies
	if pc['profession'] == 'specialist':
		for i in range (1,gen_profs+6):
			pc['char_profs'].append(random.choice(gen_prof))
	else:
		for i in range (1,gen_profs+2):
			pc['char_profs'].append(random.choice(gen_prof))

	# Chooses class proficiencies
	if pc['profession'] in ['specialist','normal man','man at arms']:
		pass
	else:
		for i in range(1,int(pc['level'] * professions[pc['profession']][1])+2):
			pc['char_profs'].append(random.choice(class_profs[pc['profession'].title()]))
	
	# Sets damage bonus for class abilities
	db = 0
	if pc['profession'] in ("fighter","assassin","barbarian","explorer","dwarven vaultguard"):
		db = '+{}'.format(fighter_db[pc['level']]+attrib_mods.get(pc['strength']))
	elif attrib_mods.get(pc['strength']) == 0:
		db = ''
	elif attrib_mods.get(pc['strength']) < 0:
		db = '{}'.format(attrib_mods.get(pc['strength']))
	else:
		db = '+{}'.format(attrib_mods.get(pc['strength']))
		
	# Generates magic item types for NPCs based on level
	pc['magic'] = ''
	m_list = []
	for item in magic_types:
		x = roll_em(1,100,0)
		if x <= int(pc['level'])*5:
			m_list.append(item)
	if m_list == []:
		pc['magic'] = "None"
	else:
		pc['magic'] = str(m_list).translate(None,"'").strip('[],')
	
	# Generates spell repertoire for arcane magic users
	pc['spells'] = ''
	pc['spelltype'] = professions[pc['profession']][6]
	spells = []
	
	if pc['spelltype'] == 'wizard':
		spells = genSpells(int(pc['level']),attrib_mods.get(int(pc['intelligence'])),0)
		pc['spells'] = '**Spells**: '+str(printSpells(spells))
	
	# Sets the text output
	full_text = '**{} - Level {} {}** ({})<br>\n**Alignment**: {}, **Motivation**: {}<br>\n**Aspects**: {}<br>\n**AC**: {} ({}), **HP**: {}, **Atk**: {} {}+ 1d{}{} <br>\n**Str**: {} **Dex**: {} **Con**: {} **Int**: {} **Wis**: {} **Cha**: {}<br>\n**Spec**: {}<br>\n**Profs**: {}<br>\n**Magic**: {}<br>\n{}'.format(pc['name'],pc['level'],pc['profession'].title(),pc['traits'],pc['alignment'],pc['motivation'],pc['aspects'],armour_list[pc['armour']][0]+attrib_mods.get(int(pc['dexterity']))+pc['shield'],pc['full_armour'],pc['hp'],pc['weapon'],pc['atk_throw'],str(weapon_list[pc['weapon']][0]),db,pc['strength'],pc['dexterity'],pc['constitution'],pc['intelligence'],pc['wisdom'],pc['charisma'],str(pc['special']).translate(None,"['']").rstrip(','),str(pc['char_profs']).rstrip(',').translate(None,"['']"),pc['magic'],pc['spells'])
	
	return full_text
   
workflow.set_output(pc_gen(name,level,profession))
Set Variable ?
Variable Name
char_text
Value
Input
Replace Selected Text ?
Replacement Text
char_text