~widelands-dev/widelands-website/trunk

« back to all changes in this revision

Viewing changes to widelandslib/map.py

  • Committer: franku
  • Date: 2016-12-13 18:28:51 UTC
  • mto: This revision was merged to the branch mainline in revision 443.
  • Revision ID: somal@arcor.de-20161213182851-bo5ebf8pdvw5beua
run the script

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
import struct
12
12
import zipfile
13
13
 
 
14
 
14
15
class Terrain(object):
15
 
    def __init__( self, name, id ):
 
16
 
 
17
    def __init__(self, name, id):
16
18
        self._id = id
17
19
        self._name = name
18
20
 
19
21
    @property
20
22
    def name(self):
21
23
        return self._name
 
24
 
22
25
    @property
23
26
    def animation(self):
24
27
        return self._animation
 
28
 
25
29
    @property
26
30
    def id(self):
27
31
        return self._id
33
37
###########################################################################
34
38
#                                 ERRORS                                  #
35
39
###########################################################################
36
 
class WlMapLibraryException( Exception ): pass
37
 
class InvalidMapPackage( WlMapLibraryException ):
38
 
    def __init__(self, package_name, error ):
 
40
class WlMapLibraryException(Exception):
 
41
    pass
 
42
 
 
43
 
 
44
class InvalidMapPackage(WlMapLibraryException):
 
45
 
 
46
    def __init__(self, package_name, error):
39
47
        self.pn = package_name
40
48
        self.error = error
41
 
    def __str__( self ):
42
 
        return "Error reading package %s: %s" % (self.pn,self.error)
43
 
class WlInvalidFile( WlMapLibraryException ): pass
 
49
 
 
50
    def __str__(self):
 
51
        return 'Error reading package %s: %s' % (self.pn, self.error)
 
52
 
 
53
 
 
54
class WlInvalidFile(WlMapLibraryException):
 
55
    pass
44
56
 
45
57
 
46
58
class WidelandsMap(object):
47
 
    """
48
 
    This class parses a widelands map file as long as it is
49
 
    a directory (not a zip file).
50
 
    """
51
 
    def __init__(self, fn = None ):
 
59
    """This class parses a widelands map file as long as it is a directory (not
 
60
    a zip file)."""
 
61
 
 
62
    def __init__(self, fn=None):
52
63
        if fn is not None:
53
 
            self.load( fn )
 
64
            self.load(fn)
54
65
 
55
66
#############
56
67
# FUNCTIONS #
57
68
#############
58
 
    def load( self, fn ):
59
 
        """
60
 
        Load a map from the given directory or zipfile
 
69
    def load(self, fn):
 
70
        """Load a map from the given directory or zipfile.
61
71
 
62
72
        fn - path to directory or zipfile or a file handle to the opened zipfile
 
73
 
63
74
        """
64
75
 
65
 
        if isinstance(fn,str) and os.path.isdir(fn):
 
76
        if isinstance(fn, str) and os.path.isdir(fn):
66
77
            basedir = fn + '/'
67
78
            self._is_zip = False
68
79
            open_file = open
74
85
                raise WlInvalidFile()
75
86
 
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]
80
91
 
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" ]
 
94
                elementals = [
 
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")
 
97
                    raise WlInvalidFile(
 
98
                        'Map contains an invalid number of elemental packets')
86
99
            el = elementals[0].rsplit('/')
87
100
            if len(el) == 1:
88
101
                basedir = ''
89
102
            else:
90
103
                basedir = el[0] + '/'
91
104
 
92
 
            open_file = lambda fn,mode: StringIO(zf.read(fn))
 
105
            open_file = lambda fn, mode: StringIO(zf.read(fn))
93
106
 
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'))
98
111
 
99
112
##############
100
113
# Properties #
101
114
##############
102
115
    @property
103
116
    def dim(self):
104
 
        "Map dimensions (h,w). Not: height first! like in numpy"
 
117
        """Map dimensions (h,w).
 
118
 
 
119
        Not: height first! like in numpy
 
120
 
 
121
        """
105
122
        return self._dim
 
123
 
106
124
    @property
107
125
    def w(self):
108
126
        return self._dim[1]
 
127
 
109
128
    @property
110
129
    def h(self):
111
130
        return self._dim[0]
112
131
 
113
132
    @property
114
133
    def nr_players(self):
115
 
        "Nr of players"
 
134
        """Nr of players."""
116
135
        return self._nr_players
 
136
 
117
137
    @property
118
138
    def world_name(self):
119
 
        "Name of world"
 
139
        """Name of world."""
120
140
        return self._world_name
 
141
 
121
142
    @property
122
143
    def name(self):
123
 
        "Name of map"
 
144
        """Name of map."""
124
145
        return self._name
 
146
 
125
147
    @property
126
148
    def author(self):
127
 
        "The maps creator"
 
149
        """The maps creator."""
128
150
        return self._author
 
151
 
129
152
    @property
130
153
    def descr(self):
131
 
        "The maps description"
 
154
        """The maps description."""
132
155
        return self._descr
133
156
 
134
157
    @property
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
138
161
 
139
162
    @property
140
163
    def ter_r(self):
141
 
        "The RO foo property."
 
164
        """The RO foo property."""
142
165
        return self._terr
 
166
 
143
167
    @property
144
168
    def ter_d(self):
145
 
        "The RO foo property."
 
169
        """The RO foo property."""
146
170
        return self._terd
 
171
 
147
172
    @property
148
173
    def terrains(self):
149
 
        "The RO foo property."
 
174
        """The RO foo property."""
150
175
        return self._terrains
151
176
 
152
177
 
155
180
###########################################################################
156
181
    def _read_elemental(self, file):
157
182
        def error(m):
158
 
            raise InvalidMapPackage("elemental", m)
159
 
        cp =Cp( file )
 
183
            raise InvalidMapPackage('elemental', m)
 
184
        cp = Cp(file)
160
185
 
161
186
        try:
162
 
            version = cp.getint("global","packet_version")
 
187
            version = cp.getint('global', 'packet_version')
163
188
            if version != 1:
164
 
                error("Invalid package version: %i" % version)
 
189
                error('Invalid package version: %i' % version)
165
190
 
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,))
176
202
 
177
203
          # TODO: background picture
178
204
 
179
205
    def _read_heights(self, file):
180
206
        def error(m):
181
 
            raise InvalidMapPackage("heights", m)
 
207
            raise InvalidMapPackage('heights', m)
182
208
        s = file.read()
183
 
        version, = struct.unpack_from("<H",s)
 
209
        version, = struct.unpack_from('<H', s)
184
210
        if version != 1:
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)
189
215
 
190
216
    def _read_terrains(self, file):
191
217
        def error(m):
192
 
            raise InvalidMapPackage("terrain", m)
 
218
            raise InvalidMapPackage('terrain', m)
193
219
        s = file.read()
194
 
        version, = struct.unpack_from("<H",s)
 
220
        version, = struct.unpack_from('<H', s)
195
221
        if version != 1:
196
 
            error("Invalid package version: %i" % version)
 
222
            error('Invalid package version: %i' % version)
197
223
 
198
224
        try:
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.')
202
228
 
203
229
        terrains = [None] * nrterrains
204
230
        nread = 4
205
231
        for i in range(nrterrains):
206
232
            try:
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')
212
238
 
213
239
            nread += 2
214
 
            name = s[nread:s.find("\x00",nread)]
215
 
            nread += len(name)+1
 
240
            name = s[nread:s.find('\x00', nread)]
 
241
            nread += len(name) + 1
216
242
 
217
 
            terrains[tid] = Terrain( name, tid )
 
243
            terrains[tid] = Terrain(name, tid)
218
244
 
219
245
        self._terrains = terrains
220
 
        a = fromstring(s[nread:],dtype="u1")
 
246
        a = fromstring(s[nread:], dtype='u1')
221
247
 
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.')
224
250
 
225
251
        try:
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.')
234
262
 
235
 
    def make_minimap( self, datadir ):
236
 
        """
237
 
        Returns an RGB minimap of the map.
 
263
    def make_minimap(self, datadir):
 
264
        """Returns an RGB minimap of the map.
238
265
 
239
266
        datadir - Path to widelands directory so that the texture can be read
 
267
 
240
268
        """
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)
247
276
 
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]):
252
 
                t = self._terr[y,x]
253
 
                mm[y,x] = colors[t.id]
 
281
                t = self._terr[y, x]
 
282
                mm[y, x] = colors[t.id]
254
283
 
255
284
        # Now, add the heights
256
 
        rubbish,dx = gradient(self._heights.astype("float64"))
 
285
        rubbish, dx = gradient(self._heights.astype('float64'))
257
286
        dx -= dx.min()
258
287
        if dx.max():
259
288
            dx /= dx.max()
260
289
        dx *= 255.
261
 
        rdx = empty( (self._dim)+(3,), dtype="float64")
262
 
        rdx[:,:,0] = dx
263
 
        rdx[:,:,1] = dx
264
 
        rdx[:,:,2] = dx
 
290
        rdx = empty((self._dim) + (3,), dtype='float64')
 
291
        rdx[:, :, 0] = dx
 
292
        rdx[:, :, 1] = dx
 
293
        rdx[:, :, 2] = dx
265
294
        dx = rdx
266
295
 
267
296
        # This is taken from the gimps overlay functionality
269
298
        # http://docs.gimp.org/en/gimp-concepts-layer-modes.html
270
299
        mm = mm / 255. * (mm + 2 * dx / 255. * (255. - mm))
271
300
 
272
 
        return mm.astype("uint8")
273
 
 
274
 
 
275
 
 
276
 
 
 
301
        return mm.astype('uint8')