#!/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
Key
Where
Effect
'''
for key, where, description in keys:
txt += (f'
{key}
'
f'
{where}
'
f'
{description}
')
txt += '''
'''
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
##