From 2f407242589c7c65f2ad1f9b0bac2354c08a7149 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 18 Sep 2025 17:01:10 -0500 Subject: [PATCH] Map saving first pass --- assets/CMakeLists.txt | 1 + assets/entity/entities.tsx | 2 +- assets/map/CMakeLists.txt | 6 ++ assets/map/untitled.tmx | 28 +++++ assets/palette/CMakeLists.txt | 2 +- assets/palette/palette0.png | Bin 0 -> 241 bytes assets/palette/palette0.pxo | Bin 0 -> 1576 bytes assets/palette/pallet0.png | Bin 268 -> 0 bytes assets/tileset/prarie.png | Bin 0 -> 758 bytes assets/tileset/prarie.pxo | Bin 0 -> 1314 bytes assets/tileset/prarie.tsx | 4 + assets/untitled.tiled-project | 14 +++ assets/untitled.tiled-session | 56 ++++++++++ data/map project.tiled-session | 39 +++++-- data/map.tmj | 2 +- tools/assetstool/assetcache.py | 12 +++ tools/assetstool/processasset.py | 4 +- tools/assetstool/processconfig.py | 7 +- tools/assetstool/processimage.py | 20 +++- tools/assetstool/processmap.py | 164 +++++++++++++++++++++++++++++ tools/assetstool/processpalette.py | 6 +- tools/assetstool/processtileset.py | 12 ++- 22 files changed, 355 insertions(+), 24 deletions(-) create mode 100644 assets/map/CMakeLists.txt create mode 100644 assets/map/untitled.tmx create mode 100644 assets/palette/palette0.png create mode 100644 assets/palette/palette0.pxo delete mode 100644 assets/palette/pallet0.png create mode 100644 assets/tileset/prarie.png create mode 100644 assets/tileset/prarie.pxo create mode 100644 assets/tileset/prarie.tsx create mode 100644 assets/untitled.tiled-project create mode 100644 assets/untitled.tiled-session create mode 100644 tools/assetstool/assetcache.py create mode 100644 tools/assetstool/processmap.py diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 660e4ad..334492b 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -7,4 +7,5 @@ add_subdirectory(palette)# Palette asset needs to be added before any images. add_subdirectory(config) add_subdirectory(entity) +add_subdirectory(map) add_subdirectory(ui) \ No newline at end of file diff --git a/assets/entity/entities.tsx b/assets/entity/entities.tsx index ede9f43..c7d7ecf 100644 --- a/assets/entity/entities.tsx +++ b/assets/entity/entities.tsx @@ -1,4 +1,4 @@ - + diff --git a/assets/map/CMakeLists.txt b/assets/map/CMakeLists.txt new file mode 100644 index 0000000..4f7a2b6 --- /dev/null +++ b/assets/map/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_asset(MAP untitled.tmx) \ No newline at end of file diff --git a/assets/map/untitled.tmx b/assets/map/untitled.tmx new file mode 100644 index 0000000..9f69427 --- /dev/null +++ b/assets/map/untitled.tmx @@ -0,0 +1,28 @@ + + + + + +2,3,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +9,10,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +16,17,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + diff --git a/assets/palette/CMakeLists.txt b/assets/palette/CMakeLists.txt index 947af28..d8eda53 100644 --- a/assets/palette/CMakeLists.txt +++ b/assets/palette/CMakeLists.txt @@ -3,4 +3,4 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_asset(PALETTE pallet0.png) \ No newline at end of file +add_asset(PALETTE palette0.png) \ No newline at end of file diff --git a/assets/palette/palette0.png b/assets/palette/palette0.png new file mode 100644 index 0000000000000000000000000000000000000000..e60ee7f03b8203942557e7c074c2a48812e84763 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^W*#=i1b8AR%#qqQstn z``@3-=$>}U=4HrSV4$B{aFadA`RJL~3YUHQdn=`K602tM@4fuB_ppWURp#AXdeZx9 z0{IV}KNR?5ckQNooqyjRu<-Ahf9j=YjQB&5ca2UOuD|BJbJ!dpx^({}zmS$!E+5@? oElzfP-Bo8T#q8FVdQ&MBb@0Pmh#umAu6 literal 0 HcmV?d00001 diff --git a/assets/palette/palette0.pxo b/assets/palette/palette0.pxo new file mode 100644 index 0000000000000000000000000000000000000000..070274afbdfd1fc0eef1d4f6e873b51cbf7d8a86 GIT binary patch literal 1576 zcmWIWW@Zs#VBp|jU=1>g4oRH7^$jxv!v$dm22KVBhLpsTM7^xy{Jh>7uZvb02po?; zdUE+?$=iw(3aZ;avu!=RM9Ose9k!&^r8VCta|dqZJuN=bq3%uIxBV@)i@(3S@V>JD z)AjADQRbtuI@m$Y`#bJ@IGS7+y8P7xaM%-k15v~!x zXVvFcxtJY^w!1F*t7gi)Jl@-X>e+J9C_dveN-rcE<#y=&-J;^flUnoT!Uq%I!6{X_|CBC`e7{YP+Gc(Aw`-+rb|G#}*eQd>-?ht5A5Fzt8H;lJyck zb+e9El-)}TVAk~f@aIOO)q+fibuZltg?>ja;F^=!Huaa|0){pI#m;Q64m^C;%5%lZ z=kk|VYJ4wQxI>z>CCY=oibe*oo%3B@F}uP}MuWM%c&qNh#AnAFjdr-{@=ZRNEYE$V z!C3c5B+mv1Mw@51z9=N-yS-s_LPMSmo-Xpq*-yK{oj$GJ?&(IJfGMBf#n;Y)LH~En8h7VOFZOj z@U=O5pMN9AoG;E#%lq6C-Tqh@yq7G_)e2$Qu|v`&9ldZ zbH=ZJ#m(9e51;)l#Id$@vc8(p`{Vo1EHPo+oWLPd>s|)7g7x_uj9p zFZEot`(kDu)C#Nlw9edC`b%g02kzQ;s+VPXOM|cfd7K@&!|ck1GZN)9cH|juJ^t+2 zLhmadb;Ep(w{=UjWmz_e5f9str1MB@yao(KUwM}qq=EsCp(=@6$ugIJ% zJDjs}cJwaq5Si}sYu)!2*2P)Oe0DbXPs!{LuXk*9?*D%0>kK~I_guG6IPN-sIP2p3 z(&$%NPj5f94?rmr(kFREZ3Gqt42%p6(m*ORH!(dm9#k;srxhjUrWWfP>gOa@rWVB; zhMrO6V=@$Ay$?uCb2R_0tEz4cpc$VDLSGu zKb|>p(sVWBzxFK*f{S~~W)@CTc8g5NVJTF*v6ki0N@e%x>XfwR=l(ulvyzJ!HoI$I|6IDEm^;<%rOdiF54W`M;o9^4zKzA< z6c5ipkJS&`zCY%w?z`|P@XBW8z{VMc%kTeTNA+p%d!|dkvJd1@24x&R&CE;5OinG1 zhx#~~fg!+~k%@r;cM%FS2y7yVMk-LzwV?+yL^+5QX2Bci=mw$t0%6c!90sBJDZrbR Q4Wx+~2rmL@M^+FI09ARJfdBvi literal 0 HcmV?d00001 diff --git a/assets/palette/pallet0.png b/assets/palette/pallet0.png deleted file mode 100644 index c611a09343f9dfe7d6667e42c41b2521464caac4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 268 zcmV+n0rUQeP)Px##z{m$R2Y>_%n@_La2$Z)r+0xuAy6m=0)at4Kp+qp4vWKmghH_p9P9&Bc30d7 z2owi{L18e6%mTr{fA<^gcDENEd>sylAO(H2RUc1|aaP1R7u{OiZH2w?Anb%@7ygws zP2-pnL#-S$?MH6qYF!P+GFKLpml_ka3|lVez``GOpW^0??UdZZaH6=#s!`hXF3rYi zs3}_Ksj+`N>ALQB5E1eG^=*Ak3KTHDnr|kne3s^SL&2ay;WjJHpUJXXF5m~~)lpKv S=t0&10000hHTLQ diff --git a/assets/tileset/prarie.png b/assets/tileset/prarie.png new file mode 100644 index 0000000000000000000000000000000000000000..324cdd09d8cb1b4929f7ce648f19ad4458cc1977 GIT binary patch literal 758 zcmVPx%ut`KgRCt{2oIP@bFc5_w424UMpwfp)nIc6FkR!NA>4Jl#NRcv!N$De`Oy?9q zuu_ot@fx&?zL|_XM8Jl}uJ%F31zvDP4d5H-Mzo`8N4u>?kKOi;0*d%}#r59jcs%o6 zvkE{jhR>xP4f!0KFPxvR(`@lR-x@qTpXQzC8P~(HVG*$e&DM_-_!?KRwt6wVUy zQlR=L)>wAgGHXlj2um)Ey*oqu9*#|6e>au^V|V(;E3R`hhyxq{t(o{&LJiy!Y1)yl zhgLW{FCyL?(S`*0`8q{Q%$(!Xxm;x{CCW~`8h^rtGOQ*elXx-iQPQNA$rx299^hsn z;C7!Ez!5u>1Z5`%%{T)$DowqAT3d!Zw+gpoKPRO zhVPpi%MvPQ%>&$wGw4g^1$}b^Ov=Dpy1OO*YV!W){^knJx$F4@u`Ho-N~j%&8W{t3 zjif?nHlqxhb6+6(*?6NMv{{L=`!w{N)6Y(8HNL&=LeM52Kp&+Ic|KZ3#~!x{K%);V zoopHsrS?Vy=0V0Cu1&-;`eeoTi1rmzCK*^WW@}G16 zQ+*#uo0(A6SS$Wp8>`)|f!f%WjR0iCI0yC&ly-p*$EKh^Yo3?mIP3zZ-if9VpB0Zo z#9i8mP@GcYr|hzc*|N-YpL_Q{P&#+>fm7MSFG_2)V(hYnYJyEbNezIcSjWg2D4Z;y zaI%2H$pQ+8t(}(Q0fFoTgGhWID4kqFg_8vo4%@%h18!+bZB5ivHYuz9QK4@pihUbX o>^OFgzrR+Y#GvUhS0*#cFXX^M?6Ie02mk;807*qoM6N<$f>V`Pq5uE@ literal 0 HcmV?d00001 diff --git a/assets/tileset/prarie.pxo b/assets/tileset/prarie.pxo new file mode 100644 index 0000000000000000000000000000000000000000..34d9b812c8f176bb3d4f8494c1ad3e6256d0c214 GIT binary patch literal 1314 zcmWIWW@Zs#VBp|junIGZmSWqzav~!GgC{cs11AFmLrP*vqFz>UeqQUOyLpEUcv|CG zpJq+wiV`rml72(lCpaZa+~$&sSJ1Vlitm<7pSt_p@||k+?c>ikbB-J{Pi%L5GCzK% z#+q$$SGT!L)c?8tqnvNZ)b?4*`P)0%_-f@3UW+`hxg;?3+I4~XhnB4uJJ+l(GINE@ z_UN+~tHu5H`Y&>*wY(y+u!Td(ey(w%tcZy9#M4vmPk5!f>DFm!b~XO_;n(c6j8wP1 zd3eSq^|9h4Q|F(1OLpBe3+a1dd-3Q&x5b+6nks3&=O*4SM7wm zF`vmX3n%NWiB}h}l_vbE3worGUbJma8;586^I+D#Gh**IZ=IvR)9O&!h0d40 zOW&>t%T);2(PekPiq(PQenoy$~JDw(**wqfB0Z~jNGzQ6v%9e@&V z7u@e1U}FMCSO^1yG?2>7O-xUX2gRO#T2W$dYO%hdeokU#YEiu5+Zlm*%?3PemTK-N z1S@V#EL$L(nxEt0%sbCnh~wwmFaMXUSY)#HV|e=vO`q3W{`CDQl`bx^`QNK>EibclSbx$}BJynP)OgJ?E%OvUU zf}aj?Ma7Sccm8?$X!orDyZ_CqoZ|N_dFM2*{pw$@Kl`eR0pND^(uDm>NBWwJ>EvMxV&A&7KM3uK~a`7|&)6=W#)4%Vzv+v82`VFuApPkve ze~$jm-Rbk{FU6N;xL?*u zH|I~&2U2(EpOO5#*CEFK?^fo!-W&h#X5Y2W>-q8|w Jq^(&%JOIM1O<({3 literal 0 HcmV?d00001 diff --git a/assets/tileset/prarie.tsx b/assets/tileset/prarie.tsx new file mode 100644 index 0000000..dfaa242 --- /dev/null +++ b/assets/tileset/prarie.tsx @@ -0,0 +1,4 @@ + + + + diff --git a/assets/untitled.tiled-project b/assets/untitled.tiled-project new file mode 100644 index 0000000..d0eb592 --- /dev/null +++ b/assets/untitled.tiled-project @@ -0,0 +1,14 @@ +{ + "automappingRulesFile": "", + "commands": [ + ], + "compatibilityVersion": 1100, + "extensionsPath": "extensions", + "folders": [ + "." + ], + "properties": [ + ], + "propertyTypes": [ + ] +} diff --git a/assets/untitled.tiled-session b/assets/untitled.tiled-session new file mode 100644 index 0000000..0620966 --- /dev/null +++ b/assets/untitled.tiled-session @@ -0,0 +1,56 @@ +{ + "Map/SizeTest": { + "height": 4300, + "width": 2 + }, + "activeFile": "map/untitled.tmx", + "expandedProjectPaths": [ + "map", + ".", + "tileset" + ], + "file.lastUsedOpenFilter": "All Files (*)", + "fileStates": { + "entity/entities.tsx": { + "scaleInDock": 1, + "scaleInEditor": 1 + }, + "map/prarie.tsx": { + "scaleInDock": 1, + "scaleInEditor": 11 + }, + "map/untitled.tmx": { + "scale": 3.8326562499999994, + "selectedLayer": 0, + "viewCenter": { + "x": 169.5951730604591, + "y": 108.41045293326268 + } + }, + "tileset/prarie.tsx": { + "scaleInDock": 1, + "scaleInEditor": 1 + } + }, + "last.imagePath": "/home/yourwishes/htdocs/dusk/assets/tileset", + "map.lastUsedFormat": "tmx", + "map.tileHeight": 16, + "map.tileWidth": 16, + "openFiles": [ + "entity/entities.tsx", + "map/untitled.tmx", + "tileset/prarie.tsx" + ], + "project": "untitled.tiled-project", + "recentFiles": [ + "entity/entities.tsx", + "tileset/prarie.tsx", + "map/untitled.tmx", + "map/prarie.tsx" + ], + "tileset.lastUsedFormat": "tsx", + "tileset.tileSize": { + "height": 16, + "width": 16 + } +} diff --git a/data/map project.tiled-session b/data/map project.tiled-session index 921a6ac..aaf4404 100644 --- a/data/map project.tiled-session +++ b/data/map project.tiled-session @@ -1,10 +1,26 @@ { - "activeFile": "map.tmj", + "Map/SizeTest": { + "height": 4300, + "width": 2 + }, + "activeFile": "/home/yourwishes/htdocs/dusk/assets/map/untitled.tmx", "expandedProjectPaths": [ - ".", - "templates" + "templates", + "." ], "fileStates": { + "/home/yourwishes/htdocs/dusk/assets/entity/entities.tsx": { + "scaleInDock": 1, + "scaleInEditor": 1 + }, + "/home/yourwishes/htdocs/dusk/assets/map/untitled.tmx": { + "scale": 1.9163281249999997, + "selectedLayer": 0, + "viewCenter": { + "x": 546.8792042072649, + "y": 320.14350360797425 + } + }, ":/automap-tiles.tsx": { "scaleInDock": 1 }, @@ -16,8 +32,8 @@ "scale": 3, "selectedLayer": 2, "viewCenter": { - "x": 6603.333333333333, - "y": 6846.5 + "x": 6912, + "y": 6911.833333333333 } }, "minogram.tsx": { @@ -32,17 +48,20 @@ "last.externalTilesetPath": "/home/yourwishes/htdocs/dusk/data", "last.imagePath": "/home/yourwishes/htdocs/dusk/data", "last.objectTemplatePath": "/home/yourwishes/htdocs/dusk/data/templates", + "map.lastUsedFormat": "tmx", "openFiles": [ - "map.tmj", - "overworld.tsx" + "/home/yourwishes/htdocs/dusk/assets/map/untitled.tmx", + "/home/yourwishes/htdocs/dusk/assets/entity/entities.tsx" ], "project": "map project.tiled-project", "property.type": "string", "recentFiles": [ - "overworld.tsx", + "/home/yourwishes/htdocs/dusk/assets/entity/entities.tsx", + "/home/yourwishes/htdocs/dusk/assets/map/untitled.tmx", "map.tmj", - "minogram.tsx", - "entities.tsx" + "overworld.tsx", + "entities.tsx", + "minogram.tsx" ], "tileset.lastUsedFilter": "Tiled tileset files (*.tsx *.xml)", "tileset.lastUsedFormat": "tsx", diff --git a/data/map.tmj b/data/map.tmj index c310d4a..f5b013c 100644 --- a/data/map.tmj +++ b/data/map.tmj @@ -455,7 +455,7 @@ "nextobjectid":14, "orientation":"orthogonal", "renderorder":"right-down", - "tiledversion":"1.11.1", + "tiledversion":"1.11.2", "tileheight":16, "tilesets":[ { diff --git a/tools/assetstool/assetcache.py b/tools/assetstool/assetcache.py new file mode 100644 index 0000000..9c1d028 --- /dev/null +++ b/tools/assetstool/assetcache.py @@ -0,0 +1,12 @@ +processedAssets = {} + +def assetGetCache(assetPath): + if assetPath in processedAssets: + return processedAssets[assetPath] + return None + +def assetCache(assetPath, processedData): + if assetPath in processedAssets: + return processedAssets[assetPath] + processedAssets[assetPath] = processedData + return processedData \ No newline at end of file diff --git a/tools/assetstool/processasset.py b/tools/assetstool/processasset.py index 4b5f1a2..6411f3a 100644 --- a/tools/assetstool/processasset.py +++ b/tools/assetstool/processasset.py @@ -4,13 +4,13 @@ from processimage import processImage from processpalette import processPalette from processconfig import processConfig from processtileset import processTileset +from processmap import processMap processedAssets = [] def processAsset(asset): if asset['path'] in processedAssets: return - processedAssets.append(asset['path']) # Handle tiled tilesets @@ -23,6 +23,8 @@ def processAsset(asset): return processConfig(asset) elif t == 'tileset': return processTileset(asset) + elif t == 'map': + return processMap(asset) else: print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'") sys.exit(1) \ No newline at end of file diff --git a/tools/assetstool/processconfig.py b/tools/assetstool/processconfig.py index 40df4eb..2c17697 100644 --- a/tools/assetstool/processconfig.py +++ b/tools/assetstool/processconfig.py @@ -2,9 +2,14 @@ import os import sys from args import args from assethelpers import getAssetRelativePath +from assetcache import assetGetCache, assetCache def processConfig(asset): assetPath = asset['path'] + cache = assetGetCache(assetPath) + if cache is not None: + return cache + print(f"Processing config: {assetPath}") # Takes each line, seperates it by either semicolon or newline, @@ -41,4 +46,4 @@ def processConfig(asset): outConfig = { "files": [ outputFilePath ], } - return outConfig \ No newline at end of file + return assetCache(assetPath, outConfig) \ No newline at end of file diff --git a/tools/assetstool/processimage.py b/tools/assetstool/processimage.py index 85b0ed4..7ed0ec6 100644 --- a/tools/assetstool/processimage.py +++ b/tools/assetstool/processimage.py @@ -4,24 +4,32 @@ from PIL import Image from processpalette import extractPaletteFromImage, palettes from args import args from assethelpers import getAssetRelativePath +from assetcache import assetGetCache, assetCache images = [] def processImage(asset): + cache = assetGetCache(asset['path']) + if cache is not None: + return cache + type = None if 'type' in asset['options']: type = asset['options'].get('type', 'PALETTIZED').upper() if type == 'PALETTIZED' or type is None: - return processPalettizedImage(asset) + return assetCache(asset['path'], processPalettizedImage(asset)) elif type == 'ALPHA': - return processAlphaImage(asset) + return assetCache(asset['path'], processAlphaImage(asset)) else: print(f"Error: Unknown image type {type} for asset {asset['path']}") sys.exit(1) def processPalettizedImage(asset): assetPath = asset['path'] + cache = assetGetCache(assetPath) + if cache is not None: + return cache image = Image.open(assetPath) imagePalette = extractPaletteFromImage(image) @@ -72,10 +80,14 @@ def processPalettizedImage(asset): 'width': image.width, 'height': image.height, } - return outImage + return assetCache(assetPath, outImage) def processAlphaImage(asset): assetPath = asset['path'] + cache = assetGetCache(assetPath) + if cache is not None: + return cache + print(f"Processing alpha image: {assetPath}") data = bytearray() @@ -101,4 +113,4 @@ def processAlphaImage(asset): 'width': image.width, 'height': image.height, } - return outImage \ No newline at end of file + return assetCache(assetPath, outImage) \ No newline at end of file diff --git a/tools/assetstool/processmap.py b/tools/assetstool/processmap.py new file mode 100644 index 0000000..15764bb --- /dev/null +++ b/tools/assetstool/processmap.py @@ -0,0 +1,164 @@ +import sys +import os +from args import args +from xml.etree import ElementTree as ET +from processtileset import processTileset +from assetcache import assetCache, assetGetCache +from assethelpers import getAssetRelativePath + +def processMap(asset): + cache = assetGetCache(asset['path']) + if cache is not None: + return cache + + # Load the TMX file + tree = ET.parse(asset['path']) + root = tree.getroot() + + # Root needs to be "map" element. + if root.tag != 'map': + print(f"Error: TMX file {asset['path']} does not have a root element") + sys.exit(1) + + # Root needs to be orientation="orthogonal" + if 'orientation' not in root.attrib or root.attrib['orientation'] != 'orthogonal': + print(f"Error: TMX file {asset['path']} does not have orientation='orthogonal'") + sys.exit(1) + + # Extract width, height, tilewidth, tileheight attributes + if 'width' not in root.attrib or 'height' not in root.attrib or 'tilewidth' not in root.attrib or 'tileheight' not in root.attrib: + print(f"Error: TMX file {asset['path']} is missing required attributes (width, height, tilewidth, tileheight)") + sys.exit(1) + + mapWidth = int(root.attrib['width']) + mapHeight = int(root.attrib['height']) + tileWidth = int(root.attrib['tilewidth']) + tileHeight = int(root.attrib['tileheight']) + + # Find all tileset elements + tilesets = [] + for tilesetElement in root.findall('tileset'): + # Tileset must have a source attribute + if 'source' not in tilesetElement.attrib: + print(f"Error: element in {asset['path']} is missing a source attribute") + sys.exit(1) + # Must have a firstgid attribute + if 'firstgid' not in tilesetElement.attrib: + print(f"Error: element in {asset['path']} is missing a firstgid attribute") + sys.exit(1) + + firstGid = int(tilesetElement.attrib['firstgid']) + source = tilesetElement.attrib['source'] + + # Get source path relative to the tmx file's working directory. + # Needs normalizing also since ".." is often used. + source = os.path.normpath(os.path.join(os.path.dirname(asset['path']), source)) + tileset = processTileset({ 'path': source, 'type': 'tileset', 'options': {} }) + + tilesets.append({ + 'firstGid': firstGid, + 'source': source, + 'tileset': tileset + }) + + # Sort tilesets by firstGid, highest first + tilesets.sort(key=lambda x: x['firstGid'], reverse=True) + + # Layer types + # objectLayers = [] # Not implemented + tileLayers = [] + for layerElement in root.findall('layer'): + # Assume tile layer for now + # Must have id, name, width, height attributes + if 'id' not in layerElement.attrib or 'name' not in layerElement.attrib or 'width' not in layerElement.attrib or 'height' not in layerElement.attrib: + print(f"Error: element in {asset['path']} is missing required attributes (id, name, width, height)") + sys.exit(1) + + id = int(layerElement.attrib['id']) + name = layerElement.attrib['name'] + width = int(layerElement.attrib['width']) + height = int(layerElement.attrib['height']) + + # Need exactly one data element + dataElements = layerElement.findall('data') + if len(dataElements) != 1: + print(f"Error: element in {asset['path']} must have exactly one child element") + sys.exit(1) + + # Get text, remove whitespace, split by comman and convert to int + dataElement = dataElements[0] + if dataElement.attrib.get('encoding', '') != 'csv': + print(f"Error: element in {asset['path']} must have encoding='csv'") + sys.exit(1) + + dataText = dataElement.text.strip() + data = [int(gid) for gid in dataText.split(',') if gid.strip().isdigit()] + + # Should be exactly width * height entries + if len(data) != width * height: + print(f"Error: element in {asset['path']} has {len(data)} entries but expected {width * height} (width * height)") + sys.exit(1) + + tileLayers.append({ + 'id': id, + 'name': name, + 'width': width, + 'height': height, + 'data': data, + }) + + # Now we have our layers all parsed out. + data = bytearray() + data += b'drm' # Dusk RPG Map + data += mapWidth.to_bytes(4, 'little') # Map width in tiles + data += mapHeight.to_bytes(4, 'little') # Map height in tiles + data += len(tilesets).to_bytes(4, 'little') # Number of tilesets + data += len(tileLayers).to_bytes(4, 'little') # Number of layers + + # For each tileset + for tileset in tilesets: + data += tileset['firstGid'].to_bytes(4, 'little') # First GID + + # For each layer... + for layer in tileLayers: + for gid in layer['data']: + # Get tileset for this gid, since the tilesets are already sorted we can + # simply find the first one that has firstGid <= gid + if gid == 0: + # Empty tile + localIndex = 0xFF + tilesetIndex = 0xFFFFFFFF + else: + for tileset in tilesets: + if gid >= tileset['firstGid']: + tilesetIndex = tilesets.index(tileset) + localIndex = gid - tileset['firstGid'] + break + else: + # If no tileset was found, this is an invalid gid + print(f"Error: Invalid tile GID {gid} in {asset['path']}") + sys.exit(1) + + if localIndex > 255: + print(f"Error: Local tile index {localIndex} exceeds 255 in {asset['path']}") + sys.exit(1) + + data += tilesetIndex.to_bytes(4, 'little') # Tileset index + data += localIndex.to_bytes(1, 'little') # Local tile index + + relative = getAssetRelativePath(asset['path']) + fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0] + outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.drm") + outputFilePath = os.path.join(args.output_assets, outputFileRelative) + os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) + with open(outputFilePath, "wb") as f: + f.write(data) + + outMap = { + 'mapPath': outputFileRelative, + 'files': [ outputFilePath ], + 'width': mapWidth, + 'height': mapHeight, + } + + return assetCache(asset['path'], outMap) \ No newline at end of file diff --git a/tools/assetstool/processpalette.py b/tools/assetstool/processpalette.py index d45a457..ae8d22c 100644 --- a/tools/assetstool/processpalette.py +++ b/tools/assetstool/processpalette.py @@ -3,6 +3,7 @@ from PIL import Image from args import args import sys import datetime +from assetcache import assetCache, assetGetCache palettes = [] @@ -22,6 +23,9 @@ def extractPaletteFromImage(image): def processPalette(asset): print(f"Processing palette: {asset['path']}") + cache = assetGetCache(asset['path']) + if cache is not None: + return cache paletteIndex = len(palettes) image = Image.open(asset['path']) @@ -72,7 +76,7 @@ def processPalette(asset): } palettes.append(palette) - return palette + return assetCache(asset['path'], palette) def processPaletteList(): data = f"// Auto-generated palette list\n" diff --git a/tools/assetstool/processtileset.py b/tools/assetstool/processtileset.py index a10d119..970ab65 100644 --- a/tools/assetstool/processtileset.py +++ b/tools/assetstool/processtileset.py @@ -6,6 +6,7 @@ import os import datetime from args import args from xml.etree import ElementTree +from assetcache import assetGetCache, assetCache def loadTilesetFromTSX(asset): # Load the TSX file @@ -99,8 +100,11 @@ def loadTilesetFromArgs(asset): } def processTileset(asset): - print(f"Processing tileset: {asset['path']}") + cache = assetGetCache(asset['path']) + if cache is not None: + return cache + print(f"Processing tileset: {asset['path']}") tilesetData = None if asset['path'].endswith('.tsx'): tilesetData = loadTilesetFromTSX(asset) @@ -135,9 +139,9 @@ def processTileset(asset): with open(outputFile, 'w') as f: f.write(data) - outTileset = { + return assetCache(asset['path'], { + "files": [], "image": tilesetData['image'], "headerFile": outputFile, "files": tilesetData['image']['files'], - } - return outTileset \ No newline at end of file + }) \ No newline at end of file