~widelands-dev/widelands-website/trunk

« back to all changes in this revision

Viewing changes to widelandslib/map.py

  • Committer: Holger Rapp
  • Date: 2010-01-01 21:35:23 UTC
  • mto: (173.3.2 widelands)
  • mto: This revision was merged to the branch mainline in revision 176.
  • Revision ID: rapp@mrt.uka.de-20100101213523-53rcapbemm69ep6u
Made the site compatible to django 1.1 and all the various packages

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python -tt
2
 
# encoding: utf-8
3
 
#
4
 
 
5
 
from conf import WidelandsConfigParser as Cp, NoOptionError, NoSectionError
6
 
from numpy import fromstring, array, empty, gradient
7
 
import struct
8
 
import os
9
 
import numpy
10
 
import zipfile
11
 
from cStringIO import StringIO
12
 
import Image
13
 
 
14
 
class Terrain(object):
15
 
    def __init__( self, name, id ):
16
 
        self._id = id
17
 
        self._name = name
18
 
 
19
 
    @property
20
 
    def name(self):
21
 
        return self._name
22
 
    @property
23
 
    def animation(self):
24
 
        return self._animation
25
 
    @property
26
 
    def id(self):
27
 
        return self._id
28
 
 
29
 
    def __repr__(self):
30
 
        return self._name
31
 
 
32
 
 
33
 
###########################################################################
34
 
#                                 ERRORS                                  #
35
 
###########################################################################
36
 
class WlMapLibraryException( Exception ): pass
37
 
class InvalidMapPackage( WlMapLibraryException ):
38
 
    def __init__(self, package_name, error ):
39
 
        self.pn = package_name
40
 
        self.error = error
41
 
    def __str__( self ):
42
 
        return "Error reading package %s: %s" % (self.pn,self.error)
43
 
class WlInvalidFile( WlMapLibraryException ): pass
44
 
 
45
 
 
46
 
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 ):
52
 
        if fn is not None:
53
 
            self.load( fn )
54
 
 
55
 
#############
56
 
# FUNCTIONS #
57
 
#############
58
 
    def load( self, fn ):
59
 
        """
60
 
        Load a map from the given directory or zipfile
61
 
 
62
 
        fn - path to directory or zipfile or a file handle to the opened zipfile
63
 
        """
64
 
 
65
 
        if isinstance(fn,str) and os.path.isdir(fn):
66
 
            basedir = fn + '/'
67
 
            self._is_zip = False
68
 
            open_file = open
69
 
        else:
70
 
            self._is_zip = True
71
 
            try:
72
 
                zf = zipfile.ZipFile(fn)
73
 
            except zipfile.BadZipfile:
74
 
                raise WlInvalidFile()
75
 
 
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]
80
 
 
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('/')
87
 
            if len(el) == 1:
88
 
                basedir = ''
89
 
            else:
90
 
                basedir = el[0] + '/'
91
 
 
92
 
            open_file = lambda fn,mode: StringIO(zf.read(fn))
93
 
 
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') )
98
 
 
99
 
##############
100
 
# Properties #
101
 
##############
102
 
    @property
103
 
    def dim(self):
104
 
        "Map dimensions (h,w). Not: height first! like in numpy"
105
 
        return self._dim
106
 
    @property
107
 
    def w(self):
108
 
        return self._dim[1]
109
 
    @property
110
 
    def h(self):
111
 
        return self._dim[0]
112
 
 
113
 
    @property
114
 
    def nr_players(self):
115
 
        "Nr of players"
116
 
        return self._nr_players
117
 
    @property
118
 
    def world_name(self):
119
 
        "Name of world"
120
 
        return self._world_name
121
 
    @property
122
 
    def name(self):
123
 
        "Name of map"
124
 
        return self._name
125
 
    @property
126
 
    def author(self):
127
 
        "The maps creator"
128
 
        return self._author
129
 
    @property
130
 
    def descr(self):
131
 
        "The maps description"
132
 
        return self._descr
133
 
 
134
 
    @property
135
 
    def heights(self):
136
 
        "The heights of the various fields, an 2d array: a[y,x]"
137
 
        return self._heights
138
 
 
139
 
    @property
140
 
    def ter_r(self):
141
 
        "The RO foo property."
142
 
        return self._terr
143
 
    @property
144
 
    def ter_d(self):
145
 
        "The RO foo property."
146
 
        return self._terd
147
 
    @property
148
 
    def terrains(self):
149
 
        "The RO foo property."
150
 
        return self._terrains
151
 
 
152
 
 
153
 
###########################################################################
154
 
#                   PRIVATE PARSING FUNCTIONALITY BELOW                   #
155
 
###########################################################################
156
 
    def _read_elemental(self, file):
157
 
        def error(m):
158
 
            raise InvalidMapPackage("elemental", m)
159
 
        cp =Cp( file )
160
 
 
161
 
        try:
162
 
            version = cp.getint("global","packet_version")
163
 
            if version != 1:
164
 
                error("Invalid package version: %i" % version)
165
 
 
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,))
176
 
 
177
 
          # TODO: background picture
178
 
 
179
 
    def _read_heights(self, file):
180
 
        def error(m):
181
 
            raise InvalidMapPackage("heights", m)
182
 
        s = file.read()
183
 
        version, = struct.unpack_from("<H",s)
184
 
        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)
189
 
 
190
 
    def _read_terrains(self, file):
191
 
        def error(m):
192
 
            raise InvalidMapPackage("terrain", m)
193
 
        s = file.read()
194
 
        version, = struct.unpack_from("<H",s)
195
 
        if version != 1:
196
 
            error("Invalid package version: %i" % version)
197
 
 
198
 
        try:
199
 
            nrterrains, = struct.unpack_from("<H",s,2)
200
 
        except struct.error:
201
 
            error("Package has wrong length.")
202
 
 
203
 
        terrains = [None] * nrterrains
204
 
        nread = 4
205
 
        for i in range(nrterrains):
206
 
            try:
207
 
                tid, = struct.unpack_from("<H",s,nread)
208
 
            except struct.error:
209
 
                error("Package has wrong length.")
210
 
            if tid >= nrterrains:
211
 
                error("Invalid terrain id in package-header")
212
 
 
213
 
            nread += 2
214
 
            name = s[nread:s.find("\x00",nread)]
215
 
            nread += len(name)+1
216
 
 
217
 
            terrains[tid] = Terrain( name, tid )
218
 
 
219
 
        self._terrains = terrains
220
 
        a = fromstring(s[nread:],dtype="u1")
221
 
 
222
 
        if len(a) != self._dim[0]*self._dim[1]*2:
223
 
            error("Package has wrong length.")
224
 
 
225
 
        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
232
 
        except IndexError:
233
 
            error("Invalid terrain index in package.")
234
 
 
235
 
    def make_minimap( self, datadir ):
236
 
        """
237
 
        Returns an RGB minimap of the map.
238
 
 
239
 
        datadir - Path to widelands directory so that the texture can be read
240
 
        """
241
 
        # Read the terrains
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)
247
 
 
248
 
        # Make the minimap
249
 
        mm = empty( (self._dim)+ (3,), dtype = "uint8" )
250
 
        for y in range(self._dim[0]):
251
 
            for x in range(self._dim[1]):
252
 
                t = self._terr[y,x]
253
 
                mm[y,x] = colors[t.id]
254
 
 
255
 
        # Now, add the heights
256
 
        rubbish,dx = gradient(self._heights.astype("float64"))
257
 
        dx -= dx.min()
258
 
        if dx.max():
259
 
            dx /= dx.max()
260
 
        dx *= 255.
261
 
        rdx = empty( (self._dim)+(3,), dtype="float64")
262
 
        rdx[:,:,0] = dx
263
 
        rdx[:,:,1] = dx
264
 
        rdx[:,:,2] = dx
265
 
        dx = rdx
266
 
 
267
 
        # This is taken from the gimps overlay functionality
268
 
        # see here:
269
 
        # http://docs.gimp.org/en/gimp-concepts-layer-modes.html
270
 
        mm = mm / 255. * (mm + 2 * dx / 255. * (255. - mm))
271
 
 
272
 
        return mm.astype("uint8")
273
 
 
274
 
 
275
 
 
276