1
#!/usr/bin/env python -tt
5
from conf import WidelandsConfigParser as Cp, NoOptionError, NoSectionError
6
from numpy import fromstring, array, empty, gradient
11
from cStringIO import StringIO
14
class Terrain(object):
15
def __init__( self, name, id ):
24
return self._animation
33
###########################################################################
35
###########################################################################
36
class WlMapLibraryException( Exception ): pass
37
class InvalidMapPackage( WlMapLibraryException ):
38
def __init__(self, package_name, error ):
39
self.pn = package_name
42
return "Error reading package %s: %s" % (self.pn,self.error)
43
class WlInvalidFile( WlMapLibraryException ): pass
46
class WidelandsMap(object):
48
This class parses a widelands map file as long as it is
49
a directory (not a zip file).
51
def __init__(self, fn = None ):
60
Load a map from the given directory or zipfile
62
fn - path to directory or zipfile or a file handle to the opened zipfile
65
if isinstance(fn,str) and os.path.isdir(fn):
72
zf = zipfile.ZipFile(fn)
73
except zipfile.BadZipfile:
76
# Try to find elemental packet
77
elementals = [ i.filename for i in zf.filelist if
78
i.filename.find("elemental") != -1 and
79
i.filename.find('.svn') == -1]
81
if len(elementals) != 1:
82
# Try to use the one called 'elemental'
83
elementals = [ e for e in elementals if os.path.basename(e) == "elemental" ]
84
if len(elementals) != 1:
85
raise WlInvalidFile("Map contains an invalid number of elemental packets")
86
el = elementals[0].rsplit('/')
92
open_file = lambda fn,mode: StringIO(zf.read(fn))
94
# Okay, try to read our files
95
self._read_elemental( open_file(basedir + 'elemental','r') )
96
self._read_heights( open_file(basedir + 'binary/heights','rb') )
97
self._read_terrains( open_file(basedir + 'binary/terrain', 'rb') )
104
"Map dimensions (h,w). Not: height first! like in numpy"
114
def nr_players(self):
116
return self._nr_players
118
def world_name(self):
120
return self._world_name
131
"The maps description"
136
"The heights of the various fields, an 2d array: a[y,x]"
141
"The RO foo property."
145
"The RO foo property."
149
"The RO foo property."
150
return self._terrains
153
###########################################################################
154
# PRIVATE PARSING FUNCTIONALITY BELOW #
155
###########################################################################
156
def _read_elemental(self, file):
158
raise InvalidMapPackage("elemental", m)
162
version = cp.getint("global","packet_version")
164
error("Invalid package version: %i" % version)
166
self._dim = cp.getint("global","map_h"), cp.getint("global","map_w")
167
self._nr_players = cp.getint("global","nr_players")
168
self._world_name = cp.getstring("global", "world")
169
self._name = cp.getstring("global", "name")
170
self._author = cp.getstring("global", "author")
171
self._descr = cp.getstring("global", "descr")
172
except NoOptionError,e:
173
error("Missing option: %s:%s" % (e.section,e.option))
174
except NoSectionError,e:
175
error("Missing section: %s" % (e.section,))
177
# TODO: background picture
179
def _read_heights(self, file):
181
raise InvalidMapPackage("heights", m)
183
version, = struct.unpack_from("<H",s)
185
error("Invalid package version: %i" % version)
186
if len(s) != self._dim[0]*self._dim[1] + 2:
187
error("Package has wrong length.")
188
self._heights = fromstring(s[2:],dtype="u1").reshape(self._dim)
190
def _read_terrains(self, file):
192
raise InvalidMapPackage("terrain", m)
194
version, = struct.unpack_from("<H",s)
196
error("Invalid package version: %i" % version)
199
nrterrains, = struct.unpack_from("<H",s,2)
201
error("Package has wrong length.")
203
terrains = [None] * nrterrains
205
for i in range(nrterrains):
207
tid, = struct.unpack_from("<H",s,nread)
209
error("Package has wrong length.")
210
if tid >= nrterrains:
211
error("Invalid terrain id in package-header")
214
name = s[nread:s.find("\x00",nread)]
217
terrains[tid] = Terrain( name, tid )
219
self._terrains = terrains
220
a = fromstring(s[nread:],dtype="u1")
222
if len(a) != self._dim[0]*self._dim[1]*2:
223
error("Package has wrong length.")
226
self._terr = numpy.empty( self._dim[0]*self._dim[1], dtype="object")
227
self._terr[:] = [ terrains[o] for o in a[::2] ]
228
self._terr.shape = self._dim
229
self._terd = numpy.empty( self._dim[0]*self._dim[1], dtype="object")
230
self._terd[:] = [ terrains[o] for o in a[1::2] ]
231
self._terd.shape = self._dim
233
error("Invalid terrain index in package.")
235
def make_minimap( self, datadir ):
237
Returns an RGB minimap of the map.
239
datadir - Path to widelands directory so that the texture can be read
242
colors = [None]* len(self._terrains)
243
for t in self._terrains:
244
i = Image.open(datadir + '/worlds/' + self._world_name + '/pics/' + t.name + '_00.png').convert("RGB")
245
i = fromstring(i.tostring(),dtype="uint8").reshape( (64,64,3))
246
colors[ t.id ] = i.mean(axis=0).mean(axis=0)
249
mm = empty( (self._dim)+ (3,), dtype = "uint8" )
250
for y in range(self._dim[0]):
251
for x in range(self._dim[1]):
253
mm[y,x] = colors[t.id]
255
# Now, add the heights
256
rubbish,dx = gradient(self._heights.astype("float64"))
261
rdx = empty( (self._dim)+(3,), dtype="float64")
267
# This is taken from the gimps overlay functionality
269
# http://docs.gimp.org/en/gimp-concepts-layer-modes.html
270
mm = mm / 255. * (mm + 2 * dx / 255. * (255. - mm))
272
return mm.astype("uint8")