Editorial Workflows

Dungeons

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: A quick script to generate random dungeons for a basic/expert edition-like D&D game. Largely self-sufficient, but requires data files Monsters.txt and treasure.txt installed in ACKS data folder.

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
Run Python Script ?
Source Code
#coding: utf-8
import workflow, random

dungeon_theme = {1: 'Abandoned mine', 11: 'Natural caverns', 2: 'Barrow mound', 12: 'Prison', 3: 'Catacombs', 13: 'Ruined manor', 4: 'Cliff city', 14: 'Sewers', 5: 'Crumbling castle', 15: 'Sunken city', 6: 'Giant burrow', 16: 'Temple', 7: 'Giant insect hive', 17: 'Tomb', 8: 'Humanoid warren', 18: 'Tower', 9: 'Maze',19: 'Underground river',10: 'Monster lair', 20: "Wizard's dungeon"}

workflow.set_output(str(random.choice(dungeon_theme)))
Set Variable ?
Variable Name
theme
Value
Input
Show HUD ?
HUD Text
Your theme is: theme
Duration
  • 1 Second
  • 2 Seconds
  • 3 Seconds
Icon
  • "Success"
  • "Error"
Request Text Input ?
Title
What is the dungeon's name?
Initial Text
  • Single Line
  • Multiple Lines
Keyboard Options:
Set Variable ?
Variable Name
name
Value
Input
Select from List ?
Title
Number of levels in the Dungeon?
List (Lines)
1 2 3 4 5 6
Multiple Selection
OFF
Show in Popover
OFF
Set Variable ?
Variable Name
levels
Value
Input
Get File Contents ?
File Name
ACKS data/Monsters.txt
In Dropbox
OFF
If File Does Not Exist
  • Empty Output
  • Stop Workflow
Set Variable ?
Variable Name
monster_dict
Value
Input
Get File Contents ?
File Name
ACKS data/treasure.txt
In Dropbox
OFF
If File Does Not Exist
  • Empty Output
  • Stop Workflow
Set Variable ?
Variable Name
treasure
Value
Input
Run Python Script ?
Source Code
#coding: utf-8
import workflow, ast

levels = int(workflow.get_variable('levels'))
theme = workflow.get_variable('theme')
name = workflow.get_variable('name')
monster_dict = ast.literal_eval(workflow.get_variable('monster_dict'))
treasure_dict = ast.literal_eval(workflow.get_variable('treasure'))

# Script to generate random dungeons for ACKS Rome

import random, collections

room_aspects = ['Uneven footing','Difficult to see','Maze-like','Poorly lit','Scattered old bones','Trickling water','Collapsed Corridor','Unnatural Light','Ancient Runes','Flora growing through cracks','Secret Passages','Littered with Traps','Dust-filled','Crumbling Stone','Unending Stairway','Light From Outside','Terrible Odor','Sounds of Underground Lake','Scurrying Creatures','Ancient Murals','Endless Chasm','Walls Closing In','Low Ceiling','Busted Statues','L shaped','Mysterious Well','Celtic Runes','Rotting corpses','Scratchings about the greater darkness','Round','Oblong','Narrow walkways']

room_size = {'Small': [4,4], 'Medium': [10,10], 'Large': [20,20], 'Massive': [40,40]}

traps = {'Arrow Trap': 'When triggered, an arrow fires from a hidden location, attacking one adventurer as a 1st level fighter for 1d6+1 damage.', 'Bricks from Ceiling': "When triggered, bricks fall from the ceiling. Each adventurer in a 10' radius must make a saving throw versus Blast or suffer 2d6 points of damage.", 'Camouflaged Pit Trap': "When triggered, a pit opens beneath the feet of all adventurers in a 10' x 10' square. Adventurers who fall down the pit take 1d6 points of damage per 10' fallen. Pits should generally be 10' deep per dungeon level.", 'Poison Dart Trap': 'When triggered, a dart fires from a hidden location, attacking one adventurer as a 1st level fighter for 1d4+1 damage. If the adventurer is hit, he must save versus Poison or die.', 'Poison Needle Trap': "When triggered, a small needle pops out of a lock. The adventurer who triggered the trap must save versus Poison or die.", 'Portcullis Trap': "When triggered, a portcullis falls suddenly downward. The adventurer who triggered the trap must make a save versus Blast or suffer 3d6 points of damage. The way will then be blocked, and party members may be separated.", 'Rolling Rock Trap': "When triggered, a rock rolls out from a hidden location. All adventurers in the room or hallway must save versus Blast or suffer 2d6 points of damage."}

unique_enc = {1: ['Rot grub', 'spitting cobra', 'skeleton','runes','statue'], 2: ['Green slime', 'wood golem', 'yellow mold','cursed item','ghost or spirit'], 3: ['ghost or spirit','Gelatinous cube', 'gray ooze', 'animated statue (iron, crystal)', 'doppelganger', 'shadow'], 4: ['Animated statue (stone)', 'ochre jelly', 'rust monster'], 5: ['Golem (bone)', 'invisible stalker', 'mummy'], 6: ['Golem (amber, bronze)', 'black pudding', 'djinni', 'efreeti', 'elemental','bound demon/devil']} 

unprot_treasure = {1: ['A', 'B', 'C', 'D', 'E', 'F'], 2: ['C', 'D', 'E', 'F', 'G', 'H'], 3: ['E', 'F', 'G', 'H', 'I', 'J'], 4: ['G', 'H', 'I', 'J', 'K', 'L'], 5: ['I', 'J', 'K', 'L', 'M', 'N'], 6: ['M', 'N', 'O', 'P', 'Q', 'R']}

wandering_m = {1: [['Beetle, Giant Fire',1,8,0],['Centipede, Giant',2,4,0],['Ferret, Giant',1,8,0],['Goblin',2,4,0], ['Kobold',4,4,0], ['Brigand',2,4,0],['Goblin',1,12,0], ['NPC Party',1,4,2],['Orc',2,4,0], ['Rat, Giant',3,6,0],['Skeleton',3,4,0],['Stirge',1,10,0]], 
               2: [['Bat, Giant',1,10,0],['Fly, Giant Carnivorous',1,8,0],['Ghoul',1,6,0],['Gnoll',1,6,0],['Hobgoblin',1,6,0],['Lizardman',2,4,0],['Insect Swarm, 2HD',1,10,0], ['Berserker',1,6,0],['NPC Party',1,4,2],['Snake, Pit Viper',1,8,0],['Troglodyte',1,8,0],['Zombie',2,4,0]], 
               3: [['Ant, Giant',2,4,0],['Bugbear',2,4,0],['Carcass Scavenger',1,3,0],['Gargoyle',1,6,0],['Lizard, Giant Draco',1,3,0],['Werewolf',1,6,0],['NPC Party',1,4,2],['Ogre',1,6,0], 	['Scorpion, Giant',1,6,0],['Throghrin',1,6,0],['Wight',1,6,0],['Wolf, Dire',1,4,0]], 
               4: [['Boar, Giant',1,4,0],['Cockatrice',1,3,0],['Wereboar',1,4,0],['Weretiger',1,4,0],['Medusa',1,3,0],['Minotaur',1,6,0],['NPC Party',1,4,2],['Owl Bear',1,4,0],['Phase Tiger',1,4,0],['Rhagodessa, Giant',1,4,0],['Snake, Giant Python',1,3,0],['Wraith',1,4,0]], 
               5: [['Ankheg',1,6,0],['Basilisk',1,6,0],['Caecilian',1,3,0],['Ettin',1,2,0],['Giant, Hill',1,4,0],['Giant, Stone',1,2,0],['Hell Hound, Greater',2,4,0],['NPC Party',1,4,3],['Salamander, Flame',1,4,1],['Spectre',1,4,0],['Troll',1,8,0],['Wyvern',1,2,0]], 
               6: [['Cyclops',1,1,0],['Demon Boar',1,4,0],['Dragon, 20HD',1,1,0],['Giant, Cloud',1,2,0],['Gorgon',1,2,0],['Hydra, 12HD',1,1,0],['Lamia',1,1,0],['NPC Party',1,4,3],['Purple Worm',1,2,0],['Remorhaz, 15HD',1,1,0],['Skittering Maw',1,1,0],['Vampire, 9HD',1,4,0]]} 

wm_level = {1:[0,0,10,11,13],2:[0,4,10,12,13],3:[2,4,10,12,13],4:[2,4,10,12,13],5:[2,4,10,13,14],6:[2,4,13,14,15]}

treasure_list = ['Copper',"Silver",'Electrum','Gold','Platinum']
gem_list = ['Gem','Jewelry']
gem_value = {'ornamental': 50, 'gems': 200, 'brilliants':4000, 'trinkets': 225, 'jewelry': 1000, 'regalia': 11000}
coin_value = {'Copper': .01, 'Silver': .1, 'Electrum': .5, 'Gold': 1, 'Platinum': 5}

def roll_em(dice,pips,mod):
	total = 0
	for i in range(1,dice+1):
		total += random.randint(1,pips)
	total += mod
	return total

def dungeon_gen(levels):
	
	room_list = {}
	dungeon_dict = {}
	for level in range(1,levels+1):
		dungeon_dict['level '+str(level)] = {}
		rooms = roll_em(2,10,0)
		for room in range(1,rooms+1):
			dungeon_dict['level '+str(level)][room] = []
			x = roll_em(1,100,0)
			
			# **** empty room ****
			if x < 30:
				if roll_em(1,100,0) < 15:
					treasure = random.choice(unprot_treasure[level])
					treasure_hoard = generate_treasure(str(treasure))
				else:
					treasure = 'None'
					treasure_hoard = ''
				dungeon_dict['level '+str(level)][room] = '{}, Empty, {}, Treasure: {} <br> Notes: {}'.format(str(random.choice(room_size.keys())).title(),str(random.choice(room_aspects)),treasure, treasure_hoard)
				
			# **** monster room ****
			
			elif x < 60:
				wm_mod = roll_em(1,12,0)
				mod_level = 0
				if wm_mod < wm_level[level][0]:
					mod_level = level -2
				elif wm_mod < wm_level[level][1]:
					mod_level = level -1
				elif wm_mod < wm_level[level][2]:
					mod_level = level
				elif wm_mod < wm_level[level][3]:
					mod_level = level +1
				elif wm_mod < wm_level[level][4]:
					mod_level = level +2
					
				if mod_level < 1:
					mod_level = 1
					
				rm_monster = random.choice(wandering_m[mod_level])
				monster_name = rm_monster[0]
				monster_die = int(rm_monster[1])
				monster_pips = int(rm_monster[2])
				monster_mod = int(rm_monster[3])
				
				if roll_em(1,100,0) < 15:
					treasure = 'by monster'
				else:
					treasure = 'None'
				
				dungeon_dict['level '+str(level)][room] = '{}, Monster: {}, {}, Treasure: {} <br> Notes: {}'.format(str(random.choice(room_size.keys())).title(),rm_monster[0],str(random.choice(room_aspects)),treasure,str(encounter(rm_monster[0],rm_monster[1],rm_monster[2],rm_monster[3]).translate(None,'"').lstrip(',')))
			# **** Trap Room ****
					
			elif x < 75:
				if roll_em(1,100,0) < 15:
					treasure = random.choice(unprot_treasure[level])
					treasure_hoard = generate_treasure(str(treasure))
				else:
					treasure = 'None'
					treasure_hoard = ''
				trap = random.choice(traps.keys())
				
				dungeon_dict['level '+str(level)][room] = '{}, Trap: {}, {}, Treasure: {} <br> Notes: {}<br> {}'.format(str(random.choice(room_size.keys())).title(),trap,str(random.choice(room_aspects)),treasure,traps[trap],treasure_hoard)
				
			# **** Unique Room ****
					
			else:
				if roll_em(1,100,0) < 15:
					treasure = random.choice(unprot_treasure[level])
					treasure_hoard = generate_treasure(str(treasure))
				else:
					treasure = 'None'
					treasure_hoard = ''
				unique = random.choice(unique_enc[level])
				
				dungeon_dict['level '+str(level)][room] = '{}, Unique: {}, {}, Treasure: {} <br> Notes: This room has a puzzle, mystery or something special about it. <br>{} '.format(str(random.choice(room_size.keys())).title(),unique,str(random.choice(room_aspects)),treasure,treasure_hoard)
					
	dun_string = '**{}** - {} - {} Level \n \n'.format(name.title(),theme.title(),levels)

	for level in collections.OrderedDict(sorted(dungeon_dict.items(), key=lambda t: t[0])):
		dun_string += '**{}**: \n'.format(level).title()
		for k,v in dungeon_dict[level].items():
			dun_string += '* Room {}: {}\n'.format(k,str(v).strip('[]').translate(None,"'"))
		dun_string += ' <br> <br>'
	#print dungeon_dict
	return dun_string
	
def encounter(monster,dice,pips,mod):
		try:
			mon_dict = monster_dict[monster.title()]
		except KeyError:
			mon_dict = monster_dict['Goblin']
			print monster

		# Determining the number of creatures encountered, the distance (as per a wooded encounter) and surprise.
		tot_enc = 0
		if int(pips) == 1:
			tot_enc = 1
			enc1 = "Encounter: {} {}.".format(tot_enc,monster)
		else:
			tot_enc = roll_em(int(dice),int(pips),mod)
			
			enc1 = "Encounter: {} {}.".format(tot_enc,mon_dict['Natural Spelling Plural'])

		# Determining hit dice and hit points for creatures (NOTE: I screwed up some low hp creatures in the CSV file.  Will need to fix.)
		hd = 1
		hp = []
		x = 0
		
		try:
			int(mon_dict['HD'])
		except ValueError:
			raise
			mon_dict['HD'] = 1
			for i in range(1,100):
				print '********************'+mon_dict[""]+'**********************'
		
		num_beasties = 0
		if tot_enc > 26:
			num_beasties = 26
		else:
			num_beasties = tot_enc

		if int(mon_dict['Pips']) == 0:
			hd = str(mon_dict['HD'])
			for beast in range(1,num_beasties+1):
				x = roll_em(int(mon_dict['HD']),8,0)
				hp.append(x)

		elif mon_dict['HD'] in (0,'',""):
			hd = str(mon_dict['Pips'])+'hp'
			for beast in range(1,num_beasties+1):
				x = mon_dict['Pips']
				hp.append(x)
		else:
			hd = str(mon_dict['HD'])+'+'+str(mon_dict['Pips'])
			for beast in range(1,num_beasties+1):
				x = roll_em(int(mon_dict['HD']),8,int(mon_dict['Pips']))
				hp.append(x)

		if mon_dict['2nd Move Type'] == '':
			two_move = 'None'
		else:
			two_move = mon_dict['2nd Move Type']+" "+mon_dict['2nd Move Spd']

		# Final part of the stat block and returning the full script to the map.
		enc3 = '>**Al**: {}, **Move**: {}, **Alt**: {}, **AC**: {}, **HD**: {}<br>**HP**: {}<br>**Atk**: {}, **Dam**: {}<br>**Save**: {}, **Morale**: {}, **Treasure**: {}, **XP**: {} (total {}xp)<br> **Special**: {}.'.format(mon_dict['Align'],mon_dict['Move'],two_move,str(mon_dict['AC']),str(hd),str(hp).strip('[]'),mon_dict['Attacks'],mon_dict['Damage'],mon_dict['Save'],str(mon_dict['Morale']),str(mon_dict['Treasure Type']),str(mon_dict['XP']),int(mon_dict['XP'])*num_beasties,mon_dict['Special Attack'])
		full_text = enc1+'\n'+enc3
		#print(full_text)
		#print('')
		return full_text

def generate_treasure(t_type):
	
	total = 0
	treasure = ''
	treasure = '**Treasure Type {}**<br> '.format(t_type)
	for item in treasure_list:
		amount = 0
		prob = int(treasure_dict[t_type][item])
		x = roll_em(1,100,0)
		if x <= prob:
			amount = roll_em(int(treasure_dict[t_type][item+'_amount'][1]),int(treasure_dict[t_type][item+'_amount'][3]),0)
			amount *= 1000
			treasure += '* {}: {} coins <br> '.format(item,amount)
			total += (amount * float(coin_value[item]))
			
	for item in gem_list:
		amount = 0
		prob = int(treasure_dict[t_type][item])
		x = roll_em(1,100,0)
		if x <= prob:
			amount = int(roll_em(int(treasure_dict[t_type][item+'_amount'][1]),int(treasure_dict[t_type][item+'_amount'][3]),int(treasure_dict[t_type][item+'_amount'][5])))
			treasure += '* {}: {} {} (value {} each) <br> '.format(item,amount,treasure_dict[t_type][item+'_type'],gem_value[treasure_dict[t_type][item+'_type']])
			total += (amount * gem_value[treasure_dict[t_type][item+'_type']])
	
	treasure += '* Magic {} <br> '.format(treasure_dict[t_type]['Magic '])
	treasure += 'The total value of this hoard is {}gp <br>'.format(int(total))
	
	return treasure

workflow.set_output(dungeon_gen(levels))
Replace Selected Text ?
Replacement Text
Input