texlive[69692] Master/texmf-dist: wargame (3feb24)

commits+karl at tug.org commits+karl at tug.org
Sat Feb 3 22:13:58 CET 2024


Revision: 69692
          https://tug.org/svn/texlive?view=revision&revision=69692
Author:   karl
Date:     2024-02-03 22:13:58 +0100 (Sat, 03 Feb 2024)
Log Message:
-----------
wargame (3feb24)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/wargame/README.md
    trunk/Master/texmf-dist/doc/latex/wargame/compat.pdf
    trunk/Master/texmf-dist/doc/latex/wargame/symbols.pdf
    trunk/Master/texmf-dist/doc/latex/wargame/tutorial/export.tex
    trunk/Master/texmf-dist/doc/latex/wargame/tutorial/game.pdf
    trunk/Master/texmf-dist/doc/latex/wargame/wargame.pdf
    trunk/Master/texmf-dist/source/latex/wargame/Makefile
    trunk/Master/texmf-dist/source/latex/wargame/chit/battle.dtx
    trunk/Master/texmf-dist/source/latex/wargame/chit/misc.dtx
    trunk/Master/texmf-dist/source/latex/wargame/chit/modifiers.dtx
    trunk/Master/texmf-dist/source/latex/wargame/chit/oob.dtx
    trunk/Master/texmf-dist/source/latex/wargame/chit/shape.dtx
    trunk/Master/texmf-dist/source/latex/wargame/chit/table.dtx
    trunk/Master/texmf-dist/source/latex/wargame/hex/labels.dtx
    trunk/Master/texmf-dist/source/latex/wargame/hex/ridges.dtx
    trunk/Master/texmf-dist/source/latex/wargame/hex/terrain.dtx
    trunk/Master/texmf-dist/source/latex/wargame/tests/chits.dtx
    trunk/Master/texmf-dist/source/latex/wargame/tests/test.tex
    trunk/Master/texmf-dist/source/latex/wargame/util/export.dtx
    trunk/Master/texmf-dist/source/latex/wargame/util/tikz.dtx
    trunk/Master/texmf-dist/source/latex/wargame/utils/wgexport.py
    trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.chit.code.tex
    trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.hex.code.tex
    trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.util.code.tex
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.beach.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.city.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.light_woods.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.mountains.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.rough.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.swamp.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.town.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.village.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wargame.woods.pdf
    trunk/Master/texmf-dist/tex/latex/wargame/wgexport.cls
    trunk/Master/texmf-dist/tex/latex/wargame/wgexport.py

Modified: trunk/Master/texmf-dist/doc/latex/wargame/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/wargame/README.md	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/doc/latex/wargame/README.md	2024-02-03 21:13:58 UTC (rev 69692)
@@ -1,6 +1,6 @@
 # A package to make Hex'n'Counter wargames in LaTeX
 
-## Version 0.5
+## Version 0.6
 
 This package can help make classic [Hex'n'Counter
 wargames](https://en.wikipedia.org/wiki/Wargame) using LaTeX. The

Modified: trunk/Master/texmf-dist/doc/latex/wargame/compat.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/wargame/symbols.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/wargame/tutorial/export.tex
===================================================================
--- trunk/Master/texmf-dist/doc/latex/wargame/tutorial/export.tex	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/doc/latex/wargame/tutorial/export.tex	2024-02-03 21:13:58 UTC (rev 69692)
@@ -77,9 +77,9 @@
 % counter.
 %
 %    \begin{macrocode}
-\doublechitimages[A]{\alla}
-\doublechitimages[B]{\allb}
-\doublechitimages[Markers]{{game turn chit}}
+\doublechitimages[A][chit drop shadows]{\alla}
+\doublechitimages[B][chit drop shadows]{\allb}
+\doublechitimages[Markers][chit drop shadows]{{game turn chit}}
 %    \end{macrocode}
 %
 % Again, the macros we made for the counters comes in handy.  Note
@@ -96,7 +96,9 @@
 % can.}.
 %
 %    \begin{macrocode}
-\battlemarkers{12}
+\tikzset{}
+\battlemarkers[marker drop shadows]{12}
+\tikzset{every battle marker/.style={}}
 %    \end{macrocode}
 %
 % Furthermore, we can add ``odds'' and battle result markers.   Odds
@@ -108,7 +110,7 @@
 % in the markers. 
 % 
 %    \begin{macrocode}
-\oddsmarkers{%
+\oddsmarkers[marker drop shadows]{%
   1:3/red!25!white,%
   1:2/red!15!white,%
   1:1/orange!15!white,%
@@ -115,7 +117,7 @@
   2:1/white,%
   3:1/green!15!white,%
   4:1/green!25!white}
-\resultmarkers{
+\resultmarkers[marker drop shadows]{
   AE/red!50!white,
   AR/red!25!white,
   EX/white,

Modified: trunk/Master/texmf-dist/doc/latex/wargame/tutorial/game.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/doc/latex/wargame/wargame.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/source/latex/wargame/Makefile
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/Makefile	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/Makefile	2024-02-03 21:13:58 UTC (rev 69692)
@@ -2,7 +2,7 @@
 #
 #
 NAME		:= wargame
-VERSION		:= 0.5
+VERSION		:= 0.6
 LATEX_FLAGS	:= -interaction=nonstopmode 	\
 		   -file-line-error		\
 		   --synctex=15			\

Modified: trunk/Master/texmf-dist/source/latex/wargame/chit/battle.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/chit/battle.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/chit/battle.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -41,7 +41,7 @@
       minimum size=8mm,
       draw=black,
       fill=#2,
-      every odds marker/.try] at (.2,-.2) {#1};
+      every odds marker/.try] at (.16,-.16) {#1};
     }
   },
   odds marker/.style args={#1,#2}{

Modified: trunk/Master/texmf-dist/source/latex/wargame/chit/misc.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/chit/misc.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/chit/misc.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -4,7 +4,59 @@
 % \fi
 % \subsubsection{Some utilities}
 %
+% This style allows us to add a fading drop-shadow to chits.
 %
+%    \begin{macrocode}
+\usetikzlibrary{shadows.blur}
+\newif\ifwg at chit@drop\wg at chit@dropfalse
+\tikzset{
+  chit has drop/.is if=wg at chit@drop,
+  chit has drop/.default=true,
+  chit has drop/.initial=false,
+  /tikz/render blur shadow/.add code={%
+    \chit at dbg{2}{Number of blur steps: \pgfbs at steps}%
+    \ifnum\pgfbs at steps=0\else
+    \chit at dbg{2}{Making shadow blur}%
+    }{\fi}}
+\tikzset{%
+  chit drop/.code={%
+    %% \message{^^J Args `#1'}%
+    \pgfkeysalso{%
+      chit has drop=true,
+      /tikz/blur shadow={shadow blur steps=5,
+        shadow opacity=25,
+        shadow xshift=.05cm,
+        shadow yshift=-.05cm,
+        shadow blur radius=.05cm,
+        #1}}%
+    \ifnum\pgfbs at steps=0%
+      \gdef\wg at drop@margin{0pt}%
+    \else%
+      \ifwg at chit@drop%
+        \pgfmathparse{
+          \pgfbs at radius+
+          veclen(
+          \pgfkeysvalueof{/tikz/shadow xshift},
+          \pgfkeysvalueof{/tikz/shadow yshift})}
+        \xdef\wg at drop@margin{\pgfmathresult pt}%
+      \else%
+        \gdef\wg at drop@margin{0pt}%
+      \fi
+      %% \message{^^J Drop margin is `\wg at drop@margin'
+      %%   `\pgfbs at radius'
+      %%   `\pgfkeysvalueof{/tikz/shadow xshift}',
+      %%   `\pgfkeysvalueof{/tikz/shadow yshift}'}%
+    \fi%
+  },%
+  chit drop/.default=,%
+  no chit drop/.code={%
+    \pgfkeysalso{
+      /tikz/blur shadow={shadow blur steps=0}}
+    \gdef\wg at drop@margin{0pt}%
+  }  
+}%
+%    \end{macrocode}
+%
 % Game turn marker
 %
 %    \begin{macrocode}

Modified: trunk/Master/texmf-dist/source/latex/wargame/chit/modifiers.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/chit/modifiers.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/chit/modifiers.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -14,7 +14,7 @@
     code={%
       \path[fill=white,opacity=#1,pic actions] (-.6,-.6) rectangle(.6,.6);}},
   pics/chit/eliminate/.style={
-    code={%
+    code={%      
       \path[fill=red,opacity=#1,pic actions] (-.6,-.6) rectangle(.6,.6);}},
   pics/chit/shade/.default=0.5,
   pics/chit/eliminate/.default=0.25,

Modified: trunk/Master/texmf-dist/source/latex/wargame/chit/oob.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/chit/oob.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/chit/oob.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -37,8 +37,9 @@
 \def\chit at oob@rowupdate(#1,#2)#3#4{%
   \chit at dbg{2}{ Row update  c=`#1',r=`#2',s=`#3',e=`#4'}
   %\pgfmathparse{ifthenelse(#1>0,#2-#3,#2)}%
-  \pgfmathparse{#2-#3)}%
+  \pgfmathparse{#2-#3-#4}%
   \xdef#2{\pgfmathresult}%
+  \xdef#1{0}%\pgfmathresult}%
   %\xdef#1{0}
   \chit at dbg{2}{ \space\space-> update `\string#2'=#2}
 }
@@ -71,6 +72,7 @@
 %    \begin{macrocode}
 \newif\ifwg at oob@inv\wg at oob@invfalse
 \def\chit at oob@spacer{hspace}
+\def\chit at oob@vspacer{vspace}
 \def\wg at star@oob{\wg at oob@invtrue\wg at oob}
 \def\wg at nostar@oob{\wg at oob@invfalse\wg at oob}
 \def\oob{%
@@ -91,6 +93,8 @@
 %    \begin{macrocode}
 \def\wg at oob#1#2#3#4{
   \def\r{0}
+  \pgfmathparse{#3*(#2-1)}%
+  \edef\a{\pgfmathresult}
   \chit at dbg{2}{OOB: `#1'}
   \foreach[count=\ti from 0] \t/\y in #1{
     \xdef\o{\r}
@@ -109,20 +113,28 @@
           \ifx\m\@empty\def\m{1}\fi
           \ifx\u\m\def\m{1}\fi
           \foreach \n in {1,...,\m}{%
-            \chit at dbg{2}{OOB Chit is `\u'}%
+            \chit at dbg{2}{OOB Chit is `\u' `\chit at oob@spacer'}%
             \ifx\u\chit at oob@spacer%
               \chit at dbg{3}{Chit `\u' is spacer `\chit at oob@spacer'}
               \pgfmathparse{\c+#4}%
               \xdef\c{\pgfmathresult}%
-            \else
-              \ifnum\chitdbglvl>2%
-                \node[minimum width=#3cm,minimum height=#3cm,
-                      draw,transform shape] at (\c,\r) {};
+            \else%
+              \ifx\u\chit at oob@vspacer%
+                \chit at dbg{3}{Chit `\u' is vspacer `\chit at oob@vspacer'}
+                \pgfmathparse{ifthenelse(abs(\c)<0.0001,0,#3)}
+                \xdef\ll{\pgfmathresult}
+                \chit at dbg{2}{\string\ll=`\ll'}
+                \chit at oob@rowupdate(\c,\r){\ll}{#4}
+              \else
+                \ifnum\chitdbglvl>2%
+                  \node[minimum width=#3cm,minimum height=#3cm,
+                        draw,transform shape] at (\c,\r) {};
+                \fi
+                \ifx\u\chit at blank\else%
+                  \chit[\u=\ti,zone oob point={\u}{\c}{\r}](\c,\r);%
+                \fi%
+                \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
               \fi
-              \ifx\u\chit at blank\else%
-                \chit[\u=\ti,zone oob point={\u}{\c}{\r}](\c,\r);%
-              \fi%
-              \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
             \fi
           }
         \fi
@@ -153,7 +165,7 @@
         %  \chit at dbg{2}{ \space Breaking loop \rr\space > \y}%
         %  \breakforeach%
         %\else%
-          \chit at oob@rowupdate(\c,\r){#3}{#4}
+          \chit at oob@rowupdate(\c,\r){#3}{0}% Extra spacing? 
         %\fi
       }
     \fi
@@ -166,6 +178,169 @@
   \@ifnextchar;{\@gobble}{}}
 %    \end{macrocode}
 %
+% Horizontal flow OOB
+% 
+%    \begin{macrocode}
+\def\wg at star@hoob{\wg at oob@invtrue\wg at hoob}
+\def\wg at nostar@hoob{\wg at oob@invfalse\wg at hoob}
+\def\hoob{%
+  \@ifstar{\wg at star@hoob%
+  }{\wg at nostar@hoob%
+  }%
+}
+%    \end{macrocode}
+% 
+% The inner macro of \cs{hoob}.  The arguments are
+% \begin{enumerate}
+% \item The list of lists of chits styles
+% \item The maximum number of columns
+% \item The width of each cell
+% \item Additional row spacing between turns
+% \end{enumerate}
+% 
+%    \begin{macrocode}
+\def\wg at hoob#1#2#3#4{
+  \def\r{0}
+  \def\c{0}
+  \pgfmathparse{#3*(#2-1)}%
+  \edef\a{\pgfmathresult}
+  \chit at dbg{2}{OOB: `#1'}
+  \foreach[count=\ti from 0] \t/\y in #1{
+    \xdef\o{\r}
+    % \def\c{0}
+    \ifx\t\y\def\y{0}\fi
+    \chit at dbg{2}{Turn \ti\space(\r,\t,y=\y):'}
+    \ifx\t\empty\else
+      % Count how many are left for this turn
+      \chit at dbg{2}{At start of turn \t\space\string\c=\c}
+      \def\l{\c}%
+      \let\ig\empty
+      \foreach \u/\m in \t{
+        \ifx\ig\empty
+          \ifx\u\empty\else
+            \ifx\u\m\def\m{1}\fi
+            \ifx\u\chit at oob@spacer%
+              \pgfmathparse{\l+\m*#4}\xdef\l{\pgfmathresult}
+              \chit at dbg{2}{Got \m\space hspace (#4) -> \l}
+            \else
+              \ifx\u\chit at oob@vspace%
+                \xdef\ig{1}
+                \chit at dbg{2}{Got vspace -> \l (\ig)}
+              \else
+                \pgfmathparse{\l+\m*#3}
+                \xdef\l{\pgfmathresult}
+                \chit at dbg{2}{Got \m\space units -> \l}
+              \fi
+            \fi
+          \fi
+        \fi}
+      % Check if there's enough room
+      \chit at dbg{2}{To fill the rest of turn needs `\l' compared to
+        `\a' (#3*(#2-1))}
+      \pgfmathparse{ifthenelse(abs(\l)>=#3*(#2-1),0,1}%
+      \xdef\l{\pgfmathresult}%
+      \chit at dbg{2}{Break or not `\l'}
+      \ifnum\l=0\chit at oob@turnupdate(\c,\r){#3}{#4}\fi
+    \fi    
+    \ifwg at oob@inv%
+      \pic[transform shape] at (\c+.5*#3,\r) {chit/oob turn=\ti};% was dx=0.5
+    \else
+      \pic[transform shape] at (\c+-.5*#3,\r) {chit/oob turn=\ti};% was dx=-0.5
+    \fi%
+    %\chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
+    \ifx\t\empty\else%
+      \def\lv{0}
+      \foreach \u/\m in \t{
+        %% \chit at dbg{2}{ `\u'=`\m'}
+        \ifx\u\empty\else
+          \ifx\m\@empty\def\m{1}\fi
+          \ifx\u\m\def\m{1}\fi
+          \foreach \n in {1,...,\m}{%
+            \chit at dbg{2}{OOB Chit is `\u' `\chit at oob@spacer'}%
+            \ifx\u\chit at oob@spacer%
+              \chit at dbg{3}{Chit `\u' is spacer `\chit at oob@spacer'}
+              \pgfmathparse{\c+#4}%
+              \xdef\c{\pgfmathresult}%
+            \else%
+              \ifx\u\chit at oob@vspacer%
+                \chit at dbg{3}{Chit `\u' is vspacer `\chit at oob@vspacer'}
+                \pgfmathparse{ifthenelse(abs(\c)<0.0001,0,#3)}
+                \xdef\ll{\pgfmathresult}
+                \chit at dbg{2}{\string\ll=`\ll'}
+                \chit at oob@rowupdate(\c,\r){\ll}{#4}
+                \xdef\lv{1}
+              \else
+                \ifnum\chitdbglvl>2%
+                  \node[minimum width=#3cm,minimum height=#3cm,
+                        draw,transform shape] at (\c,\r) {};
+                \fi
+                \ifx\u\chit at blank\else%
+                  \chit[\u=\ti,zone oob point={\u}{\c}{\r}](\c,\r);%
+                \fi%
+                \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
+              \fi
+            \fi
+          }
+        \fi
+      }
+    \fi
+    \chit at dbg{2}{ End of chits in turn
+      \ti\space(c=`\c',r=`\r',o='\o',y='\y')}
+    % --- Not relevant, I think 
+    % IF no units where given, then we force \c to be non-zero so that
+    % \chit at oob@turnupdate increments the row 
+    % \ifx\t\@empty
+    %   \def\c{#3}
+    %   \chit at dbg{2}{ Turn is empty, set c=`\c'}
+    % \fi
+    % ---
+    %\ifnum\y<0% No explicit number of rows given
+    %  \def\c{#3}
+    %  \chit at dbg{2}{ No explicit number of rows given, set c=`\c'}
+    %\fi
+    % In case the user gave and explicit number of rows, add the rows
+    % that are missing.  \y is initially set to the number of
+    % requested rows, and then decremented every time we go down one
+    % row.  So if the number of rows we did so far is N, and the
+    % requested number of rows is M, then the loop below adds M-N
+    % rows.
+    \ifnum\y>0%
+      \chit at dbg{2}{ Looping rows from 2 to \y, break when row > \y}%
+      \foreach \rr  in {2,...,\y}{
+        %\ifnum\rr>\y% A little funny, but \y can be negative!
+        %  \chit at dbg{2}{ \space Breaking loop \rr\space > \y}%
+        %  \breakforeach%
+        %\else%
+          \chit at oob@rowupdate(\c,\r){#3}{0}% Extra spacing?
+        %\fi
+      }
+    \fi
+    % --- Not relevant I think   
+    % This will zero \c.  However, if on entry |\c|>0, then we also
+    % increment the row 
+    % \chit at oob@turnupdate(\c,\r){#3}{#4}
+    % ---
+    % Horizontal spacer
+    
+    %\pgfmathparse{ifthenelse(abs(\c)>=\a,1,0)}\xdef\l{\pgfmathresult}
+    \pgfmathparse{\c+1.5*#4}%
+    \xdef\c{\pgfmathresult}%
+    \ifnum\lv=1%
+      \pgfmathparse{\r-#4}
+      \chit at oob@rowupdate(\c,\r){0}{#4}
+    \else
+      \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
+      \ifnum\y<0
+        \chit at oob@turnupdate(\c,\r){#3}{#4}
+      \else
+      \fi
+    \fi
+    % \xdef\y{0}
+    \chit at dbg{2}{End of turn \ti\space(c=`\c',r=`\r',o='\o',y='\y')} 
+  }
+  \chit at dbg{3}{End of OOB (c=`\c',r=`\r',y=`\y')}
+  \@ifnextchar;{\@gobble}{}} 
+%    \end{macrocode}
 % \iffalse
 % </chit>
 % --------------------------------------------------------------------

Modified: trunk/Master/texmf-dist/source/latex/wargame/chit/shape.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/chit/shape.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/chit/shape.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -92,7 +92,7 @@
   chit/symbol/.style={scale=.4,transform shape},
   chit/parts/.style={shape=rectangle,transform shape},
   chit/factors/.style={chit/parts,anchor=south},
-  chit/left/.style={chit/parts,anchor=south,rotate=90},
+  chit/left/.style={chit/parts,anchor=base,rotate=90},%Anchor was south
   chit/right/.style={chit/parts,anchor=north,rotate=90},
   chit/upper left/.style={chit/parts,anchor=north west},
   chit/upper right/.style={chit/parts,anchor=north east},
@@ -141,7 +141,8 @@
 %    \begin{macrocode}
 \tikzset{%
   chit/.code={%
-    \pgfkeys{/tikz/transform shape,/tikz/shape=chit}
+    \chit at dbg{2}{chit arguments are `#1'}%
+    \pgfkeys{/tikz/transform shape,/tikz/shape=chit}%
     \pgfkeys{/chit/.cd,#1}}}
 %    \end{macrocode}
 % \end{TikzKey}
@@ -657,8 +658,12 @@
 %
 % The work horse.  This simply makes a \cs{node} with the shape
 % \texttt{chit}.   Note, we allow for a trailing semi-colon
-% (\texttt{;}) to have a similar feel to other \TikZ{} macros. 
+% (\texttt{;}) to have a similar feel to other \TikZ{} macros.
 %
+% The macro will execute the style \texttt{/tikz/every chit} if
+% defined.  Note that this will be executed \emph{before} the usual
+% \texttt{every chip node} style. 
+%
 %    \begin{macrocode}
 \def\chit@@@#1#2(#3){%
   \chit at dbg{5}{Chit final:
@@ -667,7 +672,7 @@
     ^^J  Name:       `#3'}
   \let\name\pgfutil at empty%
   \chit at dbg{1}{=== Before chit node}%
-  \node[chit={every chit/.try,id=#3,#1}] (tmp) at (#2) {};
+  \node[chit={/tikz/every chit/.try,id=#3,#1}] (tmp) at (#2) {};
   \chit at dbg{2}{=== After chit node}%
   \ifx|#3|\relax%
   \else%

Modified: trunk/Master/texmf-dist/source/latex/wargame/chit/table.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/chit/table.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/chit/table.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -7,7 +7,9 @@
 %    \begin{macrocode}
 \tikzset{
   chit/cell background/.style={fill=black},
+  %chit/cell background flipped/.style={fill=black},
   blank chit/.style={/chit/frame={draw=none,fill=none}},
+  chit/grid lines/.style={dashed},
 }
 %    \end{macrocode}
 % 
@@ -20,6 +22,10 @@
 \def\chit at cellbg(#1,#2)#3{%
   \draw[chit/cell background](#1-#3/2,#2-#3/2) rectangle++(#3,#3);
 }
+\def\chit at celldblbg(#1,#2)#3{%
+  \draw[chit/cell background,chit/cell background flipped/.try]%
+  (#1-#3/2,#2-#3/2) rectangle++(#3,#3);
+}
 %    \end{macrocode}
 % 
 % \begin{Macro}{\ifchits at reset}
@@ -83,7 +89,28 @@
   \@ifnextchar;{\@gobble}{}}
 %    \end{macrocode}
 % \end{Macro}
+% \begin{Macro}{\chitgrid}
+% \begin{enumerate}
+% \item columns
+% \item rows
+% \item cell-size
+% \end{enumerate}
 %
+%    \begin{macrocode}
+\def\chitgrid#1#2#3{%
+  \pgfmathparse{#3/2}\edef\rmin{\pgfmathresult}%
+  \pgfmathparse{#2*#3-#3/2}\edef\rmax{\pgfmathresult}%
+  %\draw[red](-#3/2,\rmin)rectangle(#3*#1-#3/2,-\rmax);
+  \foreach \cc in {0,...,#1}{
+    \draw[chit/grid lines] (\cc*#3-#3/2,3*#3/4)--(\cc*#3-#3/2,-\rmax-#3/4);}
+  %\chit at dbg{0}{Drawing horizontal lines from `\rmin, `-\rmin', ..., `-\rmax'}
+  \foreach \rr in {\rmin,-\rmin,...,-\rmax}{
+    %\chit at dbg{0}{Horizontal line at `\rr'}
+    \draw[chit/grid lines] (-3*#3/4,\rr)--(#1*#3-#3/4,\rr);}  
+}
+%    \end{macrocode}
+% \end{Macro}
+%
 % \begin{Macro}{\doublechits,
 %   \@doublechits,
 %   \chit at dbl@cellupdate,
@@ -154,7 +181,7 @@
               \chit at cellbg(\c,\r){#3}
               \chit[\u=\ti](\c,\r)
               \chit at dbl@flip(\c,\r){#3}
-              \chit at cellbg(\mc,\r){#3}
+              \chit at celldblbg(\mc,\r){#3}
               \chit[\u\space flipped=\ti,zone turn=\t,zone mult=\n](\mc,\r)
               \chit at dbl@cellupdate(\c,\r){#2}{#3}
             \fi
@@ -166,10 +193,39 @@
   \draw[dashed](0,-3*#3/4)--(0,\r-#3/4);%
   \draw[dashed,<-] (#3/5,-2*#3/3)--(#3/2,-2*#3/3) node[transform shape,anchor=west]{Back};%
   \draw[dashed,<-] (-#3/5,-2*#3/3)--(-#3/2,-2*#3/3) node[transform shape,anchor=east]{Front};%
+  % \foreach \cc in {0,...,#2}{
+  %   \draw[dashed] (\cc*#3,-3*#3/4)--(\cc*#3,\r-#3/4);
+  %   \draw[dashed] (-\cc*#3,-3*#3/4)--(-\cc*#3,\r-#3/4);}
+  % \pgfmathparse{#3/2}\edef\rmin{\pgfmathresult}%
+  % \chit at dbg{0}{Drawing horizontal lines from `-\rmin, `\rmin', ..., `\r'}
+  % \foreach \rr in {-\rmin,\rmin,...,\r}{
+  %   \chit at dbg{0}{Horizontal line at `\rr'}
+  %   \draw[dashed] (-#2*#3-#3/4,\rr)--(#2*#3+#3/4,\rr);}
   \@ifnextchar;{\@gobble}{}}  
 %    \end{macrocode}
 % \end{Macro}
 %    
+% \begin{Macro}{\doublechitgrid}
+% \begin{enumerate}
+% \item columns
+% \item rows
+% \item cell-size
+% \end{enumerate}
+%
+%    \begin{macrocode}
+\def\doublechitgrid#1#2#3{%
+  \pgfmathparse{#3/2}\edef\rmin{\pgfmathresult}%
+  \pgfmathparse{#2*#3-#3/2}\edef\rmax{\pgfmathresult}%
+  \foreach \cc in {0,...,#1}{
+    \draw[chit/grid lines] (\cc*#3,-3*#3/4)--(\cc*#3,\rmax+#3/4);
+    \draw[chit/grid lines] (-\cc*#3,-3*#3/4)--(-\cc*#3,\rmax+#3/4);}
+  %\chit at dbg{0}{Drawing horizontal lines from `-\rmin, `\rmin', ..., `\rmax'}
+  \foreach \rr in {-\rmin,\rmin,...,\rmax}{
+    %\chit at dbg{0}{Horizontal line at `\rr'}
+    \draw[chit/grid lines] (-#1*#3-#3/4,\rr)--(#1*#3+#3/4,\rr);}  
+}
+%    \end{macrocode}
+% \end{Macro}
 % \iffalse
 % </chit>
 % --------------------------------------------------------------------

Modified: trunk/Master/texmf-dist/source/latex/wargame/hex/labels.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/hex/labels.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/hex/labels.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -132,7 +132,7 @@
 %
 %    \begin{macrocode}
 \def\hex at do@label{%
-  \hex at dbg{1}{Hex label: `\meaning\hex at label'}%
+  \hex at dbg{3}{Hex label: `\meaning\hex at label'}%
   \edef\hex at l@tmp{[%
     /hex/label/.cd,%
     rotate=0,%

Modified: trunk/Master/texmf-dist/source/latex/wargame/hex/ridges.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/hex/ridges.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/hex/ridges.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -157,11 +157,12 @@
         \xdef\hex at r@p{\hex at r@p ($(300:\hex at r@r)+(-120:\hex at r@t/2)$)}
       \fi
       \ifhex at r@ne
-        \xdef\hex at r@p{\hex at r@p --cycle}
+      %\xdef\hex at r@p{\hex at r@p --cycle}
+      \xdef\hex at r@p{\hex at r@p -- (0:\hex at r@r)}
       \else
         \xdef\hex at r@p{\hex at r@p --(.5:\hex at r@r)}
       \fi
-      \hex at dbg{4}{Ridge along se: `\hex at r@p'}
+      \hex at dbg{4}{Ridge along south east: `\hex at r@p'}
     \fi
     \hex at dbg{3}{ Ridges path: \hex at r@p}
     % \draw[red] \hex at r@p;

Modified: trunk/Master/texmf-dist/source/latex/wargame/hex/terrain.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/hex/terrain.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/hex/terrain.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -37,6 +37,7 @@
 %   select the common terrains.
 % 
 %    \begin{macrocode}
+\newif\if at hex@t at rot\@hex at t@rotfalse%
 \tikzset{%
   /hex/terrain/.search also={/tikz},%
   /hex/terrain/.cd,%
@@ -44,6 +45,7 @@
   image/.store in=\hex at t@image,%
   code/.store in=\hex at t@code,%
   clip/.store in=\hex at t@clip,%
+  random rotation/.is if=@hex at t@rot,
   pic/.default=,
   image/.default=,
   code/.default=,
@@ -137,9 +139,24 @@
     \@ifundefined{hex at t@code}{\let\hex at t@code\empty}{}
 %    \end{macrocode}
 %
+% Possible make rotation. We define a scope and rotate within that.
+% 
 %    \begin{macrocode}
-    \ifx\hex at t@code\empty\else\hex at t@code\fi%
+    \def\hex at t@angle{0}%
+    \if at hex@t at rot%
+      \pgfmathrandominteger{\hex at t@angle}{0}{5}
+      \pgfmathparse{int(60*\hex at t@angle)}\edef\hex at t@angle{\pgfmathresult}%
+    \fi%
+    \hex at dbg{5}{Will rotate terrain by `\hex at t@angle'}%
 %    \end{macrocode}
+%
+% If we have specified code for the terrain, then execute that.
+% 
+%    \begin{macrocode}
+    \scope[rotate=\hex at t@angle]%  
+      \ifx\hex at t@code\empty\else\hex at t@code\fi%
+    \endscope% End rotate code
+%    \end{macrocode}
 % First we check if we have not got terrain images, but terrain
 % pictures.  If we have that, we process these in turn.  Note, the
 % user can give options to each terrain picture by preceding the
@@ -154,7 +171,9 @@
         \hex at dbg{5}{Terrain pictures}%
         \pgfpointorigin\wg at tmpa=\pgf at x\wg at tmpb=\pgf at y%
         \foreach \i in \hex at t@pic{%
-          \wg at pic@all{\i}{}{\the\wg at tmpa,\the\wg at tmpb}{}}%
+          \wg at pic@all{\i}{}{\the\wg at tmpa,\the\wg at tmpb}{%
+            rotate=\hex at t@angle,
+            transform shape}}%
       \fi% We have pictures.
 %    \end{macrocode}
 %    
@@ -172,6 +191,7 @@
           \expandafter\wg at node{%
             \includegraphics[width=2cm]{\i}}\@endwg at node %
           {}{\wg at tmpa,\wg at tmpb}{%
+            rotate=\hex at t@angle,%
             shape=rectangle,%
             anchor=center,%
             transform shape,%

Modified: trunk/Master/texmf-dist/source/latex/wargame/tests/chits.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/tests/chits.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/tests/chits.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -6,7 +6,11 @@
 %   Some chits for testing stuff
 %
 %    \begin{macrocode}
-\def\firstchit{
+\makeatletter
+\def\firstchit{%
+  \@ifnextchar[{\@firstchit}{\@firstchit[]}%]
+}%
+\def\@firstchit[#1]{
   % \chitdbglvl=5
   % \natoappdbglvl=5
   \chit[symbol={
@@ -17,13 +21,14 @@
     left={text=A},
     frame={fill=yellow!50!black},
     ultra thick,
-    command=land},
+    command=land,
+    },
   left={chit/identifier={1}},
   right={chit/identifier={2}},
   factors={chit/2 factors={4,8}},
   frame={draw=black},
-  color=white,text=yellow,fill={blue}]}
-\makeatletter
+  color=white,text=yellow,fill={blue},
+  #1]}
 \def\secondchit{%
   \@ifnextchar({\second at chit}{\second at chit(0,0)}%)
 }
@@ -34,6 +39,7 @@
       command=air](0,-.15)},
     left={[{red,font=\noexpand\sffamily}]chit/identifier={Fighter}},
     right={chit/identifier={USAF}},
+    fill=white,
     factors={chit/1 factor=3}}] at (#1){};}
 \def\thirdchit{
   \chit[symbol={[echelon=battalion,

Modified: trunk/Master/texmf-dist/source/latex/wargame/tests/test.tex
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/tests/test.tex	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/tests/test.tex	2024-02-03 21:13:58 UTC (rev 69692)
@@ -2,6 +2,7 @@
 
 \usepackage{wargame}
 \usetikzlibrary{decorations.markings}
+\usetikzlibrary{shadows.blur}
 \title{A test of \textsf{wargame}}
 \author{}
 \date{}
@@ -28,7 +29,7 @@
 
 \section{Some chits}
       
-\begin{tikzpicture}
+\begin{tikzpicture}[every chit node/.style={blur shadow={shadow blur steps=5}}]
   \fill[red!50] (1,-1) rectangle (3,1);
   \firstchit(0,0)%
   %\secondchit(2,0)%
@@ -45,18 +46,20 @@
 \end{tikzpicture}
  
 \section{Chits on a board}
-\begin{tikzpicture}
+\begin{tikzpicture}[every chit/.style={chit drop}]
+  \chitdbglvl=5
   \begin{scope}[every hex/.style={/hex/label=auto},transform shape]
     \boardframe[0.5](c=0,r=0)(c=3,r=3)
     \boardclip(c=0,r=0)(c=3,r=3){fill=white}
     \testmap{}
-    \firstchit(hex cs:c=2,r=1)
-    \secondchit(hex cs:c=3,r=0)
+    \firstchit[no chit drop](hex cs:c=2,r=2)
+    %\secondchit(hex cs:c=3,r=0)
     \thirdchit(hex cs:c=0,r=3)
  
     \stackchits(hex cs:c=0,r=1)(.3,.3){
       \noexpand\firstchit,
-      \noexpand\secondchit/\noexpand\chit[{full=chit/eliminate}]}
+      \noexpand\secondchit%/\noexpand\eliminatechit
+    }
     
     \begin{scope}[m/.style={hex/long move=green}]
       \path[m] (hex cs:c=3,r=0) -- (hex cs:c=3,r=1)--(hex cs:c=3,r=2);

Modified: trunk/Master/texmf-dist/source/latex/wargame/util/export.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/util/export.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/util/export.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -72,6 +72,7 @@
 %
 % Preceed all images (\textsf{tikzpicture}) with this command
 %
+%    
 % First argument is the name of the image.  This can be anything.
 % Note that for counters, if the name ends in \texttt{flipped} then it
 % is considered the backside of a counter.
@@ -140,13 +141,32 @@
 % 
 % Make separate images for each counter (single sided).
 % 
+% First optional argument is the group to put the chits into.
+% Second optional argument is options to give to each Tikz picture
+% environment.  Third, mandatory, argument is the list of chit
+% identifiers to render. 
+% 
 %    \begin{macrocode}
-\newcommand\chitimages[2][]{%
+\def\wg at add@drop at margin{%
+  \@ifundefined{wg at drop@margin}{}{
+    \dimen0=\wg at drop@margin
+    % \ifwg at chit@drop
+    \ifdim\dimen0>0pt%
+      \path ($(current bounding box.north east)+(45:\wg at drop@margin)$)
+      -- ($(current bounding box.south west)+(225:\wg at drop@margin)$);
+    \fi}}
+\def\chitimages{%
+  \@ifnextchar[{\@chitimages}{\chitimages[]}%]
+}%
+\def\@chitimages[#1]{%
+  \@ifnextchar[{\@@chitimages[#1]}{\@@chitimages[#1][]}%]
+}%
+\def\@@chitimages[#1][#2]#3{%
   \begingroup%
   \let\chit at report\do at chit@report%
   \let\natoapp at report\do at natoapp@report%
-  \chit at dbg{2}{chits to make images of `#2'}%
-  \foreach[count=\ti from 0] \t/\x in #2{%
+  \chit at dbg{2}{chits to make images of `#3'}%
+  \foreach[count=\ti from 0] \t/\x in #3{%
     \chit at dbg{2}{^^JRow: `\t' (`\x')}
     \ifx\t\empty\else% Ignore empty rows
       \chit at dbg{5}{^^JSubcategory: `\x' (default `#1')}
@@ -156,14 +176,17 @@
         \ifx\u\empty\else% Ignore empty cells 
           \ifx\u\chit at blank\else%
             \chit at dbg{2}{Next chit `\u' with possible multiplicity `\m'}%
-            \ifx\m\@empty\def\m{1}\fi% If not multiplicity defined 
+            \ifx\m\@empty\def\m{1}\fi% If no multiplicity defined 
             \ifx\u\m\def\m{1}\fi% If the same as unit
             \chit at dbg{2}{Next chit `\u' multiplicity `\m'}%
             %% We only make one copy of the chit, since we can duplicate
             %% it in VASSAL
             \info*{\u}{counter}{\x}
-            \begin{tikzpicture}
+            \nopagecolor%
+            \gdef\wg at drop@margin{0pt}%
+            \begin{tikzpicture}[chit has drop=false,#2]
               \chit[\u=\ti]%
+              \wg at add@drop at margin%
             \end{tikzpicture}
             \end at info%
             %% \foreach \n in {1,...,\m}{% Make a number of copies 
@@ -189,14 +212,25 @@
 % 
 % Make separate images for each counter (double sided).  The back-side
 % counters must be defined by append `\texttt{ flipped}' the front
-% face name
+% face name.
+%
+% First optional argument is the group to put the chits into.
+% Second optional argument is options to give to each Tikz picture
+% environment.  Third, mandatory, argument is the list of chit
+% identifiers to render. 
 % 
 %    \begin{macrocode}
-\newcommand\doublechitimages[2][]{%
+\def\doublechitimages{%
+  \@ifnextchar[{\@doublechitimages}{\doublechitimages[]}%]
+}%
+\def\@doublechitimages[#1]{%
+  \@ifnextchar[{\@@doublechitimages[#1]}{\@@doublechitimages[#1][]}%]
+}%
+\def\@@doublechitimages[#1][#2]#3{%
   \begingroup%
   \let\chit at report\do at chit@report%
   \let\natoapp at report\do at natoapp@report%
-  \foreach[count=\ti from 0] \t/\x in #2{%
+  \foreach[count=\ti from 0] \t/\x in #3{%
     \ifx\t\empty\else% Ignore empty rows
       \chit at dbg{5}{^^JSubcategory: `\x' (default `#1')}
       % Take sub-category or default
@@ -213,13 +247,18 @@
             %% We only make one copy of the chit, since we can duplicate
             %% it in VASSAL
             \info*{\u}{counter}{\x}%
-            \begin{tikzpicture}%
+            \nopagecolor%
+            \gdef\wg at drop@margin{0pt}%
+            \begin{tikzpicture}[chit has drop=false,#2]%
               \chit[\u=\ti]%
+              \wg at add@drop at margin%
             \end{tikzpicture}%
             \end at info%
             \info*{\s}{counter}{\x}%
-            \begin{tikzpicture}%
+            \nopagecolor%
+            \begin{tikzpicture}[chit has drop=false,#2]%
               \chit[\s=\ti]%
+              \wg at add@drop at margin%
             \end{tikzpicture}%
             \end at info%
             %% \foreach \n in {1,...,\m}{% Make a number of copies 
@@ -414,7 +453,7 @@
       \def\hex at col{0}%
       \def\hex at row{0}%
       \node[hex,inner sep=0,outer sep=0]{%
-        \message{^^JHex label: `\meaning\hex at label'}%
+        %\message{^^JHex label: `\meaning\hex at label'}%
         \global\let\mk at label\hex at label}}}%
 %    \end{macrocode}
 %
@@ -427,7 +466,7 @@
   \edef\mk at i{\mk at i\space}
   %% Everything is made into centimeters
   \mk at w{ \mk at i "units": "cm",}
-  \hex at dbg{0}{Label: `\meaning\mk at label'}
+  \hex at dbg{3}{Label: `\meaning\mk at label'}
   \@ifundefined{mk at label}{}{\mk at w{ \mk at i "labels": "\mk at label",}}
   %% Write out coordinate options as "coords" object
   \mk at w{ \mk at i"coords": \@lbchar}%
@@ -577,7 +616,7 @@
 % optional is the group to add the markers to.
 % 
 %    \begin{macrocode}
-\def\wg at gennumberm@rkers#1#2#3{
+\def\wg at gennumberm@rkers#1#2#3#4{
   \message{^^JNumbered markers: Type=`#1' Max=`#2' Category=`#3'}
   \def\markers{}
   \def\keys{}
@@ -585,15 +624,27 @@
     \xdef\keys{/tikz/#1 \i/.style={/tikz/#1=\i},\keys}
     \xdef\markers{\markers,#1 \i}}
   {%
-    \nopagecolor\pgfkeysalsofrom{\keys}\chitimages[#3]{\markers}}}%
+    \nopagecolor\pgfkeysalsofrom{\keys}\chitimages[#3][#4]{\markers}}}%
 \tikzset{
   wg hidden unit/.pic={},
   wg hidden unit/.style={
     chit={
+      no chit drop,
       frame={draw=none,fill=none},
       full=wg hidden unit}}}
-\DeclareRobustCommand\battlemarkers[2][BattleMarkers]{%
-  \wg at gennumberm@rkers{battle marker}{#2}{#1}%
+%
+% First optional argument are extra styles
+% Second is category
+% Third is number of markers
+%
+\def\battlemarkers{%
+  \@ifnextchar[{\@battlemarkers}{\battlemarkers[]}%]
+}%
+\def\@battlemarkers[#1]{%
+  \@ifnextchar[{\@@battlemarkers[#1]}{\battlemarkers[#1][BattleMarkers]}%]
+}%
+\def\@@battlemarkers[#1][#2]#3{%
+  \wg at gennumberm@rkers{battle marker}{#3}{#2}{#1}%
   \message{^^JMake a hidden unit and add to Markers category}
   {%
     \nopagecolor%
@@ -600,9 +651,10 @@
     \chitimages[Markers]{{wg hidden unit}}%
     %
     \info{battle-marker-icon}{icon}{}%
-    \tikz[scale=.7,transform shape]{\pic{battle marker=0};}%
+    \tikz[scale=.7,transform shape,auto icon more/.try]{%
+      \pic{battle marker=0};}%
     \info{clear-battles-icon}{icon}{}
-    \tikz[scale=.4,transform shape]{%
+    \tikz[scale=.4,transform shape,auto icon more/.try]{%
       \pic{eliminate icon};
       \pic[scale=.7,transform shape] at (-.3,0) {battle marker=0};}%
   }%
@@ -613,21 +665,40 @@
 % Optional is the group to add the markers to.
 % 
 %    \begin{macrocode}
-\def\wg at gencolorm@rkers#1#2#3{%
+\def\wg at gencolorm@rkers#1#2#3#4{%
   \def\markers{}
   \def\keys{}
-  \foreach \o/\f in {#2}{%
+  \foreach \o/\f/\n [count=\i] in {#2}{%
+    \ifx\n\f\def\n{\o}\fi%
     \ifx\o\f\def\f{white}\fi%
-    \message{^^JOdds marker `#1 \o' w/fill `\f'}%
-    \xdef\keys{/tikz/#1 \o/.style={/tikz/#1={\o,\f}},\keys}
-    \xdef\markers{\markers,#1 \o}}
-  {\nopagecolor\pgfkeysalsofrom{\keys}\chitimages[#3]{\markers}}}%
-\DeclareRobustCommand\oddsmarkers[2][OddsMarkers]{%
-  \wg at gencolorm@rkers{odds marker}{#2}{#1}%
+    \message{^^JColour no \i marker `#1 \n' w/fill `\f' text `\o'}%
+    \protected at xdef\keys{/tikz/#1 \n/.style={/tikz/#1={\o,\f}},\keys}
+    \xdef\markers{\markers,#1 \n}
+  }%
+  {%
+    \nopagecolor%
+    \pgfkeysalsofrom{\keys}%
+    \chitimages[#3][#4]{\markers}%
+  }%
+}%
+%
+% First optional argument are extra styles
+% Second is category
+% Third is marker list
+%
+\def\oddsmarkers{%
+  \@ifnextchar[{\@oddsmarkers}{\oddsmarkers[]}%]
+}%
+\def\@oddsmarkers[#1]{%
+  \@ifnextchar[{\@@oddsmarkers[#1]}{\oddsmarkers[#1][OddsMarkers]}%]
+}%
+\def\@@oddsmarkers[#1][#2]#3{%
+  \wg at gencolorm@rkers{odds marker}{#3}{#2}{#1}%
   \info{odds-battles-icon}{icon}{}
-  \tikz[scale=.5,transform shape]{\pic{odds marker={?:?,white}}}
+  \tikz[scale=.5,transform shape,auto icon more/.try]{%
+    \pic{odds marker={?:?,white}}}
   \info{resolve-battles-icon}{icon}{}
-  \tikz[scale=.3,transform shape]{%
+  \tikz[scale=.3,transform shape,auto icon more/.try]{%
     \pic{dice};
     \pic[scale=1.2,transform shape] at (-.2,-.2) {battle marker=0};}%
 }
@@ -636,40 +707,51 @@
 % Make results markers.  Mandatory argument is a list of results and
 % fill colours.  Optional is the group to add the markers to.
 % 
+%
+% First optional argument are extra styles
+% Second is category
+% Third is marker list
+%
 %    \begin{macrocode}
-\DeclareRobustCommand\resultmarkers[2][ResultMarkers]{%
-  \wg at gencolorm@rkers{result marker}{#2}{#1}}
+\def\resultmarkers{%
+  \@ifnextchar[{\@resultmarkers}{\resultmarkers[]}%]
+}%
+\def\@resultmarkers[#1]{%
+  \@ifnextchar[{\@@resultmarkers[#1]}{\resultmarkers[#1][ResultMarkers]}%]
+}%
+\def\@@resultmarkers[#1][#2]#3{%
+  \wg at gencolorm@rkers{result marker}{#3}{#2}{#1}}%
 %    \end{macrocode}
 % 
 % Common icons used by many modules
 % 
 %    \begin{macrocode}
-\DeclareRobustCommand\commonicons[2]{%
+\DeclareRobustCommand\commonicons[3][]{%
   \begingroup%
   \nopagecolor%
-  \tikzset{icon/.style={scale=.4,transform shape}}%
+  \tikzset{auto icon/.style={scale=.4,transform shape,#1}}%
   %
   \info{pool-icon}{icon}{}
-  \tikz[icon]{\pic{pool icon};}
+  \tikz[auto icon,auto icon more/.try]{\pic{pool icon};}
   % 
   \info{oob-icon}{icon}{}%
-  \tikz[icon]{\pic{oob icon={#1}{#2}};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{oob icon={#2}{#3}};}%
   %
   \info{flip-icon}{icon}{}%
-  \tikz[icon]{\pic{flip icon};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{flip icon};}%
   %
   \info{eliminate-icon}{icon}{}%
-  \tikz[icon]{\pic{eliminate icon};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{eliminate icon};}%
   %
   \info{restore-icon}{icon}{}%
-  \tikz[icon]{\pic{restore icon};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{restore icon};}%
   %
   \info{dice-icon}{icon}{}%
-  \tikz[icon,scale=.9]{\pic{dice};}%  
+  \tikz[auto icon,scale=.9,auto icon more/.try]{\pic{dice};}%  
   %
   \info{unit-icon}{icon}{}%
-  \tikz[icon,scale=.7]{%
-    \chit[fill=#1,
+  \tikz[auto icon,scale=.7,auto icon more/.try]{%
+    \chit[fill=#2,
       symbol={[
         scale line widths,
         line width=1pt,
@@ -677,7 +759,29 @@
         command=land,
         main=infantry,
         scale=1.3](0,-.15)}]}%
-    \endgroup%
+    %
+    \info{layer-icon}{icon}{}%
+    \begin{tikzpicture}[scale=.25]
+      \foreach \i in {-1,0,1}{
+        \scoped[shift={(0,\i*.15)}]{
+          \draw[black,fill=white] (-.5,0)
+          --(0,.3)--(.5,0)--(0,-.3)--cycle;
+        }
+      }
+    \end{tikzpicture}%
+    %
+    \info{los-icon}{icon}{}
+    \begin{tikzpicture}[scale=.25]
+      \draw[scale line widths,line width=2pt,fill=white](-.5,0)
+      to[out=70,in=110] (.5,0)
+      to[out=-110,in=-70] cycle;
+      \begin{scope}[even odd rule]
+        \clip circle(.2);
+        \fill circle(.2) (125:.18) circle(.1);
+      \end{scope}
+    \end{tikzpicture}%
+  %
+  \endgroup%
 }
 %    \end{macrocode}
 %    
@@ -708,9 +812,11 @@
   \foreach \v/\p in {#5}{%
     \info{#3-\v}{die-roll}{#3}
     \tikz[#1]{
-      \node[shape=#4,transform shape,draw=none,fill=black,opacity=.5]
-      at (.05,-.03){};
-      \node[shape=#4,#2,transform shape]{\p};}}}
+      %\node[shape=#4,transform shape,draw=none,fill=black,opacity=.5]
+      %at (.05,-.03){};
+      \node[shape=#4,#2,transform shape,
+      chit drop
+      ]{\p};\wg at add@drop at margin{}}}}
 %    \end{macrocode}
 % 
 % \subsubsection{Hooks into chits, etc.}
@@ -809,6 +915,28 @@
   \mk at w{ \mk at i\space "end": 0}
   \mk at w{ \mk at i \@rbchar}
 }
+\tikzset{
+  chit drop margin/.store in=\wg at drop@margin,
+  chit drop shadows/.code={
+    \pgfkeysalso{%
+      /tikz/every chit node/.prefix style={chit drop={#1}},
+      /tikz/chit has drop=true}
+  },
+  chit drop shadows/.default=,
+  marker drop shadows/.code={
+    \pgfkeysalso{%
+      /tikz/every battle marker/.prefix style={chit drop={#1}},
+      /tikz/every odds marker/.prefix style={chit drop={#1}},
+      /tikz/every result marker/.prefix style={chit drop={#1}},
+      /tikz/auto icon more/.prefix style={no chit drop}}},
+  marker drop shadows/.default={
+    chit has drop=false,
+    shadow xshift=0.04cm,
+    shadow yshift=-0.04cm,
+    shadow blur radius=0.04cm}
+}
+      
+      
 %    \end{macrocode}
 %
 %

Modified: trunk/Master/texmf-dist/source/latex/wargame/util/tikz.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/util/tikz.dtx	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/util/tikz.dtx	2024-02-03 21:13:58 UTC (rev 69692)
@@ -86,12 +86,17 @@
 %      Save pgf rounded corners macro
 %      \let\wg at pgfsetcornersarced\pgfsetcornersarced
 \def\wg at setcornersarched#1{%
-  \ifx|#1|\else%
-  \edef\pgf at corner@arc{{#1}{#1}}%
-  \pgf at arccornerstrue%
-  \ifdim#1=0pt%
-    \pgf at arccornersfalse%
-    \fi\fi}
+  \def\arg{#1}%
+  \let\isarched\relax%   Cannot set \ifpgf at arccorners directly inside
+                     %   other \if
+  \ifx\arg\@empty\else%
+    \edef\pgf at corner@arc{{#1}{#1}}%
+    \let\isarched\pgf at arccornerstrue%
+    \ifdim#1=0pt%
+      \let\isarched\pgf at arccornersfalse%
+    \fi%
+  \fi%
+  \isarched}
 \newdimen\wg at lw@scaled\wg at lw@scaled=1pt
 \def\wg at getscale{%
   \pgfgettransformentries{%

Modified: trunk/Master/texmf-dist/source/latex/wargame/utils/wgexport.py
===================================================================
--- trunk/Master/texmf-dist/source/latex/wargame/utils/wgexport.py	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/source/latex/wargame/utils/wgexport.py	2024-02-03 21:13:58 UTC (rev 69692)
@@ -208,6 +208,8 @@
 ALT_SHIFT = ALT+SHIFT
 NONE = '\ue004'
 NONE_MOD = 0
+
+# --------------------------------------------------------------------
 def key(let,mod=CTRL):
 
     '''Encode a key sequence
@@ -220,6 +222,8 @@
         Modifier mask
     '''
     return f'{ord(let)},{mod}'
+
+# --------------------------------------------------------------------
 #
 def hexcolor(s):
     if isinstance(s,str):
@@ -241,6 +245,7 @@
 
     return rgb(int(r*256),int(g*256),int(b*256))
     
+# --------------------------------------------------------------------
 # Colour encoding 
 def rgb(r,g,b):
     '''Encode RGB colour
@@ -261,6 +266,7 @@
     '''
     return ','.join([str(r),str(g),str(b)])
 
+# --------------------------------------------------------------------
 def rgba(r,g,b,a):
     '''Encode RGBA colour
 
@@ -282,6 +288,7 @@
     '''
     return ','.join([str(r),str(g),str(b),str(a)])
 
+# --------------------------------------------------------------------
 def dumpTree(node,ind=''):
     '''Dump the tree of nodes
 
@@ -296,6 +303,12 @@
     for c in node.childNodes:
         dumpTree(c,ind+' ')
 
+# --------------------------------------------------------------------
+def registerElement(cls):
+    
+    Element.known_tags[cls.TAG] = cls
+    
+        
 #
 # EOF
 #
@@ -310,6 +323,8 @@
     MAP    = MODULE + 'map.'    
     PICKER = MAP    + 'boardPicker.'
     BOARD  = PICKER + 'board.'
+    known_tags = {}
+    
     def __init__(self,parent,tag,node=None,**kwargs):
         '''Create a new element
 
@@ -429,9 +444,14 @@
             return {c[key] : c for c in cand}
         return cand
     
-    def getParent(self,cls,checkTag=True):
+    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)
@@ -440,6 +460,10 @@
         '''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:
@@ -447,6 +471,11 @@
             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
@@ -518,7 +547,83 @@
         '''  
         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
 #
@@ -526,7 +631,7 @@
 # From globalkey.py
 
 # --------------------------------------------------------------------
-class GlobalKey(Element):
+class GlobalKey(ToolbarElement):
     SELECTED = 'MAP|false|MAP|||||0|0||true|Selected|true|EQUALS'
     def __init__(self,
                  parent,
@@ -533,19 +638,21 @@
                  tag,
                  node                 = None,
                  name                 = '',                
+                 icon                 = '',
+                 tooltip              = '',
                  buttonHotkey         = '',
-                 hotkey               = '',
                  buttonText           = '',
                  canDisable           = False,
+                 propertyGate         = '',
+                 disabledIcon         = '',
+                 # Local
+                 hotkey               = '',
                  deckCount            = '-1',
                  filter               = '',
-                 propertyGate         = '',
                  reportFormat         = '',
                  reportSingle         = False,
                  singleMap            = True,
-                 target               = SELECTED,
-                 tooltip              = '',
-                 icon                 = ''):
+                 target               = SELECTED):
         '''
         Parameters
         ----------
@@ -569,28 +676,26 @@
 
         Default targets are selected units
         '''
-        nme = (name    if len(name)    > 0 else
-               tooltip if len(tooltip) > 0 else '')
-        tip = (tooltip if len(tooltip) > 0 else
-               name    if len(name)    > 0 else '') 
+        
         super(GlobalKey,self).\
             __init__(parent,
                      tag,
                      node                 = node,
-                     name                 = nme,                
+                     name                 = name,
+                     icon                 = icon,
+                     tooltip              = tooltip,
                      buttonHotkey         = buttonHotkey, # This hot key
-                     hotkey               = hotkey,       # Target hot key
                      buttonText           = buttonText,
                      canDisable           = canDisable,
+                     propertyGate         = propertyGate,
+                     disabledIcon         = disabledIcon,
+                     hotkey               = hotkey,       # Target hot key
                      deckCount            = deckCount,
                      filter               = filter,
-                     propertyGate         = propertyGate,
                      reportFormat         = reportFormat,
                      reportSingle         = reportSingle,
                      singleMap            = singleMap,
-                     target               = target,
-                     tooltip              = tip,
-                     icon                 = icon)
+                     target               = target)
 #
 # EOF
 # 
@@ -600,7 +705,7 @@
 # --------------------------------------------------------------------
 class GameElementService:
     def getGame(self):
-        return self.getParent(Game)
+        return self.getParentOfClass(Game)
 
 # --------------------------------------------------------------------
 class GameElement(Element,GameElementService):
@@ -608,23 +713,34 @@
         super(GameElement,self).__init__(game,tag,node=node,**kwargs)
 
 # --------------------------------------------------------------------
-class Notes(GameElement):
+class Notes(ToolbarElement,GameElementService):
     TAG = Element.MODULE+'NotesWindow'
     def __init__(self,elem,node=None,
-                 name             = 'Notes',
-                 buttonText       = '',
-                 hotkey           = key('N',ALT),
-                 icon             = '/images/notes.gif',
-                 tooltip          = 'Show notes window'):
-        super(Notes,self).__init__(elem,self.TAG,node=node,
-                                   name       = name,
-                                   buttonText = buttonText,
-                                   hotkey     = hotkey,
-                                   icon       = icon,
-                                   tooltip    = tooltip)
+                 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'
@@ -673,6 +789,7 @@
     
         
                    
+registerElement(PredefinedSetup)
                   
 # --------------------------------------------------------------------
 class GlobalTranslatableMessages(GameElement):
@@ -690,11 +807,15 @@
         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):
@@ -712,6 +833,8 @@
             Attributes
         '''
         super(Chatter,self).__init__(elem,self.TAG,node=node,**kwargs)
+
+registerElement(Chatter)
         
 # --------------------------------------------------------------------
 class KeyNamer(GameElement):
@@ -730,6 +853,8 @@
         '''
         super(KeyNamer,self).__init__(elem,self.TAG,node=node,**kwargs)
         
+registerElement(KeyNamer)
+        
 
 # --------------------------------------------------------------------
 #    <VASSAL.build.module.GlobalOptions
@@ -883,6 +1008,7 @@
 
         return retd
     
+registerElement(GlobalOptions)
 
 # --------------------------------------------------------------------
 class Option(Element):
@@ -894,6 +1020,8 @@
     def getGlobalOptions(self):
         return self.getParent(GlobalOptions)
 
+registerElement(Option)
+    
 # --------------------------------------------------------------------
 class Preference(Element):
     PREFS = 'VASSAL.preferences.'
@@ -947,6 +1075,9 @@
                                            default = str(default),
                                            desc    = desc,
                                            tab     = tab)
+
+registerElement(IntPreference)
+    
 # --------------------------------------------------------------------
 class FloatPreference(Preference):
     TAG = Preference.PREFS+'DoublePreference'
@@ -964,6 +1095,9 @@
                                              default = str(default),
                                              desc    = desc,
                                              tab     = tab)
+
+registerElement(FloatPreference)
+    
 # --------------------------------------------------------------------
 class BoolPreference(Preference):
     TAG = Preference.PREFS+'BooleanPreference'
@@ -982,6 +1116,9 @@
                                                        else 'false'),
                                             desc    = desc,
                                             tab     = tab)
+
+registerElement(BoolPreference)
+    
 # --------------------------------------------------------------------
 class StrPreference(Preference):
     TAG = Preference.PREFS+'StringPreference'
@@ -999,6 +1136,9 @@
                                            default = default,
                                            desc    = desc,
                                            tab     = tab)
+
+registerElement(StrPreference)
+    
 # --------------------------------------------------------------------
 class TextPreference(Preference):
     TAG = Preference.PREFS+'TextPreference'
@@ -1017,6 +1157,9 @@
                                                        .replace('\n','
')),
                                             desc    = desc,
                                             tab     = tab)
+
+registerElement(TextPreference)
+    
 # --------------------------------------------------------------------
 class EnumPreference(Preference):
     TAG = Preference.PREFS+'EnumPreference'
@@ -1042,39 +1185,42 @@
                                             tab     = tab,
                                             list    = sl)
 
+
+registerElement(EnumPreference)
     
+    
 # --------------------------------------------------------------------
 # CurrentMap == "Board"
-class Inventory(GameElement):
+class Inventory(ToolbarElement,GameElementService):
     TAG = Element.MODULE+'Inventory'
     def __init__(self,doc,node=None,
-                  canDisable          = False,
-                  centerOnPiece       = True,
-                  disabledIcon        = '',
-                  drawPieces          = True,
-                  foldersOnly         = False,
-                  forwardKeystroke    = True,
-                  groupBy             = '',
-                  hotkey              = key('I',ALT),
-                  icon                = '/images/inventory.gif',
-                  include             = '{}',
-                  launchFunction      = 'functionHide',
-                  leafFormat          = '$PieceName$',
-                  name                = '',
-                  nonLeafFormat       = '$PropertyValue$',
-                  pieceZoom           = '0.33',
-                  pieceZoom2          = '0.5',
-                  pieceZoom3          = '0.6',
-                  propertyGate        = '',
-                  refreshHotkey       = key('I',ALT_SHIFT),
-                  showMenu            = True,
-                  sides               = '',
-                  sortFormat          = '$PieceName$',
-                  sortPieces          = True,
-                  sorting             = 'alpha',
-                  text                = '',
-                  tooltip             = 'Show inventory of all pieces',
-                  zoomOn              = False):
+                 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,
@@ -1104,17 +1250,8 @@
                                        tooltip             = tooltip,
                                        zoomOn              = zoomOn)
                   
-    
-        
+registerElement(Inventory)
 
-    
-    
-
-
-
-
-
-
 # --------------------------------------------------------------------
 class Prototypes(GameElement):
     TAG = Element.MODULE+'PrototypesContainer'
@@ -1149,17 +1286,21 @@
         '''
         return self.getElementsByKey(Prototype,'name',asdict=asdict)
         
-    
+registerElement(Prototypes)
 
-
 # --------------------------------------------------------------------
-class DiceButton(GameElement):
+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,
-                 canDisable           = False,
-                 hotkey               = key('6',ALT),
-                 icon                 = '/images/die.gif',
                  keepCount            = 1,
                  keepDice             = False,
                  keepOption           = '>',
@@ -1169,19 +1310,16 @@
                  lockSides            = False,
                  nDice                = 1,
                  nSides               = 6,
-                 name                 = '1d6',
                  plus                 = 0,
                  prompt               = False,
-                 propertyGate         = '',
-                 reportFormat         = '** $name$ = $result$ *** <$PlayerName$>;',
+                 reportFormat         = '$name$ = $result$',
                  reportTotal          = False,
-                 sortDice             = False,
-                 text                 = '1d6',
-                 tooltip              = 'Roll a 1d6'):
+                 sortDice             = False):
         super(DiceButton,self).\
             __init__(elem,self.TAG,node=node,
                      addToTotal           = addToTotal,
                      canDisable           = canDisable,
+                     disabledIcon         = disabledIcon,
                      hotkey               = hotkey,
                      icon                 = icon,
                      keepCount            = keepCount,
@@ -1203,24 +1341,27 @@
                      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               = '',
-                 buttonText           = '',
-                 canDisable           = False,
                  deckCount            = '-1',
                  filter               = '',
-                 propertyGate         = '',
                  reportFormat         = '',
                  reportSingle         = False,
                  singleMap            = True,
-                 target               = GlobalKey.SELECTED,
-                 tooltip              = '',
-                 icon                 = ''):
+                 target               = GlobalKey.SELECTED):
         '''Default targets are selected units'''
         super(GameMassKey,self).\
             __init__(map,
@@ -1241,6 +1382,8 @@
                      tooltip              = tooltip,
                      icon                 = icon)
         
+registerElement(GameMassKey)
+
 # --------------------------------------------------------------------
 class StartupMassKey(GlobalKey,GameElementService):
     TAG = Element.MODULE+'StartupGlobalKeyCommand'
@@ -1287,6 +1430,8 @@
         if node is None:
             self['whenToApply'] = whenToApply
 
+registerElement(StartupMassKey)
+
 # --------------------------------------------------------------------
 class Menu(GameElement):
     TAG = Element.MODULE+'ToolbarMenu'
@@ -1293,15 +1438,16 @@
     def __init__(self,
                  game,
                  node                 = None,
+                 name                 = '',
+                 tooltip              = '',
+                 text                 = '', # Menu name
                  canDisable           = False,
+                 propertyGate         = '',
+                 disabledIcon         = '',
                  description          = '',
-                 disabledIcon         = '',
                  hotkey               = '',
                  icon                 = '',
-                 menuItems            = [],# Button texts
-                 propertyGate         = '',
-                 text                 = '',# Menu name
-                 tooltip              = ''):
+                 menuItems            = []):
         if len(description) <= 0 and len(tooltip) > 0:
             description = tooltip
         if len(tooltip) <= 0 and len(description) > 0:
@@ -1310,6 +1456,7 @@
             __init__(game,
                      self.TAG,
                      node                 = node,
+                     name                 = name,
                      canDisable           = canDisable,
                      description          = description,
                      disabledIcon         = disabledIcon,
@@ -1320,6 +1467,8 @@
                      text                 = text,
                      tooltip              = tooltip)
                      
+registerElement(Menu)
+
         
 # --------------------------------------------------------------------
 class SymbolicDice(GameElement):
@@ -1370,6 +1519,8 @@
     def getSymbolicDice(self):
         return self.getParent(SymbolicDice)
         
+registerElement(SymbolicDice)
+
         
 # --------------------------------------------------------------------
 class SpecialDie(GameElement):
@@ -1399,8 +1550,7 @@
     def getSymbolicDice(self):
         return self.getParent(SymbolicDice)
         
-
-        
+registerElement(SpecialDie)
                      
 # --------------------------------------------------------------------
 class DieFace(GameElement):
@@ -1421,6 +1571,9 @@
                      
     def getSpecialDie(self):
         return self.getParent(SpecialDie)
+
+registerElement(DieFace)
+                     
 #
 # EOF
 #
@@ -1451,6 +1604,223 @@
 
 
 # --------------------------------------------------------------------
+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,
@@ -1474,6 +1844,8 @@
                                           unexSepY             = unexSepY,
                                           up                   = up)
 
+registerElement(StackMetrics)
+
 # --------------------------------------------------------------------
 class ImageSaver(MapElement):
     TAG=Element.MAP+'ImageSaver'
@@ -1491,6 +1863,9 @@
                                         icon                 = icon,
                                         propertyGate         = propertyGate,
                                         tooltip              = tooltip)
+
+registerElement(ImageSaver)
+
 # --------------------------------------------------------------------
 class TextSaver(MapElement):
     TAG=Element.MAP+'TextSaver'
@@ -1509,6 +1884,7 @@
                                         propertyGate         = propertyGate,
                                         tooltip              = tooltip)
 
+registerElement(TextSaver)
 
 # --------------------------------------------------------------------
 class ForwardToChatter(MapElement):
@@ -1516,6 +1892,8 @@
     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'
@@ -1522,6 +1900,8 @@
     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'
@@ -1528,6 +1908,8 @@
     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'
@@ -1534,6 +1916,8 @@
     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'
@@ -1540,6 +1924,8 @@
     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'
@@ -1547,6 +1933,8 @@
         super(SelectionHighlighters,self).\
             __init__(map,self.TAG,node=node,**kwargs)
 
+registerElement(SelectionHighlighters)
+
 # --------------------------------------------------------------------
 class KeyBufferer(MapElement):
     TAG=Element.MAP+'KeyBufferer'
@@ -1553,6 +1941,8 @@
     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'
@@ -1565,6 +1955,8 @@
                                                 enabled   = enabled,
                                                 thickness = thickness)
 
+registerElement(HighlightLastMoved)
+
 # --------------------------------------------------------------------
 class CounterDetailViewer(MapElement):
     TAG=Element.MAP+'CounterDetailViewer'
@@ -1587,7 +1979,7 @@
                  extraTextPadding     = 0,
                  fgColor              = rgb(0,0,0),
                  fontSize             = 9,
-                 graphicsZoom         = 1.0,
+                 graphicsZoom         = 1.0,# Zoom on counters
                  hotkey               = key('\n',0),
                  layerList            = '',
                  minDisplayPieces     = 2,
@@ -1649,6 +2041,8 @@
                       verticalTopText       = verticalTopText,
                       zoomlevel             = zoomlevel)
 
+registerElement(CounterDetailViewer)
+
 # --------------------------------------------------------------------
 class GlobalMap(MapElement):
     TAG=Element.MAP+'GlobalMap'
@@ -1668,6 +2062,8 @@
                      scale                = scale,
                      tooltip              = 'Show/Hide overview window')
 
+registerElement(GlobalMap)
+
 # --------------------------------------------------------------------
 class Zoomer(MapElement):
     TAG = Element.MAP+'Zoomer'
@@ -1687,6 +2083,7 @@
                  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])
@@ -1707,6 +2104,8 @@
                      zoomPickKey          = zoomPickKey,
                      zoomStart            = zoomStart)
 
+registerElement(Zoomer)
+
 # --------------------------------------------------------------------
 class HidePiecesButton(MapElement):
     TAG=Element.MAP+'HidePiecesButton'
@@ -1724,6 +2123,8 @@
                      showingIcon          = showingIcon,
                      tooltip              = tooltip)
         
+registerElement(HidePiecesButton)
+
 # --------------------------------------------------------------------
 class MassKey(GlobalKey,MapElementService):
     TAG = Element.MAP+'MassKeyCommand'
@@ -1760,6 +2161,7 @@
                      tooltip              = tooltip,
                      icon                 = icon)
 
+registerElement(MassKey)
 
 # --------------------------------------------------------------------
 class Flare(MapElement):
@@ -1783,10 +2185,13 @@
                                    flarePulsesPerSec    = flarePulsesPerSec,
                                    reportFormat         = '')
 
+registerElement(Flare)
+
 # --------------------------------------------------------------------
 class AtStart(MapElement):
     TAG = Element.MODULE+'map.SetupStack'
-    def __init__(self,map,node=None,
+    def __init__(self,map,
+                 node            = None,
                  name            = '',
                  location        = '',
                  useGridLocation = True,
@@ -1861,6 +2266,8 @@
         '''
         return self.getElementsWithKey(PieceSlot,'entryName',asdict)
 
+registerElement(AtStart)
+
 #
 # EOF
 #
@@ -1894,6 +2301,7 @@
     def getProperties(self):
         return getElementsByKey(GlobalProperty,'name')
         
+registerElement(GlobalProperties)
 
 # --------------------------------------------------------------------
 class GlobalProperty(Element):
@@ -1919,6 +2327,7 @@
     def getGlobalProperties(self):
         return self.getParent(GlobalProperties)
 
+registerElement(GlobalProperty)
 
 #
 # EOF
@@ -2099,7 +2508,8 @@
         
         return []
 
-        
+registerElement(TurnTrack)
+
 # --------------------------------------------------------------------
 class TurnCounter(TurnLevel):
     TAG = Element.MODULE+"turn.CounterTurnLevel"
@@ -2118,6 +2528,8 @@
                                          loopLimit      = loopLimit,
                                          turnFormat     = turnFormat)
                     
+registerElement(TurnCounter)
+
 # --------------------------------------------------------------------
 class TurnList(TurnLevel):
     TAG = Element.MODULE+"turn.ListTurnLevel"
@@ -2135,6 +2547,8 @@
                      configList     = configList,
                      turnFormat     = turnFormat)
                   
+registerElement(TurnList)
+
 # --------------------------------------------------------------------
 class TurnGlobalHotkey(Element):
     TAG = Element.MODULE+'turn.TurnGlobalHotkey'
@@ -2172,6 +2586,8 @@
         '''Get the turn track'''
         return self.getParent(TurnTrack)
 
+registerElement(TurnGlobalHotkey)
+
 #
 # EOF
 #
@@ -2316,6 +2732,7 @@
         
         return txt 
 
+registerElement(Documentation)
 
 # --------------------------------------------------------------------
 class AboutScreen(Element):
@@ -2343,6 +2760,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(AboutScreen)
+
 # --------------------------------------------------------------------
 class BrowserPDFFile(Element):
     TAG = Element.MODULE+'documentation.BrowserPDFFile'
@@ -2368,6 +2787,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(BrowserPDFFile)
+    
 # --------------------------------------------------------------------
 class HelpFile(Element):
     TAG = Element.MODULE+'documentation.HelpFile'
@@ -2400,6 +2821,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(HelpFile)
+    
 # --------------------------------------------------------------------
 class BrowserHelpFile(Element):
     TAG = Element.MODULE+'documentation.BrowserHelpFile'
@@ -2427,7 +2850,9 @@
     def getDocumentation(self):
         '''Get Parent element'''
         return self.getParent(Documentation)
-        
+
+registerElement(BrowserHelpFile)
+    
 # --------------------------------------------------------------------
 class Tutorial(Element):
     TAG = Element.MODULE+'documentation.Tutorial'
@@ -2468,6 +2893,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(Tutorial)
+    
 
 #
 # EOF
@@ -2517,6 +2944,8 @@
         '''Encode for save'''
         return ['PLAYER\ta\ta\t<observer>']
 
+registerElement(PlayerRoster)
+
 # --------------------------------------------------------------------
 class PlayerSide(Element):
     TAG = 'entry'
@@ -2540,7 +2969,9 @@
         '''Get Parent element'''
         return self.getParent(PlayerRoster)
 
+registerElement(PlayerSide)
 
+
 #
 # EOF
 #
@@ -2614,7 +3045,9 @@
     def getControl(self):
         '''Get Parent element'''
         return self.getParent(ChessClockControl)
-                 
+
+registerElement(ChessClock)
+
 # ====================================================================
 class ChessClockControl(GameElement):
     TAG=Element.MODULE+'ChessClockControl'
@@ -2727,6 +3160,7 @@
         '''Return dictionary of clocs'''
         return self.getElementsByKey(ChessClock,'side',asdict)
 
+registerElement(ChessClockControl)
 
 #
 # EOF
@@ -2961,6 +3395,8 @@
                                          tooltip      = tooltip,
                                          icon         = icon)
 
+registerElement(PieceWindow)
+
 # --------------------------------------------------------------------
 class ComboWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'BoxWidget'
@@ -2972,6 +3408,8 @@
                                        width     = width,
                                        height    = height)
         
+registerElement(ComboWidget)
+
 # --------------------------------------------------------------------
 class TabWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'TabWidget'
@@ -2981,6 +3419,8 @@
                                        node      = node,
                                        entryName = entryName)
 
+registerElement(TabWidget)
+
 # --------------------------------------------------------------------
 class ListWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'ListWidget'
@@ -2997,6 +3437,8 @@
                                         scale     = scale,
                                         divider   = divider)
 
+registerElement(ListWidget)
+
 # --------------------------------------------------------------------
 class PanelWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'PanelWidget'
@@ -3011,6 +3453,8 @@
                                          nColumns  = nColumns,
                                          vert      = vert)
 
+registerElement(PanelWidget)
+
 # --------------------------------------------------------------------
 class MapWidget(Element):
     TAG=Element.WIDGET+'MapWidget'
@@ -3046,6 +3490,8 @@
         '''
         return self.getElementsByKey(WidgetMap,'mapName',asdict=asdict)
     
+registerElement(MapWidget)
+
 #
 # EOF
 #
@@ -3258,6 +3704,8 @@
         width    = self.getZone().getWidth()
         return floor(width / self.getDeltaY()  + .5)
 
+registerElement(HexGrid)
+
 # --------------------------------------------------------------------
 class SquareGrid(BaseGrid):
     TAG = Element.BOARD+'SquareGrid'
@@ -3307,6 +3755,8 @@
         width    = self.getZone().getWidth()
         return floor(width / self.getDeltaX()  + .5)
 
+registerElement(SquareGrid)
+
 # --------------------------------------------------------------------
 class HexNumbering(BaseNumbering):
     TAG = Element.BOARD+'mapgrid.HexGridNumbering'
@@ -3368,6 +3818,8 @@
 
         return x,y
 
+registerElement(HexNumbering)
+
 # --------------------------------------------------------------------
 class SquareNumbering(BaseNumbering):
     TAG = Element.BOARD+'mapgrid.SquareGridNumbering'
@@ -3395,6 +3847,7 @@
 
         return x,y
         
+registerElement(SquareNumbering)
     
 # --------------------------------------------------------------------
 class RegionGrid(Element):
@@ -3452,6 +3905,8 @@
 
         return None
         
+registerElement(RegionGrid)
+
 # --------------------------------------------------------------------
 class Region(Element):
     TAG = Element.BOARD+'Region'
@@ -3527,6 +3982,8 @@
             return b.getMap()
         return None
 
+registerElement(Region)
+
 #
 # EOF
 #
@@ -3579,7 +4036,7 @@
         children : list
             List of `Highligter` children (even if `single=True`)
         '''
-        return self.getAllElements(ZonedGridHighliger,single=single)
+        return self.getAllElements(ZonedGridHighlighter,single=single)
     def addZone(self,**kwargs):
         '''Add a `Zone` element to this
 
@@ -3607,6 +4064,8 @@
         '''
         return self.getElementsByKey(Zone,'name',asdict=asdict)
 
+registerElement(ZonedGrid)
+
 # --------------------------------------------------------------------
 class ZonedGridHighlighter(Element):
     TAG=Element.BOARD+'mapgrid.ZonedGridHighlighter'
@@ -3614,8 +4073,98 @@
         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,
@@ -3693,6 +4242,19 @@
             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
 
@@ -3758,6 +4320,17 @@
         if g is not None: return g
 
         return g
+    def getProperties(self):
+        '''Get all `Property` element from this
+
+        Parameters
+        ----------
+        Returns
+        -------
+        children : dict
+            dict of `Property` children
+        '''
+        return getElementsByKey(ZoneProperty,'name')
     
     def getPath(self):
         p  = self['path'].split(';')
@@ -3785,6 +4358,8 @@
     def getYOffset(self):
         return self.getBB()[1]
 
+registerElement(Zone)
+
 #
 # EOF
 #
@@ -3879,6 +4454,8 @@
 
         return ret
 
+registerElement(BoardPicker)
+
 # --------------------------------------------------------------------
 class Setup(Element):
     TAG = 'setup'
@@ -3901,7 +4478,9 @@
         self.addText(txt)
 
     def getPicker(self): return self.getParent(BoardPicker)
-            
+
+registerElement(Setup)
+    
 # --------------------------------------------------------------------
 class Board(Element):
     TAG = Element.PICKER+'Board'
@@ -3982,6 +4561,7 @@
             return int(self['height'])
         return 0
 
+registerElement(Board)
 
 #
 # EOF
@@ -4340,6 +4920,57 @@
         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
 
@@ -4600,6 +5231,47 @@
             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):
@@ -4657,7 +5329,9 @@
 
     def getGame(self):
         return self.getParent(Game)
-        
+
+registerElement(Map)
+
 # --------------------------------------------------------------------
 class WidgetMap(BaseMap):
     TAG = Element.WIDGET+'WidgetMap'
@@ -4669,7 +5343,9 @@
     def getMapWidget(self):
         return self.getParent(MapWidget)
 
+registerElement(WidgetMap)
 
+
 #
 # EOF
 #
@@ -4721,7 +5397,7 @@
         '''
         return self.getElementsById(Chart,'chartName',asdict=asdict)
     
-    
+registerElement(ChartWindow)    
 
 # --------------------------------------------------------------------
 class Chart(Element):
@@ -4735,6 +5411,8 @@
                                    description = description,
                                    fileName    = fileName)
 
+registerElement(Chart)
+
 #
 # EOF
 #
@@ -4766,7 +5444,7 @@
         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:
@@ -5040,6 +5718,15 @@
             Identifier
 
         '''
+        last = traits[-1]
+        # A little hackish to use the name of the class, but needed
+        # because of imports into patch scripts.
+        if not isinstance(last,BasicTrait) and \
+           not last.__class__.__name__.endswith('BasicTrait'):
+            from sys import stderr
+            print(f'Warning - last trait NOT a BasicTrait, but a {type(last)}',
+                  file=stderr)
+            
         types = []
         states = []
         for trait in traits:
@@ -5165,7 +5852,9 @@
         if parent is not None:
             parent.remove(self)
 
-    
+
+registerElement(DummyWithTraits)
+
 # --------------------------------------------------------------------
 class PieceSlot(WithTraits):
     TAG = Element.WIDGET+'PieceSlot'
@@ -5226,6 +5915,7 @@
         piece.setTraits(*traits)
         return piece
         
+registerElement(PieceSlot)
 
 # --------------------------------------------------------------------
 class Prototype(WithTraits):
@@ -5255,6 +5945,8 @@
                                        name        = name,
                                        description = description)
     
+registerElement(Prototype)
+
 #
 # EOF
 #
@@ -5262,10 +5954,11 @@
 # From traits/dynamicproperty.py
 
 # --------------------------------------------------------------------
-# Base class for property (piece or global) change traits.  Encodes
-# constraints and commands.
+# 
+# 
 class ChangePropertyTrait(Trait):
     DIRECT = 'P'
+    INCREMENT = 'I'
     def __init__(self,
                  *commands,
                  numeric     = False,
@@ -5272,6 +5965,10 @@
                  min         = 0,
                  max         = 100,
                  wrap        = False):
+        '''Base class for property (piece or global) change traits.
+        
+           Encodes constraints and commands.
+        '''
         # assert name is not None and len(name) > 0, \
         #     'No name specified for ChangePropertyTriat'
         super(ChangePropertyTrait,self).__init__()
@@ -5293,8 +5990,11 @@
             # print(cmd)
             com = cmd[0] + ':' + cmd[1].replace(',',r'\,') + ':' + cmd[2]
             if cmd[2] == self.DIRECT:
-                com += f'\,'+cmd[3].replace(':',r'\:')
+                com += f'\,'+cmd[3].replace(',',r'\\,').replace(':',r'\:')
+            elif cmd[2] == self.INCREMENT:
+                com += f'\,'+cmd[3].replace(',',r'\\,').replace(':',r'\:')
             cmds.append(com)
+        # print(cmds)
         return ','.join(cmds)
 
     def decodeCommands(self,commands):
@@ -5305,6 +6005,8 @@
             # 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
@@ -5840,7 +6542,7 @@
                  canStack    = False,
                  ignoreGrid  = False,
                  description = ''):
-
+        '''No stacking trait'''
         selectionOptions = (select +
                             (self.IGNORE_GRID if ignoreGrid else '') +
                             bandSelect)
@@ -7319,6 +8021,7 @@
         '''
         return self.getAllElements(AtStart,single)
 
+registerElement(Game)
 
 # --------------------------------------------------------------------
 class BasicCommandEncoder(GameElement):
@@ -7326,6 +8029,8 @@
     def __init__(self,doc,node=None):
         super(BasicCommandEncoder,self).__init__(doc,self.TAG,node=node)
 
+registerElement(BasicCommandEncoder)
+
 #
 # EOF
 #
@@ -8446,7 +9151,8 @@
                  vassalVersion = '3.6.7',
                  nonato        = False,
                  nochit        = False,
-                 counterScale  = 1):
+                 counterScale  = 1,
+                 resolution    = 150):
         '''Exports a PDF and associated JSON files to a VASSAL module.
 
         Parameters
@@ -8473,6 +9179,8 @@
             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
@@ -8487,6 +9195,7 @@
         self._vassalVersion   = vassalVersion
         self._nonato          = nonato
         self._nochit          = nochit
+        self._resolution      = resolution
         self._counterScale    = counterScale
         self._battleMark      = 'wgBattleMarker'
         self._battleMarks     = []
@@ -8572,6 +9281,7 @@
             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}')
         
     def setup(self):
@@ -8704,6 +9414,7 @@
         args = ['pdftocairo',
                 '-transp',
                 '-singlefile',
+                '-r', str(self._resolution),
                 '-f', str(page),
                 '-l', str(page),
                 '-png' ]
@@ -8772,9 +9483,9 @@
         imgsinfo = self.getImagesInfo()
 
         if len(imgsinfo) - 1 != docinfo['Pages']:
-            raise RuntimeError(f'Number of pages in {pdfname} '
-                               f'(doc["Pages"]) not matched in JSON '
-                               f'{self._infoname} -> {len(imagesinfo)}')
+            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:
@@ -9126,10 +9837,16 @@
                                                     'property': 'Phase',
                                                     'names': self._sides } })
             turns.addHotkey(hotkey = self._clearMoved+'Phase',
-                            name   = 'Clear moved markers')
+                            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')
+                                name   = 'Clear battle markers',
+                                reportFormat = (f'{{{self._verbose}?('
+                                                f'"`Clear all battle markers, "+'
+                                                f'""):""}}'))
 
             self._dice   = self._categories\
                                .get('die-roll',{})
@@ -9153,7 +9870,9 @@
                                         # f'+" <img src=\'{die}-"+result1'
                                         # f'+".png\' width=24 height=24>"'
                                         f'}}'),
-                        resultWindow = True);
+                        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']
@@ -10186,13 +10905,14 @@
 
     # ----------------------------------------------------------------
     def factionTraits(self,faction):
-        traits = [ReportTrait(self._eliminateKey,
-                              self._restoreKey,
-                              self._trailKey),
+        offX =  36 * self._counterScale * self._resolution/150
+        offY = -38 * self._counterScale * self._resolution/150
+        traits = [#ReportTrait(self._eliminateKey,
+                  #            self._restoreKey,
+                  #            self._trailKey),
                   TrailTrait(),
                   RotateTrait(),
-                  MovedTrait(xoff = int( 36 * self._counterScale),
-                             yoff = int(-38 * self._counterScale)),
+                  MovedTrait(xoff = int(offX),yoff = int(offY)),
                   DeleteTrait(),
                   SendtoTrait(mapName     = 'DeadMap',
                               boardName   = f'{faction} pool',
@@ -11308,7 +12028,10 @@
                             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,
@@ -11499,7 +12222,7 @@
     def addDie(self):
         '''Add a `Die` element to the module
         '''
-        if self._dice is not None:
+        if self._dice is not None and len(self._dice) > 0:
             return
         self._game.addDiceButton(name       = '1d6',
                                  hotkey     = self._diceKey)
@@ -11568,6 +12291,9 @@
     ap.add_argument('-S','--counter-scale',
                     type=float, default=1,
                     help='Scale counters by factor')
+    ap.add_argument('-R','--resolution',
+                    type=int, default=150,
+                    help='Resolution of images')
 
 
     args = ap.parse_args()
@@ -11600,6 +12326,7 @@
                                  vassalVersion = args.vassal_version,
                                  nonato        = args.no_nato_prototypes,
                                  nochit        = args.no_chit_information,
+                                 resolution    = args.resolution,
                                  counterScale  = args.counter_scale)
         exporter.run()
     except Exception as e:

Modified: trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.chit.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.chit.code.tex	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.chit.code.tex	2024-02-03 21:13:58 UTC (rev 69692)
@@ -84,7 +84,7 @@
   chit/symbol/.style={scale=.4,transform shape},
   chit/parts/.style={shape=rectangle,transform shape},
   chit/factors/.style={chit/parts,anchor=south},
-  chit/left/.style={chit/parts,anchor=south,rotate=90},
+  chit/left/.style={chit/parts,anchor=base,rotate=90},%Anchor was south
   chit/right/.style={chit/parts,anchor=north,rotate=90},
   chit/upper left/.style={chit/parts,anchor=north west},
   chit/upper right/.style={chit/parts,anchor=north east},
@@ -116,7 +116,8 @@
 
 \tikzset{%
   chit/.code={%
-    \pgfkeys{/tikz/transform shape,/tikz/shape=chit}
+    \chit at dbg{2}{chit arguments are `#1'}%
+    \pgfkeys{/tikz/transform shape,/tikz/shape=chit}%
     \pgfkeys{/chit/.cd,#1}}}
 \newcounter{chit at id}\setcounter{chit at id}{0}
 \def\chit at n@to#1#2{%
@@ -441,7 +442,7 @@
     ^^J  Name:       `#3'}
   \let\name\pgfutil at empty%
   \chit at dbg{1}{=== Before chit node}%
-  \node[chit={every chit/.try,id=#3,#1}] (tmp) at (#2) {};
+  \node[chit={/tikz/every chit/.try,id=#3,#1}] (tmp) at (#2) {};
   \chit at dbg{2}{=== After chit node}%
   \ifx|#3|\relax%
   \else%
@@ -602,8 +603,9 @@
 \def\chit at oob@rowupdate(#1,#2)#3#4{%
   \chit at dbg{2}{ Row update  c=`#1',r=`#2',s=`#3',e=`#4'}
   %\pgfmathparse{ifthenelse(#1>0,#2-#3,#2)}%
-  \pgfmathparse{#2-#3)}%
+  \pgfmathparse{#2-#3-#4}%
   \xdef#2{\pgfmathresult}%
+  \xdef#1{0}%\pgfmathresult}%
   %\xdef#1{0}
   \chit at dbg{2}{ \space\space-> update `\string#2'=#2}
 }
@@ -617,6 +619,7 @@
 }
 \newif\ifwg at oob@inv\wg at oob@invfalse
 \def\chit at oob@spacer{hspace}
+\def\chit at oob@vspacer{vspace}
 \def\wg at star@oob{\wg at oob@invtrue\wg at oob}
 \def\wg at nostar@oob{\wg at oob@invfalse\wg at oob}
 \def\oob{%
@@ -626,6 +629,8 @@
 }
 \def\wg at oob#1#2#3#4{
   \def\r{0}
+  \pgfmathparse{#3*(#2-1)}%
+  \edef\a{\pgfmathresult}
   \chit at dbg{2}{OOB: `#1'}
   \foreach[count=\ti from 0] \t/\y in #1{
     \xdef\o{\r}
@@ -644,20 +649,28 @@
           \ifx\m\@empty\def\m{1}\fi
           \ifx\u\m\def\m{1}\fi
           \foreach \n in {1,...,\m}{%
-            \chit at dbg{2}{OOB Chit is `\u'}%
+            \chit at dbg{2}{OOB Chit is `\u' `\chit at oob@spacer'}%
             \ifx\u\chit at oob@spacer%
               \chit at dbg{3}{Chit `\u' is spacer `\chit at oob@spacer'}
               \pgfmathparse{\c+#4}%
               \xdef\c{\pgfmathresult}%
-            \else
-              \ifnum\chitdbglvl>2%
-                \node[minimum width=#3cm,minimum height=#3cm,
-                      draw,transform shape] at (\c,\r) {};
+            \else%
+              \ifx\u\chit at oob@vspacer%
+                \chit at dbg{3}{Chit `\u' is vspacer `\chit at oob@vspacer'}
+                \pgfmathparse{ifthenelse(abs(\c)<0.0001,0,#3)}
+                \xdef\ll{\pgfmathresult}
+                \chit at dbg{2}{\string\ll=`\ll'}
+                \chit at oob@rowupdate(\c,\r){\ll}{#4}
+              \else
+                \ifnum\chitdbglvl>2%
+                  \node[minimum width=#3cm,minimum height=#3cm,
+                        draw,transform shape] at (\c,\r) {};
+                \fi
+                \ifx\u\chit at blank\else%
+                  \chit[\u=\ti,zone oob point={\u}{\c}{\r}](\c,\r);%
+                \fi%
+                \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
               \fi
-              \ifx\u\chit at blank\else%
-                \chit[\u=\ti,zone oob point={\u}{\c}{\r}](\c,\r);%
-              \fi%
-              \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
             \fi
           }
         \fi
@@ -688,7 +701,7 @@
         %  \chit at dbg{2}{ \space Breaking loop \rr\space > \y}%
         %  \breakforeach%
         %\else%
-          \chit at oob@rowupdate(\c,\r){#3}{#4}
+          \chit at oob@rowupdate(\c,\r){#3}{0}% Extra spacing?
         %\fi
       }
     \fi
@@ -699,14 +712,168 @@
   }
   \chit at dbg{3}{End of OOB (c=`\c',r=`\r',y=`\y')}
   \@ifnextchar;{\@gobble}{}}
+\def\wg at star@hoob{\wg at oob@invtrue\wg at hoob}
+\def\wg at nostar@hoob{\wg at oob@invfalse\wg at hoob}
+\def\hoob{%
+  \@ifstar{\wg at star@hoob%
+  }{\wg at nostar@hoob%
+  }%
+}
+\def\wg at hoob#1#2#3#4{
+  \def\r{0}
+  \def\c{0}
+  \pgfmathparse{#3*(#2-1)}%
+  \edef\a{\pgfmathresult}
+  \chit at dbg{2}{OOB: `#1'}
+  \foreach[count=\ti from 0] \t/\y in #1{
+    \xdef\o{\r}
+    % \def\c{0}
+    \ifx\t\y\def\y{0}\fi
+    \chit at dbg{2}{Turn \ti\space(\r,\t,y=\y):'}
+    \ifx\t\empty\else
+      % Count how many are left for this turn
+      \chit at dbg{2}{At start of turn \t\space\string\c=\c}
+      \def\l{\c}%
+      \let\ig\empty
+      \foreach \u/\m in \t{
+        \ifx\ig\empty
+          \ifx\u\empty\else
+            \ifx\u\m\def\m{1}\fi
+            \ifx\u\chit at oob@spacer%
+              \pgfmathparse{\l+\m*#4}\xdef\l{\pgfmathresult}
+              \chit at dbg{2}{Got \m\space hspace (#4) -> \l}
+            \else
+              \ifx\u\chit at oob@vspace%
+                \xdef\ig{1}
+                \chit at dbg{2}{Got vspace -> \l (\ig)}
+              \else
+                \pgfmathparse{\l+\m*#3}
+                \xdef\l{\pgfmathresult}
+                \chit at dbg{2}{Got \m\space units -> \l}
+              \fi
+            \fi
+          \fi
+        \fi}
+      % Check if there's enough room
+      \chit at dbg{2}{To fill the rest of turn needs `\l' compared to
+        `\a' (#3*(#2-1))}
+      \pgfmathparse{ifthenelse(abs(\l)>=#3*(#2-1),0,1}%
+      \xdef\l{\pgfmathresult}%
+      \chit at dbg{2}{Break or not `\l'}
+      \ifnum\l=0\chit at oob@turnupdate(\c,\r){#3}{#4}\fi
+    \fi
+    \ifwg at oob@inv%
+      \pic[transform shape] at (\c+.5*#3,\r) {chit/oob turn=\ti};% was dx=0.5
+    \else
+      \pic[transform shape] at (\c+-.5*#3,\r) {chit/oob turn=\ti};% was dx=-0.5
+    \fi%
+    %\chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
+    \ifx\t\empty\else%
+      \def\lv{0}
+      \foreach \u/\m in \t{
+        %% \chit at dbg{2}{ `\u'=`\m'}
+        \ifx\u\empty\else
+          \ifx\m\@empty\def\m{1}\fi
+          \ifx\u\m\def\m{1}\fi
+          \foreach \n in {1,...,\m}{%
+            \chit at dbg{2}{OOB Chit is `\u' `\chit at oob@spacer'}%
+            \ifx\u\chit at oob@spacer%
+              \chit at dbg{3}{Chit `\u' is spacer `\chit at oob@spacer'}
+              \pgfmathparse{\c+#4}%
+              \xdef\c{\pgfmathresult}%
+            \else%
+              \ifx\u\chit at oob@vspacer%
+                \chit at dbg{3}{Chit `\u' is vspacer `\chit at oob@vspacer'}
+                \pgfmathparse{ifthenelse(abs(\c)<0.0001,0,#3)}
+                \xdef\ll{\pgfmathresult}
+                \chit at dbg{2}{\string\ll=`\ll'}
+                \chit at oob@rowupdate(\c,\r){\ll}{#4}
+                \xdef\lv{1}
+              \else
+                \ifnum\chitdbglvl>2%
+                  \node[minimum width=#3cm,minimum height=#3cm,
+                        draw,transform shape] at (\c,\r) {};
+                \fi
+                \ifx\u\chit at blank\else%
+                  \chit[\u=\ti,zone oob point={\u}{\c}{\r}](\c,\r);%
+                \fi%
+                \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
+              \fi
+            \fi
+          }
+        \fi
+      }
+    \fi
+    \chit at dbg{2}{ End of chits in turn
+      \ti\space(c=`\c',r=`\r',o='\o',y='\y')}
+    % --- Not relevant, I think
+    % IF no units where given, then we force \c to be non-zero so that
+    % \chit at oob@turnupdate increments the row
+    % \ifx\t\@empty
+    %   \def\c{#3}
+    %   \chit at dbg{2}{ Turn is empty, set c=`\c'}
+    % \fi
+    % ---
+    %\ifnum\y<0% No explicit number of rows given
+    %  \def\c{#3}
+    %  \chit at dbg{2}{ No explicit number of rows given, set c=`\c'}
+    %\fi
+    % In case the user gave and explicit number of rows, add the rows
+    % that are missing.  \y is initially set to the number of
+    % requested rows, and then decremented every time we go down one
+    % row.  So if the number of rows we did so far is N, and the
+    % requested number of rows is M, then the loop below adds M-N
+    % rows.
+    \ifnum\y>0%
+      \chit at dbg{2}{ Looping rows from 2 to \y, break when row > \y}%
+      \foreach \rr  in {2,...,\y}{
+        %\ifnum\rr>\y% A little funny, but \y can be negative!
+        %  \chit at dbg{2}{ \space Breaking loop \rr\space > \y}%
+        %  \breakforeach%
+        %\else%
+          \chit at oob@rowupdate(\c,\r){#3}{0}% Extra spacing?
+        %\fi
+      }
+    \fi
+    % --- Not relevant I think
+    % This will zero \c.  However, if on entry |\c|>0, then we also
+    % increment the row
+    % \chit at oob@turnupdate(\c,\r){#3}{#4}
+    % ---
+    % Horizontal spacer
+
+    %\pgfmathparse{ifthenelse(abs(\c)>=\a,1,0)}\xdef\l{\pgfmathresult}
+    \pgfmathparse{\c+1.5*#4}%
+    \xdef\c{\pgfmathresult}%
+    \ifnum\lv=1%
+      \pgfmathparse{\r-#4}
+      \chit at oob@rowupdate(\c,\r){0}{#4}
+    \else
+      \chit at oob@cellupdate(\c,\r){#2}{#3}{\y}
+      \ifnum\y<0
+        \chit at oob@turnupdate(\c,\r){#3}{#4}
+      \else
+      \fi
+    \fi
+    % \xdef\y{0}
+    \chit at dbg{2}{End of turn \ti\space(c=`\c',r=`\r',o='\o',y='\y')}
+  }
+  \chit at dbg{3}{End of OOB (c=`\c',r=`\r',y=`\y')}
+  \@ifnextchar;{\@gobble}{}}
 \tikzset{
   chit/cell background/.style={fill=black},
+  %chit/cell background flipped/.style={fill=black},
   blank chit/.style={/chit/frame={draw=none,fill=none}},
+  chit/grid lines/.style={dashed},
 }
 \def\chit at blank{blank chit}
 \def\chit at cellbg(#1,#2)#3{%
   \draw[chit/cell background](#1-#3/2,#2-#3/2) rectangle++(#3,#3);
 }
+\def\chit at celldblbg(#1,#2)#3{%
+  \draw[chit/cell background,chit/cell background flipped/.try]%
+  (#1-#3/2,#2-#3/2) rectangle++(#3,#3);
+}
 \newif\ifchits at reset\chits at resettrue
 \def\chit at sng@cellupdate(#1,#2)#3#4{%
   \chit at dbg{2}{Current `#1' vs `#4'*(`#3'+1)}
@@ -746,6 +913,17 @@
     \fi%
   }%
   \@ifnextchar;{\@gobble}{}}
+\def\chitgrid#1#2#3{%
+  \pgfmathparse{#3/2}\edef\rmin{\pgfmathresult}%
+  \pgfmathparse{#2*#3-#3/2}\edef\rmax{\pgfmathresult}%
+  %\draw[red](-#3/2,\rmin)rectangle(#3*#1-#3/2,-\rmax);
+  \foreach \cc in {0,...,#1}{
+    \draw[chit/grid lines] (\cc*#3-#3/2,3*#3/4)--(\cc*#3-#3/2,-\rmax-#3/4);}
+  %\chit at dbg{0}{Drawing horizontal lines from `\rmin, `-\rmin', ..., `-\rmax'}
+  \foreach \rr in {\rmin,-\rmin,...,-\rmax}{
+    %\chit at dbg{0}{Horizontal line at `\rr'}
+    \draw[chit/grid lines] (-3*#3/4,\rr)--(#1*#3-#3/4,\rr);}
+}
 \def\chit at dbl@flip(#1,#2)#3{%
   \pgfmathparse{-#1}%
   \xdef\mc{\pgfmathresult}%
@@ -780,7 +958,7 @@
               \chit at cellbg(\c,\r){#3}
               \chit[\u=\ti](\c,\r)
               \chit at dbl@flip(\c,\r){#3}
-              \chit at cellbg(\mc,\r){#3}
+              \chit at celldblbg(\mc,\r){#3}
               \chit[\u\space flipped=\ti,zone turn=\t,zone mult=\n](\mc,\r)
               \chit at dbl@cellupdate(\c,\r){#2}{#3}
             \fi
@@ -792,7 +970,26 @@
   \draw[dashed](0,-3*#3/4)--(0,\r-#3/4);%
   \draw[dashed,<-] (#3/5,-2*#3/3)--(#3/2,-2*#3/3) node[transform shape,anchor=west]{Back};%
   \draw[dashed,<-] (-#3/5,-2*#3/3)--(-#3/2,-2*#3/3) node[transform shape,anchor=east]{Front};%
+  % \foreach \cc in {0,...,#2}{
+  %   \draw[dashed] (\cc*#3,-3*#3/4)--(\cc*#3,\r-#3/4);
+  %   \draw[dashed] (-\cc*#3,-3*#3/4)--(-\cc*#3,\r-#3/4);}
+  % \pgfmathparse{#3/2}\edef\rmin{\pgfmathresult}%
+  % \chit at dbg{0}{Drawing horizontal lines from `-\rmin, `\rmin', ..., `\r'}
+  % \foreach \rr in {-\rmin,\rmin,...,\r}{
+  %   \chit at dbg{0}{Horizontal line at `\rr'}
+  %   \draw[dashed] (-#2*#3-#3/4,\rr)--(#2*#3+#3/4,\rr);}
   \@ifnextchar;{\@gobble}{}}
+\def\doublechitgrid#1#2#3{%
+  \pgfmathparse{#3/2}\edef\rmin{\pgfmathresult}%
+  \pgfmathparse{#2*#3-#3/2}\edef\rmax{\pgfmathresult}%
+  \foreach \cc in {0,...,#1}{
+    \draw[chit/grid lines] (\cc*#3,-3*#3/4)--(\cc*#3,\rmax+#3/4);
+    \draw[chit/grid lines] (-\cc*#3,-3*#3/4)--(-\cc*#3,\rmax+#3/4);}
+  %\chit at dbg{0}{Drawing horizontal lines from `-\rmin, `\rmin', ..., `\rmax'}
+  \foreach \rr in {-\rmin,\rmin,...,\rmax}{
+    %\chit at dbg{0}{Horizontal line at `\rr'}
+    \draw[chit/grid lines] (-#1*#3-#3/4,\rr)--(#1*#3+#3/4,\rr);}
+}
 \tikzset{%
   battle marker/.pic={
     \node[shape=circle,
@@ -817,7 +1014,7 @@
       minimum size=8mm,
       draw=black,
       fill=#2,
-      every odds marker/.try] at (.2,-.2) {#1};
+      every odds marker/.try] at (.16,-.16) {#1};
     }
   },
   odds marker/.style args={#1,#2}{
@@ -1012,7 +1209,55 @@
     \pgfpathlineto{\pgfpoint{-.253cm}{-.146cm}}
     % \pgfusepath{draw}  %draw interiaor
   }}
+\usetikzlibrary{shadows.blur}
+\newif\ifwg at chit@drop\wg at chit@dropfalse
 \tikzset{
+  chit has drop/.is if=wg at chit@drop,
+  chit has drop/.default=true,
+  chit has drop/.initial=false,
+  /tikz/render blur shadow/.add code={%
+    \chit at dbg{2}{Number of blur steps: \pgfbs at steps}%
+    \ifnum\pgfbs at steps=0\else
+    \chit at dbg{2}{Making shadow blur}%
+    }{\fi}}
+\tikzset{%
+  chit drop/.code={%
+    %% \message{^^J Args `#1'}%
+    \pgfkeysalso{%
+      chit has drop=true,
+      /tikz/blur shadow={shadow blur steps=5,
+        shadow opacity=25,
+        shadow xshift=.05cm,
+        shadow yshift=-.05cm,
+        shadow blur radius=.05cm,
+        #1}}%
+    \ifnum\pgfbs at steps=0%
+      \gdef\wg at drop@margin{0pt}%
+    \else%
+      \ifwg at chit@drop%
+        \pgfmathparse{
+          \pgfbs at radius+
+          veclen(
+          \pgfkeysvalueof{/tikz/shadow xshift},
+          \pgfkeysvalueof{/tikz/shadow yshift})}
+        \xdef\wg at drop@margin{\pgfmathresult pt}%
+      \else%
+        \gdef\wg at drop@margin{0pt}%
+      \fi
+      %% \message{^^J Drop margin is `\wg at drop@margin'
+      %%   `\pgfbs at radius'
+      %%   `\pgfkeysvalueof{/tikz/shadow xshift}',
+      %%   `\pgfkeysvalueof{/tikz/shadow yshift}'}%
+    \fi%
+  },%
+  chit drop/.default=,%
+  no chit drop/.code={%
+    \pgfkeysalso{
+      /tikz/blur shadow={shadow blur steps=0}}
+    \gdef\wg at drop@margin{0pt}%
+  }
+}%
+\tikzset{
   chit/text base/.style={
     shape=rectangle,
     inner sep=0pt,

Modified: trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.hex.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.hex.code.tex	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.hex.code.tex	2024-02-03 21:13:58 UTC (rev 69692)
@@ -524,6 +524,7 @@
   \fi%
   \@ifnextchar;{\@gobble}{}%
 }
+\newif\if at hex@t at rot\@hex at t@rotfalse%
 \tikzset{%
   /hex/terrain/.search also={/tikz},%
   /hex/terrain/.cd,%
@@ -531,6 +532,7 @@
   image/.store in=\hex at t@image,%
   code/.store in=\hex at t@code,%
   clip/.store in=\hex at t@clip,%
+  random rotation/.is if=@hex at t@rot,
   pic/.default=,
   image/.default=,
   code/.default=,
@@ -591,7 +593,15 @@
     \@ifundefined{hex at t@image}{\let\hex at t@image\empty}{}
     \@ifundefined{hex at t@code}{\let\hex at t@code\empty}{}
     \@ifundefined{hex at t@code}{\let\hex at t@code\empty}{}
-    \ifx\hex at t@code\empty\else\hex at t@code\fi%
+    \def\hex at t@angle{0}%
+    \if at hex@t at rot%
+      \pgfmathrandominteger{\hex at t@angle}{0}{5}
+      \pgfmathparse{int(60*\hex at t@angle)}\edef\hex at t@angle{\pgfmathresult}%
+    \fi%
+    \hex at dbg{5}{Will rotate terrain by `\hex at t@angle'}%
+    \scope[rotate=\hex at t@angle]%
+      \ifx\hex at t@code\empty\else\hex at t@code\fi%
+    \endscope% End rotate code
     % If we have no image, check if we have pictures.
     \ifx\hex at t@image\empty%
       \hex at dbg{8}{No terrain images}%
@@ -600,7 +610,9 @@
         \hex at dbg{5}{Terrain pictures}%
         \pgfpointorigin\wg at tmpa=\pgf at x\wg at tmpb=\pgf at y%
         \foreach \i in \hex at t@pic{%
-          \wg at pic@all{\i}{}{\the\wg at tmpa,\the\wg at tmpb}{}}%
+          \wg at pic@all{\i}{}{\the\wg at tmpa,\the\wg at tmpb}{%
+            rotate=\hex at t@angle,
+            transform shape}}%
       \fi% We have pictures.
       \else % We have images
         \hex at dbg{5}{Terrain images}%
@@ -610,6 +622,7 @@
           \expandafter\wg at node{%
             \includegraphics[width=2cm]{\i}}\@endwg at node %
           {}{\wg at tmpa,\wg at tmpb}{%
+            rotate=\hex at t@angle,%
             shape=rectangle,%
             anchor=center,%
             transform shape,%
@@ -12302,11 +12315,12 @@
         \xdef\hex at r@p{\hex at r@p ($(300:\hex at r@r)+(-120:\hex at r@t/2)$)}
       \fi
       \ifhex at r@ne
-        \xdef\hex at r@p{\hex at r@p --cycle}
+      %\xdef\hex at r@p{\hex at r@p --cycle}
+      \xdef\hex at r@p{\hex at r@p -- (0:\hex at r@r)}
       \else
         \xdef\hex at r@p{\hex at r@p --(.5:\hex at r@r)}
       \fi
-      \hex at dbg{4}{Ridge along se: `\hex at r@p'}
+      \hex at dbg{4}{Ridge along south east: `\hex at r@p'}
     \fi
     \hex at dbg{3}{ Ridges path: \hex at r@p}
     % \draw[red] \hex at r@p;
@@ -12446,7 +12460,7 @@
   \ifnum#1<10 0\fi%
   #1}
 \def\hex at do@label{%
-  \hex at dbg{1}{Hex label: `\meaning\hex at label'}%
+  \hex at dbg{3}{Hex label: `\meaning\hex at label'}%
   \edef\hex at l@tmp{[%
     /hex/label/.cd,%
     rotate=0,%

Modified: trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.util.code.tex
===================================================================
--- trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.util.code.tex	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/tex/latex/wargame/tikzlibrarywargame.util.code.tex	2024-02-03 21:13:58 UTC (rev 69692)
@@ -293,12 +293,17 @@
   save clip/.initial={false},
 }
 \def\wg at setcornersarched#1{%
-  \ifx|#1|\else%
-  \edef\pgf at corner@arc{{#1}{#1}}%
-  \pgf at arccornerstrue%
-  \ifdim#1=0pt%
-    \pgf at arccornersfalse%
-    \fi\fi}
+  \def\arg{#1}%
+  \let\isarched\relax%   Cannot set \ifpgf at arccorners directly inside
+                     %   other \if
+  \ifx\arg\@empty\else%
+    \edef\pgf at corner@arc{{#1}{#1}}%
+    \let\isarched\pgf at arccornerstrue%
+    \ifdim#1=0pt%
+      \let\isarched\pgf at arccornersfalse%
+    \fi%
+  \fi%
+  \isarched}
 \newdimen\wg at lw@scaled\wg at lw@scaled=1pt
 \def\wg at getscale{%
   \pgfgettransformentries{%

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.beach.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.city.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.light_woods.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.mountains.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.rough.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.swamp.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.town.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.village.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wargame.woods.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wgexport.cls
===================================================================
--- trunk/Master/texmf-dist/tex/latex/wargame/wgexport.cls	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/tex/latex/wargame/wgexport.cls	2024-02-03 21:13:58 UTC (rev 69692)
@@ -76,12 +76,26 @@
 \def\end at info{%
   \let\mk at i\oldmk at i%
   \mk at w{ \space \@rbchar,}}
-\newcommand\chitimages[2][]{%
+\def\wg at add@drop at margin{%
+  \@ifundefined{wg at drop@margin}{}{
+    \dimen0=\wg at drop@margin
+    % \ifwg at chit@drop
+    \ifdim\dimen0>0pt%
+      \path ($(current bounding box.north east)+(45:\wg at drop@margin)$)
+      -- ($(current bounding box.south west)+(225:\wg at drop@margin)$);
+    \fi}}
+\def\chitimages{%
+  \@ifnextchar[{\@chitimages}{\chitimages[]}%]
+}%
+\def\@chitimages[#1]{%
+  \@ifnextchar[{\@@chitimages[#1]}{\@@chitimages[#1][]}%]
+}%
+\def\@@chitimages[#1][#2]#3{%
   \begingroup%
   \let\chit at report\do at chit@report%
   \let\natoapp at report\do at natoapp@report%
-  \chit at dbg{2}{chits to make images of `#2'}%
-  \foreach[count=\ti from 0] \t/\x in #2{%
+  \chit at dbg{2}{chits to make images of `#3'}%
+  \foreach[count=\ti from 0] \t/\x in #3{%
     \chit at dbg{2}{^^JRow: `\t' (`\x')}
     \ifx\t\empty\else% Ignore empty rows
       \chit at dbg{5}{^^JSubcategory: `\x' (default `#1')}
@@ -91,14 +105,17 @@
         \ifx\u\empty\else% Ignore empty cells
           \ifx\u\chit at blank\else%
             \chit at dbg{2}{Next chit `\u' with possible multiplicity `\m'}%
-            \ifx\m\@empty\def\m{1}\fi% If not multiplicity defined
+            \ifx\m\@empty\def\m{1}\fi% If no multiplicity defined
             \ifx\u\m\def\m{1}\fi% If the same as unit
             \chit at dbg{2}{Next chit `\u' multiplicity `\m'}%
             %% We only make one copy of the chit, since we can duplicate
             %% it in VASSAL
             \info*{\u}{counter}{\x}
-            \begin{tikzpicture}
+            \nopagecolor%
+            \gdef\wg at drop@margin{0pt}%
+            \begin{tikzpicture}[chit has drop=false,#2]
               \chit[\u=\ti]%
+              \wg at add@drop at margin%
             \end{tikzpicture}
             \end at info%
             %% \foreach \n in {1,...,\m}{% Make a number of copies
@@ -120,11 +137,17 @@
   \chit at dbg{2}{End of outer loop}%
   \endgroup%
 }
-\newcommand\doublechitimages[2][]{%
+\def\doublechitimages{%
+  \@ifnextchar[{\@doublechitimages}{\doublechitimages[]}%]
+}%
+\def\@doublechitimages[#1]{%
+  \@ifnextchar[{\@@doublechitimages[#1]}{\@@doublechitimages[#1][]}%]
+}%
+\def\@@doublechitimages[#1][#2]#3{%
   \begingroup%
   \let\chit at report\do at chit@report%
   \let\natoapp at report\do at natoapp@report%
-  \foreach[count=\ti from 0] \t/\x in #2{%
+  \foreach[count=\ti from 0] \t/\x in #3{%
     \ifx\t\empty\else% Ignore empty rows
       \chit at dbg{5}{^^JSubcategory: `\x' (default `#1')}
       % Take sub-category or default
@@ -141,13 +164,18 @@
             %% We only make one copy of the chit, since we can duplicate
             %% it in VASSAL
             \info*{\u}{counter}{\x}%
-            \begin{tikzpicture}%
+            \nopagecolor%
+            \gdef\wg at drop@margin{0pt}%
+            \begin{tikzpicture}[chit has drop=false,#2]%
               \chit[\u=\ti]%
+              \wg at add@drop at margin%
             \end{tikzpicture}%
             \end at info%
             \info*{\s}{counter}{\x}%
-            \begin{tikzpicture}%
+            \nopagecolor%
+            \begin{tikzpicture}[chit has drop=false,#2]%
               \chit[\s=\ti]%
+              \wg at add@drop at margin%
             \end{tikzpicture}%
             \end at info%
             %% \foreach \n in {1,...,\m}{% Make a number of copies
@@ -235,7 +263,7 @@
       \def\hex at col{0}%
       \def\hex at row{0}%
       \node[hex,inner sep=0,outer sep=0]{%
-        \message{^^JHex label: `\meaning\hex at label'}%
+        %\message{^^JHex label: `\meaning\hex at label'}%
         \global\let\mk at label\hex at label}}}%
   \info*{#2}{#1}{#3}%
   \mk at w{ \mk at i "zones": \@lbchar}%
@@ -242,7 +270,7 @@
   \edef\mk at i{\mk at i\space}
   %% Everything is made into centimeters
   \mk at w{ \mk at i "units": "cm",}
-  \hex at dbg{0}{Label: `\meaning\mk at label'}
+  \hex at dbg{3}{Label: `\meaning\mk at label'}
   \@ifundefined{mk at label}{}{\mk at w{ \mk at i "labels": "\mk at label",}}
   %% Write out coordinate options as "coords" object
   \mk at w{ \mk at i"coords": \@lbchar}%
@@ -345,7 +373,7 @@
   \mk at w{ \mk at i\@rbchar}%
   \end at info%
 }
-\def\wg at gennumberm@rkers#1#2#3{
+\def\wg at gennumberm@rkers#1#2#3#4{
   \message{^^JNumbered markers: Type=`#1' Max=`#2' Category=`#3'}
   \def\markers{}
   \def\keys{}
@@ -353,15 +381,22 @@
     \xdef\keys{/tikz/#1 \i/.style={/tikz/#1=\i},\keys}
     \xdef\markers{\markers,#1 \i}}
   {%
-    \nopagecolor\pgfkeysalsofrom{\keys}\chitimages[#3]{\markers}}}%
+    \nopagecolor\pgfkeysalsofrom{\keys}\chitimages[#3][#4]{\markers}}}%
 \tikzset{
   wg hidden unit/.pic={},
   wg hidden unit/.style={
     chit={
+      no chit drop,
       frame={draw=none,fill=none},
       full=wg hidden unit}}}
-\DeclareRobustCommand\battlemarkers[2][BattleMarkers]{%
-  \wg at gennumberm@rkers{battle marker}{#2}{#1}%
+\def\battlemarkers{%
+  \@ifnextchar[{\@battlemarkers}{\battlemarkers[]}%]
+}%
+\def\@battlemarkers[#1]{%
+  \@ifnextchar[{\@@battlemarkers[#1]}{\battlemarkers[#1][BattleMarkers]}%]
+}%
+\def\@@battlemarkers[#1][#2]#3{%
+  \wg at gennumberm@rkers{battle marker}{#3}{#2}{#1}%
   \message{^^JMake a hidden unit and add to Markers category}
   {%
     \nopagecolor%
@@ -368,59 +403,80 @@
     \chitimages[Markers]{{wg hidden unit}}%
     %
     \info{battle-marker-icon}{icon}{}%
-    \tikz[scale=.7,transform shape]{\pic{battle marker=0};}%
+    \tikz[scale=.7,transform shape,auto icon more/.try]{%
+      \pic{battle marker=0};}%
     \info{clear-battles-icon}{icon}{}
-    \tikz[scale=.4,transform shape]{%
+    \tikz[scale=.4,transform shape,auto icon more/.try]{%
       \pic{eliminate icon};
       \pic[scale=.7,transform shape] at (-.3,0) {battle marker=0};}%
   }%
 }
-\def\wg at gencolorm@rkers#1#2#3{%
+\def\wg at gencolorm@rkers#1#2#3#4{%
   \def\markers{}
   \def\keys{}
-  \foreach \o/\f in {#2}{%
+  \foreach \o/\f/\n [count=\i] in {#2}{%
+    \ifx\n\f\def\n{\o}\fi%
     \ifx\o\f\def\f{white}\fi%
-    \message{^^JOdds marker `#1 \o' w/fill `\f'}%
-    \xdef\keys{/tikz/#1 \o/.style={/tikz/#1={\o,\f}},\keys}
-    \xdef\markers{\markers,#1 \o}}
-  {\nopagecolor\pgfkeysalsofrom{\keys}\chitimages[#3]{\markers}}}%
-\DeclareRobustCommand\oddsmarkers[2][OddsMarkers]{%
-  \wg at gencolorm@rkers{odds marker}{#2}{#1}%
+    \message{^^JColour no \i marker `#1 \n' w/fill `\f' text `\o'}%
+    \protected at xdef\keys{/tikz/#1 \n/.style={/tikz/#1={\o,\f}},\keys}
+    \xdef\markers{\markers,#1 \n}
+  }%
+  {%
+    \nopagecolor%
+    \pgfkeysalsofrom{\keys}%
+    \chitimages[#3][#4]{\markers}%
+  }%
+}%
+\def\oddsmarkers{%
+  \@ifnextchar[{\@oddsmarkers}{\oddsmarkers[]}%]
+}%
+\def\@oddsmarkers[#1]{%
+  \@ifnextchar[{\@@oddsmarkers[#1]}{\oddsmarkers[#1][OddsMarkers]}%]
+}%
+\def\@@oddsmarkers[#1][#2]#3{%
+  \wg at gencolorm@rkers{odds marker}{#3}{#2}{#1}%
   \info{odds-battles-icon}{icon}{}
-  \tikz[scale=.5,transform shape]{\pic{odds marker={?:?,white}}}
+  \tikz[scale=.5,transform shape,auto icon more/.try]{%
+    \pic{odds marker={?:?,white}}}
   \info{resolve-battles-icon}{icon}{}
-  \tikz[scale=.3,transform shape]{%
+  \tikz[scale=.3,transform shape,auto icon more/.try]{%
     \pic{dice};
     \pic[scale=1.2,transform shape] at (-.2,-.2) {battle marker=0};}%
 }
-\DeclareRobustCommand\resultmarkers[2][ResultMarkers]{%
-  \wg at gencolorm@rkers{result marker}{#2}{#1}}
-\DeclareRobustCommand\commonicons[2]{%
+\def\resultmarkers{%
+  \@ifnextchar[{\@resultmarkers}{\resultmarkers[]}%]
+}%
+\def\@resultmarkers[#1]{%
+  \@ifnextchar[{\@@resultmarkers[#1]}{\resultmarkers[#1][ResultMarkers]}%]
+}%
+\def\@@resultmarkers[#1][#2]#3{%
+  \wg at gencolorm@rkers{result marker}{#3}{#2}{#1}}%
+\DeclareRobustCommand\commonicons[3][]{%
   \begingroup%
   \nopagecolor%
-  \tikzset{icon/.style={scale=.4,transform shape}}%
+  \tikzset{auto icon/.style={scale=.4,transform shape,#1}}%
   %
   \info{pool-icon}{icon}{}
-  \tikz[icon]{\pic{pool icon};}
+  \tikz[auto icon,auto icon more/.try]{\pic{pool icon};}
   %
   \info{oob-icon}{icon}{}%
-  \tikz[icon]{\pic{oob icon={#1}{#2}};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{oob icon={#2}{#3}};}%
   %
   \info{flip-icon}{icon}{}%
-  \tikz[icon]{\pic{flip icon};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{flip icon};}%
   %
   \info{eliminate-icon}{icon}{}%
-  \tikz[icon]{\pic{eliminate icon};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{eliminate icon};}%
   %
   \info{restore-icon}{icon}{}%
-  \tikz[icon]{\pic{restore icon};}%
+  \tikz[auto icon,auto icon more/.try]{\pic{restore icon};}%
   %
   \info{dice-icon}{icon}{}%
-  \tikz[icon,scale=.9]{\pic{dice};}%
+  \tikz[auto icon,scale=.9,auto icon more/.try]{\pic{dice};}%
   %
   \info{unit-icon}{icon}{}%
-  \tikz[icon,scale=.7]{%
-    \chit[fill=#1,
+  \tikz[auto icon,scale=.7,auto icon more/.try]{%
+    \chit[fill=#2,
       symbol={[
         scale line widths,
         line width=1pt,
@@ -428,7 +484,29 @@
         command=land,
         main=infantry,
         scale=1.3](0,-.15)}]}%
-    \endgroup%
+    %
+    \info{layer-icon}{icon}{}%
+    \begin{tikzpicture}[scale=.25]
+      \foreach \i in {-1,0,1}{
+        \scoped[shift={(0,\i*.15)}]{
+          \draw[black,fill=white] (-.5,0)
+          --(0,.3)--(.5,0)--(0,-.3)--cycle;
+        }
+      }
+    \end{tikzpicture}%
+    %
+    \info{los-icon}{icon}{}
+    \begin{tikzpicture}[scale=.25]
+      \draw[scale line widths,line width=2pt,fill=white](-.5,0)
+      to[out=70,in=110] (.5,0)
+      to[out=-110,in=-70] cycle;
+      \begin{scope}[even odd rule]
+        \clip circle(.2);
+        \fill circle(.2) (125:.18) circle(.1);
+      \end{scope}
+    \end{tikzpicture}%
+  %
+  \endgroup%
 }
 \def\dice{%
   \@ifnextchar[{\wg at dice}{\wg at dice[]}%]
@@ -440,9 +518,11 @@
   \foreach \v/\p in {#5}{%
     \info{#3-\v}{die-roll}{#3}
     \tikz[#1]{
-      \node[shape=#4,transform shape,draw=none,fill=black,opacity=.5]
-      at (.05,-.03){};
-      \node[shape=#4,#2,transform shape]{\p};}}}
+      %\node[shape=#4,transform shape,draw=none,fill=black,opacity=.5]
+      %at (.05,-.03){};
+      \node[shape=#4,#2,transform shape,
+      chit drop
+      ]{\p};\wg at add@drop at margin{}}}}
 \tikzset{
   zone turn/.store in=\zone at turn,
   zone mult/.store in=\zone at mult
@@ -502,6 +582,27 @@
   \mk at w{ \mk at i\space "end": 0}
   \mk at w{ \mk at i \@rbchar}
 }
+\tikzset{
+  chit drop margin/.store in=\wg at drop@margin,
+  chit drop shadows/.code={
+    \pgfkeysalso{%
+      /tikz/every chit node/.prefix style={chit drop={#1}},
+      /tikz/chit has drop=true}
+  },
+  chit drop shadows/.default=,
+  marker drop shadows/.code={
+    \pgfkeysalso{%
+      /tikz/every battle marker/.prefix style={chit drop={#1}},
+      /tikz/every odds marker/.prefix style={chit drop={#1}},
+      /tikz/every result marker/.prefix style={chit drop={#1}},
+      /tikz/auto icon more/.prefix style={no chit drop}}},
+  marker drop shadows/.default={
+    chit has drop=false,
+    shadow xshift=0.04cm,
+    shadow yshift=-0.04cm,
+    shadow blur radius=0.04cm}
+}
+
 %% Local Variables:
 %%   mode: LaTeX
 %% End:

Modified: trunk/Master/texmf-dist/tex/latex/wargame/wgexport.py
===================================================================
--- trunk/Master/texmf-dist/tex/latex/wargame/wgexport.py	2024-02-03 21:13:44 UTC (rev 69691)
+++ trunk/Master/texmf-dist/tex/latex/wargame/wgexport.py	2024-02-03 21:13:58 UTC (rev 69692)
@@ -208,6 +208,8 @@
 ALT_SHIFT = ALT+SHIFT
 NONE = '\ue004'
 NONE_MOD = 0
+
+# --------------------------------------------------------------------
 def key(let,mod=CTRL):
 
     '''Encode a key sequence
@@ -220,6 +222,8 @@
         Modifier mask
     '''
     return f'{ord(let)},{mod}'
+
+# --------------------------------------------------------------------
 #
 def hexcolor(s):
     if isinstance(s,str):
@@ -241,6 +245,7 @@
 
     return rgb(int(r*256),int(g*256),int(b*256))
     
+# --------------------------------------------------------------------
 # Colour encoding 
 def rgb(r,g,b):
     '''Encode RGB colour
@@ -261,6 +266,7 @@
     '''
     return ','.join([str(r),str(g),str(b)])
 
+# --------------------------------------------------------------------
 def rgba(r,g,b,a):
     '''Encode RGBA colour
 
@@ -282,6 +288,7 @@
     '''
     return ','.join([str(r),str(g),str(b),str(a)])
 
+# --------------------------------------------------------------------
 def dumpTree(node,ind=''):
     '''Dump the tree of nodes
 
@@ -296,6 +303,12 @@
     for c in node.childNodes:
         dumpTree(c,ind+' ')
 
+# --------------------------------------------------------------------
+def registerElement(cls):
+    
+    Element.known_tags[cls.TAG] = cls
+    
+        
 #
 # EOF
 #
@@ -310,6 +323,8 @@
     MAP    = MODULE + 'map.'    
     PICKER = MAP    + 'boardPicker.'
     BOARD  = PICKER + 'board.'
+    known_tags = {}
+    
     def __init__(self,parent,tag,node=None,**kwargs):
         '''Create a new element
 
@@ -429,9 +444,14 @@
             return {c[key] : c for c in cand}
         return cand
     
-    def getParent(self,cls,checkTag=True):
+    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)
@@ -440,6 +460,10 @@
         '''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:
@@ -447,6 +471,11 @@
             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
@@ -518,7 +547,83 @@
         '''  
         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
 #
@@ -526,7 +631,7 @@
 # From globalkey.py
 
 # --------------------------------------------------------------------
-class GlobalKey(Element):
+class GlobalKey(ToolbarElement):
     SELECTED = 'MAP|false|MAP|||||0|0||true|Selected|true|EQUALS'
     def __init__(self,
                  parent,
@@ -533,19 +638,21 @@
                  tag,
                  node                 = None,
                  name                 = '',                
+                 icon                 = '',
+                 tooltip              = '',
                  buttonHotkey         = '',
-                 hotkey               = '',
                  buttonText           = '',
                  canDisable           = False,
+                 propertyGate         = '',
+                 disabledIcon         = '',
+                 # Local
+                 hotkey               = '',
                  deckCount            = '-1',
                  filter               = '',
-                 propertyGate         = '',
                  reportFormat         = '',
                  reportSingle         = False,
                  singleMap            = True,
-                 target               = SELECTED,
-                 tooltip              = '',
-                 icon                 = ''):
+                 target               = SELECTED):
         '''
         Parameters
         ----------
@@ -569,28 +676,26 @@
 
         Default targets are selected units
         '''
-        nme = (name    if len(name)    > 0 else
-               tooltip if len(tooltip) > 0 else '')
-        tip = (tooltip if len(tooltip) > 0 else
-               name    if len(name)    > 0 else '') 
+        
         super(GlobalKey,self).\
             __init__(parent,
                      tag,
                      node                 = node,
-                     name                 = nme,                
+                     name                 = name,
+                     icon                 = icon,
+                     tooltip              = tooltip,
                      buttonHotkey         = buttonHotkey, # This hot key
-                     hotkey               = hotkey,       # Target hot key
                      buttonText           = buttonText,
                      canDisable           = canDisable,
+                     propertyGate         = propertyGate,
+                     disabledIcon         = disabledIcon,
+                     hotkey               = hotkey,       # Target hot key
                      deckCount            = deckCount,
                      filter               = filter,
-                     propertyGate         = propertyGate,
                      reportFormat         = reportFormat,
                      reportSingle         = reportSingle,
                      singleMap            = singleMap,
-                     target               = target,
-                     tooltip              = tip,
-                     icon                 = icon)
+                     target               = target)
 #
 # EOF
 # 
@@ -600,7 +705,7 @@
 # --------------------------------------------------------------------
 class GameElementService:
     def getGame(self):
-        return self.getParent(Game)
+        return self.getParentOfClass(Game)
 
 # --------------------------------------------------------------------
 class GameElement(Element,GameElementService):
@@ -608,23 +713,34 @@
         super(GameElement,self).__init__(game,tag,node=node,**kwargs)
 
 # --------------------------------------------------------------------
-class Notes(GameElement):
+class Notes(ToolbarElement,GameElementService):
     TAG = Element.MODULE+'NotesWindow'
     def __init__(self,elem,node=None,
-                 name             = 'Notes',
-                 buttonText       = '',
-                 hotkey           = key('N',ALT),
-                 icon             = '/images/notes.gif',
-                 tooltip          = 'Show notes window'):
-        super(Notes,self).__init__(elem,self.TAG,node=node,
-                                   name       = name,
-                                   buttonText = buttonText,
-                                   hotkey     = hotkey,
-                                   icon       = icon,
-                                   tooltip    = tooltip)
+                 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'
@@ -673,6 +789,7 @@
     
         
                    
+registerElement(PredefinedSetup)
                   
 # --------------------------------------------------------------------
 class GlobalTranslatableMessages(GameElement):
@@ -690,11 +807,15 @@
         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):
@@ -712,6 +833,8 @@
             Attributes
         '''
         super(Chatter,self).__init__(elem,self.TAG,node=node,**kwargs)
+
+registerElement(Chatter)
         
 # --------------------------------------------------------------------
 class KeyNamer(GameElement):
@@ -730,6 +853,8 @@
         '''
         super(KeyNamer,self).__init__(elem,self.TAG,node=node,**kwargs)
         
+registerElement(KeyNamer)
+        
 
 # --------------------------------------------------------------------
 #    <VASSAL.build.module.GlobalOptions
@@ -883,6 +1008,7 @@
 
         return retd
     
+registerElement(GlobalOptions)
 
 # --------------------------------------------------------------------
 class Option(Element):
@@ -894,6 +1020,8 @@
     def getGlobalOptions(self):
         return self.getParent(GlobalOptions)
 
+registerElement(Option)
+    
 # --------------------------------------------------------------------
 class Preference(Element):
     PREFS = 'VASSAL.preferences.'
@@ -947,6 +1075,9 @@
                                            default = str(default),
                                            desc    = desc,
                                            tab     = tab)
+
+registerElement(IntPreference)
+    
 # --------------------------------------------------------------------
 class FloatPreference(Preference):
     TAG = Preference.PREFS+'DoublePreference'
@@ -964,6 +1095,9 @@
                                              default = str(default),
                                              desc    = desc,
                                              tab     = tab)
+
+registerElement(FloatPreference)
+    
 # --------------------------------------------------------------------
 class BoolPreference(Preference):
     TAG = Preference.PREFS+'BooleanPreference'
@@ -982,6 +1116,9 @@
                                                        else 'false'),
                                             desc    = desc,
                                             tab     = tab)
+
+registerElement(BoolPreference)
+    
 # --------------------------------------------------------------------
 class StrPreference(Preference):
     TAG = Preference.PREFS+'StringPreference'
@@ -999,6 +1136,9 @@
                                            default = default,
                                            desc    = desc,
                                            tab     = tab)
+
+registerElement(StrPreference)
+    
 # --------------------------------------------------------------------
 class TextPreference(Preference):
     TAG = Preference.PREFS+'TextPreference'
@@ -1017,6 +1157,9 @@
                                                        .replace('\n','
')),
                                             desc    = desc,
                                             tab     = tab)
+
+registerElement(TextPreference)
+    
 # --------------------------------------------------------------------
 class EnumPreference(Preference):
     TAG = Preference.PREFS+'EnumPreference'
@@ -1042,39 +1185,42 @@
                                             tab     = tab,
                                             list    = sl)
 
+
+registerElement(EnumPreference)
     
+    
 # --------------------------------------------------------------------
 # CurrentMap == "Board"
-class Inventory(GameElement):
+class Inventory(ToolbarElement,GameElementService):
     TAG = Element.MODULE+'Inventory'
     def __init__(self,doc,node=None,
-                  canDisable          = False,
-                  centerOnPiece       = True,
-                  disabledIcon        = '',
-                  drawPieces          = True,
-                  foldersOnly         = False,
-                  forwardKeystroke    = True,
-                  groupBy             = '',
-                  hotkey              = key('I',ALT),
-                  icon                = '/images/inventory.gif',
-                  include             = '{}',
-                  launchFunction      = 'functionHide',
-                  leafFormat          = '$PieceName$',
-                  name                = '',
-                  nonLeafFormat       = '$PropertyValue$',
-                  pieceZoom           = '0.33',
-                  pieceZoom2          = '0.5',
-                  pieceZoom3          = '0.6',
-                  propertyGate        = '',
-                  refreshHotkey       = key('I',ALT_SHIFT),
-                  showMenu            = True,
-                  sides               = '',
-                  sortFormat          = '$PieceName$',
-                  sortPieces          = True,
-                  sorting             = 'alpha',
-                  text                = '',
-                  tooltip             = 'Show inventory of all pieces',
-                  zoomOn              = False):
+                 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,
@@ -1104,17 +1250,8 @@
                                        tooltip             = tooltip,
                                        zoomOn              = zoomOn)
                   
-    
-        
+registerElement(Inventory)
 
-    
-    
-
-
-
-
-
-
 # --------------------------------------------------------------------
 class Prototypes(GameElement):
     TAG = Element.MODULE+'PrototypesContainer'
@@ -1149,17 +1286,21 @@
         '''
         return self.getElementsByKey(Prototype,'name',asdict=asdict)
         
-    
+registerElement(Prototypes)
 
-
 # --------------------------------------------------------------------
-class DiceButton(GameElement):
+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,
-                 canDisable           = False,
-                 hotkey               = key('6',ALT),
-                 icon                 = '/images/die.gif',
                  keepCount            = 1,
                  keepDice             = False,
                  keepOption           = '>',
@@ -1169,19 +1310,16 @@
                  lockSides            = False,
                  nDice                = 1,
                  nSides               = 6,
-                 name                 = '1d6',
                  plus                 = 0,
                  prompt               = False,
-                 propertyGate         = '',
-                 reportFormat         = '** $name$ = $result$ *** <$PlayerName$>;',
+                 reportFormat         = '$name$ = $result$',
                  reportTotal          = False,
-                 sortDice             = False,
-                 text                 = '1d6',
-                 tooltip              = 'Roll a 1d6'):
+                 sortDice             = False):
         super(DiceButton,self).\
             __init__(elem,self.TAG,node=node,
                      addToTotal           = addToTotal,
                      canDisable           = canDisable,
+                     disabledIcon         = disabledIcon,
                      hotkey               = hotkey,
                      icon                 = icon,
                      keepCount            = keepCount,
@@ -1203,24 +1341,27 @@
                      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               = '',
-                 buttonText           = '',
-                 canDisable           = False,
                  deckCount            = '-1',
                  filter               = '',
-                 propertyGate         = '',
                  reportFormat         = '',
                  reportSingle         = False,
                  singleMap            = True,
-                 target               = GlobalKey.SELECTED,
-                 tooltip              = '',
-                 icon                 = ''):
+                 target               = GlobalKey.SELECTED):
         '''Default targets are selected units'''
         super(GameMassKey,self).\
             __init__(map,
@@ -1241,6 +1382,8 @@
                      tooltip              = tooltip,
                      icon                 = icon)
         
+registerElement(GameMassKey)
+
 # --------------------------------------------------------------------
 class StartupMassKey(GlobalKey,GameElementService):
     TAG = Element.MODULE+'StartupGlobalKeyCommand'
@@ -1287,6 +1430,8 @@
         if node is None:
             self['whenToApply'] = whenToApply
 
+registerElement(StartupMassKey)
+
 # --------------------------------------------------------------------
 class Menu(GameElement):
     TAG = Element.MODULE+'ToolbarMenu'
@@ -1293,15 +1438,16 @@
     def __init__(self,
                  game,
                  node                 = None,
+                 name                 = '',
+                 tooltip              = '',
+                 text                 = '', # Menu name
                  canDisable           = False,
+                 propertyGate         = '',
+                 disabledIcon         = '',
                  description          = '',
-                 disabledIcon         = '',
                  hotkey               = '',
                  icon                 = '',
-                 menuItems            = [],# Button texts
-                 propertyGate         = '',
-                 text                 = '',# Menu name
-                 tooltip              = ''):
+                 menuItems            = []):
         if len(description) <= 0 and len(tooltip) > 0:
             description = tooltip
         if len(tooltip) <= 0 and len(description) > 0:
@@ -1310,6 +1456,7 @@
             __init__(game,
                      self.TAG,
                      node                 = node,
+                     name                 = name,
                      canDisable           = canDisable,
                      description          = description,
                      disabledIcon         = disabledIcon,
@@ -1320,6 +1467,8 @@
                      text                 = text,
                      tooltip              = tooltip)
                      
+registerElement(Menu)
+
         
 # --------------------------------------------------------------------
 class SymbolicDice(GameElement):
@@ -1370,6 +1519,8 @@
     def getSymbolicDice(self):
         return self.getParent(SymbolicDice)
         
+registerElement(SymbolicDice)
+
         
 # --------------------------------------------------------------------
 class SpecialDie(GameElement):
@@ -1399,8 +1550,7 @@
     def getSymbolicDice(self):
         return self.getParent(SymbolicDice)
         
-
-        
+registerElement(SpecialDie)
                      
 # --------------------------------------------------------------------
 class DieFace(GameElement):
@@ -1421,6 +1571,9 @@
                      
     def getSpecialDie(self):
         return self.getParent(SpecialDie)
+
+registerElement(DieFace)
+                     
 #
 # EOF
 #
@@ -1451,6 +1604,223 @@
 
 
 # --------------------------------------------------------------------
+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,
@@ -1474,6 +1844,8 @@
                                           unexSepY             = unexSepY,
                                           up                   = up)
 
+registerElement(StackMetrics)
+
 # --------------------------------------------------------------------
 class ImageSaver(MapElement):
     TAG=Element.MAP+'ImageSaver'
@@ -1491,6 +1863,9 @@
                                         icon                 = icon,
                                         propertyGate         = propertyGate,
                                         tooltip              = tooltip)
+
+registerElement(ImageSaver)
+
 # --------------------------------------------------------------------
 class TextSaver(MapElement):
     TAG=Element.MAP+'TextSaver'
@@ -1509,6 +1884,7 @@
                                         propertyGate         = propertyGate,
                                         tooltip              = tooltip)
 
+registerElement(TextSaver)
 
 # --------------------------------------------------------------------
 class ForwardToChatter(MapElement):
@@ -1516,6 +1892,8 @@
     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'
@@ -1522,6 +1900,8 @@
     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'
@@ -1528,6 +1908,8 @@
     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'
@@ -1534,6 +1916,8 @@
     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'
@@ -1540,6 +1924,8 @@
     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'
@@ -1547,6 +1933,8 @@
         super(SelectionHighlighters,self).\
             __init__(map,self.TAG,node=node,**kwargs)
 
+registerElement(SelectionHighlighters)
+
 # --------------------------------------------------------------------
 class KeyBufferer(MapElement):
     TAG=Element.MAP+'KeyBufferer'
@@ -1553,6 +1941,8 @@
     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'
@@ -1565,6 +1955,8 @@
                                                 enabled   = enabled,
                                                 thickness = thickness)
 
+registerElement(HighlightLastMoved)
+
 # --------------------------------------------------------------------
 class CounterDetailViewer(MapElement):
     TAG=Element.MAP+'CounterDetailViewer'
@@ -1587,7 +1979,7 @@
                  extraTextPadding     = 0,
                  fgColor              = rgb(0,0,0),
                  fontSize             = 9,
-                 graphicsZoom         = 1.0,
+                 graphicsZoom         = 1.0,# Zoom on counters
                  hotkey               = key('\n',0),
                  layerList            = '',
                  minDisplayPieces     = 2,
@@ -1649,6 +2041,8 @@
                       verticalTopText       = verticalTopText,
                       zoomlevel             = zoomlevel)
 
+registerElement(CounterDetailViewer)
+
 # --------------------------------------------------------------------
 class GlobalMap(MapElement):
     TAG=Element.MAP+'GlobalMap'
@@ -1668,6 +2062,8 @@
                      scale                = scale,
                      tooltip              = 'Show/Hide overview window')
 
+registerElement(GlobalMap)
+
 # --------------------------------------------------------------------
 class Zoomer(MapElement):
     TAG = Element.MAP+'Zoomer'
@@ -1687,6 +2083,7 @@
                  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])
@@ -1707,6 +2104,8 @@
                      zoomPickKey          = zoomPickKey,
                      zoomStart            = zoomStart)
 
+registerElement(Zoomer)
+
 # --------------------------------------------------------------------
 class HidePiecesButton(MapElement):
     TAG=Element.MAP+'HidePiecesButton'
@@ -1724,6 +2123,8 @@
                      showingIcon          = showingIcon,
                      tooltip              = tooltip)
         
+registerElement(HidePiecesButton)
+
 # --------------------------------------------------------------------
 class MassKey(GlobalKey,MapElementService):
     TAG = Element.MAP+'MassKeyCommand'
@@ -1760,6 +2161,7 @@
                      tooltip              = tooltip,
                      icon                 = icon)
 
+registerElement(MassKey)
 
 # --------------------------------------------------------------------
 class Flare(MapElement):
@@ -1783,10 +2185,13 @@
                                    flarePulsesPerSec    = flarePulsesPerSec,
                                    reportFormat         = '')
 
+registerElement(Flare)
+
 # --------------------------------------------------------------------
 class AtStart(MapElement):
     TAG = Element.MODULE+'map.SetupStack'
-    def __init__(self,map,node=None,
+    def __init__(self,map,
+                 node            = None,
                  name            = '',
                  location        = '',
                  useGridLocation = True,
@@ -1861,6 +2266,8 @@
         '''
         return self.getElementsWithKey(PieceSlot,'entryName',asdict)
 
+registerElement(AtStart)
+
 #
 # EOF
 #
@@ -1894,6 +2301,7 @@
     def getProperties(self):
         return getElementsByKey(GlobalProperty,'name')
         
+registerElement(GlobalProperties)
 
 # --------------------------------------------------------------------
 class GlobalProperty(Element):
@@ -1919,6 +2327,7 @@
     def getGlobalProperties(self):
         return self.getParent(GlobalProperties)
 
+registerElement(GlobalProperty)
 
 #
 # EOF
@@ -2099,7 +2508,8 @@
         
         return []
 
-        
+registerElement(TurnTrack)
+
 # --------------------------------------------------------------------
 class TurnCounter(TurnLevel):
     TAG = Element.MODULE+"turn.CounterTurnLevel"
@@ -2118,6 +2528,8 @@
                                          loopLimit      = loopLimit,
                                          turnFormat     = turnFormat)
                     
+registerElement(TurnCounter)
+
 # --------------------------------------------------------------------
 class TurnList(TurnLevel):
     TAG = Element.MODULE+"turn.ListTurnLevel"
@@ -2135,6 +2547,8 @@
                      configList     = configList,
                      turnFormat     = turnFormat)
                   
+registerElement(TurnList)
+
 # --------------------------------------------------------------------
 class TurnGlobalHotkey(Element):
     TAG = Element.MODULE+'turn.TurnGlobalHotkey'
@@ -2172,6 +2586,8 @@
         '''Get the turn track'''
         return self.getParent(TurnTrack)
 
+registerElement(TurnGlobalHotkey)
+
 #
 # EOF
 #
@@ -2316,6 +2732,7 @@
         
         return txt 
 
+registerElement(Documentation)
 
 # --------------------------------------------------------------------
 class AboutScreen(Element):
@@ -2343,6 +2760,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(AboutScreen)
+
 # --------------------------------------------------------------------
 class BrowserPDFFile(Element):
     TAG = Element.MODULE+'documentation.BrowserPDFFile'
@@ -2368,6 +2787,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(BrowserPDFFile)
+    
 # --------------------------------------------------------------------
 class HelpFile(Element):
     TAG = Element.MODULE+'documentation.HelpFile'
@@ -2400,6 +2821,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(HelpFile)
+    
 # --------------------------------------------------------------------
 class BrowserHelpFile(Element):
     TAG = Element.MODULE+'documentation.BrowserHelpFile'
@@ -2427,7 +2850,9 @@
     def getDocumentation(self):
         '''Get Parent element'''
         return self.getParent(Documentation)
-        
+
+registerElement(BrowserHelpFile)
+    
 # --------------------------------------------------------------------
 class Tutorial(Element):
     TAG = Element.MODULE+'documentation.Tutorial'
@@ -2468,6 +2893,8 @@
         '''Get Parent element'''
         return self.getParent(Documentation)
 
+registerElement(Tutorial)
+    
 
 #
 # EOF
@@ -2517,6 +2944,8 @@
         '''Encode for save'''
         return ['PLAYER\ta\ta\t<observer>']
 
+registerElement(PlayerRoster)
+
 # --------------------------------------------------------------------
 class PlayerSide(Element):
     TAG = 'entry'
@@ -2540,7 +2969,9 @@
         '''Get Parent element'''
         return self.getParent(PlayerRoster)
 
+registerElement(PlayerSide)
 
+
 #
 # EOF
 #
@@ -2614,7 +3045,9 @@
     def getControl(self):
         '''Get Parent element'''
         return self.getParent(ChessClockControl)
-                 
+
+registerElement(ChessClock)
+
 # ====================================================================
 class ChessClockControl(GameElement):
     TAG=Element.MODULE+'ChessClockControl'
@@ -2727,6 +3160,7 @@
         '''Return dictionary of clocs'''
         return self.getElementsByKey(ChessClock,'side',asdict)
 
+registerElement(ChessClockControl)
 
 #
 # EOF
@@ -2961,6 +3395,8 @@
                                          tooltip      = tooltip,
                                          icon         = icon)
 
+registerElement(PieceWindow)
+
 # --------------------------------------------------------------------
 class ComboWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'BoxWidget'
@@ -2972,6 +3408,8 @@
                                        width     = width,
                                        height    = height)
         
+registerElement(ComboWidget)
+
 # --------------------------------------------------------------------
 class TabWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'TabWidget'
@@ -2981,6 +3419,8 @@
                                        node      = node,
                                        entryName = entryName)
 
+registerElement(TabWidget)
+
 # --------------------------------------------------------------------
 class ListWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'ListWidget'
@@ -2997,6 +3437,8 @@
                                         scale     = scale,
                                         divider   = divider)
 
+registerElement(ListWidget)
+
 # --------------------------------------------------------------------
 class PanelWidget(Element,WidgetElement):
     TAG=Element.WIDGET+'PanelWidget'
@@ -3011,6 +3453,8 @@
                                          nColumns  = nColumns,
                                          vert      = vert)
 
+registerElement(PanelWidget)
+
 # --------------------------------------------------------------------
 class MapWidget(Element):
     TAG=Element.WIDGET+'MapWidget'
@@ -3046,6 +3490,8 @@
         '''
         return self.getElementsByKey(WidgetMap,'mapName',asdict=asdict)
     
+registerElement(MapWidget)
+
 #
 # EOF
 #
@@ -3258,6 +3704,8 @@
         width    = self.getZone().getWidth()
         return floor(width / self.getDeltaY()  + .5)
 
+registerElement(HexGrid)
+
 # --------------------------------------------------------------------
 class SquareGrid(BaseGrid):
     TAG = Element.BOARD+'SquareGrid'
@@ -3307,6 +3755,8 @@
         width    = self.getZone().getWidth()
         return floor(width / self.getDeltaX()  + .5)
 
+registerElement(SquareGrid)
+
 # --------------------------------------------------------------------
 class HexNumbering(BaseNumbering):
     TAG = Element.BOARD+'mapgrid.HexGridNumbering'
@@ -3368,6 +3818,8 @@
 
         return x,y
 
+registerElement(HexNumbering)
+
 # --------------------------------------------------------------------
 class SquareNumbering(BaseNumbering):
     TAG = Element.BOARD+'mapgrid.SquareGridNumbering'
@@ -3395,6 +3847,7 @@
 
         return x,y
         
+registerElement(SquareNumbering)
     
 # --------------------------------------------------------------------
 class RegionGrid(Element):
@@ -3452,6 +3905,8 @@
 
         return None
         
+registerElement(RegionGrid)
+
 # --------------------------------------------------------------------
 class Region(Element):
     TAG = Element.BOARD+'Region'
@@ -3527,6 +3982,8 @@
             return b.getMap()
         return None
 
+registerElement(Region)
+
 #
 # EOF
 #
@@ -3579,7 +4036,7 @@
         children : list
             List of `Highligter` children (even if `single=True`)
         '''
-        return self.getAllElements(ZonedGridHighliger,single=single)
+        return self.getAllElements(ZonedGridHighlighter,single=single)
     def addZone(self,**kwargs):
         '''Add a `Zone` element to this
 
@@ -3607,6 +4064,8 @@
         '''
         return self.getElementsByKey(Zone,'name',asdict=asdict)
 
+registerElement(ZonedGrid)
+
 # --------------------------------------------------------------------
 class ZonedGridHighlighter(Element):
     TAG=Element.BOARD+'mapgrid.ZonedGridHighlighter'
@@ -3614,8 +4073,98 @@
         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,
@@ -3693,6 +4242,19 @@
             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
 
@@ -3758,6 +4320,17 @@
         if g is not None: return g
 
         return g
+    def getProperties(self):
+        '''Get all `Property` element from this
+
+        Parameters
+        ----------
+        Returns
+        -------
+        children : dict
+            dict of `Property` children
+        '''
+        return getElementsByKey(ZoneProperty,'name')
     
     def getPath(self):
         p  = self['path'].split(';')
@@ -3785,6 +4358,8 @@
     def getYOffset(self):
         return self.getBB()[1]
 
+registerElement(Zone)
+
 #
 # EOF
 #
@@ -3879,6 +4454,8 @@
 
         return ret
 
+registerElement(BoardPicker)
+
 # --------------------------------------------------------------------
 class Setup(Element):
     TAG = 'setup'
@@ -3901,7 +4478,9 @@
         self.addText(txt)
 
     def getPicker(self): return self.getParent(BoardPicker)
-            
+
+registerElement(Setup)
+    
 # --------------------------------------------------------------------
 class Board(Element):
     TAG = Element.PICKER+'Board'
@@ -3982,6 +4561,7 @@
             return int(self['height'])
         return 0
 
+registerElement(Board)
 
 #
 # EOF
@@ -4340,6 +4920,57 @@
         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
 
@@ -4600,6 +5231,47 @@
             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):
@@ -4657,7 +5329,9 @@
 
     def getGame(self):
         return self.getParent(Game)
-        
+
+registerElement(Map)
+
 # --------------------------------------------------------------------
 class WidgetMap(BaseMap):
     TAG = Element.WIDGET+'WidgetMap'
@@ -4669,7 +5343,9 @@
     def getMapWidget(self):
         return self.getParent(MapWidget)
 
+registerElement(WidgetMap)
 
+
 #
 # EOF
 #
@@ -4721,7 +5397,7 @@
         '''
         return self.getElementsById(Chart,'chartName',asdict=asdict)
     
-    
+registerElement(ChartWindow)    
 
 # --------------------------------------------------------------------
 class Chart(Element):
@@ -4735,6 +5411,8 @@
                                    description = description,
                                    fileName    = fileName)
 
+registerElement(Chart)
+
 #
 # EOF
 #
@@ -4766,7 +5444,7 @@
         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:
@@ -5040,6 +5718,15 @@
             Identifier
 
         '''
+        last = traits[-1]
+        # A little hackish to use the name of the class, but needed
+        # because of imports into patch scripts.
+        if not isinstance(last,BasicTrait) and \
+           not last.__class__.__name__.endswith('BasicTrait'):
+            from sys import stderr
+            print(f'Warning - last trait NOT a BasicTrait, but a {type(last)}',
+                  file=stderr)
+            
         types = []
         states = []
         for trait in traits:
@@ -5165,7 +5852,9 @@
         if parent is not None:
             parent.remove(self)
 
-    
+
+registerElement(DummyWithTraits)
+
 # --------------------------------------------------------------------
 class PieceSlot(WithTraits):
     TAG = Element.WIDGET+'PieceSlot'
@@ -5226,6 +5915,7 @@
         piece.setTraits(*traits)
         return piece
         
+registerElement(PieceSlot)
 
 # --------------------------------------------------------------------
 class Prototype(WithTraits):
@@ -5255,6 +5945,8 @@
                                        name        = name,
                                        description = description)
     
+registerElement(Prototype)
+
 #
 # EOF
 #
@@ -5262,10 +5954,11 @@
 # From traits/dynamicproperty.py
 
 # --------------------------------------------------------------------
-# Base class for property (piece or global) change traits.  Encodes
-# constraints and commands.
+# 
+# 
 class ChangePropertyTrait(Trait):
     DIRECT = 'P'
+    INCREMENT = 'I'
     def __init__(self,
                  *commands,
                  numeric     = False,
@@ -5272,6 +5965,10 @@
                  min         = 0,
                  max         = 100,
                  wrap        = False):
+        '''Base class for property (piece or global) change traits.
+        
+           Encodes constraints and commands.
+        '''
         # assert name is not None and len(name) > 0, \
         #     'No name specified for ChangePropertyTriat'
         super(ChangePropertyTrait,self).__init__()
@@ -5293,8 +5990,11 @@
             # print(cmd)
             com = cmd[0] + ':' + cmd[1].replace(',',r'\,') + ':' + cmd[2]
             if cmd[2] == self.DIRECT:
-                com += f'\,'+cmd[3].replace(':',r'\:')
+                com += f'\,'+cmd[3].replace(',',r'\\,').replace(':',r'\:')
+            elif cmd[2] == self.INCREMENT:
+                com += f'\,'+cmd[3].replace(',',r'\\,').replace(':',r'\:')
             cmds.append(com)
+        # print(cmds)
         return ','.join(cmds)
 
     def decodeCommands(self,commands):
@@ -5305,6 +6005,8 @@
             # 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
@@ -5840,7 +6542,7 @@
                  canStack    = False,
                  ignoreGrid  = False,
                  description = ''):
-
+        '''No stacking trait'''
         selectionOptions = (select +
                             (self.IGNORE_GRID if ignoreGrid else '') +
                             bandSelect)
@@ -7319,6 +8021,7 @@
         '''
         return self.getAllElements(AtStart,single)
 
+registerElement(Game)
 
 # --------------------------------------------------------------------
 class BasicCommandEncoder(GameElement):
@@ -7326,6 +8029,8 @@
     def __init__(self,doc,node=None):
         super(BasicCommandEncoder,self).__init__(doc,self.TAG,node=node)
 
+registerElement(BasicCommandEncoder)
+
 #
 # EOF
 #
@@ -8446,7 +9151,8 @@
                  vassalVersion = '3.6.7',
                  nonato        = False,
                  nochit        = False,
-                 counterScale  = 1):
+                 counterScale  = 1,
+                 resolution    = 150):
         '''Exports a PDF and associated JSON files to a VASSAL module.
 
         Parameters
@@ -8473,6 +9179,8 @@
             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
@@ -8487,6 +9195,7 @@
         self._vassalVersion   = vassalVersion
         self._nonato          = nonato
         self._nochit          = nochit
+        self._resolution      = resolution
         self._counterScale    = counterScale
         self._battleMark      = 'wgBattleMarker'
         self._battleMarks     = []
@@ -8572,6 +9281,7 @@
             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}')
         
     def setup(self):
@@ -8704,6 +9414,7 @@
         args = ['pdftocairo',
                 '-transp',
                 '-singlefile',
+                '-r', str(self._resolution),
                 '-f', str(page),
                 '-l', str(page),
                 '-png' ]
@@ -8772,9 +9483,9 @@
         imgsinfo = self.getImagesInfo()
 
         if len(imgsinfo) - 1 != docinfo['Pages']:
-            raise RuntimeError(f'Number of pages in {pdfname} '
-                               f'(doc["Pages"]) not matched in JSON '
-                               f'{self._infoname} -> {len(imagesinfo)}')
+            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:
@@ -9126,10 +9837,16 @@
                                                     'property': 'Phase',
                                                     'names': self._sides } })
             turns.addHotkey(hotkey = self._clearMoved+'Phase',
-                            name   = 'Clear moved markers')
+                            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')
+                                name   = 'Clear battle markers',
+                                reportFormat = (f'{{{self._verbose}?('
+                                                f'"`Clear all battle markers, "+'
+                                                f'""):""}}'))
 
             self._dice   = self._categories\
                                .get('die-roll',{})
@@ -9153,7 +9870,9 @@
                                         # f'+" <img src=\'{die}-"+result1'
                                         # f'+".png\' width=24 height=24>"'
                                         f'}}'),
-                        resultWindow = True);
+                        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']
@@ -10186,13 +10905,14 @@
 
     # ----------------------------------------------------------------
     def factionTraits(self,faction):
-        traits = [ReportTrait(self._eliminateKey,
-                              self._restoreKey,
-                              self._trailKey),
+        offX =  36 * self._counterScale * self._resolution/150
+        offY = -38 * self._counterScale * self._resolution/150
+        traits = [#ReportTrait(self._eliminateKey,
+                  #            self._restoreKey,
+                  #            self._trailKey),
                   TrailTrait(),
                   RotateTrait(),
-                  MovedTrait(xoff = int( 36 * self._counterScale),
-                             yoff = int(-38 * self._counterScale)),
+                  MovedTrait(xoff = int(offX),yoff = int(offY)),
                   DeleteTrait(),
                   SendtoTrait(mapName     = 'DeadMap',
                               boardName   = f'{faction} pool',
@@ -11308,7 +12028,10 @@
                             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,
@@ -11499,7 +12222,7 @@
     def addDie(self):
         '''Add a `Die` element to the module
         '''
-        if self._dice is not None:
+        if self._dice is not None and len(self._dice) > 0:
             return
         self._game.addDiceButton(name       = '1d6',
                                  hotkey     = self._diceKey)
@@ -11568,6 +12291,9 @@
     ap.add_argument('-S','--counter-scale',
                     type=float, default=1,
                     help='Scale counters by factor')
+    ap.add_argument('-R','--resolution',
+                    type=int, default=150,
+                    help='Resolution of images')
 
 
     args = ap.parse_args()
@@ -11600,6 +12326,7 @@
                                  vassalVersion = args.vassal_version,
                                  nonato        = args.no_nato_prototypes,
                                  nochit        = args.no_chit_information,
+                                 resolution    = args.resolution,
                                  counterScale  = args.counter_scale)
         exporter.run()
     except Exception as e:



More information about the tex-live-commits mailing list.