New OOTS products from CafePress
New OOTS t-shirts, ornaments, mugs, bags, and more
Results 1 to 15 of 15

Thread: NPC generator

  1. - Top - End - #1
    Halfling in the Playground
    Join Date
    Oct 2008

    Default NPC generator

    I wrote a tool in python for my dm that will randomly generate npcs in bulk according to the tables in the dmg. It doesn't give a statblock, but it shows alignment, race, class, and attributes. The tables it uses to generate class and race can be extended with your own classes and races with minimal work, and it supports a variety to attribute rolling methods.

    I have 2 questions about this.
    1. Would there be any interest in me posting this?
    2. Would there be any issues due to the fact that it copies the tables from the dmg?

  2. - Top - End - #2
    Colossus in the Playground
     
    Flickerdart's Avatar

    Join Date
    Mar 2008
    Location
    NYC
    Gender
    Male

    Default Re: NPC generator

    I don't think the sample NPCs are covered under the OGL.
    Quote Originally Posted by Inevitability View Post
    Greater
    \ˈgrā-tər \
    comparative adjective
    1. Describing basically the exact same monster but with twice the RHD.
    Quote Originally Posted by Artanis View Post
    I'm going to be honest, "the Welsh became a Great Power and conquered Germany" is almost exactly the opposite of the explanation I was expecting

  3. - Top - End - #3
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    Does that mean the sample statblocks starting on page 113 of the dmg? This program only uses the tables starting on 110.

  4. - Top - End - #4
    Colossus in the Playground
     
    Flickerdart's Avatar

    Join Date
    Mar 2008
    Location
    NYC
    Gender
    Male

    Default Re: NPC generator

    If it's not in the SRD, I don't think you're allowed to reproduce it. I mean, a lot of sites do anyway, but that doesn't mean they are allowed to.
    Quote Originally Posted by Inevitability View Post
    Greater
    \ˈgrā-tər \
    comparative adjective
    1. Describing basically the exact same monster but with twice the RHD.
    Quote Originally Posted by Artanis View Post
    I'm going to be honest, "the Welsh became a Great Power and conquered Germany" is almost exactly the opposite of the explanation I was expecting

  5. - Top - End - #5
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    Would this still be useful to anyone if it didn't include the tables, but i posted instructions to make your own?

    Forgot to mention above that i'm working on making it support templates.
    Last edited by Erith; 2012-08-12 at 04:43 PM. Reason: Templates are done.

  6. - Top - End - #6
    Ogre in the Playground
    Join Date
    Jan 2012
    Gender
    Male

    Default Re: NPC generator

    I'd certainly be interested. Maybe make up some new stuff to fill in the gaps or use pathfinder ones? I think those are OGL.
    If you see me talking about Shaper Psions, assume that anything not poison immune within 100 feet will be dead.
    Quote Originally Posted by kardar233 View Post
    I was going to PM you about it because I wanted to know, but then you posted it later. Elegant solution. Watch out for Necropolitans.
    My Homebrew Signature such as it is.

  7. - Top - End - #7
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    This is the main file. Name it what you want and make the extention '.py'

    Edit: changed the program to pull attribute rolling style from problib. This file should no longer be editted
    Code:
    import random
    from problib import alignment, classes, races, rollstyle
    import os
    
    #============================================================================
    def getrange(dic):
        return max(dic.keys()) + 1
    #============================================================================
    def rolltable(dic):
        result = ''
        done = False
        
        while not done:
            roll = random.randrange(1, getrange(dic))
    
            keys = sorted(dic.keys())
            temp = ''
            for i in keys:
                if roll <= i:         
                    temp = dic[i]
                    break
                        
            if temp[0] == '#':
                temp = temp[1:]
            else:
                done = True
    
            
            if result.find(temp) < 0:
                if len(result) > 0:
                    result += ' '
                result += temp
    
        return result
    #============================================================================
    def rollatt(num=4, die=6, keep=3, reroll=0):
        #if no arguments are passed use values from problib
        if num == 4 and die == 6 and keep == 3 and reroll == 0:
            num = rollstyle[0]
            die = rollstyle[1]
            keep = rollstyle[2]
            reroll = rollstyle[3]
        roll = []
        for i in range(1, num+1):
            j = 0
            while j <= reroll:
                j = random.randrange(1, die + 1)
            roll.append(j)
    
        while len(roll) > keep:
            roll.sort()
            del roll[0]
    
        return sum(roll)
    #============================================================================
    def rollattributes():
        attributes = []
        for i in range(6):
            attributes.append(rollatt())    #add arguments to rollatt() to change rolling style
        return attributes                   #default is 4d6 keep 3 no rerolls
    #============================================================================
    def rollchar():
        _alignment = rolltable(alignment)
        _class = rolltable(classes[_alignment])
        _race = rolltable(races[_alignment][_class])
        _attributes = rollattributes()
        
        returnval = ''
        returnval += 'Alignment: {0}\n'.format(_alignment)
        returnval += 'Class: {0}\n'.format(_class)
        returnval += 'Race: {0}\n'.format(_race)
        returnval += 'Str: {0}\n'.format(_attributes[0])
        returnval += 'Dex: {0}\n'.format(_attributes[1])
        returnval += 'Con: {0}\n'.format(_attributes[2])
        returnval += 'Int: {0}\n'.format(_attributes[3])
        returnval += 'Wis: {0}\n'.format(_attributes[4])
        returnval += 'Cha: {0}\n'.format(_attributes[5])
    
        return returnval
    #============================================================================
    def writefile(contents, filename):
        f=file(filename + ".txt", 'a')
    
        try:
            assert isinstance(contents, list)
            for char in contents:
                f.write(char)
                f.write('==========================================\n')
        except:    
            f.write(contents)
            f.write('==========================================\n')
        f.close()
    #============================================================================
    def main():
        again = 'y'
        while again == 'y' or again == 'Y':
            isnum = False
            while not isnum:
                num = raw_input('Enter the number of characters to generate: ')
                try:
                    int(num)
                    isnum = True
                except:
                    isnum = False
                
            fileyn = 'p'
            while fileyn != 'y' and fileyn != 'Y' and fileyn != 'n' and fileyn != 'N':
                fileyn = raw_input('Do you want to write the characters to a file? (y/n): ')
    
            if fileyn == 'y' or fileyn == 'Y':
                filename = ''
                while len(filename) < 1:
                    filename = raw_input('Enter the file name: ')
    
            if fileyn == 'y' or fileyn == 'Y':
                printtoscreen = 'p'
                while printtoscreen != 'y' and printtoscreen != 'Y' and printtoscreen != 'n' and printtoscreen != 'N':
                    printtoscreen = raw_input('Do you want to print the characters on the screen? (y/n): ')
            else:
                printtoscreen = 'y'
                
            for i in range(int(num)):
                char = rollchar()
                if fileyn == 'y' or fileyn == 'Y':
                    writefile(char, filename)
                if printtoscreen == 'y' or printtoscreen == 'Y':
                    print char, '===================================================\n'
            
            again = raw_input('Run again? (y/n): ')
            if again == 'y' or again == 'Y':
                os.system('cls' if os.name == 'nt' else 'clear')
            
            
    #============================================================================
    main()
    Last edited by Erith; 2012-08-13 at 01:18 PM.

  8. - Top - End - #8
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    This one needs to be named 'problib.py'

    Code:
    #edit this to change how attributes are rolled
    #the first value is the number of dice to roll
    #the second value is the type of die to roll
    #the third value is how many dice to keep
    #the fourth value is the highest number to reroll
    #default is 4d6 keep 3 no rerolls
    #examples:
    #[5,6,3,1] will roll 5d6 keep 3 reroll 1s
    #[3,8,3,2] will roll 3d8 keep 3 reroll 1s and 2s
    rollstyle = [4,6,3,0]
    
    #probability works the same for all tables
    #a random number is generated with a minimum of 1 and a max of the highest value in the table
    #each number in the table is the max result that will give that entry
    #the min result is the previus table entry + 1
    #for example if you have this table:
    #   10  :   x1
    #   20  :   x2
    #   50  :   x3
    #the result will be x1 on a roll of 1-10
    #x2 has a range of 11-20
    #x3 has a range of 21-50
    
    #a note on table formatting:
    #i used a lot of white space to make the tables easier to read, but this is not necessary
    #separate the probability from the class or race names with a colon
    #each entry needs to be separated with a comma, but there shouldnt be one after the last entry of the table
    #all strings (anything thats not a number) needs to be enclosed in either double("") or single('') quotes
    
    #alignment is the simplest table
    #just put in the probabilities you you want for each alignment
    #the law-neutral-chaos axis is not rolled
    alignment = {       1     :     'Good',
                        2     :     'Neutral',
                        3     :     'Evil'
                    }
    
    #don't touch the good, neutral, and evil lines, they are needed for the programto work
    #you can list as many classes as you want with any probability range
    #classes should be copied into each of the good, neutral, and evil sections unless they are restricted by alignment
    #watch the spelling, the class names you enter here will be used to generate the race later
    classes = {
        'Good'      :   {
                  5      :     'Example 1',
                  10     :     'Example 2',
                  30     :     'Example 3'
                  },
        'Neutral'   :   {
                  5      :     'Example 1',
                  10     :     'Example 2',
                  15     :     'Example 3',
                  25     :     'Example 4'
                  },
        'Evil'      :   {
                  10     :     'Example 1',
                  15     :     'Example 2',
                  35     :     'Example 3'
                  }
    }
    
    #as with the classes table, don't touch the good, neutral or evil lines
    #the class lines, here the various 'example x' lines, should correspond exactly to the classes from the classes table
    #any race entry starting with '#' will be interpreted as a template
    #if a template is rolled the program will keep rolling until it gets a race
    #this means its possible to stack templates
    #note that the program doesn't check the legality of adding a template
    races = {
        'Good'  :   {
            'Example 1'    :   {
                  2      :     'Race 1',
                  32     :     'Race 2',
                  34     :     'Race 3'
                },
            'Example 2'    :    {
                  1      :     'Race 1',
                  6      :     'Race 2',
                  11     :     '#Template 1'
                },
            'Example 3'    :    {
                  1      :     'Race 1',
                  2      :     'Race 2',
                  22     :     '#Template 1',
                  24     :     '#Template 2'
                }
        },
        'Neutral'   :   {
            'Example 1'    :    {
                  1      :     'Race 1',
                  2      :     'Race 2',
                  13     :     'Race 3',
                  14     :     'Race 4'
                },
            'Example 2'    :    {
                  1      :     'Race 1',
                  3      :     'Race 2',
                  5      :     'Race 3'
                },
            'Example 3'    :    {
                  15     :     'Race 1',
                  25     :     'Race 2',
                  26     :     'Race 3',
                  27     :     '#Template 1'
                },
            'Example 4'        :    {
                  1      :     'Race 1',
                  6      :     'Race 2',
                  11     :     'Race 4'
                }
        },
        'Evil'      :   {
            'Example 1'    :    {
                  1      :     'Race 2',
                  3      :     'Race 3',
                  4      :     'Race 4',
                  5      :     '#Template 2'
                },
            'Example 2'         :    {
                  1      :     'Race 1',
                  2      :     'Race 2',
                  17     :     'Race 4'
                },
            'Example 3'       :    {
                  2      :     'Race 2',
                  3      :     'Race 4',
                  4      :     '#Template 2'
                }
        }
    }
    
    #other notes:
    #if a race table only has templates and no races the program will hang because it will keep rolling until it finds a race
    #strictly speaking the alignment table can be extended, for example adding vile and exalted alignments
    #this isn't recommended because you would then be required to add the new alignments to the classes and races tables or else the program could crash
    Last edited by Erith; 2012-08-13 at 01:19 PM.

  9. - Top - End - #9
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    You'll need python 2.7 to run them. Let me know if there are any problems.

    Sorry for the triple post.

  10. - Top - End - #10
    Ogre in the Playground
    Join Date
    Jan 2012
    Gender
    Male

    Default Re: NPC generator

    Other than a lack of cls being an avaliable sh command, runs fine. Very cool.
    If you see me talking about Shaper Psions, assume that anything not poison immune within 100 feet will be dead.
    Quote Originally Posted by kardar233 View Post
    I was going to PM you about it because I wanted to know, but then you posted it later. Elegant solution. Watch out for Necropolitans.
    My Homebrew Signature such as it is.

  11. - Top - End - #11
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    Sorry about that, I'm running windows and it works fine for me. Any suggestions to fix that?

  12. - Top - End - #12
    Ogre in the Playground
    Join Date
    Jan 2012
    Gender
    Male

    Default Re: NPC generator

    You clear the screen at the end with system("cls"). It's not needed, if you delete it works fine with bash. You can also remove the importing of system at the beginning.
    If you see me talking about Shaper Psions, assume that anything not poison immune within 100 feet will be dead.
    Quote Originally Posted by kardar233 View Post
    I was going to PM you about it because I wanted to know, but then you posted it later. Elegant solution. Watch out for Necropolitans.
    My Homebrew Signature such as it is.

  13. - Top - End - #13
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    Personally I don't like the idea of having old results being on the screen if you rerun it, and I can't figure out how to do it without system. This should at least make it a bit more platform independent, but it still won't clear the screen in idle. I also cleaned up the imports and some commented out lines.

    Code:
    #the only thing that should really be changed is how rollatt() is called in line 67 in the rollattributes() function
    #as it is, attributes are rolled as 4d6 keep 3 best no rerolls
    #this can be changed by supplying arguments to rollatt()
    #You can changed the number of dice rolled, the type of dice rolled, how many to keep, and whether to reroll anything
    #the num argument is the number of dice to roll
    #the die argument is the type of die
    #the keep argument is how many to keep
    #the reroll argument is the highest result to be rerolled
    #
    #rollatt(num=3) will roll attributes as 3d6
    #rollatt(num=5, keep=4) will roll 5d6 and keep the 4 highest
    #rollatt(die=8) will roll 4d8 keep 3
    #rollatt(reroll=2) will roll 4d6 keep 3 reroll 1s and 2s
    
    import random
    from problib import alignment, classes, races
    import os
    
    #============================================================================
    def getrange(dic):
        return max(dic.keys()) + 1
    #============================================================================
    def rolltable(dic):
        result = ''
        done = False
        
        while not done:
            roll = random.randrange(1, getrange(dic))
    
            keys = sorted(dic.keys())
            temp = ''
            for i in keys:
                if roll <= i:         
                    temp = dic[i]
                    break
                        
            if temp[0] == '#':
                temp = temp[1:]
            else:
                done = True
    
            
            if result.find(temp) < 0:
                if len(result) > 0:
                    result += ' '
                result += temp
    
        return result
    #============================================================================
    def rollatt(num=4, die=6, keep=3, reroll=0):
        roll = []
        for i in range(1, num+1):
            j = 0
            while j <= reroll:
                j = random.randrange(1, die + 1)
            roll.append(j)
    
        while len(roll) > keep:
            roll.sort()
            del roll[0]
    
        return sum(roll)
    #============================================================================
    def rollattributes():
        attributes = []
        for i in range(6):
            attributes.append(rollatt())    #add arguments to rollatt() to change rolling style
        return attributes                   #default is 4d6 keep 3 no rerolls
    #============================================================================
    def rollchar():
        _alignment = rolltable(alignment)
        _class = rolltable(classes[_alignment])
        _race = rolltable(races[_alignment][_class])
        _attributes = rollattributes()
        
        returnval = ''
        returnval += 'Alignment: {0}\n'.format(_alignment)
        returnval += 'Class: {0}\n'.format(_class)
        returnval += 'Race: {0}\n'.format(_race)
        returnval += 'Str: {0}\n'.format(_attributes[0])
        returnval += 'Dex: {0}\n'.format(_attributes[1])
        returnval += 'Con: {0}\n'.format(_attributes[2])
        returnval += 'Int: {0}\n'.format(_attributes[3])
        returnval += 'Wis: {0}\n'.format(_attributes[4])
        returnval += 'Cha: {0}\n'.format(_attributes[5])
    
        return returnval
    #============================================================================
    def writefile(contents, filename):
        f=file(filename + ".txt", 'a')
    
        try:
            assert isinstance(contents, list)
            for char in contents:
                f.write(char)
                f.write('==========================================\n')
        except:    
            f.write(contents)
            f.write('==========================================\n')
        f.close()
    #============================================================================
    def main():
        again = 'y'
        while again == 'y' or again == 'Y':
            isnum = False
            while not isnum:
                num = raw_input('Enter the number of characters to generate: ')
                try:
                    int(num)
                    isnum = True
                except:
                    isnum = False
                
            fileyn = 'p'
            while fileyn != 'y' and fileyn != 'Y' and fileyn != 'n' and fileyn != 'N':
                fileyn = raw_input('Do you want to write the characters to a file? (y/n): ')
    
            if fileyn == 'y' or fileyn == 'Y':
                filename = ''
                while len(filename) < 1:
                    filename = raw_input('Enter the file name: ')
    
            if fileyn == 'y' or fileyn == 'Y':
                printtoscreen = 'p'
                while printtoscreen != 'y' and printtoscreen != 'Y' and printtoscreen != 'n' and printtoscreen != 'N':
                    printtoscreen = raw_input('Do you want to print the characters on the screen? (y/n): ')
            else:
                printtoscreen = 'y'
                
            for i in range(int(num)):
                char = rollchar()
                if fileyn == 'y' or fileyn == 'Y':
                    writefile(char, filename)
                if printtoscreen == 'y' or printtoscreen == 'Y':
                    print char, '===================================================\n'
            
            again = raw_input('Run again? (y/n): ')
            if again == 'y' or again == 'Y':
                os.system('cls' if os.name == 'nt' else 'clear')
            
            
    #============================================================================
    main()

  14. - Top - End - #14
    Ogre in the Playground
    Join Date
    Jan 2012
    Gender
    Male

    Default Re: NPC generator

    Yep, works fine now.
    If you see me talking about Shaper Psions, assume that anything not poison immune within 100 feet will be dead.
    Quote Originally Posted by kardar233 View Post
    I was going to PM you about it because I wanted to know, but then you posted it later. Elegant solution. Watch out for Necropolitans.
    My Homebrew Signature such as it is.

  15. - Top - End - #15
    Halfling in the Playground
    Join Date
    Oct 2008

    Default Re: NPC generator

    I don't suppose the position on posting the tables would be any different if they were saved in unreadable binary? It could only be read by the program that way. Just thought I'd throw that out there.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •