diff options
Diffstat (limited to '')
| -rw-r--r-- | Sample/AnimatedDoodad.kpmap | 2 | ||||
| -rw-r--r-- | Sample/PathTest.kpmap | 1 | ||||
| -rw-r--r-- | Sample/Terrain.kpmap | 1 | ||||
| -rw-r--r-- | src/editorui/paths.py | 24 | ||||
| -rw-r--r-- | src/exporter.py | 335 | ||||
| -rw-r--r-- | src/mapdata.py | 18 | ||||
| -rw-r--r-- | src/mapfile.py | 1 | ||||
| -rw-r--r-- | src/ui.py | 14 | 
8 files changed, 330 insertions, 66 deletions
diff --git a/Sample/AnimatedDoodad.kpmap b/Sample/AnimatedDoodad.kpmap index 93d22c0..d73e0dc 100644 --- a/Sample/AnimatedDoodad.kpmap +++ b/Sample/AnimatedDoodad.kpmap @@ -1 +1 @@ -{"layers": [{"paths": [], "nodes": [], "_t": "path_layer", "name": "Paths", "_visible": true}, {"_t": "doodad_layer", "name": "Doodads - Layer 1", "objects": [{"angle": 0, "_t": "doodad", "sourceRef": 0, "animations": [["Loop", "Sinusoidial", 600.0, "X Position", 0.0, 100.0], ["Reversible Loop", "Cosinoidial", 600.0, "Y Position", 0.0, 100.0]], "position": [282.0, 237.0], "size": [172.0, 184.0]}], "_visible": true}], "doodadDefinitions": [["BlackLevel", {"_t": "pixmap", "png": "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"}]], "nextLayerNumber": 2, "_t": "map_root"}
\ No newline at end of file +{"layers": [{"paths": [], "nodes": [], "_t": "path_layer", "name": "Paths", "_visible": true}, {"_t": "doodad_layer", "name": "Doodads - Layer 1", "objects": [{"angle": 0, "_t": "doodad", "sourceRef": 0, "animations": [["Loop", "Sinusoidial", 5000.0, "X Position", -400.0, 400.0], ["Loop", "Sinusoidial", 200.0, "Y Position", 10.0, -10.0], ["Reversible Loop", "Linear", 500.0, "Opacity", 80.0, 40.0]], "position": [282.0, 237.0], "size": [172.0, 184.0]}], "_visible": true}], "doodadDefinitions": [["BlackLevel", {"_t": "pixmap", "png": "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCelRYdFhNTAAAeJx9kk1u2zAQha9CMNtapOTIiglJhiPZaBYqAsVAu5XFUSw4/AFJ14yX6YF6gC4K2BfropYbFHC2g/e9eZh5SgQNV2sIvNAMpTOvm3YLDq3huZcZPv04/jy9HX+d3o6/Mep5hr/GFa10AZv+88HA0+HLqj1s2ynHsxylnnmhBbgGefEiLfMZbrhaA5OWCXANwcgzL7TbZnjO1RrQt+oRFcoAigM6aumEokkYhOPbJEk+oYiGlNCIhNEoTNg4YpQinKPU8I7V5fK8xPAuwxvnNCNkv98H+3GgzDMJp9MpoRGJopHh3ci+Stf4kbQ3g0MJtjW9dr2SyPCONWu1cxnGQ3ahL7bSnq/UKkF8o0kYUPJOWFUfS4W4qK2roftYbVevGkgNVu1MCzV0Nxh5oVlhoHHKrJR6Gc73uFFO2Y3SqHiKUdW0vXTKbjDyQlcVe5DWNbKFhzLDXuig7zmLymQxvqe39H5ShOEinM8XRbFY3iVlHMfxXTKwpWp3AqQbWP6Pja+yOUrPNJj+O/ClUQJZV0PH+utZoutZ/rL8epbxVZbkKCX/PXoY1eUyRym59DV/V32QPMMGz/I/rlL9ChD5WT8AAAHOelRYdFhNTDpjb20uYWRvYmUueG1wAAB4nH2TTW7bMBCFr0Iw21qk5MiKCUmGI9loFioCxUC7lcVRLDj8AUnXjJfJgXqALgrYF+uillsUcLgcvO/xkTOTzrxu2i04tIbnXmb49H78cXo7/jy9HX9h1PMMf40rWukCNv3ng4Gnw5dVe9i2U45nOUo980ILcA3y4kVa5jPccLUGJi0T4BqCkWdeaLfN8JyrNaBv1SMqlAEUB3TU0glFkzAIx7dJknxCEQ0poREJo1GYsHHEKEXng3OUGt6xulye7zK8y/DGOc0I2e/3wX4cKPNMwul0SmhEomhkeDeyr9I1fiTtzeBQgm1Nr12vJDK8Y81a7VyG8fAEoS+20gYNV2sIWiWIbzQJA0r+EVbVx1IhLmrraug+VtvVqwZSg1U700IN3Q1GXmhWGGicMiulXoZffNwop+xGaVQ8xahq2l46ZTcYeaGrij1I6xrZwkOZYS900PecRWWyGN/TW3o/KcJwEc7ni6JYLO+SMo7j+C4Z2FK1OwHSDSz/y8ZX2RylZxpM/x340iiBrKuhY/31LNH1LH9Yfj3L+CpLcpSS/xo9lOpymaOUXMY2R+llA0DyDBs8y38DM675CPg54vEAAAw/SURBVGiB7Zl7rGXVXcc/67Xf59xz3/NimAdTCA+hBdo6IkVKxUJLS8tQ/zCWaAptokWixqTGGKN/+E/TP0xMK02liUqs0Vqb2EcME1BHSilYYIaZMsPM3Hneue9z7tlnv9Za/nG5tyDDzPCo8Y/5Jis5e2edvb+ftX6/9dpwURd1URd1UW9D4mf58N27Px11IFK2DGuRRMrUoZCpqH0wqJSa2bnzS4O3+463DXDo6fuv0aK+zeHfb63dbGs7slyrpCp8ZJsmlkYExhhjvURKgdFmwavoBefkHofco6Tfd/3OLx38Pwc4/NxDdxuR3+Ns/p75ntg0O6+y+SXH7LxlabGmKBweMKFEhwYtPK3Esn7CMDaiiVODkhEO/aQ01TcJlr9z7bWPPP8zBzj8/EO/FKv6s7UvbpmekRN7D1r2HyiYOt5jZm5At9dnMOjTWIf3AilWWl5pSKKYsdGULZcatm82bN+SsHFThzBs0zTVc42qHzahe+Tqq/9y+R0HmJp6KBZ98VCc1PfPnCov/a9n4dkXcva/NMupkzP0eouUZUFT1zjf4LwHL5BSIITEO4n3DqE8SiUMD49w9RVD/OLOEW64fh0bJoYZVB7HwqPtsfLPtmz5633vGMCRp7+wPmmrP0/T3q8fOZrz6Lcr9jy9wOz0NN3uLHm+TFWVWFfjrMd7EGLFvJQSpRRCCPBgvcU1lsYCImFiYpQP3bKBO2/fzI5tEwSBRunej4q0/1tXbv3qk+fzps5X4YUffHpdkmVf67T9PT95ucvXH8157N+nmZk9yiCfI8/7VGVJ3dQ01oIHIcAYTRAY4jgmSRKSJCGKI8IgRGmNlALvCxbmF9h3YJFDR/uMjRu2bZ9E1OEGIYubbv+N7c/+3Vf2HnvLAIeevn8ImXxl3UR218nTPb78tTke23OCfjGNq3oUec2gKKiqCrtmXqC1JooisqxFp9NheHiY4eFhhoaGyLKMMAyRr/SKFI5B0ePosXlOnizYekmbbTsmoAzHQno/9wu/tu6Jb37t0NybBti9+491Ehd/ONJpPxCZgL//1in+4dtTVHYOZwuKQcWgqKjrkqaxAEgp0VoTxzGdTofR0VEmJiYYGxtjdHSUTqdDu90mTVO01njvsc4B4OqG4yeWmJmr2LZjHRMTE2TUG+OgO7r+3nXfevzrR93ZfOo3AhgNz9wivf2dJEt54UCX7zw2w6DqYpSnqSxlZanqiqaxOOdWYhxQSq0BjI+PMzY2RpqmRFGElBJrLXmeo7XGOUfd1NR1jQsddZPzxJ6X2PjoKH/we6OMt0fZpKY/sfFU833gkbP5lGe7+Y1v7BpqXPmgMj6tSsVTz8zy8tQSoXZYW1PXDdZWOPda80IIlFIEQUCSJHQ6ndeE0GoPZFlGmqbEcUxgAozWCCMJIoW3OU89dZCDB3so08I3cXjduH7g9796V+uCAWjUe5Vv7hRSYxvFiZN9bFUihcN7j3Me5xzOrVx773/6QCnXQikIAqIoWjEaBARBgFJqpeXrmqZpVv4rBAKQKEBw8uQCP/jhcSpvqMsW61vuyslI3HFBAF/84q64rKsPh0EjAJyQ9JZBSoeUK4kngFd5BlgDaZqGuq4py5KyLKnrlRCpqop+v8/i4uJa6fV6FEVBXddYZwGHEJ68KPjRMy8xPTMAHTEU0LphU/WRswG8LgfWD6lhY/ytUjiKok9gLEUpMQGEOsR6S2NqCqVQSmGtXeuB1ZbN85ylpSWmp6ex1tJqtZBSUtc1y8vLawBLS0sMBgOqqsJZh/MOJSXWWxaXlun3S3zLEmkvrtror7kggDBUUnvehXcU/UWSpM+6yYRYG0wgQSd456maGuccTdOsQVi7Mhrlec7c3Bx1XdPr9YiiCKUUTdNQliV5npPnOUVRUJblq8IRQKGUQ5sAaTRNUwE57diPXxBAWdjaxy62dYOtujSux403ruOJPVNYclJCBGIt/pumAViDcM5RliXAWotrrVFKrUGuhpS1dq2AX5kXtEMKTagjvBUIWyF9RV3a2K9U8ecGWB6UtRdN2bdaUdHvnmbH1qvYsHmEY1OQRgajzdrywHtPnudUVbUG471fuy6KYi2xV+s759aAvfevrGckWguE8EiTsGF9h9T0SeQsUpWUtQhPH/lACI8X5wToynKgu6pY6vusEzsG3SOEcZubbpzgH08s45FkWYbWBqn1yoijFPlgQFEUr+mJVYNS/nSsWIVY/S2lBCEJlMcoDWjaI6PsvPkyhrI52ulJhOyTl0EgqyIGXgPwulHowQe/W8726sHSsqUcWFwxy8LMj/n56yTvu2EdRdXgrKCdxYx1OkyMjzE2Ns7w8AhDQyuzbBiGGGMwxqwZFkKsFaUUxhjCMCROErI0pZ1mJHFMECS8593v4r3vHiWRpwnNLDQVea8pe3M2PG8OAMzMF/NL4+F4bEsCU+HrIyRhzB23X8dCt2Tv3gVaLUUSxQxphTaGJIlY7ifkeb42fK4m52prw8pMvRp+SimUNhgJ1guqSnLJJZPc+SuXM5kdpaVfRtKDfkC+LI8Mmuh1Xs8KsJwPDpxZMJdHQhG4mizr03UvMrQh4K4PbyUONM88P0Ov6NLJErKsTRRGxGlKng+oyvI1SfpqiFcDCAHWeoqqokCwZctmPvWJy7jpullGoudIsxnwUHQ1c0v68RnRf91G56wA3ojvTy+Ut3WSJIlcgK+h3SzT9c+yfmKBu+/cxtbtm9i9+wynT/YJYkOcCOI4JgwC6rqhaVaKcw77Sj6sToCrSVxWDXUDrWyEKzeP8JHbNnPHzX3G0ueJkmkIDb4bcabrWerV/7rrN/f1LwggjOw/9fv55+YX9FWjWUpdzQEDWq6Pcgt0Jua4+frL2TA6wd4DQ+w/1GV2piRfrhHSo/RK4iqtEU6As3jvcNZT1zXeeZxXJEnEti1D3HDtKDdcKbji0lOMJi9jsiUIAygTysWSU9P147ZKf/i/h1A4x47svvuv+cIY4k8vmXTSqDNEzDOSOVqZJ25poqEOOr2U3A2z0B3m9EzMkamGqWMV8/MFeT/HWo8Q0HgQ0qFVQxBIRodSOsMxO7ZFvO8az7ZNXdrJNErNoTKHTyJEHmNPO6YOL7uDx93dv/zAkX85m883XE73rf2yKLk1XXIfbEURhY1wtsIikKLGsISoDpClEUOdYTaNdLhq+zDz3YS8jGiamKbyDPo1gzIhigXDQznDQ4rhtiQLC1rpNEPxLGG8gMgExAHOtJA2wXdLpk/Mc/x09XBTRN97I5/n3BN/8lOX75xM/KNjSb1ZizmMWmYoU0y2YaTjaCWGIBQEsUBHAegESxsrNVJJtAwQSCwxxnjSYECoK8KgRqoCAoFuW0SS4NUYECMwMF8ws/cULx6a/Y/Fxt/zsc+cmX4jj+fcUr64d+7YZVePHfNNc6uti6RuKurG4azAeoXzEuElSiikE2gaIpWTiJxEDGjrHkPRgFa4QCtYIosHxGmFzkC1Q1QnRsTjWLkJGEO6ELoF8wdn+cmBxafnBtz38c+ePnouj+ff1D839+KOK4aP9Pt2p3J12zmLawSNkxTW09TgGoF14IVH4pEiQAmNUYJISSIjMYFGxiFE2UpJ2qA7wCjSp4h+gT21yMLhWQ6+vPhvZxaKBz7+26f2n8/feQEAnt+7uG/jls6PG+e2UdrNzjuc8DSNYFAJ8tpTNYqmUVSVoSihrB2NV3gfIIRBeo3wEdQRVAbqAFEryB1+Lqc7tcDUobnlIyfyv1pYqh+66/MnDl+Itzd1Mvehj05uDav8gY7OPzOaMZKlEISCKNIkkaKVSJJIEmiBCSVJqEiTgDRRpKEm0hohBU5IpAqRQuAbx9ySK6cXiifzonz4js9N/e2b8fSWzkY/dlv4wVj4T8aRvTtLWZeGnjhWJIkmjgSBNiijCbUnCjVJJIlDhVISpEcIjUM7Yf30oHL7lxv7z3bg/ube3z0+/2a9vOXD3V27COo5fZ1JzPszU38gltyYpPKSOBYYrTBGYozHGO0zZbyOhdPSV8rJXGp1okH+J0Z+Ty+2vnvvn+yr3qqPd+T7wK9+NJ1MnbvUG3etCeXlAfWGNNIjJjKx0fggkv1A61NG+MPKq/1Jxn/f90czh96Jd7+jHzh27UKNQ2wqdDQ57Js8aDYPTzef/wuqlZPRi7qoi7qoi/p/pv8BAuxDthb8jJMAAAAASUVORK5CYII="}]], "nextLayerNumber": 2, "_t": "map_root"}
\ No newline at end of file diff --git a/Sample/PathTest.kpmap b/Sample/PathTest.kpmap new file mode 100644 index 0000000..b1ebdfa --- /dev/null +++ b/Sample/PathTest.kpmap @@ -0,0 +1 @@ +{"layers": [{"paths": [{"endNodeLink": 1, "linkedLayer": -1, "unlocks": 2, "_t": "path", "secret": 0, "animation": 0, "startNodeLink": 0, "movementSpeed": 1.0}, {"endNodeLink": 6, "linkedLayer": -1, "unlocks": 0, "_t": "path", "secret": 0, "animation": 0, "startNodeLink": 0, "movementSpeed": 1.0}, {"endNodeLink": 3, "linkedLayer": -1, "unlocks": 2, "_t": "path", "secret": 0, "animation": 0, "startNodeLink": 2, "movementSpeed": 1.0}, {"endNodeLink": 4, "linkedLayer": -1, "unlocks": 2, "_t": "path", "secret": 1, "animation": 0, "startNodeLink": 3, "movementSpeed": 1.0}, {"endNodeLink": 1, "linkedLayer": -1, "unlocks": 1, "_t": "path", "secret": 0, "animation": 0, "startNodeLink": 4, "movementSpeed": 1.0}, {"endNodeLink": 5, "linkedLayer": -1, "unlocks": 0, "_t": "path", "secret": 0, "animation": 0, "startNodeLink": 4, "movementSpeed": 1.0}, {"endNodeLink": 2, "linkedLayer": -1, "unlocks": 0, "_t": "path", "secret": 0, "animation": 0, "startNodeLink": 6, "movementSpeed": 1.0}, {"endNodeLink": 7, "linkedLayer": -1, "unlocks": 0, "_t": "path", "secret": 0, "animation": 0, "startNodeLink": 6, "movementSpeed": 1.0}], "nodes": [{"_t": "node", "level": null, "transition": 0, "mapID": null, "actions": [], "exitIDs": [0, 1], "position": [180, 180], "foreignID": null, "mapChange": null}, {"_t": "node", "level": null, "transition": 0, "mapID": null, "actions": [], "exitIDs": [0, 4], "position": [324, 180], "foreignID": null, "mapChange": null}, {"_t": "node", "level": null, "transition": 0, "mapID": null, "actions": [], "exitIDs": [2, 6], "position": [180, 324], "foreignID": null, "mapChange": null}, {"_t": "node", "level": null, "transition": 0, "mapID": null, "actions": [], "exitIDs": [2, 3], "position": [324, 324], "foreignID": null, "mapChange": null}, {"_t": "node", "level": [2, 2], "transition": 0, "mapID": null, "actions": [], "exitIDs": [3, 4, 5], "position": [324, 252], "foreignID": null, "mapChange": null}, {"_t": "node", "level": null, "transition": 0, "mapID": null, "actions": [], "exitIDs": [5], "position": [420, 252], "foreignID": null, "mapChange": null}, {"_t": "node", "level": null, "transition": 0, "mapID": null, "actions": [], "exitIDs": [1, 6, 7], "position": [180, 252], "foreignID": null, "mapChange": null}, {"_t": "node", "level": null, "transition": 0, "mapID": null, "actions": [], "exitIDs": [7], "position": [84, 252], "foreignID": null, "mapChange": null}], "_t": "path_layer", "name": "Paths", "_visible": true}, {"tileset": "Test3", "_t": "tile_layer", "name": "Walls", "objects": [{"position": [7, 14], "kind": 71, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [14, 14], "kind": 74, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [18, 11], "kind": 74, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [15, 12], "kind": 50, "_t": "object", "tileset": "Test3", "size": [3, 1]}, {"position": [8, 15], "kind": 50, "_t": "object", "tileset": "Test3", "size": [6, 1]}, {"position": [4, 12], "kind": 50, "_t": "object", "tileset": "Test3", "size": [3, 1]}, {"position": [3, 11], "kind": 71, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [3, 17], "kind": 73, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [3, 13], "kind": 72, "_t": "object", "tileset": "Test3", "size": [1, 4]}, {"position": [7, 16], "kind": 72, "_t": "object", "tileset": "Test3", "size": [1, 4]}, {"position": [7, 20], "kind": 73, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [18, 13], "kind": 75, "_t": "object", "tileset": "Test3", "size": [1, 4]}, {"position": [14, 16], "kind": 75, "_t": "object", "tileset": "Test3", "size": [1, 4]}, {"position": [14, 20], "kind": 76, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [18, 17], "kind": 76, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [15, 17], "kind": 52, "_t": "object", "tileset": "Test3", "size": [3, 1]}, {"position": [8, 20], "kind": 52, "_t": "object", "tileset": "Test3", "size": [6, 1]}, {"position": [4, 17], "kind": 52, "_t": "object", "tileset": "Test3", "size": [3, 1]}, {"position": [4, 13], "kind": 51, "_t": "object", "tileset": "Test3", "size": [3, 4]}, {"position": [8, 16], "kind": 51, "_t": "object", "tileset": "Test3", "size": [6, 4]}, {"position": [15, 13], "kind": 51, "_t": "object", "tileset": "Test3", "size": [3, 4]}, {"position": [9, 9], "kind": 53, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [12, 9], "kind": 56, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [10, 10], "kind": 51, "_t": "object", "tileset": "Test3", "size": [2, 1]}, {"position": [10, 9], "kind": 50, "_t": "object", "tileset": "Test3", "size": [2, 1]}], "_visible": true}, {"tileset": "Test3", "_t": "tile_layer", "name": "Grass", "objects": [{"position": [12, 12], "kind": 49, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [13, 7], "kind": 24, "_t": "object", "tileset": "Test3", "size": [2, 8]}, {"position": [15, 11], "kind": 24, "_t": "object", "tileset": "Test3", "size": [4, 1]}, {"position": [15, 10], "kind": 31, "_t": "object", "tileset": "Test3", "size": [4, 1]}, {"position": [18, 10], "kind": 43, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [14, 7], "kind": 43, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [8, 7], "kind": 31, "_t": "object", "tileset": "Test3", "size": [6, 1]}, {"position": [7, 7], "kind": 42, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [7, 8], "kind": 24, "_t": "object", "tileset": "Test3", "size": [6, 1]}, {"position": [7, 8], "kind": 24, "_t": "object", "tileset": "Test3", "size": [2, 7]}, {"position": [9, 13], "kind": 24, "_t": "object", "tileset": "Test3", "size": [4, 2]}, {"position": [9, 12], "kind": 48, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [3, 10], "kind": 42, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [4, 10], "kind": 31, "_t": "object", "tileset": "Test3", "size": [3, 1]}, {"position": [3, 11], "kind": 24, "_t": "object", "tileset": "Test3", "size": [4, 1]}, {"position": [9, 9], "kind": 24, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [12, 9], "kind": 24, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [10, 13], "kind": 31, "_t": "object", "tileset": "Test3", "size": [2, 1]}], "_visible": true}, {"tileset": "Test3", "_t": "tile_layer", "name": "Walls: Behind", "objects": [{"position": [9, 11], "kind": 54, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [12, 11], "kind": 57, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [10, 11], "kind": 51, "_t": "object", "tileset": "Test3", "size": [2, 2]}], "_visible": true}], "doodadDefinitions": [], "nextLayerNumber": 4, "_t": "map_root"}
\ No newline at end of file diff --git a/Sample/Terrain.kpmap b/Sample/Terrain.kpmap new file mode 100644 index 0000000..662b5c1 --- /dev/null +++ b/Sample/Terrain.kpmap @@ -0,0 +1 @@ +{"layers": [{"paths": [], "nodes": [], "_t": "path_layer", "name": "Paths", "_visible": true}, {"tileset": "Test3", "_t": "tile_layer", "name": "Walls", "objects": [{"position": [13, 15], "kind": 50, "_t": "object", "tileset": "Test3", "size": [7, 1]}, {"position": [13, 17], "kind": 52, "_t": "object", "tileset": "Test3", "size": [7, 1]}, {"position": [13, 16], "kind": 51, "_t": "object", "tileset": "Test3", "size": [7, 1]}, {"position": [12, 14], "kind": 71, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [12, 16], "kind": 72, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [12, 17], "kind": 73, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [20, 14], "kind": 74, "_t": "object", "tileset": "Test3", "size": [1, 2]}, {"position": [20, 16], "kind": 75, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [20, 17], "kind": 76, "_t": "object", "tileset": "Test3", "size": [1, 1]}], "_visible": true}, {"tileset": "Test3", "_t": "tile_layer", "name": "Ground", "objects": [{"position": [12, 12], "kind": 42, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [20, 12], "kind": 43, "_t": "object", "tileset": "Test3", "size": [1, 1]}, {"position": [13, 12], "kind": 31, "_t": "object", "tileset": "Test3", "size": [7, 1]}, {"position": [12, 13], "kind": 24, "_t": "object", "tileset": "Test3", "size": [9, 2]}], "_visible": true}], "doodadDefinitions": [], "nextLayerNumber": 3, "_t": "map_root"}
\ No newline at end of file diff --git a/src/editorui/paths.py b/src/editorui/paths.py index 2f4388a..617e981 100644 --- a/src/editorui/paths.py +++ b/src/editorui/paths.py @@ -18,7 +18,6 @@ class KPEditorNode(KPEditorItem):  			self.iconList = [QtGui.QIcon("Resources/Through.png"),  							 QtGui.QIcon("Resources/Level.png"), -							 QtGui.QIcon("Resources/Stop.png"),  							 QtGui.QIcon("Resources/Exit.png")]  			self.state = 0 @@ -33,7 +32,7 @@ class KPEditorNode(KPEditorItem):  		def toggle(self):  			self.state += 1 -			if self.state == 4: +			if self.state == 3:  				self.state = 0  			self.stateToggled.emit(self.state) @@ -156,8 +155,7 @@ class KPEditorNode(KPEditorItem):  		node.mapChange = None  		node.mapID = None  		node.foreignID = None -		node.isStop = True -		node.level = [0,0] +		node.level = None  		if state == 1:  			node.level = [1, 1] @@ -165,9 +163,6 @@ class KPEditorNode(KPEditorItem):  			self.stage.setValue(node.level[1])  		elif state == 2: -			pass - -		elif state == 3:  			node.transition = 0  			node.mapChange = 'None.arc'  			node.foreignID = 0 @@ -187,10 +182,6 @@ class KPEditorNode(KPEditorItem):  			self.transition.setCurrentIndex(0) -		else: -			node.isStop = False - -  		self.update() @@ -254,7 +245,7 @@ class KPEditorNode(KPEditorItem):  		selectionRect = None -		if node.level != [0,0]: +		if node.level:  			painter.setBrush(QtGui.QColor(0, 0, 0, 0))  			painter.setPen(QtGui.QColor(0, 0, 0, 0))  			painter.drawPixmap(self._boundingRect.topLeft(), QtGui.QPixmap("Resources/BlackLevel.png")) @@ -279,7 +270,7 @@ class KPEditorNode(KPEditorItem):  			selectionRect = self._boundingRect.adjusted(1,5,-1,-5) -		elif node.isStop: +		elif len(node.exits) != 2:  			brush = QtGui.QBrush(QtGui.QColor(255, 220, 220))  			painter.setPen(QtGui.QColor(255, 255, 255))  			painter.setBrush(brush) @@ -301,7 +292,7 @@ class KPEditorNode(KPEditorItem):  			self.buttonProxy.show() -			if node.level != [0,0]: +			if node.level:  				self.worldProxy.show()  				self.stageProxy.show() @@ -474,7 +465,6 @@ class KPEditorPath(QtGui.QGraphicsLineItem):  				path = self._pathRef()  				path.animation = buttonID -				print path.animation  				path.qtItem.update() @@ -619,6 +609,9 @@ class KPEditorPath(QtGui.QGraphicsLineItem):  		startNode = path._startNodeRef().qtItem  		endNode = path._endNodeRef().qtItem +		startNode.update() +		endNode.update() +  		self._startNodeRef = weakref.ref(startNode)  		self._endNodeRef = weakref.ref(endNode)  		self._pathRef = weakref.ref(path) @@ -696,6 +689,7 @@ class KPEditorPath(QtGui.QGraphicsLineItem):  			node = ref()._nodeRef()  			try:  				node.exits.remove(path) +				node.qtItem.update()  			except ValueError:  				pass diff --git a/src/exporter.py b/src/exporter.py index fec2f86..5233cdd 100644 --- a/src/exporter.py +++ b/src/exporter.py @@ -1,7 +1,54 @@  from common import *  import array  import sys +from ctypes import create_string_buffer +def RGB4A3Encode(tex): +	tex = tex.toImage() +	w, h = tex.width(), tex.height() +	padW = (w + 3) & ~3 +	padH = (h + 3) & ~3 + +	destBuffer = create_string_buffer(padW * padH * 2) + +	shortstruct = struct.Struct('>H') +	sspack = shortstruct.pack_into +	offset = 0 + +	for ytile in xrange(0, padW, 4): +		for xtile in xrange(0, padH, 4): +			for ypixel in xrange(ytile, ytile + 4): +				for xpixel in xrange(xtile, xtile + 4): + +					if xpixel >= w or ypixel >= h: +						rgbDAT = 0x7FFF +					else: +						pixel = tex.pixel(xpixel, ypixel) + +						a = pixel >> 24 +						r = (pixel >> 16) & 0xFF +						g = (pixel >> 8) & 0xFF +						b = pixel & 0xFF + +						if a < 245: #RGB4A3 +							alpha = a/32 +							red = r/16 +							green = g/16 +							blue = b/16 + +							rgbDAT = (blue) | (green << 4) | (red << 8) | (alpha << 12) + +						else: # RGB555 +							red = r/8 +							green = g/8 +							blue = b/8 + +							rgbDAT = (blue) | (green << 5) | (red << 10) | (0x8000) # 0rrrrrgggggbbbbb + +					sspack(destBuffer, offset, rgbDAT) +					offset += 2 + +	return destBuffer.raw  class KPMapExporter:  	class LayerExporter: @@ -10,6 +57,7 @@ class KPMapExporter:  		def buildSectors(self, sectors, indices):  			# we'll use the cache held by the layer: why reinvent the wheel? +			layer = self.layer  			layer.updateCache()  			cache = layer.cache @@ -34,13 +82,13 @@ class KPMapExporter:  				destY = worldY % 16  				destRow = rawSectors[sectorY] -				 -				for srcX in xrange(layerX, layerX+layerWidth): + +				for srcX in xrange(layerWidth):  					worldX = srcX + layerX  					sectorX = worldX / 16  					destX = worldX % 16 -					tile = srcRow[i] +					tile = srcRow[srcX]  					if tile == -1: continue  					destSector = destRow[sectorX] @@ -51,80 +99,279 @@ class KPMapExporter:  					destSector[destY][destX] = tile  			# now add the created sectors to the data -			sectorMap = [-1 for i in xrange(len(self.rawSectors))] +			count = reduce(lambda x,y: x+len(y), rawSectors, 0) +			sectorMap = [-1 for i in xrange(count)] +			destIdx = 0 -			for srcRow, mapRow in zip(self.rawSectors, sectorMap): -				for index, sector in enumerate(srcRow): +			for srcRow in rawSectors: +				for sector in srcRow:  					if sector is not None:  						# see if it's a duplicate or not  						sectorKey = '|'.join(map(lambda x: ','.join(map(str, x)), sector))  						try: -							mapRow[index] = sectorIndices[sectorKey] -						except ValueError: -							sectorIndices[sectorKey] = len(sectors) -							mapRow[index] = len(sectors) +							sectorMap[destIdx] = indices[sectorKey] +						except KeyError: +							indices[sectorKey] = len(sectors) +							sectorMap[destIdx] = len(sectors)  							sectors.append(sector) +					destIdx += 1 +  			self.sectorBounds = (sectorLeft, sectorTop, sectorRight, sectorBottom)  			self.sectorMap = sectorMap -	 +  	def __init__(self, mapObj):  		self.map = mapObj  		self.layers = map(KPMapExporter.LayerExporter, self.map.layers) -		self.archive = WiiArchiveU8() -	 -	def buildAndPack(self, handle=None): -		# make the BG data +	def build(self): +		u32 = struct.Struct('>I') +		u16 = struct.Struct('>H') +		zero32 = u32.pack(0) + +		requiredFixUps = [] +		stringsToAdd = set() +		textures = set() +		offsets = {None: 0xFFFFFFFF} + +		# first off, build the sectors  		sectors = []  		sectorIndices = {}  		for layer in self.layers: -			layer.buildSectors(sectors, sectorIndices) +			if isinstance(layer.layer, KPTileLayer): +				layer.buildSectors(sectors, sectorIndices) -		# pack it into the arc -		self.archive.resolvePath('/sectors.bin').data = self._packSectorData(sectors) -		self.archive.resolvePath('/sectorMaps.bin').data = self._packSectorMaps() +		sectorData = self._packSectorData(sectors) -		return self.archive.pack(handle) +		# now that we've got that, we can pack the first part of the file +		data = bytearray(struct.pack('>IIII', len(self.layers), 16 + len(sectorData), 0, 0)) +		# list of layer pointers goes here.. or will, later +		data += sectorData -	def _packSectorData(self, sectors): -		rowStruct = struct.Struct('>16h') -		output = [] +		for layer in self.layers: +			requiredFixUps.append((len(data), layer)) +			data += zero32 -		for sector in sectors: -			for row in sector: -				output.append(rowStruct.pack(*row)) +		# now build the layers +		for eLayer in self.layers: +			layer = eLayer.layer -		return ''.join(output) +			offsets[eLayer] = len(data) +			offsets[layer] = len(data) + +			if isinstance(layer, KPTileLayer): +				data += u32.pack(0) + +				# tileset name +				stringsToAdd.add(layer.tileset) +				requiredFixUps.append((len(data), layer.tileset)) +				data += zero32 + +				# sector info +				data += struct.pack('>IIII', *eLayer.sectorBounds) +				data += ''.join(map(u16.pack, eLayer.sectorMap)) + +				pad = (4 - (len(data) & 3)) % 4 +				data += ('\0' * pad) + +			elif isinstance(layer, KPDoodadLayer): +				data += u32.pack(1) + +				# doodad list +				data += u32.pack(len(layer.objects)) +				for doodad in layer.objects: +					requiredFixUps.append((len(data), doodad)) +					data += zero32 + +				# now pack them ... +				for doodad in layer.objects: +					offsets[doodad] = len(data) + +					x, y = doodad.position +					w, h = doodad.size +					data += struct.pack('>fffffii', x, y, w, h, doodad.angle, 0, len(doodad.animations)) + +					texture = doodad.source[1] +					textures.add(texture) +					requiredFixUps.append((len(data) - 8, texture)) + +					for anim in doodad.animations: +						rLoop, rCurve, rFrames, rType, rStart, rEnd = anim + +						loopid = self.ANIM_LOOPS.index(rLoop) +						curveid = self.ANIM_CURVES.index(rCurve) +						typeid = self.ANIM_TYPES.index(rType) +						data += struct.pack('>iififf', loopid, curveid, rFrames, typeid, rStart, rEnd) + +			elif isinstance(layer, KPPathLayer): +				data += u32.pack(2) + +				# lists +				current = len(data) +				nodeArray = current + 16 +				pathArray = nodeArray + (len(layer.nodes) * 4) + +				data += struct.pack('>IIII', len(layer.nodes), nodeArray, len(layer.paths), pathArray) + +				for node in layer.nodes: +					requiredFixUps.append((len(data), node)) +					data += zero32 +				for path in layer.paths: +					requiredFixUps.append((len(data), path)) +					data += zero32 + +				# now do the actual structs +				for node in layer.nodes: +					offsets[node] = len(data) -	def _packSectorMaps(self): -		offsets = array.array('I') -		assert offsets.itemsize == 4 +					x, y = node.position +					current = len(data) +					data += struct.pack('>hhiiii', x, y, 0, 0, 0, 0) -		currentOffset = len(self.layers) * 4 +					exits = node.exits + [None,None,None,None] # TODO +					requiredFixUps.append((current+4, exits[0])) +					requiredFixUps.append((current+8, exits[1])) +					requiredFixUps.append((current+12, exits[2])) +					requiredFixUps.append((current+16, exits[3])) -		data = [] +					if node.isStop(): +						if node.level: +							level1, level2 = node.level +							data += struct.pack('>ibbbb', 2, level1, level2, 0, 0) +							typeid = 2 -		for index, layer in enumerate(self.layers): -			offsets.append(currentOffset) +						elif node.mapChange: +							data += u32.pack(3) -			data.append(struct.pack('>hhhh', *layer.sectorBounds)) -			currentOffset += 8 -			 -			first = layer.sectorMap[0] -			rowStruct = struct.Struct('>%dh' % len(first)) -			for row in layer.sectorMap: -				data.append(rowStruct.pack(*row)) +							destMap = node.mapChange +							requiredFixUps.append((len(data), destMap)) +							stringsToAdd.add(destMap) -			currentOffset += (len(first) * len(layer.sectorMap) * 2) +							data += struct.pack('>ibbbb', 0, node.mapID, node.foreignID, node.transition, 0) -		if sys.byteorder == 'little': offsets.byteswap() -		return offsets.tostring() + ''.join(data) +						else: +							data += u32.pack(1) +					else: +						data += u32.pack(0) + +				for path in layer.paths: +					offsets[path] = len(data) + +					start = path._startNodeRef() +					end = path._endNodeRef() +					current = len(data) + +					requiredFixUps.append((current, start)) +					requiredFixUps.append((current+4, end)) +					requiredFixUps.append((current+8, path.linkedLayer)) +					data += (zero32 * 3) + +					data += struct.pack('>fi', path.movementSpeed, path.animation) + +		# unlocking paths/nodes! +		unlocks = [] +		us = struct.Struct('>bbbbi') + +		for node in self.map.pathLayer.nodes: +			if not node.level: continue + +			checked = set() +			affected = [] +			self._findUnlocksForNode(node, checked, affected, True) + +			level1, level2 = node.level + +			for item, secret in affected: +				unlocks.append(us.pack(secret, level1, level2, 0, offsets[item])) + +		struct.pack_into('>ii', data, 8, len(unlocks), len(data)) +		data += ''.join(unlocks) + +		# now that we're almost done... pack the strings +		for string in stringsToAdd: +			offsets[string] = len(data) +			data += str(string) +			data += '\0' + +		# textures +		texHeaderStartOffset = len(data) +		texHeaderEndOffset = texHeaderStartOffset + (len(textures) * 8) + +		texDataStartOffset = (texHeaderEndOffset + 0x1F) & ~0x1F +		texPadding = texDataStartOffset - texHeaderEndOffset + +		currentTexOffset = texDataStartOffset +		 +		imageData = [] + +		for tex in textures: +			offsets[tex] = len(data) +			data += struct.pack('>hhi', tex.width(), tex.height(), currentTexOffset) + +			converted = RGB4A3Encode(tex) +			imageData.append(converted) +			currentTexOffset += len(converted) + +		data += ('\0' * texPadding) +		for piece in imageData: +			data += piece + +		# to finish up, correct every offset +		for offset, target in requiredFixUps: +			u32.pack_into(data, offset, offsets[target]) + +		return data + +	ANIM_LOOPS = ['Contiguous', 'Loop', 'Reversible Loop'] +	ANIM_CURVES = ['Linear', 'Sinusoidial', 'Cosinoidial'] +	ANIM_TYPES = ['X Position', 'Y Position', 'Angle', 'X Scale', 'Y Scale', 'Opacity'] + + +	def _findUnlocksForNode(self, node, checked, affected, isFirstBranch=False, secret=None): +		if node in checked: return + +		checked.add(node) + +		for path in node.exits: +			if path not in checked: +				checked.add(path) +				self._findUnlocksForPath(path, node, checked, affected, isFirstBranch, secret) + + +	def _findUnlocksForPath(self, path, sourceNode, checked, affected, isFirstBranch, secret=None): +		start, end = path._startNodeRef(), path._endNodeRef() +		if start == sourceNode: +			destNode = end +			if isFirstBranch and path.unlocks != 1: +				return +		else: +			destNode = start +			if isFirstBranch and path.unlocks != 2: +				return + +		if secret is None: +			secret = path.secret +		affected.append((path, secret)) + +		if not destNode.isStop(): +			self._findUnlocksForNode(destNode, checked, affected, False, secret) + + + +	def _packSectorData(self, sectors): +		rowStruct = struct.Struct('>16h') +		output = [] + +		for sector in sectors: +			for row in sector: +				output.append(rowStruct.pack(*row)) + +		return ''.join(output) diff --git a/src/mapdata.py b/src/mapdata.py index ec0feff..23c6b83 100644 --- a/src/mapdata.py +++ b/src/mapdata.py @@ -1,7 +1,6 @@  from common import *  import weakref  import mapfile -import base64  TILE_SIZE = (24,24)  MAP_SIZE_IN_TILES = (512,512) @@ -250,7 +249,7 @@ class KPNodeAction(object):  @mapfile.dumpable('node')  class KPNode(object):    	__dump_attribs__ = ( -			'position', 'actions', 'level', 'isStop', 'mapChange', +			'position', 'actions', 'level', 'mapChange',  			'transition', 'mapID', 'foreignID')  	def _dump(self, mapObj, dest): @@ -265,12 +264,15 @@ class KPNode(object):  		self.position = (0,0)  		self.actions = []  		self.exits = [] -		self.level = [0,0] -		self.isStop = False +		self.level = None  		self.mapChange = None  		self.transition = 0  		self.mapID = None  		self.foreignID = None +	 +	def isStop(self): +		return True if (self.level or self.mapChange or len(self.exits) != 2) else False +  @mapfile.dumpable('path') @@ -304,7 +306,7 @@ class KPPath(object):  		self.secret = 0 	# 0 = unlocks from normal exit, 1 = unlocks from secret exit  		if cloneFrom is None: -			self.animation = None +			self.animation = 0  		else:  			self.animation = cloneFrom.animation @@ -388,6 +390,12 @@ class KPMap(object):  		dumped = mapfile.dump(self)  		open(path, 'wb').write(dumped) +	def export(self, path): +		from exporter import KPMapExporter +		exp = KPMapExporter(self) +		data = exp.build() +		open(path, 'wb').write(data) +  	def __init__(self):  		self.filePath = None diff --git a/src/mapfile.py b/src/mapfile.py index 16d57db..a6ebede 100644 --- a/src/mapfile.py +++ b/src/mapfile.py @@ -1,4 +1,5 @@  from common import * +import base64  import json  DUMPABLE_CLASSES_BY_NAME = {} @@ -442,7 +442,7 @@ class KPMainWindow(QtGui.QMainWindow):  		f.addSeparator()  		self.fd = f.addAction('Save',						self.saveMap, QKeySequence("Ctrl+S"))  		self.fe = f.addAction('Save As...',					self.saveMapAs, QKeySequence("Ctrl+Shift+S")) -		self.ff = f.addAction('Export...')					# E +		self.ff = f.addAction('Export...',					self.exportMap, QKeySequence("Ctrl+Shift+E"))  		f.addSeparator()  		self.fg = f.addAction('Take Screenshot...',			self.screenshot, QKeySequence("Ctrl+Alt+S"))  		f.addSeparator() @@ -636,6 +636,18 @@ class KPMainWindow(QtGui.QMainWindow):  		self.saveMap(True) +	def exportMap(self): +		target = KP.map.filePath + +		dialogDir = '' if target is None else os.path.dirname(target) +		target = unicode(QtGui.QFileDialog.getSaveFileName( +				self, 'Export Map', dialogDir, 'Koopatlas binary map (*.kpbin)')) + +		if len(target) == 0: +			return + +		KP.map.export(target) +  	@QtCore.pyqtSlot()  	def screenshot(self):  | 
