33
37
###########################################################################
35
39
###########################################################################
36
class WlMapLibraryException( Exception ): pass
37
class InvalidMapPackage( WlMapLibraryException ):
38
def __init__(self, package_name, error ):
40
class WlMapLibraryException(Exception):
44
class InvalidMapPackage(WlMapLibraryException):
46
def __init__(self, package_name, error):
39
47
self.pn = package_name
42
return "Error reading package %s: %s" % (self.pn,self.error)
43
class WlInvalidFile( WlMapLibraryException ): pass
51
return 'Error reading package %s: %s' % (self.pn, self.error)
54
class WlInvalidFile(WlMapLibraryException):
46
58
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 ):
59
"""This class parses a widelands map file as long as it is a directory (not
62
def __init__(self, fn=None):
60
Load a map from the given directory or zipfile
70
"""Load a map from the given directory or zipfile.
62
72
fn - path to directory or zipfile or a file handle to the opened zipfile
65
if isinstance(fn,str) and os.path.isdir(fn):
76
if isinstance(fn, str) and os.path.isdir(fn):
67
78
self._is_zip = False
74
85
raise WlInvalidFile()
76
87
# 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]
88
elementals = [i.filename for i in zf.filelist if
89
i.filename.find('elemental') != -1 and
90
i.filename.find('.svn') == -1]
81
92
if len(elementals) != 1:
82
93
# Try to use the one called 'elemental'
83
elementals = [ e for e in elementals if os.path.basename(e) == "elemental" ]
95
e for e in elementals if os.path.basename(e) == 'elemental']
84
96
if len(elementals) != 1:
85
raise WlInvalidFile("Map contains an invalid number of elemental packets")
98
'Map contains an invalid number of elemental packets')
86
99
el = elementals[0].rsplit('/')
90
103
basedir = el[0] + '/'
92
open_file = lambda fn,mode: StringIO(zf.read(fn))
105
open_file = lambda fn, mode: StringIO(zf.read(fn))
94
107
# 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') )
108
self._read_elemental(open_file(basedir + 'elemental', 'r'))
109
self._read_heights(open_file(basedir + 'binary/heights', 'rb'))
110
self._read_terrains(open_file(basedir + 'binary/terrain', 'rb'))
104
"Map dimensions (h,w). Not: height first! like in numpy"
117
"""Map dimensions (h,w).
119
Not: height first! like in numpy
108
126
return self._dim[1]
111
130
return self._dim[0]
114
133
def nr_players(self):
116
135
return self._nr_players
118
138
def world_name(self):
120
140
return self._world_name
124
145
return self._name
126
148
def author(self):
149
"""The maps creator."""
128
150
return self._author
131
"The maps description"
154
"""The maps description."""
132
155
return self._descr
135
158
def heights(self):
136
"The heights of the various fields, an 2d array: a[y,x]"
159
'The heights of the various fields, an 2d array: a[y,x]'
137
160
return self._heights
141
"The RO foo property."
164
"""The RO foo property."""
142
165
return self._terr
145
"The RO foo property."
169
"""The RO foo property."""
146
170
return self._terd
148
173
def terrains(self):
149
"The RO foo property."
174
"""The RO foo property."""
150
175
return self._terrains
155
180
###########################################################################
156
181
def _read_elemental(self, file):
158
raise InvalidMapPackage("elemental", m)
183
raise InvalidMapPackage('elemental', m)
162
version = cp.getint("global","packet_version")
187
version = cp.getint('global', 'packet_version')
164
error("Invalid package version: %i" % version)
189
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,))
191
self._dim = cp.getint(
192
'global', 'map_h'), cp.getint('global', 'map_w')
193
self._nr_players = cp.getint('global', 'nr_players')
194
self._world_name = cp.getstring('global', 'world')
195
self._name = cp.getstring('global', 'name')
196
self._author = cp.getstring('global', 'author')
197
self._descr = cp.getstring('global', 'descr')
198
except NoOptionError, e:
199
error('Missing option: %s:%s' % (e.section, e.option))
200
except NoSectionError, e:
201
error('Missing section: %s' % (e.section,))
177
203
# TODO: background picture
179
205
def _read_heights(self, file):
181
raise InvalidMapPackage("heights", m)
207
raise InvalidMapPackage('heights', m)
183
version, = struct.unpack_from("<H",s)
209
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)
211
error('Invalid package version: %i' % version)
212
if len(s) != self._dim[0] * self._dim[1] + 2:
213
error('Package has wrong length.')
214
self._heights = fromstring(s[2:], dtype='u1').reshape(self._dim)
190
216
def _read_terrains(self, file):
192
raise InvalidMapPackage("terrain", m)
218
raise InvalidMapPackage('terrain', m)
194
version, = struct.unpack_from("<H",s)
220
version, = struct.unpack_from('<H', s)
196
error("Invalid package version: %i" % version)
222
error('Invalid package version: %i' % version)
199
nrterrains, = struct.unpack_from("<H",s,2)
225
nrterrains, = struct.unpack_from('<H', s, 2)
200
226
except struct.error:
201
error("Package has wrong length.")
227
error('Package has wrong length.')
203
229
terrains = [None] * nrterrains
205
231
for i in range(nrterrains):
207
tid, = struct.unpack_from("<H",s,nread)
233
tid, = struct.unpack_from('<H', s, nread)
208
234
except struct.error:
209
error("Package has wrong length.")
235
error('Package has wrong length.')
210
236
if tid >= nrterrains:
211
error("Invalid terrain id in package-header")
237
error('Invalid terrain id in package-header')
214
name = s[nread:s.find("\x00",nread)]
240
name = s[nread:s.find('\x00', nread)]
241
nread += len(name) + 1
217
terrains[tid] = Terrain( name, tid )
243
terrains[tid] = Terrain(name, tid)
219
245
self._terrains = terrains
220
a = fromstring(s[nread:],dtype="u1")
246
a = fromstring(s[nread:], dtype='u1')
222
if len(a) != self._dim[0]*self._dim[1]*2:
223
error("Package has wrong length.")
248
if len(a) != self._dim[0] * self._dim[1] * 2:
249
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
252
self._terr = numpy.empty(
253
self._dim[0] * self._dim[1], dtype='object')
254
self._terr[:] = [terrains[o] for o in a[::2]]
255
self._terr.shape = self._dim
256
self._terd = numpy.empty(
257
self._dim[0] * self._dim[1], dtype='object')
258
self._terd[:] = [terrains[o] for o in a[1::2]]
259
self._terd.shape = self._dim
232
260
except IndexError:
233
error("Invalid terrain index in package.")
261
error('Invalid terrain index in package.')
235
def make_minimap( self, datadir ):
237
Returns an RGB minimap of the map.
263
def make_minimap(self, datadir):
264
"""Returns an RGB minimap of the map.
239
266
datadir - Path to widelands directory so that the texture can be read
241
269
# Read the terrains
242
colors = [None]* len(self._terrains)
270
colors = [None] * len(self._terrains)
243
271
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)
272
i = Image.open(datadir + '/worlds/' + self._world_name +
273
'/pics/' + t.name + '_00.png').convert('RGB')
274
i = fromstring(i.tostring(), dtype='uint8').reshape((64, 64, 3))
275
colors[t.id] = i.mean(axis=0).mean(axis=0)
248
277
# Make the minimap
249
mm = empty( (self._dim)+ (3,), dtype = "uint8" )
278
mm = empty((self._dim) + (3,), dtype='uint8')
250
279
for y in range(self._dim[0]):
251
280
for x in range(self._dim[1]):
253
mm[y,x] = colors[t.id]
282
mm[y, x] = colors[t.id]
255
284
# Now, add the heights
256
rubbish,dx = gradient(self._heights.astype("float64"))
285
rubbish, dx = gradient(self._heights.astype('float64'))
261
rdx = empty( (self._dim)+(3,), dtype="float64")
290
rdx = empty((self._dim) + (3,), dtype='float64')
267
296
# This is taken from the gimps overlay functionality