#!/usr/bin/env python # Script collected from other scripts # # ../vassal/vassal.py # latexexporter.py # main.py # # ==================================================================== # From ../vassal/vassal.py # Script collected from other scripts # # ../common/singleton.py # ../common/verbose.py # ../common/verboseguard.py # base.py # element.py # globalkey.py # gameelements.py # mapelements.py # globalproperty.py # turn.py # documentation.py # player.py # chessclock.py # widget.py # grid.py # zone.py # board.py # map.py # chart.py # command.py # trait.py # withtraits.py # traits/area.py # traits/dynamicproperty.py # traits/globalproperty.py # traits/prototype.py # traits/place.py # traits/report.py # traits/calculatedproperty.py # traits/restrictcommand.py # traits/label.py # traits/layer.py # traits/globalcommand.py # traits/globalhotkey.py # traits/nostack.py # traits/deselect.py # traits/restrictaccess.py # traits/rotate.py # traits/stack.py # traits/mark.py # traits/mask.py # traits/trail.py # traits/delete.py # traits/sendto.py # traits/moved.py # traits/skel.py # traits/submenu.py # traits/basic.py # traits/trigger.py # traits/nonrect.py # traits/click.py # game.py # buildfile.py # moduledata.py # save.py # vsav.py # vmod.py # upgrade.py # exporter.py # # ==================================================================== # From ../common/singleton.py # ==================================================================== class Singleton(type): '''Meta base class for singletons''' _instances = {} def __call__(cls, *args, **kwargs): '''Create the singleton object or returned existing Parameters ---------- args : tuple Arguments kwargs : dict Keyword arguments ''' if cls not in cls._instances: cls._instances[cls] = \ super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] # # EOF # # ==================================================================== # From ../common/verbose.py # -------------------------------------------------------------------- class Verbose(metaclass=Singleton): def __init__(self,verbose=False): '''Singleton for writing message to screen, contigent on setting Parameters ---------- verbose : bool Whether to show messages or not ''' self._indent = '' self._verbose = verbose def setVerbose(self,verbose): '''Set whether to print or not Parameters ---------- verbose : bool Whether to show messages or not ''' self._verbose = verbose @property def verbose(self): '''Test if this is verbose''' return self._verbose def message(self,*args,**kwargs): '''Write messsage if verbose Parameters ---------- args : tuple Arguments kwargs : dict Keyword arguments ''' if not self._verbose: return if not kwargs.pop('noindent', False): print(self._indent,end='') print(*args,**kwargs) def incr(self): '''Increment indention''' self._indent += ' ' def decr(self): '''Decrement indention''' if len(self._indent) > 0: self._indent = self._indent[:-1] # # EOF # # ==================================================================== # From ../common/verboseguard.py # -------------------------------------------------------------------- class VerboseGuard: def __init__(self,*args,**kwargs): '''A guard pattern that increases verbose indention This is a context manager. The arguments passed are used for an initial message, before increasinig indention. Parameters ---------- args : tuple Arguments kwargs : dict Keyword arguments ''' Verbose().message(*args,**kwargs) def __bool_(self): '''Test if verbose''' return Verbose().verbose def __enter__(self): '''Enter context''' Verbose().incr() return self def __exit__(self,*args): '''Exit context''' Verbose().decr() def __call__(self,*args,**kwargs): '''Write a message at current indention level Parameters ---------- args : tuple Arguments kwargs : dict Keyword arguments ''' Verbose().message(*args,**kwargs) # # EOF # # ==================================================================== # From base.py # ==================================================================== # Key encoding SHIFT = 65 CTRL = 130 ALT = 520 CTRL_SHIFT = CTRL+SHIFT ALT_SHIFT = ALT+SHIFT NONE = '\ue004' NONE_MOD = 0 # -------------------------------------------------------------------- def key(let,mod=CTRL): '''Encode a key sequence Parameters ---------- let : str Key code (Letter) mod : int Modifier mask ''' return f'{ord(let)},{mod}' # -------------------------------------------------------------------- # def hexcolor(s): if isinstance(s,str): s = s.replace('0x','') if len(s) == 3: r, g, b = [int(si,16)/16 for si in s] elif len(s) == 6: r = int(s[0:2],16) / 256 g = int(s[2:4],16) / 256 b = int(s[4:6],16) / 256 else: raise RuntimeError('3 or 6 hexadecimal digits for color string') elif isinstance(s,int): r = ((s >> 16) & 0xFF) / 256 g = ((s >> 8) & 0xFF) / 256 b = ((s >> 0) & 0xFF) / 256 else: raise RuntimeError('Hex colour must be string or integer') return rgb(int(r*256),int(g*256),int(b*256)) # -------------------------------------------------------------------- # Colour encoding def rgb(r,g,b): '''Encode RGB colour Parameters ---------- r : int Red channel g : int Green channel b : int Blue channel Returns ------- colour : str RGB colour as a string ''' return ','.join([str(r),str(g),str(b)]) # -------------------------------------------------------------------- def rgba(r,g,b,a): '''Encode RGBA colour Parameters ---------- r : int Red channel g : int Green channel b : int Blue channel a : int Alpha channel Returns ------- colour : str RGBA colour as a string ''' return ','.join([str(r),str(g),str(b),str(a)]) # -------------------------------------------------------------------- def dumpTree(node,ind=''): '''Dump the tree of nodes Parameters ---------- node : xml.dom.Node Node to dump ind : str Current indent ''' print(f'{ind}{node}') for c in node.childNodes: dumpTree(c,ind+' ') # -------------------------------------------------------------------- def registerElement(cls): Element.known_tags[cls.TAG] = cls # # EOF # # ==================================================================== # From element.py # ==================================================================== class Element: BUILD = 'VASSAL.build.' MODULE = BUILD + 'module.' WIDGET = BUILD + 'widget.' MAP = MODULE + 'map.' PICKER = MAP + 'boardPicker.' BOARD = PICKER + 'board.' known_tags = {} def __init__(self,parent,tag,node=None,**kwargs): '''Create a new element Parameters ---------- parent : Element Parent element to add this element to tag : str Element tag node : xml.dom.Node If not None, then read attributes from that. Otherwise set elements according to kwargs kwargs : dict Attribute keys and values. Only used if node is None ''' if parent is not None: self._root = parent._root self._node = (node if node is not None else parent.addNode(tag,**kwargs)) else: self._root = None self._node = None # ---------------------------------------------------------------- # Attributes def __contains__(self,key): '''Check if element has attribute key''' return self.hasAttribute(key) def __getitem__(self,key): '''Get attribute key value''' return self.getAttribute(key) def __setitem__(self,key,value): '''Set attribute key value''' self.setAttribute(key,value) def hasAttribute(self,k): '''Check if element has attribute ''' return self._node.hasAttribute(k) def getAttribute(self,k): '''Get attribute key value''' return self._node.getAttribute(k) def setAttribute(self,k,v): '''Set attribute key value''' self._node.setAttribute(k,str(v).lower() if isinstance(v,bool) else str(v)) def setAttributes(self,**kwargs): '''Set attributes to dictionary key and value''' for k,v in kwargs.items(): self.setAttribute(k,v) def getAttributes(self): '''Get attributes as dict''' return self._node.attributes # ---------------------------------------------------------------- # Plain nodes def getChildren(self): '''Get child nodes (xml.dom.Node)''' return self._node.childNodes # ---------------------------------------------------------------- # Getters # # First generics def getAsDict(self,tag='',key=None,enable=True): '''Get elements with a specific tag as a dictionary where the key is given by attribute key''' cont = self._node.getElementsByTagName(tag) if not enable or key is None: return cont return {e.getAttribute(key): e for e in cont} def getAsOne(self,tag='',single=True): '''Get elements with a specific tag, as a list. If single is true, then assume we only have one such child element, or fail.''' cont = self._node.getElementsByTagName(tag) if single and len(cont) != 1: return None return cont def getElementsByKey(self,cls,key='',asdict=True): '''Get elments of a specific class as a dictionary, where the key is set by the key attribute.''' cont = self.getAsDict(cls.TAG,key,asdict) if cont is None: return None if not asdict: return [cls(self,node=n) for n in cont] return {k : cls(self,node=n) for k, n in cont.items()} def getAllElements(self,cls,single=True): '''Get elements with a specific tag, as a list. If single is true, then assume we only have one such child element, or fail. ''' cont = self.getAsOne(cls.TAG,single=single) if cont is None: return None return [cls(self,node=n) for n in cont] def getSpecificElements(self,cls,key,*names,asdict=True): '''Get all elements of specific class and that has the attribute key, and the attribute value is in names ''' cont = self.getAsOne(cls.TAG,single=False) cand = [cls(self,node=n) for n in cont if n.getAttribute(key) in names] if asdict: return {c[key] : c for c in cand} return cand def getParent(self,cls=None,checkTag=True): if self._node.parentNode is None: return None if cls is None: cls = self.getTagClass(self._node.parentNode.tagName) checkTag = False if cls is None: return None if checkTag and self._node.parentNode.tagName != cls.TAG: return None return cls(self,node=self._node.parentNode) def getParentOfClass(self,cls): '''Searches back until we find the parent with the right class, or none ''' try: iter(cls) except: cls = [cls] t = {c.TAG: c for c in cls} p = self._node.parentNode while p is not None: c = t.get(p.tagName,None) if c is not None: return c(self,node=p) p = p.parentNode return None def getTagClass(self,tag): '''Get class corresponding to the tag''' if tag not in self.known_tags: return None; return self.known_tags[tag] # ---------------------------------------------------------------- # Adders def addNode(self,tag,**attr): '''Add a note to this element Parameters ---------- tag : str Node tag name attr : dict Attributes to set ''' e = self._root.createElement(tag) if self._node: self._node.appendChild(e) for k, v in attr.items(): e.setAttribute(k,str(v).lower() if isinstance(v,bool) else str(v)) return e def addText(self,text): '''Add a text child node to an element''' t = self._root.createTextNode(text) self._node.appendChild(t) return t def getText(self): '''Get contained text node content''' if self._node.firstChild is None or \ self._node.firstChild.nodeType != self._node.firstChild.TEXT_NODE: return '' return self._node.firstChild.nodeValue def add(self,cls,**kwargs): '''Add an element and return wrapped in cls object''' return cls(self,node=None,**kwargs) def append(self,elem): '''Append and element''' if self._node.appendChild(elem._node): return elem return False # ---------------------------------------------------------------- def remove(self,elem): '''Remove an element''' try: self._node.removeChild(elem._node) except: return None return elem # ---------------------------------------------------------------- def insertBefore(self,toadd,ref): '''Insert an element before another element''' try: self._node.insertBefore(toadd._node,ref._node) except: return None return toadd # -------------------------------------------------------------------- class DummyElement(Element): def __init__(self,parent,node=None,**kwargs): '''A dummy element we can use to select elements of different classes ''' super(DummyElement,self).__init__(parent,'Dummy',node=node) # -------------------------------------------------------------------- class ToolbarElement(Element): def __init__(self, parent, tag, node = None, name = '', # Toolbar element name tooltip = '', # Tool tip text = '', # Button text icon = '', # Button icon, hotkey = '', # Named key or key stroke canDisable = False, propertyGate = '', disabledIcon = '', **kwargs): '''Base class for toolbar elements. Parameters ---------- parent : Element Parent element if any tag : str Element tag node : XMLNode Possible node - when reading back name : str Name of element (user reminder). If not set, and tooltip is set, set to tooltip toolttip : str Tool tip when hovering. If not set, and name is set, then use name as tooltip. text : str Text of button icon : str Image path for button image hotkey : str Named key or key-sequence canDisable : bool If true, then the element can be disabled propertyGate : str Name of a global property. When this property is `true`, then this element is _disabled_. Note that this _must_ be the name of a property - it cannot be a BeanShell expression. disabledIcon : str Path to image to use when the element is disabled. kwargs : dict Other attributes to set on the element ''' if name == '' and tooltip != '': name = tooltip if name != '' and tooltip == '': tooltip = name # Build arguments for super class args = { 'node': node, 'name': name, 'icon': icon, 'tooltip': tooltip, 'hotkey': hotkey, 'canDisable': canDisable, 'propertyGate': propertyGate, 'disabledIcon': disabledIcon } bt = kwargs.pop('buttonText',None) # If the element expects buttonText attribute, then do not set # the text attribute - some elements interpret that as a # legacy name attribute, if bt is not None: args['buttonText'] = bt else: args['text'] = text args.update(kwargs) super(ToolbarElement,self).__init__(parent, tag, **args) # print('Attributes\n','\n'.join([f'- {k}="{v}"' for k,v in self._node.attributes.items()])) # # EOF # # ==================================================================== # From globalkey.py # -------------------------------------------------------------------- class GlobalKey(ToolbarElement): SELECTED = 'MAP|false|MAP|||||0|0||true|Selected|true|EQUALS' def __init__(self, parent, tag, node = None, name = '', icon = '', tooltip = '', buttonHotkey = '', buttonText = '', canDisable = False, propertyGate = '', disabledIcon = '', # Local hotkey = '', deckCount = '-1', filter = '', reportFormat = '', reportSingle = False, singleMap = True, target = SELECTED): ''' Parameters ---------- - tag The XML tag to use - parent Parent node - node Optionally existing node - name Name of key - buttonHotkey Key in "global" scope - hotkey Key to send to targeted pieces - buttonText Text on button - canDisable If true, disabled when propertyGate is true - deckCount Number of decks (-1 is all) - filter Which units to target - propertyGate When true, disable - reportFormat Chat message - reportSingle Also show single piece reports - singleMap Only originating map if True - target Preselection filter (default selected pieces) - tooltip Hover-over message - icon Image to use as icon Default targets are selected units ''' super(GlobalKey,self).\ __init__(parent, tag, node = node, name = name, icon = icon, tooltip = tooltip, buttonHotkey = buttonHotkey, # This hot key buttonText = buttonText, canDisable = canDisable, propertyGate = propertyGate, disabledIcon = disabledIcon, hotkey = hotkey, # Target hot key deckCount = deckCount, filter = filter, reportFormat = reportFormat, reportSingle = reportSingle, singleMap = singleMap, target = target) # # EOF # # ==================================================================== # From gameelements.py # -------------------------------------------------------------------- class GameElementService: def getGame(self): return self.getParentOfClass(Game) # -------------------------------------------------------------------- class GameElement(Element,GameElementService): def __init__(self,game,tag,node=None,**kwargs): super(GameElement,self).__init__(game,tag,node=node,**kwargs) # -------------------------------------------------------------------- class Notes(ToolbarElement,GameElementService): TAG = Element.MODULE+'NotesWindow' def __init__(self,elem,node=None, name = 'Notes', # Toolbar element name tooltip = 'Show notes window', # Tool tip text = '', # Button text icon = '/images/notes.gif', # Button icon, hotkey = key('N',ALT), # Named key or key stroke canDisable = False, propertyGate = '', disabledIcon = '', description = ''): super(Notes,self).__init__(elem,self.TAG, node = node, name = name, tooltip = tooltip, text = text, icon = icon, hotkey = hotkey, canDisable = canDisable, propertyGate = propertyGate, disabledIcon = disabledIcon, description = description) def encode(self): return ['NOTES\t\\','PNOTES'] registerElement(Notes) # -------------------------------------------------------------------- class PredefinedSetup(GameElement): TAG = Element.MODULE+'PredefinedSetup' def __init__(self,elem,node=None, name = '', file = '', useFile = False, isMenu = False, description = ''): useFile = ((useFile or not isMenu) and (file is not None and len(file) > 0)) if file is None: file = '' super(PredefinedSetup,self).__init__(elem,self.TAG,node=node, name = name, file = file, useFile = useFile, isMenu = isMenu, description = description) def addPredefinedSetup(self,**kwargs): '''Add a `PredefinedSetup` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : PredefinedSetup The added element ''' return self.add(PredefinedSetup,**kwargs) def getPredefinedSetups(self,asdict=True): '''Get all PredefinedSetup element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `PredefinedSetup` elements. If `False`, return a list of all PredefinedSetup` children. Returns ------- children : dict or list Dictionary or list of `PredefinedSetup` children ''' return self.getElementsByKey(PredefinedSetup,'name',asdict) registerElement(PredefinedSetup) # -------------------------------------------------------------------- class GlobalTranslatableMessages(GameElement): TAG=Element.MODULE+'properties.GlobalTranslatableMessages' def __init__(self,elem,node=None): '''Translations Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from ''' super(GlobalTranslatableMessages,self).\ __init__(elem,self.TAG,node=node) registerElement(GlobalTranslatableMessages) # -------------------------------------------------------------------- class Language(GameElement): TAG = 'VASSAL.i18n.Language' def __init__(self,elem,node=None,**kwargs): super(Languate,self).__init__(sele,self.TAG,node=none,**kwargs) registerElement(Language) # -------------------------------------------------------------------- class Chatter(GameElement): TAG=Element.MODULE+'Chatter' def __init__(self,elem,node=None,**kwargs): '''Chat Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from kwargs : dict Attributes ''' super(Chatter,self).__init__(elem,self.TAG,node=node,**kwargs) registerElement(Chatter) # -------------------------------------------------------------------- class KeyNamer(GameElement): TAG=Element.MODULE+'KeyNamer' def __init__(self,elem,node=None,**kwargs): '''Key namer (or help menu) Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from kwargs : dict Attributes ''' super(KeyNamer,self).__init__(elem,self.TAG,node=node,**kwargs) registerElement(KeyNamer) # -------------------------------------------------------------------- # # # # # # # # # class GlobalOptions(GameElement): NEVER = 'Never' ALWAYS = 'Always' PROMPT = 'Use Preferences Setting' TAG = Element.MODULE+'GlobalOptions' def __init__(self,doc,node=None, autoReport = PROMPT, centerOnMove = PROMPT, chatterHTMLSupport = ALWAYS, hotKeysOnClosedWindows = NEVER, inventoryForAll = ALWAYS, nonOwnerUnmaskable = PROMPT, playerIdFormat = "$playerName$", promptString = "Opponents can unmask pieces", sendToLocationMoveTrails = NEVER, storeLeadingZeroIntegersAsStrings = False, description = 'Global options', dragThreshold = 10): '''Set global options on the module Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from autoReport : str='always' centerOnMove : str Option chatterHTMLSupport : str='never' hotKeysOnClosedWindows : str='never' inventoryForAll : str='always' nonOwnerUnmaskable : str='never' playerIdFormat : str='$PlayerName$' promptString : str=? sendToLocationMoveTrails : bool=false storeLeadingZeroIntegersAsStrings : bool=False ''' super(GlobalOptions,self).\ __init__(doc,self.TAG,node=node, autoReport = autoReport, centerOnMove = centerOnMove, chatterHTMLSupport = chatterHTMLSupport, hotKeysOnClosedWindows = hotKeysOnClosedWindows, inventoryForAll = inventoryForAll, nonOwnerUnmaskable = nonOwnerUnmaskable, playerIdFormat = playerIdFormat, promptString = promptString, sendToLocationMoveTrails = sendToLocationMoveTrails, storeLeadingZeroIntegersAsStrings = storeLeadingZeroIntegersAsStrings, dragThreshold = dragThreshold, description = description) def addOption(self,**kwargs): '''Add a `Option` element to this Options known - stepIcon - image file name (/images/StepForward16.gif) - stepHotKey - key - undoIcon - image file name (/images/Undo16.gif) - undoHotKey - key - serverControlsIcon - image file name (/images/connect.gif) - serverControlsHotKey - key - debugControlsIcon - image file name - debugControlsHotKey - key Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Option The added element ''' return self.add(Option,**kwargs) def getOptions(self): return self.getElementsByKey(Option,'name') def addPreference(self,cls,**kwargs): return self.add(cls,**kwargs) def addIntPreference(self,**kwargs): return self.add(IntPreference,**kwargs) def addFloatPreference(self,**kwargs): return self.add(FloatPreference,**kwargs) def addBoolPreference(self,**kwargs): return self.add(BoolPreference,**kwargs) def addStrPreference(self,**kwargs): return self.add(StrPreference,**kwargs) def addTextPreference(self,**kwargs): return self.add(TextPreference,**kwargs) def addEnumPreference(self,**kwargs): return self.add(EnumPreference,**kwargs) def getIntPreferences(self): return self.getElementsByKey(IntPreference,'name') def getFloatPreferences(self): return self.getElementsByKey(FloatPreference,'name') def getBoolPreferences(self): return self.getElementsByKey(BoolPreference,'name') def getStrPreferences(self): return self.getElementsByKey(StrPreference,'name') def getTextPreferences(self): return self.getElementsByKey(TextPreference,'name') def getEnumPreferences(self): return self.getElementsByKey(EnumPreference,'name') def getPreferences(self): retd = {} for cls in [IntPreference, FloatPreference, BoolPreference, StrPreference, TextPreference, EnumPreference]: retd.update(self.getElementsByKey(cls,'name')) return retd registerElement(GlobalOptions) # -------------------------------------------------------------------- class Option(Element): TAG = 'option' def __init__(self,doc,node=None,name='',value=''): super(Option,self).__init__(doc,tag=self.TAG,node=node,name=name) self.addText(value) def getGlobalOptions(self): return self.getParent(GlobalOptions) registerElement(Option) # -------------------------------------------------------------------- class Preference(Element): PREFS = 'VASSAL.preferences.' def __init__(self, doc, tag, node = None, name = '', default = '', desc = '', tab = '', **kwargs): '''Add a preference Parameters ---------- name : str Name of property default : str Default value desc : str Description tab : str Preference tab to put in to ''' super(Preference,self).__init__(doc, tag = tag, node = node, name = name, default = default, desc = desc, tab = tab) def getGlobalOptions(self): return self.getParent(GlobalOptions) # -------------------------------------------------------------------- class IntPreference(Preference): TAG = Preference.PREFS+'IntegerPreference' def __init__(self, doc, node = None, name = '', default = 0, desc = '', tab = ''): super(IntPreference,self).__init__(doc, tag = self.TAG, node = node, name = name, default = str(default), desc = desc, tab = tab) registerElement(IntPreference) # -------------------------------------------------------------------- class FloatPreference(Preference): TAG = Preference.PREFS+'DoublePreference' def __init__(self, doc, node = None, name = '', default = 0., desc = '', tab = ''): super(FloatPreference,self).__init__(doc, tag = self.TAG, node = node, name = name, default = str(default), desc = desc, tab = tab) registerElement(FloatPreference) # -------------------------------------------------------------------- class BoolPreference(Preference): TAG = Preference.PREFS+'BooleanPreference' def __init__(self, doc, node = None, name = '', default = False, desc = '', tab = ''): super(BoolPreference,self).__init__(doc, tag = self.TAG, node = node, name = name, default = ('true' if default else 'false'), desc = desc, tab = tab) registerElement(BoolPreference) # -------------------------------------------------------------------- class StrPreference(Preference): TAG = Preference.PREFS+'StringPreference' def __init__(self, doc, node = None, name = '', default = '', desc = '', tab = ''): super(StrPreference,self).__init__(doc, tag = self.TAG, node = node, name = name, default = default, desc = desc, tab = tab) registerElement(StrPreference) # -------------------------------------------------------------------- class TextPreference(Preference): TAG = Preference.PREFS+'TextPreference' def __init__(self, doc, node = None, name = '', default = '', desc = '', tab = ''): super(TextPreference,self).__init__(doc, tag = self.TAG, node = node, name = name, default = (default .replace('\n',' ')), desc = desc, tab = tab) registerElement(TextPreference) # -------------------------------------------------------------------- class EnumPreference(Preference): TAG = Preference.PREFS+'EnumPreference' def __init__(self, doc, node = None, name = '', values = [], default = '', desc = '', tab = ''): ce = lambda v : str(v).replace(',',r'\,') sl = [ce(v) for v in values] df = ce(v) assert df in sl, \ f'Default value "{default}" not in list {":".join(values)}' super(EnumPreference,self).__init__(doc, tag = self.TAG, node = node, name = name, default = df, desc = desc, tab = tab, list = sl) registerElement(EnumPreference) # -------------------------------------------------------------------- # CurrentMap == "Board" class Inventory(ToolbarElement,GameElementService): TAG = Element.MODULE+'Inventory' def __init__(self,doc,node=None, name = '', icon = '/images/inventory.gif', text = '', tooltip = 'Show inventory of all pieces', hotkey = key('I',ALT), canDisable = False, propertyGate = '', disabledIcon = '', centerOnPiece = True, drawPieces = True, foldersOnly = False, forwardKeystroke = True, groupBy = '', include = '{}', launchFunction = 'functionHide', leafFormat = '$PieceName$', nonLeafFormat = '$PropertyValue$', pieceZoom = '0.33', pieceZoom2 = '0.5', pieceZoom3 = '0.6', refreshHotkey = key('I',ALT_SHIFT), showMenu = True, sides = '', sortFormat = '$PieceName$', sortPieces = True, sorting = 'alpha', zoomOn = False): super(Inventory,self).__init__(doc,self.TAG,node=node, canDisable = canDisable, centerOnPiece = centerOnPiece, disabledIcon = disabledIcon, drawPieces = drawPieces, foldersOnly = foldersOnly, forwardKeystroke = forwardKeystroke, groupBy = groupBy, hotkey = hotkey, icon = icon, include = include, launchFunction = launchFunction, leafFormat = leafFormat, name = name, nonLeafFormat = nonLeafFormat, pieceZoom = pieceZoom, pieceZoom2 = pieceZoom2, pieceZoom3 = pieceZoom3, propertyGate = propertyGate, refreshHotkey = refreshHotkey, showMenu = showMenu, sides = sides, sortFormat = sortFormat, sortPieces = sortPieces, sorting = sorting, text = text, tooltip = tooltip, zoomOn = zoomOn) registerElement(Inventory) # -------------------------------------------------------------------- class Prototypes(GameElement): TAG = Element.MODULE+'PrototypesContainer' def __init__(self,game,node=None,**kwargs): super(Prototypes,self).\ __init__(game,self.TAG,node=node,**kwargs) def addPrototype(self,**kwargs): '''Add a `Prototype` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Prototype The added element ''' return self.add(Prototype,**kwargs) def getPrototypes(self,asdict=True): '''Get all Prototype element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Prototype` elements. If `False`, return a list of all Prototype` children. Returns ------- children : dict or list Dictionary or list of `Prototype` children ''' return self.getElementsByKey(Prototype,'name',asdict=asdict) registerElement(Prototypes) # -------------------------------------------------------------------- class DiceButton(ToolbarElement,GameElementService): TAG=Element.MODULE+'DiceButton' def __init__(self,elem,node=None, name = '1d6', tooltip = 'Roll a 1d6', text = '1d6', icon = '/images/die.gif', hotkey = key('6',ALT), canDisable = False, propertyGate = '', disabledIcon = '', addToTotal = 0, keepCount = 1, keepDice = False, keepOption = '>', lockAdd = False, lockDice = False, lockPlus = False, lockSides = False, nDice = 1, nSides = 6, plus = 0, prompt = False, reportFormat = '$name$ = $result$', reportTotal = False, sortDice = False): super(DiceButton,self).\ __init__(elem,self.TAG,node=node, addToTotal = addToTotal, canDisable = canDisable, disabledIcon = disabledIcon, hotkey = hotkey, icon = icon, keepCount = keepCount, keepDice = keepDice, keepOption = keepOption, lockAdd = lockAdd, lockDice = lockDice, lockPlus = lockPlus, lockSides = lockSides, nDice = nDice, nSides = nSides, name = name, plus = plus, prompt = prompt, propertyGate = propertyGate, reportFormat = reportFormat, reportTotal = reportTotal, sortDice = sortDice, text = text, tooltip = tooltip) registerElement(DiceButton) # -------------------------------------------------------------------- class GameMassKey(GlobalKey,GameElementService): TAG = Element.MODULE+'GlobalKeyCommand' def __init__(self,map,node=None, name = '', buttonText = '', tooltip = '', icon = '', canDisable = False, propertyGate = '', disabledIcon = '', buttonHotkey = '', hotkey = '', deckCount = '-1', filter = '', reportFormat = '', reportSingle = False, singleMap = True, target = GlobalKey.SELECTED): '''Default targets are selected units''' super(GameMassKey,self).\ __init__(map, self.TAG, node = node, name = name, buttonHotkey = buttonHotkey, # This hot key hotkey = hotkey, # Target hot key buttonText = buttonText, canDisable = canDisable, deckCount = deckCount, filter = filter, propertyGate = propertyGate, reportFormat = reportFormat, reportSingle = reportSingle, singleMap = singleMap, target = target, tooltip = tooltip, icon = icon) registerElement(GameMassKey) # -------------------------------------------------------------------- class StartupMassKey(GlobalKey,GameElementService): TAG = Element.MODULE+'StartupGlobalKeyCommand' FIRST_LAUNCH = 'firstLaunchOfSession' EVERY_LAUNCH = 'everyLaunchOfSession' START_GAME = 'startOfGameOnly' def __init__(self, map, node = None, name = '', buttonHotkey = '', hotkey = '', buttonText = '', canDisable = False, deckCount = '-1', filter = '', propertyGate = '', reportFormat = '', reportSingle = False, singleMap = True, target = GlobalKey.SELECTED, tooltip = '', icon = '', whenToApply = EVERY_LAUNCH): '''Default targets are selected units''' super(StartupMassKey,self).\ __init__(map, self.TAG, node = node, name = name, buttonHotkey = buttonHotkey, # This hot key hotkey = hotkey, # Target hot key buttonText = buttonText, canDisable = canDisable, deckCount = deckCount, filter = filter, propertyGate = propertyGate, reportFormat = reportFormat, reportSingle = reportSingle, singleMap = singleMap, target = target, tooltip = tooltip, icon = icon) if node is None: self['whenToApply'] = whenToApply registerElement(StartupMassKey) # -------------------------------------------------------------------- class Menu(GameElement): TAG = Element.MODULE+'ToolbarMenu' def __init__(self, game, node = None, name = '', tooltip = '', text = '', # Menu name canDisable = False, propertyGate = '', disabledIcon = '', description = '', hotkey = '', icon = '', menuItems = []): if len(description) <= 0 and len(tooltip) > 0: description = tooltip if len(tooltip) <= 0 and len(description) > 0: tooltip = description super(Menu,self).\ __init__(game, self.TAG, node = node, name = name, canDisable = canDisable, description = description, disabledIcon = disabledIcon, hotkey = hotkey, icon = icon, menuItems = ','.join(menuItems), propertyGate = propertyGate, text = text, tooltip = tooltip) registerElement(Menu) # -------------------------------------------------------------------- class SymbolicDice(GameElement): TAG = Element.MODULE+'SpecialDiceButton' def __init__(self, game, node = None, canDisable = False, disabledIcon = '', hotkey = key('6',ALT), name = "Dice", # GP prefix text = '', # Text on button icon = '/images/die.gif', # Icon on button format = '{name+": "+result1}', # Report tooltip = 'Die roll', # Help propertyGate = '', # Property to disable when T resultButton = False, # Result on button? resultChatter = True, # Result in Chatter? resultWindow = False, # Result window? backgroundColor = rgb(0xdd,0xdd,0xdd), # Window background windowTitleResultFormat = "$name$", # Window title windowX = '67', # Window size windowY = '65', doHotkey = False, doLoop = False, doReport = False, doSound = False, hideWhenDisabled = False, hotkeys = '', index = False, indexProperty = '', indexStart = 1, indexStep = 1, loopCount = 1, loopType = 'counted', postLoopKey = '', reportFormat = '', soundClip = '', untilExpression = '', whileExpression = '' ): super(SymbolicDice,self).\ __init__(game, self.TAG, node = node, canDisable = canDisable, disabledIcon = disabledIcon, hotkey = hotkey, name = name, text = text, icon = icon, format = format, tooltip = tooltip, propertyGate = propertyGate, resultButton = resultButton, resultChatter = resultChatter, resultWindow = resultWindow, backgroundColor = backgroundColor, windowTitleResultFormat = windowTitleResultFormat, windowX = windowX, windowY = windowY, doHotkey = doHotkey, doLoop = doLoop, doReport = doReport, doSound = doSound, hideWhenDisabled = hideWhenDisabled, hotkeys = hotkeys, index = index, indexProperty = indexProperty, indexStart = indexStart, indexStep = indexStep, loopCount = loopCount, loopType = loopType, postLoopKey = postLoopKey, reportFormat = reportFormat, soundClip = soundClip, untilExpression = untilExpression, whileExpression = whileExpression) def addDie(self,**kwargs): return self.add(SpecialDie,**kwargs) def getSymbolicDice(self): return self.getParent(SymbolicDice) registerElement(SymbolicDice) # -------------------------------------------------------------------- class SpecialDie(GameElement): TAG = Element.MODULE+'SpecialDie' def __init__(self, symbolic, # Symblic dice node = None, name = '', # Name of dice (no GP) report = '{name+": "+result}', faces = None): super(SpecialDie,self).\ __init__(symbolic, self.TAG, node = node, name = name, report = report) if node is not None or faces is None: return if isinstance(faces,list): faces = {i+1: f for i,f in enumerate(faces)} for v,f in faces: self.addFace(text = str(v), value = v, icon = f) def addFace(self,**kwargs): self.add(DieFace,**kwargs) def getSymbolicDice(self): return self.getParent(SymbolicDice) def getFaces(self): return self.getAllElements(DieFace,single=False) registerElement(SpecialDie) # -------------------------------------------------------------------- class DieFace(GameElement): TAG = Element.MODULE+'SpecialDieFace' def __init__(self, special, # Special dice node, # existing node icon = '', # graphical representation text = '', # Text representation value = 0): # Value representation super(DieFace,self).\ __init__(special, self.TAG, node = node, icon = icon, text = text, value = value) def getSpecialDie(self): return self.getParent(SpecialDie) registerElement(DieFace) # # EOF # # ==================================================================== # From mapelements.py # -------------------------------------------------------------------- class MapElementService: def getMap(self): '''Get map - either a Map or WidgetMap''' return self.getParentOfClass([WidgetMap,Map]) # if self._parent is None: # return None # # if 'WidgetMap' in self._parent.tagName: # return self.getParent(WidgetMap) # # return self.getParent(Map) def getGame(self): m = self.getMap() if m is not None: return m.getGame() return None # -------------------------------------------------------------------- class MapElement(Element,MapElementService): def __init__(self,map,tag,node=None,**kwargs): super(MapElement,self).__init__(map,tag,node=node,**kwargs) # -------------------------------------------------------------------- class PieceLayers(MapElement): TAG=Element.MAP+'LayeredPieceCollection' def __init__(self,map,node=None, property = 'PieceLayer', description = '', layerOrder = []): super(PieceLayers,self).__init__(map,self.TAG,node=node, property = property, description = description, layerOrder = ','.join(layerOrder)) def addControl(self,**kwargs): '''Add `LayerControl` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : LayerControl The added element ''' return self.add(LayerControl,**kwargs) def getControls(self,asdict=True): '''Get all `LayerControl` element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps name to `LayerControl` elements. If `False`, return a list of all `LayerControl` children. Returns ------- children : dict or list Dictionary or list of `LayerControl` children ''' return self.getElementsByKey(LayerControl,'name',asdict) registerElement(PieceLayers) # -------------------------------------------------------------------- class LayerControl(MapElement): TAG=Element.MAP+'LayerControl' CYCLE_UP='Rotate Layer Order Up' CYCLE_DOWN='Rotate Layer Order Down' ENABLE='Make Layer Active' DISABLE='Make Layer Inactive' TOGGLE='Switch Layer between Active and Inactive' RESET='Reset All Layers' def __init__(self,col,node=None, name = '', tooltip = '', text = '', hotkey = '', icon = '', canDisable = False, propertyGate = '', #Property name, disable when property false disabledIcon = '', command = TOGGLE, skip = False, layers = [], description = ''): super(LayerControl,self).__init__(col,self.TAG,node=node, name = name, tooltip = tooltip, text = text, buttonText = text, hotkey = hotkey, icon = icon, canDisable = canDisable, propertyGate = propertyGate, disabledIcon = disabledIcon, command = command, skip = skip, layers = ','.join(layers), description = description) def getLayers(self): '''Get map - either a Map or WidgetMap''' return self.getParentOfClass([PieceLayers]) registerElement(LayerControl) # -------------------------------------------------------------------- class LineOfSight(MapElement): TAG=Element.MAP+'LOS_Thread' ROUND_UP = 'Up' ROUND_DOWN = 'Down' ROUND_NEAREST = 'Nearest whole number' FROM_LOCATION = 'FromLocation' TO_LOCATION = 'ToLocation' CHECK_COUNT = 'NumberOfLocationsChecked' CHECK_LIST = 'AllLocationsChecked' RANGE = 'Range' NEVER = 'Never' ALWAYS = 'Always' WHEN_PERSISTENT = 'When persistent' CTRL_CLICK = 'Cltr-Click & Drag' def __init__(self,map, node=None, threadName = 'LOS', hotkey = key('L',ALT), tooltip = 'Trace line of sight', iconName = '/images/thread.gif', #'los-icon.png', label = '', snapLOS = False, snapStart = True, snapEnd = True, report = (f'{{"Range from "+{FROM_LOCATION}' f'+" to "+{TO_LOCATION}+" is "' f'+{RANGE}+" (via "+{CHECK_LIST}+")"}}'), persistent = CTRL_CLICK, persistentIconName = '/images/thread.gif', globl = ALWAYS, losThickness = 3, threadColor = rgb(255,0,0), drawRange = True, # rangeBg = rgb(255,255,255), # rangeFg = rgb(0,0,0), rangeScale = 0, hideCounters = True, hideOpacity = 50, round = ROUND_UP, canDisable = False, propertyGate = '', disabledIcon = ''): '''Make Line of Sight interface Parameters ---------- threadName : str Name of interface hotkey : str Start LOS key tooltip : str Tool tip text iconName : str Path to button icon label : str Button text snapLOS : bool Wether to snap both ends snapStart : bool Snap to start snapEnd: bool Snap to end report : str Report format persistent : str When persistent persistentIconName : str Icon when persistent(?) globl : str Visisble to opponents losThickness : int Thickness in pixels losColor : str Colour of line drawRange : bool Draw the range next to LOST thread rangeBg : str Range backgroung colour rangeFg : str Range foregrond colour rangeScale : int Scale of range - pixels per unit round : str How to round range hideCounters :bool If true, hide counters while making thread hideOpacity : int Opacity of hidden counters (percent) canDisable : bool IF true, then can be hidden propertyGate : str Name of property. When that property is TRUE, then the interface is disabled. Must be a property name, not an expression. disabledIcon : str Icon to use when disabled ''' super(LineOfSight,self).__init__(map,self.TAG, node = node, threadName = threadName, hotkey = hotkey, tooltip = tooltip, iconName = iconName, label = label, snapLOS = snapLOS, snapStart = snapStart, snapEnd = snapEnd, report = report, persistent = persistent, persistentIconName = persistentIconName, losThickness = losThickness, threadColor = threadColor, drawRange = drawRange, #rangeBg = rangeBg, #rangeFg = rangeFg, rangeScale = rangeScale, hideCounters = hideCounters, hideOpacity = hideOpacity, round = round, canDisable = canDisable, propertyGate = propertyGate, disabledIcon = disabledIcon) self.setAttribute('global',globl) registerElement(LineOfSight) # -------------------------------------------------------------------- class StackMetrics(MapElement): TAG=Element.MAP+'StackMetrics' def __init__(self,map,node=None, bottom = key('(',0), down = key('%',0), top = key('&',0), up = key("'",0), disabled = False, exSepX = 6, # Expanded (after double click) exSepY = 18, # Expanded (after double click) unexSepX = 8, # Compact unexSepY = 16): # Compact super(StackMetrics,self).__init__(map,self.TAG,node=node, bottom = bottom, disabled = disabled, down = down, exSepX = exSepX, exSepY = exSepY, top = top, unexSepX = unexSepX, unexSepY = unexSepY, up = up) registerElement(StackMetrics) # -------------------------------------------------------------------- class ImageSaver(MapElement): TAG=Element.MAP+'ImageSaver' def __init__(self,map,node=None, buttonText = '', canDisable = False, hotkey = '', icon = '/images/camera.gif', propertyGate = '', tooltip = 'Save map as PNG image'): super(ImageSaver,self).__init__(map,self.TAG,node=node, buttonText = buttonText, canDisable = canDisable, hotkey = hotkey, icon = icon, propertyGate = propertyGate, tooltip = tooltip) registerElement(ImageSaver) # -------------------------------------------------------------------- class TextSaver(MapElement): TAG=Element.MAP+'TextSaver' def __init__(self,map,node=None, buttonText = '', canDisable = False, hotkey = '', icon = '/images/camera.gif', propertyGate = '', tooltip = 'Save map as text'): super(TextSaver,self).__init__(map,self.TAG,node=node, buttonText = buttonText, canDisable = canDisable, hotkey = hotkey, icon = icon, propertyGate = propertyGate, tooltip = tooltip) registerElement(TextSaver) # -------------------------------------------------------------------- class ForwardToChatter(MapElement): TAG=Element.MAP+'ForwardToChatter' def __init__(self,map,node=None,**kwargs): super(ForwardToChatter,self).__init__(map,self.TAG,node=node,**kwargs) registerElement(ForwardToChatter) # -------------------------------------------------------------------- class MenuDisplayer(MapElement): TAG=Element.MAP+'MenuDisplayer' def __init__(self,map,node=None,**kwargs): super(MenuDisplayer,self).__init__(map,self.TAG,node=node,**kwargs) registerElement(MenuDisplayer) # -------------------------------------------------------------------- class MapCenterer(MapElement): TAG=Element.MAP+'MapCenterer' def __init__(self,map,node=None,**kwargs): super(MapCenterer,self).__init__(map,self.TAG,node=node,**kwargs) registerElement(MapCenterer) # -------------------------------------------------------------------- class StackExpander(MapElement): TAG=Element.MAP+'StackExpander' def __init__(self,map,node=None,**kwargs): super(StackExpander,self).__init__(map,self.TAG,node=node,**kwargs) registerElement(StackExpander) # -------------------------------------------------------------------- class PieceMover(MapElement): TAG=Element.MAP+'PieceMover' def __init__(self,map,node=None,**kwargs): super(PieceMover,self).__init__(map,self.TAG,node=node,**kwargs) registerElement(PieceMover) # -------------------------------------------------------------------- class SelectionHighlighters(MapElement): TAG=Element.MAP+'SelectionHighlighters' def __init__(self,map,node=None,**kwargs): super(SelectionHighlighters,self).\ __init__(map,self.TAG,node=node,**kwargs) registerElement(SelectionHighlighters) # -------------------------------------------------------------------- class KeyBufferer(MapElement): TAG=Element.MAP+'KeyBufferer' def __init__(self,map,node=None,**kwargs): super(KeyBufferer,self).__init__(map,self.TAG,node=node,**kwargs) registerElement(KeyBufferer) # -------------------------------------------------------------------- class HighlightLastMoved(MapElement): TAG=Element.MAP+'HighlightLastMoved' def __init__(self,map,node=None, color = rgb(255,0,0), enabled = True, thickness = 2): super(HighlightLastMoved,self).__init__(map,self.TAG,node=node, color = color, enabled = enabled, thickness = thickness) registerElement(HighlightLastMoved) # -------------------------------------------------------------------- class CounterDetailViewer(MapElement): TAG=Element.MAP+'CounterDetailViewer' TOP_LAYER = 'from top-most layer only' ALL_LAYERS = 'from all layers' INC_LAYERS = 'from listed layers only' EXC_LAYERS = 'from layers other than those listed' FILTER = 'by using a property filter' def __init__(self,map,node=None, borderWidth = 0, centerAll = False, centerText = False, combineCounterSummary = False, counterReportFormat = '', delay = 700, description = '', display = TOP_LAYER, emptyHexReportForma = '$LocationName$', enableHTML = True, extraTextPadding = 0, bgColor = None, fgColor = rgb(0,0,0), fontSize = 11, graphicsZoom = 1.0,# Zoom on counters hotkey = key('\n'), layerList = '', minDisplayPieces = 2, propertyFilter = '', showDeck = False, showDeckDepth = 1, showDeckMasked = False, showMoveSelectde = False, showNoStack = False, showNonMovable = False, showOverlap = False, showgraph = True, showgraphsingle = False, showtext = True, showtextsingle = False, stretchWidthSummary = False, summaryReportFormat = '$LocationName$', unrotatePieces = False, version = 3, verticalOffset = 2, verticalTopText = 0, zoomlevel = 1.0, stopAfterShowing = False): # showTerrain attributes bg = '' if bgColor is None else bgColor fg = '' if fgColor is None else fgColor super(CounterDetailViewer,self)\ .__init__(map,self.TAG,node=node, borderWidth = borderWidth, centerAll = centerAll, centerText = centerText, combineCounterSummary = combineCounterSummary, counterReportFormat = counterReportFormat, delay = delay, description = description, display = display, emptyHexReportForma = emptyHexReportForma, enableHTML = enableHTML, extraTextPadding = extraTextPadding, bgColor = bg, fgColor = fg, fontSize = fontSize, graphicsZoom = graphicsZoom, hotkey = hotkey, layerList = layerList, minDisplayPieces = minDisplayPieces, propertyFilter = propertyFilter, showDeck = showDeck, showDeckDepth = showDeckDepth, showDeckMasked = showDeckMasked, showMoveSelectde = showMoveSelectde, showNoStack = showNoStack, showNonMovable = showNonMovable, showOverlap = showOverlap, showgraph = showgraph, showgraphsingle = showgraphsingle, showtext = showtext, showtextsingle = showtextsingle, stretchWidthSummary = stretchWidthSummary, summaryReportFormat = summaryReportFormat, unrotatePieces = unrotatePieces, version = version, verticalOffset = verticalOffset, verticalTopText = verticalTopText, zoomlevel = zoomlevel, stopAfterShowing = stopAfterShowing) registerElement(CounterDetailViewer) # -------------------------------------------------------------------- class GlobalMap(MapElement): TAG=Element.MAP+'GlobalMap' def __init__(self,map,node=None, buttonText = '', color = rgb(255,0,0), hotkey = key('O',CTRL_SHIFT), icon = '/images/overview.gif', scale = 0.2, tooltip = 'Show/Hide overview window'): super(GlobalMap,self).\ __init__(map,self.TAG,node=node, buttonText = buttonText, color = color, hotkey = hotkey, icon = icon, scale = scale, tooltip = 'Show/Hide overview window') registerElement(GlobalMap) # -------------------------------------------------------------------- class Zoomer(MapElement): TAG = Element.MAP+'Zoomer' def __init__(self,map,node=None, inButtonText = '', inIconName = '/images/zoomIn.gif', inTooltip = 'Zoom in', outButtonText = '', outIconName = '/images/zoomOut.gif', outTooltip = 'Zoom out', pickButtonText = '', pickIconName = '/images/zoom.png', pickTooltip = 'Select Zoom', zoomInKey = key('=',CTRL_SHIFT), zoomLevels = [0.2,0.25,0.333,0.4,0.5, 0.555,0.625,0.75,1.0,1.25,1.6], zoomOutKey = key('-'), zoomPickKey = key('='), zoomStart = 3): '''Zoom start is counting from the back (with default zoom levels, and zoom start, the default zoom is 1''' lvls = ','.join([str(z) for z in zoomLevels]) super(Zoomer,self).\ __init__(map,self.TAG,node=node, inButtonText = inButtonText, inIconName = inIconName, inTooltip = inTooltip, outButtonText = outButtonText, outIconName = outIconName, outTooltip = outTooltip, pickButtonText = pickButtonText, pickIconName = pickIconName, pickTooltip = pickTooltip, zoomInKey = zoomInKey, zoomLevels = lvls, zoomOutKey = zoomOutKey, zoomPickKey = zoomPickKey, zoomStart = zoomStart) registerElement(Zoomer) # -------------------------------------------------------------------- class HidePiecesButton(MapElement): TAG=Element.MAP+'HidePiecesButton' def __init__(self,map,node=None, buttonText = '', hiddenIcon = '/images/globe_selected.gif', hotkey = key('O'), showingIcon = '/images/globe_unselected.gif', tooltip = 'Hide all pieces on this map'): super(HidePiecesButton,self).\ __init__(map,self.TAG,node=node, buttonText = buttonText, hiddenIcon = hiddenIcon, hotkey = hotkey, showingIcon = showingIcon, tooltip = tooltip) registerElement(HidePiecesButton) # -------------------------------------------------------------------- class MassKey(GlobalKey,MapElementService): TAG = Element.MAP+'MassKeyCommand' def __init__(self,map,node=None, name = '', buttonHotkey = '', hotkey = '', buttonText = '', canDisable = False, deckCount = '-1', filter = '', propertyGate = '', reportFormat = '', reportSingle = False, singleMap = True, target = GlobalKey.SELECTED, tooltip = '', icon = ''): '''Default targets are selected units''' super(MassKey,self).\ __init__(map,self.TAG,node=node, name = name, buttonHotkey = buttonHotkey, # This hot key hotkey = hotkey, # Target hot key buttonText = buttonText, canDisable = canDisable, deckCount = deckCount, filter = filter, propertyGate = propertyGate, reportFormat = reportFormat, reportSingle = reportSingle, singleMap = singleMap, target = target, tooltip = tooltip, icon = icon) registerElement(MassKey) # -------------------------------------------------------------------- class Flare(MapElement): TAG=Element.MAP+'Flare' def __init__(self,map,node=None, circleColor = rgb(255,0,0), circleScale = True, circleSize = 100, flareKey = 'keyAlt', flareName = 'Map Flare', flarePulses = 6, flarePulsesPerSec = 3, reportFormat = ''): super(Flare,self).__init__(map,self.TAG,node=node, circleColor = circleColor, circleScale = circleScale, circleSize = circleSize, flareKey = flareKey, flareName = flareName, flarePulses = flarePulses, flarePulsesPerSec = flarePulsesPerSec, reportFormat = '') registerElement(Flare) # -------------------------------------------------------------------- class AtStart(MapElement): TAG = Element.MODULE+'map.SetupStack' def __init__(self,map, node = None, name = '', location = '', useGridLocation = True, owningBoard = '', x = 0, y = 0): '''Pieces are existing PieceSlot elements Parameters ---------- node : xml.minidom.Node Existing node or None name : str Name of node location : str Where the at-start element is put if `useGridLocation` useGridLocation : bool If true, use maps grid owningBoard : str Board that owns the at-start (can be empty) x : float Coordinate (ignored if `useGridLocation`) y : float Coordinate (ignored if `useGridLocation`) ''' super(AtStart,self).\ __init__(map,self.TAG,node=node, name = name, location = location, owningBoard = owningBoard, useGridLocation = useGridLocation, x = x, y = y) def addPieces(self,*pieces): '''Add a `Pieces` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Pieces The added element ''' # copy pieces here copies = [] for p in pieces: c = self.addPiece(p) if c is not None: copies.append(c) return copies def addPiece(self,piece): '''Add a `Piece` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Piece The added element ''' if not isinstance(piece,PieceSlot): print(f'Trying to add {type(piece)} to AtStart') return None p = piece.clone(self) # self._node.appendChild(p._node) return p def getPieces(self,asdict=True): '''Get all Piece element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Piece` elements. If `False`, return a list of all Piece` children. Returns ------- children : dict or list Dictionary or list of `Piece` children ''' return self.getElementsByKey(PieceSlot,'entryName',asdict) registerElement(AtStart) # # EOF # # ==================================================================== # From globalproperty.py # -------------------------------------------------------------------- class GlobalProperties(Element): TAG = Element.MODULE+'properties.GlobalProperties' def __init__(self,elem,node=None,**named): super(GlobalProperties,self).__init__(elem,self.TAG,node=node) for n, p in named: self.addProperty(n, **p) def getGame(self): return self.getParent(Game) def addProperty(self,**kwargs): '''Add a `Property` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Property The added element ''' return GlobalProperty(self,node=None,**kwargs) def getProperties(self): return getElementsByKey(GlobalProperty,'name') registerElement(GlobalProperties) # -------------------------------------------------------------------- class GlobalProperty(Element): TAG = Element.MODULE+'properties.GlobalProperty' def __init__(self,elem,node=None, name = '', initialValue = '', isNumeric = False, min = "null", max = "null", wrap = False, description = ""): super(GlobalProperty,self).__init__(elem,self.TAG, node = node, name = name, initialValue = initialValue, isNumeric = isNumeric, min = min, max = max, wrap = wrap, description = description) def getGlobalProperties(self): return self.getParent(GlobalProperties) registerElement(GlobalProperty) # # EOF # # ==================================================================== # From turn.py # -------------------------------------------------------------------- class TurnLevel(Element): def __init__(self,elem,tag,node=None,**kwargs): super(TurnLevel,self).__init__(elem,tag,node=node,**kwargs) def addLevel(self,counter=None,phases=None): '''Add a `Level` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Level The added element ''' if counter is None and phases is None: return self t = TurnCounter if counter is not None else TurnList o = counter if counter is not None else phases subcounter = o.pop('counter',None) subphases = o.pop('phases',None) s = t(self,node=None,**o) return s.addLevel(subcounter, subphases) def getUp(self): return self.getParent(TurnLevel) def addCounter(self,**kwargs): '''Add a `Counter` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Counter The added element ''' return self.add(self,TurnCounter,**kwargs) def addList(self,**kwargs): '''Add a `List` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : List The added element ''' return self.add(self,TurnList,**kwargs) def getCounter(self): return self.getAllElements(TurnCounter) def getList(self): return self.getAllElements(TurnList) # -------------------------------------------------------------------- class TurnTrack(TurnLevel): TAG = Element.MODULE+'turn.TurnTracker' def __init__(self,elem,node=None, name = '', buttonText = 'Turn', hotkey = '', icon = '', length = -1, lengthStyle = 'Maximum', nexthotkey = key('T',ALT), plusButtonSize = 22, prevhotkey = key('T',ALT_SHIFT), reportFormat = 'Turn updated from $oldTurn$ to $newTurn$', turnButtonHeight = 22, fwdOnly = True, turnFormat = None, counter = None, phases = None): levels = (counter if counter is not None else phases if phases is not None else None) if levels is not None: lvl = 1 lvls = [f'$level{lvl}$'] sub = levels while True: sub = sub.get('counter',sub.get('phases',None)) if sub is None: break lvl += 1 lvls.append(f'$level{lvl}$') turnFormat = ' '.join(lvls) if turnFormat is None: turnFormat = '$level1$ $level2$ $level3$ $level4$' super(TurnTrack,self).__init__(elem, self.TAG, node = node, name = name, buttonText = buttonText, hotkey = hotkey, icon = icon, length = length, lengthStyle = lengthStyle, nexthotkey = nexthotkey, plusButtonSize = plusButtonSize, prevhotkey = prevhotkey, reportFormat = reportFormat, turnButtonHeight = turnButtonHeight, turnFormat = turnFormat) self.addLevel(counter=counter, phases=phases) def getGame(self): return self.getParent(Game) def getLists(self,asdict=True): '''Get all List element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `List` elements. If `False`, return a list of all List` children. Returns ------- children : dict or list Dictionary or list of `List` children ''' return self.getElementsByKey(TurnList,'property',asdict=asdict) def getCounters(self,asdict=True): '''Get all Counter element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Counter` elements. If `False`, return a list of all Counter` children. Returns ------- children : dict or list Dictionary or list of `Counter` children ''' return self.getElementsByKey(TurnCounter,'property',asdict=asdict) def addHotkey(self,**kwargs): '''Add a `Hotkey` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Hotkey The added element ''' return self.add(TurnGlobalHotkey,**kwargs) def getHotkeys(self,asdict): return self.getElementsByKey(TurnGlobalHotkey,'name',asdict=asdict) def encode(self): ret = f'TURN{self["name"]}\t' return [] registerElement(TurnTrack) # -------------------------------------------------------------------- class TurnCounter(TurnLevel): TAG = Element.MODULE+"turn.CounterTurnLevel" def __init__(self,elem,node=None, property = '', start = 1, incr = 1, loop = False, loopLimit = -1, turnFormat = "$value$"): super(TurnCounter,self).__init__(elem,self.TAG,node=node, property = property, start = start, incr = incr, loop = loop, loopLimit = loopLimit, turnFormat = turnFormat) registerElement(TurnCounter) # -------------------------------------------------------------------- class TurnList(TurnLevel): TAG = Element.MODULE+"turn.ListTurnLevel" def __init__(self,elem,node=None, property = '', names = [], configFirst = False, configList = False, turnFormat = '$value$'): super(TurnList,self).\ __init__(elem,self.TAG,node=node, property = property, list = ','.join([str(p) for p in names]), configFirst = configFirst, configList = configList, turnFormat = turnFormat) registerElement(TurnList) # -------------------------------------------------------------------- class TurnGlobalHotkey(Element): TAG = Element.MODULE+'turn.TurnGlobalHotkey' def __init__(self,elem, node = None, hotkey = '', match = '{true}', reportFormat = '', name = ''): '''Global key activated by turn change Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from hotkey : str What to send (global command) match : str When to send reportFormat : str What to what name : str A free form name ''' super(TurnGlobalHotkey,self).__init__(elem,self.TAG, node = node, hotkey = hotkey, match = match, reportFormat = reportFormat, name = name) def getTurnTrack(self): '''Get the turn track''' return self.getParent(TurnTrack) registerElement(TurnGlobalHotkey) # # EOF # # ==================================================================== # From documentation.py # ==================================================================== def createKeyHelp(*args,**kwargs): '''Creates a help file with key-bindings See Documentation.createKeyHelp ''' return Documentation.createKeyHelp(*args,**kwargs) # -------------------------------------------------------------------- class Documentation(GameElement): TAG=Element.MODULE+'Documentation' def __init__(self,doc,node=None,**kwargs): '''Documentation (or help menu) Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from kwargs : dict Attributes ''' super(Documentation,self).__init__(doc,self.TAG,node=node,**kwargs) def addAboutScreen(self,**kwargs): '''Add a `AboutScreen` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : AboutScreen The added element ''' return self.add(AboutScreen,**kwargs) def addHelpFile(self,**kwargs): '''Add a `HelpFile` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : HelpFile The added element ''' return self.add(HelpFile,**kwargs) def addBrowserHelpFile(self,**kwargs): '''Add a `BrowserHelpFile` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : BrowserHelpFile The added element ''' return self.add(HelpBrowserFile,**kwargs) def addBrowserPDFFile(self,**kwargs): '''Add a `BrowserPDFFile` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : BrowserPDFFile The added element ''' return self.add(BrowserPDFFile,**kwargs) def addTutorial(self,**kwargs): '''Add a `Tutorial` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Tutorial The added element ''' return self.add(Tutorial,**kwargs) def getAboutScreens(self): return self.getElementsByKey(AboutScreen,'title') def getHelpFiles(self): return self.getElementsByKey(HelpFile,'title') def getBrowserHelpFiles(self): return self.getElementsByKey(BrowserHelpFile,'title') def getBrowserPDFFiles(self): return self.getElementsByKey(BrowserPDFFile,'title') def getTutorials(self): return self.getElementsByKey(Tutorial,'name') @classmethod def createKeyHelp(cls,keys,title='',version=''): '''Creates a help file with key-bindings Parameters ---------- keys : list of list of str List of key-binding documentation title : str Title of help file version : str Version number Returns ------- txt : str File content ''' txt = f'''

{title} (Version {version}) Key bindings

''' for key, where, description in keys: txt += (f'' f'' f'') txt += '''
KeyWhereEffect
{key}{where}{description}
''' return txt registerElement(Documentation) # -------------------------------------------------------------------- class AboutScreen(Element): TAG = Element.MODULE+'documentation.AboutScreen' def __init__(self,doc,node=None,title='',fileName=""): '''Create an about screen element that shows image Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from title : str Entry title fileName : str Internal file name ''' super(AboutScreen,self).__init__(doc, self.TAG, node = node, fileName = fileName, title = title) def getDocumentation(self): '''Get Parent element''' return self.getParent(Documentation) registerElement(AboutScreen) # -------------------------------------------------------------------- class BrowserPDFFile(Element): TAG = Element.MODULE+'documentation.BrowserPDFFile' def __init__(self,doc,node=None,title='',pdfFile=''): '''Create help menu item that opens an embedded PDF Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from title : str Entry title pdfFile : str Internal file name ''' super(BrowserPDFFile,self).__init__(doc,self.TAG, node = node, pdfFile = pdfFile, title = title) def getDocumentation(self): '''Get Parent element''' return self.getParent(Documentation) registerElement(BrowserPDFFile) # -------------------------------------------------------------------- class HelpFile(Element): TAG = Element.MODULE+'documentation.HelpFile' ARCHIVE = 'archive' def __init__(self,doc,node=None, title='', fileName='', fileType=ARCHIVE): '''Create a help menu entry that opens an embeddded file Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from title : str Entry title fileName : str Internal file name fileType : str How to find the file ''' super(HelpFile,self).__init__(doc,self.TAG,node=node, fileName = fileName, fileType = fileType, title = title) def getDocumentation(self): '''Get Parent element''' return self.getParent(Documentation) registerElement(HelpFile) # -------------------------------------------------------------------- class BrowserHelpFile(Element): TAG = Element.MODULE+'documentation.BrowserHelpFile' def __init__(self,doc,node=None, title='', startingPage='index.html'): '''Create a help menu entry that opens an embeddded HTML page (with possible sub-pages) file Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from title : str Entry title startingPage : str which file to start at ''' super(BrowserHelpFile,self).__init__(doc,self.TAG,node=node, startingPage=startingPage, title=title) def getDocumentation(self): '''Get Parent element''' return self.getParent(Documentation) registerElement(BrowserHelpFile) # -------------------------------------------------------------------- class Tutorial(Element): TAG = Element.MODULE+'documentation.Tutorial' def __init__(self,doc,node=None, name = 'Tutorial', logfile = 'tutorial.vlog', promptMessage = 'Load the tutorial?', welcomeMessage = 'Press "Step forward" (PnDn) to step through the tutorial', launchOnStartup = True): '''Add a help menu item that loads the tutorial Also adds the start-up option to run the tutorial Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from name : str Name of entry logfile : str Internal file name promptMessage : str What to show launchOnStartup : bool By default, launch tutorial first time running module ''' super(Tutorial,self).__init__(doc,self.TAG,node=node, name = name, logfile = logfile, promptMessage = promptMessage, welcomeMessage = welcomeMessage, launchOnStartup = launchOnStartup) def getDocumentation(self): '''Get Parent element''' return self.getParent(Documentation) registerElement(Tutorial) # # EOF # # ==================================================================== # From player.py # -------------------------------------------------------------------- class PlayerRoster(GameElement): TAG = Element.MODULE+'PlayerRoster' def __init__(self,doc,node=None,buttonKeyStroke='', buttonText='Retire', buttonToolTip='Switch sides, become observer, or release faction'): '''Add a player roster to the module Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from buttonText : str Text on button buttonTooltip : str Tool tip to show when hovering over button ''' super(PlayerRoster,self).__init__(doc,self.TAG,node=node, buttonKeyStroke = buttonKeyStroke, buttonText = buttonText, buttonToolTip = buttonToolTip) def addSide(self,name): '''Add a `Side` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Side The added element ''' return self.add(PlayerSide,name=name) def getSides(self): '''Get all sides''' return self.getAllElements(PlayerSide,False) def encode(self): '''Encode for save''' return ['PLAYER\ta\ta\t'] registerElement(PlayerRoster) # -------------------------------------------------------------------- class PlayerSide(Element): TAG = 'entry' def __init__(self,doc,node=None,name=''): '''Adds a side to the player roster Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from name : str Name of side ''' super(PlayerSide,self).__init__(doc,self.TAG,node=node) if node is None: self.addText(name) def getPlayerRoster(self): '''Get Parent element''' return self.getParent(PlayerRoster) registerElement(PlayerSide) # # EOF # # ==================================================================== # From chessclock.py # ==================================================================== class ChessClock(Element): TAG=Element.MODULE+'chessclockcontrol.ChessClock' def __init__(self, doc, node = None, icon = '', description = '', side = '', tooltip = 'Individual clock control', buttonText = '', startHotkey = '', stopHotkey = '', tickingBackgroundColor = rgb(255,255,0), tickingFontColor = rgb(0,0,0), tockingFontColor = rgb(51,51,51)): '''Individual clock for a side When the clock is running, the background colour may be changed, and the colour of the numbers alternate between `tickingFontColor` and `tockingFontColor`. Parameters ---------- doc : Element Parent element node : xml.dom.Element Read from this node icon : str File name of button icon description : str Note on this clock side : str Name of side this clock belongs to tooltop : str Hover help text buttonText : str Text on button startHotkey : str (key code) Key or command to start timer stopHotkey : str (key code) Key or command to stop timer tickingBackgroundColor : str (color) Background color of time display when clock is running tickingFontColor : str (color) First color of numbers in display when clock is running. tockingFontColor : str (color) Second color of numbers in display when clock is running. ''' super(ChessClock,self).__init__(#ChessClock doc, self.TAG, node = node, icon = icon, description = description, side = side, tooltip = tooltip, buttonText = buttonText, startHotkey = startHotkey, stopHotkey = stopHotkey, tickingBackgroundColor = tickingBackgroundColor, tickingFontColor = tickingFontColor, tockingFontColor = tockingFontColor) def getControl(self): '''Get Parent element''' return self.getParent(ChessClockControl) registerElement(ChessClock) # ==================================================================== class ChessClockControl(GameElement): TAG=Element.MODULE+'ChessClockControl' ALWAYS = 'Always' AUTO = 'Auto' NEVER = 'Never' def __init__(self, doc, node = None, name = 'Chess clock', description = '', buttonIcon = 'chess_clock.png', buttonText = '', buttonTooltip = 'Show/stop/hide chess clocks', showHotkey = key('U',ALT), pauseHotkey = key('U',CTRL_SHIFT), nextHotkey = key('U'), startOpponentKey = '', showTenths = AUTO, showSeconds = AUTO, showHours = AUTO, showDays = AUTO, allowReset = False, addClocks = True): '''A set of chess clocs Parameters ---------- doc : Element Parent node : xml.dom.Element Node to read state from name : str Name of clock control description : str Note on the chess clocks control buttonIcon : str Icon file name for button (chess_clock.png) buttonText : str Text on button buttonTooltip : str Hower help showHotkey : str (key code) Show or hide interface hot key nextHotkey : str (key code) Start the next clock hot key pauseHotkey : str (key code) Pause all clocks hot key startOpponentKey : str (key code) Start opponens clock showTenths : one of AUTO, ALWAYS, NEVER Whether to show tenths of seconds showSeconds : one of AUTO, ALWAYS, NEVER Whether to show seconds in clock showHours : one of AUTO, ALWAYS, NEVER Whether to show hours in clock showDays : one of AUTO, ALWAYS, NEVER Whether to show days in clock allowReset : boolean If true, allow manual reset of all clocks ''' super(ChessClockControl,self).__init__(# ChessclockControl doc, self.TAG, node = node, name = name, description = description, buttonIcon = buttonIcon, buttonText = buttonText, buttonTooltip = buttonTooltip, showHotkey = showHotkey, pauseHotkey = pauseHotkey, nextHotkey = nextHotkey, startOpponentKey = startOpponentKey, showTenths = showTenths, showSeconds = showSeconds, showHours = showHours, showDays = showDays, allowReset = allowReset) print(node,addClocks) if node is not None or not addClocks: return print('--- Will add clocks') game = self.getGame() roster = game.getPlayerRoster()[0] sides = roster.getSides() for side in sides: name = side.getText() self.addClock(side = name, tooltip = f'Clock for {name}', buttonText = name, startHotkey = key('U'), stopHotkey = key('U')) def addClock(self,**kwargs): '''Add a clock element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : AboutScreen The added element ''' return self.add(ChessClock,**kwargs) def getClocks(self,asdict=True): '''Return dictionary of clocs''' return self.getElementsByKey(ChessClock,'side',asdict) registerElement(ChessClockControl) # # EOF # # ==================================================================== # From widget.py # -------------------------------------------------------------------- class WidgetElement: def __init__(self): pass def addTabs(self,**kwargs): '''Add a `Tabs` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Tabs The added element ''' return self.add(TabWidget,**kwargs) def addCombo(self,**kwargs): '''Add a drop-down menu to this Parameters ---------- Dictionary of attribute key-value pairs Returns ------- element : Combo The added element ''' return self.add(ComboWidget,**kwargs) def addPanel(self,**kwargs): '''Add a `Panel` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Panel The added element ''' return self.add(PanelWidget,**kwargs) def addList(self,**kwargs): '''Add a `List` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : List The added element ''' return self.add(ListWidget,**kwargs) def addMapWidget(self,**kwargs): '''Add a `MapWidget` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : MapWidget The added element ''' return self.add(MapWidget,**kwargs) def addChart(self,**kwargs): '''Add a `Chart` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Chart The added element ''' return self.add(Chart,**kwargs) def addPieceSlot(self,**kwargs): '''Add a `PieceSlot` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : PieceSlot The added element ''' return self.add(PieceSlot,**kwargs) def addPiece(self,piece): '''Add a `Piece` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Piece The added element ''' if not isinstance(piece,PieceSlot): print(f'Trying to add {type(piece)} to ListWidget') return None p = piece.clone(self) return p def getTabs(self,asdict=True): '''Get all Tab element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Tab` elements. If `False`, return a list of all Tab` children. Returns ------- children : dict or list Dictionary or list of `Tab` children ''' return self.getElementsByKey(TabWidget,'entryName',asdict) def getCombos(self,asdict=True): '''Get all Combo element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Tab` elements. If `False`, return a list of all Tab` children. Returns ------- children : dict or list Dictionary or list of `Tab` children ''' return self.getElementsByKey(ComboWidget,'entryName',asdict) def getLists(self,asdict=True): '''Get all List element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `List` elements. If `False`, return a list of all List` children. Returns ------- children : dict or list Dictionary or list of `List` children ''' return self.getElementsByKey(ListWidget,'entryName',asdict) def getPanels(self,asdict=True): '''Get all Panel element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Panel` elements. If `False`, return a list of all Panel` children. Returns ------- children : dict or list Dictionary or list of `Panel` children ''' return self.getElementsByKey(PanelWidget,'entryName',asdict) def getMapWidgets(self,asdict=True): '''Get all MapWidget element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `MapWidget` elements. If `False`, return a list of all MapWidget` children. Returns ------- children : dict or list Dictionary or list of `MapWidget` children ''' return self.getElementsByKey(MapWidget,'entryName',asdict) def getCharts(self,asdict=True): '''Get all Chart element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Chart` elements. If `False`, return a list of all Chart` children. Returns ------- children : dict or list Dictionary or list of `Chart` children ''' return self.getElementsByKey(Chart,'chartName',asdict) def getPieceSlots(self,asdict=True): '''Get all PieceSlot element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `PieceSlot` elements. If `False`, return a list of all PieceSlot` children. Returns ------- children : dict or list Dictionary or list of `PieceSlot` children ''' return self.getElementsByKey(PieceSlot,'entryName',asdict) # -------------------------------------------------------------------- class PieceWindow(GameElement,WidgetElement): TAG=Element.MODULE+'PieceWindow' def __init__(self,elem,node=None, name = '', defaultWidth = 0, hidden = False, hotkey = key('C',ALT), scale = 1., text = '', tooltip = 'Show/hide piece window', icon = '/images/counter.gif'): super(PieceWindow,self).__init__(elem,self.TAG,node=node, name = name, defaultWidth = defaultWidth, hidden = hidden, hotkey = hotkey, scale = scale, text = text, tooltip = tooltip, icon = icon) registerElement(PieceWindow) # -------------------------------------------------------------------- class ComboWidget(Element,WidgetElement): TAG=Element.WIDGET+'BoxWidget' def __init__(self,elem,node=None,entryName='',width=0,height=0): super(ComboWidget,self).__init__(elem, self.TAG, node = node, entryName = entryName, width = width, height = height) registerElement(ComboWidget) # -------------------------------------------------------------------- class TabWidget(Element,WidgetElement): TAG=Element.WIDGET+'TabWidget' def __init__(self,elem,node=None,entryName=''): super(TabWidget,self).__init__(elem, self.TAG, node = node, entryName = entryName) registerElement(TabWidget) # -------------------------------------------------------------------- class ListWidget(Element,WidgetElement): TAG=Element.WIDGET+'ListWidget' def __init__(self,elem,node = None, entryName = '', height = 0, width = 300, scale = 1., divider = 150): super(ListWidget,self).__init__(elem,self.TAG,node=node, entryName = entryName, height = height, width = width, scale = scale, divider = divider) registerElement(ListWidget) # -------------------------------------------------------------------- class PanelWidget(Element,WidgetElement): TAG=Element.WIDGET+'PanelWidget' def __init__(self,elem,node=None, entryName = '', fixed = False, nColumns = 1, vert = False): super(PanelWidget,self).__init__(elem,self.TAG,node=node, entryName = entryName, fixed = fixed, nColumns = nColumns, vert = vert) registerElement(PanelWidget) # -------------------------------------------------------------------- class MapWidget(Element): TAG=Element.WIDGET+'MapWidget' def __init__(self,elem,node=None,entryName=''): super(MapWidget,self).__init__(elem,self.TAG, node = node, entryName = entryName) def addWidgetMap(self,**kwargs): '''Add a `WidgetMap` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : WidgetMap The added element ''' return self.add(WidgetMap,**kwargs) def getWidgetMaps(self,asdict=True): '''Get all WidgetMap element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `WidgetMap` elements. If `False`, return a list of all WidgetMap` children. Returns ------- children : dict or list Dictionary or list of `WidgetMap` children ''' return self.getElementsByKey(WidgetMap,'mapName',asdict=asdict) registerElement(MapWidget) # # EOF # # ==================================================================== # From grid.py # -------------------------------------------------------------------- HEX_WIDTH = 88.50779626676963 HEX_HEIGHT = 102.2 RECT_WIDTH = 80 RECT_HEIGHT = 80 # -------------------------------------------------------------------- class BaseGrid(Element): def __init__(self,zone,tag,node=None, color = rgb(0,0,0), cornersLegal = False, dotsVisible = False, dx = HEX_WIDTH, # Meaning seems reversed! dy = HEX_HEIGHT, edgesLegal = False, sideways = False, snapTo = True, visible = True, x0 = 0, y0 = 32): super(BaseGrid,self).__init__(zone,tag,node=node, color = color, cornersLegal = cornersLegal, dotsVisible = dotsVisible, dx = dx, dy = dy, edgesLegal = edgesLegal, sideways = sideways, snapTo = snapTo, visible = visible, x0 = x0, y0 = y0) def getZone(self): z = self.getParent(Zone) return z def getZonedGrid(self): z = self.getZone() if z is not None: return z.getZonedGrid() return None def getBoard(self): z = self.getZonedGrid() if z is not None: return z.getBoard() return self.getParent(Board) def getPicker(self): z = self.getBoard() if z is not None: return z.getPicker() return None def getMap(self): b = self.getPicker() if b is not None: return b.getMap() return None def getNumbering(self): pass def getLocation(self,loc): numbering = self.getNumbering() if numbering is None or len(numbering) < 1: return None return numbering[0].getLocation(loc) # -------------------------------------------------------------------- class BaseNumbering(Element): def __init__(self,grid,tag,node=None, color = rgb(255,0,0), first = 'H', fontSize = 24, hDescend = False, hDrawOff = 0, hLeading = 1, hOff = 0, hType = 'A', locationFormat = '$gridLocation$', rotateText = 0, sep = '', stagger = True, vDescend = False, vDrawOff = 32, vLeading = 0, vOff = 0, vType = 'N', visible = True): super(BaseNumbering,self).__init__(grid,tag,node=node, color = color, first = first, fontSize = fontSize, hDescend = hDescend, hDrawOff = hDrawOff, hLeading = hLeading, hOff = hOff, hType = hType, locationFormat = locationFormat, rotateText = rotateText, sep = sep, stagger = stagger, vDescend = vDescend, vDrawOff = vDrawOff, vLeading = vLeading, vOff = vOff, vType = vType, visible = visible) def getGrid(self): return getParent(BaseGrid) def _getMatcher(self,tpe,leading): if tpe == 'A': return \ '-?(?:A+|B+|C+|D+|E+|F+|G+|H+|I+|' + \ 'J+|K+|L+|M+|N+|O+|P+|Q+|R+|S+|T+|' + \ 'U+|V+|W+|X+|Y+|Z+)' return f'-?[0-9]{{{int(leading)+1},}}' def _getIndex(self,name,tpe): if tpe == 'A': negative = name.startswith('-') if negative: name = name[1:] value = 0 for num,let in enumerate(name): if not let.isupper(): continue if num < len(name) - 1: value += 26 else: value += ord(let)-ord('A') if negative: value *= -1 return value return int(name) def _getCenter(self,col,row): '''Convert col and row index to picture coordinates''' print('Dummy GetCenter') pass def getLocation(self,loc): '''Get picture coordinates from grid location''' from re import match first = self['first'] vType = self['vType'] hType = self['hType'] vOff = int(self['vOff']) hOff = int(self['hOff']) colPat = self._getMatcher(hType,self['hLeading']) rowPat = self._getMatcher(vType,self['vLeading']) patts = ((colPat,rowPat) if first == 'H' else (rowPat,colPat)) colGrp = 1 if first == 'H' else 2 rowGrp = 2 if first == 'H' else 1 patt = ''.join([f'({p})' for p in patts]) matched = match(patt,loc) if not matched: return None rowStr = matched[rowGrp] colStr = matched[colGrp] rowNum = self._getIndex(rowStr,vType) colNum = self._getIndex(colStr,hType) return self._getCenter(colNum-hOff, rowNum-vOff); # -------------------------------------------------------------------- class HexGrid(BaseGrid): TAG = Element.BOARD+'HexGrid' def __init__(self,zone,node=None,**kwargs): super(HexGrid,self).__init__(zone,self.TAG,node=node,**kwargs) def addNumbering(self,**kwargs): '''Add a `Numbering` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Numbering The added element ''' return self.add(HexNumbering,**kwargs) def getNumbering(self): return self.getAllElements(HexNumbering) def getDeltaX(self): return float(self['dx']) def getDeltaY(self): return float(self['dy']) def getXOffset(self): return int(self['x0']) def getYOffset(self): return int(self['y0']) def getMaxRows(self): from math import floor height = self.getZone().getHeight() return floor(height / self.getDeltaX() + .5) def getMaxCols(self): from math import floor width = self.getZone().getWidth() return floor(width / self.getDeltaY() + .5) registerElement(HexGrid) # -------------------------------------------------------------------- class SquareGrid(BaseGrid): TAG = Element.BOARD+'SquareGrid' def __init__(self,zone,node=None, dx = RECT_WIDTH, dy = RECT_HEIGHT, edgesLegal = False, x0 = 0, y0 = int(0.4*RECT_HEIGHT), **kwargs): super(SquareGrid,self).__init__(zone,self.TAG,node=node, dx = dx, dy = dy, edgesLegal = edgesLegal, x0 = x0, y0 = y0, **kwargs) def addNumbering(self,**kwargs): '''Add a `Numbering` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Numbering The added element ''' return self.add(SquareNumbering,**kwargs) def getNumbering(self): return self.getAllElements(SquareNumbering) def getDeltaX(self): return float(self['dx']) def getDeltaY(self): return float(self['dy']) def getXOffset(self): return int(self['x0']) def getYOffset(self): return int(self['y0']) def getMaxRows(self): from math import floor height = self.getZone().getHeight() return floor(height / self.getDeltaY() + .5) def getMaxCols(self): from math import floor width = self.getZone().getWidth() return floor(width / self.getDeltaX() + .5) registerElement(SquareGrid) # -------------------------------------------------------------------- class HexNumbering(BaseNumbering): TAG = Element.BOARD+'mapgrid.HexGridNumbering' def __init__(self,grid,node=None,**kwargs): super(HexNumbering,self).__init__(grid,self.TAG,node=node,**kwargs) def getGrid(self): g = self.getParent(HexGrid) return g def _getCenter(self,col,row): '''Convert col and row index to picture coordinates''' from math import floor stagger = self['stagger'] == 'true' sideways = self.getGrid()['sideways'] == 'true' hDesc = self['hDescend'] == 'true' vDesc = self['vDescend'] == 'true' xOff = self.getGrid().getXOffset() yOff = self.getGrid().getYOffset() hexW = self.getGrid().getDeltaX() hexH = self.getGrid().getDeltaY() zxOff = self.getGrid().getZone().getXOffset() zyOff = self.getGrid().getZone().getYOffset() maxRows = self.getGrid().getMaxRows() maxCols = self.getGrid().getMaxCols() # print(f' Col: {col}') # print(f' Row: {row}') # print(f' Stagger: {stagger}') # print(f' Sideways: {sideways}') # print(f' hDesc: {hDesc}') # print(f' vDesc: {vDesc}') # print(f' maxRows: {maxRows}') # print(f' maxCols: {maxCols}') if sideways: maxRows, maxCols = maxCols, maxRows if stagger: if sideways: if col % 2 != 0: row += 1 if hDesc else -1 else: if col % 2 != 0: row += 1 if vDesc else -1 if hDesc: col = maxCols - col if vDesc: row = maxRows - row x = col * hexW + xOff y = row * hexH + yOff + (hexH/2 if col % 2 != 0 else 0) x = int(floor(x + .5)) y = int(floor(y + .5)) if sideways: # print(f'Swap coordinates because {sideways}') x, y = y, x return x,y registerElement(HexNumbering) # -------------------------------------------------------------------- class SquareNumbering(BaseNumbering): TAG = Element.BOARD+'mapgrid.SquareGridNumbering' def __init__(self,grid,node=None,hType='N',**kwargs): super(SquareNumbering,self).__init__(grid,self.TAG,node=node, hType=hType,**kwargs) def getGrid(self): return self.getParent(SquareGrid) def getCenter(self,col,row): hDesc = self['hDescend'] == 'true' vDesc = self['vDescend'] == 'true' xOff = self.getGrid().getXOffset() yOff = self.getGrid().getYOffset() squareW = self.getGrid().getDeltaX() squareH = self.getGrid().getDeltaY() maxRows = self.getGrid().getMaxRows() maxCols = self.getGrid().getMaxCols() if vDesc: row = maxRows - row if hDesc: col = maxCols - col x = col * squareW + xOff y = row * squareH + yOff return x,y registerElement(SquareNumbering) # -------------------------------------------------------------------- class RegionGrid(Element): TAG = Element.BOARD+'RegionGrid' def __init__(self,zone,node=None,snapto=True,fontsize=9,visible=True): super(RegionGrid,self).__init__(zone,self.TAG,node=node, snapto = snapto, fontsize = fontsize, visible = visible) def getZone(self): return self.getParent(Zone) def getZoneGrid(self): z = self.getZone() if z is not None: return z.getBoard() return None def getBoard(self): z = self.getZonedGrid() if z is not None: return z.getBoard() return self.getParent(Board) def getMap(self): b = self.getBoard() if b is not None: return b.getMap() return None def getRegions(self): return self.getElementsByKey(Region,'name') def checkName(self,name): '''Get unique name''' poss = len([e for e in self.getRegions() if e == name or e.startswith(name+'_')]) if poss == 0: return name return name + f'_{poss}' def addRegion(self,**kwargs): '''Add a `Region` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Region The added element ''' return self.add(Region,**kwargs) def getLocation(self,loc): for r in self.getRegions().values(): if loc == r['name']: return int(r['originx']),int(r['originy']) return None registerElement(RegionGrid) # -------------------------------------------------------------------- class Region(Element): TAG = Element.BOARD+'Region' def __init__(self,grid,node=None, name = '', originx = 0, originy = 0, alsoPiece = True, piece = None, prefix = ''): fullName = name + ("@"+prefix if len(prefix) else "") realName = grid.checkName(fullName) if node is None else fullName super(Region,self).__init__(grid, self.TAG, node = node, name = realName, originx = originx, originy = originy) if node is None and alsoPiece: m = self.getMap() b = self.getBoard() if m is not None and b is not None: if piece is None: g = m.getGame() pieces = g.getSpecificPieces(name,asdict=False) piece = pieces[0] if len(pieces) > 0 else None if piece is not None: # bname = m['mapName'] bname = b['name'] #print(f'Adding at-start name={name} location={realName} ' # f'owning board={bname}') a = m.addAtStart(name = name, location = realName, useGridLocation = True, owningBoard = bname, x = 0, y = 0) p = a.addPiece(piece) if p is None: print(f'EEE Failed to add piece {name} ({piece}) to add-start {a}') #if p is not None: # print(f'Added piece {name} in region') #else: # print(f'Could not find piece {name}') def getGrid(self): return self.getParent(RegionGrid) def getZone(self): g = self.getGrid() if g is not None: return g.getZone() return None def getZonedGrid(self): z = self.getZone() if z is not None: return z.getZonedGrid() return None def getBoard(self): z = self.getZonedGrid() if z is not None: return z.getBoard() return self.getParent(Board) def getPicker(self): z = self.getBoard() if z is not None: return z.getPicker() return None def getMap(self): b = self.getPicker() if b is not None: return b.getMap() return None registerElement(Region) # # EOF # # ==================================================================== # From zone.py # -------------------------------------------------------------------- class ZonedGrid(Element): TAG=Element.BOARD+'ZonedGrid' def __init__(self,board,node=None): super(ZonedGrid,self).__init__(board,self.TAG,node=node) def getBoard(self): b = self.getParent(Board) # print(f'Get Board of Zoned: {b}') return b def getPicker(self): z = self.getBoard() if z is not None: return z.getPicker() return None def getMap(self): z = self.getPicker() if z is not None: return z.getMap() return None def addHighlighter(self,**kwargs): '''Add a `Highlighter` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Highlighter The added element ''' return self.add(ZonedGridHighlighter,**kwargs) def getHighlighters(self,single=True): '''Get all or a sole `ZonedGridHighlighter` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `Highligter` child, otherwise fail. If `False` return all `Highligter` children in this element Returns ------- children : list List of `Highligter` children (even if `single=True`) ''' return self.getAllElements(ZonedGridHighlighter,single=single) def addZone(self,**kwargs): '''Add a `Zone` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Zone The added element ''' return self.add(Zone,**kwargs) def getZones(self,asdict=True): '''Get all Zone element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Zone` elements. If `False`, return a list of all Zone` children. Returns ------- children : dict or list Dictionary or list of `Zone` children ''' return self.getElementsByKey(Zone,'name',asdict=asdict) registerElement(ZonedGrid) # -------------------------------------------------------------------- class ZonedGridHighlighter(Element): TAG=Element.BOARD+'mapgrid.ZonedGridHighlighter' def __init__(self,zoned,node=None): super(ZonedGridHighlighter,self).__init__(zoned,self.TAG,node=node) def getZonedGrid(self): return self.getParent(ZonedGrid) def addZoneHighlight(self,**kwargs): '''Add a `ZoneHighlight` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : ZoneHighlight The added element ''' return self.add(ZoneHighlight,**kwargs) def getZoneHighlights(self,asdict=True): '''Get all ZoneHighlight element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Zone` elements. If `False`, return a list of all Zone` children. Returns ------- children : dict or list Dictionary or list of `Zone` children ''' return self.getElementsByKey(ZoneHighlight,'name',asdict=asdict) registerElement(ZonedGridHighlighter) # -------------------------------------------------------------------- class ZoneHighlight(Element): TAG=Element.BOARD+'mapgrid.ZoneHighlight' FULL='Entire Zone', BORDER='Zone Border', PLAIN='Plain', STRIPED='Striped' CROSS='Crosshatched', TILES='Tiled Image' def __init__(self, highlighters, node = None, name = '', color = rgb(255,0,0), coverage = FULL, width = 1, style = PLAIN, image = '', opacity = 50): super(ZoneHighlight,self).__init__(highlighters, self.TAG, node = node, name = name, color = color, coverage = coverage, width = width, style = style, image = image, opacity = int(opacity)) def getZonedGridHighlighter(self): return self.getParent(ZonedGridHighlighter) registerElement(ZoneHighlight) # -------------------------------------------------------------------- class ZoneProperty(Element): TAG = Element.MODULE+'properties.ZoneProperty' def __init__(self,zone,node=None, name = '', initialValue = '', isNumeric = False, min = "null", max = "null", wrap = False, description = ""): super(ZoneProperty,self).__init__(zone,self.TAG, node = node, name = name, initialValue = initialValue, isNumeric = isNumeric, min = min, max = max, wrap = wrap, description = description) def getZone(self): return self.getParent(Zone) registerElement(ZoneProperty) # -------------------------------------------------------------------- class Zone(Element): TAG = Element.BOARD+'mapgrid.Zone' def __init__(self,zoned,node=None, name = "", highlightProperty = "", locationFormat = "$gridLocation$", path = "0,0;976,0;976,976;0,976", useHighlight = False, useParentGrid = False): super(Zone,self).\ __init__(zoned,self.TAG,node=node, name = name, highlightProperty = highlightProperty, locationFormat = locationFormat, path = path, useHighlight = useHighlight, useParentGrid = useParentGrid) def getZonedGrid(self): z = self.getParent(ZonedGrid) # print(f'Get Zoned of Zone {self["name"]}: {z}') return z def getBoard(self): z = self.getZonedGrid() if z is not None: return z.getBoard() return None def getPicker(self): z = self.getBoard() if z is not None: return z.getPicker() return None def getMap(self): z = self.getPicker() if z is not None: return z.getMap() return None def addHexGrid(self,**kwargs): '''Add a `HexGrid` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : HexGrid The added element ''' return self.add(HexGrid,**kwargs) def addSquareGrid(self,**kwargs): '''Add a `SquareGrid` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : SquareGrid The added element ''' return self.add(SquareGrid,**kwargs) def addRegionGrid(self,**kwargs): '''Add a `RegionGrid` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : RegionGrid The added element ''' return self.add(RegionGrid,**kwargs) def addProperty(self,**kwargs): '''Add a `Property` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Property The added element ''' return self.add(ZoneProperty,**kwargs) def getHexGrids(self,single=True): '''Get all or a sole `HexGrid` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `HexGrid` child, otherwise fail. If `False` return all `HexGrid` children in this element Returns ------- children : list List of `HexGrid` children (even if `single=True`) ''' return self.getAllElements(HexGrid,single=single) def getSquareGrids(self,single=True): '''Get all or a sole `SquareGrid` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `SquareGrid` child, otherwise fail. If `False` return all `SquareGrid` children in this element Returns ------- children : list List of `SquareGrid` children (even if `single=True`) ''' return self.getAllElements(SquareGrid,single=single) def getRegionGrids(self,single=True): '''Get all or a sole `RegionGrid` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `RegionGrid` child, otherwise fail. If `False` return all `RegionGrid` children in this element Returns ------- children : list List of `RegionGrid` children (even if `single=True`) ''' return self.getAllElements(RegionGrid,single=single) def getGrids(self,single=True): '''Get all or a sole `Grid` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `Grid` child, otherwise fail. If `False` return all `Grid` children in this element Returns ------- children : list List of `Grid` children (even if `single=True`) ''' g = self.getHexGrids(single=single) if g is not None: return g g = self.getSquareGrids(single=single) if g is not None: return g g = self.getRegionGrids(single=single) if g is not None: return g return g def getProperties(self): '''Get all `Property` element from this Returns ------- children : dict dict of `Property` children ''' return getElementsByKey(ZoneProperty,'name') def getPath(self): p = self['path'].split(';') r = [] for pp in p: c = pp.split(',') r.append([int(c[0]),int(c[1])]) return r def getBB(self): from functools import reduce path = self.getPath() llx = reduce(lambda old,point:min(point[0],old),path,100000000000) lly = reduce(lambda old,point:min(point[1],old),path,100000000000) urx = reduce(lambda old,point:max(point[0],old),path,-1) ury = reduce(lambda old,point:max(point[1],old),path,-1) return llx,lly,urx,ury def getWidth(self): llx,_,urx,_ = self.getBB() return urx-llx def getHeight(self): _,lly,_,ury = self.getBB() return ury-lly def getXOffset(self): return self.getBB()[0] def getYOffset(self): return self.getBB()[1] registerElement(Zone) # # EOF # # ==================================================================== # From board.py # -------------------------------------------------------------------- class BoardPicker(MapElement): TAG = Element.MAP+'BoardPicker' def __init__(self,doc,node=None, addColumnText = 'Add column', addRowText = 'Add row', boardPrompt = 'Select board', slotHeight = 125, slotScale = 0.2, slotWidth = 350, title = 'Choose Boards'): super(BoardPicker,self).__init__(doc,self.TAG,node=node, addColumnText = addColumnText, addRowText = addRowText, boardPrompt = boardPrompt, slotHeight = slotHeight, slotScale = slotScale, slotWidth = slotWidth, title = title) def addSetup(self,**kwargs): '''Add a `Setup` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Setup The added element ''' if 'mapName' not in kwargs: m = self.getMap() kwargs['mapName'] = m.getAttribute('mapName') return self.add(Setup,**kwargs) def getSetups(self,single=False): '''Get all or a sole `Setup` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `Setup` child, otherwise fail. If `False` return all `Setup` children in this element Returns ------- children : list List of `Setup` children (even if `single=True`) ''' return self.getAllElements(Setup,single=single) def addBoard(self,**kwargs): '''Add a `Board` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Board The added element ''' return self.add(Board,**kwargs) def getBoards(self,asdict=True): '''Get all Board element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Board` elements. If `False`, return a list of all Board` children. Returns ------- children : dict or list Dictionary or list of `Board` children ''' return self.getElementsByKey(Board,'name',asdict=asdict) def encode(self): setups = self.getSetups() if setups is not None and len(setups)>0: return [setups[0]._node.childNodes[0].nodeValue] ret = [] for bn in self.getBoards().keys(): ret.append(bn+'BoardPicker\t'+bn+'\t0\t0') return ret registerElement(BoardPicker) # -------------------------------------------------------------------- class Setup(Element): TAG = 'setup' def __init__(self,picker,node=None, mapName = '', maxColumns = 1, boardNames = []): super(Setup,self).__init__(picker,self.TAG,node=node) col = 0 row = 0 lst = [f'{mapName}BoardPicker'] for bn in boardNames: lst.extend([bn,str(col),str(row)]) col += 1 if col >= maxColumns: col = 0 row += 1 txt = r' '.join(lst) self.addText(txt) def getPicker(self): return self.getParent(BoardPicker) registerElement(Setup) # -------------------------------------------------------------------- class Board(Element): TAG = Element.PICKER+'Board' def __init__(self,picker,node=None, name = '', image = '', reversible = False, color = rgb(255,255,255), width = 0, height = 0): super(Board,self).__init__(picker,self.TAG,node=node, image = image, name = name, reversible = reversible, color = color, width = width, height = height) def getPicker(self): return self.getParent(BoardPicker) def getMap(self): z = self.getPicker() if z is not None: return z.getMap() return None def addZonedGrid(self,**kwargs): '''Add a `ZonedGrid` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : ZonedGrid The added element ''' return self.add(ZonedGrid,**kwargs) def getZonedGrids(self,single=True): '''Get all or a sole `ZonedGrid` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `ZonedGrid` child, otherwise fail. If `False` return all `ZonedGrid` children in this element Returns ------- children : list List of `ZonedGrid` children (even if `single=True`) ''' return self.getAllElements(ZonedGrid,single=single) def getZones(self,asdict=True): '''Get all Zone element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Zone` elements. If `False`, return a list of all Zone` children. Returns ------- children : dict or list Dictionary or list of `Zone` children ''' zoned = self.getZonedGrids(single=True) if zoned is None: return None return zoned[0].getZones(asdict=asdict) def getWidth(self): # print(f'Getting width of {self}: {self["width"]}') if 'width' in self and int(self['width']) != 0: return int(self['width']) return 0 def getHeight(self): # print(f'Getting height of {self}: {self["height"]}') if 'height' in self and int(self['height']) != 0: return int(self['height']) return 0 registerElement(Board) # # EOF # # ==================================================================== # From map.py # -------------------------------------------------------------------- class BaseMap(Element): def __init__(self,doc,tag,node=None, mapName = '', allowMultiple = 'false', backgroundcolor = rgb(255,255,255), buttonName = '', changeFormat = '$message$', color = rgb(0,0,0), # Selected pieces createFormat = '$pieceName$ created in $location$ *', edgeHeight = '0', edgeWidth = '0', hideKey = '', hotkey = key('M',ALT), icon = '/images/map.gif', launch = 'false', markMoved = 'Always', markUnmovedHotkey = '', markUnmovedIcon = '/images/unmoved.gif', markUnmovedReport = '', markUnmovedText = '', markUnmovedTooltip = 'Mark all pieces on this map as not moved', moveKey = '', moveToFormat = '$pieceName$ moves $previousLocation$ → $location$ *', moveWithinFormat = '$pieceName$ moves $previousLocation$ → $location$ *', showKey = '', thickness = '3'): '''Create a map Parameters ---------- doc : xml.minidom.Document Parent document tag : str XML tag node : xml.minidom.Node or None Existing node or None mapName : str Name of map allowMultiple : bool Allow multiple boards backgroundcolor : color Bckground color buttonName : str Name on button to show map = '', changeFormat : Message format to show on changes color : color Color of selected pieces createFormat : str Message format when creating a piece edgeHeight : int Height of edge (margin) edgeWidth : int Width of edge (margin) hideKey : Key Hot-key or key-command to hide map hotkey : Key Hot-key or key-command to show map icon : path Icon image launch : bool Show on launch markMoved : str Show moved markUnmovedHotkey : key Remove moved markers markUnmovedIcon : path Icon for unmoved markUnmovedReport : str Message when marking as unmoved markUnmovedText : str Text on button markUnmovedTooltip : str Tooltip on button moveKey : key Key to set moved marker moveToFormat : str Message format when moving moveWithinFormat : str Message when moving within map showKey : str, Key to show map thickness : int Thickness of line around selected pieces ''' super(BaseMap,self).__init__(doc,tag,node=node, allowMultiple = allowMultiple, backgroundcolor = backgroundcolor, buttonName = buttonName, changeFormat = changeFormat, color = color, createFormat = createFormat, edgeHeight = edgeHeight, edgeWidth = edgeWidth, hideKey = hideKey, hotkey = hotkey, icon = icon, launch = launch, mapName = mapName, markMoved = markMoved, markUnmovedHotkey = markUnmovedHotkey, markUnmovedIcon = markUnmovedIcon, markUnmovedReport = markUnmovedReport, markUnmovedText = markUnmovedText, markUnmovedTooltip = markUnmovedTooltip, moveKey = moveKey, moveToFormat = moveToFormat, moveWithinFormat = moveWithinFormat, showKey = showKey, thickness = thickness) def getGame(self): '''Get the game''' return self.getParentOfClass([Game]) def addPicker(self,**kwargs): '''Add a `Picker` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Picker The added element ''' return self.add(BoardPicker,**kwargs) def getBoardPicker(self,single=True): '''Get all or a sole `BoardPicker` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `BoardPicker` child, otherwise fail. If `False` return all `BoardPicker` children in this element Returns ------- children : list List of `BoardPicker` children (even if `single=True`) ''' return self.getAllElements(BoardPicker,single) def getPicker(self,single=True): '''Get all or a sole `BoardPicker` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `BoardPicker` child, otherwise fail. If `False` return all `BoardPicker` children in this element Returns ------- children : list List of `BoardPicker` children (even if `single=True`) ''' return self.getAllElements(BoardPicker,single) def getStackMetrics(self,single=True): '''Get all or a sole `StackMetric` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `StackMetric` child, otherwise fail. If `False` return all `StackMetric` children in this element Returns ------- children : list List of `StackMetric` children (even if `single=True`) ''' return self.getAllElements(StackMetrics,single) def getImageSaver(self,single=True): '''Get all or a sole `ImageSaver` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `ImageSaver` child, otherwise fail. If `False` return all `ImageSaver` children in this element Returns ------- children : list List of `ImageSaver` children (even if `single=True`) ''' return self.getAllElements(ImageSaver,single) def getTextSaver(self,single=True): '''Get all or a sole `TextSaver` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `TextSaver` child, otherwise fail. If `False` return all `TextSaver` children in this element Returns ------- children : list List of `TextSaver` children (even if `single=True`) ''' return self.getAllElements(TextSaver,single) def getForwardToChatter(self,single=True): '''Get all or a sole `ForwardToChatter` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `ForwardToChatter` child, otherwise fail. If `False` return all `ForwardToChatter` children in this element Returns ------- children : list List of `ForwardToChatter` children (even if `single=True`) ''' return self.getAllElements(ForwardToChatter,single) def getMenuDisplayer(self,single=True): '''Get all or a sole `MenuDi` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `MenuDi` child, otherwise fail. If `False` return all `MenuDi` children in this element Returns ------- children : list List of `MenuDi` children (even if `single=True`) ''' return self.getAllElements(MenuDisplayer,single) def getMapCenterer(self,single=True): '''Get all or a sole `MapCenterer` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `MapCenterer` child, otherwise fail. If `False` return all `MapCenterer` children in this element Returns ------- children : list List of `MapCenterer` children (even if `single=True`) ''' return self.getAllElements(MapCenterer,single) def getStackExpander(self,single=True): '''Get all or a sole `StackExpander` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `StackExpander` child, otherwise fail. If `False` return all `StackExpander` children in this element Returns ------- children : list List of `StackExpander` children (even if `single=True`) ''' return self.getAllElements(StackExpander,single) def getPieceMover(self,single=True): '''Get all or a sole `PieceMover` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `PieceMover` child, otherwise fail. If `False` return all `PieceMover` children in this element Returns ------- children : list List of `PieceMover` children (even if `single=True`) ''' return self.getAllElements(PieceMover,single) def getSelectionHighlighters(self,single=True): '''Get all or a sole `SelectionHighlighter` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `SelectionHighlighter` child, otherwise fail. If `False` return all `SelectionHighlighter` children in this element Returns ------- children : list List of `SelectionHighlighter` children (even if `single=True`) ''' return self.getAllElements(SelectionHighlighters,single) def getKeyBufferer(self,single=True): return self.getAllElements(KeyBufferer,single) def getHighlightLastMoved(self,single=True): '''Get all or a sole `HighlightLa` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `HighlightLa` child, otherwise fail. If `False` return all `HighlightLa` children in this element Returns ------- children : list List of `HighlightLa` children (even if `single=True`) ''' return self.getAllElements(HighlightLastMoved,single) def getCounterDetailViewer(self,single=True): '''Get all or a sole `CounterDetailViewer` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `CounterDetailViewer` child, otherwise fail. If `False` return all `CounterDetailViewer` children in this element Returns ------- children : list List of `CounterDetailViewer` children (even if `single=True`) ''' return self.getAllElements(CounterDetailViewer,single) def getGlobalMap(self,single=True): '''Get all or a sole `GlobalMap` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `GlobalMap` child, otherwise fail. If `False` return all `GlobalMap` children in this element Returns ------- children : list List of `GlobalMap` children (even if `single=True`) ''' return self.getAllElements(GlobalMap,single) def getZoomer(self,single=True): '''Get all or a sole `Zoomer` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `Zoomer` child, otherwise fail. If `False` return all `Zoomer` children in this element Returns ------- children : list List of `Zoomer` children (even if `single=True`) ''' return self.getAllElements(Zoomer,single) def getHidePiecesButton(self,single=True): '''Get all or a sole `HidePiece` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `HidePiece` child, otherwise fail. If `False` return all `HidePiece` children in this element Returns ------- children : list List of `HidePiece` children (even if `single=True`) ''' return self.getAllElements(HidePiecesButton,single) def getMassKeys(self,asdict=True): '''Get all MassKey element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `MassKey` elements. If `False`, return a list of all MassKey` children. Returns ------- children : dict or list Dictionary or list of `MassKey` children ''' return self.getElementsByKey(MassKey,'name',asdict) def getFlare(self,single=True): '''Get all or a sole `Flare` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `Flare` child, otherwise fail. If `False` return all `Flare` children in this element Returns ------- children : list List of `Flare` children (even if `single=True`) ''' return self.getAllElements(Flare,single) def getAtStarts(self,single=True): '''Get all or a sole `AtStart` element(s) from this Parameters ---------- single : bool If `True`, there can be only one `AtStart` child, otherwise fail. If `False` return all `AtStart` children in this element Returns ------- children : list List of `AtStart` children (even if `single=True`) ''' return self.getAllElements(AtStart,single) def getBoards(self,asdict=True): '''Get all Board element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Board` elements. If `False`, return a list of all Board` children. Returns ------- children : dict or list Dictionary or list of `Board` children ''' picker = self.getPicker() if picker is None: return None return picker[0].getBoards(asdict=asdict) def getLayers(self,asdict=True): '''Get all `PieceLayer` element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps property name `PieceLayers` elements. If `False`, return a list of all `PieceLayers` children. Returns ------- children : dict or list Dictionary or list of `PieceLayers` children ''' return self.getElementsByKey(PieceLayers,'property',asdict) def getMenus(self,asdict=True): '''Get all Menu element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Board` elements. If `False`, return a list of all Board` children. Returns ------- children : dict or list Dictionary or list of `Board` children ''' return self.getElementsByKey(Menu,'name',asdict) def getLOSs(self,asdict=True): '''Get all Menu element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Board` elements. If `False`, return a list of all Board` children. Returns ------- children : dict or list Dictionary or list of `Board` children ''' return self.getElementsByKey(LineOfSight,'threadName',asdict) def addBoardPicker(self,**kwargs): '''Add a `BoardPicker` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : BoardPicker The added element ''' return self.add(BoardPicker,**kwargs) def addStackMetrics(self,**kwargs): '''Add a `StackMetrics` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : StackMetrics The added element ''' return self.add(StackMetrics,**kwargs) def addImageSaver(self,**kwargs): '''Add a `ImageSaver` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : ImageSaver The added element ''' return self.add(ImageSaver,**kwargs) def addTextSaver(self,**kwargs): '''Add a `TextSaver` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : TextSaver The added element ''' return self.add(TextSaver,**kwargs) def addForwardToChatter(self,**kwargs): '''Add a `ForwardToChatter` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : ForwardToChatter The added element ''' return self.add(ForwardToChatter,**kwargs) def addMenuDisplayer(self,**kwargs): '''Add a `MenuDisplayer` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : MenuDisplayer The added element ''' return self.add(MenuDisplayer,**kwargs) def addMapCenterer(self,**kwargs): '''Add a `MapCenterer` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : MapCenterer The added element ''' return self.add(MapCenterer,**kwargs) def addStackExpander(self,**kwargs): '''Add a `StackExpander` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : StackExpander The added element ''' return self.add(StackExpander,**kwargs) def addPieceMover(self,**kwargs): '''Add a `PieceMover` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : PieceMover The added element ''' return self.add(PieceMover,**kwargs) def addSelectionHighlighters(self,**kwargs): '''Add a `SelectionHighlighters` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : SelectionHighlighters The added element ''' return self.add(SelectionHighlighters,**kwargs) def addKeyBufferer(self,**kwargs): '''Add a `KeyBufferer` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : KeyBufferer The added element ''' return self.add(KeyBufferer,**kwargs) def addHighlightLastMoved(self,**kwargs): '''Add a `HighlightLastMoved` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : HighlightLastMoved The added element ''' return self.add(HighlightLastMoved,**kwargs) def addCounterDetailViewer(self,**kwargs): '''Add a `CounterDetailViewer` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : CounterDetailViewer The added element ''' return self.add(CounterDetailViewer,**kwargs) def addGlobalMap(self,**kwargs): '''Add a `GlobalMap` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : GlobalMap The added element ''' return self.add(GlobalMap,**kwargs) def addZoomer(self,**kwargs): '''Add a `Zoomer` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Zoomer The added element ''' return self.add(Zoomer,**kwargs) def addHidePiecesButton(self,**kwargs): '''Add a `HidePiecesButton` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : HidePiecesButton The added element ''' return self.add(HidePiecesButton,**kwargs) def addMassKey(self,**kwargs): '''Add a `MassKey` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : MassKey The added element ''' return self.add(MassKey,**kwargs) def addStartupMassKey(self,**kwargs): '''Add a `StartupMassKey` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : StartupMassKey The added element ''' return self.add(MassKey,**kwargs) def addFlare(self,**kwargs): '''Add a `Flare` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Flare The added element ''' return self.add(Flare,**kwargs) def addAtStart(self,**kwargs): '''Add a `AtStart` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : AtStart The added element ''' return self.add(AtStart,**kwargs) def addLayers(self,**kwargs): '''Add `PieceLayers` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : PieceLayers The added element ''' return self.add(PieceLayers,**kwargs) def addMenu(self,**kwargs): '''Add a `Menu` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Menu The added element ''' return self.add(Menu,**kwargs) def addLOS(self,**kwargs): '''Add a `Menu` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Menu The added element ''' return self.add(LineOfSight,**kwargs) # -------------------------------------------------------------------- class Map(BaseMap): TAG = Element.MODULE+'Map' def __init__(self,doc,node=None, mapName = '', allowMultiple = 'false', backgroundcolor = rgb(255,255,255), buttonName = '', changeFormat = '$message$', color = rgb(0,0,0), createFormat = '$pieceName$ created in $location$ *', edgeHeight = '0', edgeWidth = '0', hideKey = '', hotkey = key('M',ALT), icon = '/images/map.gif', launch = 'false', markMoved = 'Always', markUnmovedHotkey = '', markUnmovedIcon = '/images/unmoved.gif', markUnmovedReport = '', markUnmovedText = '', markUnmovedTooltip = 'Mark all pieces on this map as not moved', moveKey = '', moveToFormat = '$pieceName$ moves $previousLocation$ → $location$ *', moveWithinFormat = '$pieceName$ moves $previousLocation$ → $location$ *', showKey = '', thickness = '3'): super(Map,self).__init__(doc,self.TAG,node=node, allowMultiple = allowMultiple, backgroundcolor = backgroundcolor, buttonName = buttonName, changeFormat = changeFormat, color = color, createFormat = createFormat, edgeHeight = edgeHeight, edgeWidth = edgeWidth, hideKey = hideKey, hotkey = hotkey, icon = icon, launch = launch, mapName = mapName, markMoved = markMoved, markUnmovedHotkey = markUnmovedHotkey, markUnmovedIcon = markUnmovedIcon, markUnmovedReport = markUnmovedReport, markUnmovedText = markUnmovedText, markUnmovedTooltip = markUnmovedTooltip, moveKey = moveKey, moveToFormat = moveToFormat, moveWithinFormat = moveWithinFormat, showKey = showKey, thickness = thickness) def getGame(self): return self.getParent(Game) registerElement(Map) # -------------------------------------------------------------------- class WidgetMap(BaseMap): TAG = Element.WIDGET+'WidgetMap' def __init__(self,doc,node=None,**attr): super(WidgetMap,self).__init__(doc,self.TAG,node=node,**attr) def getGame(self): return self.getParentOfClass([Game]) def getMapWidget(self): return self.getParent(MapWidget) registerElement(WidgetMap) # # EOF # # ==================================================================== # From chart.py # -------------------------------------------------------------------- class ChartWindow(GameElement,WidgetElement): TAG=Element.MODULE+'ChartWindow' def __init__(self,elem,node=None, name = '', hotkey = key('A',ALT), description = '', text = '', tooltip = 'Show/hide Charts', icon = '/images/chart.gif'): super(ChartWindow,self).__init__(elem,self.TAG,node=node, name = name, hotkey = hotkey, description = description, text = text, tooltip = tooltip, icon = icon) def addChart(self,**kwargs): '''Add a `Chart` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Chart The added element ''' return self.add(Chart,**kwargs) def getCharts(self,asdict=True): '''Get all Chart element(s) from this Parameters ---------- asdict : bool If `True`, return a dictonary that maps key to `Chart` elements. If `False`, return a list of all Chart` children. Returns ------- children : dict or list Dictionary or list of `Chart` children ''' return self.getElementsById(Chart,'chartName',asdict=asdict) registerElement(ChartWindow) # -------------------------------------------------------------------- class Chart(Element): TAG=Element.WIDGET+'Chart' def __init__(self,elem,node=None, chartName = '', fileName = '', description = ''): super(Chart,self).__init__(elem,self.TAG,node=node, chartName = chartName, description = description, fileName = fileName) registerElement(Chart) # # EOF # # ==================================================================== # From command.py # -------------------------------------------------------------------- class Command: def __init__(self,what,iden,tpe,state): self.cmd = '/'.join([what,iden,tpe,state]) # -------------------------------------------------------------------- class AddCommand(Command): ID = '+' def __init__(self,iden,tpe,state): super(AddCommand,self).__init__(self.ID,iden,tpe,state) # # EOF # # ==================================================================== # From trait.py # ==================================================================== class Trait: known_traits = [] def __init__(self): '''Base class for trait capture. Unlike the Element classes, this actually holds state that isn't reflected elsewhere in the DOM. This means that the data here is local to the object. So when we do piece = foo.getPieceSlots()[0] traits = p.getTraits() for trait in traits: if trait.ID == 'piece': trait["gpid"] = newPid trait["lpid"] = newPid we do not actually change anything in the DOM. To do that, we must add back _all_ the traits as piece.setTraits(traits) We can add traits to a piece, like piece.addTrait(MarkTrait('Hello','World')) But it is not particularly efficient. Better to do (continuing from above) traits.append(MarkTrait('Hello','World;) piece.setTraits(traits) .. include:: ../../vassal/traits/README.md :parser: myst_parser.sphinx_ ''' self._type = None self._state = None def setType(self,**kwargs): '''Set types. Dictionary of names and values. Dictonary keys defines how we access the fields, which is internal here. What matters is the order of the values. ''' self._type = list(kwargs.values()) self._tnames = list(kwargs.keys()) def setState(self,**kwargs): '''Set states. Dictionary of names and values. Dictonary keys defines how we access the fields, which is internal here. What matters is the order of the values. ''' self._state = list(kwargs.values()) self._snames = list(kwargs.keys()) def __getitem__(self,key): '''Look up item in either type or state''' try: return self._type[self._tnames.index(key)] except: pass return self._state[self._snames.index(key)] def __setitem__(self,key,value): '''Set item in either type or state''' try: self._type[self._tnames.index(key)] = value return except: pass self._state[self._snames.index(key)] = value def encode(self,term=False): ''' returns type and state encoded''' t = self.encodeFields(self.ID,*self._type,term=term) s = self.encodeFields(*self._state,term=term) return t,s @classmethod def findTrait(cls,traits,ID,key=None,value=None,verbose=False): for trait in traits: if trait.ID != ID: continue if verbose: print(f' {trait.ID}') if key is None or value is None: if verbose: print(f' Return {trait.ID}') return trait if verbose: print(f' Check {key}={value}: {trait[key]}') if trait[key] == value: return trait if verbose: print(f' Trait of type {ID} with {key}={value} not found') return None @classmethod def take(cls,iden,t,s): '''If the first part of the string t matches the ID, then take it. t and s are lists of strings. ''' if iden != cls.ID: return None ret = cls() ret._type = t ret._state = s ret.check() # Check if we're reasonable, or raise #print(f'Took {iden} {cls}\n' # f' {ret._tnames}\n' # f' {ret._snames}') return ret def check(self): '''Implement if trait should check that all is OK when cloning''' pass @classmethod def encodeFields(cls,*args,term=False): return ';'.join([str(e).lower() if isinstance(e,bool) else str(e) for e in args])+(';' if term else '') @classmethod def decodeFields(cls,s): from re import split return split(r'(? count: best = c count = cnt if verbose and best is None: print(f'No candidate for {strait.ID} {len(again)}') if verbose and count+2 < len(types): print(f'Ambigious candidate for {strait.ID} ' f'({count} match out of {len(types)})') #print(best._type) #print(types) ttrait = best if ttrait is None: continue ttrait._state = strait._state matches += 1 # print(ttrait.ID) # for n,s in zip(ttrait._snames,ttrait._state): # print(f' {n:30s}: {s}') if verbose: print(f'Got {matches} matches out of {len(dtraits)}') self.setTraits(*dtraits) def decodeAdd(self,code,verbose=False): '''Try to decode make a piece from a piece of state code''' from re import split cmd, iden, typ, sta = split(fr'(? 0, \ # 'No name specified for ChangePropertyTriat' super(ChangePropertyTrait,self).__init__() self._constraints = self.encodeConstraints(numeric,wrap,min,max) self._commands = self.encodeCommands(commands) def encodeConstraints(self,numeric,wrap,min,max): isnum = f'{numeric}'.lower() iswrap = f'{wrap}'.lower() return f'{isnum},{min},{max},{iswrap}' def decodeConstraints(self,constraints): f = Trait.decodeKeys(constraints) return f[0]=='true',f[3]=='true',int(f[1]),int(f[2]) def encodeCommands(self,commands): cmds = [] for cmd in commands: # print(cmd) com = cmd[0] + ':' + cmd[1].replace(',',r'\,') + ':' + cmd[2] if cmd[2] == self.DIRECT: com += r'\,'+cmd[3].replace(',',r'\\,').replace(':',r'\:') elif cmd[2] == self.INCREMENT: com += r'\,'+cmd[3].replace(',',r'\\,').replace(':',r'\:') cmds.append(com) # print(cmds) return ','.join(cmds) def decodeCommands(self,commands): cmds = Trait.decodeKeys(commands) ret = [] for cmd in cmds: parts = Trait.decodeKeys(cmd,':') # print('parts',parts) if parts[-1][0] == self.DIRECT: parts = parts[:-1]+Trait.decodeKeys(parts[-1],',') if parts[-1][0] == self.INCREMENT: parts = parts[:-1]+Trait.decodeKeys(parts[-1],',') ret.append(parts) # print(commands,parts) return ret def getCommands(self): return self.decodeCommands(self['commands']) def setCommands(self,commands): self['commands'] = self.encodeCommands(commands) def check(self): assert len(self['name']) > 0,\ f'No name given for ChangePropertyTrait' # -------------------------------------------------------------------- class DynamicPropertyTrait(ChangePropertyTrait): ID = 'PROP' def __init__(self, *commands, name = '', value = 0, numeric = False, min = 0, max = 100, wrap = False, description = ''): '''Commands are - menu - key - Type (only 'P' for now) - Expression ''' super(DynamicPropertyTrait,self).__init__(*commands, numeric = numeric, min = min, max = max, wrap = wrap) # print(commands,'Name',name) self.setType(name = name, constraints = self._constraints, commands = self._commands, description = description) self.setState(value=value) Trait.known_traits.append(DynamicPropertyTrait) # # EOF # # ==================================================================== # From traits/globalproperty.py # -------------------------------------------------------------------- class GlobalPropertyTrait(ChangePropertyTrait): # The real value of CURRENT_ZONE causes problems when copying the # trait, since it contains slashes. Maybe a solition is to make # it a raw string with escaped slashes? No, that's already done # below when setting the type. However, the default in the Java # code is the CURRENT_ZONE real value, so setting this to the # empty string should make it be that value. ID = 'setprop' CURRENT_ZONE = 'Current Zone/Current Map/Module' NAMED_ZONE = 'Named Zone' NAMED_MAP = 'Named Map' DIRECT = 'P' def __init__(self, *commands, name = '', numeric = False, min = 0, max = 100, wrap = False, description = '', level = CURRENT_ZONE, search = ''): '''Commands are - menu - key - Type (only 'P' for now) - Expression ''' super(GlobalPropertyTrait,self).__init__(*commands, numeric = numeric, min = min, max = max, wrap = wrap) self.setType(name = name, constraints = self._constraints, commands = self._commands, description = description, level = level.replace('/',r'\/'), search = search) self.setState() Trait.known_traits.append(GlobalPropertyTrait) # # EOF # # ==================================================================== # From traits/prototype.py # -------------------------------------------------------------------- class PrototypeTrait(Trait): ID = 'prototype' def __init__(self,name=''): '''Create a prototype trait (VASSAL.counter.UsePrototype)''' super(PrototypeTrait,self).__init__() self.setType(name = name) self.setState(ignored = '') Trait.known_traits.append(PrototypeTrait) # # EOF # # ==================================================================== # From traits/place.py # -------------------------------------------------------------------- class PlaceTrait(Trait): ID = 'placemark' STACK_TOP = 0 STACK_BOTTOM = 1 ABOVE = 2 BELOW = 3 # How the LaTeX exporter organises the units. Format with # 0: the group # 1: the piece name # SKEL_PATH = (PieceWindow.TAG +r':Counters\/' + # TabWidget.TAG +r':Counters\/' + # PanelWidget.TAG +':{0}' +r'\/'+ # ListWidget.TAG +':{0} counters'+r'\/'+ # PieceSlot.TAG +':{1}') @classmethod @property def SKEL_PATH(cls): return (PieceWindow.TAG +r':Counters\/' + TabWidget.TAG +r':Counters\/' + PanelWidget.TAG +':{0}' +r'\/'+ ListWidget.TAG +':{0} counters'+r'\/'+ PieceSlot.TAG +':{1}') def __init__(self, command = '', # Context menu name key = '', # Context menu key markerSpec = '', # Full path in module markerText = 'null', # Hard coded message xOffset = 0, yOffset = 0, matchRotation = True, afterKey = '', description = '', gpid = '', # Set in JAVA, but with warning placement = ABOVE, above = False): '''Create a place marker trait (VASSAL.counter.PlaceMarker)''' super(PlaceTrait,self).__init__() self.setType(command = command, # Context menu name key = key, # Context menu key markerSpec = markerSpec, markerText = markerText, xOffset = xOffset, yOffset = yOffset, matchRotation = matchRotation, afterKey = afterKey, description = description, gpid = gpid, placement = placement, above = above) self.setState() Trait.known_traits.append(PlaceTrait) # -------------------------------------------------------------------- class ReplaceTrait(PlaceTrait): ID = 'replace' def __init__(self, command = '', # Context menu name key = '', # Context menu key markerSpec = '', # Full path in module markerText = 'null', # Hard message xOffset = 0, yOffset = 0, matchRotation = True, afterKey = '', description = '', gpid = '', # Set in JAVA placement = PlaceTrait.ABOVE, above = False): super(ReplaceTrait,self).__init__(command = command, key = key, markerSpec = markerSpec, markerText = markerText, xOffset = xOffset, yOffset = yOffset, matchRotation = matchRotation, afterKey = afterKey, description = description, gpid = gpid, placement = placement, above = above) Trait.known_traits.append(ReplaceTrait) # # EOF # # ==================================================================== # From traits/report.py # -------------------------------------------------------------------- class ReportTrait (Trait): ID = 'report' def __init__(self, *keys, nosuppress = True, description = '', report = '$location$: $newPieceName$ $menuCommand$ *', cyclekeys = '', cyclereps = ''): '''Create a report trait (VASSAL.counters.ReportActon)''' super(ReportTrait,self).__init__() esckeys = ','.join([k.replace(',',r'\,') for k in keys]) esccycl = ','.join([k.replace(',',r'\,') for k in cyclekeys]) escreps = ','.join([k.replace(',',r'\,') for k in cyclereps]) self.setType(keys = esckeys, report = report, cycleKeys = esccycl, cycleReports = escreps, description = description, nosuppress = nosuppress) self.setState(cycle = -1) Trait.known_traits.append(ReportTrait) # # EOF # # ==================================================================== # From traits/calculatedproperty.py # -------------------------------------------------------------------- class CalculatedTrait(Trait): ID = 'calcProp' def __init__(self,name='',expression='',description=''): '''Define a trait that calculates a property''' super(CalculatedTrait,self).__init__() self.setType(name = name, expression = expression, description = description) self.setState() Trait.known_traits.append(CalculatedTrait) # # EOF # # ==================================================================== # From traits/restrictcommand.py # -------------------------------------------------------------------- class RestrictCommandsTrait(Trait): ID = 'hideCmd' HIDE = 'Hide' DISABLE = 'Disable' def __init__(self, name = '', hideOrDisable = HIDE, expression = '',# Restrict when true keys = []): '''Create a layer trait (VASSAL.counter.RestrictCommands)''' super(RestrictCommandsTrait,self).__init__() encKeys = ','.join([k.replace(',',r'\,') for k in keys]) self.setType(name = name, hideOrDisable = hideOrDisable, expression = expression, keys = encKeys) self.setState(state='') def setKeys(self,keys): self['keys'] = ','.join([k.replace(',',r'\,') for k in keys]) Trait.known_traits.append(RestrictCommandsTrait) # # EOF # # ==================================================================== # From traits/label.py class LabelTraitCodes: TOP = 't' BOTTOM = 'b' CENTER = 'c' LEFT = 'l' RIGHT = 'r' PLAIN = 0 BOLD = 1 ITALIC = 2 # -------------------------------------------------------------------- class LabelTrait(Trait): ID = 'label' def __init__(self, label = None, labelKey = '', menuCommand ='Change label', fontSize = 10, background = 'none', foreground = '255,255,255', vertical = LabelTraitCodes.TOP, verticalOff = 0, horizontal = LabelTraitCodes.CENTER, horizontalOff = 0, verticalJust = LabelTraitCodes.BOTTOM, horizontalJust = LabelTraitCodes.CENTER, nameFormat = '$pieceName$ ($label$)', fontFamily = 'Dialog', fontStyle = LabelTraitCodes.PLAIN, rotate = 0, propertyName = 'TextLabel', description = '', alwaysUseFormat = False): '''Create a label trait (can be edited property)''' super(LabelTrait,self).__init__() self.setType(labelKey = labelKey, menuCommand = menuCommand, fontSize = fontSize, background = background, foreground = foreground, vertical = vertical, verticalOff = verticalOff, horizontal = horizontal, horizontalOff = horizontalOff, verticalJust = verticalJust, horizontalJust = horizontalJust, nameFormat = nameFormat, fontFamily = fontFamily, fontStyle = fontStyle, rotate = rotate, propertyName = propertyName, description = description, alwaysUseFormat = alwaysUseFormat) self.setState(label = (nameFormat if label is None else label)) Trait.known_traits.append(LabelTrait) # # EOF # # ==================================================================== # From traits/layer.py # -------------------------------------------------------------------- class LayerTrait(Trait): ID = 'emb2' def __init__(self, images = [''], newNames = None, activateName = 'Activate', activateMask = CTRL, activateChar = 'A', increaseName = 'Increase', increaseMask = CTRL, increaseChar = '[', decreaseName = '', decreaseMask = CTRL, decreaseChar = ']', resetName = '', resetKey = '', resetLevel = 1, under = False, underXoff = 0, underYoff = 0, loop = True, name = '', description = '', randomKey = '', randomName = '', follow = False, expression = '', first = 1, version = 1, # 1:new, 0:old always = True, activateKey = key('A'), increaseKey = key('['), decreaseKey = key(']'), scale = 1.): '''Create a layer trait (VASSAL.counter.Embellishment)''' super(LayerTrait,self).__init__() if newNames is None and images is not None: newNames = ['']*len(images) self.setType( activateName = activateName, activateMask = activateMask, activateChar = activateChar, increaseName = increaseName, increaseMask = increaseMask, increaseChar = increaseChar, decreaseName = decreaseName, decreaseMask = decreaseMask, decreaseChar = decreaseChar, resetName = resetName, resetKey = resetKey, resetLevel = resetLevel, under = under, underXoff = underXoff, underYoff = underYoff, images = ','.join(images), newNames = ','.join(newNames), loop = loop, name = name, randomKey = randomKey, randomName = randomName, follow = follow, expression = expression, first = first, version = version, always = always, activateKey = activateKey, increaseKey = increaseKey, decreaseKey = decreaseKey, description = description, scale = scale) self.setState(level=1) Trait.known_traits.append(LayerTrait) # # EOF # # ==================================================================== # From traits/globalcommand.py # -------------------------------------------------------------------- class GlobalCommandTrait(Trait): ID = 'globalkey' def __init__(self, commandName = '', key = '', # Command received globalKey = '', # Command to send to targets properties = '', # Filter target on this expression ranged = False, range = 1, reportSingle = True, fixedRange = True, rangeProperty = '', description = '', deskSelect = '-1', target = ''): '''Create a global key command in piece (VASSAL.counters.CounterGlobalKeyCommand)''' self.setType(commandName = commandName, key = key, globalKey = globalKey, properties = properties, ranged = ranged, range = range, reportSingle = reportSingle, fixedRange = fixedRange, rangeProperty = rangeProperty, description = description, deskSelect = deskSelect, target = target) self.setState() Trait.known_traits.append(GlobalCommandTrait) # # EOF # # ==================================================================== # From traits/globalhotkey.py # -------------------------------------------------------------------- class GlobalHotkeyTrait(Trait): ID = 'globalhotkey' def __init__(self, name = '', # Command received key = '', # Command key received globalHotkey = '', # Key to send description = ''): '''Create a global key command in piece (VASSAL.counters.GlobalHotkey)''' self.setType(name = name, key = key, globalHotkey = globalHotkey, description = description) self.setState() Trait.known_traits.append(GlobalHotkeyTrait) # # EOF # # ==================================================================== # From traits/nostack.py # -------------------------------------------------------------------- class NoStackTrait(Trait): ID = 'immob' NORMAL_SELECT = '' SHIFT_SELECT = 'i' CTRL_SELECT = 't' ALT_SELECT = 'c' NEVER_SELECT = 'n' NORMAL_BAND_SELECT = '' ALT_BAND_SELECT = 'A' ALT_SHIFT_BAND_SELECT = 'B' NEVER_BAND_SELECT = 'Z' NORMAL_MOVE = 'N' SELECT_MOVE = 'I' NEVER_MOVE = 'V' NORMAL_STACK = 'L' NEVER_STACK = 'R' IGNORE_GRID = 'g' def __init__(self, select = NORMAL_SELECT, bandSelect = NORMAL_BAND_SELECT, move = NORMAL_MOVE, canStack = False, ignoreGrid = False, description = ''): '''No stacking trait''' selectionOptions = (select + (self.IGNORE_GRID if ignoreGrid else '') + bandSelect) movementOptions = move stackingOptions = self.NORMAL_STACK if canStack else self.NEVER_STACK '''Create a mark trait (static property)''' super(NoStackTrait,self).__init__() self.setType(selectionOptions = selectionOptions, movementOptions = movementOptions, stackingOptions = stackingOptions, description = description) self.setState() Trait.known_traits.append(NoStackTrait) # # EOF # # ==================================================================== # From traits/deselect.py # -------------------------------------------------------------------- class DeselectTrait(Trait): ID = 'deselect' THIS = 'D' # Deselect only this piece ALL = 'A' # Deselect all pieces ONLY = 'S' # Select this piece only def __init__(self, command = '', key = '', description = '', unstack = False, deselect = THIS): '''Create a deselect trait''' super(DeselectTrait,self).__init__() self.setType(command = command, key = key, description = description, unstack = unstack, deselect = deselect) self.setState() Trait.known_traits.append(DeselectTrait) # # EOF # # ==================================================================== # From traits/restrictaccess.py # -------------------------------------------------------------------- class RestrictAccessTrait(Trait): ID = 'restrict' def __init__(self, sides = [], byPlayer = False, noMovement = True, description = '', owner = '',): '''Create a layer trait (VASSAL.counter.Restricted)''' super(RestrictAccessTrait,self).__init__() encSides = ','.join(sides) self.setType(sides = encSides, byPlayer = byPlayer, noMovement = noMovement, description = description) self.setState(owner=owner) Trait.known_traits.append(RestrictAccessTrait) # # EOF # # ==================================================================== # From traits/rotate.py # -------------------------------------------------------------------- class RotateTrait(Trait): ID = 'rotate' def __init__(self, nangles = 6, rotateCWKey = key(']'), rotateCCWKey = key('['), rotateCW = 'Rotate CW', rotateCCW = 'Rotate CCW', rotateRndKey = '', rotateRnd = '', name = 'Rotate', description = 'Rotate piece', rotateDirectKey = '', rotateDirect = '', directExpression = '', directIsFacing = True, angle = 0): '''Create a Rotate trait''' super(RotateTrait,self).__init__() if nangles == 1: self.setType(nangles = nangles, rotateKey = rotateCWKey, rotate = rotateCW, rotateRndKey = rotateRndKey, rotateRnd = rotateRnd, name = name, description = description, rotateDirectKey = rotateDirectKey, rotateDirect = rotateDirect, directExpression = directExpression, directIsFacing = directIsFacing) else: self.setType(nangles = nangles, rotateCWKey = rotateCWKey, rotateCCWKey = rotateCCWKey, rotateCW = rotateCW, rotateCCW = rotateCCW, rotateRndKey = rotateRndKey, rotateRnd = rotateRnd, name = name, description = description, rotateDirectKey = rotateDirectKey, rotateDirect = rotateDirect, directExpression = directExpression, directIsFacing = directIsFacing) self.setState(angle = int(angle) if nangles > 1 else float(angle)) Trait.known_traits.append(RotateTrait) # # EOF # # ==================================================================== # From traits/stack.py # -------------------------------------------------------------------- class StackTrait(Trait): ID = 'stack' def __init__(self, board = '', x = '', y = '', pieceIds = [], layer = -1): '''Create a stack trait in a save file''' self.setType() # NAME # print('Piece IDs:',pieceIds) self.setState(board = board, x = x, y = y, pieceIds = ';'.join([str(p) for p in pieceIds]), layer = f'@@{layer}') Trait.known_traits.append(StackTrait) # # EOF # # ==================================================================== # From traits/mark.py # -------------------------------------------------------------------- class MarkTrait(Trait): ID = 'mark' def __init__(self,name='',value=''): '''Create a mark trait (static property)''' super(MarkTrait,self).__init__() self.setType(name = name) self.setState(value = value) Trait.known_traits.append(MarkTrait) # # EOF # # ==================================================================== # From traits/mask.py # -------------------------------------------------------------------- # Inset # obs;88,130;ag_hide_1.png;Reveal;I;?;sides:Argentine;Peek;;true;; # obs;88,130;ag_hide_1.png;Reveal;I;?;side:Argentine;;;true;; # # Peek # obs;88,130;ag_hide_1.png;Reveal;P89,130;?;sides:Argentine;Peek;;true;; # # Image # class MaskTrait(Trait): ID = 'obs' INSET = 'I' BACKGROUND = 'B' PEEK = 'P' IMAGE = 'G' INSET2 = '2' PLAYER = 'player:' SIDE = 'side:' SIDES = 'sides:' def __init__(self, keyCommand = '', imageName = '', hideCommand = '', displayStyle = '', peekKey = '', ownerImage = '', maskedName = '?', access = '',#? peekCommand = '', description = '', autoPeek = True, dealKey = '', dealExpr = ''): '''Create a masking trait''' super(MaskTrait,self).__init__() disp = displayStyle if displayStyle == self.PEEK: disp += peekKey elif displayStyle == self.IMAGE: disp += ownerImage acc = self.PLAYER if isinstance(access,list): acc = self.SIDES + ':'.join(access) elif access.startswith('player'): acc = self.PLAYER elif access.startswith('side'): acc = self.SIDE self.setType(keyCommand = keyCommand, imageImage = imageName, hideCommand = hideCommand, displayStyle = disp, maskedName = maskedName, access = acc, # ? peekCommand = peekCommand, description = description, autoPeek = autoPeek, dealKey = dealKey, dealExpr = dealExpr) self.setState(value='null') @classmethod def peekDisplay(cls,key):#Encoded key return cls.PEEK + key @classmethod def peekImage(cls,ownerImage): return cls.IMAGE + ownerImage @classmethod def sides(cls,*names): return cls.SIDES+':'.join(names) Trait.known_traits.append(MaskTrait) # # EOF # # ==================================================================== # From traits/trail.py # -------------------------------------------------------------------- class TrailTrait(Trait): ID = 'footprint' def __init__(self, key = key('T'), name = 'Movement Trail', localVisible = False, globalVisible = False, radius = 10, fillColor = rgb(255,255,255), lineColor = rgb(0,0,0), activeOpacity = 100, inactiveOpacity = 50, edgesBuffer = 20, displayBuffer = 30, lineWidth = 5, turnOn = '', turnOff = '', reset = '', description = 'Enable or disable movement trail'): ''' Create a movement trail trait ( VASSAL.counters.Footprint)''' super(TrailTrait,self).__init__() lw = (lineWidth if isinstance(lineWidth,str) and lineWidth.startswith('{') else int(lineWidth)) ra = (radius if isinstance(radius,str) and radius.startswith('{') else int(radius)) self.setType(key = key,# ENABLE KEY name = name,# MENU localVisible = localVisible,# LOCAL VISABLE globalVisible = globalVisible,# GLOBAL VISABLE radius = ra,# RADIUS fillColor = fillColor,# FILL COLOR lineColor = lineColor,# LINE COLOR activeOpacity = activeOpacity,# ACTIVE OPACITY inactiveOpacity = inactiveOpacity,# INACTIVE OPACITY edgesBuffer = edgesBuffer,# EDGES BUFFER displayBuffer = displayBuffer,# DISPLAY BUFFER lineWidth = lw,# LINE WIDTH turnOn = turnOn,# TURN ON KEY turnOff = turnOff,# TURN OFF KEY reset = reset,# RESET KEY description = description) # DESC self.setState(isGlobal = False, map = '', points = 0, # POINTS (followed by [; [X,Y]*] init = False) Trait.known_traits.append(TrailTrait) # # EOF # # ==================================================================== # From traits/delete.py # -------------------------------------------------------------------- class DeleteTrait(Trait): ID = 'delete' def __init__(self, name = 'Delete', key = key('D')): '''Create a delete trait (VASSAL.counters.Delete)''' super(DeleteTrait,self).__init__() self.setType(name = name, key = key, dummy = '') self.setState() Trait.known_traits.append(DeleteTrait) # # EOF # # ==================================================================== # From traits/sendto.py # -------------------------------------------------------------------- class SendtoTrait(Trait): ID = 'sendto' LOCATION = 'L' ZONE = 'Z' REGION = 'R' GRID = 'G' def __init__(self, mapName = '', boardName = '', name = '', key = key('E'), restoreName = 'Restore', restoreKey = key('R'), x = 200, y = 200, xidx = 0, yidx = 0, xoff = 1, yoff = 1, description = '', destination = LOCATION, zone = '', region = '', expression = '', position = ''): '''Create a send to trait (VASSAL.counter.SendToLocation)''' self.setType(name = name,# NAME key = key,# KEY , MODIFIER mapName = mapName,# MAP boardName = boardName,# BOARD x = x, y = y,# X ; Y restoreName = restoreName,# BACK restoreKey = restoreKey,# KEY , MODIFIER xidx = xidx, yidx = yidx,# XIDX ; YIDX xoff = xoff, yoff = yoff,# XOFF ; YOFF description = description,# DESC destination = destination,# DEST type zone = zone,# ZONE region = region,# REGION expression = expression,# EXPRESSION position = position) # GRIDPOS self.setState(backMap = '', backX = '', backY = '') Trait.known_traits.append(SendtoTrait) # # EOF # # ==================================================================== # From traits/moved.py # -------------------------------------------------------------------- class MovedTrait(Trait): ID = 'markmoved' def __init__(self, image = 'moved.gif', xoff = 36, yoff = -38, name = 'Mark moved', key = key('M'), dummy = '' # Description # ignoreSame = True ): '''Create a moved trait (VASSAL.counters.MovementMarkable)''' super(MovedTrait,self).__init__() self.setType(image = image, xoff = xoff, yoff = yoff, name = name, key = key, dummy = dummy, # Description # ignoreSame = ignoreSame ) self.setState(moved = False) Trait.known_traits.append(MovedTrait) # # EOF # # ==================================================================== # From traits/skel.py # # EOF # # ==================================================================== # From traits/submenu.py # -------------------------------------------------------------------- class SubMenuTrait(Trait): ID = 'submenu' def __init__(self, subMenu = '', # Title keys = [], # Keys description = ''): '''Create a sub menu (VASSAL.counters.SubMenu)''' self.setType(subMenu = subMenu, # CLONEKEY keys = ','.join([k.replace(',',r'\,') for k in keys]), description = description) self.setState() # PROPERTY COUNT (followed by [; KEY; VALUE]+) def setKeys(self,keys): '''Set the keys''' self['keys'] = ','.join([k.replace(',',r'\,') for k in keys]) Trait.known_traits.append(SubMenuTrait) # # EOF # # ==================================================================== # From traits/basic.py # -------------------------------------------------------------------- class BasicTrait(Trait): ID = 'piece' def __init__(self, name = '', filename = '', # Can be empty gpid = '', # Can be empty cloneKey = '', # Deprecated deleteKey = ''): # Deprecated '''Create a basic unit (VASSAL.counters.BasicPiece)''' self.setType(cloneKey = cloneKey, # CLONEKEY deleteKey = deleteKey, # DELETEKEY filename = filename, # IMAGE name = name) # NAME self.setState(map = 'null', # MAPID (possibly 'null') x = 0, y = 0, gpid = gpid, properties = 0) # PROPERTY COUNT (followed by [; KEY; VALUE]+) Trait.known_traits.append(BasicTrait) # # EOF # # ==================================================================== # From traits/trigger.py # -------------------------------------------------------------------- class TriggerTrait(Trait): ID = 'macro' WHILE = 'while' UNTIL = 'until' COUNTED = 'counted' # - Always one "do ... while" def __init__(self, name = '', command = '', # Context menu name key = '', # Context menu key property = '', # Enable/Disable watchKeys = [], actionKeys = [], # What to do loop = False, preLoop = '', # Key postLoop = '', # Key loopType = COUNTED, # Loop type whileExpression = '', untilExpression = '', count = 0, index = False, indexProperty = '', indexStart = '', indexStep = ''): '''Create a layer trait (VASSAL.counter.Trigger)''' super(TriggerTrait,self).__init__() encWKeys = Trait.encodeKeys(watchKeys, ',') encAKeys = Trait.encodeKeys(actionKeys,',') self.setType(name = name, command = command, # Context menu name key = key, # Context menu key property = property, # Enable/Disable watchKeys = encWKeys, actionKeys = encAKeys, # What to do loop = loop, preLoop = preLoop, # Key postLoop = postLoop, # Key loopType = loopType, # Loop type whileExpression = whileExpression, untilExpression = untilExpression, count = count, index = index, indexProperty = indexProperty, indexStart = indexStart, indexStep = indexStep) self.setState(state='') def getActionKeys(self): return Trait.decodeKeys(self['actionKeys'],',') def getWatchKeys(self): return Trait.decodeKeys(self['watchKeys'],',') def setActionKeys(self,keys): self['actionKeys'] = Trait.encodeKeys(keys,',') def setWatchKeys(self,keys): self['watchKeys'] = Trait.encodeKeys(keys,',') Trait.known_traits.append(TriggerTrait) # # EOF # # ==================================================================== # From traits/nonrect.py # -------------------------------------------------------------------- class NonRectangleTrait(Trait): ID = 'nonRect2' CLOSE = 'c' MOVETO = 'm' LINETO = 'l' CUBICTO = 'l' QUADTO = 'l' def __init__(self, scale = 1., filename = '', path = [], image = None): '''Create a NonRectangle trait (static property)''' super(NonRectangleTrait,self).__init__() l = [] if len(filename) > 0: l.append(f'n{filename}') if len(path) <= 0: path = self.getShape(image) if len(path) > 0: # print(path) l += [f'{p[0]},{int(p[1])},{int(p[2])}' if len(p) > 2 else p for p in path] self.setType(scale = scale, code = ','.join(l)) self.setState() @classmethod def getShape(cls,buffer): if buffer is None: return [] from io import BytesIO image = buffer if image[:5] == b'> 4], cls.ENC_MAP[b & 0x0F] out.write(pair[0]) out.write(pair[1]) # -------------------------------------------------------------------- @classmethod def writeInZip(cls,z,key,lines,filename='savedGame'): '''Write a save file in a zip file (VMod)''' # open the save file in the archive with z.open(filename,'w') as save: # Write header save.write(cls.VCS_HEADER) # Split key pair = cls.ENC_MAP[(key & 0xF0) >> 4], cls.ENC_MAP[(key & 0x0F)] save.write(pair[0]) save.write(pair[1]) # Form content content = cls.VK_ESC.join(lines) # Write each character as two for c in content: cls.writeByte(save, c, key) # -------------------------------------------------------------------- @classmethod def writeSave(cls,file,key,lines,savedata=None,moduledata=None): '''Write a save file''' from zipfile import ZipFile, ZIP_DEFLATED # We open the save file as a zip file with ZipFile(file,'w',ZIP_DEFLATED) as z: cls.writeInZip(z,key,lines,filename='savedGame') if savedata is not None: z.writestr(VSav.SAVE_DATA,savedata) z.writestr(VMod.MODULE_DATA,moduledata) # ==================================================================== # # VSave file # class SaveFile: def __init__(self,game,firstid=None): '''Creates a save file to add positions to''' from time import time self._game = game self._counters = {} self._stacks = {} self._pieces = self._game.getPieces(asdict=True) self._nextId = (int(time()*1000) - 360000 if firstid is None else firstid) def add(self,grid,**kwargs): '''Add pieces to the save. Parameters ---------- grid : BaseGrid Grid to add pieces to kwargs : dict Either a map from piece name to hex position, Or a map from hex position to list of pieces ''' for k,v in kwargs.items(): # print('Add to save',k,v) self._add(grid,k,v) def _add(self,grid,k,v): '''Add to the save''' # print(f'Adding {k} -> {v}') loc = None piece = self._pieces.get(k,None) pieces = [] boardName = grid.getBoard()['name'] # print(f'Board name: {boardName}') if piece is not None: # print(f'Key is piece: {k}->{piece}') pieces.append(piece) loc = v else: # Key is not a piece name, so a location loc = k # Convert value to iterable try: iter(v) except: v = list(v) for vv in v: if isinstance(vv,PieceSlot): pieces.append(vv) continue if isinstance(vv,str): piece = self._pieces.get(vv,None) if piece is None: continue pieces.append(piece) # print(f'Loc: {loc}') if len(pieces) < 1: return if (boardName,loc) not in self._stacks: # print(f'Adding stack {boardName},{loc}') coord = grid.getLocation(loc) if coord is None: print(f'did not get coordinates from {loc}') return self._stacks[(boardName,loc)] = { 'x': coord[0], 'y': coord[1], 'pids': [] } place = self._stacks[(boardName,loc)] for piece in pieces: name = piece['entryName'] count = self._counters.get(name,None) if count is None: count = {'pid': self._nextId, 'piece': piece, 'board': boardName, 'x': place['x'], 'y': place['y'], } self._counters[name] = count self._nextId += 1 # print(f'Adding to stack {boardName},{loc}: {count[0]}') place['pids'].append(count['pid']) def getLines(self): '''Get the final lines of code''' key = 0xAA # fixed key lines = ['begin_save', '', '\\'] self._pieceLines(lines) self._otherLines(lines) lines.append('end_save') return lines def _pieceLines(self,lines): '''Add piece lines to save file Parameters ---------- lines : list The lines to add ''' # print(self._counters) for counter in self._counters.values(): iden = counter['pid'] piece = counter['piece'] traits = piece.getTraits() traits = Trait.flatten(traits,self._game) # Get last - trait (basic piece), and modify coords basic = traits[-1] basic['map'] = counter['board'] basic['x'] = counter['x'] basic['y'] = counter['y'] # Set old location if possible parent = piece.getParent(DummyElement,checkTag=False) if parent is not None and parent._node.nodeName == AtStart.TAG: oldLoc = parent['location'] oldBoard = parent['owningBoard'] oldMap = self._game.getBoards()[oldBoard].getMap()['mapName'] oldX = parent['x'] oldY = parent['y'] oldZone = None zones = self._game.getBoards()[oldBoard].getZones() for zone in zones.values(): grid = zone.getGrids()[0] if grid is None: continue coord = grid.getLocation(oldLoc) if coord is None: continue oldZone = zone['name'] oldX = coord[0] oldY = coord[1] break if oldZone is not None: basic['properties'] = \ f'6;OldZone;{oldZone};OldLocationName;{oldLoc};'+\ f'OldX;{oldX};OldY;{oldY};'+\ f'OldBoard;{oldBoard};OldMap;{oldMap}' else: basic['properties'] = \ f'5;OldLocationName;{oldLoc};'+\ f'OldX;{oldX};OldY;{oldY};'+\ f'6;OldBoard;{oldBoard};OldMap;{oldMap}' for trait in traits: if trait.ID == TrailTrait.ID: trait['map'] = oldMap trait['points'] = f'1;{oldX},{oldY}' trait['init'] = True # Wrapper wrap = DummyWithTraits(self._game,traits=[]) wrap.setTraits(*traits,iden=str(iden)) lines.append(wrap._node.childNodes[0].nodeValue+'\\') layer = -1 for key,dat in self._stacks.items(): pids = dat.get('pids',None) x = dat['x'] y = dat['y'] if pids is None or len(pids) < 1: print(f'No pieces at {key[0]},{key[1]}') continue iden = self._nextId self._nextId += 1 stack = StackTrait(board=key[0],x=x,y=y,pieceIds=pids,layer=layer) layer = 1 wrap = DummyWithTraits(self._game,traits=[]) wrap.setTraits(stack,iden=iden) lines.append(wrap._node.childNodes[0].nodeValue+'\\') def _otherLines(self,lines): '''Add other lines to save''' lines.append('UNMASK\tnull') for r in self._game.getPlayerRoster(): lines.extend(r.encode()) for n in self._game.getNotes(single=False): lines.extend(n.encode()) setupStack = False for m in self._game.getMaps(asdict=False): for bp in m.getBoardPicker(single=False): lines.extend(bp.encode()) if not setupStack: atstart = m.getAtStarts(single=False) if atstart and len(atstart) > 0: lines.append('SETUP_STACK') setupStack = True # for tk,tt in self._game.getTurnTracks(asdict=True): # lines.extend(tt.encode()) # -------------------------------------------------------------------- class SaveData(ModuleData): def __init__(self,root=None): '''Convinience wrapper''' super(SaveData,self).__init__(root=root) # ==================================================================== # From vsav.py # -------------------------------------------------------------------- class VSav: SAVE_DATA = 'savedata' def __init__(self,build,vmod): '''Create a VASSAL save file programmatically Parameters ---------- build : xml.dom.Document `buildFile.xml` as XML vmod : VMod Module file ''' from time import time self._vmod = vmod self._game = build.getGame() self._start = int(time()*1000) def createSaveData(self,description=None): '''Create `savedgame`''' desc = (self._game['description'] if description is None else description) self._saveData = SaveData(root=None) data = self._saveData.addData() data.addVersion (version =self._game['version']) data.addVASSALVersion(version =self._game['VassalVersion']) data.addDescription (description=desc) data.addDateSaved (milisecondsSinceEpoch=self._start) return self._saveData def createModuleData(self): '''Create `moduleData`''' self._moduleData = ModuleData() data = self._moduleData.addData() data.addVersion (version =self._game['version']) data.addVASSALVersion(version =self._game['VassalVersion']) data.addName (name =self._game['name']) data.addDescription (description=self._game['description']) data.addDateSaved (milisecondsSinceEpoch=self._start) return self._moduleData def addSaveFile(self): '''Add a save file to the module Returns ------- vsav : SaveFile Save file to add content to ''' self._saveFile = SaveFile(game=self._game,firstid=self._start) return self._saveFile def run(self,savename='Save.vsav',description=None): '''Run this to generate the save file Parameters ---------- savename : str Name of save file to write description : str Short description of the save file ''' from zipfile import ZipFile, ZIP_DEFLATED self.createSaveData(description=description) self.createModuleData() with self._vmod.getInternalFile(savename,'w') as vsav: with ZipFile(vsav,'w',ZIP_DEFLATED) as zvsav: # The key is set to 0xAA (alternating ones and zeros) SaveIO.writeInZip(zvsav,0xAA,self._saveFile.getLines()) zvsav.writestr(VMod.MODULE_DATA, self._moduleData.encode()) zvsav.writestr(VSav.SAVE_DATA, self._saveData.encode()) # # EOF # # ==================================================================== # From vmod.py # ==================================================================== # # Wrapper around a module # class VMod: BUILD_FILE = 'buildFile.xml' MODULE_DATA = 'moduledata' def __init__(self,filename,mode): '''Interface to VASSAL Module (a Zip file)''' self._mode = mode self._vmod = self._open(filename,mode) def __enter__(self): '''Enter context''' return self def __exit__(self,*e): '''Exit context''' self._vmod.close() return None def _open(self,filename,mode): '''Open a file in VMod''' from zipfile import ZipFile, ZIP_DEFLATED return ZipFile(filename,mode,compression=ZIP_DEFLATED) def removeFiles(self,*filenames): '''Open a temporary zip file, and copy content from there to that file, excluding filenames mentioned in the arguments. Then close current file, rename the temporary file to this, and reopen in 'append' mode. The deleted files are returned as a dictionary. Parameters ---------- filenames : tuple List of files to remove from the VMOD Returns ------- files : dict Dictionary from filename to content of the removed files. Note, the VMOD is re-opened in append mode after this ''' from tempfile import mkdtemp from zipfile import ZipFile from shutil import move, rmtree from os import path tempdir = mkdtemp() ret = {} try: tempname = path.join(tempdir, 'new.zip') with self._open(tempname, 'w') as tmp: for item in self._vmod.infolist(): data = self._vmod.read(item.filename) if item.filename not in filenames: tmp.writestr(item, data) else: ret[item.filename] = data name = self._vmod.filename self._vmod.close() move(tempname, name) self._mode = 'a' self._vmod = self._open(name,'a') finally: rmtree(tempdir) # Return the removed files return ret def fileName(self): '''Get name of VMod file''' return self._vmod.filename def replaceFiles(self,**files): '''Replace existing files with new files Parameters ---------- files : dict Dictionary that maps file name to content ''' self.removeFiles(*list(files.keys())) self.addFiles(**files); def addFiles(self,**files): '''Add a set of files to this Parameters ---------- files : dict Dictionary that maps file name to content. ''' for filename,data in files.items(): self.addFile(filename,data) def addFile(self,filename,content): '''Add a file to this Parameters ---------- filename : str File name in module content : str File content Returns ------- element : File The added element ''' self._vmod.writestr(filename,content) def addExternalFile(self,filename,target=None): '''Add an external file element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : ExternalFile The added element ''' if target is None: target = filename self._vmod.write(filename,target) def getFileNames(self): '''Get all filenames in module''' return self._vmod.namelist() def getFileMapping(self): '''Get mapping from short name to full archive name''' from pathlib import Path names = self.getFileNames() return {Path(p).stem: str(p) for p in names} def getFiles(self,*filenames): '''Return named files as a dictionary. Parameters ---------- filenames : tuple The files to get Returns ------- files : dict Mapping of file name to file content ''' fn = self.getFileNames() ret = {} for f in filenames: if f not in fn: continue ret[f] = self._vmod.read(f) return ret def getDOM(self,filename): '''Get content of a file decoded as XML DOM Parameters ---------- filename : str Name of file in module ''' from xml.dom.minidom import parseString r = self.getFiles(filename) if filename not in r: raise RuntimeError(f'No {filename} found!') return parseString(r[filename]) def getBuildFile(self): '''Get the buildFile.xml decoded as a DOM tree''' return self.getDOM(VMod.BUILD_FILE) def getModuleData(self): '''Get the moduledata decoded as a DOM tree''' return self.getDOM(VMod.MODULE_DATA) def getInternalFile(self,filename,mode): return self._vmod.open(filename,mode) def addVSav(self,build): '''Add a `VSav` element to this Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : VSav The added element ''' return VSav(build=build,vmod=self) # # EOF # # ==================================================================== # From upgrade.py class VLogUpgrader: def __init__(self, vmodFileName, vlogFileName, verbose=False): self._readVModFile(vmodFileName,verbose) self._readVLogFile(vlogFileName,verbose) def _readVModFile(self,vmodFileName,verbose=False): with VMod(vmodFileName, 'r') as vmod: self._build = BuildFile(vmod.getBuildFile()) self._game = self._build.getGame() self._vmod_pieces = {} for piece in self._game.getPieces(): name, piece = self._expandPiece(piece,verbose) self._vmod_pieces[name] = piece def _expandPiece(self,piece,verbose=False): traits = piece.getTraits(); newTraits = Trait.flatten(traits, game=self._game,verbose=verbose) piece.setTraits(*newTraits) name = newTraits[-1]['name'] return name, piece def _readVLogFile(self,vlogFileName,verbose=False): key, lines, sdata, mdata = SaveIO.readSave(vlogFileName, alsometa=True) self._key = key self._lines = lines self._save_data = sdata self._meta_data = mdata self._vlog_pieces = {} for line in self._lines: iden, name, piece = self._vlogPiece(line,verbose) if piece is None: continue vmod_piece = self._vmod_pieces.get(name,None) if vmod_piece is None: print(f'Did not find piece "{name}" in vmod') vmod_piece = piece vmod_piece.copyStates(piece) self._vlog_pieces[iden] = {'name': name, 'vlog': piece, 'vmod': vmod_piece} def _vlogPiece(self,line,verbose=False): from re import match m = match(r'^\+/([0-9]+)/.*;([a-z0-9_]+)\.png.*',line) if m is None: return None,None,None iden = int(m.group(1)) piece = PieceSlot(None) piece.setTraits(*piece.decodeAdd(line,verbose),iden=iden) basic = piece.getTraits()[-1] return iden,basic['name'],piece def _newLine(self,line,verbose): self._new_lines.append(line) if verbose: print(line) def upgrade(self,shownew=False,verbose=False): self._new_lines = [] for line in self._lines: add_line = self.newDefine(line,verbose) if add_line: self._newLine(add_line,shownew) continue cmd_line = self.newCommand(line,verbose) if cmd_line: self._newLine(cmd_line,shownew) continue oth_line = self.other(line,verbose) if oth_line: self._newLine(oth_line,shownew) continue self._newLine(line,shownew) def newCommand(self,line,verbose=False): from re import match m = match(r'LOG\s+([+MD])/([0-9]+)/([^/]+)(.*)',line) if not m: return None cmd = m.group(1) iden = int(m.group(2)) more = m.group(3) if more == 'stack': return None vp = self._vlog_pieces.get(iden,None) if vp is None: print(f'Piece {iden} not found: "{line}"') return None if cmd == '+' or cmd == 'M': return None # Get the code code = more + m.group(4) # Decode the states from the code into the old piece vp['vlog'].decodeStates(code,verbose) # Get the previsous state from the new piece old = vp['vmod'].encodedStates() # Copy states from the old piece to the new piece vp['vmod'].copyStates(vp['vlog'],verbose) # Get the new state code from the new piece new = vp['vmod'].encodedStates() newline = 'LOG\t'+cmd+'/'+str(iden)+'/'+new+'/'+old+'\\\\' # print('WAS',line) # print('NOW',newline) return newline def newDefine(self,line,verbose): from re import match m = match(r'\+/([0-9]+)/([^/]+).*',line) if not m: return False iden = int(m.group(1)) more = m.group(2) if more == 'stack': return False vp = self._vlog_pieces.get(iden,None) if vp is None: print(f'Piece {iden} not known') old = vp['vlog'] new = vp['vmod'] old_add = old._node.childNodes[0].nodeValue; new_add = new.encodeAdd(*new.getTraits(),iden=iden,verbose=verbose); return new_add def other(self,line,verbose=False): return None def write(self,outFileName,verbose=False): SaveIO.writeSave(outFileName, key = 0xAA, lines = self._new_lines, savedata = self._save_data, moduledata = self._meta_data) # # EOF # # ==================================================================== # From exporter.py class Exporter: def __init__(self): '''Base class for exporters''' pass def setup(self): '''Should be defined to set-up for processing, for example generating images and such. This is executed in a context where the VMod file has been opened for writing via `self._vmod`. Thus, files can be added to the module at this stage. ''' pass def createBuildFile(self,ignores='(.*markers?|all|commons|[ ]+)'): '''Should be defined to make the `buildFile.xml` document Parameters ---------- ignores : str Regular expression to match ignored categories for factions determination. Python's re.fullmatch is applied to this regular exression against chit categories. If the pattern is matched, then the chit is not considered to belong to a faction. ''' pass def createModuleData(self): '''Should be defined to make the `moduledata` document''' pass def run(self,vmodname,patch=None): '''Run the exporter to generate the module ''' with VMod(vmodname,'w') as vmod: self._vmod = vmod self.setup() self.createBuildFile() self.createModuleData() self.runPatch(patch) self._vmod.addFiles(**{VMod.BUILD_FILE : self._build.encode(), VMod.MODULE_DATA : self._moduleData.encode()}) Verbose().message('Created VMOD') def runPatch(self,patch): '''Run user specified patch script. The script should define ``` def patch(buildFile,moduleData,vmod,verbose): ``` where `buildFile` is the `buildFile.xml` and `moduleData` are the XML documents as `xml.dom.Document` objects, `vmod` is a `VMod` instance, and `verbose` is a `bool` selecting verbose mode or not. ''' if patch is None or patch == '': return from importlib.util import spec_from_file_location, module_from_spec from pathlib import Path from sys import modules p = Path(patch) with VerboseGuard(f'Will patch module with {p.stem}.patch function') \ as v: spec = spec_from_file_location(p.stem, p.absolute()) module = module_from_spec(spec) spec.loader.exec_module(module) modules[p.stem] = module # Patch must accept xml.dom.document,xml.dom.document,ZipFile module.patch(self._build, self._moduleData, self._vmod, Verbose().verbose) # ==================================================================== # From latexexporter.py # ==================================================================== # # Exporter class # class LaTeXExporter(Exporter): class Specials: BATTLE_MARK = 'wgBattleMarker' BATTLE_CTRL = 'wgBattleCtrl' BATTLE_CALC = 'wgBattleCalc' BATTLE_UNIT = 'wgBattleUnit' ODDS_MARK = 'wgOddsMarker' HIDDEN_NAME = 'wg hidden unit' class Keys: MARK_BATTLE = key(NONE,0)+',wgMarkBattle' CLEAR_BATTLE = key(NONE,0)+',wgClearBattle' CLEAR_ALL_BATTLE = key(NONE,0)+',wgClearAllBattle' ZERO_BATTLE = key(NONE,0)+',wgZeroBattle' INCR_BATTLE = key(NONE,0)+',wgIncrBattle' SET_BATTLE = key(NONE,0)+',wgSetBattle' GET_BATTLE = key(NONE,0)+',wgGetBattle' MARK_ODDS = key(NONE,0)+',wgMarkOdds' MARK_RESULT = key(NONE,0)+',wgMarkResult' CLEAR_MOVED = key(NONE,0)+',wgClearMoved' ZERO_BATTLE_AF = key(NONE,0)+',wgZeroBattleAF' ZERO_BATTLE_DF = key(NONE,0)+',wgZeroBattleDF' ZERO_BATTLE_FRAC = key(NONE,0)+',wgZeroBattleFrac' ZERO_BATTLE_ODDS = key(NONE,0)+',wgZeroBattleOdds' ZERO_BATTLE_SHFT = key(NONE,0)+',wgZeroBattleShift' ZERO_BATTLE_IDX = key(NONE,0)+',wgZeroBattleIdx' CALC_BATTLE_AF = key(NONE,0)+',wgCalcBattleAF' CALC_BATTLE_DF = key(NONE,0)+',wgCalcBattleDF' CALC_BATTLE_FRAC = key(NONE,0)+',wgCalcBattleFrac' CALC_BATTLE_ODDS = key(NONE,0)+',wgCalcBattleOdds' CALC_BATTLE_SHFT = key(NONE,0)+',wgCalcBattleShift' CALC_BATTLE_IDX = key(NONE,0)+',wgCalcBattleIdx' CALC_BATTLE_RES = key(NONE,0)+',wgCalcBattleResult' CLEAR_BATTLE_PHS = key(NONE,0)+',wgClearBattlePhs' RESOLVE_BATTLE = key(NONE,0)+',wgResolveBattle' ROLL_DICE = key(NONE,0)+',wgRollDice' DICE_INIT_KEY = key(NONE,0)+',wgInitDice' CLEAR_KEY = key('C') CLEAR_ALL_KEY = key('C',CTRL_SHIFT) DELETE_KEY = key('D') ELIMINATE_KEY = key('E') FLIP_KEY = key('F') TRAIL_KEY = key('T') RESTORE_KEY = key('R') MARK_KEY = key('X') RESOLVE_KEY = key('Y') ROTATE_CCWKey = key('[') ROTATE_CWKey = key(']') CHARTS_KEY = key('A',ALT) OOB_KEY = key('B',ALT) COUNTERS_KEY = key('C',ALT) DEAD_KEY = key('E',ALT) DICE_KEY = key('6',ALT) RECALC_ODDS = key('X',CTRL_SHIFT) class Globals: BATTLE_COUNTER = 'wgBattleCounter' CURRENT_BATTLE = 'wgCurrentBattle' CURRENT_ATTACKER = 'wgCurrentAttacker' BATTLE_NO = 'wgBattleNo' BATTLE_AF = 'wgBattleAF' BATTLE_DF = 'wgBattleDF' BATTLE_FRAC = 'wgBattleFrac' BATTLE_IDX = 'wgBattleIdx' BATTLE_ODDS = 'wgBattleOdds' BATTLE_ODDSM = 'wgBattleOddsMarker' BATTLE_SHIFT = 'wgBattleShift' BATTLE_RESULT = 'wgBattleResult' AUTO_ODDS = 'wgAutoOdds' AUTO_RESULTS = 'wgAutoResults' NO_CLEAR_MOVES = 'wgNoClearMoves' NO_CLEAR_BATTLES = 'wgNoClearBattles' DEBUG = 'wgDebug' VERBOSE = 'wgVerbose' def __init__(self, vmodname = 'Draft.vmod', pdfname = 'export.pdf', infoname = 'export.json', title = 'Draft', version = 'draft', imageFormat = 'png', description = '', rules = None, tutorial = None, patch = None, visible = True, vassalVersion = '3.6.7', nonato = False, nochit = False, counterScale = 1, resolution = 150): '''Exports a PDF and associated JSON files to a VASSAL module. Parameters ---------- vmodname : str Name of module file to write pdfname : str Name of PDF file to read images from infoname : str Name of JSON file to read meta data from title : str Name of module version : str Version of midule description : str Short description of the module rules : str Optional name PDF file to attach as rules tutorial : str Optional name of a VASSAL log file to use as tutorial patch : str Optional name of Python script to post process the module visible : bool Make grids visible vassalVersion : str VASSAL version to encode this module for resolution : int Resolution for images (default 150) ''' self._vmodname = vmodname self._pdfname = pdfname self._infoname = infoname self._title = title self._version = version self._description = description self._rules = rules self._tutorial = tutorial self._patch = patch self._visible = visible or version.lower() == 'draft' self._vassalVersion = vassalVersion self._nonato = nonato self._nochit = nochit self._resolution = resolution self._counterScale = counterScale self._img_format = imageFormat.lower() self._battleMark = LaTeXExporter.Specials.BATTLE_MARK self._oddsMark = LaTeXExporter.Specials.ODDS_MARK self._battleCtrl = LaTeXExporter.Specials.BATTLE_CTRL self._battleCalc = LaTeXExporter.Specials.BATTLE_CALC self._battleUnit = LaTeXExporter.Specials.BATTLE_UNIT self._hiddenName = LaTeXExporter.Specials.HIDDEN_NAME self._markBattle = LaTeXExporter.Keys.MARK_BATTLE self._clearBattle = LaTeXExporter.Keys.CLEAR_BATTLE self._clearAllBattle = LaTeXExporter.Keys.CLEAR_ALL_BATTLE self._zeroBattle = LaTeXExporter.Keys.ZERO_BATTLE self._incrBattle = LaTeXExporter.Keys.INCR_BATTLE self._setBattle = LaTeXExporter.Keys.SET_BATTLE self._getBattle = LaTeXExporter.Keys.GET_BATTLE self._markOdds = LaTeXExporter.Keys.MARK_ODDS self._markResult = LaTeXExporter.Keys.MARK_RESULT self._clearMoved = LaTeXExporter.Keys.CLEAR_MOVED self._zeroBattleAF = LaTeXExporter.Keys.ZERO_BATTLE_AF self._zeroBattleDF = LaTeXExporter.Keys.ZERO_BATTLE_DF self._zeroBattleFrac = LaTeXExporter.Keys.ZERO_BATTLE_FRAC self._zeroBattleOdds = LaTeXExporter.Keys.ZERO_BATTLE_ODDS self._zeroBattleShft = LaTeXExporter.Keys.ZERO_BATTLE_SHFT self._zeroBattleIdx = LaTeXExporter.Keys.ZERO_BATTLE_IDX self._calcBattleAF = LaTeXExporter.Keys.CALC_BATTLE_AF self._calcBattleDF = LaTeXExporter.Keys.CALC_BATTLE_DF self._calcBattleFrac = LaTeXExporter.Keys.CALC_BATTLE_FRAC self._calcBattleOdds = LaTeXExporter.Keys.CALC_BATTLE_ODDS self._calcBattleShft = LaTeXExporter.Keys.CALC_BATTLE_SHFT self._calcBattleIdx = LaTeXExporter.Keys.CALC_BATTLE_IDX self._calcBattleRes = LaTeXExporter.Keys.CALC_BATTLE_RES self._clearBattlePhs = LaTeXExporter.Keys.CLEAR_BATTLE_PHS self._resolveBattle = LaTeXExporter.Keys.RESOLVE_BATTLE self._rollDice = LaTeXExporter.Keys.ROLL_DICE self._diceInitKey = LaTeXExporter.Keys.DICE_INIT_KEY self._clearKey = LaTeXExporter.Keys.CLEAR_KEY self._clearAllKey = LaTeXExporter.Keys.CLEAR_ALL_KEY self._deleteKey = LaTeXExporter.Keys.DELETE_KEY self._eliminateKey = LaTeXExporter.Keys.ELIMINATE_KEY self._flipKey = LaTeXExporter.Keys.FLIP_KEY self._trailKey = LaTeXExporter.Keys.TRAIL_KEY self._restoreKey = LaTeXExporter.Keys.RESTORE_KEY self._markKey = LaTeXExporter.Keys.MARK_KEY self._resolveKey = LaTeXExporter.Keys.RESOLVE_KEY self._rotateCCWKey = LaTeXExporter.Keys.ROTATE_CCWKey self._rotateCWKey = LaTeXExporter.Keys.ROTATE_CWKey self._chartsKey = LaTeXExporter.Keys.CHARTS_KEY self._oobKey = LaTeXExporter.Keys.OOB_KEY self._countersKey = LaTeXExporter.Keys.COUNTERS_KEY self._deadKey = LaTeXExporter.Keys.DEAD_KEY self._diceKey = LaTeXExporter.Keys.DICE_KEY self._recalcOdds = LaTeXExporter.Keys.RECALC_ODDS self._battleCounter = LaTeXExporter.Globals.BATTLE_COUNTER self._currentBattle = LaTeXExporter.Globals.CURRENT_BATTLE self._currentAttacker = LaTeXExporter.Globals.CURRENT_ATTACKER self._battleNo = LaTeXExporter.Globals.BATTLE_NO self._battleAF = LaTeXExporter.Globals.BATTLE_AF self._battleDF = LaTeXExporter.Globals.BATTLE_DF self._battleFrac = LaTeXExporter.Globals.BATTLE_FRAC self._battleIdx = LaTeXExporter.Globals.BATTLE_IDX self._battleOdds = LaTeXExporter.Globals.BATTLE_ODDS self._battleOddsM = LaTeXExporter.Globals.BATTLE_ODDSM self._battleShift = LaTeXExporter.Globals.BATTLE_SHIFT self._battleResult = LaTeXExporter.Globals.BATTLE_RESULT self._autoOdds = LaTeXExporter.Globals.AUTO_ODDS self._autoResults = LaTeXExporter.Globals.AUTO_RESULTS self._noClearMoves = LaTeXExporter.Globals.NO_CLEAR_MOVES self._noClearBattles = LaTeXExporter.Globals.NO_CLEAR_BATTLES self._debug = LaTeXExporter.Globals.DEBUG self._verbose = LaTeXExporter.Globals.VERBOSE self._battleMarks = [] self._oddsMarks = [] self._resultMarks = [] self._hidden = None self._dice = {} self._diceInit = None with VerboseGuard('Overall settings') as v: v(f'Module file name: {self._vmodname}') v(f'PDF file name: {self._pdfname}') v(f'JSON file name: {self._infoname}') v(f'Game title: {self._title}') v(f'Game version: {self._version}') v(f'Description: {self._description}') v(f'Rules PDF file: {self._rules}') v(f'Tutorial log: {self._tutorial}') v(f'Patch scripts: {self._patch}') v(f'Visible grids: {self._visible}') v(f'Resolution: {self._resolution}') v(f'Scale of counters: {self._counterScale}') v(f'Image format: {self._img_format}') def setup(self): # Start the processing self._info = self.convertPages() self._categories, \ self._mains, \ self._echelons, \ self._commands = self.writeImages(self._counterScale) def run(self): super(LaTeXExporter,self).run(self._vmodname,self._patch) # ================================================================ def createProcess(self,args): '''Spawn a process and pipe output here Parameters ---------- args : list List of process command line elements Returns ------- pipe : subprocess.Pipe Pipe to read from ''' from os import environ from subprocess import Popen, PIPE return Popen(args,env=environ.copy(),stdout=PIPE,stderr=PIPE) # ---------------------------------------------------------------- def addPws(self,opw=None,upw=None): '''Add a `Pws` element to arguments Add password options Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs Returns ------- element : Pws The added element ''' args = [] if upw is not None: args.extend(['-upw',upw]) if opw is not None: args.extend(['-opw',opw]) return args # ---------------------------------------------------------------- def getPdfInfo(self,upw=None,opw=None,timeout=None): '''Get information about the PDF Parameters ---------- opw : str Owner password (optional) upw : str User password (optional) timeout : int Time out in miliseconds for subprocesses Returns ------- info : dict Image information ''' args = ['pdfinfo', self._pdfname ] args.extend(self.addPws(opw=opw,upw=upw)) with VerboseGuard(f'Getting information from PDF {self._pdfname}'): proc = self.createProcess(args) try: out, err = proc.communicate(timeout=timeout) except: proc.kill() proc.communicate() raise RuntimeError(f'Failed to get PDF info: {e}') d = {} for field in out.decode('utf8','ignore').split('\n'): if field == '': continue subfields = field.split(':') key, value = subfields[0], ':'.join(subfields[1:]) if key != '': d[key] = (int(value.strip()) if key == 'Pages' else value.strip()) if 'Pages' not in d: raise ValueError(f'Page count not found from {self._pdfname}') return d # ---------------------------------------------------------------- def getImagesInfo(self): '''Read in JSON information, and return as dictionary''' from json import load with VerboseGuard(f'Getting information from JSON {self._infoname}'): with open(self._infoname) as file: info = load(file) return info # ================================================================ @classmethod def parseLength(cls,value,def_unit='px'): from re import match scales = { 'px': 1, 'pt': 1.25, 'pc': 15, 'in': 90, 'mm': 3.543307, 'cm': 35.43307, '%': -1/100 } if not value: return 0 parts = match(r'^\s*(-?\d+(?:\.\d+)?)\s*(px|in|cm|mm|pt|pc|%)?', value) if not parts: raise RuntimeError(f'Unknown length format: "{value}"') number = float(parts.group(1)) unit = parts.group(2) or def_unit factor = scales.get(unit,None) if not factor: raise RuntimeError(f'Unknown unit: "{unit}"') return factor * number # ---------------------------------------------------------------- @classmethod def scaleSVG(cls,buffer,factor): '''Buffer is bytes''' from xml.dom.minidom import parse from re import split from io import StringIO, BytesIO if not LaTeXExporter.isSVG(buffer): return buffer with BytesIO(buffer) as stream: doc = parse(stream) if not doc: raise RuntimeError('Failed to parse buffer as XML') root = doc.childNodes[0] getA = lambda e,n,d=None : \ e.getAttribute(n) if e.hasAttribute(n) else d setA = lambda e,n,v : e.setAttribute(n,v) leng = LaTeXExporter.parseLength width = leng(getA(root,'width', '0')) height = leng(getA(root,'height','0')) vport = getA(root,'viewBox','0 0 0 0').strip() vp = [leng(v) for v in split('[ \t,]',vport)] # print(f'Input WxH: {width}x{height} ({vp})') width *= factor height *= factor vp = [factor * v for v in vp] # print(f'Scaled WxH: {width}x{height} ({vp})') if width <= 0 and vp: width = vp[2] - vp[0] if height <= 0 and vp: height = vp[3] - vp[1] if not vp: vp = [0, 0, width, height] setA(root,'transform',f'scale({factor})') setA(root,'width', f'{width}') setA(root,'height',f'{height}') setA(root,'viewBox',' '.join([f'{v}' for v in vp])) with StringIO() as out: doc.writexml(out) return out.getvalue().encode() # ================================================================ def convertPage(self,page,opw=None,upw=None,timeout=None): '''Convert a page from PDF into an image (bytes) Parameters ---------- page : int Page number in the PDF to convert opw : str Owner password (optional) upw : str User password (optional) timeout : int Time out in miliseconds for subprocesses Returns ------- info : dict Image information ''' args = ['pdftocairo'] if self._img_format != 'svg': args.extend([ '-transp', '-singlefile']) args.extend([ '-r', str(self._resolution), '-f', str(page), '-l', str(page), f'-{self._img_format}' ]) args.extend(self.addPws(opw=opw,upw=upw)) args.append(self._pdfname) args.append('-') # print(f'Conversion command',' '.join(args)) proc = self.createProcess(args) try: out, err = proc.communicate(timeout=timeout) except Exception as e: proc.kill() proc.communicate() raise RuntimeError(f'Failed to convert page {page} of ' f'{self._pdfname}: {e}') if len(out) <= 0: raise RuntimeError(f'Failed to convert page {page} of ' f'{self._pdfname}: {err}') # This does not seem to work - VASSAL (and Inkscape) does not # apply the 'scale' transformation to the image! # # if self._img_format == 'svg': # out = LaTeXExporter.scaleSVG(out,2) return out # ---------------------------------------------------------------- def ignoreEntry(self,info,ignores=['<>','<>']): '''Check if we should ignore an entry in the JSON file''' return info['category'] in ignores # ---------------------------------------------------------------- def scaleImage(self,buffer,factor): from PIL import Image from io import BytesIO from math import isclose if isclose(factor,1): return buffer # print(f'Scaling image by factor {factor}') with Image.open(BytesIO(buffer)) as img: w, h = img.width, img.height cpy = img.resize((int(factor*w),int(factor*h))) with BytesIO() as out: cpy.save(out,format='PNG') return out.getvalue() # ---------------------------------------------------------------- def convertPages(self,opw=None,upw=None,timeout=None): '''Reads in JSON and pages from PDF and stores information dictionary, which is returned Parameters ---------- opw : str Owner password (optional) upw : str User password (optional) timeout : int Time out in miliseconds for subprocesses Returns ------- info : dict Image information ''' oargs = {'opw':opw,'upw':upw } docinfo = self.getPdfInfo() imgsinfo = self.getImagesInfo() if len(imgsinfo) - 1 != docinfo['Pages']: raise RuntimeError(f'Number of pages in {self._pdfname} ' f'{docinfo["Pages"]} not matched in JSON ' f'{self._infoname} -> {len(imgsinfo)}') with VerboseGuard(f'Converting {docinfo["Pages"]} ' f'pages in {self._pdfname}') as v: for i,info in enumerate(imgsinfo): if self.ignoreEntry(info): continue if i == 0: v(end='') v(f'[{info["number"]}]',end=' ',flush=True) info['img'] = self.convertPage(info['number'],**oargs) v('done') return imgsinfo # ---------------------------------------------------------------- @classmethod def isSVG(cls,buffer): return buffer[:5] == b' 0: mains += ' '+lower if len(upper) > 0: mains += ' '+upper mains = sub(r'\[[^]]+\]','',mains)\ .replace('{','').replace('}','')#.split(',') unittypes.append(mains.replace(',',' ')) unittypes.extend([s.strip().replace(',',' ') for s in mains.split(',')]) #if len(mains) > 1: # unittypes.append('+'.join(mains)) info['mains'] = mains if len(echelon) > 0: echelons.append(echelon) info['echelon'] = echelon if len(command) > 0: commands.append(command) info['command'] = command # Finished loop over infos. Make unit types, echelons, # commands unique v('done') return categories, set(unittypes), set(echelons), set(commands) # ================================================================ def createModuleData(self): '''Create the `moduleData` file in the module ''' with VerboseGuard(f'Creating module data'): self._moduleData = ModuleData() data = self._moduleData.addData() data.addVersion (version=self._version) data.addVASSALVersion(version=self._vassalVersion) data.addName (name=self._title) data.addDescription (description=self._description) data.addDateSaved () # ================================================================ def createBuildFile(self, ignores = '(.*markers?|all|commons|.*hidden|[ ]+)'): '''Create the `buildFile.xml` file in the module. Parameters ---------- ignores : str Regular expression to match ignored categories for factions determination. Python's re.fullmatch is applied to this regular exression against chit categories. If the pattern is matched, then the chit is not considered to belong to a faction. ''' from re import fullmatch, IGNORECASE with VerboseGuard(f'Creating build file') as v: self._build = BuildFile() # 'buildFile.xml') self._game = self._build.addGame(name = self._title, version = self._version, description = self._description) doc = self.addDocumentation() self._game.addBasicCommandEncoder() # Extract the sides self._sides = [ k for k in self._categories.get('counter',{}).keys() if fullmatch(ignores, k, IGNORECASE) is None] v(f'Got sides: {", ".join(self._sides)}') v(f'Adding Global options') go = self._game.addGlobalOptions( autoReport = GlobalOptions.PROMPT, centerOnMove = GlobalOptions.PROMPT, nonOwnerUnmaskable = GlobalOptions.PROMPT, playerIdFormat = '$playerName$') go.addOption(name='undoHotKey',value=key('Z')) go.addOption(name='undoIcon', value='/images/Undo16.gif') # go.addOptoin(name='stepHotKey',value='') go.addBoolPreference(name = self._verbose, default = True, desc = 'Be verbose', tab = self._title) go.addBoolPreference(name = self._debug, default = False, desc = 'Show debug chat messages', tab = self._title) go.addBoolPreference(name = self._autoOdds, default = False, desc = 'Calculate Odds on battle declaration', tab = self._title) go.addBoolPreference(name = self._autoResults, default = False, desc = 'Resolve battle results automatically', tab = self._title) go.addBoolPreference(name = self._noClearMoves, default = False, desc = ('Do not remove moved markers ' 'on phase change'), tab = self._title) go.addBoolPreference(name = self._noClearBattles, default = False, desc = ('Do not remove battle markers ' 'on phase change'), tab = self._title) v(f'Adding player roster') roster = self._game.addPlayerRoster() for side in self._sides: roster.addSide(side) v(f'Adding global properties') glob = self._game.addGlobalProperties() glob.addProperty(name='TurnTracker.defaultDocked', initialValue=True) self._battleMarks = self._categories\ .get('counter',{})\ .get('BattleMarkers',[]) if len(self._battleMarks) > 0: v(f'We have battle markers') glob.addProperty(name = self._battleCounter, initialValue = 0, isNumeric = True, min = 0, max = len(self._battleMarks), wrap = True, description = 'Counter of battles') glob.addProperty(name = self._currentBattle, initialValue = 0, isNumeric = True, min = 0, max = len(self._battleMarks), wrap = True, description = 'Current battle number') glob.addProperty(name = self._currentAttacker, initialValue = 0, isNumeric = True, min = 0, max = 1, wrap = True, description = 'Current unit is attacker') glob.addProperty(name = self._battleAF, initialValue = 0, isNumeric = True, description = 'Current battle AF') glob.addProperty(name = self._battleDF, initialValue = 0, isNumeric = True, description = 'Current battle DF') glob.addProperty(name = self._battleFrac, initialValue = 0, isNumeric = True, description = 'Current battle fraction') glob.addProperty(name = self._battleShift, initialValue = 0, isNumeric = True, description = 'Current battle odds shift') glob.addProperty(name = self._battleOdds, initialValue = '', isNumeric = False, description = 'Current battle odds') glob.addProperty(name = self._battleResult, initialValue = '', isNumeric = False, description = 'Current battle results') glob.addProperty(name = self._battleIdx, initialValue = 0, isNumeric = True, description = 'Current battle odds index') self._oddsMarks = self._categories\ .get('counter',{})\ .get('OddsMarkers',[]) if len(self._oddsMarks) > 0: v(f'We have odds markers') self._resultMarks = self._categories\ .get('counter',{})\ .get('ResultMarkers',[]) if len(self._resultMarks) > 0: v(f'We have result markers') self.addNotes() v(f'Adding turn track') turns = self._game.addTurnTrack(name='Turn', counter={ 'property': 'Turn', 'phases': { 'property': 'Phase', 'names': self._sides } }) turns.addHotkey(hotkey = self._clearMoved+'Phase', name = 'Clear moved markers', reportFormat = (f'{{{self._verbose}?(' f'"`Clear all moved markers, "+' f'""):""}}')) if len(self._battleMarks) > 0: turns.addHotkey(hotkey = self._clearBattlePhs, name = 'Clear battle markers', reportFormat = (f'{{{self._verbose}?(' f'"`Clear all battle markers, "+' f'""):""}}')) self._dice = self._categories\ .get('die-roll',{}) if len(self._dice) > 0: v(f'We have symbolic dice') self._diceInit = [] # from pprint import pprint # pprint(self._dice,depth=2) for die, faces in self._dice.items(): ico = self.getIcon(die+'-die-icon','') # print(f'Die {die} icon="{ico}"') dmin = +100000 dmax = -100000 symb = self._game.addSymbolicDice( name = die+'Dice', text = die if ico == '' else '', icon = ico, tooltip = f'{die} die roll', format = (f'{{""+PlayerSide+" "+' f'"("+PlayerName+"): "+'+ f'"{die} die roll: "+result1' # f'+" "' f'}}'), resultWindow = True, windowX = str(int(67 * self._resolution/150)), windowY = str(int(65 * self._resolution/150))); sdie = symb.addDie(name = die); for face, fdata in faces.items(): fn = fdata['filename'] val = int(fn.replace(f'.{self._img_format}','') .replace(die+'-','')) dmin = min(dmin,val) dmax = min(dmax,val) sdie.addFace(icon = fn, text = str(val), value = val); self._diceInit.extend([ GlobalPropertyTrait( ['',self._diceInitKey, GlobalPropertyTrait.DIRECT, f'{{{dmin}}}'], name = die+'Dice_result', numeric = True, min = dmin, max = dmax, description = f'Initialize {die}Dice'), ReportTrait( self._diceInitKey, report=(f'{{{self._debug}?("Initialize ' f'{die}Dice_result to {dmin}"):""}}')) ]) # Add start-up key self._game.addStartupMassKey( name = 'Initialise dice results', hotkey = self._diceInitKey, target = '', filter = f'{{BasicName=="{self._hiddenName}"}}', whenToApply = StartupMassKey.EVERY_LAUNCH, reportFormat=f'{{{self._debug}?("Init Dice results"):""}}') self.addKeybindings(doc) self.addCounters() self.addInventory() self.addBoards() self.addDeadMap() self.addOOBs() self.addCharts() self.addDie() # ---------------------------------------------------------------- def addDocumentation(self): '''Add documentation to the module. This includes rules, key-bindings, and about elements. ''' with VerboseGuard('Adding documentation') as v: doc = self._game.addDocumentation() if self._rules is not None: self._vmod.addExternalFile(self._rules,'rules.pdf') doc.addBrowserPDFFile(title = 'Show rules', pdfFile = 'rules.pdf') if self._tutorial is not None: self._vmod.addExternalFile(self._tutorial,'tutorial.vlog') doc.addTutorial(name = 'Tutorial', logfile = 'tutorial.vlog', launchOnStartup = True) fronts = self._categories.get('front',{}).get('all',[]) front = list(fronts.values())[0] if len(fronts) > 0 else None if front is not None: v(f'Adding about page') doc.addAboutScreen(title=f'About {self._title}', fileName = front['filename']) return doc # ---------------------------------------------------------------- def addKeybindings(self,doc): keys = [ ['Alt-A', '-', 'Show the charts panel'], ['Alt-B', '-', 'Show the OOBs'], ['Alt-C', '-', 'Show the counters panel'], ['Alt-E', '-', 'Show the eliminated units'], ['Alt-I', '-', 'Show/refresh inventory window'], ['Alt-M', '-', 'Show map'], ['Alt-T', '-', 'Increase turn track'], ['Alt-Shift-T', '-', 'Decrease turn track'], ['Alt-6', '-', 'Roll the dice'], ['Ctrl-D', 'Board,Counter','Delete counters'], ['Ctrl-E', 'Board,Counter','Eliminate counters'], ['Ctrl-F', 'Board,Counter','Flip counters'], ['Ctrl-M', 'Board,Counter','Toggle "moved" markers'], ['Ctrl-O', 'Board', 'Hide/show counters'], ['Ctrl-R', 'Board,Counter','Restore unit'], ['Ctrl-T', 'Board,Counter','Toggle move trail'], ['Ctrl-Z', 'Board', 'Undo last move'], ['Ctrl-+', 'Board', 'Zoom in'], ['Ctrl--', 'Board', 'Zoom out'], ['Ctrl-=', 'Board', 'Select zoom'], ['Ctrl-Shift-O', 'Board','Show overview map'], ['←,→,↑↓','Board', 'Scroll board left, right, up, down (slowly)'], ['PnUp,PnDn','Board', 'Scroll board up/down (fast)'], ['Ctrl-PnUp,Ctrl-PnDn','Board', 'Scroll board left/right (fast)'], ['Mouse-scroll up/down', 'Board', 'Scroll board up//down'], ['Shift-Mouse-scroll up/down','Board','Scroll board right/leftown'], ['Ctrl-Mouse-scroll up/down','Board','Zoom board out/in'], ['Mouse-2', 'Board', 'Centre on mouse']] if self._battleMarks: for a,l in zip(['Ctrl-D','Ctrl-Shift-O', 'Ctrl-+', 'Ctrl-+'], [['Ctrl-C', 'Counter', 'Clear battle'], ['Ctrl-Shift-C','Board', 'Clear all battle'], ['Ctrl-X', 'Board,Counter','Mark battle'], ['Ctrl-Shift-X','Board,Counter','Recalculate Odds'], ['Ctrl-Y', 'Board,Counter','Resolve battle'], ]): ks = [k[0] for k in keys] didx = ks.index(a) keys.insert(didx,l) self._vmod.addFile('help/keys.html', Documentation.createKeyHelp( keys, title=self._title, version=self._version)) doc.addHelpFile(title='Key bindings',fileName='help/keys.html') # ---------------------------------------------------------------- def addNatoPrototypes(self,prototypes): # Add unit categories as prototypes for n,c in zip(['Type','Echelon','Command'], [self._mains, self._echelons, self._commands]): sc = set([cc.strip() for cc in c]) with VerboseGuard(f'Adding prototypes for "{n}"') as vv: for i,cc in enumerate(sc): cc = cc.strip() if len(cc) <= 0: continue vv(f'[{cc}] ',end='',flush=True,noindent=True) p = prototypes.addPrototype(name = f'{cc} prototype', description = '', traits = [MarkTrait(n,cc), BasicTrait()]) vv('') # ---------------------------------------------------------------- def addBattleControlPrototype(self,prototypes): # Control of battles. # # This has traits to # # - Zero battle counter # - Increment battle counter # - Set current battle number # - Mark battle # - Calculate odds # # When wgMarkBattle is issued to this piece, then # # - Increment battle counter # - Set global current battle # - Trampoline to GCK markBattle # - For all selected pieces, issue markBattle # - All wgBattleUnit pieces then # - Get current battle # and store # - Add marker on top of it self # - Issue calculateOddsAuto # - If auto odds on, go to calcOddsStart, # - Trampoline to GCK calcOddsAuto # - Which sends calcOddsStart to all markers # - For each mark # - Set current battle to current global # - Trampoline calcOdds via GKC # - Send calcBattleOdds to wgBattleCalc # - Zero odds # - Calculate fraction # - Zero fraction # - Calculate total AF # - Zero AF # - via trampoline to GKC # - Calculate total DF # - Zero DF # - via trampoline to GKC # - Real fraction calculation # - From calculate fraction # - Access via calculate trait # - Calculate shift # - Zero shift # - Trampoline to GKC # - Access via calculate trait # - Calculate index # - Via calculated OddsIndex # - Calculate odds real # - Via calculated Index to odds # - Do markOddsAuto which selects between odds # - Do markOddsReal+OddsIndex # - Set global battle # # - Place marker # - Take global battle # # - De-select all other marks to prevent # further propagation # if len(self._battleMarks) <= 0: return False n = len(self._battleMarks) # --- Battle counter control - reset and increment ----------- traits = [ GlobalPropertyTrait( ['',self._zeroBattle,GlobalPropertyTrait.DIRECT,'{0}'], ['',self._incrBattle,GlobalPropertyTrait.DIRECT, f'{{({self._battleCounter}%{n})+1}}'], name = self._battleCounter, numeric = True, min = 0, max = n, wrap = True, description = 'Zero battle counter', ), # Set global property combat # from this GlobalPropertyTrait( ['',self._setBattle,GlobalPropertyTrait.DIRECT, f'{{{self._battleCounter}}}'], name = self._currentBattle, numeric = True, min = 0, max = n, wrap = True, description = 'Zero battle counter', ), ReportTrait(self._zeroBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+": zero battle counter: "' f'+{self._battleCounter}):""}}')), ReportTrait(self._incrBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+": ' f'increment battle counter: "' f'+{self._battleCounter}):""}}')), ReportTrait(self._setBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+": set current battle: "+' f'{self._battleCounter}+" -> "+' f'{self._currentBattle}):""}}')), GlobalHotkeyTrait(name = '', key = self._markBattle+'Trampoline', globalHotkey = self._markBattle, description = 'Mark selected for battle'), ReportTrait(self._markBattle+'Trampoline', report=(f'{{{self._debug}?' f'("~ "+BasicName+": forward mark battle: "+' f'{self._battleCounter}):""}}')), GlobalHotkeyTrait(name = '', key = self._calcBattleOdds+'Start', globalHotkey = self._calcBattleOdds+'Auto', description = 'Trampoline to global'), ReportTrait(self._calcBattleOdds+'Start', report=(f'{{{self._debug}?' f'("~ "+BasicName+": forward odds: "+' f'{self._battleCounter}):""}}')), DeselectTrait(command = '', key = self._calcBattleOdds+'Deselect', deselect = DeselectTrait.ALL), ReportTrait(self._calcBattleOdds+'Deselect', report=(f'{{{self._debug}?' f'("~ "+BasicName+": select only this: "+' f'{self._battleCounter}):""}}')), TriggerTrait(command = '', key = self._calcBattleOdds+'Auto', actionKeys = [self._calcBattleOdds+'Start'], property = f'{{{self._autoOdds}==true}}'), ReportTrait(self._calcBattleOdds+'Auto', report=(f'{{{self._debug}?' f'("~ "+BasicName+": forward odds: "+' f'{self._battleCounter}):""}}')), TriggerTrait(command = '', key = self._markBattle, actionKeys = [self._incrBattle, self._setBattle, self._markBattle+'Trampoline', self._calcBattleOdds+'Auto']), ReportTrait(self._markBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+": mark battle: "+' f'{self._battleCounter}):""}}')), GlobalHotkeyTrait(name = '', key = self._clearAllBattle+'Trampoline', globalHotkey = self._clearAllBattle, description = 'Mark selected for battle'), TriggerTrait(command = '', key = self._clearAllBattle, actionKeys = [self._clearAllBattle+'Trampoline', self._zeroBattle]), ReportTrait(self._clearBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+": clear battle: "+' f'{self._battleCounter}):""}}')), GlobalHotkeyTrait(name = '', key = self._clearMoved+'Trampoline', globalHotkey = self._clearMoved, description = 'Clear moved markers'), MarkTrait(name=self._battleCtrl,value=True), BasicTrait()] prototypes.addPrototype(name = self._battleCtrl, description = '', traits = traits) return True # ---------------------------------------------------------------- def addBattleCalculatePrototype(self,prototypes): # --- Batttle AF, DF, Odds ----------------------------------- # This calculate odds derivation from stated odds. calcIdx = 0 maxIdx = len(self._oddsMarks)+1 minIdx = 0 idx2Odds = '""' calcFrac = 1 if len(self._oddsMarks) > 0: odds = [o.replace('odds marker','').strip() for o in self._oddsMarks] ratios = all([':' in o for o in odds]) if ratios: # All is ratios! def calc(s): num, den = [float(x.strip()) for x in s.split(':')] return num/den ratios = [[calc(s),s] for s in odds] ind = [i[0] for i in sorted(enumerate(ratios), key=lambda x:x[1][0])] #print(f'=== Rations: {ratios}, Index: {ind[::-1]}') calcIdx = ':'.join([f'{self._battleFrac}>={ratios[i][0]}?' f'({i+1})' for i in ind[::-1]]) + ':0' idx2Odds = ':'.join([f'OddsIndex=={i+1}?' f'"{ratios[i][1]}"' for i in ind[::-1]]) + ':""' calcFrac = (f'{{{self._battleDF}==0?0:' f'(((double)({self._battleAF}))' fr'\/{self._battleDF})}}') #print(calcIdx,idx2Odds) else: try: nums = [[int(o),o] for o in odds] calcFrac = f'{{{self._battleAF}-{self._battleDF}}}' ind = [i[0] for i in sorted(enumerate(nums), key=lambda x:x[1])] calcIdx = ':'.join([f'{self._battleFrac}>={nums[i][0]}?' f'({i+1})' for i in ind[::-1]])+':0' idx2Odds = ':'.join([f'OddsIndex=={i+1}?"{nums[i][1]}"' for i in ind[::-1]]) + ':""' vidx2Odds = '\t'+idx2Odds.replace(':',':\n\t') #print(f'Index to odds: {vidx2Odds}') except: pass traits = [ CalculatedTrait(# This should be changed to game rules name = 'OddsShift', expression = f'{{{self._battleShift}}}', description = 'Calculated internal oddsshift'), CalculatedTrait(# This should be changed to game rules name = 'OddsIndexRaw', expression = f'{{{calcIdx}}}', description = 'Calculated internal odds index'), CalculatedTrait(# This should be changed to game rules name = 'OddsIndexLimited', expression = (f'{{OddsIndexRaw>{maxIdx}?{maxIdx}:' f'OddsIndexRaw<{minIdx}?{minIdx}:' f'OddsIndexRaw}}'), description = 'Calculated internal limited odds index'), CalculatedTrait(# This should be changed to game rules name = 'OddsIndex', expression = (f'{{OddsIndexLimited+OddsShift}}'), description = 'Calculated internal odds index (with shift)'), CalculatedTrait(# This should be changed to game rules name = 'BattleFraction', expression = calcFrac, description = 'Calculated fraction off battle'), GlobalPropertyTrait( ['',self._zeroBattleShft,GlobalPropertyTrait.DIRECT,'{0}'], name = self._battleShift, numeric = True, description = 'Zero battle odds shift', ), GlobalPropertyTrait( ['',self._zeroBattleAF,GlobalPropertyTrait.DIRECT,'{0}'], name = self._battleAF, numeric = True, description = 'Zero battle AF', ), GlobalPropertyTrait( ['',self._zeroBattleDF,GlobalPropertyTrait.DIRECT,'{0}'], name = self._battleDF, numeric = True, description = 'Zero battle AF', ), # {wgBattleDF==0?0:(double(wgBattleAF)/wgBattleDF)} GlobalPropertyTrait( ['',self._zeroBattleFrac,GlobalPropertyTrait.DIRECT,'{0}'], ['',self._calcBattleFrac+'Real',GlobalPropertyTrait.DIRECT, '{BattleFraction}'], name = self._battleFrac, description = 'Calculate battle fraction', ), GlobalPropertyTrait( ['',self._zeroBattleIdx,GlobalPropertyTrait.DIRECT,'{0}'], ['',self._calcBattleIdx,GlobalPropertyTrait.DIRECT, '{OddsIndex}'], name = self._battleIdx, description = 'Calculate battle odds index', ), GlobalPropertyTrait( ['',self._zeroBattleOdds,GlobalPropertyTrait.DIRECT,'{""}'], ['',self._calcBattleOdds+'Real',GlobalPropertyTrait.DIRECT, f'{{{idx2Odds}}}'], name = self._battleOdds, description = 'Calculate battle odds', ), GlobalHotkeyTrait(name = '',# Forward to units key = self._calcBattleAF+'Trampoline', globalHotkey = self._calcBattleAF, description = 'Calculate total AF'), GlobalHotkeyTrait(name = '',# Forward to units key = self._calcBattleDF+'Trampoline', globalHotkey = self._calcBattleDF, description = 'Calculate total DF'), GlobalHotkeyTrait(name = '',# Forward to units key = self._calcBattleShft+'Trampoline', globalHotkey = self._calcBattleShft, description = 'Calculate total shift'), TriggerTrait(command = '', key = self._calcBattleAF, actionKeys = [self._zeroBattleAF, self._calcBattleAF+'Trampoline']), TriggerTrait(command = '', key = self._calcBattleDF, actionKeys = [self._zeroBattleDF, self._calcBattleDF+'Trampoline']), TriggerTrait(command = '', key = self._calcBattleShft, actionKeys = [self._zeroBattleShft, self._calcBattleShft+'Trampoline']), TriggerTrait(command = '', key = self._calcBattleFrac, actionKeys = [self._zeroBattleFrac, self._calcBattleAF, self._calcBattleDF, self._calcBattleFrac+'Real']), TriggerTrait(command = '', key = self._calcBattleOdds, actionKeys = [self._zeroBattleOdds, self._calcBattleFrac, self._calcBattleShft, self._calcBattleIdx, self._calcBattleOdds+'Real']), ReportTrait(self._zeroBattleAF, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Reset AF: "+' f'{self._battleAF}):""}}')), ReportTrait(self._zeroBattleDF, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Reset DF: "+' f'{self._battleDF}):""}}')), ReportTrait(self._zeroBattleFrac, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Reset fraction: "+' f'{self._battleFrac}):""}}')), ReportTrait(self._zeroBattleOdds, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Reset odds: "+' f'{self._battleOdds}):""}}')), ReportTrait(self._calcBattleAF, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Total AF: "+' f'{self._battleAF}):""}}')), ReportTrait(self._calcBattleDF, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Total DF: "+' f'{self._battleDF}):""}}')), ReportTrait(self._calcBattleShft, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Battle odds shift: "+' f'{self._battleShift}):""}}')), ReportTrait(self._calcBattleFrac, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Battle fraction: "+' f'{self._battleFrac}):""}}')), ReportTrait(self._calcBattleOdds, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Battle odds: "+' f'{self._battleOdds}+" ("+' f'{self._battleIdx}+")"):""}}')), ReportTrait(self._calcBattleFrac+'Real', report=(f'{{{self._debug}?' f'("~ "+BasicName+": Battle fraction: "+' f'{self._battleFrac}+' f'" AF="+{self._battleAF}+' f'" DF="+{self._battleDF}' f'):""}}')), ReportTrait(self._calcBattleOdds+'Real', report=(f'{{{self._debug}?' f'("~ "+BasicName+": Battle odds: "+' f'{self._battleOdds}+' f'" ("+{self._battleIdx}+","+OddsShift+","+' f'" raw="+OddsIndexRaw+","+' f'" limited="+OddsIndexLimited+","+' f'" -> "+OddsIndex+","+' f'{self._battleShift}+")"+' f'" Fraction="+{self._battleFrac}+' f'" AF="+{self._battleAF}+' f'" DF="+{self._battleDF}' f'):""}}')), ReportTrait(self._calcBattleOdds+'Real', report=(f'{{{self._verbose}?' f'("! Battle # "' f'+{self._battleNo}' f'+" AF="+{self._battleAF}' f'+" DF="+{self._battleDF}' f'+" => "+{self._battleOdds}' # f'+" "' f'):""}}')), MarkTrait(name=self._battleCalc,value=True), BasicTrait()] prototypes.addPrototype(name = self._battleCalc, description = '', traits = traits) # ---------------------------------------------------------------- def addBattleUnitPrototype(self,prototypes): # --- Battle units that set battle markers ------------------- # # - Trait to add battle number 1 to max # # - Trigger trait for each of these using the global property # for the current battle # traits = [ # getBattle retrieves the battle number from the global property. # clearBattle sets piece battle to -1 DynamicPropertyTrait(['',self._getBattle, DynamicPropertyTrait.DIRECT, f'{{{self._currentBattle}}}'], ['',self._clearBattle, DynamicPropertyTrait.DIRECT, f'{{-1}}'], name = self._battleNo, numeric = True, value = f'{{-1}}', description = 'Set battle number'), # This setBattle sets current attacker in global property GlobalPropertyTrait(['',self._setBattle, GlobalPropertyTrait.DIRECT, '{IsAttacker}'], name = self._currentAttacker, numeric = True, description = 'Set attacker'), ReportTrait(self._getBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+" current battle # "+' f'{self._currentBattle}+" -> "+' f'{self._battleNo}):""}}')), ReportTrait(self._clearBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+" Clear this global="+' f'{self._currentBattle}+" this="+' f'{self._battleNo}):""}}')), ] place = [] trig = [] rept = [] for i, bm in enumerate(self._battleMarks): kn = self._markBattle+str(i+1) path = PlaceTrait.SKEL_PATH.format('BattleMarkers',bm) place.append( PlaceTrait(command = '',#f'Add battle marker {i}', key = kn, markerSpec = path, markerText = 'null', xOffset = -8, yOffset = -16, matchRotation = False, afterKey = self._getBattle, gpid = self._game.nextPieceSlotId(), description = f'Add battle marker {i+1}', placement = PlaceTrait.ABOVE, above = False)) # Get current global battle number # Set current battle # Filtered on current global battle # is equal to trig.append( TriggerTrait(command = '',#Mark battle', key = self._markBattle, actionKeys = [self._getBattle, self._setBattle,kn], property = f'{{{self._currentBattle}=={i+1}}}')) rept.append( ReportTrait(kn, report=(f'{{{self._debug}?' f'("~ "+BasicName+" placing marker ({i+1})' f' ="+ {self._currentBattle}+"' f'={kn}"):""}}'))) oth = [ GlobalHotkeyTrait(name = 'Declare battle', key = self._markKey, globalHotkey = self._markKey, description = 'Mark for combat'), GlobalPropertyTrait( ['',self._calcBattleAF,GlobalPropertyTrait.DIRECT, f'{{EffectiveAF+{self._battleAF}}}'], name = self._battleAF, numeric = True, description = 'Update battle AF'), GlobalPropertyTrait( ['',self._calcBattleDF,GlobalPropertyTrait.DIRECT, f'{{EffectiveDF+{self._battleDF}}}'], name = self._battleDF, numeric = True, description = 'Update battle AF'), GlobalPropertyTrait( ['',self._calcBattleShft,GlobalPropertyTrait.DIRECT, f'{{OddsShift}}'], name = self._battleShift, numeric = True, description = 'Update battle shift', ), CalculatedTrait(#This could be redefined in module name = 'EffectiveAF', expression = '{CF}', description = 'Current attack factor'), CalculatedTrait(#This could be redefined in module name = 'EffectiveDF', expression = '{DF}', description = 'Current defence factor'), CalculatedTrait(#This could be redefined in module name = 'IsAttacker', expression = '{Phase.contains(Faction)}', description = 'Check if current phase belongs to faction'), CalculatedTrait(#This could be redefined in module name = 'OddsShift', expression = f'{{{self._battleShift}}}', description = 'Check if current phase belongs to faction'), ReportTrait(self._calcBattleAF, report=(f'{{{self._verbose}?' f'("! "+BasicName+' f'" add "+EffectiveAF+' f'" to total attack factor -> "+' f'{self._battleAF}' f'):""}}')), ReportTrait(self._calcBattleDF, report=(f'{{{self._verbose}?' f'("! "+BasicName+' f'" add "+EffectiveDF+' f'" to total defence factor -> "+' f'{self._battleDF}' f'):""}}')), ReportTrait(self._calcBattleShft, report=(f'{{{self._debug}?' f'("~ "+BasicName+' f'" Updating odds shift with "+OddsShift+' f'" -> "+{self._battleShift}):""}}')), ] traits.extend( place+ trig+ oth+ [MarkTrait(name=self._battleUnit,value=True), BasicTrait()]) prototypes.addPrototype(name = self._battleUnit, description = '', traits = traits) # ---------------------------------------------------------------- def addBattleCorePrototype(self,prototypes): # --- Core traits for battle markers (number, odds, results) # - Set the global current battle number # - Get the current global battle number # - Clear this counter # - Trampoline to global command to clear all marks for this battle traits = [ # NoStackTrait(select = NoStackTrait.NORMAL_SELECT, # move = NoStackTrait.NORMAL_MOVE, # canStack = False, # ignoreGrid = False), GlobalPropertyTrait(['',self._setBattle,GlobalPropertyTrait.DIRECT, f'{{{self._battleNo}}}'], name = self._currentBattle, numeric = True, description = 'Set current battle'), GlobalPropertyTrait(['',self._setBattle, GlobalPropertyTrait.DIRECT, '{IsAttacker}'], name = self._currentAttacker, numeric = True, description = 'Set attacker'), DynamicPropertyTrait(['',self._getBattle, DynamicPropertyTrait.DIRECT, f'{{{self._currentBattle}}}'], name = self._battleNo, numeric = True, value = f'{{{self._battleNo}}}', description = 'Set battle number'), DynamicPropertyTrait(['',self._getBattle, DynamicPropertyTrait.DIRECT, f'{{{self._currentAttacker}}}'], name = 'IsAttacker', numeric = True, value = 'false', description = 'Set attacker'), DeleteTrait('',self._clearBattle), GlobalHotkeyTrait(name = '', key = self._clearBattle+'Trampo', globalHotkey = self._clearBattle, description = 'Clear selected battle'), TriggerTrait(command = 'Clear', key = self._clearKey, actionKeys = [self._setBattle, self._clearBattle+'Trampo']), ReportTrait(self._setBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+" battle # "+' f'{self._battleNo}+" -> "+' f'{self._currentBattle}):""}}')), ReportTrait(self._getBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+" current battle # "+' f'{self._currentBattle}+" -> "+' f'{self._battleNo}):""}}')), ReportTrait(self._clearBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+" Clear this global="+' f'{self._currentBattle}+" this="+' f'{self._battleNo}):""}}')), ReportTrait(self._clearKey, report=(f'{{{self._debug}?' f'("~ "+BasicName+" To clear battle # global="+' f'{self._currentBattle}+" this="+' f'{self._battleNo}):""}}')), ReportTrait(self._clearBattle+'Trampo', report=(f'{{{self._debug}?' f'("~ "+BasicName+" ' f'Forward clear battle # global="+' f'{self._currentBattle}+" this="+' f'{self._battleNo}):""}}')), MarkTrait(name=self._battleMark,value=True), BasicTrait() ] prototypes.addPrototype(name = self._currentBattle, description = '', traits = traits) # ---------------------------------------------------------------- def addBattlePrototypes(self,prototypes): if not self.addBattleControlPrototype(prototypes): return self.addBattleCalculatePrototype(prototypes) self.addBattleUnitPrototype(prototypes) self.addBattleCorePrototype(prototypes) # ---------------------------------------------------------------- def markerTraits(self): return [DeleteTrait(), RotateTrait()] # ---------------------------------------------------------------- def battleMarkerTraits(self,c): '''Derives from the CurrentBattle prototype and adds a submenu to place odds counter on the battle marker''' traits = [PrototypeTrait(name=self._currentBattle), NonRectangleTrait(filename = c['filename'], image = c['img'])] subs = [] ukeys = [] place = [] trig = [] rept = [] repp = [] for i, odds in enumerate(self._oddsMarks): on = odds.replace('odds marker','').strip() om = odds.replace(':',r'\:') kn = self._markOdds+str(i+1) gpid = self._game.nextPieceSlotId() path = PlaceTrait.SKEL_PATH.format('OddsMarkers',om) subs.append(on) place.append( PlaceTrait(command = '', key = kn, markerSpec = path, markerText = 'null', xOffset = -6, yOffset = -8, matchRotation = False, afterKey = self._getBattle+'Details', gpid = gpid, placement = PlaceTrait.ABOVE, description = f'Add odds marker {on}')) trig.append( TriggerTrait(name = '', command = on, key = kn+'real', actionKeys = [ self._setBattle, kn])) rept.append( ReportTrait(kn+'real', report=(f'{{{self._debug}?' f'("~ "+BasicName+": Set odds ' f'{on} ({kn})"):""}}'))) repp.append( ReportTrait(kn, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Place odds ' f'{on} ({kn})"):""}}'))) ukeys.append(kn+'real') auto = [] auton = [] if len(self._oddsMarks) > 0: auton = ['Auto'] for i, odds in enumerate(self._oddsMarks): trig.append( TriggerTrait(name = '', command = '', key = self._markOdds+'Auto', property = f'{{{self._battleIdx}=={i+1}}}', actionKeys = [self._markOdds+str(i+1)])) auto = [GlobalHotkeyTrait(name = '', key = self._calcBattleOdds, globalHotkey = self._calcBattleOdds, description = 'Calculate fraction'), DeselectTrait(command = '', key = self._calcBattleOdds+'Deselect', deselect = DeselectTrait.ONLY), ReportTrait(self._calcBattleOdds+'Deselect', report=(f'{{{self._debug}?' f'("~ "+BasicName+": Select only this "' f'+" Attacker="+IsAttacker' f'):""}}')), TriggerTrait(name = '', command = '', key = self._markOdds+'Trampoline', actionKeys = [ self._calcBattleOdds, self._markOdds+'Auto', self._calcBattleOdds+'Deselect'], property = f'{{!IsAttacker}}' ), TriggerTrait(name = '', command = 'Auto', key = self._calcBattleOdds+'Start', actionKeys = [ self._setBattle, self._markOdds+'Trampoline', ]), ReportTrait(self._calcBattleOdds+'Start', report=(f'{{{self._debug}?' f'("~ "+BasicName+": Battle odds "+' f'{self._battleOdds}):""}}')), ReportTrait(self._markOdds+'Auto', report=(f'{{{self._debug}?' f'("~ "+BasicName+": ' f'Auto battle odds "+' f'{self._battleOdds}):""}}')) ] traits.extend([ RestrictCommandsTrait( name='Hide when auto-odds are enabled', hideOrDisable = RestrictCommandsTrait.HIDE, expression = f'{{{self._autoOdds}==true}}', keys = ukeys)]+ place +trig +auto +rept +repp) if len(subs) > 0: traits.extend([ SubMenuTrait(subMenu = 'Odds', keys = auton+subs), ]) return traits # ---------------------------------------------------------------- def oddsMarkerTraits(self,c=None): '''Derives from the CurrentBattle prototype and adds a submenu to replace odds counter with result marker''' gpid = self._game.nextPieceSlotId() traits = [PrototypeTrait(name=self._currentBattle), MarkTrait(self._oddsMark,'true'), NonRectangleTrait(filename = c['filename'], image = c['img']), DynamicPropertyTrait( ['',self._getBattle+'More',DynamicPropertyTrait.DIRECT, (f'{{{self._battleAF}+" vs "+{self._battleDF}+' f'" (odds "+{self._battleOdds}+" shift "+' f'{self._battleShift}+")"}}')], name = 'BattleDetails', value = '', numeric = False, description = 'Stored battle details'), TriggerTrait(command = '', key = self._getBattle+'Details', actionKeys = [self._getBattle, self._getBattle+'More']), # DeleteTrait('',self._recalcOdds+'Delete'), # ReplaceTrait(command = '', # key = self._recalcOdds+'Replace', # markerSpec = '', # markerText = 'null', # xOffset = 0, # yOffset = 0, # matchRotation = False, # afterKey = '', # gpid = gpid, # description = f'Replace with nothing'), GlobalHotkeyTrait(name = '', key = self._calcBattleOdds+'Start', globalHotkey = self._calcBattleOdds+'ReAuto', description = 'Trampoline to global'), # Recalculate odds # First setBatle to make battle No global # Then delete this # Then send global key command TriggerTrait(command = 'Recalculate', key = self._recalcOdds, actionKeys = [self._setBattle, self._calcBattleOdds+'Start', self._clearBattle, ]), ReportTrait(self._recalcOdds+'Delete', report=(f'{{{self._debug}?' f'("~ "+BasicName+' f'": Deleting self"):""}}')), ReportTrait(self._clearBattle, report=(f'{{{self._debug}?' f'("~ "+BasicName+' f'": Remove"):""}}')), ReportTrait(self._recalcOdds, report=(f'{{{self._debug}?' f'("! Recalculate Odds"):""}}')), ReportTrait(self._calcBattleOdds+'Start', report=(f'{{{self._debug}?' f'("~ Start auto recalculate Odds"):""}}')), ] subs = [] place = [] trig = [] rept = [] ukeys = [self._recalcOdds] first = '' for i, result in enumerate(self._resultMarks): r = result.replace('result marker','').strip() kn = self._markResult+str(i+1) gpid = self._game.nextPieceSlotId() ukeys.append(kn+'real') subs.append(r) if first == '': first = r path = PlaceTrait.SKEL_PATH.format('ResultMarkers',result) place.append( ReplaceTrait(command = '', key = kn, markerSpec = path, markerText = 'null', xOffset = -6, yOffset = -8, matchRotation = False, afterKey = self._getBattle, gpid = gpid, description = f'Add result marker {r}')) trig.append( TriggerTrait(name = '', command = r, key = kn+'real', actionKeys = [ self._setBattle, kn])) rept.append( ReportTrait(kn+'real', report=(f'{{{self._debug}?' f'("~ "+BasicName+" setting result ' f'{r}"):""}}'))) auto = [] auton = [] if len(self._resultMarks) > 0: auton = ['Auto'] for i, res in enumerate(self._resultMarks): r = res.replace('result marker','').strip() trig.append( TriggerTrait( name = '', command = '', key = self._markResult+'Auto', property = f'{{{self._battleResult}=="{r}"}}', actionKeys = [self._markResult+str(i+1)])) auto = [ # Override in the module CalculatedTrait( name = 'Die', expression = '{GetProperty("1d6_result")}', description = 'Die roll'), GlobalHotkeyTrait( name = '', key = self._rollDice, globalHotkey = self._diceKey, description = 'Roll dice'), CalculatedTrait( name = 'BattleResult', expression = f'{{"{first}"}}', ), GlobalPropertyTrait( ['',self._calcBattleRes+'real',GlobalPropertyTrait.DIRECT, '{BattleResult}'], name = self._battleResult, numeric = False, description = 'Set combat result'), TriggerTrait(name = '', command = 'Resolve', key = self._resolveKey, property = f'{{{self._autoResults}==true}}', actionKeys = [ self._setBattle, self._rollDice, self._calcBattleRes+'real', self._markResult+'Auto', ]), ReportTrait(self._calcBattleRes, report=(f'{{{self._debug}?' f'("~ "+BasicName+": Battle result "+' f'{self._battleOdds}):""}}')), ReportTrait(self._markResult+'Auto', report=(f'{{{self._debug}?' f'("~ "+BasicName+": Auto battle result "+' f'{self._battleResult}):""}}')), ReportTrait(self._markResult+'Auto', report=(f'{{"` Battle # "+{self._battleNo}+": "+' f'BattleDetails+' f'" with die roll "+Die+": "+' f'{self._battleResult}' # f'+ ""' f'}}')), MarkTrait(name=self._battleOddsM,value='true') ] traits.extend( [RestrictCommandsTrait( name='Hide when auto-results are enabled', hideOrDisable = RestrictCommandsTrait.HIDE, expression = f'{{{self._autoResults}==true}}', keys = ukeys)]+ place +trig +auto) if len(subs) > 0: traits.append(SubMenuTrait(subMenu = 'Result', keys = subs)) return traits # ---------------------------------------------------------------- def resultMarkerTraits(self,c=None): traits = [PrototypeTrait(name=self._currentBattle), NonRectangleTrait(filename = c['filename'], image = c['img'])] return traits # ---------------------------------------------------------------- def factionTraits(self,faction): offX = 36 * self._counterScale * self._resolution/150 offY = -38 * self._counterScale * self._resolution/150 traits = [#ReportTrait(self._eliminateKey, # self._restoreKey, # self._trailKey), TrailTrait(lineWidth = 5, radius = 10, key = self._trailKey), RotateTrait(), MovedTrait(xoff = int(offX),yoff = int(offY)), DeleteTrait(), SendtoTrait(mapName = 'DeadMap', boardName = f'{faction} pool', name = 'Eliminate', key = self._eliminateKey, restoreName = 'Restore', restoreKey = self._restoreKey, description = 'Eliminate unit'), # ReportTrait(self._trailKey, # f'{{"Enabling trail on "+BasicName}}'), PrototypeTrait(name=self._battleUnit), MarkTrait(name='Faction',value=faction)] return traits # ---------------------------------------------------------------- def getFactors(self,val): cf = None mf = None df = None ra = None try: if 'chit 1 factor' in val: vv = val.replace('chit 1 factor=','') cf = int(vv) elif 'chit 2 factors artillery' in val: vv = val.replace('chit 2 factors artillery=','') cf,mf,ra = [int(v) for v in vv.strip('=').split()] elif 'chit 2 factors' in val: vv = val.replace('chit 2 factors=','') cf,mf = [int(v) for v in vv.split()] elif 'chit 3 factors' in val: vv = val.replace('chit 3 factors=','') cf,df,mf = [int(v) for v in vv.split()] # Set defensive factor combat factor if not defined. if df is None and cf is not None: df = cf except Exception as e: print(f'\nWarning when extracting factors: {e} ' f'in "{val}" -> "{vv}"') return None,None,None,None pass return cf,df,mf,ra # ---------------------------------------------------------------- def pieceTraits(self,subn,subc,cn,c): from re import sub bb = self.getBB(c['img']) height = bb[3]-bb[1] if bb is not None else 1 width = bb[2]-bb[0] if bb is not None else 1 cf = subc.get(cn + ' flipped', None) traits = [PrototypeTrait(name=f'{subn} prototype')] def clean(s): return s.strip().replace(',',' ').replace('/',' ').strip() if not self._nonato: mains = c.get('mains','') mm = clean(mains).strip() traits.append(PrototypeTrait(name=f'{mm} prototype')) # Commented code adds all 'main' types as prototypes, which # doesn't make so much sense # # m = set([clean(m) for m in mains.split(',')]) # traits.extend([PrototypeTrait(name=f'{m.strip()} prototype') # for m in set(m)]) for p in ['echelon','command']: val = c.get(p,None) if val is not None: pv = f'{val.strip()} prototype' traits.append(PrototypeTrait(name=pv)) if cf is not None: traits.extend([ LayerTrait(images = [c['filename'], cf['filename']], newNames = ['','Reduced +'], activateName = '', decreaseName = '', increaseName = 'Flip', increaseKey = self._flipKey, decreaseKey = '', name = 'Step'), ReportTrait(self._flipKey)]) if not self._nochit: def clean(value): return sub(r'\[[^=]+\]=','',value)\ .replace('{','')\ .replace('}','')\ .replace('/',' ')\ .replace(',',' ')\ .replace('\\',' ') # Add extra marks. This may be useful later on. for field in ['upper left', 'upper right', 'lower left', 'lower right', 'left', 'right', 'factors']: value = c.get('chit',{}).get(field,None) if value is None: continue val = clean(value) val = val\ .replace('chit identifier=','')\ .replace('chit small identifier=','') traits.append(MarkTrait(name = field, value = val)) if field != 'factors': continue af, df, mf, ra = self.getFactors(val) saf, sdf, smf, sra = None,None,None,None if cf is not None: value = cf.get('chit',{}).get(field,None) if value is not None: val = clean(value) val = val\ .replace('chit identifier=','')\ .replace('chit small identifier=','') saf, sdf, smf, sra = self.getFactors(val) rf = [] srf = [] for f,sf,n in [[af,saf,'CF'], [df,sdf,'DF'], [mf,smf,'MF'], [ra,sra,'Range']]: if f is None: continue if sf is None: rf.append(MarkTrait(name=n,value=f)) else: rf .append(MarkTrait(name='Full'+n, value=f)) srf.append(MarkTrait(name='Reduced'+n,value=sf)) traits.append(CalculatedTrait( name = n, expression = (f'{{(Step_Level==2)?' f'Reduced{n}:Full{n}}}'))) traits.extend(rf+srf) return height, width, traits # ---------------------------------------------------------------- def addCounters(self): '''Add all counters (pieces) element to the module. Prototypes are also created as part of this. ''' from re import sub with VerboseGuard('Adding counters') as v: protos = self._game.addPrototypes() self.addNatoPrototypes(protos) self.addBattlePrototypes(protos) pieces = self._game.addPieceWindow(name = 'Counters', icon = self.getIcon('unit-icon', '/images/counter.gif'), hotkey = self._countersKey) tabs = pieces.addTabs(entryName='Counters') for subn, subc in self._categories.get('counter',{}).items(): subn = subn.strip() panel = tabs.addPanel(entryName = subn, fixed = False) plist = panel.addList(entryName = f'{subn} counters') traits = [] if subn in ['BattleMarkers']: traits = self.battleMarkerTraits(list(subc.values())[0]) elif subn in ['OddsMarkers']: traits = self.oddsMarkerTraits(list(subc.values())[0]) elif subn in ['ResultMarkers']: traits = self.resultMarkerTraits(list(subc.values())[0]) elif subn.lower() in ['marker', 'markers']: traits = self.markerTraits() else: traits = self.factionTraits(subn) traits.append(BasicTrait()) p = protos.addPrototype(name = f'{subn} prototype', description = f'Prototype for {subn}', traits = traits) v('') with VerboseGuard(f'Adding pieces for "{subn}"') as vv: for i, (cn, c) in enumerate(subc.items()): if cn.endswith('flipped'): continue if i == 0: v('',end='') vv(f'[{cn}',end='',flush=True,noindent=True) height, width, traits = self.pieceTraits(subn,subc,cn,c) if cn == self._hiddenName: traits = [ PrototypeTrait(name=self._battleCtrl), PrototypeTrait(name=self._battleCalc)] if self._diceInit is not None: traits.extend(self._diceInit) traits.append( RestrictAccessTrait(sides=[], description='Fixed')) #if cn.startswith('odds marker'): # cn = cn.replace(':','_') gpid = self._game.nextPieceSlotId() traits.extend([BasicTrait(name = c['name'], filename = c['filename'], gpid = gpid)]) ps = plist.addPieceSlot(entryName = cn, gpid = gpid, height = height, width = width, traits = traits) if cn == self._hiddenName: self._hidden = ps vv('] ',end='',flush=True,noindent=True) vv('') # ---------------------------------------------------------------- def addNotes(self,**kwargs): '''Add a `Notes` element to the module Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs ''' self._game.addNotes(**kwargs) # ---------------------------------------------------------------- def addInventory(self,**kwargs): '''Add a `Inventory` element to module Parameters ---------- kwargs : dict Dictionary of attribute key-value pairs ''' filt = '{' + '||'.join([f'Faction=="{s}"' for s in self._sides])+'}' grp = 'Faction,Command,Echelon,Type' self._game.addInventory(include = filt, groupBy = grp, sortFormat = '$PieceName$', tooltip ='Show inventory of all pieces', zoomOn = True, **kwargs) # ---------------------------------------------------------------- def addBoard(self,name,info,hasFlipped=False): '''Add a `Board` element to module Parameters ---------- name : str Name of board info : dict Information on board image hasFlipped : bool True if any piece can be flipped ''' with VerboseGuard(f'Adding board {name}') as v: # from pprint import pprint # pprint(info) map = self._game.addMap(mapName=name, markUnmovedHotkey=self._clearMoved) map.addCounterDetailViewer( propertyFilter=f'{{{self._battleMark}!=true}}', fontSize = 14, summaryReportFormat = '$LocationName$', hotkey = key('\n'), stopAfterShowing = True ) map.addHidePiecesButton() map.addGlobalMap() # Basics map.addStackMetrics() map.addImageSaver() map.addTextSaver() map.addForwardToChatter() map.addMenuDisplayer() map.addMapCenterer() map.addStackExpander() map.addPieceMover() map.addKeyBufferer() map.addSelectionHighlighters() map.addHighlightLastMoved() map.addZoomer() map.addMassKey(name = 'Eliminate', buttonHotkey = self._eliminateKey, hotkey = self._eliminateKey, icon = self.getIcon('eliminate-icon', '/icons/16x16/edit-undo.png'), tooltip = 'Eliminate selected units') map.addMassKey(name = 'Delete', buttonHotkey = self._deleteKey, hotkey = self._deleteKey, icon = self.getIcon('delete-icon', '/icons/16x16/no.png'), tooltip = 'Delete selected units') map.addMassKey(name = 'Trail', buttonHotkey = self._trailKey, hotkey = self._trailKey, icon = '', tooltip = '') map.addMassKey(name='Rotate CW', buttonHotkey = self._rotateCWKey, hotkey = self._rotateCWKey, icon = '', #/icons/16x16/no.png', tooltip = 'Rotate selected units') map.addMassKey(name='Rotate CCW', buttonHotkey = self._rotateCCWKey, hotkey = self._rotateCCWKey, icon = '', #/icons/16x16/no.png', tooltip = 'Rotate selected units') map.addMassKey(name='Phase clear moved markers', buttonHotkey = self._clearMoved+'Phase', hotkey = self._clearMoved+'Trampoline', canDisable = True, target = '', filter = f'{{{self._battleCtrl}==true}}', propertyGate = f'{self._noClearMoves}', icon = '', #/icons/16x16/no.png', tooltip = 'Phase clear moved markers', reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Phase Clear moved markers "+' f'{self._noClearMoves})' f':""}}')) if hasFlipped: map.addMassKey(name = 'Flip', buttonHotkey = self._flipKey, hotkey = self._flipKey, icon = self.getIcon('flip-icon', '/images/Undo16.gif'), tooltip = 'Flip selected units') if len(self._battleMarks) > 0: v(f'Adding battle mark interface') ctrlSel = f'{{{self._battleCtrl}==true}}' oddsSel = f'{{{self._battleMark}==true}}' calcSel = f'{{{self._battleCalc}==true}}' curSel = (f'{{{self._battleNo}=={self._currentBattle}}}') curAtt = (f'{{{self._battleNo}=={self._currentBattle}&&' f'{self._battleUnit}==true&&' f'IsAttacker==true}}') curDef = (f'{{{self._battleNo}=={self._currentBattle}&&' f'{self._battleUnit}==true&&' f'IsAttacker==false}}') curUnt = (f'{{{self._battleNo}=={self._currentBattle}&&' f'{self._battleUnit}==true}}') markSel = (f'{{{self._battleNo}=={self._currentBattle}&&' f'{self._battleMark}==true&&' f'{self._oddsMark}!=true}}') # ctrlSel = '{BasicName=="wg hidden unit"}' map.addMassKey(name = 'User mark battle', buttonHotkey = self._markKey, buttonText = '', hotkey = self._markBattle, icon = f'battle-marker-icon.{self._img_format}', tooltip = 'Mark battle', target = '', singleMap = False, filter = ctrlSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'User marks battle # "+' f'{self._currentBattle})' f':""}}')) map.addMassKey(name = 'Selected mark battle', buttonHotkey = self._markBattle, hotkey = self._markBattle, icon = '', tooltip = '', singleMap = False, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Mark battle # "+' f'{self._currentBattle})' f':""}}')) map.addMassKey(name = 'Clear current battle', buttonText = '', buttonHotkey = self._clearBattle, hotkey = self._clearBattle, icon = '', tooltip = '', target = '', singleMap = False, filter = curSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Clear battle # "+' f'{self._currentBattle})' f':""}}')) map.addMassKey(name = 'Clear selected battle', buttonText = '', buttonHotkey = self._clearKey, hotkey = self._clearKey, icon = '', tooltip = '', singleMap = False, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Clear battle # "+' f'{self._currentBattle})' f':""}}')) map.addMassKey(name = 'Clear all battles', buttonText = '', buttonHotkey = self._clearAllBattle, hotkey = self._clearBattle, icon = '', tooltip = '', target = '', singleMap = False, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Clear all battle markers")' f':""}}')) map.addMassKey(name = 'User clear all battles', buttonText = '', buttonHotkey = self._clearAllKey, hotkey = self._clearAllBattle, icon = f'clear-battles-icon.{self._img_format}', tooltip = 'Clear all battles', target = '', singleMap = False, filter = ctrlSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'User clears battle markers")' f':""}}')) map.addMassKey(name = 'Phase clear all battles', buttonText = '', buttonHotkey = self._clearBattlePhs, hotkey = self._clearAllBattle, icon = '', tooltip = 'Clear all battles', canDisable = True, propertyGate = f'{self._noClearBattles}', target = '', singleMap = False, filter = ctrlSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Phase clears battle markers "+' f'{self._noClearBattles})' f':""}}')) map.addMassKey(name = 'Selected resolve battle', buttonHotkey = self._resolveKey, hotkey = self._resolveKey, icon = f'resolve-battles-icon.{self._img_format}', tooltip = 'Resolve battle', singleMap = False, filter = oddsSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Resolve battle # "+' f'{self._currentBattle})' f':""}}')) map.addMassKey(name = 'Sum AFs', buttonText = '', buttonHotkey = self._calcBattleAF, hotkey = self._calcBattleAF, icon = '', tooltip = '', target = '', singleMap = False, filter = curAtt, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Calculate total AF"):""}}')) map.addMassKey(name = 'Sum DFs', buttonText = '', buttonHotkey = self._calcBattleDF, hotkey = self._calcBattleDF, icon = '', tooltip = '', target = '', singleMap = False, filter = curDef, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Calculate total DF"):""}}')) map.addMassKey(name = 'Sum odds shifts', buttonText = '', buttonHotkey = self._calcBattleShft, hotkey = self._calcBattleShft, icon = '', tooltip = '', target = '', singleMap = False, filter = curUnt, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Calculate odds shift"):""}}')) map.addMassKey(name = 'Calc battle odds', buttonText = '', buttonHotkey = self._calcBattleOdds, hotkey = self._calcBattleOdds, icon = '', tooltip = '', target = '', singleMap = False, filter = calcSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Calculate odds"):""}}')) map.addMassKey(name = 'Auto calc battle odds', buttonText = '', buttonHotkey = self._calcBattleOdds+'Auto', hotkey = self._calcBattleOdds+'Start', icon = '', tooltip = '', # target = '', singleMap = False, filter = markSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Auto calculate odds"):""}}')) map.addMassKey(name = 'User recalc', buttonHotkey = self._recalcOdds, buttonText = '', hotkey = self._recalcOdds, icon = '', tooltip = 'Recalculate odds', singleMap = False, filter = '', reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Recalculate odds"):""}}')) map.addMassKey(name = 'Auto recalc battle odds', buttonText = '', buttonHotkey = self._calcBattleOdds+'ReAuto', hotkey = self._calcBattleOdds+'Start', icon = '', tooltip = '', target = '', singleMap = False, filter = markSel, reportFormat = (f'{{{self._debug}?' f'("~ {name}: ' f'Auto re-calculate odds"):""}}')) v(f'Getting the board dimensions') ulx,uly,lrx,lry = self.getBB(info['img']) width = int(abs(ulx - lrx)) height = int(abs(uly - lry)) # Why is it we take the width and height like this? # Do they every differ from the above? # This is the only place that we actually use this # # width, height = self.getWH(info['img']) height += 20 width += 5 # v(f'{ulx},{uly},{lrx},{lry}') v(f'Board BB=({lrx},{lry})x({ulx},{uly}) {width}x{height}') picker = map.addBoardPicker() board = picker.addBoard(name = name, image = info['filename'], width = width, height = height) zoned = board.addZonedGrid() zoned.addHighlighter() if not 'zones' in info: color = rgb(255,0,0) full = zoned.addZone(name = 'full', useParentGrid = False, path=(f'{ulx},{uly};' + f'{lrx},{uly};' + f'{lrx},{lry};' + f'{ulx},{lry}')) grid = full.addHexGrid(color = color, dx = HEX_WIDTH, dy = HEX_HEIGHT, visible = self._visible) grid.addNumbering(color = color, hType = 'A', hOff = -1, vType = 'N', vOff = -1, visible = self._visible) return w = abs(ulx-lrx) h = abs(uly-lry) self.addZones(zoned,name,info['zones'],w,h) if self._hidden is not None: v(f'Adding hidden unit to map {name}') at = map.addAtStart(name = self._hiddenName, location = '', useGridLocation = False, owningBoard = name, x = 0, y = 0) at.addPieces(self._hidden) # ---------------------------------------------------------------- def addDeadMap(self): '''Add a "Dead Map" element to the module ''' name = 'DeadMap' with VerboseGuard(f'Adding deadmap {name}') as v: map = self._game.addMap(mapName = name, buttonName = '', markMoved = 'Never', launch = True, icon = self.getIcon('pool-icon', '/images/playerAway.gif'), allowMultiple = True, hotkey = self._deadKey) # Basics map.addStackMetrics() map.addImageSaver() map.addTextSaver() map.addForwardToChatter() map.addMenuDisplayer() map.addMapCenterer() map.addStackExpander() map.addPieceMover() map.addKeyBufferer() map.addSelectionHighlighters() map.addHighlightLastMoved() map.addZoomer() map.addMassKey(name='Restore', buttonHotkey = self._restoreKey, hotkey = self._restoreKey, icon = self.getIcon('restore-icon', '/images/Undo16.gif'), tooltip = 'Restore selected units') picker = map.addBoardPicker() picker.addSetup(maxColumns=len(self._sides),mapName=name, boardNames=[s+' pool' for s in self._sides]) for i, s in enumerate(self._sides): v(f'Adding {s} pool') color = [0,0,0,64] color[i % 3] = 255 w = 400 h = 400 c = rgba(*color) img = '' dimg = self._categories.get('pool',{}).get('all',{})\ .get(s,None) if dimg: bb = self.getBB(dimg['img']) w = bb[2] - bb[0] h = bb[3] - bb[1] c = '' img = dimg['filename'] v(f'Using image provided by user {img}') board = picker.addBoard(name = f'{s} pool', image = img, width = w, height = h, color = c) if dimg is None or not 'zones' in dimg: continue zoned = board.addZonedGrid() zoned.addHighlighter() w = abs(w) h = abs(h) self.addZones(zoned,board['name'],dimg['zones'],w,h) # -------------------------------------------------------------------- def getPictureInfo(self,picture,name,width,height): ''' Returns ------- hex_width, hex_height : float, float Scale hex width scx, scy : float, float, float, float Scale to image and picture (x,y) rot90 : bool True if rotated +/-90 degrees tran : callable Translation function ''' if picture is None: print(f'WARNING: No Tikz picture information.' f"Are you sure you used the `[zoned]' option for the " f"tikzpicture environment of {name}?") f = lambda x,y: (x,y) return HEX_WIDTH,HEX_HEIGHT,1,1,False,f # Get picture bounding box tll = picture['lower left'] tur = picture['upper right'] # Get picture transformation pa = picture['xx'] pb = picture['xy'] pc = picture['yx'] pd = picture['yy'] # Get picture offset (always 0,0?) pdx = picture['dx'] pdy = picture['dy'] # Define picture global transformation pr = lambda x,y: (pa * x + pc * y, pb * x + pd * y) # Globally transform (rotate) picture bounding box pll = pr(*tll) pur = pr(*tur) # Calculate widht, height, and scaling factors pw = pur[0] - pll[0] ph = pur[1] - pll[1] scw = width / pw sch = height / ph # Extract picture scales and rotation # Courtesy of # https://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix from math import sqrt, atan2, degrees, isclose psx = sqrt(pa**2 + pb**2) # * (-1 if pa < 0 else 1) psy = sqrt(pc**2 + pd**2) # * (-1 if pd < 0 else 1) prt = degrees(atan2(pc,pd)) if not any([isclose(abs(prt),a) for a in [0,90,180,270]]): raise RuntimeException('Rotations of Tikz pictures other than ' '0 or +/-90,+/- 180, or +/-270 not supported. ' 'found {prt}') rot90 = int(prt // 90) if rot90 == 2: rot90 = -2 # Now supported # if any([isclose(prt,a) for a in [90,270,180,-180]]): # print(f'WARNING: rotations by {prt} not fully supported') from math import sqrt hex_width = psx * scw * 2 # HEX_WIDTH hex_height = psy * sch * sqrt(3) # HEX_HEIGHT with VerboseGuard('Picture') as v: v(f'Transformations: {pa},{pb},{pc},{pd}') v(f'Scale (x,y): {psx},{psy}') v(f'Rotation (degrees): {prt} ({rot90})') v(f'Scale to pixels (x,y): {scw},{sch}') # When translating the Tikz coordinates, it is important to note # that the Tikz y-axis point upwards, while the picture y-axis # point downwards. This means that the upper right corner is at # (width,0) and the lower left corner is at (0,height). def tranx(x,off=-pll[0]): # print(f'x: {x} + {off} -> {x+off} -> {int(scw*(x+off))}') return int(scw * (x + off)+.5) def trany(y,off=-pur[1]): # print(f'y: {y} + {off} -> {y+off} -> {-int(sch*(y+off))}') return -int(sch * (y + off)+.5) tran = lambda x,y : (tranx(x), trany(y)) return hex_width, hex_height, scw * psx, sch * psy, rot90, tran # -------------------------------------------------------------------- def getHexParams(self, llx, lly, urx, ury, mx, my, hex_width, hex_height, rot90, labels, coords, targs, nargs): '''rot90 = 0 No rotation = 1 Rotated -90 (clock-wise) = -1 Rotated 90 (counter clock-wise) = -2 Rotated 180 ''' with VerboseGuard('Hex parameters') as v: from math import sqrt isodd = lambda x : (x % 2 == 1) iseven = lambda x : (x % 2 == 0) isfalse = lambda x : False shorts = {'isodd': isodd, 'iseven': iseven, 'isfalse': isfalse } # Funny scaling needed by VASSAL. Seems like they only # really about the absolute value of 'dy' and then the # aspect ratio between dx and dy. pxfac = sqrt(3)/2 hex_pw = hex_height * pxfac hex_ph = hex_width * pxfac stagger = False # # Get parameters from coordinates. These should always be set # rows = coords .get('row', {}) columns = coords .get('column',{}) top_short = columns .get('top short', 'isfalse') bot_short = columns .get('bottom short','isfalse') inv_col = columns .get('factor',1) inv_row = rows .get('factor',1) voff = -rows .get('offset',0) # 0: from 0 -> -1 hoff = -columns.get('offset',0) # -1: from 1 -> -2 vdesc = inv_row == 1 hdesc = inv_col == -1 # # Calculate total dimensions, and number of columns and rows # w = abs((urx-llx) - 2 * mx) h = abs((ury-lly) - 2 * my) if abs(rot90) == 1: h, w = w, h nc = int(w // (hex_width * 3 / 4)) nr = int(h // (hex_height)) namrot = {0: 'none - 0', -1: '-90 - CCW', 1: '90 CW', -2: '180 - half-turn'} v(f'Width: {w}') v(f'Height: {h}') v(f'Margins: x={mx} y={my}') v(f'Rotation: {rot90} ({namrot[rot90]})') v(f'Labels: {labels}') v(f'Columns:') v(f' size: {nc}') v(f' start: {hoff}') v(f' direction: {inv_col}') v(f' top short: {top_short}') v(f' bottom short: {bot_short}') v(f'Rows:') v(f' size: {nr}') v(f' start: {voff}') v(f' direction: {inv_row}') v(f'Image:') v(f' BB: ({llx},{lly}) x ({urx},{ury})') # # X0 and Y0 are in the local (rotated) frame of the hex grid. # Thus X is always along hex breadth, and Y along the # height. Thus the base offset (rotated into the hex frame) differs. x0 = ury if abs(rot90) == 1 else llx y0 = llx if abs(rot90) == 1 else ury # Calculate column,row of corners llc = hoff ulc = hoff lrc = hoff+nc-1 urc = hoff+nc-1 # # Swap in directions if hdesc: llc, lrc, ulc, urc = lrc, llc, urc, ulc # is_short_top = shorts[columns.get('top short', 'isfalse')] is_short_bot = shorts[columns.get('bottom short','isfalse')] if is_short_top is isfalse: # Assume fully populated columns is_short_top = isodd if iseven(hoff) else iseven if is_short_bot is isfalse: is_short_bot = isodd if isodd(hoff) else iseven # # Now we have the hex coordinates of the corners. We can # now check how things are offset. Before rotation, we # will have that the first column is offset by hex_pw / 2. x0 += hex_width / 2 # # If the first column is _not_ short on top, then off set # is simply hex_ph / 2. Otherwise, the offset is hex_ph y0 += hex_ph / 2 voff -= 1 voff -= inv_row v(f' Initial offset of image {x0},{y0}') # Treat each kind of rotation separately. Note that -90 and # 180 uses the `is_short_bot' while 0 and 90 uses # `is_short_top'. There might be a way to unify these, if # offsets and so on may warrent it, but it may be complete # overkill. is_off = False col_map = {0 : (ulc, is_short_top, is_short_bot), -1 : (urc, is_short_top, is_short_bot), 1 : (ulc, is_short_bot, is_short_top), -2 : (urc, is_short_bot, is_short_top) } col_chk, is_s1, is_s2 = col_map[rot90] is_off = is_s1(col_chk) if is_off: y0 += hex_ph /2 v(f'Is first column off: {is_off}') # For full columns, noting more is needed # # Below is if some columns are short both top and bottom. # VASSAL seems to start numbering from a given place, and # then use that for the rest numbering, and forgets to # take into account various offsets and the like. hence, # we need to hack it hard. if iseven(nc): v(f'Even number of columns, perhaps hacks') if rot90 == 0: # Hacks # # If the last column is short in both top and bottom, # and we have inverse columns, but not inverse rows, # then add to offset if inv_col == -1 and inv_row == 1 and \ is_s1(urc) and is_s2(urc): voff += 1 # If the column we check for short is short both top # and bottom, and we have inverse rows, but not # inverse columns, then add offset if inv_row == -1 and inv_col == 1 and \ is_s2(col_chk) and is_off: voff += 1 if rot90 == -1: # If the last column is short in both top and bottom, # and we have inverse columns, then add to offset if is_s1(urc) and inv_col == -1 and is_s2(urc): voff -= inv_row if rot90 == 1: voff += inv_row + (inv_row == 1) # If the first column is short in both top and bottom, # and we have inverse columns, then add to offset if is_s1(ulc) and is_s2(ulc) and inv_col == -1: voff += inv_row if rot90 == -2: voff += inv_row * 2 # Hacks If the column we check for short is short both # top and bottom, and we have either inverse rows and # inverse columns, or rows and columns are normal, # then add offset if inv_col == inv_row and is_s1(col_chk) and is_s2(col_chk): voff += 1 # If the first column is short in both top and bottom, # and we have inverse columns and rows, then add to # offset if inv_col == inv_row and inv_col == -1 and \ is_s1(ulc) and is_s2(ulc): voff += 1 else: v(f'Odd number of columns') voff -= inv_row if rot90 == 1: # If we offset in the column direction, add the # inverse row direction, and if we have inverse rows, # substract one, otherwise add 2. voff += (inv_row * hoff + (-1 if inv_row == -1 else 2)) # If we have a short column, and that column is even, # then add, otherwise subtract, the inverse row # direction, if the checked column is even. voff += ((1 if is_off else -1) * inv_row if is_short_bot(2) else 0) if rot90 == 2: voff += inv_row * (2 + is_off) # OK for odd if rot90 == 0: if inv_col == -1 and iseven(nc): # OK stagger = not stagger hoff -= (inv_col == -1) # OK if rot90 == -1: # CCW if inv_col == 1 and iseven(nc): # OK stagger = not stagger vdesc, hdesc = hdesc, vdesc vdesc = not vdesc voff += (inv_row == 1) hoff -= (inv_col == 1) # OK if rot90 == 1: # CW if (inv_col == 1 and iseven(nc)) or isodd(nc): # OK stagger = not stagger vdesc, hdesc = hdesc, vdesc hdesc = not hdesc hoff -= (inv_col == -1) # OK if rot90 == -2: if (inv_col == -1 and iseven(nc)) or isodd(nc): # OK stagger = not stagger vdesc, hdesc = not vdesc, not hdesc hoff -= (inv_col == 1) # OK # Labels if labels is not None: labmap = { 'auto': { 'hLeading': 1,'vLeading': 1,'hType': 'N','vType': 'N' }, 'auto=numbers' : { 'hLeading': 1,'vLeading': 1,'hType': 'N','vType': 'N' }, 'auto=alpha column': { 'hLeading': 0,'vLeading': 0,'hType': 'A','vType': 'N' }, 'auto=alpha 2 column': {# Not supported 'hLeading': 1,'vLeading': 1,'hType': 'A','vType': 'N' }, 'auto=inv y x plus 1': { 'hLeading': 1,'vLeading': 1,'hType': 'N','vType': 'N' }, 'auto=x and y plus 1': { 'hLeading': 1,'vLeading': 1,'hType': 'N','vType': 'N' } } for l in labels.split(','): nargs.update(labmap.get(l,{})) if 'alpha column' in l or 'alpha 2 column' in l: hoff -= 1 # VASSAL 0->A, wargame 1->A if l == 'auto=inv y x plus 1': hoff += 1 #inv_row = not inv_row if l == 'auto=x and y plus 1': hoff -= 1 voff -= 1 # Add margins x0 += int(mx) y0 += int(my) targs['dx'] = hex_pw targs['dy'] = hex_ph nargs['vOff'] = voff nargs['hOff'] = hoff nargs['vDescend'] = vdesc nargs['hDescend'] = hdesc targs['edgesLegal'] = True targs['sideways'] = abs(rot90) == 1 nargs['stagger'] = stagger targs['x0'] = int(x0+.5) targs['y0'] = int(y0+.5) # -------------------------------------------------------------------- def getRectParams(self,i,llx,ury,width,height,targs,nargs): targs['dx'] = width targs['dy'] = height targs['x0'] = int(llx - width/2) targs['y0'] = int(ury + height/2) targs['color'] = rgb(0,255,0) nargs['color'] = rgb(0,255,0) nargs['vDescend'] = True nargs['vOff'] = -3 nargs.update({'sep':',','vLeading':0,'hLeading':0}) # ---------------------------------------------------------------- def addZones(self, zoned, name, info, width, height, labels=None, coords=None, picinfo=None): '''Add zones to the Zoned element. Parameters ---------- zoned : Zoned Parent element name : str Name of Zoned info : dict Dictionary of zones informatio width : int Width of parent height : int Height of parent labels : list On recursive call, list of labels coords : list On recursive call, coordinates picinfo : dict On recursive call, picture information ''' grids = [] picture = None with VerboseGuard(f'Adding zones to {name}') as v: for k, val in info.items(): if k == 'labels': labels = val; if k == 'coords': coords = val if k == 'zoned': picture = val if 'zone' not in k or k == 'zoned': continue grids = [[k,val]] + grids # Reverse order! # grids.append([k,v]) if len(grids) < 1: return if picinfo is None: picinfo = self.getPictureInfo(picture,name,width,height) hex_width, hex_height, scx, scy, rot90, tran = picinfo for g in grids: n, i = g v(f'Adding zone {n}') if 'scope' in n: llx,lly = tran(*i['global lower left']) urx,ury = tran(*i['global upper right']) path = [[llx,ury],[urx,ury],[urx,lly],[llx,lly]] nm = n.replace('zone scope ','') elif 'path' in n: path = [tran(*p) for p in i['path']] llx = min([px for px,py in path]) ury = max([py for px,py in path]) nm = n.replace('zone path ','') # Checkf if we have "point" type elements in this object and # add them to dict. points = [ val for k,val in i.items() if (k.startswith('point') and isinstance(val,dict) and \ val.get('type','') == 'point')] pathstr = ';'.join([f'{s[0]},{s[1]}' for s in path]) v(f'Zone path ({llx},{ury}): {pathstr} ({len(points)})') ispool = 'pool' in n.lower() and len(points) <= 0 zone = zoned.addZone(name = nm, locationFormat = ("$name$" if ispool else "$gridLocation$"), useParentGrid = False, path = pathstr) # Do not add grids to pools if ispool: v(f'Board {n} is pool with no points') continue targs = {'color':rgb(255,0,0),'visible':self._visible} nargs = {'color':rgb(255,0,0),'visible':self._visible} # print(targs,nargs) if 'turn' in n.lower(): nargs['sep'] = 'T' if 'oob' in n.lower(): nargs['sep'] = 'O' if len(points) > 0: with VerboseGuard('Using region grid') as vv: grid = zone.addRegionGrid(snapto = True, visible = self._visible) for j,p in enumerate(points): pn = p["name"].strip() pp = p.get('parent','').strip() pc = p["coords"] if j == 0: vv(f'',end='') vv(f'[{pn}] ',end='',flush=True,noindent=True) if pn.endswith(' flipped'): pn = pn[:-len(' flipped')] x, y = tran(*pc) r = grid.addRegion(name = pn, originx = x, originy = y, alsoPiece = True, prefix = pp) v('') elif 'hex' in n.lower(): margin = i.get('board frame',{}).get('margin',0) mx = scx * margin my = scy * margin # self.message(f'{margin} -> {scx},{scy} -> {mx},{my}') w = abs(urx - llx)-2*mx h = abs(ury - lly)-2*my self.getHexParams(llx = llx, lly = lly, urx = urx, ury = ury, mx = mx, my = my, hex_width = hex_width, hex_height = hex_height, rot90 = rot90, labels = labels, coords = coords, targs = targs, nargs = nargs) v(f'Adding hex grid') grid = zone.addHexGrid(**targs) grid.addNumbering(**nargs) else: width = hex_width / HEX_WIDTH * RECT_WIDTH height = hex_height / HEX_HEIGHT * RECT_HEIGHT self.getRectParams(i,llx,ury,width,height,targs,nargs) v(f'Adding rectangular grid') grid = zone.addSquareGrid(**targs) grid.addNumbering(**nargs) # Once we've dealt with this grid, we should see if we have # any embedded zones we should deal with. self.addZones(zoned,name,i,width,height, labels=labels, coords=coords, picinfo=picinfo) # ---------------------------------------------------------------- def addBoards(self): '''Add Boards to the module ''' with VerboseGuard('Adding boards') as v: hasFlipped = False for cn,cd in self._categories.get('counter',{}).items(): for sn in cd: if ' flipped' in sn: hasFlipped = True break v(f'Has flipped? {hasFlipped}') for bn, b in self._categories.get('board',{}).get('all',{}).items(): self.addBoard(bn, b,hasFlipped=hasFlipped) # ---------------------------------------------------------------- def getIcon(self,name,otherwise): with VerboseGuard(f'Get Icon {name}') as v: icon = self._categories\ .get('icon',{})\ .get('all',{})\ .get(name,{ 'filename':otherwise})['filename'] v(f'Using "{icon}"') return icon # ---------------------------------------------------------------- def addOOBs(self): '''Add OOBs to the game''' oobc = self._categories.get('oob',{}).get('all',{}).items() if len(oobc) < 1: return with VerboseGuard(f'Adding OOBs') as v: icon = self.getIcon('oob-icon','/images/inventory.gif') v(f'Using icon "{icon}" for OOB') charts = \ self._game.addChartWindow(name='OOBs', hotkey = self._oobKey, description = 'OOBs', text = '', icon = icon, tooltip = 'Show/hide OOBs') tabs = charts.addTabs(entryName='OOBs') for on, o in oobc: widget = tabs.addMapWidget(entryName=on) self.addOOB(widget, on, o) # ---------------------------------------------------------------- def addOOB(self,widget,name,info): '''Add a OOB elements to the game Parameters ---------- widget : Widget Widget to add to name : str Name info : dict Information on the OOB image ''' map = widget.addWidgetMap(mapName = name, markMoved = 'Never', hotkey = '') map.addCounterDetailViewer() map.addStackMetrics() map.addImageSaver() map.addTextSaver() map.addForwardToChatter() map.addMenuDisplayer() map.addMapCenterer() map.addStackExpander() map.addPieceMover() map.addKeyBufferer() map.addSelectionHighlighters() map.addHighlightLastMoved() map.addZoomer() picker = map.addPicker() ulx,uly,lrx,lry = self.getBB(info['img']) board = picker.addBoard(name = name, image = info['filename']) zoned = board.addZonedGrid() zoned.addHighlighter() if not 'zones' in info: zone = zoned.addZone(name = 'full', useParentGrid = False, path=(f'{ulx},{uly};' + f'{lrx},{uly};' + f'{lrx},{lry};' + f'{ulx},{lry}')) grid = zone.addSquareGrid() grid.addNumbering() return # If we get here, we have board info! w = abs(ulx-lrx) h = abs(uly-lry) self.addZones(zoned,name,info['zones'],w,h) # ---------------------------------------------------------------- def addCharts(self): '''Add Charts elements to game ''' chartc = self._categories.get('chart',{}).get('all',{}).items() if len(chartc) < 1: return with VerboseGuard('Adding charts') as v: charts = self._game.addChartWindow(name = 'Charts', hotkey = self._chartsKey, description = '', text = '', tooltip = 'Show/hide charts', icon = self.getIcon('chart-icon', '/images/chart.gif')) tabs = charts.addTabs(entryName='Charts') for i, (cn, c) in enumerate(chartc): if i == 0: v('',end='') v(f'[{cn}] ',end='',flush=True,noindent=True) tabs.addChart(chartName = cn, description = cn, fileName = c['filename']) v('') # ---------------------------------------------------------------- def addDie(self): '''Add a `Die` element to the module ''' if self._dice is not None and len(self._dice) > 0: return self._game.addDiceButton(name = '1d6', hotkey = self._diceKey) # ==================================================================== def patchVmod(vmod_filename,patch_name,verbose): with VMod(vmod_filename,'r') as vmod: buildFile = BuildFile(vmod.getBuildFile()) moduleData = ModuleData(vmod.getModuleData()) from importlib.util import spec_from_file_location, module_from_spec from pathlib import Path from sys import modules p = Path(patch_name) spec = spec_from_file_location(p.stem, p.absolute()) module = module_from_spec(spec) spec.loader.exec_module(module) modules[p.stem] = module with VMod(vmod_filename,'a') as vmod: module.patch(buildFile, moduleData, vmod, verbose) vmod.replaceFiles(**{VMod.BUILD_FILE : buildFile.encode(), VMod.MODULE_DATA : moduleData.encode()}) # # EOF # # ==================================================================== # From main.py from argparse import ArgumentParser class DefaultSubcommandArgParse(ArgumentParser): _default_subparser = None def set_default_subparser(self, name): self._default_subparser = name def _parse_known_args(self, arg_strings, *args, **kwargs): from argparse import _SubParsersAction in_args = set(arg_strings) d_sp = self._default_subparser if d_sp is not None and not {'-h', '--help'}.intersection(in_args): for x in self._subparsers._actions: subparser_found = ( isinstance(x, _SubParsersAction) and in_args.intersection(x._name_parser_map.keys()) ) if subparser_found: break else: # insert default in first position, this implies no # global options without a sub_parsers specified arg_strings = [d_sp] + arg_strings return super(DefaultSubcommandArgParse, self)._parse_known_args( arg_strings, *args, **kwargs ) # ==================================================================== def patchIt(args): vmodname = args.output.name patchname = args.patch.name args.output.close() args.patch .close() patchVmod(vmodname, patchname, args.verbose) # ==================================================================== def exportIt(args): vmodname = args.output.name patchname = args.patch.name if args.patch is not None else None args.output.close() if args.patch is not None: args.patch.close() Verbose().setVerbose(args.verbose) try: if args.version.lower() == 'draft': args.visible_grids = True rulesname = args.rules.name if args.rules is not None else None tutname = args.tutorial.name if args.tutorial is not None else None exporter = LaTeXExporter(vmodname = vmodname, pdfname = args.pdffile.name, infoname = args.infofile.name, title = args.title, version = args.version, description = args.description, rules = rulesname, tutorial = tutname, patch = patchname, visible = args.visible_grids, vassalVersion = args.vassal_version, nonato = args.no_nato_prototypes, nochit = args.no_chit_information, resolution = args.resolution, counterScale = args.counter_scale, imageFormat = args.image_format) exporter.run() except Exception as e: from sys import stderr print(f'Failed to build {vmodname}: {e}',file=stderr) from os import unlink try: unlink(vmodname) except: pass raise e # ==================================================================== if __name__ == '__main__': from argparse import ArgumentParser, FileType ap = DefaultSubcommandArgParse(description='Create draft VASSAL module') ap.set_default_subparser('export') sp = ap.add_subparsers(dest='mode') pp = sp.add_parser('patch',help='Patch VMod') pp.add_argument('output', help='Module to patch', type=FileType('r'), default='Draft.vmod') pp.add_argument('patch', help='A python script to patch generated module', type=FileType('r'), default='patch.py') pp.add_argument('-V','--verbose', help='Be verbose', action='store_true') ep = sp.add_parser('export',help='Export from PDF and JSON to VMod') ep.add_argument('pdffile', help='The PDF file to read images from', type=FileType('r'), default='export.pdf', nargs='?') ep.add_argument('infofile', help='The JSON file to read image information from', type=FileType('r'), default='export.json', nargs='?') ep.add_argument('-o','--output', help='Output file to write module to', type=FileType('w'), default='Draft.vmod') ep.add_argument('-p','--patch', help='A python script to patch generated module', type=FileType('r')) ep.add_argument('-V','--verbose', help='Be verbose', action='store_true') ep.add_argument('-t','--title', help='Module title', default='Draft', type=str) ep.add_argument('-v','--version', help='Module version', type=str, default='draft') ep.add_argument('-r','--rules', help='Rules PDF file', type=FileType('r')) ep.add_argument('-T','--tutorial', help='Tutorial (v)log file', type=FileType('r')) ep.add_argument('-d','--description', help='Short description of module', type=str, default='draft of module') ep.add_argument('-W','--vassal-version', help='Vassal version number', type=str, default='3.6.7') ep.add_argument('-G','--visible-grids', action='store_true', help='Make grids visible in the module') ep.add_argument('-N','--no-nato-prototypes', action='store_true', help='Do not make prototypes for types,echelons,commands') ep.add_argument('-C','--no-chit-information', action='store_true', help='Do not make properties from chit information') ep.add_argument('-S','--counter-scale', type=float, default=1, help='Scale counters by factor') ep.add_argument('-R','--resolution', type=int, default=150, help='Resolution of images') ep.add_argument('-I','--image-format', choices = ['png','svg'], default='png', help='Image format to use') args = ap.parse_args() if args.mode == 'patch': patchIt(args) else: exportIt(args) # # EOF # ## # End of generated script ##