v50 Steam/Premium information for editors
  • v50 information can now be added to pages in the main namespace. v0.47 information can still be found in the DF2014 namespace. See here for more details on the new versioning policy.
  • Use this page to report any issues related to the migration.
This notice may be cached—the current version can be found here.

Difference between revisions of "User:Button/ModestMod"

From Dwarf Fortress Wiki
Jump to navigation Jump to search
(updated current work)
Line 69: Line 69:
  
 
<pre>
 
<pre>
 +
import os
  
graphicsdict = [] # dict string:string, where the first string is the tag type formatted with pipes a la that section up there (but with $'s replaced), and the second string is the tag as it appears in the raws
+
template_tree = None
milestones=["OBJECT","CREATURE","CASTE","PLANT","GROWTH","INORGANIC","BUILDING_WORKSHOP","BUILDING_FURNACE","ENTITY","ITEM_TOOL"]
+
runconfig = 'resources/run.config'
files = [] # dict string:list(string), where the first string is the raw file name, and the list is a list of
+
ascii_codes = None
valid_tokens = [] # all the pipe-separated graphics tokens loaded into memory
+
graphics_tokens_by_file = {}
ascii_codes = [":"="###", "["="###", "]"="###", "'"="###"] # Gotta include leading 0s for the tags-parsing routine - it expects 3 digits
 
  
# Process graphicsified raws into a dict, or load from the save of a previously generated dict
+
properties = {
def generate_graphics_dict(sourcedir, graphicsdict):
+
              'target':['dir'],
for file in sourcedir:
+
              'source':['dir'],
currentobj = [] # Stack of tuples. Holds all the milestone type:value pairs we've passed already. Keeps track of how deep we are.
+
              'output':['dir'],
passedstones = [] # Stack. Holds all the milestone types we've passed already. Should be equivalent to the first value of each duple in currentobj.
+
              'save':['dir'],
for line in file:
+
              'templates':['file'],
for full_tag in tags(line): # For every tag in the file
+
              'ascii':['file']  
tag_type = first_token(full_tag)
+
              }
if is_milestone(tag_type): # If the tag is of a milestone type
 
if not passedstones.contains(tag_type): # If this isn't a milestone type we've already seen
 
passedstones.push(tag_type)
 
currentobj.push((tag_type,body_tokens(full_tag)))
 
else: #milestone is a new instance of a thing
 
while passedstones.contains(tag_type): # Clearing out all the milestones until we no longer have a competing tag of type tag_type in the memory
 
passedstones.pop()
 
currentobj.pop()
 
passedstones.push(tag_type) # Put the new tag on the stack
 
currentobj.push((tag_type,body_tokens(full_tag)))
 
elif is_graphics_tag(currentobj,tag): # This assumes milestones can't be graphics tags, which is correct now but may be a problem later.
 
graphicsdict[graphics_tag(currentobj,tag)] = full_tag
 
  
# Takes a line of raw file and returns all tags in the file. (Without the [brackets])
+
def load_graphics_source():
def tags(line):
+
    global properties
processed_line = escape_problematic_literals(line)
+
    if properties['source'][1] == None:
to_return = [] # list of strings, a la split()
+
        # TODO error handling
while processed_line.contains('[') and processed_line.contains(']') and processed_line.firstIndexOf('[') < processed_line.lastIndexOf(']'):
+
        print("Undefined graphics source file. Please add a 'source' property in ",runconfig,".")
if processed_line.firstIndexOf(']') < processed_line.firstIndexOf('['):
+
    else:
processed_line = processed_line.substring(processed_line.firstIndexOf('['))
+
        for root, dirs, files in os.walk(properties['source'][1]):
to_return.append(processed_line.substring(processed_line.firstIndexOf('[')+1,processed_line.firstIndexOf(']')))
+
            for rawfile in files:
processed_line = processed_line.substring(processed_line.firstIndexOf(']')+1)
+
                global template_tree
return to_return
+
                curr_template_node = template_tree
+
                curr_graphics_node = None
def escape_problematic_literals(rawline):
+
                tarpath = os.path.join(root,rawfile)
line = rawline.copy()
+
                for line in open(tarpath,encoding='cp437'):
# Replace literal key characters with number codes
+
                    for tag in tags(line):
# Literal colons are going to require some special processing, because of the following case: GROWTH:'r':'x': etc. That's why we can't just use a blind replaceAll.
+
                        matching_node = curr_template_node.find_template_match(tag)
bracketscount = 0 # If odd, we are inside a tag. If even, we are outside a tag.
+
                        if matching_node != None:
count = 0 # Where we are in the string
+
                            curr_template_node = matching_node
while count < line.length:
+
                            if curr_graphics_node == None or matching_node in template_tree._children:
# Going from inside a tag to outside or vice versa
+
                                curr_graphics_node = TagNode(rawfile,None,matching_node,tag)
if (bracketscount%2 == 0 and line[count] == "[") or (bracketscount%2 == 1 and line[count] == "]"):
+
                            else:
bracketscount += 1
+
                                while matching_node._tag_template not in curr_graphics_node._template._children:
# We are inside a tag and we have discovered a ' character beginning a literal value, with another 2 characters down on the other side.
+
                                    curr_graphics_node = curr_graphics_node._parent
elif bracketscount%2 == 1 and line[count] == "'"  and line[count+2] == "'":
+
                                curr_graphics_node = TagNode(rawfile,curr_graphics_node,matching_node,tag)
# If the character could be a problem for later processing, replace it with its ascii code.
+
                       
if line[count+1] in ascii_codes.keySet():
+
class TemplateNode:
replace_char = line[count+1]
+
   
line[count] = ascii_codes[replace_char][0] # TODO: see if there's a way this can be length-agnostic
+
    #self._tag_template #string
line[count+1] = ascii_codes[replace_char][1]
+
    #self._children    # list of TemplateNodes
line[count+2] = ascii_codes[replace_char][2]
+
    #self._parent    # TemplateNode
count += 2
+
    #self._is_graphics_tag    # Boolean
count += 1
+
   
# line has now had its literal "use this tile" versions of its special characters replaced with their numbers.
+
    #string does not contain the character '|'.
return line
+
    def __init__(self, parent, string=""):
+
        self._is_graphics_tag = False
def first_token(tag)
+
        global template_tree
return tag.substring(0,tag.firstIndexOf(':')) # Assuming all literal ':'s have been replaced with their ascii codes
+
        if parent == None:
 +
            self._parent = None
 +
            template_tree = self
 +
            self._children={}
 +
        else:
 +
            if template_tree == None:
 +
                self._parent = TemplateNode(None, "")
 +
            else:
 +
                self._parent = parent
  
# Apply graphics dict to the raws found in sourcedir, outputting in tardir .
+
            self._tag_template = string
def apply_graphics(sourcedir, tardir, graphicsdict):
+
            self._children = {}
graphicsdictcopy = graphicsdict.copy() # This copies the object references of the dict item, not the reference to the dict nor the contents of the contents.
+
           
for file in sourcedir:
+
            parent.add_child(self)
passedstones = []
+
       
currentobj = []
+
    def add_child(self, node):
# TODO create file in tardir, and open for write
+
        if node._tag_template in self._children.keys():
for line in file:
+
            return self._children[node._tag_template]
moddedline = escape_problematic_literals(line) # Our output
+
        else:
for full_tag in tags(line): # For every tag in the raw file we're applying the graphics to
+
            self._children[node._tag_template] = node
tag_type = first_token(full_tag)
+
            return node
if is_milestone(tag_type): # Keep track of our milestone status
+
       
if not passedstones.contains(tag_type):
+
    def find_template_match(self, tag):
passedstones.push(tag_type)
+
        curr_node = self
currentobj.push((tag_type,body_tokens(full_tag)))
+
        matching_node = None
else:
+
        out_of_parents = False
if has_remaining_graphics_tags(graphicsdictcopy,currentobj):
+
        while matching_node == None and not out_of_parents:
for graphicstag in pull_remaining_graphics_tags(graphicsdictcopy,currentobj):
+
            matching_node = curr_node.get_child_matching(tag)
moddedline += '\n'
+
            if curr_node._parent == None:
moddedline += write_tag(graphicstag)
+
                out_of_parents = True
graphicsdictcopy.remove(graphics_tag(currentobj,full_tag))
+
            else:
while passedstones.contains(tag_type):
+
                curr_node = curr_node._parent
passedstones.pop()
+
        return matching_node
currentobj.pop()
+
           
passedstones.add(full_tag)
+
       
currentobj[tag] = body_tokens(full_tag)
+
    def get_child_matching(self, tag):
elif is_graphics_tag(currentobj, full_tag):
+
        if tag in self._children.keys():
# TODO handle graphics lines that aren't present in the graphics we're applying
+
            return self._children[tag]
moddedline.replace(full_tag,get_replacement_tag(full_tag,graphicsdictcopy))
+
        else:
graphicsdictcopy.remove(graphics_tag(currentobj,full_tag))
+
            return_possibilities = []
# If it's not a milestone or a graphics tag, just write it as it appeared in the original file
+
            for child in self._children:
tardir.write(moddedline + '\n')
+
                return_node = self._children[child].get_template_match(tag)
close file
+
                if return_node != None:
 
+
                    return_possibilities.append(self._children[child])
# Load valid graphics token definitions from file
+
            if len(return_possibilities) == 1:
def load_valid_graphics_tokens():
+
                return return_possibilities[0]
filelocation = ..\blahblahblah\graphicstiles.txt
+
            elif len(return_possibilities) == 0:
for line in filelocation.open():
+
                return None
if length(line.strip()) > 0:
+
            else:
valid_tokens.append(line)
+
                # TODO error handling
filelocation.close()
+
                print("Found more than one matching child. You put it together wrong. Matching children are: ")
+
                for poss in return_possibilities:
# Write graphics dict to file
+
                    print(poss)
def save_graphics_dict(tarfile, graphicsdict):
+
                return return_possibilities[0]
graphicsdictcopy = graphicsdict.copy()
+
           
tarfile.open(write)
+
    # This tells if a single tag matches a single tag; that is, it assumes we've got one element of the |-separated list
for rawformmated in to_raw_format(graphicsdictcopy):
+
    def get_template_match(self, tag_to_compare):
tarfile.write(rawformatted)
+
        template_token_bag = []
tarfile.close()
+
        template_token_bag.append(self._tag_template.split(':'))
+
        candidate_tokens = tag_to_compare.split(':')
# Convenience method for if this tag is a milestone
+
       
def is_milestone(tag_type):
+
        ii = 0
return milestones.contains(tag_type)
+
        while ii < len(candidate_tokens) and len(template_token_bag) > 0:
+
            for var in template_token_bag:
def body_tokens(full_tag):
+
                # I decided this isn't necessarily true, as ranges might have 0's
return full_tag.substring(full_tag.firstIndexOf(':'),length(full_tag))
+
                #if len(var) > len(candidate_tokens):
 
+
                #    template_token_bag.remove(var)
</pre>
+
                if '&' == var[ii] or '?' == var[ii] or '$' == var[ii] or var[ii] == candidate_tokens[ii]:
 
+
                    # This case is an auto-pass
== More well-formatted graphics token reference file ==
+
                    ii = ii+1
 
+
                else:
<pre>
+
                    if '&' in var[ii] or '?' in var[ii] or '$' in var[ii]:
OBJECT:CREATURE|CREATURE:$|CREATURE_TILE:_
+
                        varii_type = var[ii][0]
OBJECT:CREATURE|CREATURE:$|COLOR:_:_:_
+
                        varii_range = var[ii][2:var[ii].index(')')].split(',')
OBJECT:CREATURE|CREATURE:$|GLOWTILE:_
+
                        # If len(varii_range) == 1 then we have a range of format (x,), indicating any number of :'s
OBJECT:CREATURE|CREATURE:$|GLOWCOLOR:_:_:_
+
                        if len(varii_range) == 1:
OBJECT:CREATURE|CREATURE:$|ALTTILE:_
+
                            varii_range[1] = len(candidate_tokens)-len(var)  
OBJECT:CREATURE|CREATURE:$|CASTE:$|CASTE_ALTTILE:_
+
                        # For every possible length (the +1 is because range is exclusive-end and my notation is inclusive-end)
OBJECT:CREATURE|CREATURE:$|CASTE:$|CASTE_COLOR:_:_:_
+
                        for jj in range(varii_range[0],varii_range[1]+1):
OBJECT:CREATURE|CREATURE:$|CASTE:$|CASTE_GLOWCOLOR:_:_:_
+
                            # Make a copy of var
OBJECT:CREATURE|CREATURE:$|CASTE:$|CASTE_GLOWTILE:_
+
                            new_var = var.copy()
OBJECT:CREATURE|CREATURE:$|CASTE:$|CASTE_SOLDIER_ALTTILE:_
+
                            # Remove the range item
OBJECT:CREATURE|CREATURE:$|CASTE:$|CASTE_SOLDIER_TILE:_
+
                            del new_var[ii]
OBJECT:CREATURE|CREATURE:$|CASTE:$|CASTE_TILE:_
+
                            # Replace it with (one of the possible lengths) times the multiplied symbol
OBJECT:CREATURE|CREATURE:$|CREATURE_SOLDIER_TILE:_
+
                            # If jj is 0 the range item is just removed
OBJECT:CREATURE|CREATURE:$|CASTE:$|REMAINS_COLOR:_:_:_
+
                            for kk in range(0,jj):
OBJECT:CREATURE|CREATURE:$|REMAINS_COLOR:_:_:_
+
                                new_var.insert(ii,varii_type)
OBJECT:CREATURE|CREATURE:$|SENSE_CREATURE_CLASS:$:_:_:_:_
+
                            # Place the new variant in the token bag for evaluation
OBJECT:CREATURE|CREATURE:$|CASTE:$|SENSE_CREATURE_CLASS:$:_:_:_:_
+
                            template_token_bag.append(new_var)
OBJECT:CREATURE|CREATURE:$|SOLDIER_ALTTILE:_
+
                    # No counting, there is a new template_token_bag[ii]
 
+
                    template_token_bag.remove(var)
OBJECT:PLANT|PLANT:$|GROWTH|GROWTH_PRINT:_:_:_:_:_:$
+
        if ii < len(candidate_tokens):
OBJECT:PLANT|PLANT:$|TREE_TILE:_
+
            return None
OBJECT:PLANT|PLANT:$|DEAD_TREE_TILE:_
+
        elif len(template_token_bag) == 1:
OBJECT:PLANT|PLANT:$|SAPLING_TILE:_
+
            return template_token_bag[0]
OBJECT:PLANT|PLANT:$|DEAD_SAPLING_TILE:_
+
        else:
OBJECT:PLANT|PLANT:$|TREE_COLOR:_:_:_
+
            # TODO error handling
OBJECT:PLANT|PLANT:$|DEAD_TREE_COLOR:_:_:_
+
            print("Button, there's a problem, more than one template matched.\nTag: ",tag_to_compare,"Matches:")
OBJECT:PLANT|PLANT:$|SAPLING_COLOR:_:_:_
+
            for template in template_token_bag:
OBJECT:PLANT|PLANT:$|DEAD_SAPLING_COLOR:_:_:_
+
                print(template)
OBJECT:PLANT|PLANT:$|PICKED_TILE:_
+
            # Technically this does in fact have a matching template
OBJECT:PLANT|PLANT:$|DEAD_PICKED_TILE:_
+
            return template_token_bag[0]
OBJECT:PLANT|PLANT:$|SHRUB_TILE:_
+
       
OBJECT:PLANT|PLANT:$|DEAD_SHRUB_TILE:_
+
    def how_many_generations(self):
OBJECT:PLANT|PLANT:$|PICKED_COLOR:_:_:_
+
        temp_node = self
OBJECT:PLANT|PLANT:$|DEAD_PICKED_COLOR:_:_:_
+
        count = -1
OBJECT:PLANT|PLANT:$|SHRUB_COLOR:_:_:_
+
        global template_tree
OBJECT:PLANT|PLANT:$|DEAD_SHRUB_COLOR:_:_:_
+
        while temp_node != template_tree:
OBJECT:PLANT|PLANT:$|GRASS_TILES:_:_:_:_
+
            temp_node = temp_node._parent
OBJECT:PLANT|PLANT:$|ALT_GRASS_TILES:_:_:_:_
+
            count = count + 1
OBJECT:PLANT|PLANT:$|GRASS_COLORS:_:_:_:_:_:_:_:_:_:_:_:_
+
        return count
OBJECT:PLANT|PLANT:$|ALT_PERIOD:_:_
+
   
OBJECT:PLANT|PLANT:$|SEED:$:$:_:_:_
+
class TagNode:
 
+
   
MATERIAL?:$|POWDER_DYE:_:_:_
+
    def __init__(self,filename,parent,template,tag):
MATERIAL?:$|TILE:_
+
        self._tag = tag
MATERIAL?:$|ITEM_SYMBOL:_
+
        self._filename = filename
MATERIAL?:$|DISPLAY_COLOR:_:_:_
+
        self._parent = parent
MATERIAL?:$|BUILD_COLOR:_:_:_
+
        self._template = template
MATERIAL?:$|TILE_COLOR:_:_:_
+
        self._children = {}
MATERIAL?:$|BASIC_COLOR:_:_:_
+
       
MATERIAL?:$|STATE_COLOR:_:_:_
+
        global graphics_tokens_by_file
 
+
        if filename not in graphics_tokens_by_file.keys():
INORGANIC:$|DISPLAY_COLOR:_:_:_
+
            graphics_tokens_by_file[filename] = []
INORGANIC:$|TILE:_
+
        if parent == None:
INORGANIC:$|ITEM_SYMBOL:_
+
            graphics_tokens_by_file[filename].append(self)
 
+
        else:
OBJECT:BUILDING_WORKSHOP|BUILDING_WORKSHOP:$|NAME_COLOR:_:_:_
+
            parent._children.append(self)
OBJECT:BUILDING_WORKSHOP|BUILDING_WORKSHOP:$|TILE:$:$:_
 
OBJECT:BUILDING_FURNACE|BUILDING_FURNACE:$|TILE:$:$:_
 
OBJECT:BUILDING_WORKSHOP|BUILDING_WORKSHOP:$|COLOR:$:$:_:_:_
 
OBJECT:BUILDING_FURNACE|BUILDING_FURNACE:$|COLOR:$:$:_:_:_
 
 
 
OBJECT:ENTITY|ENTITY:$|COLOR:_:_:_
 
 
 
OBJECT:ITEM_TOOL?|ITEM_TOOL:$|TILE:_
 
OBJECT:ITEM_TOOL?|ITEM_TOOL:$|INVERTED_TILE
 
</pre>
 
 
 
==Beginning of tree approach==
 
 
 
<pre>def class TemplateNode:
 
template_tree = None # TemplateNode at the tree root
 
 
 
#self._tag_template #string
 
#self._children # list of TemplateNodes
 
#self._parent # TemplateNode
 
#self._is_graphics_tag # Boolean
 
 
__init__(self, parent, string="", is_graphics_tag=False)
 
if parent == None:
 
self._parent = None
 
template_tree = self
 
self._children=[]
 
else:
 
if template_tree == None:
 
self._parent = new TemplateNode(None, "")
 
else:
 
self._parent = parent
 
 
 
self._tag_template = string
 
self._is_graphics_tag = is_graphics_tag
 
self.children = []
 
 
parent.add_child(self)
 
 
return self
 
 
def add_child(node):
 
if !has_matching_child(node._tag_template):
 
self._children[node._tag_template] = node
 
 
def has_matching_child(tag_to_compare):
 
template_token_bag = []
 
template_token_bag.append(self._tag_template.split(':'))
 
candidate_tokens = tag_to_compare.split(':')
 
 
found_flaw = False
 
 
ii = 0
 
while !found_flaw and ii < len(candidate_tokens) and len(template_token_bag) > 0:
 
for var in template_token_bag:
 
#TBC
 
 
 
def how_many_generations():
 
temp_node = self
 
count = -1
 
while temp_node != template_tree:
 
temp_node = temp_node._parent
 
count = count + 1
 
return count
 
 
</pre>
 
</pre>

Revision as of 13:42, 5 May 2015

This is a directory page for my Modest Mod work.

I feel a little bad for clogging up the wiki with this stuff, but sometimes I get bored and I'm not at my home computer.

Plans

  • Moving the no-fear-of-commitment orientation mods out into a pair of creature variations, in c_variation_modest. May make a Module applying them to sapients as well. Eternal Lovers are kind of frustrating.
  • Could probably just make the plant growths that I currently have wrapping tree nuts, edible or pressable or whatever in their own right, and never have to deal with the seeds. OTOH, that might mess up grinding them to paste/pressing them? And would still need the hulling reaction for plantable legumes.
  • Could change implementation of all plants, including those brewed 'whole', into growths. Then would be able to share reactions better, and do seed/no seed on a purely FAKESEED basis.
    • Probably not worth it in vanilla, but maybe fore plant features, since we'll be needing to mill "whole plants" (e.g. cassava, whip vines) and also to mill seedheads.
  • Need to move Plump Helmets back to a usable plant instead of a fruit. Save it as fruit for the Basic Adventurer's Pack.
  • Need to look at the expanded creature variation replacement tokens in preparation for the vampire anti-sobriety module.

Accelerated

  • Need to look at the old Accelerated Modest Mod and see how much of it is applicable to the current version.
  • Need to see about thinning down plant materials, like a lot.
  • Need to remove useless plant growths, like cherry blossoms etc. Leaves? Or would that be too ugly?
  • Could probably reduce grass to a single species? Or would that make pandas shitty?

Plant Features

  • Could probably make grain use the fruit(like?) reactions for brewing, milling, etc.
  • Kahlua & Chajun & possibly the plant products that are used to make them could give a temporary [NOSLEEP] tag? Followed by a 'crash' of a drowsiness syndrome?
  • Jellies? Preserves? Juices?
  • More variation in material values

Basic Adventurer's Pack

This needs to be a basic pack. Don't get cocky.

Must include

  • Plump helmet to fruit
  • Backpacks, bags, quivers, waterskins.
    • Tanning
      • Stone knife
    • Spinning & weaving
      • Spindle
  • Bone ammo
    • Stone chisel
    • Arrow bamboo?
  • Trophy jewelry/crafts for trading

Might want to include

  • Leather armor.
  • Bone & shell armor.
  • Bone bows, crossbows, blowguns
    • Bamboo?
  • Plant fiber thread. Rope reeds, pig tail stalks, others?
    • For the merge with Plant Features, abaca/banana/pineapple leaves, cotton bolls, etc. A lot of the plant fabric stuff is Plant Features-necessary.

Probably going too far

  • Enable a whole bunch of sapients playable as outsiders. Not just the ones in the gear-already-exists size range.
    • Hahaha, fairies as a playable outsider race |D That would be silly
  • Cooking. Just biscuits? Or full-on recipes?
    • Flour? Oil? Good use for mortar & pestle...
    • Maybe a balanced meal would require a veg, a protein, & a sugar/starch, & would provide minor (temporary) attribute boosts?
  • Self-buff "cheats"?

Raws

Some related LNP pseudocode

import os

template_tree = None
runconfig = 'resources/run.config'
ascii_codes = None
graphics_tokens_by_file = {}

properties = {
              'target':['dir'],
              'source':['dir'],
              'output':['dir'],
              'save':['dir'],
              'templates':['file'],
              'ascii':['file'] 
              }

def load_graphics_source():
    global properties
    if properties['source'][1] == None:
        # TODO error handling
        print("Undefined graphics source file. Please add a 'source' property in ",runconfig,".")
    else:
        for root, dirs, files in os.walk(properties['source'][1]):
            for rawfile in files:
                global template_tree
                curr_template_node = template_tree
                curr_graphics_node = None
                tarpath = os.path.join(root,rawfile)
                for line in open(tarpath,encoding='cp437'):
                    for tag in tags(line):
                        matching_node = curr_template_node.find_template_match(tag)
                        if matching_node != None:
                            curr_template_node = matching_node
                            if curr_graphics_node == None or matching_node in template_tree._children:
                                curr_graphics_node = TagNode(rawfile,None,matching_node,tag)
                            else:
                                while matching_node._tag_template not in curr_graphics_node._template._children:
                                    curr_graphics_node = curr_graphics_node._parent
                                curr_graphics_node = TagNode(rawfile,curr_graphics_node,matching_node,tag)
                        
class TemplateNode:
    
    #self._tag_template #string
    #self._children    # list of TemplateNodes
    #self._parent    # TemplateNode
    #self._is_graphics_tag    # Boolean
    
    #string does not contain the character '|'.
    def __init__(self, parent, string=""):
        self._is_graphics_tag = False
        global template_tree
        if parent == None:
            self._parent = None
            template_tree = self
            self._children={}
        else:
            if template_tree == None:
                self._parent = TemplateNode(None, "")
            else:
                self._parent = parent

            self._tag_template = string
            self._children = {}
            
            parent.add_child(self)
        
    def add_child(self, node):
        if node._tag_template in self._children.keys():
            return self._children[node._tag_template]
        else:
            self._children[node._tag_template] = node
            return node
        
    def find_template_match(self, tag):
        curr_node = self
        matching_node = None
        out_of_parents = False
        while matching_node == None and not out_of_parents:
            matching_node = curr_node.get_child_matching(tag)
            if curr_node._parent == None:
                out_of_parents = True
            else:
                curr_node = curr_node._parent
        return matching_node
            
        
    def get_child_matching(self, tag):
        if tag in self._children.keys():
            return self._children[tag]
        else:
            return_possibilities = []
            for child in self._children:
                return_node = self._children[child].get_template_match(tag)
                if return_node != None:
                    return_possibilities.append(self._children[child])
            if len(return_possibilities) == 1:
                return return_possibilities[0]
            elif len(return_possibilities) == 0:
                return None
            else:
                # TODO error handling
                print("Found more than one matching child. You put it together wrong. Matching children are: ")
                for poss in return_possibilities:
                    print(poss)
                return return_possibilities[0]
            
    # This tells if a single tag matches a single tag; that is, it assumes we've got one element of the |-separated list 
    def get_template_match(self, tag_to_compare):
        template_token_bag = []
        template_token_bag.append(self._tag_template.split(':'))
        candidate_tokens = tag_to_compare.split(':')
        
        ii = 0
        while ii < len(candidate_tokens) and len(template_token_bag) > 0:
            for var in template_token_bag:
                # I decided this isn't necessarily true, as ranges might have 0's
                #if len(var) > len(candidate_tokens):
                #    template_token_bag.remove(var)
                if '&' == var[ii] or '?' == var[ii] or '$' == var[ii] or var[ii] == candidate_tokens[ii]:
                    # This case is an auto-pass
                    ii = ii+1
                else:
                    if '&' in var[ii] or '?' in var[ii] or '$' in var[ii]:
                        varii_type = var[ii][0]
                        varii_range = var[ii][2:var[ii].index(')')].split(',')
                        # If len(varii_range) == 1 then we have a range of format (x,), indicating any number of :'s
                        if len(varii_range) == 1:
                            varii_range[1] = len(candidate_tokens)-len(var) 
                        # For every possible length (the +1 is because range is exclusive-end and my notation is inclusive-end)
                        for jj in range(varii_range[0],varii_range[1]+1):
                            # Make a copy of var
                            new_var = var.copy()
                            # Remove the range item
                            del new_var[ii]
                            # Replace it with (one of the possible lengths) times the multiplied symbol
                            # If jj is 0 the range item is just removed
                            for kk in range(0,jj):
                                new_var.insert(ii,varii_type)
                            # Place the new variant in the token bag for evaluation
                            template_token_bag.append(new_var)
                    # No counting, there is a new template_token_bag[ii]
                    template_token_bag.remove(var)
        if ii < len(candidate_tokens):
            return None
        elif len(template_token_bag) == 1:
            return template_token_bag[0]
        else:
            # TODO error handling
            print("Button, there's a problem, more than one template matched.\nTag: ",tag_to_compare,"Matches:")
            for template in template_token_bag:
                print(template)
            # Technically this does in fact have a matching template
            return template_token_bag[0]
        
    def how_many_generations(self):
        temp_node = self
        count = -1
        global template_tree
        while temp_node != template_tree:
            temp_node = temp_node._parent
            count = count + 1
        return count
    
class TagNode:
    
    def __init__(self,filename,parent,template,tag):
        self._tag = tag
        self._filename = filename
        self._parent = parent
        self._template = template
        self._children = {}
        
        global graphics_tokens_by_file
        if filename not in graphics_tokens_by_file.keys():
            graphics_tokens_by_file[filename] = []
        if parent == None:
            graphics_tokens_by_file[filename].append(self)
        else:
            parent._children.append(self)