1""" 2Pekka Kana 2 map support for Tiled 32012, <samuli@tuomola.net> 4 5Notes: 6- should make PK2 classes mixins with Tiled ones 7""" 8from lib import cpystruct 9from os.path import dirname, exists 10from struct import pack,unpack,Struct 11from base64 import b64encode, b64decode 12import os, sys, re, string 13import tiled as T 14 15maps = [] 16 17class PK2(T.Plugin): 18 @classmethod 19 def nameFilter(cls): 20 return "Pekka Kana 2 (*.map)" 21 22 @classmethod 23 def shortName(cls): 24 return "pk2" 25 26 @classmethod 27 def supportsFile(cls, f): 28 return open(f, 'rb').read(4) == b'1.3\0' 29 30 @classmethod 31 def read(cls, f): 32 lvl = PK2MAP() 33 with open(f, 'rb') as fh: 34 lvl.unpack(fh) 35 # spriteCount is +1 36 fh.seek(-len(lvl.spriteFiles[0]), 1) 37 lay1 = PK2MAPLAYER(fh) 38 lay2 = PK2MAPLAYER(fh) 39 lay3 = PK2MAPLAYER(fh) 40 print(lvl) 41 42 # -- tileset 43 img = T.qt.QImage() 44 imgfile = dirname(f)+'/../../gfx/tiles/'+str(lvl.tileFile) 45 img.load(imgfile, 'BMP') 46 t = T.Tiled.Tileset.create('Tiles', 32,32, 0, 0) 47 t.data().setTransparentColor(T.qt.QColor(img.color(255))) 48 t.data().loadFromImage(img, imgfile) 49 50 # find common bounding box for the layers 51 bb = ['','',10,10] 52 for l in [lay1,lay2,lay3]: 53 print(l) 54 bb[0] = min([bb[0], l.lx.num]) 55 bb[1] = min([bb[1], l.ly.num]) 56 bb[2] = max([bb[2], l.width()]) 57 bb[3] = max([bb[3], l.height()]) 58 59 print('bounds', bb) 60 61 m = T.Tiled.Map(T.Tiled.Map.Orthogonal, bb[2], bb[3], 32,32) 62 maps.append(m) 63 64 # -- background image 65 lai = T.Tiled.ImageLayer('Scenery', bb[2], bb[3]) 66 img = T.qt.QImage() 67 imgfile = dirname(f)+'/../../gfx/scenery/'+str(lvl.fieldFile) 68 img.load(imgfile, 'BMP') 69 lai.loadFromImage(img, imgfile) 70 71 # -- layers 72 la1 = T.Tiled.TileLayer('Back', 0,0, bb[2], bb[3]) 73 lay1.doTiles(t, la1, bb) 74 75 la2 = T.Tiled.TileLayer('Front', 0,0, bb[2], bb[3]) 76 lay2.doTiles(t, la2, bb) 77 78 sprdir = dirname(f)+'/../../sprites/' 79 lay3.sprites = [0] 80 lay3.spriteGfx = {} 81 82 for s in lvl.spriteFiles: 83 # spriteCount is +1 84 if not exists(sprdir+str(s)): break 85 86 spr = PK2SPR(sprdir+str(s), m) 87 88 if not lay3.spriteGfx.has_key(str(spr.kuvatiedosto)): 89 sprfile = find_case_insensitive_filename(sprdir, str(spr.kuvatiedosto)) 90 img = T.qt.QImage() 91 img.load(sprdir+sprfile, 'BMP') 92 print('loading', sprdir+sprfile) 93 sprts = T.Tiled.Tileset.create(sprfile, 32,32, 0, 0) 94 sprts.data().setTransparentColor(T.qt.QColor(img.color(255))) 95 sprts.data().loadFromImage(img, sprdir+sprfile) 96 lay3.spriteGfx[str(spr.kuvatiedosto)] = sprts 97 98 #sprgfx[(str(spr.kuvatiedosto] 99 lay3.sprites.append(spr) 100 101 #print spr 102 103 la3 = T.Tiled.ObjectGroup('Sprites', bb[2], bb[3]) 104 lay3.doSprites(la3, bb) 105 m.addLayer(lai) 106 m.addTileset(t) 107 for sprts in lay3.spriteGfx.values(): 108 m.addTileset(sprts) 109 m.addLayer(la1) 110 m.addLayer(la2) 111 m.addLayer(la3) 112 113 for f in lvl.__slots__: 114 val = repr(getattr(lvl, f)) 115 m.setProperty(f, b64encode(val)) 116 117 return m 118 119 @classmethod 120 def write(cls, m, fn): 121 out = PK2MAP() 122 123 for f in m.properties().keys(): 124 if not f.startswith('__'): 125 setattr(out, f, b64decode(m.property(f))) 126 127 #setattr(out, "sprites", ['pla']) 128 129 with open(fn, 'wb') as fh: 130 print(out.pack(), file=fh) 131 132 for i in range(m.layerCount()): 133 tiles = [] 134 l = 0 135 if isTileLayerAt(m, i): 136 l = tileLayerAt(m, i) 137 print(l) 138 elif isObjectGroupAt(m, i): 139 #l = objectGroupAt(m, i) 140 continue 141 142 for y in range(l.height()): 143 for x in range(l.width()): 144 if l.cellAt(x, y).tile != None: 145 tiles.append( l.cellAt(x, y).tile.id() ) 146 print(0,0, l.width(), l.height(), file=fh) 147 print(bytearray(tiles), file=fh) 148 149 return True 150 151def find_case_insensitive_filename(path, fn): 152 for f in os.listdir(path): 153 if f.lower() == fn.lower(): 154 return f 155 156class asciilongfile(cpystruct.CpyStruct("char filename[100]")): 157 @classmethod 158 def fromraw(cls, v): 159 return re.search('[\w\.]*', v, re.U).group(0) 160 def __repr__(self): 161 return str(self) 162 def __str__(self): 163 return self.filename 164 165class asciifile(cpystruct.CpyStruct("char filename[13]")): 166 @classmethod 167 def fromraw(cls, v): 168 return re.search('[\w\.]*', v, re.U).group(0) 169 def __repr__(self): 170 return str(self) 171 def __str__(self): 172 return self.filename 173 174class asciitxt(cpystruct.CpyStruct("char txt[40]")): 175 @classmethod 176 def fromraw(cls, v): 177 return re.search('[\w\. ]', v, re.U).group(0) 178 179class asciinum(cpystruct.CpyStruct("char num[8]")): 180 @classmethod 181 def fromraw(cls, v): 182 #v = ''.join(re.findall('[0-9]', v)) 183 v = re.sub('[^0-9]','',v) 184 return 0 if not v.strip().isdigit() else int(v) 185 186class PK2MAPLAYER(cpystruct.CpyStruct("asciinum lx, ly, w, h;")): 187 MAXW = 256 188 MAXH = 224 189 MAXSZ = MAXW*MAXH 190 191 def width(self): 192 return self.w.num + 1 193 def height(self): 194 return self.h.num + 1 195 196 def __init__(self, dat): 197 # should make cpystruct support this usecase better 198 super(self.__class__, self).__init__(dat) 199 200 #print str(cpystruct.peek(dat, 128)) 201 202 self.layer = bytearray(self.MAXSZ) 203 for i in range(len(self.layer)): self.layer[i] = 0xff 204 205 for y in range(self.ly.num, self.ly.num+self.height()): 206 for x in range(self.lx.num, self.lx.num+self.width()): 207 self.layer[x+y*self.MAXW] = dat.read(1) 208 209 def findBounds(self): 210 "find bounding box for coords that have tiles" 211 mx,my,mw,mh = None,None,10,10 212 213 for y in range(self.ly, self.ly+self.height()): 214 for x in range(self.lx, self.lx+self.width()): 215 if self.layer[x + y * self.MAXW] != 255: 216 if not my: my = y 217 if not mx or x < mx: mx = x 218 if x > mw: mw = x 219 if y > mh: mh = y 220 221 if not mx: mx = 0 222 if not my: my = 0 223 return mx, my, mw, mh 224 225 def doSprites(self, la, bb): 226 for y in range(self.height()): 227 for x in range(self.width()): 228 sprite = self.layer[self.lx.num + x + (self.ly.num + y) * self.MAXW] 229 if sprite != 255: 230 #if sprite > len(self.sprites): 231 # print 'invalid spr',sprite 232 # continue 233 rx = self.lx.num + x - bb[0] 234 ry = self.ly.num + y - bb[1] + 1 235 spr = self.sprites[sprite] 236 obj = T.Tiled.MapObject(str(spr.kuvatiedosto), '', T.qt.QPointF(rx, ry), T.qt.QSizeF(1, 1)) #spr.width, spr.height)) 237 # 0 should point to the actual sprite but how? 238 obj.setCell(Tiled.Cell(self.spriteGfx[str(spr.kuvatiedosto)].data().tileAt(0))) 239 la.addObject(obj) 240 241 def doTiles(self, ts, la, bb): 242 for y in range(self.height()): 243 for x in range(self.width()): 244 tile = self.layer[self.lx.num + x + (self.ly.num + y) * self.MAXW] 245 if tile != 255: 246 rx = self.lx.num + x - bb[0] 247 ry = self.ly.num + y - bb[1] 248 if tile == 149: print('start @',rx,ry) 249 if tile == 150: print('end @',rx,ry) 250 ti = ts.data().tileAt(tile) 251 if ti != None and rx < bb[2] and ry < bb[3]: 252 # app should check that coords are within layer 253 #print rx,ry,self.ly,y 254 la.setCell(rx, ry, T.Tiled.Cell(ti)) 255 else: 256 print('invalid',rx,ry) 257 258class PK2SPR_ANIM(cpystruct.CpyStruct(""" 259 uchar seq[10]; 260 uchar frames; 261 bool loop; 262""")): 263 def __repr__(self): 264 return str(self) 265 266class PK2SPR(cpystruct.CpyStruct(""" 267asciinum tyyppi; 268asciilongfile kuvatiedosto; 269asciilongfile aanitiedostot[7]; 270int aanet[7]; 271uchar frameja; 272PK2SPR_ANIM animaatiot[20]; 273uchar animaatioita; 274uchar frame_rate; 275int kuva_x; 276int kuva_y; 277int kuva_frame_leveys; 278int kuva_frame_korkeus; 279int kuva_frame_vali; 280char nimi[30]; 281int width, height; 282double paino; 283bool vihollinen; 284int energia; 285int vahinko; 286uchar vahinko_tyyppi; 287uchar suojaus; 288int pisteet; 289int AI[10]; 290uchar max_hyppy; 291double max_nopeus; 292int latausaika; 293uchar vari; 294bool este; 295int tuhoutuminen; 296bool avain; 297bool tarisee; 298uchar bonusten_lkm; 299int hyokkays1_aika; 300int hyokkays2_aika; 301int pallarx_kerroin; 302char muutos_sprite[100]; 303char bonus_sprite[100]; 304char ammus1_sprite[100]; 305char ammus2_sprite[100]; 306 307bool tiletarkistus; 308DWORD aani_frq; 309bool random_frq; 310 311bool este_ylos; 312bool este_alas; 313bool este_oikealle; 314bool este_vasemmalle; 315 316uchar lapinakyvyys; 317bool hehkuu; 318int tulitauko; 319bool liitokyky; 320bool boss; 321bool bonus_aina; 322bool osaa_uida; 323""")): 324 def __init__(self, f, m): 325 with open(f, 'rb') as fh: 326 super(self.__class__, self).__init__(fh) 327 328class PK2MAP(cpystruct.CpyStruct(""" 329 char ver[4]; 330 BYTE nan; 331 asciifile tileFile, fieldFile, musicFile; 332 asciitxt mapName, author; 333 asciinum lvl, air, trig1, trig2, trig3; 334 asciinum playTime, v1, background, plrSpr; 335 asciinum lvlX, lvlY, icon; 336 asciinum spriteCount; 337 asciifile spriteFiles[spriteCount]; 338""")): pass 339 340