pywargame 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pywargame/__init__.py +2 -0
- pywargame/common/__init__.py +3 -0
- pywargame/common/collector.py +87 -0
- pywargame/common/dicedraw.py +363 -0
- pywargame/common/drawdice.py +40 -0
- pywargame/common/singleton.py +22 -0
- pywargame/common/test.py +25 -0
- pywargame/common/verbose.py +59 -0
- pywargame/common/verboseguard.py +53 -0
- pywargame/cyberboard/__init__.py +18 -0
- pywargame/cyberboard/archive.py +283 -0
- pywargame/cyberboard/base.py +63 -0
- pywargame/cyberboard/board.py +462 -0
- pywargame/cyberboard/cell.py +200 -0
- pywargame/cyberboard/collect.py +49 -0
- pywargame/cyberboard/collectgbx0pwd.py +30 -0
- pywargame/cyberboard/collectgbxext.py +30 -0
- pywargame/cyberboard/collectgsnexp.py +32 -0
- pywargame/cyberboard/collectgsnext.py +30 -0
- pywargame/cyberboard/draw.py +396 -0
- pywargame/cyberboard/exporter.py +1132 -0
- pywargame/cyberboard/extractor.py +240 -0
- pywargame/cyberboard/features.py +17 -0
- pywargame/cyberboard/gamebox.py +81 -0
- pywargame/cyberboard/gbxexp.py +76 -0
- pywargame/cyberboard/gbxext.py +64 -0
- pywargame/cyberboard/gsnexp.py +147 -0
- pywargame/cyberboard/gsnext.py +59 -0
- pywargame/cyberboard/head.py +111 -0
- pywargame/cyberboard/image.py +76 -0
- pywargame/cyberboard/main.py +47 -0
- pywargame/cyberboard/mark.py +102 -0
- pywargame/cyberboard/palette.py +36 -0
- pywargame/cyberboard/piece.py +169 -0
- pywargame/cyberboard/player.py +36 -0
- pywargame/cyberboard/scenario.py +115 -0
- pywargame/cyberboard/testgrid.py +156 -0
- pywargame/cyberboard/tile.py +121 -0
- pywargame/cyberboard/tray.py +68 -0
- pywargame/cyberboard/windows.py +41 -0
- pywargame/cyberboard/zeropwd.py +45 -0
- pywargame/cyberboard.py +2728 -0
- pywargame/gbx0pwd.py +2776 -0
- pywargame/gbxextract.py +2795 -0
- pywargame/gsnexport.py +16499 -0
- pywargame/gsnextract.py +2790 -0
- pywargame/latex/__init__.py +2 -0
- pywargame/latex/collect.py +34 -0
- pywargame/latex/latexexporter.py +4010 -0
- pywargame/latex/main.py +184 -0
- pywargame/vassal/__init__.py +66 -0
- pywargame/vassal/base.py +139 -0
- pywargame/vassal/board.py +243 -0
- pywargame/vassal/buildfile.py +60 -0
- pywargame/vassal/chart.py +79 -0
- pywargame/vassal/chessclock.py +197 -0
- pywargame/vassal/collect.py +98 -0
- pywargame/vassal/collectpatch.py +28 -0
- pywargame/vassal/command.py +21 -0
- pywargame/vassal/documentation.py +322 -0
- pywargame/vassal/dumpcollect.py +28 -0
- pywargame/vassal/dumpvsav.py +28 -0
- pywargame/vassal/element.py +439 -0
- pywargame/vassal/exporter.py +89 -0
- pywargame/vassal/extension.py +101 -0
- pywargame/vassal/folder.py +103 -0
- pywargame/vassal/game.py +940 -0
- pywargame/vassal/gameelements.py +1091 -0
- pywargame/vassal/globalkey.py +127 -0
- pywargame/vassal/globalproperty.py +433 -0
- pywargame/vassal/grid.py +573 -0
- pywargame/vassal/map.py +1061 -0
- pywargame/vassal/mapelements.py +1020 -0
- pywargame/vassal/merge.py +57 -0
- pywargame/vassal/merger.py +460 -0
- pywargame/vassal/moduledata.py +275 -0
- pywargame/vassal/mrgcollect.py +31 -0
- pywargame/vassal/patch.py +44 -0
- pywargame/vassal/patchcollect.py +28 -0
- pywargame/vassal/player.py +83 -0
- pywargame/vassal/save.py +495 -0
- pywargame/vassal/skel.py +380 -0
- pywargame/vassal/trait.py +224 -0
- pywargame/vassal/traits/__init__.py +36 -0
- pywargame/vassal/traits/area.py +50 -0
- pywargame/vassal/traits/basic.py +35 -0
- pywargame/vassal/traits/calculatedproperty.py +22 -0
- pywargame/vassal/traits/cargo.py +29 -0
- pywargame/vassal/traits/click.py +41 -0
- pywargame/vassal/traits/clone.py +28 -0
- pywargame/vassal/traits/delete.py +24 -0
- pywargame/vassal/traits/deselect.py +32 -0
- pywargame/vassal/traits/dynamicproperty.py +112 -0
- pywargame/vassal/traits/globalcommand.py +55 -0
- pywargame/vassal/traits/globalhotkey.py +26 -0
- pywargame/vassal/traits/globalproperty.py +54 -0
- pywargame/vassal/traits/hide.py +67 -0
- pywargame/vassal/traits/label.py +76 -0
- pywargame/vassal/traits/layer.py +105 -0
- pywargame/vassal/traits/mark.py +20 -0
- pywargame/vassal/traits/mask.py +85 -0
- pywargame/vassal/traits/mat.py +26 -0
- pywargame/vassal/traits/moved.py +35 -0
- pywargame/vassal/traits/movefixed.py +51 -0
- pywargame/vassal/traits/nonrect.py +95 -0
- pywargame/vassal/traits/nostack.py +55 -0
- pywargame/vassal/traits/place.py +104 -0
- pywargame/vassal/traits/prototype.py +20 -0
- pywargame/vassal/traits/report.py +34 -0
- pywargame/vassal/traits/restrictaccess.py +28 -0
- pywargame/vassal/traits/restrictcommand.py +32 -0
- pywargame/vassal/traits/return.py +40 -0
- pywargame/vassal/traits/rotate.py +62 -0
- pywargame/vassal/traits/sendto.py +59 -0
- pywargame/vassal/traits/sheet.py +129 -0
- pywargame/vassal/traits/skel.py +9 -0
- pywargame/vassal/traits/stack.py +28 -0
- pywargame/vassal/traits/submenu.py +27 -0
- pywargame/vassal/traits/trail.py +61 -0
- pywargame/vassal/traits/trigger.py +72 -0
- pywargame/vassal/turn.py +272 -0
- pywargame/vassal/upgrade.py +191 -0
- pywargame/vassal/vmod.py +323 -0
- pywargame/vassal/vsav.py +100 -0
- pywargame/vassal/widget.py +358 -0
- pywargame/vassal/withtraits.py +634 -0
- pywargame/vassal/xml.py +4 -0
- pywargame/vassal/zone.py +399 -0
- pywargame/vassal.py +12500 -0
- pywargame/vmodpatch.py +12548 -0
- pywargame/vsavdump.py +12533 -0
- pywargame/vslmerge.py +13015 -0
- pywargame/wgexport.py +16689 -0
- pywargame/ztexport.py +14351 -0
- pywargame/zuntzu/__init__.py +5 -0
- pywargame/zuntzu/base.py +82 -0
- pywargame/zuntzu/collect.py +38 -0
- pywargame/zuntzu/countersheet.py +250 -0
- pywargame/zuntzu/dicehand.py +48 -0
- pywargame/zuntzu/exporter.py +936 -0
- pywargame/zuntzu/gamebox.py +154 -0
- pywargame/zuntzu/map.py +36 -0
- pywargame/zuntzu/piece.py +37 -0
- pywargame/zuntzu/scenario.py +208 -0
- pywargame/zuntzu/ztexp.py +115 -0
- pywargame-0.3.1.dist-info/METADATA +353 -0
- pywargame-0.3.1.dist-info/RECORD +150 -0
- pywargame-0.3.1.dist-info/WHEEL +5 -0
- pywargame-0.3.1.dist-info/licenses/LICENSE +5 -0
- pywargame-0.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1132 @@
|
|
1
|
+
## BEGIN_IMPORTS
|
2
|
+
from common import VerboseGuard, Verbose
|
3
|
+
from vassal.buildfile import BuildFile
|
4
|
+
from vassal.documentation import Documentation
|
5
|
+
from vassal.traits import *
|
6
|
+
from vassal.base import *
|
7
|
+
from vassal.moduledata import ModuleData
|
8
|
+
from vassal.exporter import Exporter
|
9
|
+
## END_IMPORTS
|
10
|
+
|
11
|
+
class CbExporter(Exporter):
|
12
|
+
def __init__(self,
|
13
|
+
d,
|
14
|
+
title = None,
|
15
|
+
version = None,
|
16
|
+
rules = None,
|
17
|
+
tutorial = None,
|
18
|
+
visible = False,
|
19
|
+
main_board = None,
|
20
|
+
map_regex = None,
|
21
|
+
vassalVersion = '3.6',
|
22
|
+
do_scenario = False):
|
23
|
+
self._d = d
|
24
|
+
self._title = title
|
25
|
+
self._version = version
|
26
|
+
self._rules = rules
|
27
|
+
self._tutorial = tutorial
|
28
|
+
self._visible = visible
|
29
|
+
self._vassalVersion = vassalVersion
|
30
|
+
self._flipped = False
|
31
|
+
self._mainBoard = main_board
|
32
|
+
self._mapRegex = map_regex
|
33
|
+
self._do_scenario = do_scenario
|
34
|
+
self._description = d['description']
|
35
|
+
self._placements = {}
|
36
|
+
|
37
|
+
# ----------------------------------------------------------------
|
38
|
+
def createModuleData(self):
|
39
|
+
'''Create the `moduleData` file in the module
|
40
|
+
'''
|
41
|
+
with VerboseGuard(f'Creating module data'):
|
42
|
+
self._moduleData = ModuleData()
|
43
|
+
data = self._moduleData.addData()
|
44
|
+
data.addVersion (version = self._version)
|
45
|
+
data.addVASSALVersion(version = self._vassalVersion)
|
46
|
+
data.addName (name = self._title)
|
47
|
+
data.addDescription (description = self._description)
|
48
|
+
data.addDateSaved ()
|
49
|
+
|
50
|
+
# ----------------------------------------------------------------
|
51
|
+
def addMoreDocumentation(self,doc):
|
52
|
+
pass
|
53
|
+
|
54
|
+
# ----------------------------------------------------------------
|
55
|
+
def addDocumentation(self):
|
56
|
+
'''Add documentation to the module. This includes rules,
|
57
|
+
key-bindings, and about elements.
|
58
|
+
'''
|
59
|
+
with VerboseGuard('Adding documentation') as v:
|
60
|
+
doc = self._game.addDocumentation()
|
61
|
+
if self._rules is not None:
|
62
|
+
self._vmod.addExternalFile(self._rules,'rules.pdf')
|
63
|
+
doc.addBrowserPDFFile(title = 'Show rules',
|
64
|
+
pdfFile = 'rules.pdf')
|
65
|
+
|
66
|
+
if self._tutorial is not None:
|
67
|
+
self._vmod.addExternalFile(self._tutorial,'tutorial.vlog')
|
68
|
+
doc.addTutorial(name = 'Tutorial',
|
69
|
+
logfile = 'tutorial.vlog',
|
70
|
+
launchOnStartup = True)
|
71
|
+
|
72
|
+
keys = [
|
73
|
+
['Alt-A', '-', 'Show the charts panel'],
|
74
|
+
['Alt-B', '-', 'Show the OOBs'],
|
75
|
+
['Alt-C', '-', 'Show the counters panel'],
|
76
|
+
['Alt-E', '-', 'Show the eliminated units'],
|
77
|
+
['Alt-I', '-', 'Show/refresh inventory window'],
|
78
|
+
['Alt-M', '-', 'Show map'],
|
79
|
+
['Alt-T', '-', 'Increase turn track'],
|
80
|
+
['Alt-Shift-T', '-', 'Decrease turn track'],
|
81
|
+
['Alt-6', '-', 'Roll the dice'],
|
82
|
+
['Ctrl-D', 'Board,Counter','Delete counters'],
|
83
|
+
['Ctrl-E', 'Board,Counter','Eliminate counters'],
|
84
|
+
['Ctrl-F', 'Board,Counter','Flip counters'],
|
85
|
+
['Ctrl-M', 'Board,Counter','Toggle "moved" markers'],
|
86
|
+
['Ctrl-O', 'Board', 'Hide/show counters'],
|
87
|
+
['Ctrl-R', 'Board,Counter','Restore unit'],
|
88
|
+
['Ctrl-T', 'Board,Counter','Toggle move trail'],
|
89
|
+
['Ctrl-+', 'Board', 'Zoom in'],
|
90
|
+
['Ctrl--', 'Board', 'Zoom out'],
|
91
|
+
['Ctrl-=', 'Board', 'Select zoom'],
|
92
|
+
['Ctrl-Shift-O', 'Board','Show overview map'],
|
93
|
+
['←,→,↑↓','Board',
|
94
|
+
'Scroll board left, right, up, down (slowly)'],
|
95
|
+
['PnUp,PnDn','Board', 'Scroll board up/down (fast)'],
|
96
|
+
['Ctrl-PnUp,Ctrl-PnDn','Board',
|
97
|
+
'Scroll board left/right (fast)'],
|
98
|
+
['Mouse-scroll up/down', 'Board',
|
99
|
+
'Scroll board up//down'],
|
100
|
+
['Shift-Mouse-scroll up/down','Board',
|
101
|
+
'Scroll board right/leftown'],
|
102
|
+
['Ctrl-Mouse-scroll up/down','Board','Zoom board out/in'],
|
103
|
+
['Mouse-2', 'Board', 'Centre on mouse']]
|
104
|
+
|
105
|
+
|
106
|
+
self._vmod.addFile('help/keys.html',
|
107
|
+
Documentation.createKeyHelp(
|
108
|
+
keys,
|
109
|
+
title=self._d['title'],
|
110
|
+
version=self._d['version']))
|
111
|
+
doc.addHelpFile(title='Key bindings',fileName='help/keys.html')
|
112
|
+
|
113
|
+
self.addMoreDocumentation(doc)
|
114
|
+
|
115
|
+
# ----------------------------------------------------------------
|
116
|
+
def saveImages(self,d,vmod):
|
117
|
+
for serial,board in d['boards'].items():
|
118
|
+
self.saveSVG(board,vmod)
|
119
|
+
|
120
|
+
for piece in d['pieces']['map'].values():
|
121
|
+
if 'back' in piece: self._flipped = True
|
122
|
+
|
123
|
+
for which in ['front','back']:
|
124
|
+
if which not in piece:
|
125
|
+
continue
|
126
|
+
|
127
|
+
side = piece[which]
|
128
|
+
self.savePNG(side,vmod)
|
129
|
+
|
130
|
+
for mark in d['marks']['map'].values():
|
131
|
+
self.savePNG(mark,vmod)
|
132
|
+
|
133
|
+
# ----------------------------------------------------------------
|
134
|
+
def saveSVG(self,d,vmod):
|
135
|
+
|
136
|
+
with VerboseGuard(f'Saving rendered SVG image {d["filename"]}') as vg:
|
137
|
+
from wand.image import Image as WandImage
|
138
|
+
from pathlib import Path
|
139
|
+
|
140
|
+
path = Path(d['filename'])
|
141
|
+
filename = path.with_suffix('.png')
|
142
|
+
image = d['image']
|
143
|
+
size = d['size']
|
144
|
+
d['filename'] = filename
|
145
|
+
svg = image.encode()
|
146
|
+
svgout = path.with_suffix('.svg')
|
147
|
+
pngout = path.with_suffix('.png')
|
148
|
+
vmod.addFile('images/'+str(svgout),svg)
|
149
|
+
vg(f'Creating WandImage')
|
150
|
+
|
151
|
+
# We write the SVG to file, because Wand seems to have
|
152
|
+
# some issues reading from a bytes. The same applies for
|
153
|
+
# the command line
|
154
|
+
if vg:
|
155
|
+
vg(f'=== Write the SVG image to {svgout} ===')
|
156
|
+
with open(svgout,'w') as out:
|
157
|
+
out.write(image)
|
158
|
+
|
159
|
+
with WandImage() as wimage:
|
160
|
+
# Reading the bytes directly fails. It doesn't work
|
161
|
+
# from reading BytesIO stream either.
|
162
|
+
vg(f'Reading in SVG code')
|
163
|
+
wimage.read(filename=svgout,
|
164
|
+
# blob=image,#.encode(),
|
165
|
+
width=size[0],
|
166
|
+
height=size[1],
|
167
|
+
format='svg')
|
168
|
+
|
169
|
+
# Make a PNG
|
170
|
+
vg(f'Making blob')
|
171
|
+
png = wimage.make_blob('png')
|
172
|
+
|
173
|
+
# Write to the VMOD
|
174
|
+
vg(f'Write to VMOD')
|
175
|
+
vmod.addFile('images/'+str(pngout),png)
|
176
|
+
vg(f'Done writing SVG as PNG to VMOD')
|
177
|
+
|
178
|
+
# ----------------------------------------------------------------
|
179
|
+
def savePNG(self,d,vmod):
|
180
|
+
with VerboseGuard(f'Saving PNG image {d["filename"]}'):
|
181
|
+
from io import BytesIO
|
182
|
+
|
183
|
+
filename = d['filename']
|
184
|
+
img = d['image']
|
185
|
+
d['size'] = img.size
|
186
|
+
stream = BytesIO()
|
187
|
+
img.save(stream,format='PNG')
|
188
|
+
vmod.addFile('images/'+filename,stream.getvalue())
|
189
|
+
|
190
|
+
# ----------------------------------------------------------------
|
191
|
+
def addPrototypes(self,sides):
|
192
|
+
with VerboseGuard('Adding prototypes') as v:
|
193
|
+
protos = self._game.addPrototypes()
|
194
|
+
traits = [ReportTrait(key('E'),key('R'),key('M')),
|
195
|
+
TrailTrait(),
|
196
|
+
RotateTrait(),
|
197
|
+
DeleteTrait(),
|
198
|
+
MarkTrait(name='PieceLayer',value='Pieces'),
|
199
|
+
SendtoTrait(mapName = 'DeadMap',
|
200
|
+
boardName = f'Pool',
|
201
|
+
name = 'Eliminate',
|
202
|
+
key = key('E'),
|
203
|
+
restoreName = 'Restore',
|
204
|
+
restoreKey = key('R'),
|
205
|
+
description = 'Eliminate unit'),
|
206
|
+
BasicTrait()]
|
207
|
+
|
208
|
+
protos.addPrototype(name = f'Piece prototype',
|
209
|
+
description = f'Prototype for pieces',
|
210
|
+
traits = traits)
|
211
|
+
traits = [DeleteTrait(),
|
212
|
+
RotateTrait(),
|
213
|
+
MarkTrait(name='PieceLayer',value='Markers'),
|
214
|
+
BasicTrait()]
|
215
|
+
protos.addPrototype(name = f'Marker prototype',
|
216
|
+
description = f'Prototype for markers',
|
217
|
+
traits = traits)
|
218
|
+
|
219
|
+
for side in sides:
|
220
|
+
v(f'Making prototype trait for faction: "{side}"')
|
221
|
+
escside = side.replace('/','\\/')
|
222
|
+
traits = [MarkTrait(name='Faction',value=escside),
|
223
|
+
BasicTrait()]
|
224
|
+
protos.addPrototype(name = f'{side} prototype',
|
225
|
+
description = f'Prototype for {side}',
|
226
|
+
traits = traits)
|
227
|
+
|
228
|
+
# ----------------------------------------------------------------
|
229
|
+
def sanitise(self,txt):
|
230
|
+
for special in ['/',',',';','\\','\n','\t','\r']:
|
231
|
+
txt = txt.replace(special,' ')
|
232
|
+
return txt
|
233
|
+
|
234
|
+
# ----------------------------------------------------------------
|
235
|
+
def getToAdd(self,master,mapping,children):
|
236
|
+
toAdd = {}
|
237
|
+
for iden in children:
|
238
|
+
if iden not in master:
|
239
|
+
print(f'ID={iden} not found')
|
240
|
+
continue
|
241
|
+
if iden in mapping:
|
242
|
+
# Already added
|
243
|
+
continue
|
244
|
+
|
245
|
+
toAdd[iden] = master[iden]
|
246
|
+
|
247
|
+
return toAdd
|
248
|
+
|
249
|
+
# ----------------------------------------------------------------
|
250
|
+
def addPieceContainer(self,container,name):
|
251
|
+
#panel = container.addPanel(entryName = name,
|
252
|
+
# fixed = False,
|
253
|
+
# vert = True)
|
254
|
+
#return panel
|
255
|
+
panel = container.addPanel(entryName = name, fixed = False)
|
256
|
+
plist = panel.addList(entryName = f'{name}',
|
257
|
+
width=300,
|
258
|
+
height=300,
|
259
|
+
divider=150)
|
260
|
+
return plist
|
261
|
+
|
262
|
+
# ----------------------------------------------------------------
|
263
|
+
def addPieces(self,container,pieces,*pieceSets):
|
264
|
+
from pathlib import Path
|
265
|
+
with VerboseGuard('Adding pieces') as v:
|
266
|
+
# Store a map from pieceID to PieceSlot
|
267
|
+
self._pieceMap = {}
|
268
|
+
for sets in pieceSets:
|
269
|
+
for set in sets:
|
270
|
+
toAdd = self.getToAdd(pieces,self._pieceMap,set['pieces'])
|
271
|
+
if len(toAdd) <= 0:
|
272
|
+
# Nothing to add from this set
|
273
|
+
continue
|
274
|
+
|
275
|
+
name = set.get('name',set.get('description',None))
|
276
|
+
if name is None:
|
277
|
+
print(f'No name for set: {set}')
|
278
|
+
continue
|
279
|
+
|
280
|
+
plist = self.addPieceContainer(container, name)
|
281
|
+
|
282
|
+
for pieceID, piece in toAdd.items():
|
283
|
+
v(f'Make piece with faction: "{name}"')
|
284
|
+
escname = name.replace('/','\\/')
|
285
|
+
front = piece['front']['filename']
|
286
|
+
size = piece['front']['size']
|
287
|
+
back = piece.get('back',{}).get('filename',None)
|
288
|
+
traits = [MovedTrait(xoff=size[0]//2,
|
289
|
+
yoff=-size[1]//2),
|
290
|
+
MarkTrait(name='Faction',value=escname),
|
291
|
+
MarkTrait(name='PieceLayer',value='Pieces'),
|
292
|
+
PrototypeTrait(name='Piece prototype')]
|
293
|
+
|
294
|
+
if back is not None:
|
295
|
+
traits.extend([
|
296
|
+
LayerTrait(images = [front, back],
|
297
|
+
newNames = ['','Reduced +'],
|
298
|
+
activateName = '',
|
299
|
+
decreaseName = '',
|
300
|
+
increaseName = 'Flip',
|
301
|
+
increaseKey = key('F'),
|
302
|
+
decreaseKey = '',
|
303
|
+
name = 'Step'),
|
304
|
+
ReportTrait(key('F'))])
|
305
|
+
if 'description' in piece:
|
306
|
+
desc = self.sanitise(piece['description'])
|
307
|
+
traits.append(MarkTrait(name='description',
|
308
|
+
value=desc))
|
309
|
+
|
310
|
+
pname = Path(front).stem.replace('_front','')
|
311
|
+
gpid = self._game.nextPieceSlotId()
|
312
|
+
traits.extend([BasicTrait(name = pname,
|
313
|
+
filename = front,
|
314
|
+
gpid = gpid)])
|
315
|
+
|
316
|
+
ps = plist.addPieceSlot(entryName = pname,
|
317
|
+
gpid = gpid,
|
318
|
+
traits = traits,
|
319
|
+
width = size[0],
|
320
|
+
height = size[1])
|
321
|
+
|
322
|
+
# print(f'Piece: {pieceID}')
|
323
|
+
self._pieceMap[pieceID] = ps
|
324
|
+
|
325
|
+
# ----------------------------------------------------------------
|
326
|
+
def addMarks(self,container,marks,*markSets):
|
327
|
+
from pathlib import Path
|
328
|
+
with VerboseGuard('Adding marks') as v:
|
329
|
+
# Store a map from markID to PieceSlot
|
330
|
+
self._markMap = {}
|
331
|
+
for sets in markSets:
|
332
|
+
for set in sets:
|
333
|
+
toAdd = self.getToAdd(marks,self._markMap,set['marks'])
|
334
|
+
if len(toAdd) <= 0:
|
335
|
+
# Nothing to add from this set
|
336
|
+
continue
|
337
|
+
|
338
|
+
name = set.get('name',set.get('description',None))
|
339
|
+
if name is None:
|
340
|
+
print(f'No name for set: {set}')
|
341
|
+
continue
|
342
|
+
|
343
|
+
mlist = self.addPieceContainer(container, name)
|
344
|
+
|
345
|
+
for markID, mark in toAdd.items():
|
346
|
+
file = mark['filename']
|
347
|
+
size = mark['size']
|
348
|
+
traits = [PrototypeTrait(name='Mark prototype'),
|
349
|
+
MarkTrait(name='PieceLayer',
|
350
|
+
value='Markers')]
|
351
|
+
if 'description' in mark:
|
352
|
+
desc = self.sanitise(mark['description'])
|
353
|
+
traits.append(MarkTrait(name='description',
|
354
|
+
value=desc))
|
355
|
+
|
356
|
+
mname = Path(file).stem
|
357
|
+
gpid = self._game.nextPieceSlotId()
|
358
|
+
traits.extend([BasicTrait(name = mname,
|
359
|
+
filename = file,
|
360
|
+
gpid = gpid)])
|
361
|
+
|
362
|
+
ps = mlist.addPieceSlot(entryName = mname,
|
363
|
+
gpid = gpid,
|
364
|
+
traits = traits,
|
365
|
+
width = size[0],
|
366
|
+
height = size[1])
|
367
|
+
|
368
|
+
# print(f'Mark: {markID}')
|
369
|
+
self._markMap[markID] = ps
|
370
|
+
|
371
|
+
# ----------------------------------------------------------------
|
372
|
+
def addBoards(self,boards):
|
373
|
+
with VerboseGuard(f'Adding boards '
|
374
|
+
f'{[n["gboard"]["name"] for n in boards]}') as v:
|
375
|
+
first = True
|
376
|
+
names = []
|
377
|
+
for board in boards:
|
378
|
+
names.append(self.addBoard(first,**board))
|
379
|
+
first = False
|
380
|
+
|
381
|
+
filtered = [n for n in names
|
382
|
+
if n != '' and n != self._mainBoard]
|
383
|
+
v(f'Filtered set of maps: {filtered}')
|
384
|
+
if len(filtered) > 1:
|
385
|
+
self._game.addMenu(description = 'Maps',
|
386
|
+
text = 'Maps',
|
387
|
+
tooltip = 'Show or hide maps',
|
388
|
+
icon = '/images/map.gif',
|
389
|
+
menuItems = filtered)
|
390
|
+
|
391
|
+
# ----------------------------------------------------------------
|
392
|
+
def addMapDefaults(self,map):
|
393
|
+
# map.addHidePiecesButton()
|
394
|
+
# Basics
|
395
|
+
map.addStackMetrics()
|
396
|
+
map.addImageSaver()
|
397
|
+
map.addTextSaver()
|
398
|
+
map.addForwardToChatter()
|
399
|
+
map.addMenuDisplayer()
|
400
|
+
map.addMapCenterer()
|
401
|
+
map.addStackExpander()
|
402
|
+
map.addCounterDetailViewer()
|
403
|
+
map.addPieceMover()
|
404
|
+
map.addKeyBufferer()
|
405
|
+
map.addForwardKeys()
|
406
|
+
map.addScroller()
|
407
|
+
map.addSelectionHighlighters()
|
408
|
+
map.addHighlightLastMoved()
|
409
|
+
map.addZoomer()
|
410
|
+
|
411
|
+
# ----------------------------------------------------------------
|
412
|
+
def addMapLayers(self,map):
|
413
|
+
layerDesc = {'Pieces': {'t': 'Pieces', 'i': ''},
|
414
|
+
'Markers': {'t': 'Markers','i': ''}}
|
415
|
+
layers = map.addLayers(property = 'PieceLayer',
|
416
|
+
layerOrder = layerDesc)
|
417
|
+
for layerTitle, layerData in layerDesc.items():
|
418
|
+
layers.addControl(name = layerTitle,
|
419
|
+
tooltip = f'Toggle display of {layerTitle}',
|
420
|
+
text = f'Toggle {layerData["t"]}',
|
421
|
+
icon = layerData['i'],
|
422
|
+
layers = [layerTitle])
|
423
|
+
layers.addControl(name = 'Show all',
|
424
|
+
tooltip = 'Show all',
|
425
|
+
text = 'Show all',
|
426
|
+
command = LayerControl.ENABLE,
|
427
|
+
layers = list(layerDesc.keys()))
|
428
|
+
map.addMenu(description = 'Toggle layers',
|
429
|
+
text = '',
|
430
|
+
tooltip = 'Toggle display of layers',
|
431
|
+
icon = '/images/inventory.gif',
|
432
|
+
menuItems = ([f'Toggle {ln["t"]}'
|
433
|
+
for ln in layerDesc.values()]
|
434
|
+
+['Show all']))
|
435
|
+
# ----------------------------------------------------------------
|
436
|
+
def addBoard(self,first,gboard,color=0xFF0000,pieces=None,indicators=None):
|
437
|
+
with VerboseGuard(f'Adding board {gboard["name"]}') as v:
|
438
|
+
mapName = gboard['name']
|
439
|
+
map = self._game.addMap(
|
440
|
+
mapName = mapName,
|
441
|
+
buttonName = '' if first else mapName,
|
442
|
+
hotkey = '' if first else key('M',ALT),
|
443
|
+
launch = not first
|
444
|
+
)
|
445
|
+
self.addMapDefaults(map)
|
446
|
+
self.addMapLayers(map)
|
447
|
+
map.addGlobalMap()
|
448
|
+
|
449
|
+
map.addMassKey(name='Eliminate',
|
450
|
+
buttonHotkey = key('E'),
|
451
|
+
hotkey = key('E'),
|
452
|
+
icon = '/icons/16x16/edit-undo.png',
|
453
|
+
tooltip = 'Eliminate selected units')
|
454
|
+
map.addMassKey(name='Delete',
|
455
|
+
buttonHotkey = key('D'),
|
456
|
+
hotkey = key('D'),
|
457
|
+
icon = '/icons/16x16/no.png',
|
458
|
+
tooltip = 'Delete selected units')
|
459
|
+
if self._flipped:
|
460
|
+
map.addMassKey(name='Flip',
|
461
|
+
buttonHotkey = key('F'),
|
462
|
+
hotkey = key('F'),
|
463
|
+
icon = '/images/Undo16.gif',
|
464
|
+
tooltip = 'Flip selected units')
|
465
|
+
|
466
|
+
|
467
|
+
size = gboard['size']
|
468
|
+
picker = map.addBoardPicker()
|
469
|
+
board = picker.addBoard(name = gboard['name'],
|
470
|
+
image = gboard['filename'],
|
471
|
+
width = size[0],
|
472
|
+
height = size[1])
|
473
|
+
zoned = board.addZonedGrid()
|
474
|
+
zoned.addHighlighter()
|
475
|
+
zone = zoned.addZone(name = 'Full',
|
476
|
+
useParentGrid = False,
|
477
|
+
path=(f'{0},{0};' +
|
478
|
+
f'{size[0]},{0};' +
|
479
|
+
f'{size[0]},{size[1]};' +
|
480
|
+
f'{0},{size[1]}'))
|
481
|
+
|
482
|
+
self.addGrid(zone,gboard,color)
|
483
|
+
self.addAtStart(map,pieces)
|
484
|
+
self.addAtStart(map,indicators)
|
485
|
+
|
486
|
+
return mapName
|
487
|
+
|
488
|
+
# ----------------------------------------------------------------
|
489
|
+
def addCharts(self,gcharts):
|
490
|
+
if len(gcharts) <= 0:
|
491
|
+
return
|
492
|
+
|
493
|
+
with VerboseGuard(f'Adding Charts '
|
494
|
+
f'{[n["gboard"]["name"] for n in gcharts]}') as v:
|
495
|
+
charts = \
|
496
|
+
self._game.addChartWindow(name='Charts',
|
497
|
+
hotkey = key('A',ALT),
|
498
|
+
description = 'Charts',
|
499
|
+
text = '',
|
500
|
+
icon = '/images/chart.gif',
|
501
|
+
tooltip = 'Show/hide Charts')
|
502
|
+
tabs = charts.addTabs(entryName='Charts')
|
503
|
+
|
504
|
+
for chart in gcharts:
|
505
|
+
widget = tabs.addMapWidget(entryName=chart['gboard']['name'])
|
506
|
+
self.addChart(widget,**chart)
|
507
|
+
|
508
|
+
# ----------------------------------------------------------------
|
509
|
+
def addChart(self,widget,gboard,color=0xFF0000,pieces=None,indicators=None):
|
510
|
+
with VerboseGuard(f'Adding Chart {gboard["name"]}') as v:
|
511
|
+
map = widget.addWidgetMap(mapName = gboard['name'],
|
512
|
+
markMoved = 'Never',
|
513
|
+
hotkey = '')
|
514
|
+
self.addMapDefaults(map)
|
515
|
+
self.addMapLayers(map)
|
516
|
+
|
517
|
+
size = gboard['size']
|
518
|
+
picker = map.addPicker()
|
519
|
+
board = picker.addBoard(name = gboard['name'],
|
520
|
+
image = gboard['filename'])
|
521
|
+
zoned = board.addZonedGrid()
|
522
|
+
zoned.addHighlighter()
|
523
|
+
zone = zoned.addZone(name = 'Full',
|
524
|
+
useParentGrid = False,
|
525
|
+
path=(f'{0},{0};' +
|
526
|
+
f'{size[0]},{0};' +
|
527
|
+
f'{size[0]},{size[1]};' +
|
528
|
+
f'{0},{size[1]}'))
|
529
|
+
|
530
|
+
self.addGrid(zone,gboard,color)
|
531
|
+
self.addAtStart(map,pieces)
|
532
|
+
self.addAtStart(map,indicators)
|
533
|
+
|
534
|
+
# ----------------------------------------------------------------
|
535
|
+
def getNumParam(self,col,gboard,geom):
|
536
|
+
rcol = (col >> 16) & 0xFF
|
537
|
+
gcol = (col >> 8) & 0xFF
|
538
|
+
bcol = (col >> 0) & 0XFF
|
539
|
+
fid = gboard['numbering']['order'].lower()
|
540
|
+
sid = 'h' if fid=='v' else 'v'
|
541
|
+
side = 'sideways' in geom['shape']
|
542
|
+
hex = 'hexagon' in geom['shape']
|
543
|
+
d = {'first': fid.upper(),
|
544
|
+
'color': rgb(rcol,gcol,bcol),
|
545
|
+
'visible': self._visible,
|
546
|
+
'vOff': gboard['rows']['offset']+1,
|
547
|
+
'vDescend': gboard['rows']['inverted'],
|
548
|
+
'hOff': gboard['columns']['offset']+1, # CB start at 1
|
549
|
+
'vOff': gboard['columns']['inverted']+1, # VSL start at 0
|
550
|
+
'hLeading': 1 if gboard['numbering']['padding'] else 0,
|
551
|
+
'vLeading': 1 if gboard['numbering']['padding'] else 0,
|
552
|
+
'stagger': geom['stagger'] == 'in',
|
553
|
+
f'{fid}Type': gboard['numbering']['first'],
|
554
|
+
f'{sid}Type': 'N'
|
555
|
+
}
|
556
|
+
# Coord 0 -> A in VSL, Coord 1 -> A in CB
|
557
|
+
d[f'{fid}Off'] += 0 if gboard['numbering']['first'] != 'A' else -1
|
558
|
+
return d
|
559
|
+
|
560
|
+
# ----------------------------------------------------------------
|
561
|
+
def addGrid(self,zone,gboard,col=0x000000):
|
562
|
+
geom = gboard.get('cells',{}).get('geometry',None)
|
563
|
+
if geom is None:
|
564
|
+
return
|
565
|
+
|
566
|
+
if geom['shape'] == 'rectangle':
|
567
|
+
self.addRegularGrid(zone,gboard,geom,col)
|
568
|
+
elif 'brick' in geom['shape']:
|
569
|
+
self.addRegionGrid(zone,gboard,geom,col)
|
570
|
+
elif 'hexagon' in geom['shape']:
|
571
|
+
self.addHexGrid(zone,gboard,geom,col)
|
572
|
+
|
573
|
+
|
574
|
+
|
575
|
+
# ----------------------------------------------------------------
|
576
|
+
def addHexGrid(self,zone,gboard,geom,col=0x000000):
|
577
|
+
from math import sin, pi
|
578
|
+
with VerboseGuard(f'Adding hex grid') as v:
|
579
|
+
size = geom['size']
|
580
|
+
side = 'sideways' in geom['shape']
|
581
|
+
npar = self.getNumParam(col,gboard,geom)
|
582
|
+
dx = int(size[0] * sin(pi/3) ** 2)
|
583
|
+
dy = size[1]
|
584
|
+
x0 = size[0]//2
|
585
|
+
y0 = size[1]//2 + (0 if geom['stagger']=='out' else dy//2)
|
586
|
+
if side:
|
587
|
+
dy = size[0]
|
588
|
+
dx = dy * sin(pi/3)
|
589
|
+
x0, y0 = y0, x0
|
590
|
+
npar['first'] = 'V' if npar['first'] == 'H' else 'H'
|
591
|
+
|
592
|
+
v(f'Size = {size}, side = {side} dx={dx} dy={dy}')
|
593
|
+
grid = zone.addHexGrid(dx = dx,
|
594
|
+
dy = dy,
|
595
|
+
x0 = x0,
|
596
|
+
y0 = y0,
|
597
|
+
sideways = side,
|
598
|
+
visible = self._visible,
|
599
|
+
color = npar['color'],
|
600
|
+
edgesLegal = True,
|
601
|
+
cornersLegal = True,
|
602
|
+
snapTo = True
|
603
|
+
#gboard['snap']['enable']
|
604
|
+
)
|
605
|
+
grid.addNumbering(**npar)
|
606
|
+
|
607
|
+
# ----------------------------------------------------------------
|
608
|
+
def addRegularGrid(self,zone,gboard,geom,col=0x000000):
|
609
|
+
size = geom['size']
|
610
|
+
npar = self.getNumParam(col,gboard,geom)
|
611
|
+
grid = zone.addSquareGrid(dx = size[0],
|
612
|
+
dy = size[1],
|
613
|
+
x0 = size[0]//2,
|
614
|
+
y0 = size[1]//2,
|
615
|
+
visible = self._visible,
|
616
|
+
color = npar['color'],
|
617
|
+
snapTo = gboard['snap']['enable'])
|
618
|
+
grid.addNumbering(**npar)
|
619
|
+
|
620
|
+
|
621
|
+
|
622
|
+
# ----------------------------------------------------------------
|
623
|
+
def addRegionGrid(self,zone,gboard,geom):
|
624
|
+
grid = zone.addRegionGrid(snapto = True,
|
625
|
+
visible = self._visible)
|
626
|
+
|
627
|
+
for row in gboard['cells']['list']:
|
628
|
+
for cell in row:
|
629
|
+
name = f'r{cell["row"]}_{cell["column"]}'
|
630
|
+
pixel = cell['pixel']
|
631
|
+
grid.addRegion(name = name,
|
632
|
+
originx = pixel[0],
|
633
|
+
originy = pixel[1],
|
634
|
+
alsoPiece = False)
|
635
|
+
# ----------------------------------------------------------------
|
636
|
+
def addAtStart(self,map,pieces):
|
637
|
+
with VerboseGuard(f'Adding at-start to {map["mapName"]}') as g:
|
638
|
+
toAdd = {}
|
639
|
+
mapName = map['mapName']
|
640
|
+
|
641
|
+
for piece in pieces:
|
642
|
+
pmap = None
|
643
|
+
if piece['type'] == 'Piece':
|
644
|
+
pmap = self._pieceMap
|
645
|
+
elif piece['type'] == 'Mark':
|
646
|
+
pmap = self._markMap
|
647
|
+
else:
|
648
|
+
continue
|
649
|
+
|
650
|
+
|
651
|
+
iden = piece['id']
|
652
|
+
pm = pmap.get(iden,None)
|
653
|
+
#print(piece,pm)
|
654
|
+
if pm is None:
|
655
|
+
print(f'Cannot fine {piece["type"]} {iden}')
|
656
|
+
print(list(pmap.keys()))
|
657
|
+
continue
|
658
|
+
|
659
|
+
grid = tuple(piece['grid'])
|
660
|
+
x = piece['pixel']['x']
|
661
|
+
y = piece['pixel']['y']
|
662
|
+
if grid not in toAdd:
|
663
|
+
toAdd[grid] = {'center': (x,y),
|
664
|
+
'pieces': [] }
|
665
|
+
toAdd[grid]['pieces'].append(pm)
|
666
|
+
|
667
|
+
if self._do_scenario:
|
668
|
+
mapped = {
|
669
|
+
a['center']: a['pieces'] for a in toAdd.values()}
|
670
|
+
|
671
|
+
if mapName not in self._placements:
|
672
|
+
self._placements[mapName] = {}
|
673
|
+
|
674
|
+
mapPlacements = self._placements[mapName]
|
675
|
+
|
676
|
+
# We have to do this carefully, because we may have
|
677
|
+
# already added something to the placements for this
|
678
|
+
# map and specific locations.
|
679
|
+
for coord, pieces in mapped.items():
|
680
|
+
if coord in mapPlacements:
|
681
|
+
mapPlacements[coord].extend(pieces)
|
682
|
+
else:
|
683
|
+
mapPlacements[coord] = pieces
|
684
|
+
|
685
|
+
else:
|
686
|
+
for grid, dpieces in toAdd.items():
|
687
|
+
center = dpieces['center']
|
688
|
+
name = f'{grid[0]:02d}{grid[1]:02d}'
|
689
|
+
atstart = map.addAtStart(name=name,
|
690
|
+
useGridLocation=False,
|
691
|
+
owningBoard=mapName,
|
692
|
+
x = center[0],
|
693
|
+
y = center[1])
|
694
|
+
atstart.addPieces(*dpieces['pieces'])
|
695
|
+
|
696
|
+
# ----------------------------------------------------------------
|
697
|
+
def addDeadMap(self,sides):
|
698
|
+
'''Add a "Dead Map" element to the module
|
699
|
+
'''
|
700
|
+
name = 'DeadMap'
|
701
|
+
with VerboseGuard(f'Adding board {name}') as v:
|
702
|
+
map = self._game.addMap(mapName = name,
|
703
|
+
buttonName = '',
|
704
|
+
markMoved = 'Never',
|
705
|
+
launch = True,
|
706
|
+
icon = '/images/playerAway.gif',
|
707
|
+
allowMultiple = True,
|
708
|
+
hotkey = key('E',ALT))
|
709
|
+
# Basics
|
710
|
+
map.addStackMetrics()
|
711
|
+
map.addImageSaver()
|
712
|
+
map.addTextSaver()
|
713
|
+
map.addForwardToChatter()
|
714
|
+
map.addMenuDisplayer()
|
715
|
+
map.addMapCenterer()
|
716
|
+
map.addStackExpander()
|
717
|
+
map.addPieceMover()
|
718
|
+
map.addKeyBufferer()
|
719
|
+
map.addSelectionHighlighters()
|
720
|
+
map.addHighlightLastMoved()
|
721
|
+
map.addZoomer()
|
722
|
+
|
723
|
+
map.addMassKey(name='Restore',
|
724
|
+
buttonHotkey = key('R'),
|
725
|
+
hotkey = key('R'),
|
726
|
+
icon = '/images/Undo16.gif',
|
727
|
+
tooltip = 'Restore selected units')
|
728
|
+
|
729
|
+
if sides is None or len(sides) <= 0:
|
730
|
+
sides = ['A']
|
731
|
+
picker = map.addBoardPicker()
|
732
|
+
picker.addSetup(maxColumns=len(sides),mapName=name,
|
733
|
+
boardNames=[s+' pool' for s in sides])
|
734
|
+
|
735
|
+
for i, s in enumerate(sides):
|
736
|
+
v(f'Adding {s} pool')
|
737
|
+
color = [0,0,0,64]
|
738
|
+
color[i % 3] = 255
|
739
|
+
w = 400
|
740
|
+
h = 400
|
741
|
+
c = rgba(*color)
|
742
|
+
board = picker.addBoard(name = f'{s} pool',
|
743
|
+
image = '',
|
744
|
+
width = w,
|
745
|
+
height = h,
|
746
|
+
color = c)
|
747
|
+
|
748
|
+
# ====================================================================
|
749
|
+
class GBXExporter(CbExporter):
|
750
|
+
def __init__(self,
|
751
|
+
d,
|
752
|
+
title = None,
|
753
|
+
version = None,
|
754
|
+
rules = None,
|
755
|
+
tutorial = None,
|
756
|
+
visible = False,
|
757
|
+
main_board = None,
|
758
|
+
map_regex = None,
|
759
|
+
vassalVersion = '3.6',
|
760
|
+
do_scenario = False):
|
761
|
+
super(GBXExporter,self).__init__(d,
|
762
|
+
title = title,
|
763
|
+
version = version,
|
764
|
+
rules = rules,
|
765
|
+
tutorial = tutorial,
|
766
|
+
visible = visible,
|
767
|
+
main_board = main_board,
|
768
|
+
map_regex = map_regex,
|
769
|
+
vassalVersion = vassalVersion,
|
770
|
+
do_scenario = do_scenario)
|
771
|
+
|
772
|
+
# ----------------------------------------------------------------
|
773
|
+
def setup(self):
|
774
|
+
self.saveImages(self._d,self._vmod)
|
775
|
+
self._title = self._d['title']
|
776
|
+
self._version = self._d['version']
|
777
|
+
self._description = self._d['description']
|
778
|
+
|
779
|
+
# ----------------------------------------------------------------
|
780
|
+
def createBuildFile(self,
|
781
|
+
ignores = ['all',
|
782
|
+
'common',
|
783
|
+
'marker',
|
784
|
+
'markers',
|
785
|
+
' ']):
|
786
|
+
with VerboseGuard(f'Creating build file') as v:
|
787
|
+
self._build = BuildFile() # 'buildFile.xml')
|
788
|
+
self._game = self._build.addGame(name = self._title,
|
789
|
+
version = self._version,
|
790
|
+
description = self._description)
|
791
|
+
self.addDocumentation()
|
792
|
+
self._game.addBasicCommandEncoder()
|
793
|
+
|
794
|
+
|
795
|
+
# ====================================================================
|
796
|
+
class GSNExporter(CbExporter):
|
797
|
+
def __init__(self,
|
798
|
+
d,
|
799
|
+
title = None,
|
800
|
+
version = None,
|
801
|
+
rules = None,
|
802
|
+
tutorial = None,
|
803
|
+
visible = False,
|
804
|
+
main_board = None,
|
805
|
+
map_regex = None,
|
806
|
+
vassalVersion = '3.6',
|
807
|
+
do_scenario = False):
|
808
|
+
super().__init__(d,
|
809
|
+
title = title,
|
810
|
+
version = version,
|
811
|
+
rules = rules,
|
812
|
+
tutorial = tutorial,
|
813
|
+
visible = visible,
|
814
|
+
main_board = main_board,
|
815
|
+
map_regex = map_regex,
|
816
|
+
vassalVersion = vassalVersion,
|
817
|
+
do_scenario = do_scenario)
|
818
|
+
|
819
|
+
# ----------------------------------------------------------------
|
820
|
+
def setup(self):
|
821
|
+
with VerboseGuard('Setup') as v:
|
822
|
+
from pathlib import Path
|
823
|
+
|
824
|
+
self.saveImages(self._d['gamebox'],self._vmod)
|
825
|
+
self.setupTitle()
|
826
|
+
|
827
|
+
def setupTitle(self):
|
828
|
+
with VerboseGuard('Setup titles and such') as v:
|
829
|
+
v(f'Before: '
|
830
|
+
f'title={self._title}, '
|
831
|
+
f'version={self._version}, '
|
832
|
+
f'description={self._description}')
|
833
|
+
v(f'Game box: '
|
834
|
+
f'title={self._d["gamebox"]["title"]}, '
|
835
|
+
f'version={self._d["gamebox"]["version"]}, '
|
836
|
+
f'description={self._d["gamebox"]["description"]}')
|
837
|
+
v(f'Scenario: '
|
838
|
+
f'title={self._d["title"]}, '
|
839
|
+
f'version={self._d["version"]}, '
|
840
|
+
f'description={self._d["description"]}')
|
841
|
+
|
842
|
+
if self._title is None or self._title == '':
|
843
|
+
# If we are making a scenario, then do not take the
|
844
|
+
# title from the scenario file
|
845
|
+
self._title = None if self._do_scenario else self._d['title']
|
846
|
+
if self._title is None or self._title == '':
|
847
|
+
self._title = self._d['gamebox']['title']
|
848
|
+
if self._title is None or self._title == '':
|
849
|
+
from random import choice
|
850
|
+
from string import ascii_lowercase
|
851
|
+
self._title = Path(self._vmod.fileName()).stem
|
852
|
+
# self._title = ''.join([choice(ascii_lowercase)
|
853
|
+
# for _ in range(32)])
|
854
|
+
if not self._do_scenario:
|
855
|
+
self._d['title'] = self._title
|
856
|
+
|
857
|
+
if self._version is None or self._version == '':
|
858
|
+
# If we are doing a scenario, do not take the version
|
859
|
+
# from the scenario file.
|
860
|
+
self._version = (None if self._do_scenario else
|
861
|
+
self._d['version'])
|
862
|
+
if self._version is None or self._version == '':
|
863
|
+
self._version = self._d['gamebox']['version']
|
864
|
+
if self._version is None or self._version == '':
|
865
|
+
self._version = '0.0'
|
866
|
+
if not self._do_scenario:
|
867
|
+
self._d['version'] = self._version
|
868
|
+
|
869
|
+
if self._do_scenario:
|
870
|
+
# Take overall description form game box
|
871
|
+
self._description = self._d['gamebox']['description']
|
872
|
+
v(f'After: '
|
873
|
+
f'title={self._title}, '
|
874
|
+
f'version={self._version}, '
|
875
|
+
f'description={self._description}')
|
876
|
+
|
877
|
+
# ----------------------------------------------------------------
|
878
|
+
def createBuildFile(self,
|
879
|
+
ignores = '(.*markers?|all|commons|[ ]+)'):
|
880
|
+
'''Create the XML buildFile.xml
|
881
|
+
|
882
|
+
Parameters
|
883
|
+
----------
|
884
|
+
ignores : str
|
885
|
+
Regular expression to match ignored categories for factions
|
886
|
+
determination. Python's re.fullmatch is applied to this
|
887
|
+
regular exression against chit categories. If the pattern
|
888
|
+
is matched, then the chit is not considered to belong to a
|
889
|
+
faction.
|
890
|
+
'''
|
891
|
+
with VerboseGuard(f'Creating build file') as v:
|
892
|
+
desc = None if self._do_scenario else self._d['description']
|
893
|
+
if desc is None or desc == '':
|
894
|
+
desc = self._d['gamebox']['description']
|
895
|
+
if len(desc) > 32:
|
896
|
+
desc = desc[:32]
|
897
|
+
self._build = BuildFile() # 'buildFile.xml')
|
898
|
+
self._game = self._build.addGame(name = self._title,
|
899
|
+
version = self._version,
|
900
|
+
description = desc)
|
901
|
+
|
902
|
+
self._sides = self._d['players']
|
903
|
+
self._sides = [p.replace(' player','').replace(' Player','')
|
904
|
+
for p in self._sides]
|
905
|
+
|
906
|
+
if not self._sides:
|
907
|
+
self._sides = [s['description'] for s in
|
908
|
+
self._d['gamebox']['pieces']['sets']]
|
909
|
+
|
910
|
+
v(f'Sides: {self._sides}')
|
911
|
+
|
912
|
+
v(f'Adding documentation')
|
913
|
+
self.addDocumentation()
|
914
|
+
|
915
|
+
v(f'Adding command encoder')
|
916
|
+
self._game.addBasicCommandEncoder()
|
917
|
+
|
918
|
+
v(f'Adding Global options')
|
919
|
+
go = self._game.addGlobalOptions(
|
920
|
+
autoReport = 'Use Preferences Setting',
|
921
|
+
centerOnMove = 'Use Preferences Setting',
|
922
|
+
nonOwnerUnmaskable = 'Use Preferences Setting',
|
923
|
+
playerIdFormat = '$playerName$')
|
924
|
+
go.addOption(name='undoHotKey',value=key('Z'))
|
925
|
+
|
926
|
+
v(f'Adding player roster {self._sides}')
|
927
|
+
roster = self._game.addPlayerRoster()
|
928
|
+
for side in self._sides:
|
929
|
+
roster.addSide(side)
|
930
|
+
|
931
|
+
v(f'Adding global properties')
|
932
|
+
glob = self._game.addGlobalProperties()
|
933
|
+
glob.addProperty(name='TurnTracker.defaultDocked',
|
934
|
+
initialValue=True)
|
935
|
+
|
936
|
+
v(f'Adding notes')
|
937
|
+
self._game.addNotes()
|
938
|
+
|
939
|
+
v(f'Adding turn track')
|
940
|
+
turns = self._game.addTurnTrack(name='Turn',
|
941
|
+
counter={
|
942
|
+
'property': 'Turn',
|
943
|
+
'phases': {
|
944
|
+
'property': 'Phase',
|
945
|
+
'names': self._sides } })
|
946
|
+
|
947
|
+
|
948
|
+
self.addPrototypes(self._sides)
|
949
|
+
self.addPiecesMarks()
|
950
|
+
self.addBoards()
|
951
|
+
self.addCharts()
|
952
|
+
self.addDeadMap(self._sides)
|
953
|
+
self.addScenario()
|
954
|
+
|
955
|
+
|
956
|
+
#
|
957
|
+
def addMoreDocumentation(self,doc):
|
958
|
+
desc = f'''<html><body>
|
959
|
+
<h1>Module</h1>
|
960
|
+
<p>
|
961
|
+
This module was created from CyberBoard Scenario
|
962
|
+
{self._d["title"]} version {self._d["version"]} and game box
|
963
|
+
{self._d["gamebox"]["title"]} version {self._d["gamebox"]["version"]} '
|
964
|
+
'by the Python script <code>gsnexport.py</code> available from
|
965
|
+
</p>
|
966
|
+
<pre>
|
967
|
+
htps://gitlab.com/wargames_tex/pywargame
|
968
|
+
</pre>
|
969
|
+
<h1>Game</h1>
|
970
|
+
<table>
|
971
|
+
<tr><td>Title:</td><td>{self._d["gamebox"]["title"]}</td></tr>
|
972
|
+
<tr><td>Version:</td><td>{self._d["gamebox"]["version"]}</td></tr>
|
973
|
+
<tr><td>Author:</td><td>{self._d["gamebox"]["author"]}</td></tr>
|
974
|
+
<tr><td>Description:</td><td>{self._d["gamebox"]["description"]}</td></tr>
|
975
|
+
</table>
|
976
|
+
<h1>Scenario</h1>
|
977
|
+
<table>
|
978
|
+
<tr><td>Title:</td><td>{self._d["title"]}</td></tr>
|
979
|
+
<tr><td>Version:</td><td>{self._d["version"]}</td></tr>
|
980
|
+
<tr><td>Author:</td><td>{self._d["author"]}</td></tr>
|
981
|
+
<tr><td>Description:</td><td>{self._d["description"]}</td></tr>
|
982
|
+
</table>
|
983
|
+
</body></html>;'''
|
984
|
+
self._vmod.addFile('help/description.html',desc)
|
985
|
+
doc.addHelpFile(title='Description',fileName='help/description.html')
|
986
|
+
|
987
|
+
|
988
|
+
# ----------------------------------------------------------------
|
989
|
+
def addPiecesMarks(self):
|
990
|
+
with VerboseGuard(f'Adding pieces and marks') as v:
|
991
|
+
window = self._game.addPieceWindow(name = 'Counters',
|
992
|
+
hotkey = key('C',ALT))
|
993
|
+
combo = window.addCombo(entryName='Counters')
|
994
|
+
self.addPieces(combo,
|
995
|
+
self._d['gamebox']['pieces']['map'],
|
996
|
+
self._d['trays'],
|
997
|
+
self._d['gamebox']['pieces']['sets'])
|
998
|
+
self.addMarks(combo,
|
999
|
+
self._d['gamebox']['marks']['map'],
|
1000
|
+
self._d['gamebox']['marks']['sets'])
|
1001
|
+
# ----------------------------------------------------------------
|
1002
|
+
def boardMatch(self,name):
|
1003
|
+
if self._mapRegex is None:
|
1004
|
+
return False
|
1005
|
+
|
1006
|
+
from re import match
|
1007
|
+
|
1008
|
+
ret = match(self._mapRegex, name) is not None
|
1009
|
+
|
1010
|
+
return ret
|
1011
|
+
|
1012
|
+
# ----------------------------------------------------------------
|
1013
|
+
def addBoards(self):
|
1014
|
+
with VerboseGuard(f'Adding boards') as v:
|
1015
|
+
toAdd = []
|
1016
|
+
for serial, sboard in self._d['boards'].items():
|
1017
|
+
board = self._d['gamebox']['boards'].get(serial,None)
|
1018
|
+
if board is None:
|
1019
|
+
continue
|
1020
|
+
# print('BOARD (gsn)',board)
|
1021
|
+
#
|
1022
|
+
# If not set to start map in GBX, and name does not
|
1023
|
+
# the user specified main board and board name does
|
1024
|
+
# not match the board regular expression, then
|
1025
|
+
# continue on (the board will be made chart instead).
|
1026
|
+
if not sboard['onload'] and \
|
1027
|
+
board['name'] != self._mainBoard and \
|
1028
|
+
not self.boardMatch(board['name']):
|
1029
|
+
continue
|
1030
|
+
|
1031
|
+
# Here, we add pieces and indicators as at-start on
|
1032
|
+
# the map. Perhaps we should _not_ do that, and write
|
1033
|
+
# a scenario file instead.
|
1034
|
+
d = {'gboard': board,
|
1035
|
+
'color': sboard['grid']['color'],
|
1036
|
+
'pieces': sboard['pieces'],
|
1037
|
+
'indicators': sboard['indicators'] }
|
1038
|
+
if board['name'] == self._mainBoard:
|
1039
|
+
# If this is the main board, as selected by user,
|
1040
|
+
# then put first in the queue.
|
1041
|
+
toAdd.insert(0,d)
|
1042
|
+
else:
|
1043
|
+
toAdd.append(d)
|
1044
|
+
|
1045
|
+
super(GSNExporter,self).addBoards(toAdd)
|
1046
|
+
|
1047
|
+
# ----------------------------------------------------------------
|
1048
|
+
def addCharts(self):
|
1049
|
+
with VerboseGuard(f'Adding Charts') as v:
|
1050
|
+
toAdd = []
|
1051
|
+
for serial, sboard in self._d['boards'].items():
|
1052
|
+
board = self._d['gamebox']['boards'].get(serial,None)
|
1053
|
+
if board is None:
|
1054
|
+
continue
|
1055
|
+
# print('BOARD (gbx)',board)
|
1056
|
+
#
|
1057
|
+
# If the board is set to start at load on GBX, or the
|
1058
|
+
# name of the board matches the main board name set by
|
1059
|
+
# user, or board name matched the user-defined board
|
1060
|
+
# regular expression, the skip this board (was added
|
1061
|
+
# previously as a board).
|
1062
|
+
if sboard['onload'] or \
|
1063
|
+
board['name'] == self._mainBoard or \
|
1064
|
+
self.boardMatch(board['name']):
|
1065
|
+
continue
|
1066
|
+
|
1067
|
+
# Here, we add pieces and indicators as at-start on
|
1068
|
+
# the map. Perhaps we should _not_ do that, and write
|
1069
|
+
# a scenario file instead.
|
1070
|
+
d = {'gboard': board,
|
1071
|
+
'color': sboard['grid']['color'],
|
1072
|
+
'pieces': sboard['pieces'],
|
1073
|
+
'indicators': sboard['indicators'] }
|
1074
|
+
if board['name'] == self._mainBoard:
|
1075
|
+
# If this is the main board, as selected by user,
|
1076
|
+
# then put first in the queue.
|
1077
|
+
#
|
1078
|
+
# Should this be done for charts? Probably not.
|
1079
|
+
toAdd.insert(0,d)
|
1080
|
+
else:
|
1081
|
+
toAdd.append(d)
|
1082
|
+
|
1083
|
+
super(GSNExporter,self).addCharts(toAdd)
|
1084
|
+
|
1085
|
+
# ----------------------------------------------------------------
|
1086
|
+
def addScenario(self):
|
1087
|
+
if not self._do_scenario or len(self._placements) <= 0:
|
1088
|
+
# Nothing to do
|
1089
|
+
return
|
1090
|
+
|
1091
|
+
from pathlib import Path
|
1092
|
+
|
1093
|
+
menu = self._game.addPredefinedSetup(name = 'Setups',
|
1094
|
+
isMenu = True)
|
1095
|
+
|
1096
|
+
menu.addPredefinedSetup(name = 'No setup',
|
1097
|
+
file = '',
|
1098
|
+
useFile = False,
|
1099
|
+
description = 'No initial setup')
|
1100
|
+
|
1101
|
+
with VerboseGuard(f'Adding a scenario {self._d["title"]}') as v:
|
1102
|
+
vsav = VSav(self._build,self._vmod)
|
1103
|
+
save = vsav.addSaveFile()
|
1104
|
+
|
1105
|
+
# from pprint import pprint
|
1106
|
+
# pprint(self._placements)
|
1107
|
+
|
1108
|
+
for mapName,placements in self._placements.items():
|
1109
|
+
|
1110
|
+
save.addNoGrid(mapName, placements)
|
1111
|
+
|
1112
|
+
# print('\n'.join(save.getLines()))
|
1113
|
+
|
1114
|
+
savfile = self._d['title'].replace(' ','_')+'.vsav'
|
1115
|
+
vsav.run(savename = savfile,
|
1116
|
+
description = self._d['description'])
|
1117
|
+
|
1118
|
+
menu.addPredefinedSetup(name=self._d['title'],
|
1119
|
+
useFile=True,
|
1120
|
+
file=savfile,
|
1121
|
+
description=self._d['description'])
|
1122
|
+
|
1123
|
+
|
1124
|
+
# ====================================================================
|
1125
|
+
|
1126
|
+
#
|
1127
|
+
# EOF
|
1128
|
+
#
|
1129
|
+
|
1130
|
+
|
1131
|
+
|
1132
|
+
|