source: trunk/game/utilities/transition.py @ 415

Revision 328, 13.6 KB checked in by Kaydeth_parpg, 10 years ago (diff)

Ticket #2: Patch by Kaydeth. Updated the classes in local_loaders and utilities directories to correct some obvious discrepancies with our coding standard. comment[s:trac, t:2] fixes[s:trac, t:2]

  • Property svn:eol-style set to native
  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2
3#   This file is part of PARPG.
4
5#   PARPG is free software: you can redistribute it and/or modify
6#   it under the terms of the GNU General Public License as published by
7#   the Free Software Foundation, either version 3 of the License, or
8#   (at your option) any later version.
9
10#   PARPG is distributed in the hope that it will be useful,
11#   but WITHOUT ANY WARRANTY; without even the implied warranty of
12#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#   GNU General Public License for more details.
14
15#   You should have received a copy of the GNU General Public License
16#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
17
18import sys,cPickle
19from xml.sax import make_parser
20from xml.sax.handler import ContentHandler
21
22# this code is for building the transition layer for the map
23# the world map is built of two layers: one for the world floor, and the other
24# for the all the objects (including the player and NPC)
25# This program accepts one argument, the original XML map file,
26# and outputs another file with 1 or more added layers: the layers holding
27# the information for the transition tiles that are rendered over the ground
28
29# usage: transition.py mapfile
30# outputs file new.xml, a simple text file that contains ONLY the new layers
31# needed in the mapfile. At the moment you have to splice these in by hand;
32# they must come AFTER the ground layer but BEFORE the building and objects
33# layers. PM maximinus at the PARPG forums if any questions.
34
35# some simple defines for each part of the tile
36TOP             =   1
37RIGHT           =   2
38BOTTOM          =   4
39LEFT            =   8
40TOP_LEFT        =   16
41TOP_RIGHT       =   32
42BOTTOM_RIGHT    =   64
43BOTTOM_LEFT     =   128
44
45# side transition tiles always block corner tiles
46# but which ones?
47TOP_BLOCK       =   TOP_RIGHT+TOP_LEFT
48RIGHT_BLOCK     =   TOP_RIGHT+BOTTOM_RIGHT
49BOTTOM_BLOCK    =   BOTTOM_RIGHT+BOTTOM_LEFT
50LEFT_BLOCK      =   TOP_LEFT+BOTTOM_LEFT
51NONE            =   0
52
53# now for each of the 15 different possible side variations we
54# can know what corner pieces do not need to be drawn
55# this table stores all of the allowed combinations
56# based on the bit pattern for the side elements
57
58CORNER_LOOKUP   =   [BOTTOM_BLOCK,  LEFT_BLOCK,
59                     BOTTOM_LEFT,   TOP_BLOCK,
60                     NONE,          TOP_LEFT,
61                     NONE,          RIGHT_BLOCK,
62                     BOTTOM_RIGHT,  NONE,
63                     NONE,          TOP_RIGHT,
64                     NONE,          NONE,
65                     NONE]
66
67class XMLTileData:
68    def __init__(self, x, y, z, o, i=None):
69        self.x = x
70        self.y = y
71        self.z = z
72        self.object = o
73        self.ident = i
74
75class XMLLayerData:
76    """Class to store one complete layer"""
77    def __init__(self, x, y, name):
78        self.x_scale = x
79        self.y_scale = y
80        self.name = name
81        self.tiles = []
82
83class LocalXMLParser(ContentHandler):
84    """Class inherits from ContantHandler, and is used to parse the
85       local map data"""
86    def __init__(self):
87        self.search = "map"
88        self.layers = []
89        self.current_layer = False
90        self.final = []
91   
92    def startElement(self, name, attrs):
93        """Called every time we meet a new element"""
94        # we are only looking for the 'layer' elements, the rest we ignore
95        if(name == "layer"):
96            # grab the data and store that as well
97            try:
98                x = attrs.getValue('x_scale')
99                y = attrs.getValue('y_scale')
100                name = attrs.getValue('id')
101            except(KeyError):
102                sys.stderr.write("Error: Layer information invalid")
103                sys.exit(False)
104            # start a new layer
105            self.layers.append(XMLLayerData(x, y, name))
106            self.current_layer = True
107        elif(name == "i"):
108            # have a current layer?
109            if self.current_layer == False:
110                sys.stderr.write("Error: item data outside of layer\n")
111                sys.exit(False)
112            # ok, it's ok, let's parse and add the data
113            try:
114                x = attrs.getValue('x')
115                y = attrs.getValue('y')
116                z = attrs.getValue('z')
117                o = attrs.getValue('o')
118            except(KeyError):
119                sys.stderr.write("Error: Data missing in tile definition\n")
120                sys.exit(False)
121            try:
122                i = attrs.getValue('id')
123            except(KeyError):
124                i = None
125            # convert tile co-ords to integers
126            x = float(x)
127            y = float(y)
128            z = float(z)
129            # now we have the tile data, save it for later
130            self.layers[-1].tiles.append(XMLTileData(x, y, z, o, i))
131
132    def endElement(self, name):
133        if(name == "layer"):
134            # end of current layer
135            self.current_layer=False
136
137class LocalMap:
138    def __init__(self):
139        self.layers = []
140        self.ttiles = []
141        self.render_tiles =[]
142        self.min_x = 0
143        self.max_x = 0
144        self.min_y = 0
145        self.max_y = 0
146
147    def outputTransLayer(self, l_file, l_count):
148        if(len(self.render_tiles) == 0):
149            return True
150        try:
151            layer_name = "TransitionLayer" + str(l_count)
152            l_file.write('''    <layer x_offset="0.0" pathing="''')
153            l_file.write('''cell_edges_and_diagonals" y_offset="0.0"''')
154            l_file.write(''' grid_type="square" id="''')
155            l_file.write(layer_name + '''"''')
156            l_file.write(''' x_scale="1" y_scale="1" rotation="0.0">\n''')
157            l_file.write('        <instances>\n')
158            for tile in self.render_tiles:
159                l_file.write('''            <i x="''')
160                l_file.write(str(tile.x))
161                l_file.write('''" o="''')
162                l_file.write(tile.object)
163                l_file.write('''" y="''')
164                l_file.write(str(tile.y))
165                l_file.write('''" r="0" z="0.0"></i>\n''')
166            l_file.write('        </instances>\n    </layer>\n')
167        except(IOError):
168            sys.stderr.write("Error: Couldn't write data")
169            return False
170        return True
171
172    def GetSurroundings(self, x, y, search):
173        """Function called by buildTransLayer to see if a tile needs to
174           display transition graphics over it (drawn on another layer)"""
175        # check all of the tiles around the current tile
176        value=0
177        if(self.pMatchSearch(x,y+1,search) == True):
178            value += RIGHT
179        if(self.pMatchSearch(x-1,y+1,search) == True):
180            value += BOTTOM_RIGHT
181        if(self.pMatchSearch(x-1,y,search) == True):
182            value += BOTTOM
183        if(self.pMatchSearch(x-1,y-1,search) == True):
184            value += BOTTOM_LEFT
185        if(self.pMatchSearch(x,y-1,search) == True):
186            value += LEFT
187        if(self.pMatchSearch(x+1,y-1,search) == True):
188            value += TOP_LEFT
189        if(self.pMatchSearch(x+1,y,search) == True):
190            value += TOP
191        if(self.pMatchSearch(x+1,y+1,search) == True):
192            value += TOP_RIGHT
193        return value
194
195    def getTransitionTiles(self, search):
196        """Build up and return a list of the tiles that might
197           need a transition tiles layed over them"""
198        size = len(search)
199        tiles = []
200        for t in self.layers[0].tiles:
201            # we are only interested in tiles that DON'T have what we are
202            # are looking for (because they might need a transition gfx)
203            if(t.object != None and t.object[:size] != search):
204                # whereas now we we need to check all the tiles around
205                # this tile
206                trans_value = self.GetSurroundings(t.x, t.y, search)
207                if(trans_value != 0):
208                    # we found an actual real transition
209                    tiles.append([t.x, t.y, trans_value])
210        return tiles
211
212    def getTransitionName(self, base, value, corner=False):
213        if(corner == False):
214            name = base + "-ts"
215        else:
216            name = base + "-tc"
217        if(value < 10):
218            name += "0"
219        name += str(value)
220        return name
221
222    def buildTransLayer(self, search):
223        """Build up the data for a transition layer
224           search is the string that matches the start of the name of
225           each tile that we are looking for"""
226        transition_tiles = self.getTransitionTiles(search)       
227        # now we have all the possible tiles, lets see what they
228        # actually need to have rendered
229        for t in transition_tiles:
230            # first we calculate the side tiles:
231            sides = (t[2]&15)
232            if(sides != 0):
233                # there are some side tiles to be drawn. Now we just
234                # need to see if there are any corners to be done
235                corners = (t[2]&240)&(CORNER_LOOKUP[sides-1])                   
236                if(corners != 0):
237                    # we must add a corner piece as well
238                    corners = corners/16
239                    name = self.getTransitionName(search, corners, True)
240                    self.ttiles.append(XMLTileData(t[0], t[1], 0, name))
241                # add the side tile pieces
242                name = self.getTransitionName(search, sides, False)
243                self.ttiles.append(XMLTileData(t[0], t[1], 0, name))
244            else:
245                # there are no side tiles, so let's just look at
246                # the corners (quite easy):
247                corners = (t[2]&240)/16
248                if(corners != 0):
249                    # there is a corner piece needed
250                    name = self.getTransitionName(search, corners, True)
251                    self.ttiles.append(XMLTileData(t[0], t[1], 0, name))
252
253    def loadFromXML(self, filename):
254        """Load a map from the XML file used in Fife
255           Returns True if it worked, False otherwise"""
256        try:
257            map_file = open(filename,'rt')
258        except(IOError):
259            sys.stderr.write("Error: No map given!\n")
260            return(False)
261        # now open and read the XML file
262        parser = make_parser()
263        cur_handler = LocalXMLParser()
264        parser.setContentHandler(cur_handler)
265        parser.parse(map_file)
266        map_file.close()
267        # make a copy of the layer data
268        self.layers = cur_handler.layers
269        return True
270   
271    def getSize(self):
272        """getSize stores the size of the grid"""
273        for t in self.layers[0].tiles:
274            if t.x > self.max_x:
275                self.max_x = t.x
276            if t.x < self.min_x:
277                self.min_x = t.x
278            if t.y > self.max_y:
279                self.max_y = t.y
280            if t.y < self.min_y:
281                self.min_y = t.y
282   
283    def checkRange(self, x, y):
284        """Grid co-ords in range?"""
285        if((x < self.min_x) or (x > self.max_x) or
286           (y < self.min_y) or (y > self.max_y)):
287           return False
288        return True
289
290    def pMatchSearch(self, x, y, search):
291        """Brute force method used for matching grid"""
292        # is the tile even in range?
293        if(self.checkRange(x, y) == False):
294            return False
295        size = len(search)
296        for t in self.layers[0].tiles:
297            if((t.x == x) and (t.y == y) and (t.object[:size] == search)):
298                return(True)
299        # no match
300        return False
301
302    def coordsMatch(self, x, y, tiles):
303        """Helper routine to check wether the list of tiles
304           in tiles has any contain the coords x,y"""
305        for t in tiles:
306            if((t.x == x) and (t.y == y)):
307                return True
308        # obviously no match
309        return False
310
311    def saveMap(self, filename):
312        """Save the new map"""
313        # open the new files for writing
314        try:
315            map_file = open(filename, 'wt')
316        except(IOError):
317            sys.stderr.write("Error: Couldn't save map\n")
318            return(False)
319        # we don't know how many layers we need, let's do that now
320        # this is a brute force solution but it does work, and speed
321        # is not required in this utility
322        layer_count = 0
323        while(self.ttiles != []):
324            recycled_tiles = []
325            self.render_tiles = []
326            for t in self.ttiles:
327                if(self.coordsMatch(t.x, t.y, self.render_tiles) == False):
328                    # no matching tile in the grid so far, so add it
329                    self.render_tiles.append(t)
330                else:
331                    # we must save this for another layer
332                    recycled_tiles.append(t)
333            # render this layer
334            if(self.outputTransLayer(map_file, layer_count) == False):
335                return False
336            layer_count += 1
337            self.ttiles = recycled_tiles
338        # phew, that was it
339        map_file.close()
340        print "Output new file as new.xml"
341        print "Had to render", layer_count, "layers"
342        return True
343   
344    def printDetails(self):
345        """Debugging routine to output some details about the map
346           Used to check the map loaded ok"""
347        # display each layer name, then the details
348        print "Layer ID's:",
349        for l in self.layers:
350            print l.name,
351        print "\nMap Dimensions: X=", (self.max_x-self.min_x) + 1,
352        print " Y=", (self.max_y-self.min_y) + 1
353
354if __name__=="__main__":
355    # pass a map name as the first argument
356    if(len(sys.argv) < 2):
357        sys.stderr.write("Error: No map given!\n")
358        sys.exit(False)
359       
360    new_map = LocalMap()
361    if(new_map.loadFromXML(sys.argv[1]) == True):
362        new_map.getSize()
363        new_map.buildTransLayer("grass")
364        new_map.saveMap("new.xml")
365        new_map.printDetails()
366
Note: See TracBrowser for help on using the repository browser.