Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch lisp-game-jam-2020 Excluding Merge-Ins
This is equivalent to a diff from 5a8a5d5d2f to c1b0bf682a
2020-04-20
| ||
05:39 | Fix build config Leaf check-in: c1b0bf682a user: chewbranca tags: lisp-game-jam-2020 | |
03:23 | s/fahombo/worpt/g check-in: 96d35b5c69 user: chewbranca tags: lisp-game-jam-2020 | |
2020-04-10
| ||
06:11 | Initial Fahombo project and import of https://gitlab.com/alexjgriffith/min-love2d-fennel check-in: 25cf6d1f59 user: chewbranca tags: lisp-game-jam-2020 | |
06:05 | Fix user create bug Leaf check-in: 5a8a5d5d2f user: chewbranca tags: trunk | |
05:22 | mkdir src check-in: 49ad980338 user: chewbranca tags: trunk | |
Added src/worpt/appimage/build.sh.
1 +#!/bin/bash 2 + 3 +set -eo >/dev/null 4 + 5 +CURRENT_APPIMAGEKIT_RELEASE=9 6 +ARCH="$(uname -m)" 7 + 8 +if [[ $# -lt 1 ]]; then 9 + echo "Usage: $0 <version>" 10 + exit 0 11 +fi 12 + 13 +VERSION="$1" 14 +LOVEFILE="$2" 15 + 16 +LOVE_TAR_URL=https://bitbucket.org/rude/love/downloads/love-${VERSION}-linux-${ARCH}.tar.gz 17 +LOVE_TAR=${HOME}/.cache/love-release/love/love-${VERSION}-${ARCH}.tar.gz 18 +if ! test -f ${LOVE_TAR}; then 19 + echo "No tarball found for $VERSION in $LOVE_TAR" 20 + exit 1 21 +fi 22 + 23 +download_if_needed() { 24 + if ! test -f "$1"; then 25 + if ! curl -L -o "$1" "https://github.com/AppImage/AppImageKit/releases/download/${CURRENT_APPIMAGEKIT_RELEASE}/$1"; then 26 + echo "Failed to download appimagetool" 27 + echo "Please supply it manually" 28 + exit 1 29 + fi 30 + chmod +x "$1" 31 + fi 32 +} 33 + 34 +main() { 35 + download_if_needed appimagetool-${ARCH}.AppImage 36 + download_if_needed AppRun-${ARCH} 37 + 38 + # Extract the tarball build into a folder 39 + rm -rf love-prepared 40 + mkdir love-prepared 41 + tar xf ${LOVE_TAR} -C love-prepared --strip-components=1 42 + 43 + cd love-prepared 44 + 45 + # Add our small wrapper script (yay, more wrappers), and AppRun 46 + cp ../wrapper usr/bin/wrapper-love 47 + cp ../AppRun-${ARCH} AppRun 48 + 49 + local desktopfile="love.desktop" 50 + local icon="love" 51 + local target="love-${VERSION}" 52 + 53 + if test -f ../../game.desktop.in; then 54 + desktopfile="game.desktop" 55 + cp ../../game.desktop.in . 56 + fi 57 + if test -f ../../game.svg; then 58 + icon="game" 59 + cp ../../game.svg . 60 + fi 61 + if test -f ${LOVEFILE}; then 62 + target="game" 63 + cat usr/bin/love ${LOVEFILE} > usr/bin/love-fused 64 + mv usr/bin/love-fused usr/bin/love 65 + chmod +x usr/bin/love 66 + else 67 + echo "Love file ${LOVEFILE} not found" 68 + fi 69 + 70 + # Add our desktop file 71 + sed -e 's/%BINARY%/wrapper-love/' -e "s/%ICON%/${icon}/" "${desktopfile}.in" > "$desktopfile" 72 + rm "${desktopfile}.in" 73 + 74 + # Add a DirIcon 75 + cp "${icon}.svg" .DirIcon 76 + 77 + # Clean up 78 + if test -f ../../game.desktop.in; then 79 + rm love.desktop.in 80 + fi 81 + if test -f ../../game.svg; then 82 + rm love.svg 83 + fi 84 + 85 + # Now build the final AppImage 86 + cd .. 87 + 88 + # Work around missing FUSE/docker 89 + ./appimagetool-${ARCH}.AppImage --appimage-extract 90 + ./squashfs-root/AppRun -n love-prepared "${target}-${ARCH}.AppImage" 91 +} 92 + 93 +main "$@"
Added src/worpt/appimage/squashfs-root/AppRun.
1 +#!/bin/sh 2 +HERE="$(dirname "$(readlink -f "${0}")")" 3 +export PATH="${HERE}"/usr/bin/:"${HERE}"/usr/sbin/:"${HERE}"/usr/games/:"${HERE}"/bin/:"${HERE}"/sbin/:"${PATH}" 4 +export LD_LIBRARY_PATH="${HERE}"/usr/lib/:"${HERE}"/usr/lib/i386-linux-gnu/:"${HERE}"/usr/lib/x86_64-linux-gnu/:"${HERE}"/usr/lib32/:"${HERE}"/usr/lib64/:"${HERE}"/lib/:"${HERE}"/lib/i386-linux-gnu/:"${HERE}"/lib/x86_64-linux-gnu/:"${HERE}"/lib32/:"${HERE}"/lib64/:"${LD_LIBRARY_PATH}" 5 +export PYTHONPATH="${HERE}"/usr/share/pyshared/:"${PYTHONPATH}" 6 +export XDG_DATA_DIRS="${HERE}"/usr/share/:"${XDG_DATA_DIRS}" 7 +export PERLLIB="${HERE}"/usr/share/perl5/:"${HERE}"/usr/lib/perl5/:"${PERLLIB}" 8 +export GSETTINGS_SCHEMA_DIR="${HERE}"/usr/share/glib-2.0/schemas/:"${GSETTINGS_SCHEMA_DIR}" 9 +export QT_PLUGIN_PATH="${HERE}"/usr/lib/qt4/plugins/:"${HERE}"/usr/lib/i386-linux-gnu/qt4/plugins/:"${HERE}"/usr/lib/x86_64-linux-gnu/qt4/plugins/:"${HERE}"/usr/lib32/qt4/plugins/:"${HERE}"/usr/lib64/qt4/plugins/:"${HERE}"/usr/lib/qt5/plugins/:"${HERE}"/usr/lib/i386-linux-gnu/qt5/plugins/:"${HERE}"/usr/lib/x86_64-linux-gnu/qt5/plugins/:"${HERE}"/usr/lib32/qt5/plugins/:"${HERE}"/usr/lib64/qt5/plugins/:"${QT_PLUGIN_PATH}" 10 +EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1) 11 +exec "${EXEC}" $@
Added src/worpt/appimage/squashfs-root/appimagetool.desktop.
1 +[Desktop Entry] 2 +Type=Application 3 +Name=appimagetool 4 +Exec=appimagetool 5 +Comment=Tool to generate AppImages from AppDirs 6 +Icon=appimagetool 7 +Categories=Development; 8 +Terminal=true 9 +
Added src/worpt/appimage/squashfs-root/appimagetool.svg.
1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 +<!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 +<svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + width="48px" 14 + height="48px" 15 + id="svg3832" 16 + version="1.1" 17 + inkscape:version="0.47 r22583" 18 + sodipodi:docname="appimage-assistant_alt3.svg"> 19 + <defs 20 + id="defs3834"> 21 + <linearGradient 22 + inkscape:collect="always" 23 + xlink:href="#linearGradient3308-4-6-931-761-0" 24 + id="linearGradient2975" 25 + gradientUnits="userSpaceOnUse" 26 + x1="24.3125" 27 + y1="22.96875" 28 + x2="24.3125" 29 + y2="41.03125" /> 30 + <linearGradient 31 + id="linearGradient3308-4-6-931-761-0"> 32 + <stop 33 + id="stop2919-2" 34 + style="stop-color:#ffffff;stop-opacity:1" 35 + offset="0" /> 36 + <stop 37 + id="stop2921-76" 38 + style="stop-color:#ffffff;stop-opacity:0" 39 + offset="1" /> 40 + </linearGradient> 41 + <linearGradient 42 + inkscape:collect="always" 43 + xlink:href="#linearGradient4222" 44 + id="linearGradient2979" 45 + gradientUnits="userSpaceOnUse" 46 + gradientTransform="matrix(0,0.3704967,-0.3617496,0,33.508315,6.1670925)" 47 + x1="7.6485429" 48 + y1="26.437023" 49 + x2="41.861729" 50 + y2="26.437023" /> 51 + <linearGradient 52 + id="linearGradient4222"> 53 + <stop 54 + id="stop4224" 55 + style="stop-color:#ffffff;stop-opacity:1" 56 + offset="0" /> 57 + <stop 58 + id="stop4226" 59 + style="stop-color:#ffffff;stop-opacity:0" 60 + offset="1" /> 61 + </linearGradient> 62 + <linearGradient 63 + inkscape:collect="always" 64 + xlink:href="#linearGradient3308-4-6-931-761" 65 + id="linearGradient2982" 66 + gradientUnits="userSpaceOnUse" 67 + gradientTransform="translate(0,0.9999987)" 68 + x1="23.99999" 69 + y1="4.999989" 70 + x2="23.99999" 71 + y2="43" /> 72 + <linearGradient 73 + id="linearGradient3308-4-6-931-761"> 74 + <stop 75 + id="stop2919" 76 + style="stop-color:#ffffff;stop-opacity:1" 77 + offset="0" /> 78 + <stop 79 + id="stop2921" 80 + style="stop-color:#ffffff;stop-opacity:0" 81 + offset="1" /> 82 + </linearGradient> 83 + <radialGradient 84 + inkscape:collect="always" 85 + xlink:href="#linearGradient3575" 86 + id="radialGradient2985" 87 + gradientUnits="userSpaceOnUse" 88 + gradientTransform="matrix(0,1.0262008,-1.6561124,9.4072203e-4,-56.097482,-45.332325)" 89 + cx="48.42384" 90 + cy="-48.027504" 91 + fx="48.42384" 92 + fy="-48.027504" 93 + r="38.212933" /> 94 + <linearGradient 95 + id="linearGradient3575"> 96 + <stop 97 + id="stop3577" 98 + style="stop-color:#fafafa;stop-opacity:1" 99 + offset="0" /> 100 + <stop 101 + id="stop3579" 102 + style="stop-color:#e6e6e6;stop-opacity:1" 103 + offset="1" /> 104 + </linearGradient> 105 + <radialGradient 106 + inkscape:collect="always" 107 + xlink:href="#linearGradient3993" 108 + id="radialGradient2990" 109 + gradientUnits="userSpaceOnUse" 110 + gradientTransform="matrix(0,2.0478765,-2.7410544,-8.6412258e-8,47.161382,-8.837436)" 111 + cx="9.3330879" 112 + cy="8.4497671" 113 + fx="9.3330879" 114 + fy="8.4497671" 115 + r="19.99999" /> 116 + <linearGradient 117 + id="linearGradient3993"> 118 + <stop 119 + offset="0" 120 + style="stop-color:#a3c0d0;stop-opacity:1" 121 + id="stop3995" /> 122 + <stop 123 + offset="1" 124 + style="stop-color:#427da1;stop-opacity:1" 125 + id="stop4001" /> 126 + </linearGradient> 127 + <linearGradient 128 + inkscape:collect="always" 129 + xlink:href="#linearGradient2508" 130 + id="linearGradient2992" 131 + gradientUnits="userSpaceOnUse" 132 + gradientTransform="translate(0,0.9674382)" 133 + x1="14.048676" 134 + y1="44.137306" 135 + x2="14.048676" 136 + y2="4.0000005" /> 137 + <linearGradient 138 + id="linearGradient2508"> 139 + <stop 140 + offset="0" 141 + style="stop-color:#2e4a5a;stop-opacity:1" 142 + id="stop2510" /> 143 + <stop 144 + offset="1" 145 + style="stop-color:#6e8796;stop-opacity:1" 146 + id="stop2512" /> 147 + </linearGradient> 148 + <radialGradient 149 + cx="4.9929786" 150 + cy="43.5" 151 + r="2.5" 152 + fx="4.9929786" 153 + fy="43.5" 154 + id="radialGradient2873-966-168" 155 + xlink:href="#linearGradient3688-166-749" 156 + gradientUnits="userSpaceOnUse" 157 + gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)" /> 158 + <linearGradient 159 + id="linearGradient3688-166-749"> 160 + <stop 161 + id="stop2883" 162 + style="stop-color:#181818;stop-opacity:1" 163 + offset="0" /> 164 + <stop 165 + id="stop2885" 166 + style="stop-color:#181818;stop-opacity:0" 167 + offset="1" /> 168 + </linearGradient> 169 + <radialGradient 170 + cx="4.9929786" 171 + cy="43.5" 172 + r="2.5" 173 + fx="4.9929786" 174 + fy="43.5" 175 + id="radialGradient2875-742-326" 176 + xlink:href="#linearGradient3688-464-309" 177 + gradientUnits="userSpaceOnUse" 178 + gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)" /> 179 + <linearGradient 180 + id="linearGradient3688-464-309"> 181 + <stop 182 + id="stop2889" 183 + style="stop-color:#181818;stop-opacity:1" 184 + offset="0" /> 185 + <stop 186 + id="stop2891" 187 + style="stop-color:#181818;stop-opacity:0" 188 + offset="1" /> 189 + </linearGradient> 190 + <linearGradient 191 + x1="25.058096" 192 + y1="47.027729" 193 + x2="25.058096" 194 + y2="39.999443" 195 + id="linearGradient2877-634-617" 196 + xlink:href="#linearGradient3702-501-757" 197 + gradientUnits="userSpaceOnUse" /> 198 + <linearGradient 199 + id="linearGradient3702-501-757"> 200 + <stop 201 + id="stop2895" 202 + style="stop-color:#181818;stop-opacity:0" 203 + offset="0" /> 204 + <stop 205 + id="stop2897" 206 + style="stop-color:#181818;stop-opacity:1" 207 + offset="0.5" /> 208 + <stop 209 + id="stop2899" 210 + style="stop-color:#181818;stop-opacity:0" 211 + offset="1" /> 212 + </linearGradient> 213 + </defs> 214 + <sodipodi:namedview 215 + id="base" 216 + pagecolor="#ffffff" 217 + bordercolor="#666666" 218 + borderopacity="1.0" 219 + inkscape:pageopacity="0.0" 220 + inkscape:pageshadow="2" 221 + inkscape:zoom="7" 222 + inkscape:cx="24" 223 + inkscape:cy="24" 224 + inkscape:current-layer="layer1" 225 + showgrid="true" 226 + inkscape:grid-bbox="true" 227 + inkscape:document-units="px" 228 + inkscape:window-width="603" 229 + inkscape:window-height="484" 230 + inkscape:window-x="417" 231 + inkscape:window-y="162" 232 + inkscape:window-maximized="0" /> 233 + <metadata 234 + id="metadata3837"> 235 + <rdf:RDF> 236 + <cc:Work 237 + rdf:about=""> 238 + <dc:format>image/svg+xml</dc:format> 239 + <dc:type 240 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 241 + <dc:title></dc:title> 242 + </cc:Work> 243 + </rdf:RDF> 244 + </metadata> 245 + <g 246 + id="layer1" 247 + inkscape:label="Layer 1" 248 + inkscape:groupmode="layer"> 249 + <g 250 + style="display:inline" 251 + id="g2036" 252 + transform="matrix(1.1,0,0,0.4444449,-2.4000022,25.11107)"> 253 + <g 254 + style="opacity:0.4" 255 + id="g3712" 256 + transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)"> 257 + <rect 258 + style="fill:url(#radialGradient2873-966-168);fill-opacity:1;stroke:none" 259 + id="rect2801" 260 + y="40" 261 + x="38" 262 + height="7" 263 + width="5" /> 264 + <rect 265 + style="fill:url(#radialGradient2875-742-326);fill-opacity:1;stroke:none" 266 + id="rect3696" 267 + transform="scale(-1,-1)" 268 + y="-47" 269 + x="-10" 270 + height="7" 271 + width="5" /> 272 + <rect 273 + style="fill:url(#linearGradient2877-634-617);fill-opacity:1;stroke:none" 274 + id="rect3700" 275 + y="40" 276 + x="10" 277 + height="7.0000005" 278 + width="28" /> 279 + </g> 280 + </g> 281 + <rect 282 + style="fill:url(#radialGradient2990);fill-opacity:1;stroke:url(#linearGradient2992);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" 283 + id="rect5505" 284 + y="5.4674392" 285 + x="4.5" 286 + ry="2.2322156" 287 + rx="2.2322156" 288 + height="39" 289 + width="39" /> 290 + <path 291 + style="opacity:0.05;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00178742;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" 292 + id="path4294-1" 293 + d="m 21,6.9687498 a 2.0165107,2.0165107 0 0 0 -2.03125,2.03125 l 0,3.9687502 -1.15625,0 a 2.0165107,2.0165107 0 0 0 -1.5,3.375 l 5.0625,5.75 c -0.06312,0.110777 -0.178724,0.246032 -0.21875,0.34375 -0.195898,0.478256 -0.25,0.83653 -0.25,1.21875 l 0,0.125 L 20.8125,23.6875 C 20.534322,23.409323 20.213169,23.162739 19.71875,22.96875 19.47154,22.87176 19.185456,22.791748 18.75,22.8125 c -0.435456,0.02075 -1.054055,0.210302 -1.46875,0.625 L 15.75,24.96875 c -0.414689,0.414689 -0.604245,1.033294 -0.625,1.46875 -0.02075,0.435456 0.05925,0.721537 0.15625,0.96875 C 15.475241,27.900677 15.721817,28.221821 16,28.5 l 0.09375,0.09375 -0.125,0 c -0.382218,0 -0.740493,0.0541 -1.21875,0.25 -0.239128,0.09795 -0.538285,0.214988 -0.84375,0.53125 -0.305465,0.316262 -0.625,0.914788 -0.625,1.53125 l 0,2.1875 c 0,0.616465 0.319536,1.214989 0.625,1.53125 0.305464,0.316261 0.604622,0.433301 0.84375,0.53125 0.478256,0.195898 0.83653,0.25 1.21875,0.25 l 0.125,0 L 16,35.5 c -0.278175,0.278176 -0.52476,0.599329 -0.71875,1.09375 -0.09699,0.24721 -0.177003,0.533292 -0.15625,0.96875 0.02075,0.435458 0.210304,1.054058 0.625,1.46875 l 1.53125,1.53125 c 0.414691,0.414697 1.033292,0.604245 1.46875,0.625 0.435458,0.02076 0.721537,-0.05926 0.96875,-0.15625 0.494425,-0.19399 0.81557,-0.440568 1.09375,-0.71875 l 0.09375,-0.09375 0,0.125 c 0,0.38222 0.0541,0.740495 0.25,1.21875 0.09795,0.239127 0.214989,0.538285 0.53125,0.84375 0.316261,0.305465 0.914783,0.625 1.53125,0.625 l 2.1875,0 c 0.616466,0 1.214989,-0.319534 1.53125,-0.625 0.316261,-0.305466 0.433302,-0.604622 0.53125,-0.84375 0.195896,-0.478255 0.25,-0.836532 0.25,-1.21875 l 0,-0.125 0.09375,0.09375 c 0.278176,0.278175 0.599329,0.52476 1.09375,0.71875 0.24721,0.09699 0.533292,0.177003 0.96875,0.15625 0.435458,-0.02075 1.054058,-0.210304 1.46875,-0.625 L 32.875,39.03125 C 33.289697,38.616559 33.479245,37.997958 33.5,37.5625 33.52076,37.127042 33.44074,36.840963 33.34375,36.59375 33.14976,36.099325 32.903182,35.77818 32.625,35.5 l -0.09375,-0.09375 0.125,0 c 0.38222,0 0.740494,-0.0541 1.21875,-0.25 0.239128,-0.09795 0.538286,-0.214988 0.84375,-0.53125 0.305464,-0.316262 0.625,-0.914787 0.625,-1.53125 l 0,-2.1875 c 0,-0.61646 -0.319535,-1.214987 -0.625,-1.53125 -0.305465,-0.316263 -0.604621,-0.433301 -0.84375,-0.53125 -0.478257,-0.195898 -0.836532,-0.25 -1.21875,-0.25 l -0.125,0 L 32.625,28.5 c 0.278177,-0.278177 0.52476,-0.599329 0.71875,-1.09375 C 33.44074,27.15904 33.520753,26.872957 33.5,26.4375 33.47925,26.002043 33.289697,25.383443 32.875,24.96875 L 31.34375,23.4375 c -0.414688,-0.414694 -1.03329,-0.604245 -1.46875,-0.625 -0.43546,-0.02076 -0.721537,0.05925 -0.96875,0.15625 -0.494426,0.193991 -0.815572,0.44057 -1.09375,0.71875 l -0.09375,0.09375 0,-0.125 c 0,-0.382218 -0.0541,-0.740493 -0.25,-1.21875 -0.09112,-0.22245 -0.228127,-0.500183 -0.5,-0.78125 l 4.71875,-5.3125 a 2.0165107,2.0165107 0 0 0 -1.5,-3.375 l -1.15625,0 0,-3.9687502 A 2.0165107,2.0165107 0 0 0 27,6.9687498 l -6,0 z M 24.3125,31.25 c 0.427097,0 0.75,0.322904 0.75,0.75 0,0.427096 -0.322903,0.75 -0.75,0.75 -0.427094,0 -0.75,-0.322906 -0.75,-0.75 0,-0.427094 0.322906,-0.75 0.75,-0.75 z" /> 294 + <path 295 + style="opacity:0.05;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00178742;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" 296 + id="path4294" 297 + d="m 20.90625,8.0312498 a 0.96385067,0.96385067 0 0 0 -0.875,0.96875 l 0,5.0312502 -2.21875,0 A 0.96385067,0.96385067 0 0 0 17.09375,15.625 l 5.78125,6.53125 c -0.158814,0.0616 -0.341836,0.0951 -0.4375,0.1875 -0.169161,0.163386 -0.252971,0.323419 -0.3125,0.46875 -0.119058,0.290663 -0.15625,0.566746 -0.15625,0.84375 l 0,1.65625 C 21.718163,25.40233 21.485871,25.509772 21.25,25.625 l -1.1875,-1.1875 c -0.199651,-0.19965 -0.421433,-0.352095 -0.71875,-0.46875 -0.148659,-0.05833 -0.329673,-0.104846 -0.5625,-0.09375 -0.232827,0.0111 -0.53583,0.09833 -0.75,0.3125 L 16.5,25.71875 c -0.214168,0.214168 -0.301403,0.517173 -0.3125,0.75 -0.0111,0.232827 0.03542,0.41384 0.09375,0.5625 0.116655,0.297321 0.269096,0.519099 0.46875,0.71875 l 1.1875,1.1875 c -0.115228,0.235871 -0.222668,0.468163 -0.3125,0.71875 l -1.65625,0 c -0.277003,0 -0.553087,0.03719 -0.84375,0.15625 -0.145332,0.05953 -0.305363,0.143338 -0.46875,0.3125 -0.163387,0.169162 -0.3125,0.46403 -0.3125,0.78125 l 0,2.1875 c 0,0.317221 0.149114,0.612089 0.3125,0.78125 0.163386,0.169161 0.323419,0.252971 0.46875,0.3125 0.290663,0.119058 0.566746,0.15625 0.84375,0.15625 l 1.65625,0 c 0.08983,0.250587 0.197272,0.482879 0.3125,0.71875 L 16.75,36.25 c -0.199649,0.19965 -0.352095,0.421432 -0.46875,0.71875 -0.05833,0.148659 -0.104846,0.329672 -0.09375,0.5625 0.0111,0.232828 0.09833,0.535831 0.3125,0.75 l 1.53125,1.53125 c 0.214168,0.214172 0.517172,0.301403 0.75,0.3125 0.232828,0.0111 0.41384,-0.03542 0.5625,-0.09375 0.29732,-0.116655 0.519098,-0.269096 0.71875,-0.46875 L 21.25,38.375 c 0.235871,0.115228 0.468164,0.222668 0.71875,0.3125 l 0,1.65625 c 0,0.277003 0.03719,0.553087 0.15625,0.84375 0.05953,0.145331 0.143339,0.305364 0.3125,0.46875 0.169161,0.163386 0.464028,0.3125 0.78125,0.3125 l 2.1875,0 c 0.317221,0 0.612089,-0.149113 0.78125,-0.3125 0.169161,-0.163387 0.252971,-0.323419 0.3125,-0.46875 0.119057,-0.290663 0.15625,-0.566748 0.15625,-0.84375 l 0,-1.65625 c 0.250586,-0.08983 0.482879,-0.197272 0.71875,-0.3125 l 1.1875,1.1875 c 0.19965,0.199649 0.421432,0.352095 0.71875,0.46875 0.148659,0.05833 0.329672,0.104846 0.5625,0.09375 0.232828,-0.0111 0.535831,-0.09833 0.75,-0.3125 L 32.125,38.28125 c 0.214172,-0.214168 0.301403,-0.517172 0.3125,-0.75 0.0111,-0.232828 -0.03542,-0.41384 -0.09375,-0.5625 C 32.227095,36.67143 32.074654,36.449652 31.875,36.25 L 30.6875,35.0625 C 30.802728,34.82663 30.910168,34.594337 31,34.34375 l 1.65625,0 c 0.277004,0 0.553087,-0.03719 0.84375,-0.15625 0.145332,-0.05953 0.305364,-0.143339 0.46875,-0.3125 0.163386,-0.169161 0.3125,-0.46403 0.3125,-0.78125 l 0,-2.1875 c 0,-0.317219 -0.149114,-0.612088 -0.3125,-0.78125 C 33.805364,29.955838 33.645332,29.872029 33.5,29.8125 33.209336,29.693442 32.933253,29.65625 32.65625,29.65625 l -1.65625,0 C 30.91017,29.405663 30.802728,29.17337 30.6875,28.9375 L 31.875,27.75 c 0.19965,-0.19965 0.352095,-0.421432 0.46875,-0.71875 0.05833,-0.148659 0.104846,-0.329672 0.09375,-0.5625 -0.0111,-0.232828 -0.09833,-0.535831 -0.3125,-0.75 L 30.59375,24.1875 c -0.214167,-0.21417 -0.517171,-0.301403 -0.75,-0.3125 -0.232829,-0.0111 -0.41384,0.03542 -0.5625,0.09375 -0.29732,0.116656 -0.519099,0.269097 -0.71875,0.46875 L 27.375,25.625 c -0.235871,-0.115228 -0.468163,-0.222668 -0.71875,-0.3125 l 0,-1.65625 c 0,-0.277003 -0.03719,-0.553087 -0.15625,-0.84375 -0.05953,-0.145332 -0.143338,-0.305363 -0.3125,-0.46875 -0.169162,-0.163387 -0.46403,-0.3125 -0.78125,-0.3125 l -0.15625,0 5.65625,-6.40625 A 0.96385067,0.96385067 0 0 0 30.1875,14.03125 l -2.21875,0 0,-5.0312502 A 0.96385067,0.96385067 0 0 0 27,8.0312498 l -6,0 a 0.96385067,0.96385067 0 0 0 -0.09375,0 z M 24.3125,30.1875 c 1.002113,0 1.8125,0.810388 1.8125,1.8125 0,1.002112 -0.810387,1.8125 -1.8125,1.8125 C 23.31039,33.8125 22.5,33.002111 22.5,32 c 0,-1.002111 0.81039,-1.8125 1.8125,-1.8125 z" /> 298 + <path 299 + style="fill:url(#radialGradient2985);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.00178742;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" 300 + id="path2317" 301 + d="M 21,8.9999996 21,15 17.8125,15 24,22 30.1875,15 27,15 l 0,-6.0000004 -6,0 z M 23.21875,23 c -0.172892,0 -0.28125,0.294922 -0.28125,0.65625 l 0,2.28125 C 22.24145,26.095996 21.585954,26.379869 21,26.75 l -1.625,-1.625 c -0.255498,-0.255497 -0.533998,-0.372253 -0.65625,-0.25 l -1.53125,1.53125 c -0.122254,0.122254 -0.0055,0.400753 0.25,0.65625 l 1.625,1.625 c -0.37013,0.585953 -0.654003,1.24145 -0.8125,1.9375 l -2.28125,0 c -0.361328,0 -0.65625,0.108357 -0.65625,0.28125 l 0,2.1875 c 0,0.172892 0.294922,0.28125 0.65625,0.28125 l 2.28125,0 c 0.158497,0.69605 0.44237,1.351546 0.8125,1.9375 l -1.625,1.625 c -0.255497,0.255498 -0.372254,0.533997 -0.25,0.65625 l 1.53125,1.53125 c 0.122252,0.122254 0.400752,0.0055 0.65625,-0.25 L 21,37.25 c 0.585954,0.37013 1.24145,0.654002 1.9375,0.8125 l 0,2.28125 C 22.9375,40.705077 23.045858,41 23.21875,41 l 2.1875,0 c 0.172893,0 0.28125,-0.294924 0.28125,-0.65625 l 0,-2.28125 c 0.69605,-0.158498 1.351546,-0.44237 1.9375,-0.8125 l 1.625,1.625 c 0.255498,0.255497 0.533997,0.372254 0.65625,0.25 l 1.53125,-1.53125 c 0.122254,-0.122252 0.0055,-0.400752 -0.25,-0.65625 l -1.625,-1.625 c 0.370129,-0.585954 0.654003,-1.24145 0.8125,-1.9375 l 2.28125,0 c 0.361329,0 0.65625,-0.108358 0.65625,-0.28125 l 0,-2.1875 c 0,-0.172893 -0.294921,-0.28125 -0.65625,-0.28125 l -2.28125,0 c -0.158497,-0.69605 -0.442371,-1.351547 -0.8125,-1.9375 l 1.625,-1.625 c 0.255497,-0.255497 0.372254,-0.533997 0.25,-0.65625 L 29.90625,24.875 C 29.783997,24.752745 29.505498,24.8695 29.25,25.125 l -1.625,1.625 c -0.585954,-0.370131 -1.24145,-0.654004 -1.9375,-0.8125 l 0,-2.28125 C 25.6875,23.294922 25.579143,23 25.40625,23 l -2.1875,0 z m 1.09375,6.21875 c 1.528616,0 2.78125,1.252635 2.78125,2.78125 0,1.528615 -1.252634,2.78125 -2.78125,2.78125 -1.528614,0 -2.78125,-1.252635 -2.78125,-2.78125 0,-1.528615 1.252636,-2.78125 2.78125,-2.78125 z" /> 302 + <rect 303 + style="opacity:0.4;fill:none;stroke:url(#linearGradient2982);stroke-width:0.99999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" 304 + id="rect6741" 305 + y="6.4999886" 306 + x="5.4999981" 307 + ry="1.365193" 308 + rx="1.365193" 309 + height="37.000011" 310 + width="36.999985" /> 311 + <path 312 + style="fill:none;stroke:url(#linearGradient2979);stroke-width:0.99829447;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" 313 + id="path2777" 314 + d="M 28.926376,15.466668 24,21.177578 18.963089,15.5 21.5,15.5 l 0,-6.0000004 5,0 0,6.0000004 2.426376,-0.03333 z" /> 315 + <path 316 + style="fill:none;stroke:url(#linearGradient2975);stroke-width:1;stroke-opacity:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" 317 + id="path4243" 318 + d="m 23.4375,23.46875 c -0.01166,0.05381 -0.03125,0.100205 -0.03125,0.1875 l 0,2.28125 a 0.48185467,0.48185467 0 0 1 -0.375,0.46875 c -0.638467,0.145384 -1.238423,0.407111 -1.78125,0.75 a 0.48185467,0.48185467 0 0 1 -0.59375,-0.0625 l -1.625,-1.625 C 18.9779,25.4154 18.9477,25.40242 18.90625,25.375 l -1.21875,1.21875 c 0.02742,0.04145 0.0404,0.07165 0.09375,0.125 l 1.625,1.625 a 0.48185467,0.48185467 0 0 1 0.0625,0.59375 c -0.342888,0.542826 -0.604615,1.142782 -0.75,1.78125 a 0.48185467,0.48185467 0 0 1 -0.46875,0.375 l -2.28125,0 c -0.08729,0 -0.133695,0.01959 -0.1875,0.03125 l 0,1.75 c 0.05381,0.01166 0.100205,0.03125 0.1875,0.03125 l 2.28125,0 a 0.48185467,0.48185467 0 0 1 0.46875,0.375 c 0.145385,0.638468 0.407112,1.238423 0.75,1.78125 a 0.48185467,0.48185467 0 0 1 -0.0625,0.59375 l -1.625,1.625 c -0.05335,0.05335 -0.06633,0.08355 -0.09375,0.125 l 1.21875,1.21875 c 0.04145,-0.02742 0.07165,-0.0404 0.125,-0.09375 l 1.625,-1.625 A 0.48185467,0.48185467 0 0 1 21.25,36.84375 c 0.542827,0.342888 1.142781,0.604614 1.78125,0.75 a 0.48185467,0.48185467 0 0 1 0.375,0.46875 l 0,2.28125 c 0,0.08729 0.01959,0.133695 0.03125,0.1875 l 1.75,0 c 0.01166,-0.0538 0.03125,-0.100206 0.03125,-0.1875 l 0,-2.28125 a 0.48185467,0.48185467 0 0 1 0.375,-0.46875 c 0.638469,-0.145386 1.238423,-0.407112 1.78125,-0.75 a 0.48185467,0.48185467 0 0 1 0.59375,0.0625 l 1.625,1.625 c 0.05335,0.05335 0.08355,0.06633 0.125,0.09375 l 1.21875,-1.21875 c -0.02742,-0.04145 -0.0404,-0.07165 -0.09375,-0.125 l -1.625,-1.625 a 0.48185467,0.48185467 0 0 1 -0.0625,-0.59375 c 0.342888,-0.542828 0.604615,-1.142783 0.75,-1.78125 a 0.48185467,0.48185467 0 0 1 0.46875,-0.375 l 2.28125,0 c 0.08729,0 0.133695,-0.01959 0.1875,-0.03125 l 0,-1.75 c -0.0538,-0.01166 -0.100204,-0.03125 -0.1875,-0.03125 l -2.28125,0 a 0.48185467,0.48185467 0 0 1 -0.46875,-0.375 c -0.145385,-0.638467 -0.407113,-1.238424 -0.75,-1.78125 a 0.48185467,0.48185467 0 0 1 0.0625,-0.59375 l 1.625,-1.625 c 0.05335,-0.05335 0.06633,-0.08355 0.09375,-0.125 L 29.71875,25.375 c -0.04145,0.02742 -0.07165,0.0404 -0.125,0.09375 l -1.625,1.625 a 0.48185467,0.48185467 0 0 1 -0.59375,0.0625 c -0.542827,-0.342889 -1.142783,-0.604616 -1.78125,-0.75 a 0.48185467,0.48185467 0 0 1 -0.375,-0.46875 l 0,-2.28125 c 0,-0.0873 -0.01959,-0.133695 -0.03125,-0.1875 l -1.75,0 z m 0.875,5.28125 c 1.791829,0 3.25,1.458172 3.25,3.25 0,1.791828 -1.458171,3.25 -3.25,3.25 -1.791827,0 -3.25,-1.458172 -3.25,-3.25 0,-1.791828 1.458173,-3.25 3.25,-3.25 z" /> 319 + </g> 320 +</svg>
Added src/worpt/appimage/squashfs-root/usr/bin/appimagetool.
cannot compute difference between binary files
Added src/worpt/appimage/squashfs-root/usr/bin/mksquashfs.
cannot compute difference between binary files
Added src/worpt/appimage/squashfs-root/usr/bin/zsyncmake.
cannot compute difference between binary files
Added src/worpt/appimage/wrapper.
1 +#!/bin/sh 2 + 3 +# For some reason this runs from the usr subdirectory 4 +exec ../love "$@"
Added src/worpt/assets/osmstudios_character_block.png.
cannot compute difference between binary files
Added src/worpt/assets/warped_city/atlas/atlas.json.
1 +{"frames": [ 2 + 3 +{ 4 + "filename": "antenna", 5 + "frame": {"x":612,"y":133,"w":21,"h":95}, 6 + "rotated": false, 7 + "trimmed": true, 8 + "spriteSourceSize": {"x":1,"y":1,"w":21,"h":95}, 9 + "sourceSize": {"w":22,"h":96}, 10 + "pivot": {"x":0.5,"y":0.5} 11 +}, 12 +{ 13 + "filename": "back-jump-1", 14 + "frame": {"x":575,"y":2,"w":24,"h":27}, 15 + "rotated": false, 16 + "trimmed": true, 17 + "spriteSourceSize": {"x":24,"y":40,"w":24,"h":27}, 18 + "sourceSize": {"w":71,"h":67}, 19 + "pivot": {"x":0.5,"y":0.5} 20 +}, 21 +{ 22 + "filename": "back-jump-2", 23 + "frame": {"x":374,"y":55,"w":35,"h":42}, 24 + "rotated": false, 25 + "trimmed": true, 26 + "spriteSourceSize": {"x":15,"y":25,"w":35,"h":42}, 27 + "sourceSize": {"w":71,"h":67}, 28 + "pivot": {"x":0.5,"y":0.5} 29 +}, 30 +{ 31 + "filename": "back-jump-3", 32 + "frame": {"x":168,"y":55,"w":35,"h":38}, 33 + "rotated": false, 34 + "trimmed": true, 35 + "spriteSourceSize": {"x":15,"y":20,"w":35,"h":38}, 36 + "sourceSize": {"w":71,"h":67}, 37 + "pivot": {"x":0.5,"y":0.5} 38 +}, 39 +{ 40 + "filename": "back-jump-4", 41 + "frame": {"x":773,"y":55,"w":44,"h":40}, 42 + "rotated": false, 43 + "trimmed": true, 44 + "spriteSourceSize": {"x":12,"y":16,"w":44,"h":40}, 45 + "sourceSize": {"w":71,"h":67}, 46 + "pivot": {"x":0.5,"y":0.5} 47 +}, 48 +{ 49 + "filename": "back-jump-5", 50 + "frame": {"x":411,"y":55,"w":31,"h":47}, 51 + "rotated": false, 52 + "trimmed": true, 53 + "spriteSourceSize": {"x":17,"y":18,"w":31,"h":47}, 54 + "sourceSize": {"w":71,"h":67}, 55 + "pivot": {"x":0.5,"y":0.5} 56 +}, 57 +{ 58 + "filename": "back-jump-6", 59 + "frame": {"x":1124,"y":2,"w":28,"h":39}, 60 + "rotated": false, 61 + "trimmed": true, 62 + "spriteSourceSize": {"x":24,"y":28,"w":28,"h":39}, 63 + "sourceSize": {"w":71,"h":67}, 64 + "pivot": {"x":0.5,"y":0.5} 65 +}, 66 +{ 67 + "filename": "back-jump-7", 68 + "frame": {"x":858,"y":2,"w":35,"h":27}, 69 + "rotated": false, 70 + "trimmed": true, 71 + "spriteSourceSize": {"x":19,"y":40,"w":35,"h":27}, 72 + "sourceSize": {"w":71,"h":67}, 73 + "pivot": {"x":0.5,"y":0.5} 74 +}, 75 +{ 76 + "filename": "back-jump-preview", 77 + "frame": {"x":601,"y":2,"w":24,"h":27}, 78 + "rotated": false, 79 + "trimmed": true, 80 + "spriteSourceSize": {"x":24,"y":40,"w":24,"h":27}, 81 + "sourceSize": {"w":71,"h":67}, 82 + "pivot": {"x":0.5,"y":0.5} 83 +}, 84 +{ 85 + "filename": "banner-big-1", 86 + "frame": {"x":635,"y":133,"w":35,"h":92}, 87 + "rotated": false, 88 + "trimmed": false, 89 + "spriteSourceSize": {"x":0,"y":0,"w":35,"h":92}, 90 + "sourceSize": {"w":35,"h":92}, 91 + "pivot": {"x":0.5,"y":0.5} 92 +}, 93 +{ 94 + "filename": "banner-big-2", 95 + "frame": {"x":672,"y":133,"w":35,"h":92}, 96 + "rotated": false, 97 + "trimmed": false, 98 + "spriteSourceSize": {"x":0,"y":0,"w":35,"h":92}, 99 + "sourceSize": {"w":35,"h":92}, 100 + "pivot": {"x":0.5,"y":0.5} 101 +}, 102 +{ 103 + "filename": "banner-big-3", 104 + "frame": {"x":746,"y":133,"w":35,"h":92}, 105 + "rotated": false, 106 + "trimmed": false, 107 + "spriteSourceSize": {"x":0,"y":0,"w":35,"h":92}, 108 + "sourceSize": {"w":35,"h":92}, 109 + "pivot": {"x":0.5,"y":0.5} 110 +}, 111 +{ 112 + "filename": "banner-big-4", 113 + "frame": {"x":783,"y":133,"w":35,"h":92}, 114 + "rotated": false, 115 + "trimmed": false, 116 + "spriteSourceSize": {"x":0,"y":0,"w":35,"h":92}, 117 + "sourceSize": {"w":35,"h":92}, 118 + "pivot": {"x":0.5,"y":0.5} 119 +}, 120 +{ 121 + "filename": "banner-big-preview", 122 + "frame": {"x":709,"y":133,"w":35,"h":92}, 123 + "rotated": false, 124 + "trimmed": false, 125 + "spriteSourceSize": {"x":0,"y":0,"w":35,"h":92}, 126 + "sourceSize": {"w":35,"h":92}, 127 + "pivot": {"x":0.5,"y":0.5} 128 +}, 129 +{ 130 + "filename": "banner-coke-1", 131 + "frame": {"x":382,"y":133,"w":27,"h":78}, 132 + "rotated": false, 133 + "trimmed": false, 134 + "spriteSourceSize": {"x":0,"y":0,"w":27,"h":78}, 135 + "sourceSize": {"w":27,"h":78}, 136 + "pivot": {"x":0.5,"y":0.5} 137 +}, 138 +{ 139 + "filename": "banner-coke-2", 140 + "frame": {"x":440,"y":133,"w":27,"h":78}, 141 + "rotated": false, 142 + "trimmed": false, 143 + "spriteSourceSize": {"x":0,"y":0,"w":27,"h":78}, 144 + "sourceSize": {"w":27,"h":78}, 145 + "pivot": {"x":0.5,"y":0.5} 146 +}, 147 +{ 148 + "filename": "banner-coke-3", 149 + "frame": {"x":411,"y":133,"w":27,"h":78}, 150 + "rotated": false, 151 + "trimmed": false, 152 + "spriteSourceSize": {"x":0,"y":0,"w":27,"h":78}, 153 + "sourceSize": {"w":27,"h":78}, 154 + "pivot": {"x":0.5,"y":0.5} 155 +}, 156 +{ 157 + "filename": "banner-coke-preview", 158 + "frame": {"x":469,"y":133,"w":27,"h":78}, 159 + "rotated": false, 160 + "trimmed": false, 161 + "spriteSourceSize": {"x":0,"y":0,"w":27,"h":78}, 162 + "sourceSize": {"w":27,"h":78}, 163 + "pivot": {"x":0.5,"y":0.5} 164 +}, 165 +{ 166 + "filename": "banner-neon-1", 167 + "frame": {"x":1215,"y":2,"w":19,"h":48}, 168 + "rotated": false, 169 + "trimmed": false, 170 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":48}, 171 + "sourceSize": {"w":19,"h":48}, 172 + "pivot": {"x":0.5,"y":0.5} 173 +}, 174 +{ 175 + "filename": "banner-neon-2", 176 + "frame": {"x":1194,"y":2,"w":19,"h":48}, 177 + "rotated": false, 178 + "trimmed": false, 179 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":48}, 180 + "sourceSize": {"w":19,"h":48}, 181 + "pivot": {"x":0.5,"y":0.5} 182 +}, 183 +{ 184 + "filename": "banner-neon-3", 185 + "frame": {"x":1103,"y":2,"w":19,"h":48}, 186 + "rotated": false, 187 + "trimmed": false, 188 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":48}, 189 + "sourceSize": {"w":19,"h":48}, 190 + "pivot": {"x":0.5,"y":0.5} 191 +}, 192 +{ 193 + "filename": "banner-neon-4", 194 + "frame": {"x":1236,"y":2,"w":19,"h":48}, 195 + "rotated": false, 196 + "trimmed": false, 197 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":48}, 198 + "sourceSize": {"w":19,"h":48}, 199 + "pivot": {"x":0.5,"y":0.5} 200 +}, 201 +{ 202 + "filename": "banner-neon-preview", 203 + "frame": {"x":1278,"y":2,"w":19,"h":48}, 204 + "rotated": false, 205 + "trimmed": false, 206 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":48}, 207 + "sourceSize": {"w":19,"h":48}, 208 + "pivot": {"x":0.5,"y":0.5} 209 +}, 210 +{ 211 + "filename": "banner-scroll-1", 212 + "frame": {"x":678,"y":2,"w":13,"h":47}, 213 + "rotated": false, 214 + "trimmed": false, 215 + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":47}, 216 + "sourceSize": {"w":13,"h":47}, 217 + "pivot": {"x":0.5,"y":0.5} 218 +}, 219 +{ 220 + "filename": "banner-scroll-2", 221 + "frame": {"x":663,"y":2,"w":13,"h":47}, 222 + "rotated": false, 223 + "trimmed": false, 224 + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":47}, 225 + "sourceSize": {"w":13,"h":47}, 226 + "pivot": {"x":0.5,"y":0.5} 227 +}, 228 +{ 229 + "filename": "banner-scroll-3", 230 + "frame": {"x":693,"y":2,"w":13,"h":47}, 231 + "rotated": false, 232 + "trimmed": false, 233 + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":47}, 234 + "sourceSize": {"w":13,"h":47}, 235 + "pivot": {"x":0.5,"y":0.5} 236 +}, 237 +{ 238 + "filename": "banner-scroll-4", 239 + "frame": {"x":723,"y":2,"w":13,"h":47}, 240 + "rotated": false, 241 + "trimmed": false, 242 + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":47}, 243 + "sourceSize": {"w":13,"h":47}, 244 + "pivot": {"x":0.5,"y":0.5} 245 +}, 246 +{ 247 + "filename": "banner-scroll-preview", 248 + "frame": {"x":708,"y":2,"w":13,"h":47}, 249 + "rotated": false, 250 + "trimmed": false, 251 + "spriteSourceSize": {"x":0,"y":0,"w":13,"h":47}, 252 + "sourceSize": {"w":13,"h":47}, 253 + "pivot": {"x":0.5,"y":0.5} 254 +}, 255 +{ 256 + "filename": "banner-side-1", 257 + "frame": {"x":1323,"y":55,"w":19,"h":76}, 258 + "rotated": false, 259 + "trimmed": false, 260 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":76}, 261 + "sourceSize": {"w":19,"h":76}, 262 + "pivot": {"x":0.5,"y":0.5} 263 +}, 264 +{ 265 + "filename": "banner-side-2", 266 + "frame": {"x":1302,"y":55,"w":19,"h":76}, 267 + "rotated": false, 268 + "trimmed": false, 269 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":76}, 270 + "sourceSize": {"w":19,"h":76}, 271 + "pivot": {"x":0.5,"y":0.5} 272 +}, 273 +{ 274 + "filename": "banner-side-3", 275 + "frame": {"x":1281,"y":55,"w":19,"h":76}, 276 + "rotated": false, 277 + "trimmed": false, 278 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":76}, 279 + "sourceSize": {"w":19,"h":76}, 280 + "pivot": {"x":0.5,"y":0.5} 281 +}, 282 +{ 283 + "filename": "banner-side-4", 284 + "frame": {"x":1365,"y":55,"w":19,"h":76}, 285 + "rotated": false, 286 + "trimmed": false, 287 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":76}, 288 + "sourceSize": {"w":19,"h":76}, 289 + "pivot": {"x":0.5,"y":0.5} 290 +}, 291 +{ 292 + "filename": "banner-side-preview", 293 + "frame": {"x":1344,"y":55,"w":19,"h":76}, 294 + "rotated": false, 295 + "trimmed": false, 296 + "spriteSourceSize": {"x":0,"y":0,"w":19,"h":76}, 297 + "sourceSize": {"w":19,"h":76}, 298 + "pivot": {"x":0.5,"y":0.5} 299 +}, 300 +{ 301 + "filename": "banner-sushi-1", 302 + "frame": {"x":499,"y":2,"w":36,"h":13}, 303 + "rotated": false, 304 + "trimmed": false, 305 + "spriteSourceSize": {"x":0,"y":0,"w":36,"h":13}, 306 + "sourceSize": {"w":36,"h":13}, 307 + "pivot": {"x":0.5,"y":0.5} 308 +}, 309 +{ 310 + "filename": "banner-sushi-2", 311 + "frame": {"x":423,"y":2,"w":36,"h":13}, 312 + "rotated": false, 313 + "trimmed": false, 314 + "spriteSourceSize": {"x":0,"y":0,"w":36,"h":13}, 315 + "sourceSize": {"w":36,"h":13}, 316 + "pivot": {"x":0.5,"y":0.5} 317 +}, 318 +{ 319 + "filename": "banner-sushi-3", 320 + "frame": {"x":461,"y":2,"w":36,"h":13}, 321 + "rotated": false, 322 + "trimmed": false, 323 + "spriteSourceSize": {"x":0,"y":0,"w":36,"h":13}, 324 + "sourceSize": {"w":36,"h":13}, 325 + "pivot": {"x":0.5,"y":0.5} 326 +}, 327 +{ 328 + "filename": "banner-sushi-preview", 329 + "frame": {"x":537,"y":2,"w":36,"h":13}, 330 + "rotated": false, 331 + "trimmed": false, 332 + "spriteSourceSize": {"x":0,"y":0,"w":36,"h":13}, 333 + "sourceSize": {"w":36,"h":13}, 334 + "pivot": {"x":0.5,"y":0.5} 335 +}, 336 +{ 337 + "filename": "climb-1", 338 + "frame": {"x":63,"y":55,"w":15,"h":55}, 339 + "rotated": false, 340 + "trimmed": true, 341 + "spriteSourceSize": {"x":30,"y":10,"w":15,"h":55}, 342 + "sourceSize": {"w":71,"h":67}, 343 + "pivot": {"x":0.5,"y":0.5} 344 +}, 345 +{ 346 + "filename": "climb-2", 347 + "frame": {"x":969,"y":2,"w":15,"h":49}, 348 + "rotated": false, 349 + "trimmed": true, 350 + "spriteSourceSize": {"x":30,"y":16,"w":15,"h":49}, 351 + "sourceSize": {"w":71,"h":67}, 352 + "pivot": {"x":0.5,"y":0.5} 353 +}, 354 +{ 355 + "filename": "climb-3", 356 + "frame": {"x":627,"y":2,"w":16,"h":40}, 357 + "rotated": false, 358 + "trimmed": true, 359 + "spriteSourceSize": {"x":30,"y":19,"w":16,"h":40}, 360 + "sourceSize": {"w":71,"h":67}, 361 + "pivot": {"x":0.5,"y":0.5} 362 +}, 363 +{ 364 + "filename": "climb-4", 365 + "frame": {"x":46,"y":55,"w":15,"h":55}, 366 + "rotated": false, 367 + "trimmed": true, 368 + "spriteSourceSize": {"x":31,"y":10,"w":15,"h":55}, 369 + "sourceSize": {"w":71,"h":67}, 370 + "pivot": {"x":0.5,"y":0.5} 371 +}, 372 +{ 373 + "filename": "climb-5", 374 + "frame": {"x":952,"y":2,"w":15,"h":49}, 375 + "rotated": false, 376 + "trimmed": true, 377 + "spriteSourceSize": {"x":31,"y":16,"w":15,"h":49}, 378 + "sourceSize": {"w":71,"h":67}, 379 + "pivot": {"x":0.5,"y":0.5} 380 +}, 381 +{ 382 + "filename": "climb-6", 383 + "frame": {"x":645,"y":2,"w":16,"h":40}, 384 + "rotated": false, 385 + "trimmed": true, 386 + "spriteSourceSize": {"x":30,"y":19,"w":16,"h":40}, 387 + "sourceSize": {"w":71,"h":67}, 388 + "pivot": {"x":0.5,"y":0.5} 389 +}, 390 +{ 391 + "filename": "climb-preview", 392 + "frame": {"x":102,"y":55,"w":15,"h":55}, 393 + "rotated": false, 394 + "trimmed": true, 395 + "spriteSourceSize": {"x":30,"y":10,"w":15,"h":55}, 396 + "sourceSize": {"w":71,"h":67}, 397 + "pivot": {"x":0.5,"y":0.5} 398 +}, 399 +{ 400 + "filename": "control-box-1", 401 + "frame": {"x":764,"y":2,"w":32,"h":30}, 402 + "rotated": false, 403 + "trimmed": false, 404 + "spriteSourceSize": {"x":0,"y":0,"w":32,"h":30}, 405 + "sourceSize": {"w":32,"h":30}, 406 + "pivot": {"x":0.5,"y":0.5} 407 +}, 408 +{ 409 + "filename": "control-box-2", 410 + "frame": {"x":216,"y":2,"w":16,"h":27}, 411 + "rotated": false, 412 + "trimmed": true, 413 + "spriteSourceSize": {"x":0,"y":0,"w":16,"h":27}, 414 + "sourceSize": {"w":17,"h":27}, 415 + "pivot": {"x":0.5,"y":0.5} 416 +}, 417 +{ 418 + "filename": "control-box-3", 419 + "frame": {"x":982,"y":55,"w":62,"h":30}, 420 + "rotated": false, 421 + "trimmed": false, 422 + "spriteSourceSize": {"x":0,"y":0,"w":62,"h":30}, 423 + "sourceSize": {"w":62,"h":30}, 424 + "pivot": {"x":0.5,"y":0.5} 425 +}, 426 +{ 427 + "filename": "crouch", 428 + "frame": {"x":1022,"y":2,"w":33,"h":33}, 429 + "rotated": false, 430 + "trimmed": true, 431 + "spriteSourceSize": {"x":21,"y":34,"w":33,"h":33}, 432 + "sourceSize": {"w":71,"h":67}, 433 + "pivot": {"x":0.5,"y":0.5} 434 +}, 435 +{ 436 + "filename": "drone-1", 437 + "frame": {"x":546,"y":55,"w":34,"h":48}, 438 + "rotated": false, 439 + "trimmed": true, 440 + "spriteSourceSize": {"x":13,"y":4,"w":34,"h":48}, 441 + "sourceSize": {"w":55,"h":52}, 442 + "pivot": {"x":0.5,"y":0.5} 443 +}, 444 +{ 445 + "filename": "drone-2", 446 + "frame": {"x":343,"y":55,"w":29,"h":48}, 447 + "rotated": false, 448 + "trimmed": true, 449 + "spriteSourceSize": {"x":11,"y":4,"w":29,"h":48}, 450 + "sourceSize": {"w":55,"h":52}, 451 + "pivot": {"x":0.5,"y":0.5} 452 +}, 453 +{ 454 + "filename": "drone-3", 455 + "frame": {"x":444,"y":55,"w":31,"h":48}, 456 + "rotated": false, 457 + "trimmed": true, 458 + "spriteSourceSize": {"x":12,"y":3,"w":31,"h":48}, 459 + "sourceSize": {"w":55,"h":52}, 460 + "pivot": {"x":0.5,"y":0.5} 461 +}, 462 +{ 463 + "filename": "drone-4", 464 + "frame": {"x":312,"y":55,"w":29,"h":48}, 465 + "rotated": false, 466 + "trimmed": true, 467 + "spriteSourceSize": {"x":15,"y":4,"w":29,"h":48}, 468 + "sourceSize": {"w":55,"h":52}, 469 + "pivot": {"x":0.5,"y":0.5} 470 +}, 471 +{ 472 + "filename": "drone_preview", 473 + "frame": {"x":510,"y":55,"w":34,"h":48}, 474 + "rotated": false, 475 + "trimmed": true, 476 + "spriteSourceSize": {"x":13,"y":4,"w":34,"h":48}, 477 + "sourceSize": {"w":55,"h":52}, 478 + "pivot": {"x":0.5,"y":0.5} 479 +}, 480 +{ 481 + "filename": "enemy-explosion-1", 482 + "frame": {"x":825,"y":2,"w":31,"h":31}, 483 + "rotated": false, 484 + "trimmed": true, 485 + "spriteSourceSize": {"x":12,"y":13,"w":31,"h":31}, 486 + "sourceSize": {"w":55,"h":52}, 487 + "pivot": {"x":0.5,"y":0.5} 488 +}, 489 +{ 490 + "filename": "enemy-explosion-2", 491 + "frame": {"x":582,"y":55,"w":42,"h":40}, 492 + "rotated": false, 493 + "trimmed": true, 494 + "spriteSourceSize": {"x":5,"y":9,"w":42,"h":40}, 495 + "sourceSize": {"w":55,"h":52}, 496 + "pivot": {"x":0.5,"y":0.5} 497 +}, 498 +{ 499 + "filename": "enemy-explosion-3", 500 + "frame": {"x":1231,"y":55,"w":48,"h":46}, 501 + "rotated": false, 502 + "trimmed": true, 503 + "spriteSourceSize": {"x":2,"y":4,"w":48,"h":46}, 504 + "sourceSize": {"w":55,"h":52}, 505 + "pivot": {"x":0.5,"y":0.5} 506 +}, 507 +{ 508 + "filename": "enemy-explosion-4", 509 + "frame": {"x":273,"y":133,"w":52,"h":50}, 510 + "rotated": false, 511 + "trimmed": true, 512 + "spriteSourceSize": {"x":1,"y":2,"w":52,"h":50}, 513 + "sourceSize": {"w":55,"h":52}, 514 + "pivot": {"x":0.5,"y":0.5} 515 +}, 516 +{ 517 + "filename": "enemy-explosion-5", 518 + "frame": {"x":555,"y":133,"w":55,"h":52}, 519 + "rotated": false, 520 + "trimmed": false, 521 + "spriteSourceSize": {"x":0,"y":0,"w":55,"h":52}, 522 + "sourceSize": {"w":55,"h":52}, 523 + "pivot": {"x":0.5,"y":0.5} 524 +}, 525 +{ 526 + "filename": "enemy-explosion-6", 527 + "frame": {"x":498,"y":133,"w":55,"h":52}, 528 + "rotated": false, 529 + "trimmed": false, 530 + "spriteSourceSize": {"x":0,"y":0,"w":55,"h":52}, 531 + "sourceSize": {"w":55,"h":52}, 532 + "pivot": {"x":0.5,"y":0.5} 533 +}, 534 +{ 535 + "filename": "enemy-explosion-preview", 536 + "frame": {"x":662,"y":55,"w":34,"h":48}, 537 + "rotated": false, 538 + "trimmed": true, 539 + "spriteSourceSize": {"x":13,"y":4,"w":34,"h":48}, 540 + "sourceSize": {"w":55,"h":52}, 541 + "pivot": {"x":0.5,"y":0.5} 542 +}, 543 +{ 544 + "filename": "hurt", 545 + "frame": {"x":141,"y":55,"w":25,"h":47}, 546 + "rotated": false, 547 + "trimmed": true, 548 + "spriteSourceSize": {"x":21,"y":20,"w":25,"h":47}, 549 + "sourceSize": {"w":71,"h":67}, 550 + "pivot": {"x":0.5,"y":0.5} 551 +}, 552 +{ 553 + "filename": "idle-1", 554 + "frame": {"x":1057,"y":2,"w":21,"h":46}, 555 + "rotated": false, 556 + "trimmed": true, 557 + "spriteSourceSize": {"x":28,"y":21,"w":21,"h":46}, 558 + "sourceSize": {"w":71,"h":67}, 559 + "pivot": {"x":0.5,"y":0.5} 560 +}, 561 +{ 562 + "filename": "idle-2", 563 + "frame": {"x":24,"y":55,"w":20,"h":50}, 564 + "rotated": false, 565 + "trimmed": true, 566 + "spriteSourceSize": {"x":28,"y":17,"w":20,"h":50}, 567 + "sourceSize": {"w":71,"h":67}, 568 + "pivot": {"x":0.5,"y":0.5} 569 +}, 570 +{ 571 + "filename": "idle-3", 572 + "frame": {"x":119,"y":55,"w":20,"h":51}, 573 + "rotated": false, 574 + "trimmed": true, 575 + "spriteSourceSize": {"x":28,"y":16,"w":20,"h":51}, 576 + "sourceSize": {"w":71,"h":67}, 577 + "pivot": {"x":0.5,"y":0.5} 578 +}, 579 +{ 580 + "filename": "idle-4", 581 + "frame": {"x":80,"y":55,"w":20,"h":50}, 582 + "rotated": false, 583 + "trimmed": true, 584 + "spriteSourceSize": {"x":28,"y":17,"w":20,"h":50}, 585 + "sourceSize": {"w":71,"h":67}, 586 + "pivot": {"x":0.5,"y":0.5} 587 +}, 588 +{ 589 + "filename": "idle-peview", 590 + "frame": {"x":1080,"y":2,"w":21,"h":46}, 591 + "rotated": false, 592 + "trimmed": true, 593 + "spriteSourceSize": {"x":28,"y":21,"w":21,"h":46}, 594 + "sourceSize": {"w":71,"h":67}, 595 + "pivot": {"x":0.5,"y":0.5} 596 +}, 597 +{ 598 + "filename": "jump-1", 599 + "frame": {"x":626,"y":55,"w":34,"h":48}, 600 + "rotated": false, 601 + "trimmed": true, 602 + "spriteSourceSize": {"x":22,"y":14,"w":34,"h":48}, 603 + "sourceSize": {"w":71,"h":67}, 604 + "pivot": {"x":0.5,"y":0.5} 605 +}, 606 +{ 607 + "filename": "jump-2", 608 + "frame": {"x":269,"y":55,"w":41,"h":35}, 609 + "rotated": false, 610 + "trimmed": true, 611 + "spriteSourceSize": {"x":15,"y":15,"w":41,"h":35}, 612 + "sourceSize": {"w":71,"h":67}, 613 + "pivot": {"x":0.5,"y":0.5} 614 +}, 615 +{ 616 + "filename": "jump-3", 617 + "frame": {"x":1342,"y":2,"w":37,"h":32}, 618 + "rotated": false, 619 + "trimmed": true, 620 + "spriteSourceSize": {"x":21,"y":18,"w":37,"h":32}, 621 + "sourceSize": {"w":71,"h":67}, 622 + "pivot": {"x":0.5,"y":0.5} 623 +}, 624 +{ 625 + "filename": "jump-4", 626 + "frame": {"x":233,"y":55,"w":34,"h":40}, 627 + "rotated": false, 628 + "trimmed": true, 629 + "spriteSourceSize": {"x":23,"y":16,"w":34,"h":40}, 630 + "sourceSize": {"w":71,"h":67}, 631 + "pivot": {"x":0.5,"y":0.5} 632 +}, 633 +{ 634 + "filename": "jump-preview", 635 + "frame": {"x":2,"y":55,"w":20,"h":50}, 636 + "rotated": false, 637 + "trimmed": true, 638 + "spriteSourceSize": {"x":28,"y":17,"w":20,"h":50}, 639 + "sourceSize": {"w":71,"h":67}, 640 + "pivot": {"x":0.5,"y":0.5} 641 +}, 642 +{ 643 + "filename": "monitor-face-1", 644 + "frame": {"x":193,"y":2,"w":21,"h":18}, 645 + "rotated": false, 646 + "trimmed": false, 647 + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":18}, 648 + "sourceSize": {"w":21,"h":18}, 649 + "pivot": {"x":0.5,"y":0.5} 650 +}, 651 +{ 652 + "filename": "monitor-face-2", 653 + "frame": {"x":101,"y":2,"w":21,"h":18}, 654 + "rotated": false, 655 + "trimmed": false, 656 + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":18}, 657 + "sourceSize": {"w":21,"h":18}, 658 + "pivot": {"x":0.5,"y":0.5} 659 +}, 660 +{ 661 + "filename": "monitor-face-3", 662 + "frame": {"x":124,"y":2,"w":21,"h":18}, 663 + "rotated": false, 664 + "trimmed": false, 665 + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":18}, 666 + "sourceSize": {"w":21,"h":18}, 667 + "pivot": {"x":0.5,"y":0.5} 668 +}, 669 +{ 670 + "filename": "monitor-face-4", 671 + "frame": {"x":147,"y":2,"w":21,"h":18}, 672 + "rotated": false, 673 + "trimmed": false, 674 + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":18}, 675 + "sourceSize": {"w":21,"h":18}, 676 + "pivot": {"x":0.5,"y":0.5} 677 +}, 678 +{ 679 + "filename": "monitorface_preview", 680 + "frame": {"x":170,"y":2,"w":21,"h":18}, 681 + "rotated": false, 682 + "trimmed": false, 683 + "spriteSourceSize": {"x":0,"y":0,"w":21,"h":18}, 684 + "sourceSize": {"w":21,"h":18}, 685 + "pivot": {"x":0.5,"y":0.5} 686 +}, 687 +{ 688 + "filename": "run-1", 689 + "frame": {"x":1046,"y":55,"w":41,"h":51}, 690 + "rotated": false, 691 + "trimmed": true, 692 + "spriteSourceSize": {"x":10,"y":16,"w":41,"h":51}, 693 + "sourceSize": {"w":71,"h":67}, 694 + "pivot": {"x":0.5,"y":0.5} 695 +}, 696 +{ 697 + "filename": "run-2", 698 + "frame": {"x":1138,"y":55,"w":47,"h":46}, 699 + "rotated": false, 700 + "trimmed": true, 701 + "spriteSourceSize": {"x":7,"y":18,"w":47,"h":46}, 702 + "sourceSize": {"w":71,"h":67}, 703 + "pivot": {"x":0.5,"y":0.5} 704 +}, 705 +{ 706 + "filename": "run-3", 707 + "frame": {"x":735,"y":55,"w":36,"h":48}, 708 + "rotated": false, 709 + "trimmed": true, 710 + "spriteSourceSize": {"x":16,"y":19,"w":36,"h":48}, 711 + "sourceSize": {"w":71,"h":67}, 712 + "pivot": {"x":0.5,"y":0.5} 713 +}, 714 +{ 715 + "filename": "run-4", 716 + "frame": {"x":205,"y":55,"w":26,"h":48}, 717 + "rotated": false, 718 + "trimmed": true, 719 + "spriteSourceSize": {"x":26,"y":19,"w":26,"h":48}, 720 + "sourceSize": {"w":71,"h":67}, 721 + "pivot": {"x":0.5,"y":0.5} 722 +}, 723 +{ 724 + "filename": "run-5", 725 + "frame": {"x":1187,"y":55,"w":42,"h":51}, 726 + "rotated": false, 727 + "trimmed": true, 728 + "spriteSourceSize": {"x":10,"y":16,"w":42,"h":51}, 729 + "sourceSize": {"w":71,"h":67}, 730 + "pivot": {"x":0.5,"y":0.5} 731 +}, 732 +{ 733 + "filename": "run-6", 734 + "frame": {"x":1089,"y":55,"w":47,"h":46}, 735 + "rotated": false, 736 + "trimmed": true, 737 + "spriteSourceSize": {"x":7,"y":18,"w":47,"h":46}, 738 + "sourceSize": {"w":71,"h":67}, 739 + "pivot": {"x":0.5,"y":0.5} 740 +}, 741 +{ 742 + "filename": "run-7", 743 + "frame": {"x":698,"y":55,"w":35,"h":48}, 744 + "rotated": false, 745 + "trimmed": true, 746 + "spriteSourceSize": {"x":16,"y":19,"w":35,"h":48}, 747 + "sourceSize": {"w":71,"h":67}, 748 + "pivot": {"x":0.5,"y":0.5} 749 +}, 750 +{ 751 + "filename": "run-8", 752 + "frame": {"x":477,"y":55,"w":31,"h":48}, 753 + "rotated": false, 754 + "trimmed": true, 755 + "spriteSourceSize": {"x":21,"y":19,"w":31,"h":48}, 756 + "sourceSize": {"w":71,"h":67}, 757 + "pivot": {"x":0.5,"y":0.5} 758 +}, 759 +{ 760 + "filename": "run-preview", 761 + "frame": {"x":939,"y":55,"w":41,"h":51}, 762 + "rotated": false, 763 + "trimmed": true, 764 + "spriteSourceSize": {"x":10,"y":16,"w":41,"h":51}, 765 + "sourceSize": {"w":71,"h":67}, 766 + "pivot": {"x":0.5,"y":0.5} 767 +}, 768 +{ 769 + "filename": "run-shoot-1", 770 + "frame": {"x":162,"y":133,"w":51,"h":51}, 771 + "rotated": false, 772 + "trimmed": true, 773 + "spriteSourceSize": {"x":10,"y":16,"w":51,"h":51}, 774 + "sourceSize": {"w":71,"h":67}, 775 + "pivot": {"x":0.5,"y":0.5} 776 +}, 777 +{ 778 + "filename": "run-shoot-2", 779 + "frame": {"x":52,"y":133,"w":55,"h":46}, 780 + "rotated": false, 781 + "trimmed": true, 782 + "spriteSourceSize": {"x":7,"y":18,"w":55,"h":46}, 783 + "sourceSize": {"w":71,"h":67}, 784 + "pivot": {"x":0.5,"y":0.5} 785 +}, 786 +{ 787 + "filename": "run-shoot-3", 788 + "frame": {"x":1386,"y":55,"w":47,"h":48}, 789 + "rotated": false, 790 + "trimmed": true, 791 + "spriteSourceSize": {"x":16,"y":19,"w":47,"h":48}, 792 + "sourceSize": {"w":71,"h":67}, 793 + "pivot": {"x":0.5,"y":0.5} 794 +}, 795 +{ 796 + "filename": "run-shoot-4", 797 + "frame": {"x":819,"y":55,"w":37,"h":48}, 798 + "rotated": false, 799 + "trimmed": true, 800 + "spriteSourceSize": {"x":26,"y":19,"w":37,"h":48}, 801 + "sourceSize": {"w":71,"h":67}, 802 + "pivot": {"x":0.5,"y":0.5} 803 +}, 804 +{ 805 + "filename": "run-shoot-5", 806 + "frame": {"x":327,"y":133,"w":53,"h":51}, 807 + "rotated": false, 808 + "trimmed": true, 809 + "spriteSourceSize": {"x":10,"y":16,"w":53,"h":51}, 810 + "sourceSize": {"w":71,"h":67}, 811 + "pivot": {"x":0.5,"y":0.5} 812 +}, 813 +{ 814 + "filename": "run-shoot-6", 815 + "frame": {"x":215,"y":133,"w":56,"h":46}, 816 + "rotated": false, 817 + "trimmed": true, 818 + "spriteSourceSize": {"x":7,"y":18,"w":56,"h":46}, 819 + "sourceSize": {"w":71,"h":67}, 820 + "pivot": {"x":0.5,"y":0.5} 821 +}, 822 +{ 823 + "filename": "run-shoot-7", 824 + "frame": {"x":2,"y":133,"w":48,"h":48}, 825 + "rotated": false, 826 + "trimmed": true, 827 + "spriteSourceSize": {"x":16,"y":19,"w":48,"h":48}, 828 + "sourceSize": {"w":71,"h":67}, 829 + "pivot": {"x":0.5,"y":0.5} 830 +}, 831 +{ 832 + "filename": "run-shoot-8", 833 + "frame": {"x":895,"y":55,"w":42,"h":48}, 834 + "rotated": false, 835 + "trimmed": true, 836 + "spriteSourceSize": {"x":21,"y":19,"w":42,"h":48}, 837 + "sourceSize": {"w":71,"h":67}, 838 + "pivot": {"x":0.5,"y":0.5} 839 +}, 840 +{ 841 + "filename": "run-shoot-preview", 842 + "frame": {"x":109,"y":133,"w":51,"h":51}, 843 + "rotated": false, 844 + "trimmed": true, 845 + "spriteSourceSize": {"x":10,"y":16,"w":51,"h":51}, 846 + "sourceSize": {"w":71,"h":67}, 847 + "pivot": {"x":0.5,"y":0.5} 848 +}, 849 +{ 850 + "filename": "shoot", 851 + "frame": {"x":858,"y":55,"w":35,"h":51}, 852 + "rotated": false, 853 + "trimmed": true, 854 + "spriteSourceSize": {"x":20,"y":16,"w":35,"h":51}, 855 + "sourceSize": {"w":71,"h":67}, 856 + "pivot": {"x":0.5,"y":0.5} 857 +}, 858 +{ 859 + "filename": "shot-1", 860 + "frame": {"x":41,"y":2,"w":12,"h":5}, 861 + "rotated": false, 862 + "trimmed": true, 863 + "spriteSourceSize": {"x":1,"y":3,"w":12,"h":5}, 864 + "sourceSize": {"w":15,"h":11}, 865 + "pivot": {"x":0.5,"y":0.5} 866 +}, 867 +{ 868 + "filename": "shot-2", 869 + "frame": {"x":2,"y":2,"w":10,"h":7}, 870 + "rotated": false, 871 + "trimmed": true, 872 + "spriteSourceSize": {"x":1,"y":2,"w":10,"h":7}, 873 + "sourceSize": {"w":15,"h":11}, 874 + "pivot": {"x":0.5,"y":0.5} 875 +}, 876 +{ 877 + "filename": "shot-3", 878 + "frame": {"x":14,"y":2,"w":11,"h":6}, 879 + "rotated": false, 880 + "trimmed": true, 881 + "spriteSourceSize": {"x":1,"y":3,"w":11,"h":6}, 882 + "sourceSize": {"w":15,"h":11}, 883 + "pivot": {"x":0.5,"y":0.5} 884 +}, 885 +{ 886 + "filename": "shot-hit-1", 887 + "frame": {"x":55,"y":2,"w":9,"h":9}, 888 + "rotated": false, 889 + "trimmed": true, 890 + "spriteSourceSize": {"x":3,"y":1,"w":9,"h":9}, 891 + "sourceSize": {"w":15,"h":11}, 892 + "pivot": {"x":0.5,"y":0.5} 893 +}, 894 +{ 895 + "filename": "shot-hit-2", 896 + "frame": {"x":66,"y":2,"w":9,"h":9}, 897 + "rotated": false, 898 + "trimmed": true, 899 + "spriteSourceSize": {"x":3,"y":1,"w":9,"h":9}, 900 + "sourceSize": {"w":15,"h":11}, 901 + "pivot": {"x":0.5,"y":0.5} 902 +}, 903 +{ 904 + "filename": "shot-hit-3", 905 + "frame": {"x":88,"y":2,"w":11,"h":11}, 906 + "rotated": false, 907 + "trimmed": true, 908 + "spriteSourceSize": {"x":2,"y":0,"w":11,"h":11}, 909 + "sourceSize": {"w":15,"h":11}, 910 + "pivot": {"x":0.5,"y":0.5} 911 +}, 912 +{ 913 + "filename": "shot-hit-preview", 914 + "frame": {"x":77,"y":2,"w":9,"h":9}, 915 + "rotated": false, 916 + "trimmed": true, 917 + "spriteSourceSize": {"x":3,"y":1,"w":9,"h":9}, 918 + "sourceSize": {"w":15,"h":11}, 919 + "pivot": {"x":0.5,"y":0.5} 920 +}, 921 +{ 922 + "filename": "shot-preview", 923 + "frame": {"x":27,"y":2,"w":12,"h":5}, 924 + "rotated": false, 925 + "trimmed": true, 926 + "spriteSourceSize": {"x":1,"y":3,"w":12,"h":5}, 927 + "sourceSize": {"w":15,"h":11}, 928 + "pivot": {"x":0.5,"y":0.5} 929 +}, 930 +{ 931 + "filename": "turret-1", 932 + "frame": {"x":315,"y":2,"w":25,"h":23}, 933 + "rotated": false, 934 + "trimmed": false, 935 + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":23}, 936 + "sourceSize": {"w":25,"h":23}, 937 + "pivot": {"x":0.5,"y":0.5} 938 +}, 939 +{ 940 + "filename": "turret-2", 941 + "frame": {"x":342,"y":2,"w":25,"h":23}, 942 + "rotated": false, 943 + "trimmed": false, 944 + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":23}, 945 + "sourceSize": {"w":25,"h":23}, 946 + "pivot": {"x":0.5,"y":0.5} 947 +}, 948 +{ 949 + "filename": "turret-3", 950 + "frame": {"x":261,"y":2,"w":25,"h":23}, 951 + "rotated": false, 952 + "trimmed": false, 953 + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":23}, 954 + "sourceSize": {"w":25,"h":23}, 955 + "pivot": {"x":0.5,"y":0.5} 956 +}, 957 +{ 958 + "filename": "turret-4", 959 + "frame": {"x":234,"y":2,"w":25,"h":23}, 960 + "rotated": false, 961 + "trimmed": false, 962 + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":23}, 963 + "sourceSize": {"w":25,"h":23}, 964 + "pivot": {"x":0.5,"y":0.5} 965 +}, 966 +{ 967 + "filename": "turret-5", 968 + "frame": {"x":288,"y":2,"w":25,"h":23}, 969 + "rotated": false, 970 + "trimmed": false, 971 + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":23}, 972 + "sourceSize": {"w":25,"h":23}, 973 + "pivot": {"x":0.5,"y":0.5} 974 +}, 975 +{ 976 + "filename": "turret-6", 977 + "frame": {"x":396,"y":2,"w":25,"h":23}, 978 + "rotated": false, 979 + "trimmed": false, 980 + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":23}, 981 + "sourceSize": {"w":25,"h":23}, 982 + "pivot": {"x":0.5,"y":0.5} 983 +}, 984 +{ 985 + "filename": "turret-preview", 986 + "frame": {"x":369,"y":2,"w":25,"h":23}, 987 + "rotated": false, 988 + "trimmed": false, 989 + "spriteSourceSize": {"x":0,"y":0,"w":25,"h":23}, 990 + "sourceSize": {"w":25,"h":23}, 991 + "pivot": {"x":0.5,"y":0.5} 992 +}, 993 +{ 994 + "filename": "v-police", 995 + "frame": {"x":1013,"y":133,"w":163,"h":60}, 996 + "rotated": false, 997 + "trimmed": false, 998 + "spriteSourceSize": {"x":0,"y":0,"w":163,"h":60}, 999 + "sourceSize": {"w":163,"h":60}, 1000 + "pivot": {"x":0.5,"y":0.5} 1001 +}, 1002 +{ 1003 + "filename": "v-red", 1004 + "frame": {"x":915,"y":133,"w":96,"h":61}, 1005 + "rotated": false, 1006 + "trimmed": false, 1007 + "spriteSourceSize": {"x":0,"y":0,"w":96,"h":61}, 1008 + "sourceSize": {"w":96,"h":61}, 1009 + "pivot": {"x":0.5,"y":0.5} 1010 +}, 1011 +{ 1012 + "filename": "v-truck", 1013 + "frame": {"x":1178,"y":133,"w":257,"h":104}, 1014 + "rotated": false, 1015 + "trimmed": false, 1016 + "spriteSourceSize": {"x":0,"y":0,"w":257,"h":104}, 1017 + "sourceSize": {"w":257,"h":104}, 1018 + "pivot": {"x":0.5,"y":0.5} 1019 +}, 1020 +{ 1021 + "filename": "v-yellow", 1022 + "frame": {"x":820,"y":133,"w":93,"h":60}, 1023 + "rotated": false, 1024 + "trimmed": false, 1025 + "spriteSourceSize": {"x":0,"y":0,"w":93,"h":60}, 1026 + "sourceSize": {"w":93,"h":60}, 1027 + "pivot": {"x":0.5,"y":0.5} 1028 +}, 1029 +{ 1030 + "filename": "walk-1", 1031 + "frame": {"x":1320,"y":2,"w":20,"h":49}, 1032 + "rotated": false, 1033 + "trimmed": true, 1034 + "spriteSourceSize": {"x":28,"y":18,"w":20,"h":49}, 1035 + "sourceSize": {"w":71,"h":67}, 1036 + "pivot": {"x":0.5,"y":0.5} 1037 +}, 1038 +{ 1039 + "filename": "walk-10", 1040 + "frame": {"x":1257,"y":2,"w":19,"h":48}, 1041 + "rotated": false, 1042 + "trimmed": true, 1043 + "spriteSourceSize": {"x":26,"y":19,"w":19,"h":48}, 1044 + "sourceSize": {"w":71,"h":67}, 1045 + "pivot": {"x":0.5,"y":0.5} 1046 +}, 1047 +{ 1048 + "filename": "walk-11", 1049 + "frame": {"x":986,"y":2,"w":16,"h":49}, 1050 + "rotated": false, 1051 + "trimmed": true, 1052 + "spriteSourceSize": {"x":27,"y":18,"w":16,"h":49}, 1053 + "sourceSize": {"w":71,"h":67}, 1054 + "pivot": {"x":0.5,"y":0.5} 1055 +}, 1056 +{ 1057 + "filename": "walk-12", 1058 + "frame": {"x":738,"y":2,"w":11,"h":50}, 1059 + "rotated": false, 1060 + "trimmed": true, 1061 + "spriteSourceSize": {"x":30,"y":17,"w":11,"h":50}, 1062 + "sourceSize": {"w":71,"h":67}, 1063 + "pivot": {"x":0.5,"y":0.5} 1064 +}, 1065 +{ 1066 + "filename": "walk-13", 1067 + "frame": {"x":895,"y":2,"w":11,"h":51}, 1068 + "rotated": false, 1069 + "trimmed": true, 1070 + "spriteSourceSize": {"x":30,"y":16,"w":11,"h":51}, 1071 + "sourceSize": {"w":71,"h":67}, 1072 + "pivot": {"x":0.5,"y":0.5} 1073 +}, 1074 +{ 1075 + "filename": "walk-14", 1076 + "frame": {"x":937,"y":2,"w":13,"h":51}, 1077 + "rotated": false, 1078 + "trimmed": true, 1079 + "spriteSourceSize": {"x":29,"y":16,"w":13,"h":51}, 1080 + "sourceSize": {"w":71,"h":67}, 1081 + "pivot": {"x":0.5,"y":0.5} 1082 +}, 1083 +{ 1084 + "filename": "walk-15", 1085 + "frame": {"x":921,"y":2,"w":14,"h":50}, 1086 + "rotated": false, 1087 + "trimmed": true, 1088 + "spriteSourceSize": {"x":28,"y":17,"w":14,"h":50}, 1089 + "sourceSize": {"w":71,"h":67}, 1090 + "pivot": {"x":0.5,"y":0.5} 1091 +}, 1092 +{ 1093 + "filename": "walk-16", 1094 + "frame": {"x":1381,"y":2,"w":19,"h":50}, 1095 + "rotated": false, 1096 + "trimmed": true, 1097 + "spriteSourceSize": {"x":27,"y":17,"w":19,"h":50}, 1098 + "sourceSize": {"w":71,"h":67}, 1099 + "pivot": {"x":0.5,"y":0.5} 1100 +}, 1101 +{ 1102 + "filename": "walk-2", 1103 + "frame": {"x":1154,"y":2,"w":19,"h":48}, 1104 + "rotated": false, 1105 + "trimmed": true, 1106 + "spriteSourceSize": {"x":26,"y":19,"w":19,"h":48}, 1107 + "sourceSize": {"w":71,"h":67}, 1108 + "pivot": {"x":0.5,"y":0.5} 1109 +}, 1110 +{ 1111 + "filename": "walk-3", 1112 + "frame": {"x":1004,"y":2,"w":16,"h":49}, 1113 + "rotated": false, 1114 + "trimmed": true, 1115 + "spriteSourceSize": {"x":27,"y":18,"w":16,"h":49}, 1116 + "sourceSize": {"w":71,"h":67}, 1117 + "pivot": {"x":0.5,"y":0.5} 1118 +}, 1119 +{ 1120 + "filename": "walk-4", 1121 + "frame": {"x":751,"y":2,"w":11,"h":50}, 1122 + "rotated": false, 1123 + "trimmed": true, 1124 + "spriteSourceSize": {"x":30,"y":17,"w":11,"h":50}, 1125 + "sourceSize": {"w":71,"h":67}, 1126 + "pivot": {"x":0.5,"y":0.5} 1127 +}, 1128 +{ 1129 + "filename": "walk-5", 1130 + "frame": {"x":908,"y":2,"w":11,"h":51}, 1131 + "rotated": false, 1132 + "trimmed": true, 1133 + "spriteSourceSize": {"x":30,"y":16,"w":11,"h":51}, 1134 + "sourceSize": {"w":71,"h":67}, 1135 + "pivot": {"x":0.5,"y":0.5} 1136 +}, 1137 +{ 1138 + "filename": "walk-6", 1139 + "frame": {"x":798,"y":2,"w":11,"h":51}, 1140 + "rotated": false, 1141 + "trimmed": true, 1142 + "spriteSourceSize": {"x":30,"y":16,"w":11,"h":51}, 1143 + "sourceSize": {"w":71,"h":67}, 1144 + "pivot": {"x":0.5,"y":0.5} 1145 +}, 1146 +{ 1147 + "filename": "walk-7", 1148 + "frame": {"x":811,"y":2,"w":12,"h":50}, 1149 + "rotated": false, 1150 + "trimmed": true, 1151 + "spriteSourceSize": {"x":30,"y":17,"w":12,"h":50}, 1152 + "sourceSize": {"w":71,"h":67}, 1153 + "pivot": {"x":0.5,"y":0.5} 1154 +}, 1155 +{ 1156 + "filename": "walk-8", 1157 + "frame": {"x":1175,"y":2,"w":17,"h":50}, 1158 + "rotated": false, 1159 + "trimmed": true, 1160 + "spriteSourceSize": {"x":29,"y":17,"w":17,"h":50}, 1161 + "sourceSize": {"w":71,"h":67}, 1162 + "pivot": {"x":0.5,"y":0.5} 1163 +}, 1164 +{ 1165 + "filename": "walk-9", 1166 + "frame": {"x":1299,"y":2,"w":19,"h":49}, 1167 + "rotated": false, 1168 + "trimmed": true, 1169 + "spriteSourceSize": {"x":29,"y":18,"w":19,"h":49}, 1170 + "sourceSize": {"w":71,"h":67}, 1171 + "pivot": {"x":0.5,"y":0.5} 1172 +}, 1173 +{ 1174 + "filename": "walk-preview", 1175 + "frame": {"x":1402,"y":2,"w":20,"h":49}, 1176 + "rotated": false, 1177 + "trimmed": true, 1178 + "spriteSourceSize": {"x":28,"y":18,"w":20,"h":49}, 1179 + "sourceSize": {"w":71,"h":67}, 1180 + "pivot": {"x":0.5,"y":0.5} 1181 +}], 1182 +"meta": { 1183 + "app": "http://www.codeandweb.com/texturepacker", 1184 + "version": "1.0", 1185 + "image": "atlas.png", 1186 + "format": "RGBA8888", 1187 + "size": {"w":1437,"h":239}, 1188 + "scale": "1", 1189 + "smartupdate": "$TexturePacker:SmartUpdate:133d307a83f4c0480bfd6bd8d553a8ee:a09b2f18f63aa97ec00a8aa0c1bb7edd:cbce6b53f0f49e0bf15173c25c41f876$" 1190 +} 1191 +}
Added src/worpt/assets/warped_city/atlas/atlas.lua.
1 +local frames = { 2 + { 3 + filename = "antenna", 4 + frame = {x=612,y=133,w=21,h=95}, 5 + rotated = false, 6 + trimmed = true, 7 + spriteSourceSize = {x=1,y=1,w=21,h=95}, 8 + sourceSize = {w=22,h=96}, 9 + pivot = {x=0.5,y=0.5} 10 + }, 11 + { 12 + filename = "back-jump-1", 13 + frame = {x=575,y=2,w=24,h=27}, 14 + rotated = false, 15 + trimmed = true, 16 + spriteSourceSize = {x=24,y=40,w=24,h=27}, 17 + sourceSize = {w=71,h=67}, 18 + pivot = {x=0.5,y=0.5} 19 + }, 20 + { 21 + filename = "back-jump-2", 22 + frame = {x=374,y=55,w=35,h=42}, 23 + rotated = false, 24 + trimmed = true, 25 + spriteSourceSize = {x=15,y=25,w=35,h=42}, 26 + sourceSize = {w=71,h=67}, 27 + pivot = {x=0.5,y=0.5} 28 + }, 29 + { 30 + filename = "back-jump-3", 31 + frame = {x=168,y=55,w=35,h=38}, 32 + rotated = false, 33 + trimmed = true, 34 + spriteSourceSize = {x=15,y=20,w=35,h=38}, 35 + sourceSize = {w=71,h=67}, 36 + pivot = {x=0.5,y=0.5} 37 + }, 38 + { 39 + filename = "back-jump-4", 40 + frame = {x=773,y=55,w=44,h=40}, 41 + rotated = false, 42 + trimmed = true, 43 + spriteSourceSize = {x=12,y=16,w=44,h=40}, 44 + sourceSize = {w=71,h=67}, 45 + pivot = {x=0.5,y=0.5} 46 + }, 47 + { 48 + filename = "back-jump-5", 49 + frame = {x=411,y=55,w=31,h=47}, 50 + rotated = false, 51 + trimmed = true, 52 + spriteSourceSize = {x=17,y=18,w=31,h=47}, 53 + sourceSize = {w=71,h=67}, 54 + pivot = {x=0.5,y=0.5} 55 + }, 56 + { 57 + filename = "back-jump-6", 58 + frame = {x=1124,y=2,w=28,h=39}, 59 + rotated = false, 60 + trimmed = true, 61 + spriteSourceSize = {x=24,y=28,w=28,h=39}, 62 + sourceSize = {w=71,h=67}, 63 + pivot = {x=0.5,y=0.5} 64 + }, 65 + { 66 + filename = "back-jump-7", 67 + frame = {x=858,y=2,w=35,h=27}, 68 + rotated = false, 69 + trimmed = true, 70 + spriteSourceSize = {x=19,y=40,w=35,h=27}, 71 + sourceSize = {w=71,h=67}, 72 + pivot = {x=0.5,y=0.5} 73 + }, 74 + { 75 + filename = "back-jump-preview", 76 + frame = {x=601,y=2,w=24,h=27}, 77 + rotated = false, 78 + trimmed = true, 79 + spriteSourceSize = {x=24,y=40,w=24,h=27}, 80 + sourceSize = {w=71,h=67}, 81 + pivot = {x=0.5,y=0.5} 82 + }, 83 + { 84 + filename = "banner-big-1", 85 + frame = {x=635,y=133,w=35,h=92}, 86 + rotated = false, 87 + trimmed = false, 88 + spriteSourceSize = {x=0,y=0,w=35,h=92}, 89 + sourceSize = {w=35,h=92}, 90 + pivot = {x=0.5,y=0.5} 91 + }, 92 + { 93 + filename = "banner-big-2", 94 + frame = {x=672,y=133,w=35,h=92}, 95 + rotated = false, 96 + trimmed = false, 97 + spriteSourceSize = {x=0,y=0,w=35,h=92}, 98 + sourceSize = {w=35,h=92}, 99 + pivot = {x=0.5,y=0.5} 100 + }, 101 + { 102 + filename = "banner-big-3", 103 + frame = {x=746,y=133,w=35,h=92}, 104 + rotated = false, 105 + trimmed = false, 106 + spriteSourceSize = {x=0,y=0,w=35,h=92}, 107 + sourceSize = {w=35,h=92}, 108 + pivot = {x=0.5,y=0.5} 109 + }, 110 + { 111 + filename = "banner-big-4", 112 + frame = {x=783,y=133,w=35,h=92}, 113 + rotated = false, 114 + trimmed = false, 115 + spriteSourceSize = {x=0,y=0,w=35,h=92}, 116 + sourceSize = {w=35,h=92}, 117 + pivot = {x=0.5,y=0.5} 118 + }, 119 + { 120 + filename = "banner-big-preview", 121 + frame = {x=709,y=133,w=35,h=92}, 122 + rotated = false, 123 + trimmed = false, 124 + spriteSourceSize = {x=0,y=0,w=35,h=92}, 125 + sourceSize = {w=35,h=92}, 126 + pivot = {x=0.5,y=0.5} 127 + }, 128 + { 129 + filename = "banner-coke-1", 130 + frame = {x=382,y=133,w=27,h=78}, 131 + rotated = false, 132 + trimmed = false, 133 + spriteSourceSize = {x=0,y=0,w=27,h=78}, 134 + sourceSize = {w=27,h=78}, 135 + pivot = {x=0.5,y=0.5} 136 + }, 137 + { 138 + filename = "banner-coke-2", 139 + frame = {x=440,y=133,w=27,h=78}, 140 + rotated = false, 141 + trimmed = false, 142 + spriteSourceSize = {x=0,y=0,w=27,h=78}, 143 + sourceSize = {w=27,h=78}, 144 + pivot = {x=0.5,y=0.5} 145 + }, 146 + { 147 + filename = "banner-coke-3", 148 + frame = {x=411,y=133,w=27,h=78}, 149 + rotated = false, 150 + trimmed = false, 151 + spriteSourceSize = {x=0,y=0,w=27,h=78}, 152 + sourceSize = {w=27,h=78}, 153 + pivot = {x=0.5,y=0.5} 154 + }, 155 + { 156 + filename = "banner-coke-preview", 157 + frame = {x=469,y=133,w=27,h=78}, 158 + rotated = false, 159 + trimmed = false, 160 + spriteSourceSize = {x=0,y=0,w=27,h=78}, 161 + sourceSize = {w=27,h=78}, 162 + pivot = {x=0.5,y=0.5} 163 + }, 164 + { 165 + filename = "banner-neon-1", 166 + frame = {x=1215,y=2,w=19,h=48}, 167 + rotated = false, 168 + trimmed = false, 169 + spriteSourceSize = {x=0,y=0,w=19,h=48}, 170 + sourceSize = {w=19,h=48}, 171 + pivot = {x=0.5,y=0.5} 172 + }, 173 + { 174 + filename = "banner-neon-2", 175 + frame = {x=1194,y=2,w=19,h=48}, 176 + rotated = false, 177 + trimmed = false, 178 + spriteSourceSize = {x=0,y=0,w=19,h=48}, 179 + sourceSize = {w=19,h=48}, 180 + pivot = {x=0.5,y=0.5} 181 + }, 182 + { 183 + filename = "banner-neon-3", 184 + frame = {x=1103,y=2,w=19,h=48}, 185 + rotated = false, 186 + trimmed = false, 187 + spriteSourceSize = {x=0,y=0,w=19,h=48}, 188 + sourceSize = {w=19,h=48}, 189 + pivot = {x=0.5,y=0.5} 190 + }, 191 + { 192 + filename = "banner-neon-4", 193 + frame = {x=1236,y=2,w=19,h=48}, 194 + rotated = false, 195 + trimmed = false, 196 + spriteSourceSize = {x=0,y=0,w=19,h=48}, 197 + sourceSize = {w=19,h=48}, 198 + pivot = {x=0.5,y=0.5} 199 + }, 200 + { 201 + filename = "banner-neon-preview", 202 + frame = {x=1278,y=2,w=19,h=48}, 203 + rotated = false, 204 + trimmed = false, 205 + spriteSourceSize = {x=0,y=0,w=19,h=48}, 206 + sourceSize = {w=19,h=48}, 207 + pivot = {x=0.5,y=0.5} 208 + }, 209 + { 210 + filename = "banner-scroll-1", 211 + frame = {x=678,y=2,w=13,h=47}, 212 + rotated = false, 213 + trimmed = false, 214 + spriteSourceSize = {x=0,y=0,w=13,h=47}, 215 + sourceSize = {w=13,h=47}, 216 + pivot = {x=0.5,y=0.5} 217 + }, 218 + { 219 + filename = "banner-scroll-2", 220 + frame = {x=663,y=2,w=13,h=47}, 221 + rotated = false, 222 + trimmed = false, 223 + spriteSourceSize = {x=0,y=0,w=13,h=47}, 224 + sourceSize = {w=13,h=47}, 225 + pivot = {x=0.5,y=0.5} 226 + }, 227 + { 228 + filename = "banner-scroll-3", 229 + frame = {x=693,y=2,w=13,h=47}, 230 + rotated = false, 231 + trimmed = false, 232 + spriteSourceSize = {x=0,y=0,w=13,h=47}, 233 + sourceSize = {w=13,h=47}, 234 + pivot = {x=0.5,y=0.5} 235 + }, 236 + { 237 + filename = "banner-scroll-4", 238 + frame = {x=723,y=2,w=13,h=47}, 239 + rotated = false, 240 + trimmed = false, 241 + spriteSourceSize = {x=0,y=0,w=13,h=47}, 242 + sourceSize = {w=13,h=47}, 243 + pivot = {x=0.5,y=0.5} 244 + }, 245 + { 246 + filename = "banner-scroll-preview", 247 + frame = {x=708,y=2,w=13,h=47}, 248 + rotated = false, 249 + trimmed = false, 250 + spriteSourceSize = {x=0,y=0,w=13,h=47}, 251 + sourceSize = {w=13,h=47}, 252 + pivot = {x=0.5,y=0.5} 253 + }, 254 + { 255 + filename = "banner-side-1", 256 + frame = {x=1323,y=55,w=19,h=76}, 257 + rotated = false, 258 + trimmed = false, 259 + spriteSourceSize = {x=0,y=0,w=19,h=76}, 260 + sourceSize = {w=19,h=76}, 261 + pivot = {x=0.5,y=0.5} 262 + }, 263 + { 264 + filename = "banner-side-2", 265 + frame = {x=1302,y=55,w=19,h=76}, 266 + rotated = false, 267 + trimmed = false, 268 + spriteSourceSize = {x=0,y=0,w=19,h=76}, 269 + sourceSize = {w=19,h=76}, 270 + pivot = {x=0.5,y=0.5} 271 + }, 272 + { 273 + filename = "banner-side-3", 274 + frame = {x=1281,y=55,w=19,h=76}, 275 + rotated = false, 276 + trimmed = false, 277 + spriteSourceSize = {x=0,y=0,w=19,h=76}, 278 + sourceSize = {w=19,h=76}, 279 + pivot = {x=0.5,y=0.5} 280 + }, 281 + { 282 + filename = "banner-side-4", 283 + frame = {x=1365,y=55,w=19,h=76}, 284 + rotated = false, 285 + trimmed = false, 286 + spriteSourceSize = {x=0,y=0,w=19,h=76}, 287 + sourceSize = {w=19,h=76}, 288 + pivot = {x=0.5,y=0.5} 289 + }, 290 + { 291 + filename = "banner-side-preview", 292 + frame = {x=1344,y=55,w=19,h=76}, 293 + rotated = false, 294 + trimmed = false, 295 + spriteSourceSize = {x=0,y=0,w=19,h=76}, 296 + sourceSize = {w=19,h=76}, 297 + pivot = {x=0.5,y=0.5} 298 + }, 299 + { 300 + filename = "banner-sushi-1", 301 + frame = {x=499,y=2,w=36,h=13}, 302 + rotated = false, 303 + trimmed = false, 304 + spriteSourceSize = {x=0,y=0,w=36,h=13}, 305 + sourceSize = {w=36,h=13}, 306 + pivot = {x=0.5,y=0.5} 307 + }, 308 + { 309 + filename = "banner-sushi-2", 310 + frame = {x=423,y=2,w=36,h=13}, 311 + rotated = false, 312 + trimmed = false, 313 + spriteSourceSize = {x=0,y=0,w=36,h=13}, 314 + sourceSize = {w=36,h=13}, 315 + pivot = {x=0.5,y=0.5} 316 + }, 317 + { 318 + filename = "banner-sushi-3", 319 + frame = {x=461,y=2,w=36,h=13}, 320 + rotated = false, 321 + trimmed = false, 322 + spriteSourceSize = {x=0,y=0,w=36,h=13}, 323 + sourceSize = {w=36,h=13}, 324 + pivot = {x=0.5,y=0.5} 325 + }, 326 + { 327 + filename = "banner-sushi-preview", 328 + frame = {x=537,y=2,w=36,h=13}, 329 + rotated = false, 330 + trimmed = false, 331 + spriteSourceSize = {x=0,y=0,w=36,h=13}, 332 + sourceSize = {w=36,h=13}, 333 + pivot = {x=0.5,y=0.5} 334 + }, 335 + { 336 + filename = "climb-1", 337 + frame = {x=63,y=55,w=15,h=55}, 338 + rotated = false, 339 + trimmed = true, 340 + spriteSourceSize = {x=30,y=10,w=15,h=55}, 341 + sourceSize = {w=71,h=67}, 342 + pivot = {x=0.5,y=0.5} 343 + }, 344 + { 345 + filename = "climb-2", 346 + frame = {x=969,y=2,w=15,h=49}, 347 + rotated = false, 348 + trimmed = true, 349 + spriteSourceSize = {x=30,y=16,w=15,h=49}, 350 + sourceSize = {w=71,h=67}, 351 + pivot = {x=0.5,y=0.5} 352 + }, 353 + { 354 + filename = "climb-3", 355 + frame = {x=627,y=2,w=16,h=40}, 356 + rotated = false, 357 + trimmed = true, 358 + spriteSourceSize = {x=30,y=19,w=16,h=40}, 359 + sourceSize = {w=71,h=67}, 360 + pivot = {x=0.5,y=0.5} 361 + }, 362 + { 363 + filename = "climb-4", 364 + frame = {x=46,y=55,w=15,h=55}, 365 + rotated = false, 366 + trimmed = true, 367 + spriteSourceSize = {x=31,y=10,w=15,h=55}, 368 + sourceSize = {w=71,h=67}, 369 + pivot = {x=0.5,y=0.5} 370 + }, 371 + { 372 + filename = "climb-5", 373 + frame = {x=952,y=2,w=15,h=49}, 374 + rotated = false, 375 + trimmed = true, 376 + spriteSourceSize = {x=31,y=16,w=15,h=49}, 377 + sourceSize = {w=71,h=67}, 378 + pivot = {x=0.5,y=0.5} 379 + }, 380 + { 381 + filename = "climb-6", 382 + frame = {x=645,y=2,w=16,h=40}, 383 + rotated = false, 384 + trimmed = true, 385 + spriteSourceSize = {x=30,y=19,w=16,h=40}, 386 + sourceSize = {w=71,h=67}, 387 + pivot = {x=0.5,y=0.5} 388 + }, 389 + { 390 + filename = "climb-preview", 391 + frame = {x=102,y=55,w=15,h=55}, 392 + rotated = false, 393 + trimmed = true, 394 + spriteSourceSize = {x=30,y=10,w=15,h=55}, 395 + sourceSize = {w=71,h=67}, 396 + pivot = {x=0.5,y=0.5} 397 + }, 398 + { 399 + filename = "control-box-1", 400 + frame = {x=764,y=2,w=32,h=30}, 401 + rotated = false, 402 + trimmed = false, 403 + spriteSourceSize = {x=0,y=0,w=32,h=30}, 404 + sourceSize = {w=32,h=30}, 405 + pivot = {x=0.5,y=0.5} 406 + }, 407 + { 408 + filename = "control-box-2", 409 + frame = {x=216,y=2,w=16,h=27}, 410 + rotated = false, 411 + trimmed = true, 412 + spriteSourceSize = {x=0,y=0,w=16,h=27}, 413 + sourceSize = {w=17,h=27}, 414 + pivot = {x=0.5,y=0.5} 415 + }, 416 + { 417 + filename = "control-box-3", 418 + frame = {x=982,y=55,w=62,h=30}, 419 + rotated = false, 420 + trimmed = false, 421 + spriteSourceSize = {x=0,y=0,w=62,h=30}, 422 + sourceSize = {w=62,h=30}, 423 + pivot = {x=0.5,y=0.5} 424 + }, 425 + { 426 + filename = "crouch", 427 + frame = {x=1022,y=2,w=33,h=33}, 428 + rotated = false, 429 + trimmed = true, 430 + spriteSourceSize = {x=21,y=34,w=33,h=33}, 431 + sourceSize = {w=71,h=67}, 432 + pivot = {x=0.5,y=0.5} 433 + }, 434 + { 435 + filename = "drone-1", 436 + frame = {x=546,y=55,w=34,h=48}, 437 + rotated = false, 438 + trimmed = true, 439 + spriteSourceSize = {x=13,y=4,w=34,h=48}, 440 + sourceSize = {w=55,h=52}, 441 + pivot = {x=0.5,y=0.5} 442 + }, 443 + { 444 + filename = "drone-2", 445 + frame = {x=343,y=55,w=29,h=48}, 446 + rotated = false, 447 + trimmed = true, 448 + spriteSourceSize = {x=11,y=4,w=29,h=48}, 449 + sourceSize = {w=55,h=52}, 450 + pivot = {x=0.5,y=0.5} 451 + }, 452 + { 453 + filename = "drone-3", 454 + frame = {x=444,y=55,w=31,h=48}, 455 + rotated = false, 456 + trimmed = true, 457 + spriteSourceSize = {x=12,y=3,w=31,h=48}, 458 + sourceSize = {w=55,h=52}, 459 + pivot = {x=0.5,y=0.5} 460 + }, 461 + { 462 + filename = "drone-4", 463 + frame = {x=312,y=55,w=29,h=48}, 464 + rotated = false, 465 + trimmed = true, 466 + spriteSourceSize = {x=15,y=4,w=29,h=48}, 467 + sourceSize = {w=55,h=52}, 468 + pivot = {x=0.5,y=0.5} 469 + }, 470 + { 471 + filename = "drone_preview", 472 + frame = {x=510,y=55,w=34,h=48}, 473 + rotated = false, 474 + trimmed = true, 475 + spriteSourceSize = {x=13,y=4,w=34,h=48}, 476 + sourceSize = {w=55,h=52}, 477 + pivot = {x=0.5,y=0.5} 478 + }, 479 + { 480 + filename = "enemy-explosion-1", 481 + frame = {x=825,y=2,w=31,h=31}, 482 + rotated = false, 483 + trimmed = true, 484 + spriteSourceSize = {x=12,y=13,w=31,h=31}, 485 + sourceSize = {w=55,h=52}, 486 + pivot = {x=0.5,y=0.5} 487 + }, 488 + { 489 + filename = "enemy-explosion-2", 490 + frame = {x=582,y=55,w=42,h=40}, 491 + rotated = false, 492 + trimmed = true, 493 + spriteSourceSize = {x=5,y=9,w=42,h=40}, 494 + sourceSize = {w=55,h=52}, 495 + pivot = {x=0.5,y=0.5} 496 + }, 497 + { 498 + filename = "enemy-explosion-3", 499 + frame = {x=1231,y=55,w=48,h=46}, 500 + rotated = false, 501 + trimmed = true, 502 + spriteSourceSize = {x=2,y=4,w=48,h=46}, 503 + sourceSize = {w=55,h=52}, 504 + pivot = {x=0.5,y=0.5} 505 + }, 506 + { 507 + filename = "enemy-explosion-4", 508 + frame = {x=273,y=133,w=52,h=50}, 509 + rotated = false, 510 + trimmed = true, 511 + spriteSourceSize = {x=1,y=2,w=52,h=50}, 512 + sourceSize = {w=55,h=52}, 513 + pivot = {x=0.5,y=0.5} 514 + }, 515 + { 516 + filename = "enemy-explosion-5", 517 + frame = {x=555,y=133,w=55,h=52}, 518 + rotated = false, 519 + trimmed = false, 520 + spriteSourceSize = {x=0,y=0,w=55,h=52}, 521 + sourceSize = {w=55,h=52}, 522 + pivot = {x=0.5,y=0.5} 523 + }, 524 + { 525 + filename = "enemy-explosion-6", 526 + frame = {x=498,y=133,w=55,h=52}, 527 + rotated = false, 528 + trimmed = false, 529 + spriteSourceSize = {x=0,y=0,w=55,h=52}, 530 + sourceSize = {w=55,h=52}, 531 + pivot = {x=0.5,y=0.5} 532 + }, 533 + { 534 + filename = "enemy-explosion-preview", 535 + frame = {x=662,y=55,w=34,h=48}, 536 + rotated = false, 537 + trimmed = true, 538 + spriteSourceSize = {x=13,y=4,w=34,h=48}, 539 + sourceSize = {w=55,h=52}, 540 + pivot = {x=0.5,y=0.5} 541 + }, 542 + { 543 + filename = "hurt", 544 + frame = {x=141,y=55,w=25,h=47}, 545 + rotated = false, 546 + trimmed = true, 547 + spriteSourceSize = {x=21,y=20,w=25,h=47}, 548 + sourceSize = {w=71,h=67}, 549 + pivot = {x=0.5,y=0.5} 550 + }, 551 + { 552 + filename = "idle-1", 553 + frame = {x=1057,y=2,w=21,h=46}, 554 + rotated = false, 555 + trimmed = true, 556 + spriteSourceSize = {x=28,y=21,w=21,h=46}, 557 + sourceSize = {w=71,h=67}, 558 + pivot = {x=0.5,y=0.5} 559 + }, 560 + { 561 + filename = "idle-2", 562 + frame = {x=24,y=55,w=20,h=50}, 563 + rotated = false, 564 + trimmed = true, 565 + spriteSourceSize = {x=28,y=17,w=20,h=50}, 566 + sourceSize = {w=71,h=67}, 567 + pivot = {x=0.5,y=0.5} 568 + }, 569 + { 570 + filename = "idle-3", 571 + frame = {x=119,y=55,w=20,h=51}, 572 + rotated = false, 573 + trimmed = true, 574 + spriteSourceSize = {x=28,y=16,w=20,h=51}, 575 + sourceSize = {w=71,h=67}, 576 + pivot = {x=0.5,y=0.5} 577 + }, 578 + { 579 + filename = "idle-4", 580 + frame = {x=80,y=55,w=20,h=50}, 581 + rotated = false, 582 + trimmed = true, 583 + spriteSourceSize = {x=28,y=17,w=20,h=50}, 584 + sourceSize = {w=71,h=67}, 585 + pivot = {x=0.5,y=0.5} 586 + }, 587 + { 588 + filename = "idle-peview", 589 + frame = {x=1080,y=2,w=21,h=46}, 590 + rotated = false, 591 + trimmed = true, 592 + spriteSourceSize = {x=28,y=21,w=21,h=46}, 593 + sourceSize = {w=71,h=67}, 594 + pivot = {x=0.5,y=0.5} 595 + }, 596 + { 597 + filename = "jump-1", 598 + frame = {x=626,y=55,w=34,h=48}, 599 + rotated = false, 600 + trimmed = true, 601 + spriteSourceSize = {x=22,y=14,w=34,h=48}, 602 + sourceSize = {w=71,h=67}, 603 + pivot = {x=0.5,y=0.5} 604 + }, 605 + { 606 + filename = "jump-2", 607 + frame = {x=269,y=55,w=41,h=35}, 608 + rotated = false, 609 + trimmed = true, 610 + spriteSourceSize = {x=15,y=15,w=41,h=35}, 611 + sourceSize = {w=71,h=67}, 612 + pivot = {x=0.5,y=0.5} 613 + }, 614 + { 615 + filename = "jump-3", 616 + frame = {x=1342,y=2,w=37,h=32}, 617 + rotated = false, 618 + trimmed = true, 619 + spriteSourceSize = {x=21,y=18,w=37,h=32}, 620 + sourceSize = {w=71,h=67}, 621 + pivot = {x=0.5,y=0.5} 622 + }, 623 + { 624 + filename = "jump-4", 625 + frame = {x=233,y=55,w=34,h=40}, 626 + rotated = false, 627 + trimmed = true, 628 + spriteSourceSize = {x=23,y=16,w=34,h=40}, 629 + sourceSize = {w=71,h=67}, 630 + pivot = {x=0.5,y=0.5} 631 + }, 632 + { 633 + filename = "jump-preview", 634 + frame = {x=2,y=55,w=20,h=50}, 635 + rotated = false, 636 + trimmed = true, 637 + spriteSourceSize = {x=28,y=17,w=20,h=50}, 638 + sourceSize = {w=71,h=67}, 639 + pivot = {x=0.5,y=0.5} 640 + }, 641 + { 642 + filename = "monitor-face-1", 643 + frame = {x=193,y=2,w=21,h=18}, 644 + rotated = false, 645 + trimmed = false, 646 + spriteSourceSize = {x=0,y=0,w=21,h=18}, 647 + sourceSize = {w=21,h=18}, 648 + pivot = {x=0.5,y=0.5} 649 + }, 650 + { 651 + filename = "monitor-face-2", 652 + frame = {x=101,y=2,w=21,h=18}, 653 + rotated = false, 654 + trimmed = false, 655 + spriteSourceSize = {x=0,y=0,w=21,h=18}, 656 + sourceSize = {w=21,h=18}, 657 + pivot = {x=0.5,y=0.5} 658 + }, 659 + { 660 + filename = "monitor-face-3", 661 + frame = {x=124,y=2,w=21,h=18}, 662 + rotated = false, 663 + trimmed = false, 664 + spriteSourceSize = {x=0,y=0,w=21,h=18}, 665 + sourceSize = {w=21,h=18}, 666 + pivot = {x=0.5,y=0.5} 667 + }, 668 + { 669 + filename = "monitor-face-4", 670 + frame = {x=147,y=2,w=21,h=18}, 671 + rotated = false, 672 + trimmed = false, 673 + spriteSourceSize = {x=0,y=0,w=21,h=18}, 674 + sourceSize = {w=21,h=18}, 675 + pivot = {x=0.5,y=0.5} 676 + }, 677 + { 678 + filename = "monitorface_preview", 679 + frame = {x=170,y=2,w=21,h=18}, 680 + rotated = false, 681 + trimmed = false, 682 + spriteSourceSize = {x=0,y=0,w=21,h=18}, 683 + sourceSize = {w=21,h=18}, 684 + pivot = {x=0.5,y=0.5} 685 + }, 686 + { 687 + filename = "run-1", 688 + frame = {x=1046,y=55,w=41,h=51}, 689 + rotated = false, 690 + trimmed = true, 691 + spriteSourceSize = {x=10,y=16,w=41,h=51}, 692 + sourceSize = {w=71,h=67}, 693 + pivot = {x=0.5,y=0.5} 694 + }, 695 + { 696 + filename = "run-2", 697 + frame = {x=1138,y=55,w=47,h=46}, 698 + rotated = false, 699 + trimmed = true, 700 + spriteSourceSize = {x=7,y=18,w=47,h=46}, 701 + sourceSize = {w=71,h=67}, 702 + pivot = {x=0.5,y=0.5} 703 + }, 704 + { 705 + filename = "run-3", 706 + frame = {x=735,y=55,w=36,h=48}, 707 + rotated = false, 708 + trimmed = true, 709 + spriteSourceSize = {x=16,y=19,w=36,h=48}, 710 + sourceSize = {w=71,h=67}, 711 + pivot = {x=0.5,y=0.5} 712 + }, 713 + { 714 + filename = "run-4", 715 + frame = {x=205,y=55,w=26,h=48}, 716 + rotated = false, 717 + trimmed = true, 718 + spriteSourceSize = {x=26,y=19,w=26,h=48}, 719 + sourceSize = {w=71,h=67}, 720 + pivot = {x=0.5,y=0.5} 721 + }, 722 + { 723 + filename = "run-5", 724 + frame = {x=1187,y=55,w=42,h=51}, 725 + rotated = false, 726 + trimmed = true, 727 + spriteSourceSize = {x=10,y=16,w=42,h=51}, 728 + sourceSize = {w=71,h=67}, 729 + pivot = {x=0.5,y=0.5} 730 + }, 731 + { 732 + filename = "run-6", 733 + frame = {x=1089,y=55,w=47,h=46}, 734 + rotated = false, 735 + trimmed = true, 736 + spriteSourceSize = {x=7,y=18,w=47,h=46}, 737 + sourceSize = {w=71,h=67}, 738 + pivot = {x=0.5,y=0.5} 739 + }, 740 + { 741 + filename = "run-7", 742 + frame = {x=698,y=55,w=35,h=48}, 743 + rotated = false, 744 + trimmed = true, 745 + spriteSourceSize = {x=16,y=19,w=35,h=48}, 746 + sourceSize = {w=71,h=67}, 747 + pivot = {x=0.5,y=0.5} 748 + }, 749 + { 750 + filename = "run-8", 751 + frame = {x=477,y=55,w=31,h=48}, 752 + rotated = false, 753 + trimmed = true, 754 + spriteSourceSize = {x=21,y=19,w=31,h=48}, 755 + sourceSize = {w=71,h=67}, 756 + pivot = {x=0.5,y=0.5} 757 + }, 758 + { 759 + filename = "run-preview", 760 + frame = {x=939,y=55,w=41,h=51}, 761 + rotated = false, 762 + trimmed = true, 763 + spriteSourceSize = {x=10,y=16,w=41,h=51}, 764 + sourceSize = {w=71,h=67}, 765 + pivot = {x=0.5,y=0.5} 766 + }, 767 + { 768 + filename = "run-shoot-1", 769 + frame = {x=162,y=133,w=51,h=51}, 770 + rotated = false, 771 + trimmed = true, 772 + spriteSourceSize = {x=10,y=16,w=51,h=51}, 773 + sourceSize = {w=71,h=67}, 774 + pivot = {x=0.5,y=0.5} 775 + }, 776 + { 777 + filename = "run-shoot-2", 778 + frame = {x=52,y=133,w=55,h=46}, 779 + rotated = false, 780 + trimmed = true, 781 + spriteSourceSize = {x=7,y=18,w=55,h=46}, 782 + sourceSize = {w=71,h=67}, 783 + pivot = {x=0.5,y=0.5} 784 + }, 785 + { 786 + filename = "run-shoot-3", 787 + frame = {x=1386,y=55,w=47,h=48}, 788 + rotated = false, 789 + trimmed = true, 790 + spriteSourceSize = {x=16,y=19,w=47,h=48}, 791 + sourceSize = {w=71,h=67}, 792 + pivot = {x=0.5,y=0.5} 793 + }, 794 + { 795 + filename = "run-shoot-4", 796 + frame = {x=819,y=55,w=37,h=48}, 797 + rotated = false, 798 + trimmed = true, 799 + spriteSourceSize = {x=26,y=19,w=37,h=48}, 800 + sourceSize = {w=71,h=67}, 801 + pivot = {x=0.5,y=0.5} 802 + }, 803 + { 804 + filename = "run-shoot-5", 805 + frame = {x=327,y=133,w=53,h=51}, 806 + rotated = false, 807 + trimmed = true, 808 + spriteSourceSize = {x=10,y=16,w=53,h=51}, 809 + sourceSize = {w=71,h=67}, 810 + pivot = {x=0.5,y=0.5} 811 + }, 812 + { 813 + filename = "run-shoot-6", 814 + frame = {x=215,y=133,w=56,h=46}, 815 + rotated = false, 816 + trimmed = true, 817 + spriteSourceSize = {x=7,y=18,w=56,h=46}, 818 + sourceSize = {w=71,h=67}, 819 + pivot = {x=0.5,y=0.5} 820 + }, 821 + { 822 + filename = "run-shoot-7", 823 + frame = {x=2,y=133,w=48,h=48}, 824 + rotated = false, 825 + trimmed = true, 826 + spriteSourceSize = {x=16,y=19,w=48,h=48}, 827 + sourceSize = {w=71,h=67}, 828 + pivot = {x=0.5,y=0.5} 829 + }, 830 + { 831 + filename = "run-shoot-8", 832 + frame = {x=895,y=55,w=42,h=48}, 833 + rotated = false, 834 + trimmed = true, 835 + spriteSourceSize = {x=21,y=19,w=42,h=48}, 836 + sourceSize = {w=71,h=67}, 837 + pivot = {x=0.5,y=0.5} 838 + }, 839 + { 840 + filename = "run-shoot-preview", 841 + frame = {x=109,y=133,w=51,h=51}, 842 + rotated = false, 843 + trimmed = true, 844 + spriteSourceSize = {x=10,y=16,w=51,h=51}, 845 + sourceSize = {w=71,h=67}, 846 + pivot = {x=0.5,y=0.5} 847 + }, 848 + { 849 + filename = "shoot", 850 + frame = {x=858,y=55,w=35,h=51}, 851 + rotated = false, 852 + trimmed = true, 853 + spriteSourceSize = {x=20,y=16,w=35,h=51}, 854 + sourceSize = {w=71,h=67}, 855 + pivot = {x=0.5,y=0.5} 856 + }, 857 + { 858 + filename = "shot-1", 859 + frame = {x=41,y=2,w=12,h=5}, 860 + rotated = false, 861 + trimmed = true, 862 + spriteSourceSize = {x=1,y=3,w=12,h=5}, 863 + sourceSize = {w=15,h=11}, 864 + pivot = {x=0.5,y=0.5} 865 + }, 866 + { 867 + filename = "shot-2", 868 + frame = {x=2,y=2,w=10,h=7}, 869 + rotated = false, 870 + trimmed = true, 871 + spriteSourceSize = {x=1,y=2,w=10,h=7}, 872 + sourceSize = {w=15,h=11}, 873 + pivot = {x=0.5,y=0.5} 874 + }, 875 + { 876 + filename = "shot-3", 877 + frame = {x=14,y=2,w=11,h=6}, 878 + rotated = false, 879 + trimmed = true, 880 + spriteSourceSize = {x=1,y=3,w=11,h=6}, 881 + sourceSize = {w=15,h=11}, 882 + pivot = {x=0.5,y=0.5} 883 + }, 884 + { 885 + filename = "shot-hit-1", 886 + frame = {x=55,y=2,w=9,h=9}, 887 + rotated = false, 888 + trimmed = true, 889 + spriteSourceSize = {x=3,y=1,w=9,h=9}, 890 + sourceSize = {w=15,h=11}, 891 + pivot = {x=0.5,y=0.5} 892 + }, 893 + { 894 + filename = "shot-hit-2", 895 + frame = {x=66,y=2,w=9,h=9}, 896 + rotated = false, 897 + trimmed = true, 898 + spriteSourceSize = {x=3,y=1,w=9,h=9}, 899 + sourceSize = {w=15,h=11}, 900 + pivot = {x=0.5,y=0.5} 901 + }, 902 + { 903 + filename = "shot-hit-3", 904 + frame = {x=88,y=2,w=11,h=11}, 905 + rotated = false, 906 + trimmed = true, 907 + spriteSourceSize = {x=2,y=0,w=11,h=11}, 908 + sourceSize = {w=15,h=11}, 909 + pivot = {x=0.5,y=0.5} 910 + }, 911 + { 912 + filename = "shot-hit-preview", 913 + frame = {x=77,y=2,w=9,h=9}, 914 + rotated = false, 915 + trimmed = true, 916 + spriteSourceSize = {x=3,y=1,w=9,h=9}, 917 + sourceSize = {w=15,h=11}, 918 + pivot = {x=0.5,y=0.5} 919 + }, 920 + { 921 + filename = "shot-preview", 922 + frame = {x=27,y=2,w=12,h=5}, 923 + rotated = false, 924 + trimmed = true, 925 + spriteSourceSize = {x=1,y=3,w=12,h=5}, 926 + sourceSize = {w=15,h=11}, 927 + pivot = {x=0.5,y=0.5} 928 + }, 929 + { 930 + filename = "turret-1", 931 + frame = {x=315,y=2,w=25,h=23}, 932 + rotated = false, 933 + trimmed = false, 934 + spriteSourceSize = {x=0,y=0,w=25,h=23}, 935 + sourceSize = {w=25,h=23}, 936 + pivot = {x=0.5,y=0.5} 937 + }, 938 + { 939 + filename = "turret-2", 940 + frame = {x=342,y=2,w=25,h=23}, 941 + rotated = false, 942 + trimmed = false, 943 + spriteSourceSize = {x=0,y=0,w=25,h=23}, 944 + sourceSize = {w=25,h=23}, 945 + pivot = {x=0.5,y=0.5} 946 + }, 947 + { 948 + filename = "turret-3", 949 + frame = {x=261,y=2,w=25,h=23}, 950 + rotated = false, 951 + trimmed = false, 952 + spriteSourceSize = {x=0,y=0,w=25,h=23}, 953 + sourceSize = {w=25,h=23}, 954 + pivot = {x=0.5,y=0.5} 955 + }, 956 + { 957 + filename = "turret-4", 958 + frame = {x=234,y=2,w=25,h=23}, 959 + rotated = false, 960 + trimmed = false, 961 + spriteSourceSize = {x=0,y=0,w=25,h=23}, 962 + sourceSize = {w=25,h=23}, 963 + pivot = {x=0.5,y=0.5} 964 + }, 965 + { 966 + filename = "turret-5", 967 + frame = {x=288,y=2,w=25,h=23}, 968 + rotated = false, 969 + trimmed = false, 970 + spriteSourceSize = {x=0,y=0,w=25,h=23}, 971 + sourceSize = {w=25,h=23}, 972 + pivot = {x=0.5,y=0.5} 973 + }, 974 + { 975 + filename = "turret-6", 976 + frame = {x=396,y=2,w=25,h=23}, 977 + rotated = false, 978 + trimmed = false, 979 + spriteSourceSize = {x=0,y=0,w=25,h=23}, 980 + sourceSize = {w=25,h=23}, 981 + pivot = {x=0.5,y=0.5} 982 + }, 983 + { 984 + filename = "turret-preview", 985 + frame = {x=369,y=2,w=25,h=23}, 986 + rotated = false, 987 + trimmed = false, 988 + spriteSourceSize = {x=0,y=0,w=25,h=23}, 989 + sourceSize = {w=25,h=23}, 990 + pivot = {x=0.5,y=0.5} 991 + }, 992 + { 993 + filename = "v-police", 994 + frame = {x=1013,y=133,w=163,h=60}, 995 + rotated = false, 996 + trimmed = false, 997 + spriteSourceSize = {x=0,y=0,w=163,h=60}, 998 + sourceSize = {w=163,h=60}, 999 + pivot = {x=0.5,y=0.5} 1000 + }, 1001 + { 1002 + filename = "v-red", 1003 + frame = {x=915,y=133,w=96,h=61}, 1004 + rotated = false, 1005 + trimmed = false, 1006 + spriteSourceSize = {x=0,y=0,w=96,h=61}, 1007 + sourceSize = {w=96,h=61}, 1008 + pivot = {x=0.5,y=0.5} 1009 + }, 1010 + { 1011 + filename = "v-truck", 1012 + frame = {x=1178,y=133,w=257,h=104}, 1013 + rotated = false, 1014 + trimmed = false, 1015 + spriteSourceSize = {x=0,y=0,w=257,h=104}, 1016 + sourceSize = {w=257,h=104}, 1017 + pivot = {x=0.5,y=0.5} 1018 + }, 1019 + { 1020 + filename = "v-yellow", 1021 + frame = {x=820,y=133,w=93,h=60}, 1022 + rotated = false, 1023 + trimmed = false, 1024 + spriteSourceSize = {x=0,y=0,w=93,h=60}, 1025 + sourceSize = {w=93,h=60}, 1026 + pivot = {x=0.5,y=0.5} 1027 + }, 1028 + { 1029 + filename = "walk-1", 1030 + frame = {x=1320,y=2,w=20,h=49}, 1031 + rotated = false, 1032 + trimmed = true, 1033 + spriteSourceSize = {x=28,y=18,w=20,h=49}, 1034 + sourceSize = {w=71,h=67}, 1035 + pivot = {x=0.5,y=0.5} 1036 + }, 1037 + { 1038 + filename = "walk-2", 1039 + frame = {x=1154,y=2,w=19,h=48}, 1040 + rotated = false, 1041 + trimmed = true, 1042 + spriteSourceSize = {x=26,y=19,w=19,h=48}, 1043 + sourceSize = {w=71,h=67}, 1044 + pivot = {x=0.5,y=0.5} 1045 + }, 1046 + { 1047 + filename = "walk-3", 1048 + frame = {x=1004,y=2,w=16,h=49}, 1049 + rotated = false, 1050 + trimmed = true, 1051 + spriteSourceSize = {x=27,y=18,w=16,h=49}, 1052 + sourceSize = {w=71,h=67}, 1053 + pivot = {x=0.5,y=0.5} 1054 + }, 1055 + { 1056 + filename = "walk-4", 1057 + frame = {x=751,y=2,w=11,h=50}, 1058 + rotated = false, 1059 + trimmed = true, 1060 + spriteSourceSize = {x=30,y=17,w=11,h=50}, 1061 + sourceSize = {w=71,h=67}, 1062 + pivot = {x=0.5,y=0.5} 1063 + }, 1064 + { 1065 + filename = "walk-5", 1066 + frame = {x=908,y=2,w=11,h=51}, 1067 + rotated = false, 1068 + trimmed = true, 1069 + spriteSourceSize = {x=30,y=16,w=11,h=51}, 1070 + sourceSize = {w=71,h=67}, 1071 + pivot = {x=0.5,y=0.5} 1072 + }, 1073 + { 1074 + filename = "walk-6", 1075 + frame = {x=798,y=2,w=11,h=51}, 1076 + rotated = false, 1077 + trimmed = true, 1078 + spriteSourceSize = {x=30,y=16,w=11,h=51}, 1079 + sourceSize = {w=71,h=67}, 1080 + pivot = {x=0.5,y=0.5} 1081 + }, 1082 + { 1083 + filename = "walk-7", 1084 + frame = {x=811,y=2,w=12,h=50}, 1085 + rotated = false, 1086 + trimmed = true, 1087 + spriteSourceSize = {x=30,y=17,w=12,h=50}, 1088 + sourceSize = {w=71,h=67}, 1089 + pivot = {x=0.5,y=0.5} 1090 + }, 1091 + { 1092 + filename = "walk-8", 1093 + frame = {x=1175,y=2,w=17,h=50}, 1094 + rotated = false, 1095 + trimmed = true, 1096 + spriteSourceSize = {x=29,y=17,w=17,h=50}, 1097 + sourceSize = {w=71,h=67}, 1098 + pivot = {x=0.5,y=0.5} 1099 + }, 1100 + { 1101 + filename = "walk-9", 1102 + frame = {x=1299,y=2,w=19,h=49}, 1103 + rotated = false, 1104 + trimmed = true, 1105 + spriteSourceSize = {x=29,y=18,w=19,h=49}, 1106 + sourceSize = {w=71,h=67}, 1107 + pivot = {x=0.5,y=0.5} 1108 + }, 1109 + { 1110 + filename = "walk-10", 1111 + frame = {x=1257,y=2,w=19,h=48}, 1112 + rotated = false, 1113 + trimmed = true, 1114 + spriteSourceSize = {x=26,y=19,w=19,h=48}, 1115 + sourceSize = {w=71,h=67}, 1116 + pivot = {x=0.5,y=0.5} 1117 + }, 1118 + { 1119 + filename = "walk-11", 1120 + frame = {x=986,y=2,w=16,h=49}, 1121 + rotated = false, 1122 + trimmed = true, 1123 + spriteSourceSize = {x=27,y=18,w=16,h=49}, 1124 + sourceSize = {w=71,h=67}, 1125 + pivot = {x=0.5,y=0.5} 1126 + }, 1127 + { 1128 + filename = "walk-12", 1129 + frame = {x=738,y=2,w=11,h=50}, 1130 + rotated = false, 1131 + trimmed = true, 1132 + spriteSourceSize = {x=30,y=17,w=11,h=50}, 1133 + sourceSize = {w=71,h=67}, 1134 + pivot = {x=0.5,y=0.5} 1135 + }, 1136 + { 1137 + filename = "walk-13", 1138 + frame = {x=895,y=2,w=11,h=51}, 1139 + rotated = false, 1140 + trimmed = true, 1141 + spriteSourceSize = {x=30,y=16,w=11,h=51}, 1142 + sourceSize = {w=71,h=67}, 1143 + pivot = {x=0.5,y=0.5} 1144 + }, 1145 + { 1146 + filename = "walk-14", 1147 + frame = {x=937,y=2,w=13,h=51}, 1148 + rotated = false, 1149 + trimmed = true, 1150 + spriteSourceSize = {x=29,y=16,w=13,h=51}, 1151 + sourceSize = {w=71,h=67}, 1152 + pivot = {x=0.5,y=0.5} 1153 + }, 1154 + { 1155 + filename = "walk-15", 1156 + frame = {x=921,y=2,w=14,h=50}, 1157 + rotated = false, 1158 + trimmed = true, 1159 + spriteSourceSize = {x=28,y=17,w=14,h=50}, 1160 + sourceSize = {w=71,h=67}, 1161 + pivot = {x=0.5,y=0.5} 1162 + }, 1163 + { 1164 + filename = "walk-16", 1165 + frame = {x=1381,y=2,w=19,h=50}, 1166 + rotated = false, 1167 + trimmed = true, 1168 + spriteSourceSize = {x=27,y=17,w=19,h=50}, 1169 + sourceSize = {w=71,h=67}, 1170 + pivot = {x=0.5,y=0.5} 1171 + }, 1172 + { 1173 + filename = "walk-preview", 1174 + frame = {x=1402,y=2,w=20,h=49}, 1175 + rotated = false, 1176 + trimmed = true, 1177 + spriteSourceSize = {x=28,y=18,w=20,h=49}, 1178 + sourceSize = {w=71,h=67}, 1179 + pivot = {x=0.5,y=0.5} 1180 + } 1181 +} 1182 +local meta = { 1183 + app = "http://www.codeandweb.com/texturepacker", 1184 + version = "1.0", 1185 + image = "atlas.png", 1186 + format = "RGBA8888", 1187 + size = {w=1437,h=239}, 1188 + scale = "1", 1189 + smartupdate = "$TexturePacker:SmartUpdate:133d307a83f4c0480bfd6bd8d553a8ee:a09b2f18f63aa97ec00a8aa0c1bb7edd:cbce6b53f0f49e0bf15173c25c41f876$" 1190 +} 1191 +return { 1192 + frames = frames, 1193 + meta = meta 1194 +}
Added src/worpt/assets/warped_city/atlas/atlas.png.
cannot compute difference between binary files
Added src/worpt/assets/warped_city/atlas/atlas.tps.
1 +<?xml version="1.0" encoding="UTF-8"?> 2 +<data version="1.0"> 3 + <struct type="Settings"> 4 + <key>fileFormatVersion</key> 5 + <int>3</int> 6 + <key>texturePackerVersion</key> 7 + <string>3.5.2</string> 8 + <key>fileName</key> 9 + <string>/Users/luiszuno/Documents/GameDev/Phaser/artpacks/2019/warped-city/assets/atlas/atlas.tps</string> 10 + <key>autoSDSettings</key> 11 + <array> 12 + <struct type="AutoSDSettings"> 13 + <key>scale</key> 14 + <double>1</double> 15 + <key>extension</key> 16 + <string></string> 17 + <key>spriteFilter</key> 18 + <string></string> 19 + <key>acceptFractionalValues</key> 20 + <false/> 21 + <key>maxTextureSize</key> 22 + <QSize> 23 + <key>width</key> 24 + <int>-1</int> 25 + <key>height</key> 26 + <int>-1</int> 27 + </QSize> 28 + </struct> 29 + </array> 30 + <key>allowRotation</key> 31 + <true/> 32 + <key>premultiplyAlpha</key> 33 + <false/> 34 + <key>shapeDebug</key> 35 + <false/> 36 + <key>dpi</key> 37 + <uint>72</uint> 38 + <key>dataFormat</key> 39 + <string>json-array</string> 40 + <key>textureFileName</key> 41 + <filename>atlas.png</filename> 42 + <key>flipPVR</key> 43 + <false/> 44 + <key>pvrCompressionQuality</key> 45 + <enum type="SettingsBase::PvrCompressionQuality">PVR_QUALITY_NORMAL</enum> 46 + <key>mipMapMinSize</key> 47 + <uint>32768</uint> 48 + <key>etc1CompressionQuality</key> 49 + <enum type="SettingsBase::Etc1CompressionQuality">ETC1_QUALITY_LOW_PERCEPTUAL</enum> 50 + <key>dxtCompressionMode</key> 51 + <enum type="SettingsBase::DxtCompressionMode">DXT_PERCEPTUAL</enum> 52 + <key>jxrColorFormat</key> 53 + <enum type="SettingsBase::JpegXrColorMode">JXR_YUV444</enum> 54 + <key>jxrTrimFlexBits</key> 55 + <uint>0</uint> 56 + <key>jxrCompressionLevel</key> 57 + <uint>0</uint> 58 + <key>ditherType</key> 59 + <enum type="SettingsBase::DitherType">NearestNeighbour</enum> 60 + <key>backgroundColor</key> 61 + <uint>0</uint> 62 + <key>libGdx</key> 63 + <struct type="LibGDX"> 64 + <key>filtering</key> 65 + <struct type="LibGDXFiltering"> 66 + <key>x</key> 67 + <enum type="LibGDXFiltering::Filtering">Linear</enum> 68 + <key>y</key> 69 + <enum type="LibGDXFiltering::Filtering">Linear</enum> 70 + </struct> 71 + </struct> 72 + <key>shapePadding</key> 73 + <uint>2</uint> 74 + <key>jpgQuality</key> 75 + <uint>80</uint> 76 + <key>pngOptimizationLevel</key> 77 + <uint>0</uint> 78 + <key>webpQualityLevel</key> 79 + <uint>101</uint> 80 + <key>textureSubPath</key> 81 + <string></string> 82 + <key>textureFormat</key> 83 + <enum type="SettingsBase::TextureFormat">png</enum> 84 + <key>borderPadding</key> 85 + <uint>2</uint> 86 + <key>maxTextureSize</key> 87 + <QSize> 88 + <key>width</key> 89 + <int>2048</int> 90 + <key>height</key> 91 + <int>2048</int> 92 + </QSize> 93 + <key>fixedTextureSize</key> 94 + <QSize> 95 + <key>width</key> 96 + <int>-1</int> 97 + <key>height</key> 98 + <int>-1</int> 99 + </QSize> 100 + <key>reduceBorderArtifacts</key> 101 + <false/> 102 + <key>algorithmSettings</key> 103 + <struct type="AlgorithmSettings"> 104 + <key>algorithm</key> 105 + <enum type="AlgorithmSettings::AlgorithmId">Basic</enum> 106 + <key>freeSizeMode</key> 107 + <enum type="AlgorithmSettings::AlgorithmFreeSizeMode">Best</enum> 108 + <key>sizeConstraints</key> 109 + <enum type="AlgorithmSettings::SizeConstraints">AnySize</enum> 110 + <key>forceSquared</key> 111 + <false/> 112 + <key>forceWordAligned</key> 113 + <false/> 114 + <key>maxRects</key> 115 + <struct type="AlgorithmMaxRectsSettings"> 116 + <key>heuristic</key> 117 + <enum type="AlgorithmMaxRectsSettings::Heuristic">Best</enum> 118 + </struct> 119 + <key>basic</key> 120 + <struct type="AlgorithmBasicSettings"> 121 + <key>sortBy</key> 122 + <enum type="AlgorithmBasicSettings::SortBy">Best</enum> 123 + <key>order</key> 124 + <enum type="AlgorithmBasicSettings::Order">Ascending</enum> 125 + </struct> 126 + </struct> 127 + <key>andEngine</key> 128 + <struct type="AndEngine"> 129 + <key>minFilter</key> 130 + <enum type="AndEngine::MinFilter">Linear</enum> 131 + <key>packageName</key> 132 + <string>Texture</string> 133 + <key>wrap</key> 134 + <struct type="AndEngineWrap"> 135 + <key>s</key> 136 + <enum type="AndEngineWrap::Wrap">Clamp</enum> 137 + <key>t</key> 138 + <enum type="AndEngineWrap::Wrap">Clamp</enum> 139 + </struct> 140 + <key>magFilter</key> 141 + <enum type="AndEngine::MagFilter">MagLinear</enum> 142 + </struct> 143 + <key>dataFileNames</key> 144 + <map type="GFileNameMap"> 145 + <key>data</key> 146 + <struct type="DataFile"> 147 + <key>name</key> 148 + <filename>atlas.json</filename> 149 + </struct> 150 + </map> 151 + <key>multiPack</key> 152 + <false/> 153 + <key>forceIdenticalLayout</key> 154 + <false/> 155 + <key>outputFormat</key> 156 + <enum type="SettingsBase::OutputFormat">RGBA8888</enum> 157 + <key>contentProtection</key> 158 + <struct type="ContentProtection"> 159 + <key>key</key> 160 + <string></string> 161 + </struct> 162 + <key>autoAliasEnabled</key> 163 + <false/> 164 + <key>trimSpriteNames</key> 165 + <true/> 166 + <key>prependSmartFolderName</key> 167 + <false/> 168 + <key>cleanTransparentPixels</key> 169 + <true/> 170 + <key>globalSpriteSettings</key> 171 + <struct type="SpriteSettings"> 172 + <key>scale</key> 173 + <double>1</double> 174 + <key>scaleMode</key> 175 + <enum type="ScaleMode">Smooth</enum> 176 + <key>innerPadding</key> 177 + <uint>0</uint> 178 + <key>extrude</key> 179 + <uint>0</uint> 180 + <key>trimThreshold</key> 181 + <uint>1</uint> 182 + <key>trimMode</key> 183 + <enum type="SpriteSettings::TrimMode">Trim</enum> 184 + <key>heuristicMask</key> 185 + <false/> 186 + <key>pivotPoint</key> 187 + <enum type="SpriteSettings::PivotPoint">Center</enum> 188 + </struct> 189 + <key>fileList</key> 190 + <array> 191 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/back-jump</filename> 192 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/climb</filename> 193 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/crouch</filename> 194 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/hurt</filename> 195 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/idle</filename> 196 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/jump</filename> 197 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/run</filename> 198 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/shoot</filename> 199 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/walk</filename> 200 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/vehicles</filename> 201 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/antenna.png</filename> 202 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/control-box-1.png</filename> 203 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/control-box-2.png</filename> 204 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/control-box-3.png</filename> 205 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/banner-big</filename> 206 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/banner-coke</filename> 207 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/banner-neon</filename> 208 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/banner-scroll</filename> 209 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/banner-side</filename> 210 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/banner-sushi</filename> 211 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/ENVIRONMENT/props/monitorface</filename> 212 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/misc/drone</filename> 213 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/misc/enemy-explosion</filename> 214 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/misc/shot</filename> 215 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/misc/shot-hit</filename> 216 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/misc/turret</filename> 217 + <filename>../../../../../../../ansimuz resources/ART PACKS/2019/warped city/warped city files/SPRITES/player/run-shoot</filename> 218 + </array> 219 + <key>ignoreFileList</key> 220 + <array/> 221 + <key>replaceList</key> 222 + <array/> 223 + <key>ignoredWarnings</key> 224 + <array/> 225 + <key>commonDivisorX</key> 226 + <uint>1</uint> 227 + <key>commonDivisorY</key> 228 + <uint>1</uint> 229 + </struct> 230 +</data>
Added src/worpt/assets/warped_city/environment/bg-1.png.
cannot compute difference between binary files
Added src/worpt/assets/warped_city/environment/bg-2.png.
cannot compute difference between binary files
Added src/worpt/assets/warped_city/environment/bg-3.png.
cannot compute difference between binary files
Added src/worpt/assets/warped_city/environment/tileset.png.
cannot compute difference between binary files
Added src/worpt/assets/warped_city/maps/map.json.
1 +{ "backgroundcolor":"#0000ff", 2 + "height":28, 3 + "infinite":false, 4 + "layers":[ 5 + { 6 + "data":[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], 7 + "height":28, 8 + "name":"Collisions Layer", 9 + "opacity":1, 10 + "type":"tilelayer", 11 + "visible":true, 12 + "width":120, 13 + "x":0, 14 + "y":0 15 + }, 16 + { 17 + "data":[0, 26, 76, 77, 78, 29, 29, 76, 77, 78, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 39, 40, 41, 37, 159, 160, 161, 37, 279, 280, 281, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 76, 77, 78, 245, 29, 76, 77, 78, 80, 0, 50, 100, 101, 102, 29, 29, 100, 101, 102, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 303, 304, 305, 61, 63, 64, 65, 67, 68, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 100, 101, 102, 269, 29, 100, 101, 102, 56, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 207, 208, 209, 85, 327, 328, 329, 61, 87, 88, 89, 91, 92, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 0, 26, 28, 29, 30, 245, 163, 28, 29, 30, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 111, 112, 113, 109, 231, 232, 233, 85, 351, 352, 353, 61, 111, 112, 113, 115, 116, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 225, 224, 224, 224, 0, 0, 0, 224, 224, 224, 225, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 28, 29, 30, 228, 229, 28, 29, 30, 32, 0, 50, 52, 53, 54, 269, 187, 52, 53, 54, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 52, 53, 54, 252, 253, 52, 53, 54, 32, 0, 74, 76, 77, 78, 245, 29, 76, 77, 78, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 39, 40, 41, 37, 159, 160, 161, 37, 39, 40, 41, 37, 279, 280, 281, 43, 44, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 29, 29, 29, 29, 165, 147, 29, 228, 229, 43, 44, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 76, 77, 78, 167, 29, 76, 77, 78, 56, 0, 26, 100, 101, 102, 269, 29, 100, 101, 102, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 63, 64, 65, 61, 303, 304, 305, 67, 68, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 189, 189, 29, 29, 29, 147, 29, 252, 253, 67, 68, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 100, 101, 102, 191, 29, 100, 101, 102, 80, 0, 50, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 87, 88, 89, 109, 207, 208, 209, 85, 87, 88, 89, 61, 327, 328, 329, 91, 92, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 165, 189, 29, 29, 29, 147, 29, 29, 29, 91, 92, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 273, 272, 272, 272, 272, 272, 272, 272, 272, 272, 0, 74, 28, 29, 30, 228, 229, 28, 29, 30, 32, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 111, 112, 113, 85, 231, 232, 233, 85, 111, 112, 113, 61, 351, 352, 353, 115, 116, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 29, 29, 29, 29, 29, 147, 29, 29, 29, 115, 116, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 50, 28, 29, 30, 167, 245, 28, 29, 30, 56, 0, 26, 52, 53, 54, 252, 253, 52, 53, 54, 32, 0, 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 147, 272, 272, 272, 272, 272, 274, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 155, 156, 157, 29, 29, 147, 29, 189, 165, 67, 68, 0, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 50, 52, 53, 54, 191, 245, 52, 53, 54, 56, 0, 50, 76, 77, 78, 29, 29, 76, 77, 78, 56, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 279, 280, 281, 37, 159, 160, 161, 37, 39, 40, 41, 147, 159, 160, 161, 43, 44, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 179, 180, 181, 29, 29, 147, 29, 189, 189, 91, 92, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 26, 76, 77, 78, 167, 245, 76, 77, 78, 32, 0, 74, 100, 101, 102, 29, 29, 100, 101, 102, 80, 0, 0, 0, 224, 224, 225, 224, 224, 224, 224, 224, 224, 224, 0, 0, 0, 0, 58, 59, 303, 304, 305, 61, 183, 184, 185, 61, 63, 64, 65, 147, 183, 184, 185, 67, 68, 0, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 0, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 147, 272, 272, 272, 272, 272, 274, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 50, 100, 101, 102, 191, 269, 100, 101, 102, 56, 271, 273, 272, 272, 272, 147, 272, 272, 272, 272, 272, 274, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 82, 83, 327, 328, 329, 85, 207, 208, 209, 85, 87, 88, 89, 147, 207, 208, 209, 91, 92, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 0, 0, 34, 35, 228, 229, 29, 37, 29, 228, 229, 37, 228, 229, 29, 147, 167, 29, 29, 43, 44, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 0, 50, 28, 29, 30, 147, 245, 28, 29, 30, 56, 0, 0, 0, 26, 228, 229, 245, 149, 29, 163, 228, 229, 32, 0, 0, 0, 0, 106, 107, 351, 352, 353, 85, 231, 232, 233, 85, 111, 112, 113, 147, 231, 232, 233, 115, 116, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 0, 0, 58, 59, 252, 253, 29, 61, 29, 252, 253, 61, 252, 253, 29, 147, 191, 29, 29, 67, 68, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 26, 28, 29, 30, 245, 163, 28, 29, 30, 32, 0, 50, 52, 53, 54, 147, 245, 52, 53, 54, 56, 0, 0, 0, 50, 252, 253, 245, 173, 29, 187, 252, 253, 56, 0, 0, 0, 271, 272, 272, 272, 272, 272, 61, 46, 46, 46, 46, 46, 46, 46, 147, 272, 272, 272, 272, 272, 274, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 0, 0, 82, 83, 165, 165, 29, 109, 29, 189, 189, 61, 189, 189, 29, 147, 167, 29, 189, 91, 92, 0, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 50, 52, 53, 54, 269, 187, 52, 53, 54, 56, 0, 26, 76, 77, 78, 147, 245, 76, 77, 78, 32, 0, 0, 0, 74, 29, 29, 245, 173, 29, 29, 29, 167, 80, 0, 0, 0, 0, 34, 35, 39, 40, 41, 61, 228, 229, 29, 29, 29, 228, 229, 147, 39, 40, 41, 43, 44, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 0, 0, 106, 107, 189, 189, 29, 61, 29, 189, 165, 61, 189, 189, 29, 147, 191, 29, 29, 115, 116, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 74, 76, 77, 78, 245, 29, 76, 77, 78, 80, 0, 50, 100, 101, 102, 147, 269, 100, 101, 102, 56, 0, 0, 0, 26, 163, 29, 245, 173, 29, 29, 29, 191, 32, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 252, 253, 29, 29, 29, 252, 253, 147, 63, 64, 65, 67, 68, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 0, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 147, 46, 46, 46, 46, 46, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 26, 100, 101, 102, 269, 29, 100, 101, 102, 56, 0, 46, 46, 46, 46, 147, 46, 46, 46, 46, 46, 0, 0, 0, 50, 187, 29, 245, 173, 155, 156, 157, 29, 56, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 165, 29, 155, 156, 157, 29, 29, 147, 87, 88, 89, 91, 92, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 0, 0, 34, 35, 279, 280, 281, 37, 159, 160, 161, 37, 39, 40, 41, 147, 159, 160, 161, 43, 44, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 0, 26, 28, 29, 30, 147, 29, 28, 29, 30, 32, 0, 0, 0, 74, 29, 189, 189, 173, 179, 180, 181, 29, 80, 0, 0, 0, 0, 106, 107, 111, 112, 113, 109, 189, 29, 179, 180, 181, 29, 29, 171, 111, 112, 113, 115, 116, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 0, 0, 58, 59, 303, 304, 305, 61, 183, 184, 185, 61, 63, 64, 65, 147, 183, 184, 185, 67, 68, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 74, 28, 29, 30, 228, 229, 28, 29, 30, 32, 0, 50, 52, 53, 54, 147, 29, 52, 53, 54, 56, 0, 0, 0, 26, 29, 163, 163, 173, 228, 229, 167, 29, 32, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 0, 0, 82, 83, 327, 328, 329, 85, 207, 208, 209, 85, 87, 88, 89, 147, 207, 208, 209, 91, 92, 0, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 26, 52, 53, 54, 252, 253, 52, 53, 54, 32, 0, 74, 76, 77, 78, 147, 163, 76, 77, 78, 80, 0, 0, 0, 50, 29, 187, 187, 197, 252, 253, 191, 29, 56, 0, 0, 0, 0, 34, 35, 159, 160, 161, 37, 159, 160, 161, 37, 279, 280, 281, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 0, 0, 106, 107, 351, 352, 353, 85, 231, 232, 233, 85, 111, 112, 113, 171, 231, 232, 233, 115, 116, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 50, 76, 77, 78, 29, 167, 76, 77, 78, 56, 0, 26, 100, 101, 102, 171, 187, 100, 101, 102, 56, 0, 0, 0, 74, 29, 245, 245, 29, 173, 29, 167, 29, 80, 0, 0, 0, 0, 58, 59, 183, 184, 185, 61, 183, 184, 185, 61, 303, 304, 305, 109, 63, 64, 65, 67, 68, 0, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 74, 100, 101, 102, 29, 191, 100, 101, 102, 80, 0, 50, 272, 272, 272, 272, 272, 272, 272, 147, 272, 274, 0, 0, 26, 29, 245, 245, 29, 173, 29, 191, 29, 32, 0, 0, 0, 0, 82, 83, 207, 208, 209, 85, 207, 208, 209, 85, 327, 328, 329, 61, 87, 88, 89, 91, 92, 0, 0, 0, 0, 26, 29, 167, 29, 56, 0, 0, 0, 0, 0, 34, 35, 39, 40, 41, 61, 159, 160, 161, 61, 159, 160, 161, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 26, 29, 167, 29, 56, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 273, 272, 272, 272, 272, 272, 272, 272, 272, 272, 0, 74, 28, 29, 30, 228, 229, 28, 29, 147, 32, 0, 0, 0, 50, 29, 245, 245, 29, 173, 189, 189, 189, 56, 0, 0, 0, 0, 106, 107, 231, 232, 233, 85, 231, 232, 233, 85, 351, 352, 353, 61, 111, 112, 113, 115, 116, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 183, 184, 185, 61, 63, 64, 65, 67, 68, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 50, 28, 29, 30, 29, 245, 28, 29, 30, 56, 0, 26, 52, 53, 54, 252, 253, 52, 53, 147, 32, 0, 0, 0, 74, 29, 245, 245, 29, 173, 189, 189, 189, 80, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 74, 29, 167, 29, 56, 0, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 207, 208, 209, 61, 207, 208, 209, 109, 87, 88, 89, 91, 92, 0, 0, 0, 0, 74, 29, 167, 29, 56, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 50, 52, 53, 54, 29, 245, 52, 53, 54, 56, 0, 50, 76, 77, 78, 29, 29, 76, 77, 147, 56, 0, 0, 0, 26, 29, 197, 245, 29, 173, 29, 29, 29, 32, 0, 0, 0, 0, 34, 35, 39, 40, 41, 37, 159, 160, 161, 37, 39, 40, 41, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 0, 106, 107, 111, 112, 113, 109, 231, 232, 233, 61, 231, 232, 233, 61, 111, 112, 113, 115, 116, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 26, 76, 77, 78, 29, 245, 76, 77, 78, 32, 0, 74, 100, 101, 102, 29, 29, 100, 101, 171, 80, 0, 0, 0, 50, 155, 156, 157, 29, 173, 29, 228, 229, 56, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 63, 64, 65, 61, 63, 64, 65, 67, 68, 0, 0, 0, 0, 50, 155, 156, 157, 80, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 50, 155, 156, 157, 80, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 50, 100, 101, 102, 29, 269, 100, 101, 102, 56, 271, 273, 273, 273, 273, 273, 272, 272, 272, 272, 272, 274, 0, 0, 74, 179, 180, 181, 29, 173, 29, 252, 253, 80, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 207, 208, 209, 85, 87, 88, 89, 61, 87, 88, 89, 91, 92, 0, 0, 0, 0, 50, 179, 180, 181, 56, 0, 0, 0, 0, 0, 34, 35, 159, 160, 161, 37, 159, 160, 161, 37, 279, 280, 281, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 50, 179, 180, 181, 56, 0, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272], 18 + "height":28, 19 + "name":"Main Layer", 20 + "opacity":1, 21 + "type":"tilelayer", 22 + "visible":true, 23 + "width":120, 24 + "x":0, 25 + "y":0 26 + }, 27 + { 28 + "draworder":"topdown", 29 + "name":"Object Layer", 30 + "objects":[ 31 + { 32 + "gid":5, 33 + "height":16, 34 + "id":12, 35 + "name":"", 36 + "rotation":0, 37 + "type":"", 38 + "visible":true, 39 + "width":16, 40 + "x":294.666666666667, 41 + "y":382.333333333333 42 + }, 43 + { 44 + "gid":6, 45 + "height":16, 46 + "id":13, 47 + "name":"", 48 + "rotation":0, 49 + "type":"", 50 + "visible":true, 51 + "width":16, 52 + "x":240.333333333333, 53 + "y":300 54 + }, 55 + { 56 + "gid":7, 57 + "height":16, 58 + "id":14, 59 + "name":"", 60 + "rotation":0, 61 + "type":"", 62 + "visible":true, 63 + "width":16, 64 + "x":19.6666666666666, 65 + "y":142.666666666667 66 + }, 67 + { 68 + "gid":8, 69 + "height":16, 70 + "id":17, 71 + "name":"", 72 + "rotation":0, 73 + "type":"", 74 + "visible":true, 75 + "width":16, 76 + "x":166, 77 + "y":415.666666666667 78 + }, 79 + { 80 + "gid":9, 81 + "height":16, 82 + "id":19, 83 + "name":"", 84 + "rotation":0, 85 + "type":"", 86 + "visible":true, 87 + "width":16, 88 + "x":161, 89 + "y":247.333333333333 90 + }, 91 + { 92 + "gid":11, 93 + "height":16, 94 + "id":21, 95 + "name":"", 96 + "rotation":0, 97 + "type":"", 98 + "visible":true, 99 + "width":16, 100 + "x":32.6666666666667, 101 + "y":417.666666666667 102 + }, 103 + { 104 + "gid":11, 105 + "height":16, 106 + "id":22, 107 + "name":"", 108 + "rotation":0, 109 + "type":"", 110 + "visible":true, 111 + "width":16, 112 + "x":30.3333333333333, 113 + "y":285 114 + }, 115 + { 116 + "gid":10, 117 + "height":16, 118 + "id":23, 119 + "name":"", 120 + "rotation":0, 121 + "type":"", 122 + "visible":true, 123 + "width":16, 124 + "x":248.333333333333, 125 + "y":425 126 + }, 127 + { 128 + "gid":6, 129 + "height":16, 130 + "id":29, 131 + "name":"", 132 + "rotation":0, 133 + "type":"", 134 + "visible":true, 135 + "width":16, 136 + "x":737.333333333333, 137 + "y":299.333333333333 138 + }, 139 + { 140 + "gid":5, 141 + "height":16, 142 + "id":30, 143 + "name":"", 144 + "rotation":0, 145 + "type":"", 146 + "visible":true, 147 + "width":16, 148 + "x":442, 149 + "y":44.6666666666667 150 + }, 151 + { 152 + "gid":9, 153 + "height":16, 154 + "id":31, 155 + "name":"", 156 + "rotation":0, 157 + "type":"", 158 + "visible":true, 159 + "width":16, 160 + "x":811.333333333333, 161 + "y":203.333333333333 162 + }, 163 + { 164 + "gid":10, 165 + "height":16, 166 + "id":32, 167 + "name":"", 168 + "rotation":0, 169 + "type":"", 170 + "visible":true, 171 + "width":16, 172 + "x":730, 173 + "y":174 174 + }, 175 + { 176 + "gid":5, 177 + "height":16, 178 + "id":33, 179 + "name":"", 180 + "rotation":0, 181 + "type":"", 182 + "visible":true, 183 + "width":16, 184 + "x":1264, 185 + "y":313.333333333333 186 + }, 187 + { 188 + "gid":5, 189 + "height":16, 190 + "id":34, 191 + "name":"", 192 + "rotation":0, 193 + "type":"", 194 + "visible":true, 195 + "width":16, 196 + "x":1480.66666666667, 197 + "y":291.333333333333 198 + }, 199 + { 200 + "gid":6, 201 + "height":16, 202 + "id":35, 203 + "name":"", 204 + "rotation":0, 205 + "type":"", 206 + "visible":true, 207 + "width":16, 208 + "x":1062.66666666667, 209 + "y":90.6666666666667 210 + }, 211 + { 212 + "gid":10, 213 + "height":16, 214 + "id":36, 215 + "name":"", 216 + "rotation":0, 217 + "type":"", 218 + "visible":true, 219 + "width":16, 220 + "x":1646, 221 + "y":268 222 + }, 223 + { 224 + "gid":11, 225 + "height":16, 226 + "id":37, 227 + "name":"", 228 + "rotation":0, 229 + "type":"", 230 + "visible":true, 231 + "width":16, 232 + "x":1039.33333333333, 233 + "y":264 234 + }, 235 + { 236 + "gid":11, 237 + "height":16, 238 + "id":38, 239 + "name":"", 240 + "rotation":0, 241 + "type":"", 242 + "visible":true, 243 + "width":16, 244 + "x":1370, 245 + "y":222.666666666667 246 + }, 247 + { 248 + "gid":11, 249 + "height":16, 250 + "id":39, 251 + "name":"", 252 + "rotation":0, 253 + "type":"", 254 + "visible":true, 255 + "width":16, 256 + "x":1129.33333333333, 257 + "y":264 258 + }, 259 + { 260 + "gid":9, 261 + "height":16, 262 + "id":40, 263 + "name":"", 264 + "rotation":0, 265 + "type":"", 266 + "visible":true, 267 + "width":16, 268 + "x":741.333333333333, 269 + "y":390.666666666667 270 + }, 271 + { 272 + "gid":9, 273 + "height":16, 274 + "id":41, 275 + "name":"", 276 + "rotation":0, 277 + "type":"", 278 + "visible":true, 279 + "width":16, 280 + "x":323.333333333333, 281 + "y":298.666666666667 282 + }, 283 + { 284 + "gid":11, 285 + "height":16, 286 + "id":42, 287 + "name":"", 288 + "rotation":0, 289 + "type":"", 290 + "visible":true, 291 + "width":16, 292 + "x":1798.66666666667, 293 + "y":318 294 + }, 295 + { 296 + "gid":11, 297 + "height":16, 298 + "id":43, 299 + "name":"", 300 + "rotation":0, 301 + "type":"", 302 + "visible":true, 303 + "width":16, 304 + "x":1869.33333333333, 305 + "y":104.666666666667 306 + }, 307 + { 308 + "gid":9, 309 + "height":16, 310 + "id":44, 311 + "name":"", 312 + "rotation":0, 313 + "type":"", 314 + "visible":true, 315 + "width":16, 316 + "x":1908, 317 + "y":186.666666666667 318 + }], 319 + "opacity":1, 320 + "type":"objectgroup", 321 + "visible":true, 322 + "x":0, 323 + "y":0 324 + }], 325 + "nextobjectid":45, 326 + "orientation":"orthogonal", 327 + "renderorder":"right-down", 328 + "tiledversion":"1.1.4", 329 + "tileheight":16, 330 + "tilesets":[ 331 + { 332 + "columns":24, 333 + "firstgid":1, 334 + "image":"..\/environment\/tileset.png", 335 + "imageheight":256, 336 + "imagewidth":384, 337 + "margin":0, 338 + "name":"tileset", 339 + "spacing":0, 340 + "tilecount":384, 341 + "tileheight":16, 342 + "tilewidth":16 343 + }], 344 + "tilewidth":16, 345 + "type":"map", 346 + "version":1, 347 + "width":120 348 +}
Added src/worpt/assets/warped_city/public-license.txt.
1 +Artwork created by Luis Zuno @ansimuz 2 + 3 +License for Everyone. 4 + 5 +Public domain and free to use on whatever you want, personal or commercial. Credit is not required but appreciated. 6 + 7 +Feeling Grateful? 8 +You can support my work at https://www.patreon.com/ansimuz 9 + 10 +Get more Free Assetslike these at: http://www.pixelgameart.org 11 + 12 +Additional credits 13 +Demo Music by Pascal Belisle 14 + 15 +pacethemusician@hotmail.com 16 +https://soundcloud.com/pascalbelisle 17 +
Added src/worpt/conf.lua.
1 +love.conf = function(t) 2 + t.gammacorrect = true 3 + t.title, t.identity = "worpt", "worpt" 4 + t.modules.joystick = false 5 + t.modules.physics = false 6 + t.window.width = 768 7 + t.window.height = 448 8 + t.window.vsync = false 9 + t.version = "11.3" 10 +end
Added src/worpt/lib/anim8.lua.
1 +local anim8 = { 2 + _VERSION = 'anim8 v2.3.1', 3 + _DESCRIPTION = 'An animation library for LÖVE', 4 + _URL = 'https://github.com/kikito/anim8', 5 + _LICENSE = [[ 6 + MIT LICENSE 7 + 8 + Copyright (c) 2011 Enrique García Cota 9 + 10 + Permission is hereby granted, free of charge, to any person obtaining a 11 + copy of this software and associated documentation files (the 12 + "Software"), to deal in the Software without restriction, including 13 + without limitation the rights to use, copy, modify, merge, publish, 14 + distribute, sublicense, and/or sell copies of the Software, and to 15 + permit persons to whom the Software is furnished to do so, subject to 16 + the following conditions: 17 + 18 + The above copyright notice and this permission notice shall be included 19 + in all copies or substantial portions of the Software. 20 + 21 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 + ]] 29 +} 30 + 31 +local Grid = {} 32 + 33 +local _frames = {} 34 + 35 +local function assertPositiveInteger(value, name) 36 + if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end 37 + if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end 38 + if value ~= math.floor(value) then error(("%s should be an integer, was %d"):format(name, value)) end 39 +end 40 + 41 +local function createFrame(self, x, y) 42 + local fw, fh = self.frameWidth, self.frameHeight 43 + return love.graphics.newQuad( 44 + self.left + (x-1) * fw + x * self.border, 45 + self.top + (y-1) * fh + y * self.border, 46 + fw, 47 + fh, 48 + self.imageWidth, 49 + self.imageHeight 50 + ) 51 +end 52 + 53 +local function getGridKey(...) 54 + return table.concat( {...} ,'-' ) 55 +end 56 + 57 +local function getOrCreateFrame(self, x, y) 58 + if x < 1 or x > self.width or y < 1 or y > self.height then 59 + error(("There is no frame for x=%d, y=%d"):format(x, y)) 60 + end 61 + local key = self._key 62 + _frames[key] = _frames[key] or {} 63 + _frames[key][x] = _frames[key][x] or {} 64 + _frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y) 65 + return _frames[key][x][y] 66 +end 67 + 68 +local function parseInterval(str) 69 + if type(str) == "number" then return str,str,1 end 70 + str = str:gsub('%s', '') -- remove spaces 71 + local min, max = str:match("^(%d+)-(%d+)$") 72 + assert(min and max, ("Could not parse interval from %q"):format(str)) 73 + min, max = tonumber(min), tonumber(max) 74 + local step = min <= max and 1 or -1 75 + return min, max, step 76 +end 77 + 78 +function Grid:getFrames(...) 79 + local result, args = {}, {...} 80 + local minx, maxx, stepx, miny, maxy, stepy 81 + 82 + for i=1, #args, 2 do 83 + minx, maxx, stepx = parseInterval(args[i]) 84 + miny, maxy, stepy = parseInterval(args[i+1]) 85 + for y = miny, maxy, stepy do 86 + for x = minx, maxx, stepx do 87 + result[#result+1] = getOrCreateFrame(self,x,y) 88 + end 89 + end 90 + end 91 + 92 + return result 93 +end 94 + 95 +local Gridmt = { 96 + __index = Grid, 97 + __call = Grid.getFrames 98 +} 99 + 100 +local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) 101 + assertPositiveInteger(frameWidth, "frameWidth") 102 + assertPositiveInteger(frameHeight, "frameHeight") 103 + assertPositiveInteger(imageWidth, "imageWidth") 104 + assertPositiveInteger(imageHeight, "imageHeight") 105 + 106 + left = left or 0 107 + top = top or 0 108 + border = border or 0 109 + 110 + local key = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border) 111 + 112 + local grid = setmetatable( 113 + { frameWidth = frameWidth, 114 + frameHeight = frameHeight, 115 + imageWidth = imageWidth, 116 + imageHeight = imageHeight, 117 + left = left, 118 + top = top, 119 + border = border, 120 + width = math.floor(imageWidth/frameWidth), 121 + height = math.floor(imageHeight/frameHeight), 122 + _key = key 123 + }, 124 + Gridmt 125 + ) 126 + return grid 127 +end 128 + 129 +----------------------------------------------------------- 130 + 131 +local Animation = {} 132 + 133 +local function cloneArray(arr) 134 + local result = {} 135 + for i=1,#arr do result[i] = arr[i] end 136 + return result 137 +end 138 + 139 +local function parseDurations(durations, frameCount) 140 + local result = {} 141 + if type(durations) == 'number' then 142 + for i=1,frameCount do result[i] = durations end 143 + else 144 + local min, max, step 145 + for key,duration in pairs(durations) do 146 + assert(type(duration) == 'number', "The value [" .. tostring(duration) .. "] should be a number") 147 + min, max, step = parseInterval(key) 148 + for i = min,max,step do result[i] = duration end 149 + end 150 + end 151 + 152 + if #result < frameCount then 153 + error("The durations table has length of " .. tostring(#result) .. ", but it should be >= " .. tostring(frameCount)) 154 + end 155 + 156 + return result 157 +end 158 + 159 +local function parseIntervals(durations) 160 + local result, time = {0},0 161 + for i=1,#durations do 162 + time = time + durations[i] 163 + result[i+1] = time 164 + end 165 + return result, time 166 +end 167 + 168 +local Animationmt = { __index = Animation } 169 +local nop = function() end 170 + 171 +local function newAnimation(frames, durations, onLoop) 172 + local td = type(durations); 173 + if (td ~= 'number' or durations <= 0) and td ~= 'table' then 174 + error("durations must be a positive number. Was " .. tostring(durations) ) 175 + end 176 + onLoop = onLoop or nop 177 + durations = parseDurations(durations, #frames) 178 + local intervals, totalDuration = parseIntervals(durations) 179 + return setmetatable({ 180 + frames = cloneArray(frames), 181 + durations = durations, 182 + intervals = intervals, 183 + totalDuration = totalDuration, 184 + onLoop = onLoop, 185 + timer = 0, 186 + position = 1, 187 + status = "playing", 188 + flippedH = false, 189 + flippedV = false 190 + }, 191 + Animationmt 192 + ) 193 +end 194 + 195 +function Animation:clone() 196 + local newAnim = newAnimation(self.frames, self.durations, self.onLoop) 197 + newAnim.flippedH, newAnim.flippedV = self.flippedH, self.flippedV 198 + return newAnim 199 +end 200 + 201 +function Animation:flipH() 202 + self.flippedH = not self.flippedH 203 + return self 204 +end 205 + 206 +function Animation:flipV() 207 + self.flippedV = not self.flippedV 208 + return self 209 +end 210 + 211 +local function seekFrameIndex(intervals, timer) 212 + local high, low, i = #intervals-1, 1, 1 213 + 214 + while(low <= high) do 215 + i = math.floor((low + high) / 2) 216 + if timer >= intervals[i+1] then low = i + 1 217 + elseif timer < intervals[i] then high = i - 1 218 + else 219 + return i 220 + end 221 + end 222 + 223 + return i 224 +end 225 + 226 +function Animation:update(dt) 227 + if self.status ~= "playing" then return end 228 + 229 + self.timer = self.timer + dt 230 + local loops = math.floor(self.timer / self.totalDuration) 231 + if loops ~= 0 then 232 + self.timer = self.timer - self.totalDuration * loops 233 + local f = type(self.onLoop) == 'function' and self.onLoop or self[self.onLoop] 234 + f(self, loops) 235 + end 236 + 237 + self.position = seekFrameIndex(self.intervals, self.timer) 238 +end 239 + 240 +function Animation:pause() 241 + self.status = "paused" 242 +end 243 + 244 +function Animation:gotoFrame(position) 245 + self.position = position 246 + self.timer = self.intervals[self.position] 247 +end 248 + 249 +function Animation:pauseAtEnd() 250 + self.position = #self.frames 251 + self.timer = self.totalDuration 252 + self:pause() 253 +end 254 + 255 +function Animation:pauseAtStart() 256 + self.position = 1 257 + self.timer = 0 258 + self:pause() 259 +end 260 + 261 +function Animation:resume() 262 + self.status = "playing" 263 +end 264 + 265 +function Animation:draw(image, x, y, r, sx, sy, ox, oy, kx, ky) 266 + love.graphics.draw(image, self:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky)) 267 +end 268 + 269 +function Animation:getFrameInfo(x, y, r, sx, sy, ox, oy, kx, ky) 270 + local frame = self.frames[self.position] 271 + if self.flippedH or self.flippedV then 272 + r,sx,sy,ox,oy,kx,ky = r or 0, sx or 1, sy or 1, ox or 0, oy or 0, kx or 0, ky or 0 273 + local _,_,w,h = frame:getViewport() 274 + 275 + if self.flippedH then 276 + sx = sx * -1 277 + ox = w - ox 278 + kx = kx * -1 279 + ky = ky * -1 280 + end 281 + 282 + if self.flippedV then 283 + sy = sy * -1 284 + oy = h - oy 285 + kx = kx * -1 286 + ky = ky * -1 287 + end 288 + end 289 + return frame, x, y, r, sx, sy, ox, oy, kx, ky 290 +end 291 + 292 +function Animation:getDimensions() 293 + local _,_,w,h = self.frames[self.position]:getViewport() 294 + return w,h 295 +end 296 + 297 +----------------------------------------------------------- 298 + 299 +anim8.newGrid = newGrid 300 +anim8.newAnimation = newAnimation 301 + 302 +return anim8
Added src/worpt/lib/bump.lua.
1 +local bump = { 2 + _VERSION = 'bump v3.1.7', 3 + _URL = 'https://github.com/kikito/bump.lua', 4 + _DESCRIPTION = 'A collision detection library for Lua', 5 + _LICENSE = [[ 6 + MIT LICENSE 7 + 8 + Copyright (c) 2014 Enrique García Cota 9 + 10 + Permission is hereby granted, free of charge, to any person obtaining a 11 + copy of this software and associated documentation files (the 12 + "Software"), to deal in the Software without restriction, including 13 + without limitation the rights to use, copy, modify, merge, publish, 14 + distribute, sublicense, and/or sell copies of the Software, and to 15 + permit persons to whom the Software is furnished to do so, subject to 16 + the following conditions: 17 + 18 + The above copyright notice and this permission notice shall be included 19 + in all copies or substantial portions of the Software. 20 + 21 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 25 + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 26 + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 27 + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 + ]] 29 +} 30 + 31 +------------------------------------------ 32 +-- Auxiliary functions 33 +------------------------------------------ 34 +local DELTA = 1e-10 -- floating-point margin of error 35 + 36 +local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max 37 + 38 +local function sign(x) 39 + if x > 0 then return 1 end 40 + if x == 0 then return 0 end 41 + return -1 42 +end 43 + 44 +local function nearest(x, a, b) 45 + if abs(a - x) < abs(b - x) then return a else return b end 46 +end 47 + 48 +local function assertType(desiredType, value, name) 49 + if type(value) ~= desiredType then 50 + error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')') 51 + end 52 +end 53 + 54 +local function assertIsPositiveNumber(value, name) 55 + if type(value) ~= 'number' or value <= 0 then 56 + error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')') 57 + end 58 +end 59 + 60 +local function assertIsRect(x,y,w,h) 61 + assertType('number', x, 'x') 62 + assertType('number', y, 'y') 63 + assertIsPositiveNumber(w, 'w') 64 + assertIsPositiveNumber(h, 'h') 65 +end 66 + 67 +local defaultFilter = function() 68 + return 'slide' 69 +end 70 + 71 +------------------------------------------ 72 +-- Rectangle functions 73 +------------------------------------------ 74 + 75 +local function rect_getNearestCorner(x,y,w,h, px, py) 76 + return nearest(px, x, x+w), nearest(py, y, y+h) 77 +end 78 + 79 +-- This is a generalized implementation of the liang-barsky algorithm, which also returns 80 +-- the normals of the sides where the segment intersects. 81 +-- Returns nil if the segment never touches the rect 82 +-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge 83 +local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2) 84 + ti1, ti2 = ti1 or 0, ti2 or 1 85 + local dx, dy = x2-x1, y2-y1 86 + local nx, ny 87 + local nx1, ny1, nx2, ny2 = 0,0,0,0 88 + local p, q, r 89 + 90 + for side = 1,4 do 91 + if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left 92 + elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right 93 + elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top 94 + else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom 95 + end 96 + 97 + if p == 0 then 98 + if q <= 0 then return nil end 99 + else 100 + r = q / p 101 + if p < 0 then 102 + if r > ti2 then return nil 103 + elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny 104 + end 105 + else -- p > 0 106 + if r < ti1 then return nil 107 + elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny 108 + end 109 + end 110 + end 111 + end 112 + 113 + return ti1,ti2, nx1,ny1, nx2,ny2 114 +end 115 + 116 +-- Calculates the minkowsky difference between 2 rects, which is another rect 117 +local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2) 118 + return x2 - x1 - w1, 119 + y2 - y1 - h1, 120 + w1 + w2, 121 + h1 + h2 122 +end 123 + 124 +local function rect_containsPoint(x,y,w,h, px,py) 125 + return px - x > DELTA and py - y > DELTA and 126 + x + w - px > DELTA and y + h - py > DELTA 127 +end 128 + 129 +local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2) 130 + return x1 < x2+w2 and x2 < x1+w1 and 131 + y1 < y2+h2 and y2 < y1+h1 132 +end 133 + 134 +local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2) 135 + local dx = x1 - x2 + (w1 - w2)/2 136 + local dy = y1 - y2 + (h1 - h2)/2 137 + return dx*dx + dy*dy 138 +end 139 + 140 +local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY) 141 + goalX = goalX or x1 142 + goalY = goalY or y1 143 + 144 + local dx, dy = goalX - x1, goalY - y1 145 + local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2) 146 + 147 + local overlaps, ti, nx, ny 148 + 149 + if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other 150 + local px, py = rect_getNearestCorner(x,y,w,h, 0, 0) 151 + local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection 152 + ti = -wi * hi -- ti is the negative area of intersection 153 + overlaps = true 154 + else 155 + local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge) 156 + 157 + -- item tunnels into other 158 + if ti1 159 + and ti1 < 1 160 + and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner 161 + and (0 < ti1 + DELTA 162 + or 0 == ti1 and ti2 > 0) 163 + then 164 + ti, nx, ny = ti1, nx1, ny1 165 + overlaps = false 166 + end 167 + end 168 + 169 + if not ti then return end 170 + 171 + local tx, ty 172 + 173 + if overlaps then 174 + if dx == 0 and dy == 0 then 175 + -- intersecting and not moving - use minimum displacement vector 176 + local px, py = rect_getNearestCorner(x,y,w,h, 0,0) 177 + if abs(px) < abs(py) then py = 0 else px = 0 end 178 + nx, ny = sign(px), sign(py) 179 + tx, ty = x1 + px, y1 + py 180 + else 181 + -- intersecting and moving - move in the opposite direction 182 + local ti1, _ 183 + ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1) 184 + if not ti1 then return end 185 + tx, ty = x1 + dx * ti1, y1 + dy * ti1 186 + end 187 + else -- tunnel 188 + tx, ty = x1 + dx * ti, y1 + dy * ti 189 + end 190 + 191 + return { 192 + overlaps = overlaps, 193 + ti = ti, 194 + move = {x = dx, y = dy}, 195 + normal = {x = nx, y = ny}, 196 + touch = {x = tx, y = ty}, 197 + itemRect = {x = x1, y = y1, w = w1, h = h1}, 198 + otherRect = {x = x2, y = y2, w = w2, h = h2} 199 + } 200 +end 201 + 202 +------------------------------------------ 203 +-- Grid functions 204 +------------------------------------------ 205 + 206 +local function grid_toWorld(cellSize, cx, cy) 207 + return (cx - 1)*cellSize, (cy-1)*cellSize 208 +end 209 + 210 +local function grid_toCell(cellSize, x, y) 211 + return floor(x / cellSize) + 1, floor(y / cellSize) + 1 212 +end 213 + 214 +-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing", 215 +-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf 216 +-- It has been modified to include both cells when the ray "touches a grid corner", 217 +-- and with a different exit condition 218 + 219 +local function grid_traverse_initStep(cellSize, ct, t1, t2) 220 + local v = t2 - t1 221 + if v > 0 then 222 + return 1, cellSize / v, ((ct + v) * cellSize - t1) / v 223 + elseif v < 0 then 224 + return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v 225 + else 226 + return 0, math.huge, math.huge 227 + end 228 +end 229 + 230 +local function grid_traverse(cellSize, x1,y1,x2,y2, f) 231 + local cx1,cy1 = grid_toCell(cellSize, x1,y1) 232 + local cx2,cy2 = grid_toCell(cellSize, x2,y2) 233 + local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2) 234 + local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2) 235 + local cx,cy = cx1,cy1 236 + 237 + f(cx, cy) 238 + 239 + -- The default implementation had an infinite loop problem when 240 + -- approaching the last cell in some occassions. We finish iterating 241 + -- when we are *next* to the last cell 242 + while abs(cx - cx2) + abs(cy - cy2) > 1 do 243 + if tx < ty then 244 + tx, cx = tx + dx, cx + stepX 245 + f(cx, cy) 246 + else 247 + -- Addition: include both cells when going through corners 248 + if tx == ty then f(cx + stepX, cy) end 249 + ty, cy = ty + dy, cy + stepY 250 + f(cx, cy) 251 + end 252 + end 253 + 254 + -- If we have not arrived to the last cell, use it 255 + if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end 256 + 257 +end 258 + 259 +local function grid_toCellRect(cellSize, x,y,w,h) 260 + local cx,cy = grid_toCell(cellSize, x, y) 261 + local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize) 262 + return cx, cy, cr - cx + 1, cb - cy + 1 263 +end 264 + 265 +------------------------------------------ 266 +-- Responses 267 +------------------------------------------ 268 + 269 +local touch = function(world, col, x,y,w,h, goalX, goalY, filter) 270 + return col.touch.x, col.touch.y, {}, 0 271 +end 272 + 273 +local cross = function(world, col, x,y,w,h, goalX, goalY, filter) 274 + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) 275 + return goalX, goalY, cols, len 276 +end 277 + 278 +local slide = function(world, col, x,y,w,h, goalX, goalY, filter) 279 + goalX = goalX or x 280 + goalY = goalY or y 281 + 282 + local tch, move = col.touch, col.move 283 + if move.x ~= 0 or move.y ~= 0 then 284 + if col.normal.x ~= 0 then 285 + goalX = tch.x 286 + else 287 + goalY = tch.y 288 + end 289 + end 290 + 291 + col.slide = {x = goalX, y = goalY} 292 + 293 + x,y = tch.x, tch.y 294 + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) 295 + return goalX, goalY, cols, len 296 +end 297 + 298 +local bounce = function(world, col, x,y,w,h, goalX, goalY, filter) 299 + goalX = goalX or x 300 + goalY = goalY or y 301 + 302 + local tch, move = col.touch, col.move 303 + local tx, ty = tch.x, tch.y 304 + 305 + local bx, by = tx, ty 306 + 307 + if move.x ~= 0 or move.y ~= 0 then 308 + local bnx, bny = goalX - tx, goalY - ty 309 + if col.normal.x == 0 then bny = -bny else bnx = -bnx end 310 + bx, by = tx + bnx, ty + bny 311 + end 312 + 313 + col.bounce = {x = bx, y = by} 314 + x,y = tch.x, tch.y 315 + goalX, goalY = bx, by 316 + 317 + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) 318 + return goalX, goalY, cols, len 319 +end 320 + 321 +------------------------------------------ 322 +-- World 323 +------------------------------------------ 324 + 325 +local World = {} 326 +local World_mt = {__index = World} 327 + 328 +-- Private functions and methods 329 + 330 +local function sortByWeight(a,b) return a.weight < b.weight end 331 + 332 +local function sortByTiAndDistance(a,b) 333 + if a.ti == b.ti then 334 + local ir, ar, br = a.itemRect, a.otherRect, b.otherRect 335 + local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h) 336 + local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h) 337 + return ad < bd 338 + end 339 + return a.ti < b.ti 340 +end 341 + 342 +local function addItemToCell(self, item, cx, cy) 343 + self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'}) 344 + local row = self.rows[cy] 345 + row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})} 346 + local cell = row[cx] 347 + self.nonEmptyCells[cell] = true 348 + if not cell.items[item] then 349 + cell.items[item] = true 350 + cell.itemCount = cell.itemCount + 1 351 + end 352 +end 353 + 354 +local function removeItemFromCell(self, item, cx, cy) 355 + local row = self.rows[cy] 356 + if not row or not row[cx] or not row[cx].items[item] then return false end 357 + 358 + local cell = row[cx] 359 + cell.items[item] = nil 360 + cell.itemCount = cell.itemCount - 1 361 + if cell.itemCount == 0 then 362 + self.nonEmptyCells[cell] = nil 363 + end 364 + return true 365 +end 366 + 367 +local function getDictItemsInCellRect(self, cl,ct,cw,ch) 368 + local items_dict = {} 369 + for cy=ct,ct+ch-1 do 370 + local row = self.rows[cy] 371 + if row then 372 + for cx=cl,cl+cw-1 do 373 + local cell = row[cx] 374 + if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling 375 + for item,_ in pairs(cell.items) do 376 + items_dict[item] = true 377 + end 378 + end 379 + end 380 + end 381 + end 382 + 383 + return items_dict 384 +end 385 + 386 +local function getCellsTouchedBySegment(self, x1,y1,x2,y2) 387 + 388 + local cells, cellsLen, visited = {}, 0, {} 389 + 390 + grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy) 391 + local row = self.rows[cy] 392 + if not row then return end 393 + local cell = row[cx] 394 + if not cell or visited[cell] then return end 395 + 396 + visited[cell] = true 397 + cellsLen = cellsLen + 1 398 + cells[cellsLen] = cell 399 + end) 400 + 401 + return cells, cellsLen 402 +end 403 + 404 +local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter) 405 + local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2) 406 + local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1 407 + local visited, itemInfo, itemInfoLen = {},{},0 408 + for i=1,len do 409 + cell = cells[i] 410 + for item in pairs(cell.items) do 411 + if not visited[item] then 412 + visited[item] = true 413 + if (not filter or filter(item)) then 414 + rect = self.rects[item] 415 + l,t,w,h = rect.x,rect.y,rect.w,rect.h 416 + 417 + ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1) 418 + if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then 419 + -- the sorting is according to the t of an infinite line, not the segment 420 + tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge) 421 + itemInfoLen = itemInfoLen + 1 422 + itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)} 423 + end 424 + end 425 + end 426 + end 427 + end 428 + table.sort(itemInfo, sortByWeight) 429 + return itemInfo, itemInfoLen 430 +end 431 + 432 +local function getResponseByName(self, name) 433 + local response = self.responses[name] 434 + if not response then 435 + error(('Unknown collision type: %s (%s)'):format(name, type(name))) 436 + end 437 + return response 438 +end 439 + 440 + 441 +-- Misc Public Methods 442 + 443 +function World:addResponse(name, response) 444 + self.responses[name] = response 445 +end 446 + 447 +function World:project(item, x,y,w,h, goalX, goalY, filter) 448 + assertIsRect(x,y,w,h) 449 + 450 + goalX = goalX or x 451 + goalY = goalY or y 452 + filter = filter or defaultFilter 453 + 454 + local collisions, len = {}, 0 455 + 456 + local visited = {} 457 + if item ~= nil then visited[item] = true end 458 + 459 + -- This could probably be done with less cells using a polygon raster over the cells instead of a 460 + -- bounding rect of the whole movement. Conditional to building a queryPolygon method 461 + local tl, tt = min(goalX, x), min(goalY, y) 462 + local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h) 463 + local tw, th = tr-tl, tb-tt 464 + 465 + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th) 466 + 467 + local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) 468 + 469 + for other,_ in pairs(dictItemsInCellRect) do 470 + if not visited[other] then 471 + visited[other] = true 472 + 473 + local responseName = filter(item, other) 474 + if responseName then 475 + local ox,oy,ow,oh = self:getRect(other) 476 + local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY) 477 + 478 + if col then 479 + col.other = other 480 + col.item = item 481 + col.type = responseName 482 + 483 + len = len + 1 484 + collisions[len] = col 485 + end 486 + end 487 + end 488 + end 489 + 490 + table.sort(collisions, sortByTiAndDistance) 491 + 492 + return collisions, len 493 +end 494 + 495 +function World:countCells() 496 + local count = 0 497 + for _,row in pairs(self.rows) do 498 + for _,_ in pairs(row) do 499 + count = count + 1 500 + end 501 + end 502 + return count 503 +end 504 + 505 +function World:hasItem(item) 506 + return not not self.rects[item] 507 +end 508 + 509 +function World:getItems() 510 + local items, len = {}, 0 511 + for item,_ in pairs(self.rects) do 512 + len = len + 1 513 + items[len] = item 514 + end 515 + return items, len 516 +end 517 + 518 +function World:countItems() 519 + local len = 0 520 + for _ in pairs(self.rects) do len = len + 1 end 521 + return len 522 +end 523 + 524 +function World:getRect(item) 525 + local rect = self.rects[item] 526 + if not rect then 527 + error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.') 528 + end 529 + return rect.x, rect.y, rect.w, rect.h 530 +end 531 + 532 +function World:toWorld(cx, cy) 533 + return grid_toWorld(self.cellSize, cx, cy) 534 +end 535 + 536 +function World:toCell(x,y) 537 + return grid_toCell(self.cellSize, x, y) 538 +end 539 + 540 + 541 +--- Query methods 542 + 543 +function World:queryRect(x,y,w,h, filter) 544 + 545 + assertIsRect(x,y,w,h) 546 + 547 + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) 548 + local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) 549 + 550 + local items, len = {}, 0 551 + 552 + local rect 553 + for item,_ in pairs(dictItemsInCellRect) do 554 + rect = self.rects[item] 555 + if (not filter or filter(item)) 556 + and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h) 557 + then 558 + len = len + 1 559 + items[len] = item 560 + end 561 + end 562 + 563 + return items, len 564 +end 565 + 566 +function World:queryPoint(x,y, filter) 567 + local cx,cy = self:toCell(x,y) 568 + local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1) 569 + 570 + local items, len = {}, 0 571 + 572 + local rect 573 + for item,_ in pairs(dictItemsInCellRect) do 574 + rect = self.rects[item] 575 + if (not filter or filter(item)) 576 + and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y) 577 + then 578 + len = len + 1 579 + items[len] = item 580 + end 581 + end 582 + 583 + return items, len 584 +end 585 + 586 +function World:querySegment(x1, y1, x2, y2, filter) 587 + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) 588 + local items = {} 589 + for i=1, len do 590 + items[i] = itemInfo[i].item 591 + end 592 + return items, len 593 +end 594 + 595 +function World:querySegmentWithCoords(x1, y1, x2, y2, filter) 596 + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) 597 + local dx, dy = x2-x1, y2-y1 598 + local info, ti1, ti2 599 + for i=1, len do 600 + info = itemInfo[i] 601 + ti1 = info.ti1 602 + ti2 = info.ti2 603 + 604 + info.weight = nil 605 + info.x1 = x1 + dx * ti1 606 + info.y1 = y1 + dy * ti1 607 + info.x2 = x1 + dx * ti2 608 + info.y2 = y1 + dy * ti2 609 + end 610 + return itemInfo, len 611 +end 612 + 613 + 614 +--- Main methods 615 + 616 +function World:add(item, x,y,w,h) 617 + local rect = self.rects[item] 618 + if rect then 619 + error('Item ' .. tostring(item) .. ' added to the world twice.') 620 + end 621 + assertIsRect(x,y,w,h) 622 + 623 + self.rects[item] = {x=x,y=y,w=w,h=h} 624 + 625 + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) 626 + for cy = ct, ct+ch-1 do 627 + for cx = cl, cl+cw-1 do 628 + addItemToCell(self, item, cx, cy) 629 + end 630 + end 631 + 632 + return item 633 +end 634 + 635 +function World:remove(item) 636 + local x,y,w,h = self:getRect(item) 637 + 638 + self.rects[item] = nil 639 + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) 640 + for cy = ct, ct+ch-1 do 641 + for cx = cl, cl+cw-1 do 642 + removeItemFromCell(self, item, cx, cy) 643 + end 644 + end 645 +end 646 + 647 +function World:update(item, x2,y2,w2,h2) 648 + local x1,y1,w1,h1 = self:getRect(item) 649 + w2,h2 = w2 or w1, h2 or h1 650 + assertIsRect(x2,y2,w2,h2) 651 + 652 + if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then 653 + 654 + local cellSize = self.cellSize 655 + local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1) 656 + local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2) 657 + 658 + if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then 659 + 660 + local cr1, cb1 = cl1+cw1-1, ct1+ch1-1 661 + local cr2, cb2 = cl2+cw2-1, ct2+ch2-1 662 + local cyOut 663 + 664 + for cy = ct1, cb1 do 665 + cyOut = cy < ct2 or cy > cb2 666 + for cx = cl1, cr1 do 667 + if cyOut or cx < cl2 or cx > cr2 then 668 + removeItemFromCell(self, item, cx, cy) 669 + end 670 + end 671 + end 672 + 673 + for cy = ct2, cb2 do 674 + cyOut = cy < ct1 or cy > cb1 675 + for cx = cl2, cr2 do 676 + if cyOut or cx < cl1 or cx > cr1 then 677 + addItemToCell(self, item, cx, cy) 678 + end 679 + end 680 + end 681 + 682 + end 683 + 684 + local rect = self.rects[item] 685 + rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2 686 + 687 + end 688 +end 689 + 690 +function World:move(item, goalX, goalY, filter) 691 + local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter) 692 + 693 + self:update(item, actualX, actualY) 694 + 695 + return actualX, actualY, cols, len 696 +end 697 + 698 +function World:check(item, goalX, goalY, filter) 699 + filter = filter or defaultFilter 700 + 701 + local visited = {[item] = true} 702 + local visitedFilter = function(itm, other) 703 + if visited[other] then return false end 704 + return filter(itm, other) 705 + end 706 + 707 + local cols, len = {}, 0 708 + 709 + local x,y,w,h = self:getRect(item) 710 + 711 + local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter) 712 + 713 + while projected_len > 0 do 714 + local col = projected_cols[1] 715 + len = len + 1 716 + cols[len] = col 717 + 718 + visited[col.other] = true 719 + 720 + local response = getResponseByName(self, col.type) 721 + 722 + goalX, goalY, projected_cols, projected_len = response( 723 + self, 724 + col, 725 + x, y, w, h, 726 + goalX, goalY, 727 + visitedFilter 728 + ) 729 + end 730 + 731 + return goalX, goalY, cols, len 732 +end 733 + 734 + 735 +-- Public library functions 736 + 737 +bump.newWorld = function(cellSize) 738 + cellSize = cellSize or 64 739 + assertIsPositiveNumber(cellSize, 'cellSize') 740 + local world = setmetatable({ 741 + cellSize = cellSize, 742 + rects = {}, 743 + rows = {}, 744 + nonEmptyCells = {}, 745 + responses = {} 746 + }, World_mt) 747 + 748 + world:addResponse('touch', touch) 749 + world:addResponse('cross', cross) 750 + world:addResponse('slide', slide) 751 + world:addResponse('bounce', bounce) 752 + 753 + return world 754 +end 755 + 756 +bump.rect = { 757 + getNearestCorner = rect_getNearestCorner, 758 + getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices, 759 + getDiff = rect_getDiff, 760 + containsPoint = rect_containsPoint, 761 + isIntersecting = rect_isIntersecting, 762 + getSquareDistance = rect_getSquareDistance, 763 + detectCollision = rect_detectCollision 764 +} 765 + 766 +bump.responses = { 767 + touch = touch, 768 + cross = cross, 769 + slide = slide, 770 + bounce = bounce 771 +} 772 + 773 +return bump
Added src/worpt/lib/fennel.
1 +#!/usr/bin/env lua 2 + 3 +local fennel_dir = arg[0]:match("(.-)[^\\/]+$") 4 +package.path = fennel_dir .. "?.lua;" .. package.path 5 +local fennel = require('fennel') 6 +local unpack = unpack or table.unpack 7 + 8 +local help = [[ 9 +Usage: fennel [FLAG] [FILE] 10 + 11 +Run fennel, a lisp programming language for the Lua runtime. 12 + 13 + --repl : Command to launch an interactive repl session 14 + --compile FILES : Command to compile files and write Lua to stdout 15 + --eval SOURCE (-e) : Command to evaluate source code and print the result 16 + 17 + --no-searcher : Skip installing package.searchers entry 18 + --indent VAL : Indent compiler output with VAL 19 + --add-package-path PATH : Add PATH to package.path for finding Lua modules 20 + --add-fennel-path PATH : Add PATH to fennel.path for finding Fennel modules 21 + --globals G1[,G2...] : Allow these globals in addition to standard ones 22 + --globals-only G1[,G2] : Same as above, but exclude standard ones 23 + --check-unused-locals : Raise error when compiling code with unused locals 24 + --require-as-include : Inline required modules in the output 25 + --metadata : Enable function metadata, even in compiled output 26 + --no-metadata : Disable function metadata, even in REPL 27 + --correlate : Make Lua output line numbers match Fennel input 28 + --load FILE (-l) : Load the specified FILE before executing the command 29 + --no-fennelrc : Skip loading ~/.fennelrc when launching repl 30 + 31 + --help (-h) : Display this text 32 + --version (-v) : Show version 33 + 34 + Metadata is typically considered a development feature and is not recommended 35 + for production. It is used for docstrings and enabled by default in the REPL. 36 + 37 + When not given a command, runs the file given as the first argument. 38 + When given neither command nor file, launches a repl. 39 + 40 + If ~/.fennelrc exists, loads it before launching a repl.]] 41 + 42 +local options = {} 43 + 44 +local function dosafe(filename, opts, args) 45 + local ok, val = xpcall(function() 46 + return fennel.dofile(filename, opts, unpack(args)) 47 + end, fennel.traceback) 48 + if not ok then 49 + io.stderr:write(val .. "\n") 50 + os.exit(1) 51 + end 52 + return val 53 +end 54 + 55 +local function allowGlobals(globalNames) 56 + options.allowedGlobals = {} 57 + for g in globalNames:gmatch("([^,]+),?") do 58 + table.insert(options.allowedGlobals, g) 59 + end 60 +end 61 + 62 +for i=#arg, 1, -1 do 63 + if arg[i] == "--no-searcher" then 64 + options.no_searcher = true 65 + table.remove(arg, i) 66 + elseif arg[i] == "--indent" then 67 + options.indent = table.remove(arg, i+1) 68 + if options.indent == "false" then options.indent = false end 69 + table.remove(arg, i) 70 + elseif arg[i] == "--add-package-path" then 71 + local entry = table.remove(arg, i+1) 72 + package.path = entry .. ";" .. package.path 73 + table.remove(arg, i) 74 + elseif arg[i] == "--add-fennel-path" then 75 + local entry = table.remove(arg, i+1) 76 + fennel.path = entry .. ";" .. fennel.path 77 + table.remove(arg, i) 78 + elseif arg[i] == "--load" or arg[i] == "-l" then 79 + local file = table.remove(arg, i+1) 80 + dosafe(file, options, {}) 81 + table.remove(arg, i) 82 + elseif arg[i] == "--no-fennelrc" then 83 + options.fennelrc = false 84 + table.remove(arg, i) 85 + elseif arg[i] == "--correlate" then 86 + options.correlate = true 87 + table.remove(arg, i) 88 + elseif arg[i] == "--check-unused-locals" then 89 + options.checkUnusedLocals = true 90 + table.remove(arg, i) 91 + elseif arg[i] == "--globals" then 92 + allowGlobals(table.remove(arg, i+1)) 93 + for globalName in pairs(_G) do 94 + table.insert(options.allowedGlobals, globalName) 95 + end 96 + table.remove(arg, i) 97 + elseif arg[i] == "--globals-only" then 98 + allowGlobals(table.remove(arg, i+1)) 99 + table.remove(arg, i) 100 + elseif arg[i] == "--require-as-include" then 101 + options.requireAsInclude = true 102 + table.remove(arg, i) 103 + elseif arg[i] == "--metadata" then 104 + options.useMetadata = true 105 + table.remove(arg, i) 106 + elseif arg[i] == "--no-metadata" then 107 + options.useMetadata = false 108 + table.remove(arg, i) 109 + end 110 +end 111 + 112 +if not options.no_searcher then 113 + local opts = {} 114 + for k,v in pairs(options) do opts[k] = v end 115 + table.insert((package.loaders or package.searchers), fennel.make_searcher(opts)) 116 +end 117 + 118 +-- Try to load readline library 119 +local function tryReadline(opts) 120 + local ok, readline = pcall(require, "readline") 121 + if ok then 122 + readline.set_options({ 123 + keeplines = 1000 124 + }) 125 + function opts.readChunk(parserState) 126 + local prompt = parserState.stackSize > 0 and '.. ' or '>> ' 127 + local str = readline.readline(prompt) 128 + if str then 129 + return str .. "\n" 130 + end 131 + end 132 + 133 + -- completer is registered by the repl, until then returns empty list 134 + local completer 135 + function opts.registerCompleter(replCompleter) 136 + completer = replCompleter 137 + end 138 + local function replCompleter(text, from, to) 139 + if completer then 140 + -- stop completed syms from adding extra space on match 141 + readline.set_completion_append_character('') 142 + return completer(text:sub(from, to)) 143 + else 144 + return {} 145 + end 146 + end 147 + readline.set_complete_function(replCompleter) 148 + end 149 +end 150 + 151 +if arg[1] == "--repl" or #arg == 0 then 152 + local ppok, pp = pcall(fennel.dofile, fennel_dir .. "fennelview.fnl", options) 153 + if ppok then 154 + options.pp = pp 155 + else 156 + ppok, pp = pcall(require, "fennelview") 157 + if ppok then 158 + options.pp = pp 159 + end 160 + end 161 + 162 + tryReadline(options) 163 + 164 + if options.fennelrc ~= false then 165 + local home = os.getenv("HOME") 166 + local xdg_config_home = os.getenv("XDG_CONFIG_HOME") or home .. "/.config" 167 + local xdg_initfile = xdg_config_home .. "/fennel/fennelrc" 168 + local home_initfile = home .. "/.fennelrc" 169 + local init, initFilename = io.open(xdg_initfile, "rb"), xdg_initfile 170 + if not init then 171 + init, initFilename = io.open(home_initfile, "rb"), home_initfile 172 + end 173 + 174 + if init then 175 + init:close() 176 + -- pass in options so fennerlrc can make changes to it 177 + dosafe(initFilename, options, {options}) 178 + end 179 + end 180 + 181 + print("Welcome to Fennel " .. fennel.version .. "!") 182 + if options.useMetadata ~= false then 183 + print("Use (doc something) to view documentation.") 184 + end 185 + fennel.repl(options) 186 +elseif arg[1] == "--compile" then 187 + for i = 2, #arg do 188 + local f = arg[i] == "-" and io.stdin or assert(io.open(arg[i], "rb")) 189 + options.filename=arg[i] 190 + local ok, val = xpcall(function() 191 + return fennel.compileString(f:read("*all"), options) 192 + end, fennel.traceback) 193 + if ok then 194 + print(val) 195 + else 196 + io.stderr:write(val .. "\n") 197 + os.exit(1) 198 + end 199 + f:close() 200 + end 201 +elseif arg[1] == "--eval" or arg[1] == "-e" then 202 + if arg[2] and arg[2] ~= "-" then 203 + print(fennel.eval(arg[2], options)) 204 + else 205 + local source = io.stdin:read("*a") 206 + print(fennel.eval(source, options)) 207 + end 208 +elseif arg[1] == "--version" or arg[1] == "-v" then 209 + print("Fennel " .. fennel.version) 210 +elseif #arg >= 1 and arg[1] ~= "--help" and arg[1] ~= "-h" then 211 + local filename = table.remove(arg, 1) -- let the script have remaining args 212 + if filename == "-" then 213 + local source = io.stdin:read("*a") 214 + fennel.eval(source, options) 215 + else 216 + dosafe(filename, options, arg) 217 + end 218 +else 219 + print(help) 220 +end
Added src/worpt/lib/fennel.lua.
1 +--[[ 2 +Copyright (c) 2016-2019 Calvin Rose and contributors 3 +Permission is hereby granted, free of charge, to any person obtaining a copy of 4 +this software and associated documentation files (the "Software"), to deal in 5 +the Software without restriction, including without limitation the rights to 6 +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 +the Software, and to permit persons to whom the Software is furnished to do so, 8 +subject to the following conditions: 9 +The above copyright notice and this permission notice shall be included in all 10 +copies or substantial portions of the Software. 11 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 +]] 18 + 19 +-- Make global variables local. 20 +local setmetatable = setmetatable 21 +local getmetatable = getmetatable 22 +local type = type 23 +local assert = assert 24 +local pairs = pairs 25 +local ipairs = ipairs 26 +local tostring = tostring 27 +local unpack = unpack or table.unpack 28 + 29 +-- 30 +-- Main Types and support functions 31 +-- 32 + 33 +-- Map function f over sequential table t, removing values where f returns nil. 34 +-- Optionally takes a target table to insert the mapped values into. 35 +local function map(t, f, out) 36 + out = out or {} 37 + if type(f) ~= "function" then local s = f f = function(x) return x[s] end end 38 + for _,x in ipairs(t) do 39 + local v = f(x) 40 + if v then table.insert(out, v) end 41 + end 42 + return out 43 +end 44 + 45 +-- Map function f over key/value table t, similar to above, but it can return a 46 +-- sequential table if f returns a single value or a k/v table if f returns two. 47 +-- Optionally takes a target table to insert the mapped values into. 48 +local function kvmap(t, f, out) 49 + out = out or {} 50 + if type(f) ~= "function" then local s = f f = function(x) return x[s] end end 51 + for k,x in pairs(t) do 52 + local korv, v = f(k, x) 53 + if korv and not v then table.insert(out, korv) end 54 + if korv and v then out[korv] = v end 55 + end 56 + return out 57 +end 58 + 59 +local function deref(self) return self[1] end 60 + 61 +local function listToString(self, tostring2) 62 + return '(' .. table.concat(map(self, tostring2 or tostring), ' ', 1, #self) .. ')' 63 +end 64 + 65 +local SYMBOL_MT = { 'SYMBOL', __tostring = deref, __fennelview = deref } 66 +local EXPR_MT = { 'EXPR', __tostring = deref } 67 +local VARARG = setmetatable({ '...' }, { 'VARARG', __tostring = deref }) 68 +local LIST_MT = { 'LIST', __tostring = listToString, __fennelview = listToString } 69 +local SEQUENCE_MT = { 'SEQUENCE' } 70 + 71 +-- Load code with an environment in all recent Lua versions 72 +local function loadCode(code, environment, filename) 73 + environment = environment or _ENV or _G 74 + if setfenv and loadstring then 75 + local f = assert(loadstring(code, filename)) 76 + setfenv(f, environment) 77 + return f 78 + else 79 + return assert(load(code, filename, "t", environment)) 80 + end 81 +end 82 + 83 +-- Create a new list. Lists are a compile-time construct in Fennel; they are 84 +-- represented as tables with a special marker metatable. They only come from 85 +-- the parser, and they represent code which comes from reading a paren form; 86 +-- they are specifically not cons cells. 87 +local function list(...) 88 + return setmetatable({...}, LIST_MT) 89 +end 90 + 91 +-- Create a new symbol. Symbols are a compile-time construct in Fennel and are 92 +-- not exposed outside the compiler. Symbols have metadata describing what file, 93 +-- line, etc that they came from. 94 +local function sym(str, scope, meta) 95 + local s = {str, scope = scope} 96 + for k, v in pairs(meta or {}) do 97 + if type(k) == 'string' then s[k] = v end 98 + end 99 + return setmetatable(s, SYMBOL_MT) 100 +end 101 + 102 +-- Create a new sequence. Sequences are tables that come from the parser when 103 +-- it encounters a form with square brackets. They are treated as regular tables 104 +-- except when certain macros need to look for binding forms, etc specifically. 105 +local function sequence(...) 106 + return setmetatable({...}, SEQUENCE_MT) 107 +end 108 + 109 +-- Create a new expr 110 +-- etype should be one of 111 +-- "literal", -- literals like numbers, strings, nil, true, false 112 +-- "expression", -- Complex strings of Lua code, may have side effects, etc, but is an expression 113 +-- "statement", -- Same as expression, but is also a valid statement (function calls). 114 +-- "vargs", -- varargs symbol 115 +-- "sym", -- symbol reference 116 +local function expr(strcode, etype) 117 + return setmetatable({ strcode, type = etype }, EXPR_MT) 118 +end 119 + 120 +local function varg() 121 + return VARARG 122 +end 123 + 124 +local function isVarg(x) 125 + return x == VARARG and x 126 +end 127 + 128 +-- Checks if an object is a List. Returns the object if is a List. 129 +local function isList(x) 130 + return type(x) == 'table' and getmetatable(x) == LIST_MT and x 131 +end 132 + 133 +-- Checks if an object is a symbol. Returns the object if it is a symbol. 134 +local function isSym(x) 135 + return type(x) == 'table' and getmetatable(x) == SYMBOL_MT and x 136 +end 137 + 138 +-- Checks if an object any kind of table, EXCEPT list or symbol 139 +local function isTable(x) 140 + return type(x) == 'table' and 141 + x ~= VARARG and 142 + getmetatable(x) ~= LIST_MT and getmetatable(x) ~= SYMBOL_MT and x 143 +end 144 + 145 +-- Checks if an object is a sequence (created with a [] literal) 146 +local function isSequence(x) 147 + return type(x) == 'table' and getmetatable(x) == SEQUENCE_MT and x 148 +end 149 + 150 +-- Returns a shallow copy of its table argument. Returns an empty table on nil. 151 +local function copy(from) 152 + local to = {} 153 + for k, v in pairs(from or {}) do to[k] = v end 154 + return to 155 +end 156 + 157 +-- 158 +-- Parser 159 +-- 160 + 161 +-- Convert a stream of chunks to a stream of bytes. 162 +-- Also returns a second function to clear the buffer in the byte stream 163 +local function granulate(getchunk) 164 + local c = '' 165 + local index = 1 166 + local done = false 167 + return function (parserState) 168 + if done then return nil end 169 + if index <= #c then 170 + local b = c:byte(index) 171 + index = index + 1 172 + return b 173 + else 174 + c = getchunk(parserState) 175 + if not c or c == '' then 176 + done = true 177 + return nil 178 + end 179 + index = 2 180 + return c:byte(1) 181 + end 182 + end, function () 183 + c = '' 184 + end 185 +end 186 + 187 +-- Convert a string into a stream of bytes 188 +local function stringStream(str) 189 + local index = 1 190 + return function() 191 + local r = str:byte(index) 192 + index = index + 1 193 + return r 194 + end 195 +end 196 + 197 +-- Table of delimiter bytes - (, ), [, ], {, } 198 +-- Opener keys have closer as the value, and closers keys 199 +-- have true as their value. 200 +local delims = { 201 + [40] = 41, -- ( 202 + [41] = true, -- ) 203 + [91] = 93, -- [ 204 + [93] = true, -- ] 205 + [123] = 125, -- { 206 + [125] = true -- } 207 +} 208 + 209 +local function iswhitespace(b) 210 + return b == 32 or (b >= 9 and b <= 13) 211 +end 212 + 213 +local function issymbolchar(b) 214 + return b > 32 and 215 + not delims[b] and 216 + b ~= 127 and -- "<BS>" 217 + b ~= 34 and -- "\"" 218 + b ~= 39 and -- "'" 219 + b ~= 126 and -- "~" 220 + b ~= 59 and -- ";" 221 + b ~= 44 and -- "," 222 + b ~= 64 and -- "@" 223 + b ~= 96 -- "`" 224 +end 225 + 226 +local prefixes = { -- prefix chars substituted while reading 227 + [96] = 'quote', -- ` 228 + [44] = 'unquote', -- , 229 + [39] = 'quote', -- ' 230 + [35] = 'hashfn' -- # 231 +} 232 + 233 +-- The resetRoot function needs to be called at every exit point of the compiler 234 +-- including when there's a parse error or compiler error. Introduce it up here 235 +-- so error functions have access to it, and set it when we have values below. 236 +local resetRoot = nil 237 + 238 +-- Parse one value given a function that 239 +-- returns sequential bytes. Will throw an error as soon 240 +-- as possible without getting more bytes on bad input. Returns 241 +-- if a value was read, and then the value read. Will return nil 242 +-- when input stream is finished. 243 +local function parser(getbyte, filename) 244 + 245 + -- Stack of unfinished values 246 + local stack = {} 247 + 248 + -- Provide one character buffer and keep 249 + -- track of current line and byte index 250 + local line = 1 251 + local byteindex = 0 252 + local lastb 253 + local function ungetb(ub) 254 + if ub == 10 then line = line - 1 end 255 + byteindex = byteindex - 1 256 + lastb = ub 257 + end 258 + local function getb() 259 + local r 260 + if lastb then 261 + r, lastb = lastb, nil 262 + else 263 + r = getbyte({ stackSize = #stack }) 264 + end 265 + byteindex = byteindex + 1 266 + if r == 10 then line = line + 1 end 267 + return r 268 + end 269 + local function parseError(msg) 270 + if resetRoot then resetRoot() end 271 + return error(msg .. ' in ' .. (filename or 'unknown') .. ':' .. line, 0) 272 + end 273 + 274 + -- Parse stream 275 + return function() 276 + 277 + -- Dispatch when we complete a value 278 + local done, retval 279 + local whitespaceSinceDispatch = true 280 + local function dispatch(v) 281 + if #stack == 0 then 282 + retval = v 283 + done = true 284 + elseif stack[#stack].prefix then 285 + local stacktop = stack[#stack] 286 + stack[#stack] = nil 287 + return dispatch(list(sym(stacktop.prefix), v)) 288 + else 289 + table.insert(stack[#stack], v) 290 + end 291 + whitespaceSinceDispatch = false 292 + end 293 + 294 + -- Throw nice error when we expect more characters 295 + -- but reach end of stream. 296 + local function badend() 297 + local accum = map(stack, "closer") 298 + parseError(('expected closing delimiter%s %s'):format( 299 + #stack == 1 and "" or "s", 300 + string.char(unpack(accum)))) 301 + end 302 + 303 + -- The main parse loop 304 + repeat 305 + local b 306 + 307 + -- Skip whitespace 308 + repeat 309 + b = getb() 310 + if b and iswhitespace(b) then 311 + whitespaceSinceDispatch = true 312 + end 313 + until not b or not iswhitespace(b) 314 + if not b then 315 + if #stack > 0 then badend() end 316 + return nil 317 + end 318 + 319 + if b == 59 then -- ; Comment 320 + repeat 321 + b = getb() 322 + until not b or b == 10 -- newline 323 + elseif type(delims[b]) == 'number' then -- Opening delimiter 324 + if not whitespaceSinceDispatch then 325 + parseError('expected whitespace before opening delimiter ' 326 + .. string.char(b)) 327 + end 328 + table.insert(stack, setmetatable({ 329 + closer = delims[b], 330 + line = line, 331 + filename = filename, 332 + bytestart = byteindex 333 + }, LIST_MT)) 334 + elseif delims[b] then -- Closing delimiter 335 + if #stack == 0 then parseError('unexpected closing delimiter ' 336 + .. string.char(b)) end 337 + local last = stack[#stack] 338 + local val 339 + if last.closer ~= b then 340 + parseError('unexpected delimiter ' .. string.char(b) .. 341 + ', expected ' .. string.char(last.closer)) 342 + end 343 + last.byteend = byteindex -- Set closing byte index 344 + if b == 41 then -- ; ) 345 + val = last 346 + elseif b == 93 then -- ; ] 347 + val = sequence() 348 + for i = 1, #last do 349 + val[i] = last[i] 350 + end 351 + else -- ; } 352 + if #last % 2 ~= 0 then 353 + parseError('expected even number of values in table literal') 354 + end 355 + val = {} 356 + for i = 1, #last, 2 do 357 + if(tostring(last[i]) == ":" and isSym(last[i + 1]) 358 + and isSym(last[i])) then 359 + last[i] = tostring(last[i + 1]) 360 + end 361 + val[last[i]] = last[i + 1] 362 + end 363 + end 364 + stack[#stack] = nil 365 + dispatch(val) 366 + elseif b == 34 then -- Quoted string 367 + local state = "base" 368 + local chars = {34} 369 + stack[#stack + 1] = {closer = 34} 370 + repeat 371 + b = getb() 372 + chars[#chars + 1] = b 373 + if state == "base" then 374 + if b == 92 then 375 + state = "backslash" 376 + elseif b == 34 then 377 + state = "done" 378 + end 379 + else 380 + -- state == "backslash" 381 + state = "base" 382 + end 383 + until not b or (state == "done") 384 + if not b then badend() end 385 + stack[#stack] = nil 386 + local raw = string.char(unpack(chars)) 387 + local formatted = raw:gsub("[\1-\31]", function (c) return '\\' .. c:byte() end) 388 + local loadFn = loadCode(('return %s'):format(formatted), nil, filename) 389 + dispatch(loadFn()) 390 + elseif prefixes[b] then 391 + -- expand prefix byte into wrapping form eg. '`a' into '(quote a)' 392 + table.insert(stack, { 393 + prefix = prefixes[b] 394 + }) 395 + local nextb = getb() 396 + if iswhitespace(nextb) then 397 + if b == 35 then 398 + stack[#stack] = nil 399 + dispatch(sym('#')) 400 + else 401 + parseError('invalid whitespace after quoting prefix') 402 + end 403 + end 404 + ungetb(nextb) 405 + elseif issymbolchar(b) or b == string.byte("~") then -- Try symbol 406 + local chars = {} 407 + local bytestart = byteindex 408 + repeat 409 + chars[#chars + 1] = b 410 + b = getb() 411 + until not b or not issymbolchar(b) 412 + if b then ungetb(b) end 413 + local rawstr = string.char(unpack(chars)) 414 + if rawstr == 'true' then dispatch(true) 415 + elseif rawstr == 'false' then dispatch(false) 416 + elseif rawstr == '...' then dispatch(VARARG) 417 + elseif rawstr:match('^:.+$') then -- colon style strings 418 + dispatch(rawstr:sub(2)) 419 + elseif rawstr:match("^~") and rawstr ~= "~=" then 420 + -- for backwards-compatibility, special-case allowance of ~= 421 + -- but all other uses of ~ are disallowed 422 + parseError("illegal character: ~") 423 + else 424 + local forceNumber = rawstr:match('^%d') 425 + local numberWithStrippedUnderscores = rawstr:gsub("_", "") 426 + local x 427 + if forceNumber then 428 + x = tonumber(numberWithStrippedUnderscores) or 429 + parseError('could not read token "' .. rawstr .. '"') 430 + else 431 + x = tonumber(numberWithStrippedUnderscores) or 432 + (rawstr:match("%.[0-9]") and 433 + parseError("can't start multisym segment " .. 434 + "with digit: ".. rawstr)) or 435 + ((rawstr:match(":%.") or 436 + rawstr:match("%.:") or 437 + rawstr:match("::") or 438 + (rawstr:match("%.%.") and rawstr ~= "..")) and 439 + parseError("malformed multisym: " .. rawstr)) or 440 + (rawstr:match(":.+:") and 441 + parseError("method call must be last component " .. 442 + "of multisym: " .. rawstr)) or 443 + sym(rawstr, nil, { line = line, 444 + filename = filename, 445 + bytestart = bytestart, 446 + byteend = byteindex, }) 447 + end 448 + dispatch(x) 449 + end 450 + else 451 + parseError("illegal character: " .. string.char(b)) 452 + end 453 + until done 454 + return true, retval 455 + end, function () 456 + stack = {} 457 + end 458 +end 459 + 460 +-- 461 +-- Compilation 462 +-- 463 + 464 +-- Top level compilation bindings. 465 +local rootChunk, rootScope, rootOptions 466 + 467 +local function setResetRoot(oldChunk, oldScope, oldOptions) 468 + local oldResetRoot = resetRoot -- this needs to nest! 469 + resetRoot = function() 470 + rootChunk, rootScope, rootOptions = oldChunk, oldScope, oldOptions 471 + resetRoot = oldResetRoot 472 + end 473 +end 474 + 475 +local GLOBAL_SCOPE 476 + 477 +-- Create a new Scope, optionally under a parent scope. Scopes are compile time 478 +-- constructs that are responsible for keeping track of local variables, name 479 +-- mangling, and macros. They are accessible to user code via the 480 +-- 'eval-compiler' special form (may change). They use metatables to implement 481 +-- nesting via metatables. 482 +local function makeScope(parent) 483 + if not parent then parent = GLOBAL_SCOPE end 484 + return { 485 + unmanglings = setmetatable({}, { 486 + __index = parent and parent.unmanglings 487 + }), 488 + manglings = setmetatable({}, { 489 + __index = parent and parent.manglings 490 + }), 491 + specials = setmetatable({}, { 492 + __index = parent and parent.specials 493 + }), 494 + symmeta = setmetatable({}, { 495 + __index = parent and parent.symmeta 496 + }), 497 + includes = setmetatable({}, { 498 + __index = parent and parent.includes 499 + }), 500 + refedglobals = setmetatable({}, { 501 + __index = parent and parent.refedglobals 502 + }), 503 + autogensyms = {}, 504 + parent = parent, 505 + vararg = parent and parent.vararg, 506 + depth = parent and ((parent.depth or 0) + 1) or 0, 507 + hashfn = parent and parent.hashfn 508 + } 509 +end 510 + 511 +-- Assert a condition and raise a compile error with line numbers. The ast arg 512 +-- should be unmodified so that its first element is the form being called. 513 +local function assertCompile(condition, msg, ast) 514 + -- if we use regular `assert' we can't provide the `level' argument of zero 515 + if not condition then 516 + if resetRoot then resetRoot() end 517 + error(string.format("Compile error in '%s' %s:%s: %s", 518 + isSym(ast[1]) and ast[1][1] or ast[1] or '()', 519 + ast.filename or "unknown", ast.line or '?', msg), 0) 520 + end 521 + return condition 522 +end 523 + 524 +GLOBAL_SCOPE = makeScope() 525 +GLOBAL_SCOPE.vararg = true 526 +local SPECIALS = GLOBAL_SCOPE.specials 527 +local COMPILER_SCOPE = makeScope(GLOBAL_SCOPE) 528 + 529 +local luaKeywords = { 530 + 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 531 + 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 532 + 'until', 'while' 533 +} 534 + 535 +for i, v in ipairs(luaKeywords) do luaKeywords[v] = i end 536 + 537 +local function isValidLuaIdentifier(str) 538 + return (str:match('^[%a_][%w_]*$') and not luaKeywords[str]) 539 +end 540 + 541 +-- Allow printing a string to Lua, also keep as 1 line. 542 +local serializeSubst = { 543 + ['\a'] = '\\a', 544 + ['\b'] = '\\b', 545 + ['\f'] = '\\f', 546 + ['\n'] = 'n', 547 + ['\t'] = '\\t', 548 + ['\v'] = '\\v' 549 +} 550 +local function serializeString(str) 551 + local s = ("%q"):format(str) 552 + s = s:gsub('.', serializeSubst):gsub("[\128-\255]", function(c) 553 + return "\\" .. c:byte() 554 + end) 555 + return s 556 +end 557 + 558 +-- A multi symbol is a symbol that is actually composed of 559 +-- two or more symbols using the dot syntax. The main differences 560 +-- from normal symbols is that they cannot be declared local, and 561 +-- they may have side effects on invocation (metatables) 562 +local function isMultiSym(str) 563 + if isSym(str) then 564 + return isMultiSym(tostring(str)) 565 + end 566 + if type(str) ~= 'string' then return end 567 + local parts = {} 568 + for part in str:gmatch('[^%.%:]+[%.%:]?') do 569 + local lastChar = part:sub(-1) 570 + if lastChar == ":" then 571 + parts.multiSymMethodCall = true 572 + end 573 + if lastChar == ":" or lastChar == "." then 574 + parts[#parts + 1] = part:sub(1, -2) 575 + else 576 + parts[#parts + 1] = part 577 + end 578 + end 579 + return #parts > 0 and 580 + (str:match('%.') or str:match(':')) and 581 + (not str:match('%.%.')) and 582 + str:byte() ~= string.byte '.' and 583 + str:byte(-1) ~= string.byte '.' and 584 + parts 585 +end 586 + 587 +local function isQuoted(symbol) return symbol.quoted end 588 + 589 +-- Mangler for global symbols. Does not protect against collisions, 590 +-- but makes them unlikely. This is the mangling that is exposed to 591 +-- to the world. 592 +local function globalMangling(str) 593 + if isValidLuaIdentifier(str) then 594 + return str 595 + end 596 + -- Use underscore as escape character 597 + return '__fnl_global__' .. str:gsub('[^%w]', function (c) 598 + return ('_%02x'):format(c:byte()) 599 + end) 600 +end 601 + 602 +-- Reverse a global mangling. Takes a Lua identifier and 603 +-- returns the fennel symbol string that created it. 604 +local function globalUnmangling(identifier) 605 + local rest = identifier:match('^__fnl_global__(.*)$') 606 + if rest then 607 + local r = rest:gsub('_[%da-f][%da-f]', function (code) 608 + return string.char(tonumber(code:sub(2), 16)) 609 + end) 610 + return r -- don't return multiple values 611 + else 612 + return identifier 613 + end 614 +end 615 + 616 +-- If there's a provided list of allowed globals, don't let references thru that 617 +-- aren't on the list. This list is set at the compiler entry points of compile 618 +-- and compileStream. 619 +local allowedGlobals 620 + 621 +local function globalAllowed(name) 622 + if not allowedGlobals then return true end 623 + for _, g in ipairs(allowedGlobals) do 624 + if g == name then return true end 625 + end 626 +end 627 + 628 +-- Creates a symbol from a string by mangling it. 629 +-- ensures that the generated symbol is unique 630 +-- if the input string is unique in the scope. 631 +local function localMangling(str, scope, ast, tempManglings) 632 + local append = 0 633 + local mangling = str 634 + assertCompile(not isMultiSym(str), 'did not expect multi symbol ' .. str, ast) 635 + 636 + -- Mapping mangling to a valid Lua identifier 637 + if luaKeywords[mangling] or mangling:match('^%d') then 638 + mangling = '_' .. mangling 639 + end 640 + mangling = mangling:gsub('-', '_') 641 + mangling = mangling:gsub('[^%w_]', function (c) 642 + return ('_%02x'):format(c:byte()) 643 + end) 644 + 645 + -- Prevent name collisions with existing symbols 646 + local raw = mangling 647 + while scope.unmanglings[mangling] do 648 + mangling = raw .. append 649 + append = append + 1 650 + end 651 + 652 + scope.unmanglings[mangling] = str 653 + local manglings = tempManglings or scope.manglings 654 + manglings[str] = mangling 655 + return mangling 656 +end 657 + 658 +-- Calling this function will mean that further 659 +-- compilation in scope will use these new manglings 660 +-- instead of the current manglings. 661 +local function applyManglings(scope, newManglings, ast) 662 + for raw, mangled in pairs(newManglings) do 663 + assertCompile(not scope.refedglobals[mangled], 664 + "use of global " .. raw .. " is aliased by a local", ast) 665 + scope.manglings[raw] = mangled 666 + end 667 +end 668 + 669 +-- Combine parts of a symbol 670 +local function combineParts(parts, scope) 671 + local ret = scope.manglings[parts[1]] or globalMangling(parts[1]) 672 + for i = 2, #parts do 673 + if isValidLuaIdentifier(parts[i]) then 674 + if parts.multiSymMethodCall and i == #parts then 675 + ret = ret .. ':' .. parts[i] 676 + else 677 + ret = ret .. '.' .. parts[i] 678 + end 679 + else 680 + ret = ret .. '[' .. serializeString(parts[i]) .. ']' 681 + end 682 + end 683 + return ret 684 +end 685 + 686 +-- Generates a unique symbol in the scope. 687 +local function gensym(scope, base) 688 + local mangling 689 + local append = 0 690 + repeat 691 + mangling = (base or '') .. '_' .. append .. '_' 692 + append = append + 1 693 + until not scope.unmanglings[mangling] 694 + scope.unmanglings[mangling] = true 695 + return mangling 696 +end 697 + 698 +-- Generates a unique symbol in the scope based on the base name. Calling 699 +-- repeatedly with the same base and same scope will return existing symbol 700 +-- rather than generating new one. 701 +local function autogensym(base, scope) 702 + if scope.autogensyms[base] then return scope.autogensyms[base] end 703 + local mangling = gensym(scope, base) 704 + scope.autogensyms[base] = mangling 705 + return mangling 706 +end 707 + 708 +-- Check if a binding is valid 709 +local function checkBindingValid(symbol, scope, ast) 710 + -- Check if symbol will be over shadowed by special 711 + local name = symbol[1] 712 + assertCompile(not scope.specials[name], 713 + ("symbol %s may be overshadowed by a special form or macro"):format(name), ast) 714 + assertCompile(not isQuoted(symbol), 'macro tried to bind ' .. name .. 715 + " without gensym; try ' .. name .. '# instead", ast) 716 + 717 +end 718 + 719 +-- Declare a local symbol 720 +local function declareLocal(symbol, meta, scope, ast, tempManglings) 721 + checkBindingValid(symbol, scope, ast) 722 + local name = symbol[1] 723 + assertCompile(not isMultiSym(name), "did not expect mutltisym", ast) 724 + local mangling = localMangling(name, scope, ast, tempManglings) 725 + scope.symmeta[name] = meta 726 + return mangling 727 +end 728 + 729 +-- Convert symbol to Lua code. Will only work for local symbols 730 +-- if they have already been declared via declareLocal 731 +local function symbolToExpression(symbol, scope, isReference) 732 + local name = symbol[1] 733 + local multiSymParts = isMultiSym(name) 734 + if scope.hashfn then 735 + if name == '$' then name = '$1' end 736 + if multiSymParts then 737 + if multiSymParts[1] == "$" then 738 + multiSymParts[1] = "$1" 739 + name = table.concat(multiSymParts, ".") 740 + end 741 + end 742 + end 743 + local parts = multiSymParts or {name} 744 + local etype = (#parts > 1) and "expression" or "sym" 745 + local isLocal = scope.manglings[parts[1]] 746 + if isLocal and scope.symmeta[parts[1]] then scope.symmeta[parts[1]].used = true end 747 + -- if it's a reference and not a symbol which introduces a new binding 748 + -- then we need to check for allowed globals 749 + assertCompile(not isReference or isLocal or globalAllowed(parts[1]), 750 + 'unknown global in strict mode: ' .. parts[1], symbol) 751 + if not isLocal then 752 + rootScope.refedglobals[parts[1]] = true 753 + end 754 + return expr(combineParts(parts, scope), etype) 755 +end 756 + 757 + 758 +-- Emit Lua code 759 +local function emit(chunk, out, ast) 760 + if type(out) == 'table' then 761 + table.insert(chunk, out) 762 + else 763 + table.insert(chunk, {leaf = out, ast = ast}) 764 + end 765 +end 766 + 767 +-- Do some peephole optimization. 768 +local function peephole(chunk) 769 + if chunk.leaf then return chunk end 770 + -- Optimize do ... end in some cases. 771 + if #chunk == 3 and 772 + chunk[1].leaf == 'do' and 773 + not chunk[2].leaf and 774 + chunk[3].leaf == 'end' then 775 + return peephole(chunk[2]) 776 + end 777 + -- Recurse 778 + return map(chunk, peephole) 779 +end 780 + 781 +-- correlate line numbers in input with line numbers in output 782 +local function flattenChunkCorrelated(mainChunk) 783 + local function flatten(chunk, out, lastLine, file) 784 + if chunk.leaf then 785 + out[lastLine] = (out[lastLine] or "") .. " " .. chunk.leaf 786 + else 787 + for _, subchunk in ipairs(chunk) do 788 + -- Ignore empty chunks 789 + if subchunk.leaf or #subchunk > 0 then 790 + -- don't increase line unless it's from the same file 791 + if subchunk.ast and file == subchunk.ast.file then 792 + lastLine = math.max(lastLine, subchunk.ast.line or 0) 793 + end 794 + lastLine = flatten(subchunk, out, lastLine, file) 795 + end 796 + end 797 + end 798 + return lastLine 799 + end 800 + local out = {} 801 + local last = flatten(mainChunk, out, 1, mainChunk.file) 802 + for i = 1, last do 803 + if out[i] == nil then out[i] = "" end 804 + end 805 + return table.concat(out, "\n") 806 +end 807 + 808 +-- Flatten a tree of indented Lua source code lines. 809 +-- Tab is what is used to indent a block. 810 +local function flattenChunk(sm, chunk, tab, depth) 811 + if type(tab) == 'boolean' then tab = tab and ' ' or '' end 812 + if chunk.leaf then 813 + local code = chunk.leaf 814 + local info = chunk.ast 815 + -- Just do line info for now to save memory 816 + if sm then sm[#sm + 1] = info and info.line or -1 end 817 + return code 818 + else 819 + local parts = map(chunk, function(c) 820 + if c.leaf or #c > 0 then -- Ignore empty chunks 821 + local sub = flattenChunk(sm, c, tab, depth + 1) 822 + if depth > 0 then sub = tab .. sub:gsub('\n', '\n' .. tab) end 823 + return sub 824 + end 825 + end) 826 + return table.concat(parts, '\n') 827 + end 828 +end 829 + 830 +-- Some global state for all fennel sourcemaps. For the time being, 831 +-- this seems the easiest way to store the source maps. 832 +-- Sourcemaps are stored with source being mapped as the key, prepended 833 +-- with '@' if it is a filename (like debug.getinfo returns for source). 834 +-- The value is an array of mappings for each line. 835 +local fennelSourcemap = {} 836 +-- TODO: loading, unloading, and saving sourcemaps? 837 + 838 +local function makeShortSrc(source) 839 + source = source:gsub('\n', ' ') 840 + if #source <= 49 then 841 + return '[fennel "' .. source .. '"]' 842 + else 843 + return '[fennel "' .. source:sub(1, 46) .. '..."]' 844 + end 845 +end 846 + 847 +-- Return Lua source and source map table 848 +local function flatten(chunk, options) 849 + chunk = peephole(chunk) 850 + if(options.correlate) then 851 + return flattenChunkCorrelated(chunk), {} 852 + else 853 + local sm = {} 854 + local ret = flattenChunk(sm, chunk, options.indent, 0) 855 + if sm then 856 + local key, short_src 857 + if options.filename then 858 + short_src = options.filename 859 + key = '@' .. short_src 860 + else 861 + key = ret 862 + short_src = makeShortSrc(options.source or ret) 863 + end 864 + sm.short_src = short_src 865 + sm.key = key 866 + fennelSourcemap[key] = sm 867 + end 868 + return ret, sm 869 + end 870 +end 871 + 872 +-- module-wide state for metadata 873 +-- create metadata table with weakly-referenced keys 874 +local function makeMetadata() 875 + return setmetatable({}, { 876 + __mode = 'k', 877 + __index = { 878 + get = function(self, tgt, key) 879 + if self[tgt] then return self[tgt][key] end 880 + end, 881 + set = function(self, tgt, key, value) 882 + self[tgt] = self[tgt] or {} 883 + self[tgt][key] = value 884 + return tgt 885 + end, 886 + setall = function(self, tgt, ...) 887 + local kvLen, kvs = select('#', ...), {...} 888 + if kvLen % 2 ~= 0 then 889 + error('metadata:setall() expected even number of k/v pairs') 890 + end 891 + self[tgt] = self[tgt] or {} 892 + for i = 1, kvLen, 2 do self[tgt][kvs[i]] = kvs[i + 1] end 893 + return tgt 894 + end, 895 + }}) 896 +end 897 + 898 +local metadata = makeMetadata() 899 +local doc = function(tgt, name) 900 + if(not tgt) then return name .. " not found" end 901 + local docstring = (metadata:get(tgt, 'fnl/docstring') or 902 + '#<undocumented>'):gsub('\n$', ''):gsub('\n', '\n ') 903 + if type(tgt) == "function" then 904 + local arglist = table.concat(metadata:get(tgt, 'fnl/arglist') or 905 + {'#<unknown-arguments>'}, ' ') 906 + return string.format("(%s%s%s)\n %s", name, #arglist > 0 and ' ' or '', 907 + arglist, docstring) 908 + else 909 + return string.format("%s\n %s", name, docstring) 910 + end 911 +end 912 + 913 +local function docSpecial(name, arglist, docstring) 914 + metadata[SPECIALS[name]] = 915 + { ["fnl/docstring"] = docstring, ["fnl/arglist"] = arglist } 916 +end 917 + 918 +-- Convert expressions to Lua string 919 +local function exprs1(exprs) 920 + return table.concat(map(exprs, 1), ', ') 921 +end 922 + 923 +-- Compile side effects for a chunk 924 +local function keepSideEffects(exprs, chunk, start, ast) 925 + start = start or 1 926 + for j = start, #exprs do 927 + local se = exprs[j] 928 + -- Avoid the rogue 'nil' expression (nil is usually a literal, 929 + -- but becomes an expression if a special form 930 + -- returns 'nil'.) 931 + if se.type == 'expression' and se[1] ~= 'nil' then 932 + emit(chunk, ('do local _ = %s end'):format(tostring(se)), ast) 933 + elseif se.type == 'statement' then 934 + local code = tostring(se) 935 + emit(chunk, code:byte() == 40 and ("do end " .. code) or code , ast) 936 + end 937 + end 938 +end 939 + 940 +-- Does some common handling of returns and register 941 +-- targets for special forms. Also ensures a list expression 942 +-- has an acceptable number of expressions if opts contains the 943 +-- "nval" option. 944 +local function handleCompileOpts(exprs, parent, opts, ast) 945 + if opts.nval then 946 + local n = opts.nval 947 + if n ~= #exprs then 948 + local len = #exprs 949 + if len > n then 950 + -- Drop extra 951 + keepSideEffects(exprs, parent, n + 1, ast) 952 + for i = n + 1, len do 953 + exprs[i] = nil 954 + end 955 + else 956 + -- Pad with nils 957 + for i = #exprs + 1, n do 958 + exprs[i] = expr('nil', 'literal') 959 + end 960 + end 961 + end 962 + end 963 + if opts.tail then 964 + emit(parent, ('return %s'):format(exprs1(exprs)), ast) 965 + end 966 + if opts.target then 967 + emit(parent, ('%s = %s'):format(opts.target, exprs1(exprs)), ast) 968 + end 969 + if opts.tail or opts.target then 970 + -- Prevent statements and expression from being used twice if they 971 + -- have side-effects. Since if the target or tail options are set, 972 + -- the expressions are already emitted, we should not return them. This 973 + -- is fine, as when these options are set, the caller doesn't need the result 974 + -- anyways. 975 + exprs = {} 976 + end 977 + return exprs 978 +end 979 + 980 +-- Compile an AST expression in the scope into parent, a tree 981 +-- of lines that is eventually compiled into Lua code. Also 982 +-- returns some information about the evaluation of the compiled expression, 983 +-- which can be used by the calling function. Macros 984 +-- are resolved here, as well as special forms in that order. 985 +-- the 'ast' param is the root AST to compile 986 +-- the 'scope' param is the scope in which we are compiling 987 +-- the 'parent' param is the table of lines that we are compiling into. 988 +-- add lines to parent by appending strings. Add indented blocks by appending 989 +-- tables of more lines. 990 +-- the 'opts' param contains info about where the form is being compiled. 991 +-- Options include: 992 +-- 'target' - mangled name of symbol(s) being compiled to. 993 +-- Could be one variable, 'a', or a list, like 'a, b, _0_'. 994 +-- 'tail' - boolean indicating tail position if set. If set, form will generate a return 995 +-- instruction. 996 +-- 'nval' - The number of values to compile to if it is known to be a fixed value. 997 + 998 +-- In Lua, an expression can evaluate to 0 or more values via multiple 999 +-- returns. In many cases, Lua will drop extra values and convert a 0 value 1000 +-- expression to nil. In other cases, Lua will use all of the values in an 1001 +-- expression, such as in the last argument of a function call. Nval is an 1002 +-- option passed to compile1 to say that the resulting expression should have 1003 +-- at least n values. It lets us generate better code, because if we know we 1004 +-- are only going to use 1 or 2 values from an expression, we can create 1 or 2 1005 +-- locals to store intermediate results rather than turn the expression into a 1006 +-- closure that is called immediately, which we have to do if we don't know. 1007 + 1008 +local function compile1(ast, scope, parent, opts) 1009 + opts = opts or {} 1010 + local exprs = {} 1011 + 1012 + -- Compile the form 1013 + if isList(ast) then 1014 + -- Function call or special form 1015 + local len = #ast 1016 + assertCompile(len > 0, "expected a function to call", ast) 1017 + -- Test for special form 1018 + local first = ast[1] 1019 + if isSym(first) then -- Resolve symbol 1020 + first = first[1] 1021 + end 1022 + local multiSymParts = isMultiSym(first) 1023 + local special = scope.specials[first] 1024 + if special and isSym(ast[1]) then 1025 + -- Special form 1026 + exprs = special(ast, scope, parent, opts) or expr('nil', 'literal') 1027 + -- Be very accepting of strings or expression 1028 + -- as well as lists or expressions 1029 + if type(exprs) == 'string' then exprs = expr(exprs, 'expression') end 1030 + if getmetatable(exprs) == EXPR_MT then exprs = {exprs} end 1031 + -- Unless the special form explicitly handles the target, tail, and 1032 + -- nval properties, (indicated via the 'returned' flag), handle 1033 + -- these options. 1034 + if not exprs.returned then 1035 + exprs = handleCompileOpts(exprs, parent, opts, ast) 1036 + elseif opts.tail or opts.target then 1037 + exprs = {} 1038 + end 1039 + exprs.returned = true 1040 + return exprs 1041 + elseif multiSymParts and multiSymParts.multiSymMethodCall then 1042 + local tableWithMethod = table.concat({ 1043 + unpack(multiSymParts, 1, #multiSymParts - 1) 1044 + }, '.') 1045 + local methodToCall = multiSymParts[#multiSymParts] 1046 + local newAST = list(sym(':', scope), sym(tableWithMethod, scope), methodToCall) 1047 + for i = 2, len do 1048 + newAST[#newAST + 1] = ast[i] 1049 + end 1050 + local compiled = compile1(newAST, scope, parent, opts) 1051 + exprs = compiled 1052 + else 1053 + -- Function call 1054 + local fargs = {} 1055 + local fcallee = compile1(ast[1], scope, parent, { 1056 + nval = 1 1057 + })[1] 1058 + assertCompile(fcallee.type ~= 'literal', 1059 + 'cannot call literal value', ast) 1060 + fcallee = tostring(fcallee) 1061 + for i = 2, len do 1062 + local subexprs = compile1(ast[i], scope, parent, { 1063 + nval = i ~= len and 1 or nil 1064 + }) 1065 + fargs[#fargs + 1] = subexprs[1] or expr('nil', 'literal') 1066 + if i == len then 1067 + -- Add sub expressions to function args 1068 + for j = 2, #subexprs do 1069 + fargs[#fargs + 1] = subexprs[j] 1070 + end 1071 + else 1072 + -- Emit sub expression only for side effects 1073 + keepSideEffects(subexprs, parent, 2, ast[i]) 1074 + end 1075 + end 1076 + local call = ('%s(%s)'):format(tostring(fcallee), exprs1(fargs)) 1077 + exprs = handleCompileOpts({expr(call, 'statement')}, parent, opts, ast) 1078 + end 1079 + elseif isVarg(ast) then 1080 + assertCompile(scope.vararg, "unexpected vararg", ast) 1081 + exprs = handleCompileOpts({expr('...', 'varg')}, parent, opts, ast) 1082 + elseif isSym(ast) then 1083 + local e 1084 + local multiSymParts = isMultiSym(ast) 1085 + assertCompile(not (multiSymParts and multiSymParts.multiSymMethodCall), 1086 + "multisym method calls may only be in call position", ast) 1087 + -- Handle nil as special symbol - it resolves to the nil literal rather than 1088 + -- being unmangled. Alternatively, we could remove it from the lua keywords table. 1089 + if ast[1] == 'nil' then 1090 + e = expr('nil', 'literal') 1091 + else 1092 + e = symbolToExpression(ast, scope, true) 1093 + end 1094 + exprs = handleCompileOpts({e}, parent, opts, ast) 1095 + elseif type(ast) == 'nil' or type(ast) == 'boolean' then 1096 + exprs = handleCompileOpts({expr(tostring(ast), 'literal')}, parent, opts) 1097 + elseif type(ast) == 'number' then 1098 + local n = ('%.17g'):format(ast) 1099 + exprs = handleCompileOpts({expr(n, 'literal')}, parent, opts) 1100 + elseif type(ast) == 'string' then 1101 + local s = serializeString(ast) 1102 + exprs = handleCompileOpts({expr(s, 'literal')}, parent, opts) 1103 + elseif type(ast) == 'table' then 1104 + local buffer = {} 1105 + for i = 1, #ast do -- Write numeric keyed values. 1106 + local nval = i ~= #ast and 1 1107 + buffer[#buffer + 1] = exprs1(compile1(ast[i], scope, parent, {nval = nval})) 1108 + end 1109 + local function writeOtherValues(k) 1110 + if type(k) ~= 'number' or math.floor(k) ~= k or k < 1 or k > #ast then 1111 + if type(k) == 'string' and isValidLuaIdentifier(k) then 1112 + return {k, k} 1113 + else 1114 + local kstr = '[' .. tostring(compile1(k, scope, parent, 1115 + {nval = 1})[1]) .. ']' 1116 + return { kstr, k } 1117 + end 1118 + end 1119 + end 1120 + local keys = kvmap(ast, writeOtherValues) 1121 + table.sort(keys, function (a, b) return a[1] < b[1] end) 1122 + map(keys, function(k) 1123 + local v = tostring(compile1(ast[k[2]], scope, parent, {nval = 1})[1]) 1124 + return ('%s = %s'):format(k[1], v) end, 1125 + buffer) 1126 + local tbl = '{' .. table.concat(buffer, ', ') ..'}' 1127 + exprs = handleCompileOpts({expr(tbl, 'expression')}, parent, opts, ast) 1128 + else 1129 + assertCompile(false, 'could not compile value of type ' .. type(ast), ast) 1130 + end 1131 + exprs.returned = true 1132 + return exprs 1133 +end 1134 + 1135 +-- SPECIALS -- 1136 + 1137 +-- For statements and expressions, put the value in a local to avoid 1138 +-- double-evaluating it. 1139 +local function once(val, ast, scope, parent) 1140 + if val.type == 'statement' or val.type == 'expression' then 1141 + local s = gensym(scope) 1142 + emit(parent, ('local %s = %s'):format(s, tostring(val)), ast) 1143 + return expr(s, 'sym') 1144 + else 1145 + return val 1146 + end 1147 +end 1148 + 1149 +-- Implements destructuring for forms like let, bindings, etc. 1150 +-- Takes a number of options to control behavior. 1151 +-- var: Whether or not to mark symbols as mutable 1152 +-- declaration: begin each assignment with 'local' in output 1153 +-- nomulti: disallow multisyms in the destructuring. Used for (local) and (global). 1154 +-- noundef: Don't set undefined bindings. (set) 1155 +-- forceglobal: Don't allow local bindings 1156 +local function destructure(to, from, ast, scope, parent, opts) 1157 + opts = opts or {} 1158 + local isvar = opts.isvar 1159 + local declaration = opts.declaration 1160 + local nomulti = opts.nomulti 1161 + local noundef = opts.noundef 1162 + local forceglobal = opts.forceglobal 1163 + local forceset = opts.forceset 1164 + local setter = declaration and "local %s = %s" or "%s = %s" 1165 + 1166 + local newManglings = {} 1167 + 1168 + -- Get Lua source for symbol, and check for errors 1169 + local function getname(symbol, up1) 1170 + local raw = symbol[1] 1171 + assertCompile(not (nomulti and isMultiSym(raw)), 1172 + 'did not expect multisym', up1) 1173 + if declaration then 1174 + return declareLocal(symbol, {var = isvar}, scope, symbol, newManglings) 1175 + else 1176 + local parts = isMultiSym(raw) or {raw} 1177 + local meta = scope.symmeta[parts[1]] 1178 + if #parts == 1 and not forceset then 1179 + assertCompile(not(forceglobal and meta), 1180 + 'expected global, found var', up1) 1181 + assertCompile(meta or not noundef, 1182 + 'expected local var ' .. parts[1], up1) 1183 + assertCompile(not (meta and not meta.var), 1184 + 'expected local var', up1) 1185 + end 1186 + if forceglobal then 1187 + assertCompile(not scope.symmeta[scope.unmanglings[raw]], 1188 + "global " .. raw .. " conflicts with local", ast) 1189 + scope.manglings[raw] = globalMangling(raw) 1190 + scope.unmanglings[globalMangling(raw)] = raw 1191 + if allowedGlobals then 1192 + table.insert(allowedGlobals, raw) 1193 + end 1194 + end 1195 + 1196 + return symbolToExpression(symbol, scope)[1] 1197 + end 1198 + end 1199 + 1200 + -- Compile the outer most form. We can generate better Lua in this case. 1201 + local function compileTopTarget(lvalues) 1202 + -- Calculate initial rvalue 1203 + local inits = map(lvalues, function(x) 1204 + return scope.manglings[x] and x or 'nil' end) 1205 + local init = table.concat(inits, ', ') 1206 + local lvalue = table.concat(lvalues, ', ') 1207 + 1208 + local plen = #parent 1209 + local ret = compile1(from, scope, parent, {target = lvalue}) 1210 + if declaration then 1211 + if #parent == plen + 1 and parent[#parent].leaf then 1212 + -- A single leaf emitted means an simple assignment a = x was emitted 1213 + parent[#parent].leaf = 'local ' .. parent[#parent].leaf 1214 + else 1215 + table.insert(parent, plen + 1, { leaf = 'local ' .. lvalue .. 1216 + ' = ' .. init, ast = ast}) 1217 + end 1218 + end 1219 + return ret 1220 + end 1221 + 1222 + -- Recursive auxiliary function 1223 + local function destructure1(left, rightexprs, up1, top) 1224 + if isSym(left) and left[1] ~= "nil" then 1225 + checkBindingValid(left, scope, left) 1226 + local lname = getname(left, up1) 1227 + if top then 1228 + compileTopTarget({lname}) 1229 + else 1230 + emit(parent, setter:format(lname, exprs1(rightexprs)), left) 1231 + end 1232 + elseif isTable(left) then -- table destructuring 1233 + if top then rightexprs = compile1(from, scope, parent) end 1234 + local s = gensym(scope) 1235 + emit(parent, ("local %s = %s"):format(s, exprs1(rightexprs)), left) 1236 + for k, v in pairs(left) do 1237 + if isSym(left[k]) and left[k][1] == "&" then 1238 + assertCompile(type(k) == "number" and not left[k+2], 1239 + "expected rest argument in final position", left) 1240 + local subexpr = expr(('{(table.unpack or unpack)(%s, %s)}'):format(s, k), 1241 + 'expression') 1242 + destructure1(left[k+1], {subexpr}, left) 1243 + return 1244 + else 1245 + if isSym(k) and tostring(k) == ":" and isSym(v) then k = tostring(v) end 1246 + if type(k) ~= "number" then k = serializeString(k) end 1247 + local subexpr = expr(('%s[%s]'):format(s, k), 'expression') 1248 + destructure1(v, {subexpr}, left) 1249 + end 1250 + end 1251 + elseif isList(left) then -- values destructuring 1252 + local leftNames, tables = {}, {} 1253 + for i, name in ipairs(left) do 1254 + local symname 1255 + if isSym(name) then -- binding directly to a name 1256 + symname = getname(name, up1) 1257 + else -- further destructuring of tables inside values 1258 + symname = gensym(scope) 1259 + tables[i] = {name, expr(symname, 'sym')} 1260 + end 1261 + table.insert(leftNames, symname) 1262 + end 1263 + if top then 1264 + compileTopTarget(leftNames) 1265 + else 1266 + local lvalue = table.concat(leftNames, ', ') 1267 + emit(parent, setter:format(lvalue, exprs1(rightexprs)), left) 1268 + end 1269 + for _, pair in pairs(tables) do -- recurse if left-side tables found 1270 + destructure1(pair[1], {pair[2]}, left) 1271 + end 1272 + else 1273 + assertCompile(false, 'unable to bind ' .. tostring(left), up1) 1274 + end 1275 + if top then return {returned = true} end 1276 + end 1277 + 1278 + local ret = destructure1(to, nil, ast, true) 1279 + applyManglings(scope, newManglings, ast) 1280 + return ret 1281 +end 1282 + 1283 +-- Unlike most expressions and specials, 'values' resolves with multiple 1284 +-- values, one for each argument, allowing multiple return values. The last 1285 +-- expression can return multiple arguments as well, allowing for more than the number 1286 +-- of expected arguments. 1287 +local function values(ast, scope, parent) 1288 + local len = #ast 1289 + local exprs = {} 1290 + for i = 2, len do 1291 + local subexprs = compile1(ast[i], scope, parent, { 1292 + nval = (i ~= len) and 1 1293 + }) 1294 + exprs[#exprs + 1] = subexprs[1] 1295 + if i == len then 1296 + for j = 2, #subexprs do 1297 + exprs[#exprs + 1] = subexprs[j] 1298 + end 1299 + end 1300 + end 1301 + return exprs 1302 +end 1303 + 1304 +-- Compile a list of forms for side effects 1305 +local function compileDo(ast, scope, parent, start) 1306 + start = start or 2 1307 + local len = #ast 1308 + local subScope = makeScope(scope) 1309 + for i = start, len do 1310 + compile1(ast[i], subScope, parent, { 1311 + nval = 0 1312 + }) 1313 + end 1314 +end 1315 + 1316 +-- Raises compile error if unused locals are found and we're checking for them. 1317 +local function checkUnused(scope, ast) 1318 + if not rootOptions.checkUnusedLocals then return end 1319 + for symName in pairs(scope.symmeta) do 1320 + assertCompile(scope.symmeta[symName].used or symName:find("^_"), 1321 + ("unused local %s"):format(symName), ast) 1322 + end 1323 +end 1324 + 1325 +-- Implements a do statement, starting at the 'start' element. By default, start is 2. 1326 +local function doImpl(ast, scope, parent, opts, start, chunk, subScope) 1327 + start = start or 2 1328 + subScope = subScope or makeScope(scope) 1329 + chunk = chunk or {} 1330 + local len = #ast 1331 + local outerTarget = opts.target 1332 + local outerTail = opts.tail 1333 + local retexprs = {returned = true} 1334 + 1335 + -- See if we need special handling to get the return values 1336 + -- of the do block 1337 + if not outerTarget and opts.nval ~= 0 and not outerTail then 1338 + if opts.nval then 1339 + -- Generate a local target 1340 + local syms = {} 1341 + for i = 1, opts.nval do 1342 + local s = gensym(scope) 1343 + syms[i] = s 1344 + retexprs[i] = expr(s, 'sym') 1345 + end 1346 + outerTarget = table.concat(syms, ', ') 1347 + emit(parent, ('local %s'):format(outerTarget), ast) 1348 + emit(parent, 'do', ast) 1349 + else 1350 + -- We will use an IIFE for the do 1351 + local fname = gensym(scope) 1352 + local fargs = scope.vararg and '...' or '' 1353 + emit(parent, ('local function %s(%s)'):format(fname, fargs), ast) 1354 + retexprs = expr(fname .. '(' .. fargs .. ')', 'statement') 1355 + outerTail = true 1356 + outerTarget = nil 1357 + end 1358 + else 1359 + emit(parent, 'do', ast) 1360 + end 1361 + -- Compile the body 1362 + if start > len then 1363 + -- In the unlikely case we do a do with no arguments. 1364 + compile1(nil, subScope, chunk, { 1365 + tail = outerTail, 1366 + target = outerTarget 1367 + }) 1368 + -- There will be no side effects 1369 + else 1370 + for i = start, len do 1371 + local subopts = { 1372 + nval = i ~= len and 0 or opts.nval, 1373 + tail = i == len and outerTail or nil, 1374 + target = i == len and outerTarget or nil 1375 + } 1376 + local subexprs = compile1(ast[i], subScope, chunk, subopts) 1377 + if i ~= len then 1378 + keepSideEffects(subexprs, parent, nil, ast[i]) 1379 + end 1380 + end 1381 + end 1382 + emit(parent, chunk, ast) 1383 + emit(parent, 'end', ast) 1384 + checkUnused(subScope, ast) 1385 + return retexprs 1386 +end 1387 + 1388 +SPECIALS["do"] = doImpl 1389 +docSpecial("do", {"..."}, "Evaluate multiple forms; return last value.") 1390 + 1391 +SPECIALS["values"] = values 1392 +docSpecial("values", {"..."}, 1393 + "Return multiple values from a function. Must be in tail position.") 1394 + 1395 +-- The fn special declares a function. Syntax is similar to other lisps; 1396 +-- (fn optional-name [arg ...] (body)) 1397 +-- Further decoration such as docstrings, meta info, and multibody functions a possibility. 1398 +SPECIALS["fn"] = function(ast, scope, parent) 1399 + local fScope = makeScope(scope) 1400 + local fChunk = {} 1401 + local index = 2 1402 + local fnName = isSym(ast[index]) 1403 + local isLocalFn 1404 + local docstring 1405 + fScope.vararg = false 1406 + local multi = fnName and isMultiSym(fnName[1]) 1407 + assertCompile(not multi or not multi.multiSymMethodCall, 1408 + "malformed function name: " .. tostring(fnName), ast) 1409 + if fnName and fnName[1] ~= 'nil' then 1410 + isLocalFn = not multi 1411 + if isLocalFn then 1412 + fnName = declareLocal(fnName, {}, scope, ast) 1413 + else 1414 + fnName = symbolToExpression(fnName, scope)[1] 1415 + end 1416 + index = index + 1 1417 + else 1418 + isLocalFn = true 1419 + fnName = gensym(scope) 1420 + end 1421 + local argList = assertCompile(isTable(ast[index]), 1422 + "expected vector arg list [a b ...]", ast) 1423 + local function getArgName(i, name) 1424 + if isVarg(name) then 1425 + assertCompile(i == #argList, "expected vararg as last parameter", ast) 1426 + fScope.vararg = true 1427 + return "..." 1428 + elseif(isSym(name) and deref(name) ~= "nil" 1429 + and not isMultiSym(deref(name))) then 1430 + return declareLocal(name, {}, fScope, ast) 1431 + elseif isTable(name) then 1432 + local raw = sym(gensym(scope)) 1433 + local declared = declareLocal(raw, {}, fScope, ast) 1434 + destructure(name, raw, ast, fScope, fChunk, 1435 + { declaration = true, nomulti = true }) 1436 + return declared 1437 + else 1438 + assertCompile(false, "expected symbol for function parameter", ast) 1439 + end 1440 + end 1441 + local argNameList = kvmap(argList, getArgName) 1442 + if type(ast[index + 1]) == 'string' and index + 1 < #ast then 1443 + index = index + 1 1444 + docstring = ast[index] 1445 + end 1446 + for i = index + 1, #ast do 1447 + compile1(ast[i], fScope, fChunk, { 1448 + tail = i == #ast, 1449 + nval = i ~= #ast and 0 or nil, 1450 + }) 1451 + end 1452 + if isLocalFn then 1453 + emit(parent, ('local function %s(%s)') 1454 + :format(fnName, table.concat(argNameList, ', ')), ast) 1455 + else 1456 + emit(parent, ('%s = function(%s)') 1457 + :format(fnName, table.concat(argNameList, ', ')), ast) 1458 + end 1459 + 1460 + emit(parent, fChunk, ast) 1461 + emit(parent, 'end', ast) 1462 + 1463 + if rootOptions.useMetadata then 1464 + local args = map(argList, function(v) 1465 + -- TODO: show destructured args properly instead of replacing 1466 + return isTable(v) and '"#<table>"' or string.format('"%s"', tostring(v)) 1467 + end) 1468 + 1469 + local metaFields = { 1470 + '"fnl/arglist"', '{' .. table.concat(args, ', ') .. '}', 1471 + } 1472 + if docstring then 1473 + table.insert(metaFields, '"fnl/docstring"') 1474 + table.insert(metaFields, '"' .. docstring:gsub('%s+$', '') 1475 + :gsub('\\', '\\\\'):gsub('\n', '\\n') 1476 + :gsub('"', '\\"') .. '"') 1477 + end 1478 + local metaStr = ('require("%s").metadata'):format(rootOptions.moduleName or "fennel") 1479 + emit(parent, string.format('pcall(function() %s:setall(%s, %s) end)', 1480 + metaStr, fnName, table.concat(metaFields, ', '))) 1481 + end 1482 + 1483 + checkUnused(fScope, ast) 1484 + return expr(fnName, 'sym') 1485 +end 1486 +docSpecial("fn", {"name?", "args", "docstring?", "..."}, 1487 + "Function syntax. May optionally include a name and docstring." 1488 + .."\nIf a name is provided, the function will be bound in the current scope." 1489 + .."\nWhen called with the wrong number of args, excess args will be discarded" 1490 + .."\nand lacking args will be nil, use lambda for arity-checked functions.") 1491 + 1492 +-- (lua "print('hello!')") -> prints hello, evaluates to nil 1493 +-- (lua "print 'hello!'" "10") -> prints hello, evaluates to the number 10 1494 +-- (lua nil "{1,2,3}") -> Evaluates to a table literal 1495 +SPECIALS['lua'] = function(ast, _, parent) 1496 + assertCompile(#ast == 2 or #ast == 3, 1497 + "expected 2 or 3 arguments in 'lua' special form", ast) 1498 + if ast[2] ~= nil then 1499 + table.insert(parent, {leaf = tostring(ast[2]), ast = ast}) 1500 + end 1501 + if #ast == 3 then 1502 + return tostring(ast[3]) 1503 + end 1504 +end 1505 + 1506 +SPECIALS['doc'] = function(ast, scope, parent) 1507 + assert(rootOptions.useMetadata, "can't look up doc with metadata disabled.") 1508 + assertCompile(#ast == 2, "expected one argument", ast) 1509 + 1510 + local target = deref(ast[2]) 1511 + local special = scope.specials[target] 1512 + if special then 1513 + return ("print([[%s]])"):format(doc(special, target)) 1514 + else 1515 + local value = tostring(compile1(ast[2], scope, parent, {nval = 1})[1]) 1516 + -- need to require here since the metadata is stored in the module 1517 + -- and we need to make sure we look it up in the same module it was 1518 + -- declared from. 1519 + return ("print(require('%s').doc(%s, '%s'))") 1520 + :format(rootOptions.moduleName or "fennel", value, tostring(ast[2])) 1521 + end 1522 +end 1523 +docSpecial("doc", {"x"}, 1524 + "Print the docstring and arglist for a function, macro, or special form.") 1525 + 1526 +-- Table lookup 1527 +SPECIALS["."] = function(ast, scope, parent) 1528 + local len = #ast 1529 + assertCompile(len > 1, "expected table argument", ast) 1530 + local lhs = compile1(ast[2], scope, parent, {nval = 1}) 1531 + if len == 2 then 1532 + return tostring(lhs[1]) 1533 + else 1534 + local indices = {} 1535 + for i = 3, len do 1536 + local index = ast[i] 1537 + if type(index) == 'string' and isValidLuaIdentifier(index) then 1538 + table.insert(indices, '.' .. index) 1539 + else 1540 + index = compile1(index, scope, parent, {nval = 1})[1] 1541 + table.insert(indices, '[' .. tostring(index) .. ']') 1542 + end 1543 + end 1544 + -- extra parens are needed for table literals 1545 + if isTable(ast[2]) then 1546 + return '(' .. tostring(lhs[1]) .. ')' .. table.concat(indices) 1547 + else 1548 + return tostring(lhs[1]) .. table.concat(indices) 1549 + end 1550 + end 1551 +end 1552 +docSpecial(".", {"tbl", "key1", "..."}, 1553 + "Look up key1 in tbl table. If more args are provided, do a nested lookup.") 1554 + 1555 +SPECIALS["global"] = function(ast, scope, parent) 1556 + assertCompile(#ast == 3, "expected name and value", ast) 1557 + destructure(ast[2], ast[3], ast, scope, parent, { 1558 + nomulti = true, 1559 + forceglobal = true 1560 + }) 1561 +end 1562 +docSpecial("global", {"name", "val"}, "Set name as a global with val.") 1563 + 1564 +SPECIALS["set"] = function(ast, scope, parent) 1565 + assertCompile(#ast == 3, "expected name and value", ast) 1566 + destructure(ast[2], ast[3], ast, scope, parent, { 1567 + noundef = true 1568 + }) 1569 +end 1570 +docSpecial("set", {"name", "val"}, 1571 + "Set a local variable to a new value. Only works on locals using var.") 1572 + 1573 +SPECIALS["set-forcibly!"] = function(ast, scope, parent) 1574 + assertCompile(#ast == 3, "expected name and value", ast) 1575 + destructure(ast[2], ast[3], ast, scope, parent, { 1576 + forceset = true 1577 + }) 1578 +end 1579 + 1580 +SPECIALS["local"] = function(ast, scope, parent) 1581 + assertCompile(#ast == 3, "expected name and value", ast) 1582 + destructure(ast[2], ast[3], ast, scope, parent, { 1583 + declaration = true, 1584 + nomulti = true 1585 + }) 1586 +end 1587 +docSpecial("local", {"name", "val"}, 1588 + "Introduce new top-level immutable local.") 1589 + 1590 +SPECIALS["var"] = function(ast, scope, parent) 1591 + assertCompile(#ast == 3, "expected name and value", ast) 1592 + destructure(ast[2], ast[3], ast, scope, parent, { 1593 + declaration = true, 1594 + nomulti = true, 1595 + isvar = true 1596 + }) 1597 +end 1598 +docSpecial("var", {"name", "val"}, 1599 + "Introduce new mutable local.") 1600 + 1601 +SPECIALS["let"] = function(ast, scope, parent, opts) 1602 + local bindings = ast[2] 1603 + assertCompile(isList(bindings) or isTable(bindings), 1604 + "expected table for destructuring", ast) 1605 + assertCompile(#bindings % 2 == 0, 1606 + "expected even number of name/value bindings", ast) 1607 + assertCompile(#ast >= 3, "missing body expression", ast) 1608 + local subScope = makeScope(scope) 1609 + local subChunk = {} 1610 + for i = 1, #bindings, 2 do 1611 + destructure(bindings[i], bindings[i + 1], ast, subScope, subChunk, { 1612 + declaration = true, 1613 + nomulti = true 1614 + }) 1615 + end 1616 + return doImpl(ast, scope, parent, opts, 3, subChunk, subScope) 1617 +end 1618 +docSpecial("let", {"[name1 val1 ... nameN valN]", "..."}, 1619 + "Introduces a new scope in which a given set of local bindings are used.") 1620 + 1621 +-- For setting items in a table 1622 +SPECIALS["tset"] = function(ast, scope, parent) 1623 + assertCompile(#ast > 3, ("tset form needs table, key, and value"), ast) 1624 + local root = compile1(ast[2], scope, parent, {nval = 1})[1] 1625 + local keys = {} 1626 + for i = 3, #ast - 1 do 1627 + local key = compile1(ast[i], scope, parent, {nval = 1})[1] 1628 + keys[#keys + 1] = tostring(key) 1629 + end 1630 + local value = compile1(ast[#ast], scope, parent, {nval = 1})[1] 1631 + local rootstr = tostring(root) 1632 + -- Prefix 'do end ' so parens are not ambiguous (grouping or function call?) 1633 + local fmtstr = (rootstr:match("^{")) and "do end (%s)[%s] = %s" or "%s[%s] = %s" 1634 + emit(parent, fmtstr:format(tostring(root), 1635 + table.concat(keys, ']['), 1636 + tostring(value)), ast) 1637 +end 1638 +docSpecial("tset", {"tbl", "key1", "...", "keyN", "val"}, 1639 + "Set the value of a table field. Can take additional keys to set" 1640 + .. "nested values,\nbut all parents must contain an existing table.") 1641 + 1642 +-- The if special form behaves like the cond form in 1643 +-- many languages 1644 +SPECIALS["if"] = function(ast, scope, parent, opts) 1645 + local doScope = makeScope(scope) 1646 + local branches = {} 1647 + local elseBranch = nil 1648 + 1649 + -- Calculate some external stuff. Optimizes for tail calls and what not 1650 + local wrapper, innerTail, innerTarget, targetExprs 1651 + if opts.tail or opts.target or opts.nval then 1652 + if opts.nval and opts.nval ~= 0 and not opts.target then 1653 + -- We need to create a target 1654 + targetExprs = {} 1655 + local accum = {} 1656 + for i = 1, opts.nval do 1657 + local s = gensym(scope) 1658 + accum[i] = s 1659 + targetExprs[i] = expr(s, 'sym') 1660 + end 1661 + wrapper = 'target' 1662 + innerTail = opts.tail 1663 + innerTarget = table.concat(accum, ', ') 1664 + else 1665 + wrapper = 'none' 1666 + innerTail = opts.tail 1667 + innerTarget = opts.target 1668 + end 1669 + else 1670 + wrapper = 'iife' 1671 + innerTail = true 1672 + innerTarget = nil 1673 + end 1674 + 1675 + -- Compile bodies and conditions 1676 + local bodyOpts = { 1677 + tail = innerTail, 1678 + target = innerTarget, 1679 + nval = opts.nval 1680 + } 1681 + local function compileBody(i) 1682 + local chunk = {} 1683 + local cscope = makeScope(doScope) 1684 + keepSideEffects(compile1(ast[i], cscope, chunk, bodyOpts), 1685 + chunk, nil, ast[i]) 1686 + return { 1687 + chunk = chunk, 1688 + scope = cscope 1689 + } 1690 + end 1691 + for i = 2, #ast - 1, 2 do 1692 + local condchunk = {} 1693 + local res = compile1(ast[i], doScope, condchunk, {nval = 1}) 1694 + local cond = res[1] 1695 + local branch = compileBody(i + 1) 1696 + branch.cond = cond 1697 + branch.condchunk = condchunk 1698 + branch.nested = i ~= 2 and next(condchunk, nil) == nil 1699 + table.insert(branches, branch) 1700 + end 1701 + local hasElse = #ast > 3 and #ast % 2 == 0 1702 + if hasElse then elseBranch = compileBody(#ast) end 1703 + 1704 + -- Emit code 1705 + local s = gensym(scope) 1706 + local buffer = {} 1707 + local lastBuffer = buffer 1708 + for i = 1, #branches do 1709 + local branch = branches[i] 1710 + local fstr = not branch.nested and 'if %s then' or 'elseif %s then' 1711 + local cond = tostring(branch.cond) 1712 + local condLine = (cond == "true" and branch.nested and i == #branches) 1713 + and "else" 1714 + or fstr:format(cond) 1715 + if branch.nested then 1716 + emit(lastBuffer, branch.condchunk, ast) 1717 + else 1718 + for _, v in ipairs(branch.condchunk) do emit(lastBuffer, v, ast) end 1719 + end 1720 + emit(lastBuffer, condLine, ast) 1721 + emit(lastBuffer, branch.chunk, ast) 1722 + if i == #branches then 1723 + if hasElse then 1724 + emit(lastBuffer, 'else', ast) 1725 + emit(lastBuffer, elseBranch.chunk, ast) 1726 + -- TODO: Consolidate use of condLine ~= "else" with hasElse 1727 + elseif(innerTarget and condLine ~= 'else') then 1728 + emit(lastBuffer, 'else', ast) 1729 + emit(lastBuffer, ("%s = nil"):format(innerTarget), ast) 1730 + end 1731 + emit(lastBuffer, 'end', ast) 1732 + elseif not branches[i + 1].nested then 1733 + emit(lastBuffer, 'else', ast) 1734 + local nextBuffer = {} 1735 + emit(lastBuffer, nextBuffer, ast) 1736 + emit(lastBuffer, 'end', ast) 1737 + lastBuffer = nextBuffer 1738 + end 1739 + end 1740 + 1741 + if wrapper == 'iife' then 1742 + local iifeargs = scope.vararg and '...' or '' 1743 + emit(parent, ('local function %s(%s)'):format(tostring(s), iifeargs), ast) 1744 + emit(parent, buffer, ast) 1745 + emit(parent, 'end', ast) 1746 + return expr(('%s(%s)'):format(tostring(s), iifeargs), 'statement') 1747 + elseif wrapper == 'none' then 1748 + -- Splice result right into code 1749 + for i = 1, #buffer do 1750 + emit(parent, buffer[i], ast) 1751 + end 1752 + return {returned = true} 1753 + else -- wrapper == 'target' 1754 + emit(parent, ('local %s'):format(innerTarget), ast) 1755 + for i = 1, #buffer do 1756 + emit(parent, buffer[i], ast) 1757 + end 1758 + return targetExprs 1759 + end 1760 +end 1761 +docSpecial("if", {"cond1", "body1", "...", "condN", "bodyN"}, 1762 + "Conditional form.\n" .. 1763 + "Takes any number of condition/body pairs and evaluates the first body where" 1764 + .. "\nthe condition evaluates to truthy. Similar to cond in other lisps.") 1765 + 1766 +-- (each [k v (pairs t)] body...) => [] 1767 +SPECIALS["each"] = function(ast, scope, parent) 1768 + local binding = assertCompile(isTable(ast[2]), "expected binding table", ast) 1769 + local iter = table.remove(binding, #binding) -- last item is iterator call 1770 + local destructures = {} 1771 + local newManglings = {} 1772 + local function destructureBinding(v) 1773 + assertCompile(isSym(v) or isTable(v), "expected iterator symbol or table", ast) 1774 + if isSym(v) then 1775 + return declareLocal(v, {}, scope, ast, newManglings) 1776 + else 1777 + local raw = sym(gensym(scope)) 1778 + destructures[raw] = v 1779 + return declareLocal(raw, {}, scope, ast) 1780 + end 1781 + end 1782 + local bindVars = map(binding, destructureBinding) 1783 + local vals = compile1(iter, scope, parent) 1784 + local valNames = map(vals, tostring) 1785 + 1786 + emit(parent, ('for %s in %s do'):format(table.concat(bindVars, ', '), 1787 + table.concat(valNames, ", ")), ast) 1788 + local chunk = {} 1789 + for raw, args in pairs(destructures) do 1790 + destructure(args, raw, ast, scope, chunk, 1791 + { declaration = true, nomulti = true }) 1792 + end 1793 + applyManglings(scope, newManglings, ast) 1794 + compileDo(ast, scope, chunk, 3) 1795 + emit(parent, chunk, ast) 1796 + emit(parent, 'end', ast) 1797 +end 1798 +docSpecial("each", {"[key value (iterator)]", "..."}, 1799 + "Runs the body once for each set of values provided by the given iterator." 1800 + .."\nMost commonly used with ipairs for sequential tables or pairs for" 1801 + .." undefined\norder, but can be used with any iterator.") 1802 + 1803 +-- (while condition body...) => [] 1804 +SPECIALS["while"] = function(ast, scope, parent) 1805 + local len1 = #parent 1806 + local condition = compile1(ast[2], scope, parent, {nval = 1})[1] 1807 + local len2 = #parent 1808 + local subChunk = {} 1809 + if len1 ~= len2 then 1810 + -- Compound condition 1811 + -- Move new compilation to subchunk 1812 + for i = len1 + 1, len2 do 1813 + subChunk[#subChunk + 1] = parent[i] 1814 + parent[i] = nil 1815 + end 1816 + emit(parent, 'while true do', ast) 1817 + emit(subChunk, ('if not %s then break end'):format(condition[1]), ast) 1818 + else 1819 + -- Simple condition 1820 + emit(parent, 'while ' .. tostring(condition) .. ' do', ast) 1821 + end 1822 + compileDo(ast, makeScope(scope), subChunk, 3) 1823 + emit(parent, subChunk, ast) 1824 + emit(parent, 'end', ast) 1825 +end 1826 +docSpecial("while", {"condition", "..."}, 1827 + "The classic while loop. Evaluates body until a condition is non-truthy.") 1828 + 1829 +SPECIALS["for"] = function(ast, scope, parent) 1830 + local ranges = assertCompile(isTable(ast[2]), "expected binding table", ast) 1831 + local bindingSym = assertCompile(isSym(table.remove(ast[2], 1)), 1832 + "expected iterator symbol", ast) 1833 + local rangeArgs = {} 1834 + for i = 1, math.min(#ranges, 3) do 1835 + rangeArgs[i] = tostring(compile1(ranges[i], scope, parent, {nval = 1})[1]) 1836 + end 1837 + emit(parent, ('for %s = %s do'):format( 1838 + declareLocal(bindingSym, {}, scope, ast), 1839 + table.concat(rangeArgs, ', ')), ast) 1840 + local chunk = {} 1841 + compileDo(ast, scope, chunk, 3) 1842 + emit(parent, chunk, ast) 1843 + emit(parent, 'end', ast) 1844 +end 1845 +docSpecial("for", {"[index start stop step?]", "..."}, "Numeric loop construct." .. 1846 + "\nEvaluates body once for each value between start and stop (inclusive).") 1847 + 1848 +SPECIALS[":"] = function(ast, scope, parent) 1849 + assertCompile(#ast >= 3, "expected at least 3 arguments", ast) 1850 + -- Compile object 1851 + local objectexpr = compile1(ast[2], scope, parent, {nval = 1})[1] 1852 + -- Compile method selector 1853 + local methodstring 1854 + local methodident = false 1855 + if type(ast[3]) == 'string' and isValidLuaIdentifier(ast[3]) then 1856 + methodident = true 1857 + methodstring = ast[3] 1858 + else 1859 + methodstring = tostring(compile1(ast[3], scope, parent, {nval = 1})[1]) 1860 + objectexpr = once(objectexpr, ast[2], scope, parent) 1861 + end 1862 + -- Compile arguments 1863 + local args = {} 1864 + for i = 4, #ast do 1865 + local subexprs = compile1(ast[i], scope, parent, { 1866 + nval = i ~= #ast and 1 or nil 1867 + }) 1868 + map(subexprs, tostring, args) 1869 + end 1870 + local fstring 1871 + if not methodident then 1872 + -- Make object first argument 1873 + table.insert(args, 1, tostring(objectexpr)) 1874 + fstring = objectexpr.type == 'sym' 1875 + and '%s[%s](%s)' 1876 + or '(%s)[%s](%s)' 1877 + elseif(objectexpr.type == 'literal' or objectexpr.type == 'expression') then 1878 + fstring = '(%s):%s(%s)' 1879 + else 1880 + fstring = '%s:%s(%s)' 1881 + end 1882 + return expr(fstring:format( 1883 + tostring(objectexpr), 1884 + methodstring, 1885 + table.concat(args, ', ')), 'statement') 1886 +end 1887 +docSpecial(":", {"tbl", "method-name", "..."}, 1888 + "Call the named method on tbl with the provided args.".. 1889 + "\nMethod name doesn\"t have to be known at compile-time; if it is, use" 1890 + .."\n(tbl:method-name ...) instead.") 1891 + 1892 +SPECIALS["comment"] = function(ast, _, parent) 1893 + local els = {} 1894 + for i = 2, #ast do 1895 + els[#els + 1] = tostring(ast[i]):gsub('\n', ' ') 1896 + end 1897 + emit(parent, ' -- ' .. table.concat(els, ' '), ast) 1898 +end 1899 +docSpecial("comment", {"..."}, "Comment which will be emitted in Lua output.") 1900 + 1901 +SPECIALS["hashfn"] = function(ast, scope, parent) 1902 + assertCompile(#ast == 2, "expected one argument", ast) 1903 + local fScope = makeScope(scope) 1904 + local fChunk = {} 1905 + local name = gensym(scope) 1906 + local symbol = sym(name) 1907 + declareLocal(symbol, {}, scope, ast) 1908 + fScope.vararg = false 1909 + fScope.hashfn = true 1910 + local args = {} 1911 + for i = 1, 9 do args[i] = declareLocal(sym('$' .. i), {}, fScope, ast) end 1912 + -- Compile body 1913 + compile1(ast[2], fScope, fChunk, {tail = true}) 1914 + local maxUsed = 0 1915 + for i = 1, 9 do if fScope.symmeta['$' .. i].used then maxUsed = i end end 1916 + local argStr = table.concat(args, ', ', 1, maxUsed) 1917 + emit(parent, ('local function %s(%s)'):format(name, argStr), ast) 1918 + emit(parent, fChunk, ast) 1919 + emit(parent, 'end', ast) 1920 + return expr(name, 'sym') 1921 +end 1922 +docSpecial("hashfn", {"..."}, "Function literal shorthand; args are $1, $2, etc.") 1923 + 1924 +local function defineArithmeticSpecial(name, zeroArity, unaryPrefix) 1925 + local paddedOp = ' ' .. name .. ' ' 1926 + SPECIALS[name] = function(ast, scope, parent) 1927 + local len = #ast 1928 + if len == 1 then 1929 + assertCompile(zeroArity ~= nil, 'Expected more than 0 arguments', ast) 1930 + return expr(zeroArity, 'literal') 1931 + else 1932 + local operands = {} 1933 + for i = 2, len do 1934 + local subexprs = compile1(ast[i], scope, parent, { 1935 + nval = (i == 1 and 1 or nil) 1936 + }) 1937 + map(subexprs, tostring, operands) 1938 + end 1939 + if #operands == 1 then 1940 + if unaryPrefix then 1941 + return '(' .. unaryPrefix .. paddedOp .. operands[1] .. ')' 1942 + else 1943 + return operands[1] 1944 + end 1945 + else 1946 + return '(' .. table.concat(operands, paddedOp) .. ')' 1947 + end 1948 + end 1949 + end 1950 + docSpecial(name, {"a", "b", "..."}, 1951 + "Arithmetic operator; works the same as Lua but accepts more arguments.") 1952 +end 1953 + 1954 +defineArithmeticSpecial('+', '0') 1955 +defineArithmeticSpecial('..', "''") 1956 +defineArithmeticSpecial('^') 1957 +defineArithmeticSpecial('-', nil, '') 1958 +defineArithmeticSpecial('*', '1') 1959 +defineArithmeticSpecial('%') 1960 +defineArithmeticSpecial('/', nil, '1') 1961 +defineArithmeticSpecial('//', nil, '1') 1962 +defineArithmeticSpecial('or', 'false') 1963 +defineArithmeticSpecial('and', 'true') 1964 + 1965 +docSpecial("and", {"a", "b", "..."}, 1966 + "Boolean operator; works the same as Lua but accepts more arguments.") 1967 +docSpecial("or", {"a", "b", "..."}, 1968 + "Boolean operator; works the same as Lua but accepts more arguments.") 1969 +docSpecial("..", {"a", "b", "..."}, 1970 + "String concatenation operator; works the same as Lua but accepts more arguments.") 1971 + 1972 +local function defineComparatorSpecial(name, realop, chainOp) 1973 + local op = realop or name 1974 + SPECIALS[name] = function(ast, scope, parent) 1975 + local len = #ast 1976 + assertCompile(len > 2, "expected at least two arguments", ast) 1977 + local lhs = compile1(ast[2], scope, parent, {nval = 1})[1] 1978 + local lastval = compile1(ast[3], scope, parent, {nval = 1})[1] 1979 + -- avoid double-eval by introducing locals for possible side-effects 1980 + if len > 3 then lastval = once(lastval, ast[3], scope, parent) end 1981 + local out = ('(%s %s %s)'): 1982 + format(tostring(lhs), op, tostring(lastval)) 1983 + if len > 3 then 1984 + for i = 4, len do -- variadic comparison 1985 + local nextval = once(compile1(ast[i], scope, parent, {nval = 1})[1], 1986 + ast[i], scope, parent) 1987 + out = (out .. " %s (%s %s %s)"): 1988 + format(chainOp or 'and', tostring(lastval), op, tostring(nextval)) 1989 + lastval = nextval 1990 + end 1991 + out = '(' .. out .. ')' 1992 + end 1993 + return out 1994 + end 1995 + docSpecial(name, {"a", "b", "..."}, 1996 + "Comparison operator; works the same as Lua but accepts more arguments.") 1997 +end 1998 + 1999 +defineComparatorSpecial('>') 2000 +defineComparatorSpecial('<') 2001 +defineComparatorSpecial('>=') 2002 +defineComparatorSpecial('<=') 2003 +defineComparatorSpecial('=', '==') 2004 +defineComparatorSpecial('not=', '~=', 'or') 2005 +SPECIALS["~="] = SPECIALS["not="] -- backwards-compatibility alias 2006 + 2007 +local function defineUnarySpecial(op, realop) 2008 + SPECIALS[op] = function(ast, scope, parent) 2009 + assertCompile(#ast == 2, 'expected one argument', ast) 2010 + local tail = compile1(ast[2], scope, parent, {nval = 1}) 2011 + return (realop or op) .. tostring(tail[1]) 2012 + end 2013 +end 2014 + 2015 +defineUnarySpecial("not", "not ") 2016 +docSpecial("not", {"x"}, "Logical operator; works the same as Lua.") 2017 + 2018 +defineUnarySpecial("length", "#") 2019 +docSpecial("length", {"x"}, "Returns the length of a table or string.") 2020 +SPECIALS["#"] = SPECIALS["length"] 2021 + 2022 +-- Save current macro scope 2023 +local macroCurrentScope = nil 2024 + 2025 +-- Covert a macro function to a special form 2026 +local function macroToSpecial(mac) 2027 + local special = function(ast, scope, parent, opts) 2028 + local oldScope = macroCurrentScope 2029 + macroCurrentScope = scope 2030 + local ok, transformed = pcall(mac, unpack(ast, 2)) 2031 + macroCurrentScope = oldScope 2032 + assertCompile(ok, transformed, ast) 2033 + local result = compile1(transformed, scope, parent, opts) 2034 + return result 2035 + end 2036 + if metadata[mac] then 2037 + -- copy metadata from original function to special form function 2038 + metadata[mac], metadata[special] = nil, metadata[mac] 2039 + end 2040 + return special 2041 +end 2042 + 2043 +local requireSpecial 2044 +local function compile(ast, options) 2045 + local opts = copy(options) 2046 + local oldGlobals = allowedGlobals 2047 + setResetRoot(rootChunk, rootScope, rootOptions) 2048 + allowedGlobals = opts.allowedGlobals 2049 + if opts.indent == nil then opts.indent = ' ' end 2050 + local chunk = {} 2051 + local scope = opts.scope or makeScope(GLOBAL_SCOPE) 2052 + rootChunk, rootScope, rootOptions = chunk, scope, opts 2053 + if opts.requireAsInclude then scope.specials.require = requireSpecial end 2054 + local exprs = compile1(ast, scope, chunk, {tail = true}) 2055 + keepSideEffects(exprs, chunk, nil, ast) 2056 + allowedGlobals = oldGlobals 2057 + resetRoot() 2058 + return flatten(chunk, opts) 2059 +end 2060 + 2061 +-- make a transformer for key / value table pairs, preserving all numeric keys 2062 +local function entryTransform(fk,fv) 2063 + return function(k, v) 2064 + if type(k) == 'number' then 2065 + return k,fv(v) 2066 + else 2067 + return fk(k),fv(v) 2068 + end 2069 + end 2070 +end 2071 + 2072 +-- consume everything return nothing 2073 +local function no() end 2074 + 2075 +local function mixedConcat(t, joiner) 2076 + local ret = "" 2077 + local s = "" 2078 + local seen = {} 2079 + for k,v in ipairs(t) do 2080 + table.insert(seen, k) 2081 + ret = ret .. s .. v 2082 + s = joiner 2083 + end 2084 + for k,v in pairs(t) do 2085 + if not(seen[k]) then 2086 + ret = ret .. s .. '[' .. k .. ']' .. '=' .. v 2087 + s = joiner 2088 + end 2089 + end 2090 + return ret 2091 +end 2092 + 2093 +-- expand a quoted form into a data literal, evaluating unquote 2094 +local function doQuote (form, scope, parent, runtime) 2095 + local q = function (x) return doQuote(x, scope, parent, runtime) end 2096 + -- vararg 2097 + if isVarg(form) then 2098 + assertCompile(not runtime, "quoted ... may only be used at compile time", form) 2099 + return "_VARARG" 2100 + -- symbol 2101 + elseif isSym(form) then 2102 + assertCompile(not runtime, "symbols may only be used at compile time", form) 2103 + if deref(form):find("#$") then -- autogensym 2104 + return ("sym('%s')"):format(autogensym(deref(form), scope)) 2105 + else -- prevent non-gensymmed symbols from being bound as an identifier 2106 + return ("sym('%s', nil, {quoted=true})"):format(deref(form)) 2107 + end 2108 + -- unquote 2109 + elseif isList(form) and isSym(form[1]) and (deref(form[1]) == 'unquote') then 2110 + local payload = form[2] 2111 + local res = unpack(compile1(payload, scope, parent)) 2112 + return res[1] 2113 + -- list 2114 + elseif isList(form) then 2115 + assertCompile(not runtime, "lists may only be used at compile time", form) 2116 + local mapped = kvmap(form, entryTransform(no, q)) 2117 + return 'list(' .. mixedConcat(mapped, ", ") .. ')' 2118 + -- table 2119 + elseif type(form) == 'table' then 2120 + local mapped = kvmap(form, entryTransform(q, q)) 2121 + return '{' .. mixedConcat(mapped, ", ") .. '}' 2122 + -- string 2123 + elseif type(form) == 'string' then 2124 + return serializeString(form) 2125 + else 2126 + return tostring(form) 2127 + end 2128 +end 2129 + 2130 +SPECIALS['quote'] = function(ast, scope, parent) 2131 + assertCompile(#ast == 2, "quote only takes a single form") 2132 + local runtime, thisScope = true, scope 2133 + while thisScope do 2134 + thisScope = thisScope.parent 2135 + if thisScope == COMPILER_SCOPE then runtime = false end 2136 + end 2137 + return doQuote(ast[2], scope, parent, runtime) 2138 +end 2139 +docSpecial('quote', {'x'}, 'Quasiquote the following form. Only works in macro/compiler scope.') 2140 + 2141 +local function compileStream(strm, options) 2142 + local opts = copy(options) 2143 + local oldGlobals = allowedGlobals 2144 + setResetRoot(rootChunk, rootScope, rootOptions) 2145 + allowedGlobals = opts.allowedGlobals 2146 + if opts.indent == nil then opts.indent = ' ' end 2147 + local scope = opts.scope or makeScope(GLOBAL_SCOPE) 2148 + if opts.requireAsInclude then scope.specials.require = requireSpecial end 2149 + local vals = {} 2150 + for ok, val in parser(strm, opts.filename) do 2151 + if not ok then break end 2152 + vals[#vals + 1] = val 2153 + end 2154 + local chunk = {} 2155 + rootChunk, rootScope, rootOptions = chunk, scope, opts 2156 + for i = 1, #vals do 2157 + local exprs = compile1(vals[i], scope, chunk, { 2158 + tail = i == #vals, 2159 + }) 2160 + keepSideEffects(exprs, chunk, nil, vals[i]) 2161 + end 2162 + allowedGlobals = oldGlobals 2163 + resetRoot() 2164 + return flatten(chunk, opts) 2165 +end 2166 + 2167 +local function compileString(str, options) 2168 + local strm = stringStream(str) 2169 + return compileStream(strm, options) 2170 +end 2171 + 2172 +--- 2173 +--- Evaluation 2174 +--- 2175 + 2176 +-- Convert a fennel environment table to a Lua environment table. 2177 +-- This means automatically unmangling globals when getting a value, 2178 +-- and mangling values when setting a value. This means the original 2179 +-- env will see its values updated as expected, regardless of mangling rules. 2180 +local function wrapEnv(env) 2181 + return setmetatable({}, { 2182 + __index = function(_, key) 2183 + if type(key) == 'string' then 2184 + key = globalUnmangling(key) 2185 + end 2186 + return env[key] 2187 + end, 2188 + __newindex = function(_, key, value) 2189 + if type(key) == 'string' then 2190 + key = globalMangling(key) 2191 + end 2192 + env[key] = value 2193 + end, 2194 + -- checking the __pairs metamethod won't work automatically in Lua 5.1 2195 + -- sadly, but it's important for 5.2+ and can be done manually in 5.1 2196 + __pairs = function() 2197 + local function putenv(k, v) 2198 + return type(k) == 'string' and globalUnmangling(k) or k, v 2199 + end 2200 + local pt = kvmap(env, putenv) 2201 + return next, pt, nil 2202 + end, 2203 + }) 2204 +end 2205 + 2206 +-- A custom traceback function for Fennel that looks similar to 2207 +-- the Lua's debug.traceback. 2208 +-- Use with xpcall to produce fennel specific stacktraces. 2209 +local function traceback(msg, start) 2210 + local level = start or 2 -- Can be used to skip some frames 2211 + local lines = {} 2212 + if msg then 2213 + if msg:find("^Compile error") then 2214 + table.insert(lines, msg) 2215 + else 2216 + local newmsg = msg:gsub('^[^:]*:%d+:%s+', 'runtime error: ') 2217 + table.insert(lines, newmsg) 2218 + end 2219 + end 2220 + table.insert(lines, 'stack traceback:') 2221 + while true do 2222 + local info = debug.getinfo(level, "Sln") 2223 + if not info then break end 2224 + local line 2225 + if info.what == "C" then 2226 + if info.name then 2227 + line = (' [C]: in function \'%s\''):format(info.name) 2228 + else 2229 + line = ' [C]: in ?' 2230 + end 2231 + else 2232 + local remap = fennelSourcemap[info.source] 2233 + if remap and remap[info.currentline] then 2234 + -- And some global info 2235 + info.short_src = remap.short_src 2236 + local mapping = remap[info.currentline] 2237 + -- Overwrite info with values from the mapping (mapping is now 2238 + -- just integer, but may eventually be a table) 2239 + info.currentline = mapping 2240 + end 2241 + if info.what == 'Lua' then 2242 + local n = info.name and ("'" .. info.name .. "'") or '?' 2243 + line = (' %s:%d: in function %s'):format(info.short_src, info.currentline, n) 2244 + elseif info.short_src == '(tail call)' then 2245 + line = ' (tail call)' 2246 + else 2247 + line = (' %s:%d: in main chunk'):format(info.short_src, info.currentline) 2248 + end 2249 + end 2250 + table.insert(lines, line) 2251 + level = level + 1 2252 + end 2253 + return table.concat(lines, '\n') 2254 +end 2255 + 2256 +local function currentGlobalNames(env) 2257 + return kvmap(env or _G, globalUnmangling) 2258 +end 2259 + 2260 +local function eval(str, options, ...) 2261 + local opts = copy(options) 2262 + -- eval and dofile are considered "live" entry points, so we can assume 2263 + -- that the globals available at compile time are a reasonable allowed list 2264 + -- UNLESS there's a metatable on env, in which case we can't assume that 2265 + -- pairs will return all the effective globals; for instance openresty 2266 + -- sets up _G in such a way that all the globals are available thru 2267 + -- the __index meta method, but as far as pairs is concerned it's empty. 2268 + if opts.allowedGlobals == nil and not getmetatable(opts.env) then 2269 + opts.allowedGlobals = currentGlobalNames(opts.env) 2270 + end 2271 + local env = opts.env and wrapEnv(opts.env) 2272 + local luaSource = compileString(str, opts) 2273 + local loader = loadCode(luaSource, env, 2274 + opts.filename and ('@' .. opts.filename) or str) 2275 + opts.filename = nil 2276 + return loader(...) 2277 +end 2278 + 2279 +local function dofileFennel(filename, options, ...) 2280 + local opts = copy(options) 2281 + if opts.allowedGlobals == nil then 2282 + opts.allowedGlobals = currentGlobalNames(opts.env) 2283 + end 2284 + local f = assert(io.open(filename, "rb")) 2285 + local source = f:read("*all"):gsub("^#![^\n]*\n", "") 2286 + f:close() 2287 + opts.filename = filename 2288 + return eval(source, opts, ...) 2289 +end 2290 + 2291 +local macroLoaded = {} 2292 + 2293 +local pathTable = {"./?.fnl", "./?/init.fnl"} 2294 +local osPath = os and os.getenv and os.getenv("FENNEL_PATH") 2295 +if osPath then 2296 + table.insert(pathTable, osPath) 2297 +end 2298 + 2299 +local module = { 2300 + parser = parser, 2301 + granulate = granulate, 2302 + stringStream = stringStream, 2303 + compile = compile, 2304 + compileString = compileString, 2305 + compileStream = compileStream, 2306 + compile1 = compile1, 2307 + loadCode = loadCode, 2308 + mangle = globalMangling, 2309 + unmangle = globalUnmangling, 2310 + list = list, 2311 + sym = sym, 2312 + varg = varg, 2313 + scope = makeScope, 2314 + gensym = gensym, 2315 + eval = eval, 2316 + dofile = dofileFennel, 2317 + macroLoaded = macroLoaded, 2318 + path = table.concat(pathTable, ";"), 2319 + traceback = traceback, 2320 + version = "0.4.0-dev", 2321 +} 2322 + 2323 +-- In order to make this more readable, you can switch your editor to treating 2324 +-- this file as if it were Fennel for the purposes of this section 2325 +local replsource = [===[(local (fennel internals) ...) 2326 + 2327 +(fn default-read-chunk [parser-state] 2328 + (io.write (if (< 0 parser-state.stackSize) ".." ">> ")) 2329 + (io.flush) 2330 + (let [input (io.read)] 2331 + (and input (.. input "\n")))) 2332 + 2333 +(fn default-on-values [xs] 2334 + (io.write (table.concat xs "\t")) 2335 + (io.write "\n")) 2336 + 2337 +(fn default-on-error [errtype err lua-source] 2338 + (io.write 2339 + (match errtype 2340 + "Lua Compile" (.. "Bad code generated - likely a bug with the compiler:\n" 2341 + "--- Generated Lua Start ---\n" 2342 + lua-source 2343 + "--- Generated Lua End ---\n") 2344 + "Runtime" (.. (fennel.traceback err 4) "\n") 2345 + _ (: "%s error: %s\n" :format errtype (tostring err))))) 2346 + 2347 +(local save-source 2348 + (table.concat ["local ___i___ = 1" 2349 + "while true do" 2350 + " local name, value = debug.getlocal(1, ___i___)" 2351 + " if(name and name ~= \"___i___\") then" 2352 + " ___replLocals___[name] = value" 2353 + " ___i___ = ___i___ + 1" 2354 + " else break end end"] "\n")) 2355 + 2356 +(fn splice-save-locals [env lua-source] 2357 + (set env.___replLocals___ (or env.___replLocals___ {})) 2358 + (let [spliced-source [] 2359 + bind "local %s = ___replLocals___['%s']"] 2360 + (each [line (lua-source:gmatch "([^\n]+)\n?")] 2361 + (table.insert spliced-source line)) 2362 + (each [name (pairs env.___replLocals___)] 2363 + (table.insert spliced-source 1 (bind:format name name))) 2364 + (when (and (: (. spliced-source (# spliced-source)) :match "^ *return .*$") 2365 + (< 1 (# spliced-source))) 2366 + (table.insert spliced-source (# spliced-source) save-source)) 2367 + (table.concat spliced-source "\n"))) 2368 + 2369 +(fn completer [env scope text] 2370 + (let [matches [] 2371 + input-fragment (text:gsub ".*[%s)(]+" "")] 2372 + (fn add-partials [input tbl prefix] ; add partial key matches in tbl 2373 + (each [k (pairs tbl)] 2374 + (let [k (if (or (= tbl env) (= tbl env.___replLocals___)) 2375 + (. scope.unmanglings k) 2376 + k)] 2377 + (when (and (< (# matches) 40) 2378 + (= (type k) "string") 2379 + (= input (k:sub 0 (# input)))) 2380 + (table.insert matches (.. prefix k)))))) 2381 + (fn add-matches [input tbl prefix] ; add matches, descending into tbl fields 2382 + (let [prefix (if prefix (.. prefix ".") "")] 2383 + (if (not (input:find "%.")) ; no more dots, so add matches 2384 + (add-partials input tbl prefix) 2385 + (let [(head tail) (input:match "^([^.]+)%.(.*)") 2386 + raw-head (if (or (= tbl env) (= tbl env.___replLocals___)) 2387 + (. scope.manglings head) 2388 + head)] 2389 + (when (= (type (. tbl raw-head)) "table") 2390 + (add-matches tail (. tbl raw-head) (.. prefix head))))))) 2391 + 2392 + (add-matches input-fragment (or scope.specials [])) 2393 + (add-matches input-fragment (or internals.SPECIALS [])) 2394 + (add-matches input-fragment (or env.___replLocals___ [])) 2395 + (add-matches input-fragment env) 2396 + (add-matches input-fragment (or env._ENV env._G [])) 2397 + matches)) 2398 + 2399 +(fn repl [options] 2400 + (let [old-root-options internals.rootOptions 2401 + env (if options.env 2402 + (internals.wrapEnv options.env) 2403 + (setmetatable {} {:__index (or _G._ENV _G)})) 2404 + save-locals? (and (not= options.saveLocals false) 2405 + env.debug env.debug.getlocal) 2406 + opts {} 2407 + _ (each [k v (pairs options)] (tset opts k v)) 2408 + read-chunk (or opts.readChunk default-read-chunk) 2409 + on-values (or opts.onValues default-on-values) 2410 + on-error (or opts.onError default-on-error) 2411 + pp (or opts.pp tostring) 2412 + ;; make parser 2413 + (byte-stream clear-stream) (fennel.granulate read-chunk) 2414 + chars [] 2415 + (read reset) (fennel.parser (fn [parser-state] 2416 + (let [c (byte-stream parser-state)] 2417 + (tset chars (+ (# chars) 1) c) 2418 + c))) 2419 + scope (fennel.scope)] 2420 + 2421 + ;; use metadata unless we've specifically disabled it 2422 + (set opts.useMetadata (not= options.useMetadata false)) 2423 + (when (= opts.allowedGlobals nil) 2424 + (set opts.allowedGlobals (internals.currentGlobalNames opts.env))) 2425 + 2426 + (when opts.registerCompleter 2427 + (opts.registerCompleter (partial completer env scope))) 2428 + 2429 + (fn loop [] 2430 + (each [k (pairs chars)] (tset chars k nil)) 2431 + (let [(ok parse-ok? x) (pcall read) 2432 + src-string (string.char ((or _G.unpack table.unpack) chars))] 2433 + (internals.setRootOptions opts) 2434 + (if (not ok) 2435 + (do (on-error "Parse" parse-ok?) 2436 + (clear-stream) 2437 + (reset) 2438 + (loop)) 2439 + (when parse-ok? ; if this is false, we got eof 2440 + (match (pcall fennel.compile x {:correlate opts.correlate 2441 + :source src-string 2442 + :scope scope 2443 + :useMetadata opts.useMetadata 2444 + :moduleName opts.moduleName}) 2445 + (false msg) (do (clear-stream) 2446 + (on-error "Compile" msg)) 2447 + (true source) (let [source (if save-locals? 2448 + (splice-save-locals env source) 2449 + source) 2450 + (lua-ok? loader) (pcall fennel.loadCode 2451 + source env)] 2452 + (if (not lua-ok?) 2453 + (do (clear-stream) 2454 + (on-error "Lua Compile" loader source)) 2455 + (match (xpcall #[(loader)] 2456 + (partial on-error "Runtime")) 2457 + (true ret) 2458 + (do (set env._ (. ret 1)) 2459 + (set env.__ ret) 2460 + (on-values (internals.map ret pp))))))) 2461 + (internals.setRootOptions old-root-options) 2462 + (loop))))) 2463 + (loop)))]===] 2464 + 2465 +module.repl = function(options) 2466 + -- functionality the repl needs that isn't part of the public API yet 2467 + local internals = { rootOptions = rootOptions, 2468 + setRootOptions = function(r) rootOptions = r end, 2469 + currentGlobalNames = currentGlobalNames, 2470 + wrapEnv = wrapEnv, 2471 + SPECIALS = SPECIALS, 2472 + map = map } 2473 + return eval(replsource, { correlate = true }, module, internals)(options) 2474 +end 2475 + 2476 +local function searchModule(modulename, pathstring) 2477 + modulename = modulename:gsub("%.", "/") 2478 + for path in string.gmatch((pathstring or module.path)..";", "([^;]*);") do 2479 + local filename = path:gsub("%?", modulename) 2480 + local file = io.open(filename, "rb") 2481 + if(file) then 2482 + file:close() 2483 + return filename 2484 + end 2485 + end 2486 +end 2487 + 2488 +module.makeSearcher = function(options) 2489 + return function(modulename) 2490 + -- this will propagate options from the repl but not from eval, because 2491 + -- eval unsets rootOptions after compiling but before running the actual 2492 + -- calls to require. 2493 + local opts = copy(rootOptions) 2494 + for k,v in pairs(options or {}) do opts[k] = v end 2495 + local filename = searchModule(modulename) 2496 + if filename then 2497 + return function(modname) 2498 + return dofileFennel(filename, opts, modname) 2499 + end 2500 + end 2501 + end 2502 +end 2503 + 2504 +-- Add metadata and docstrings to fennel module 2505 +module.metadata = metadata 2506 +module.doc = doc 2507 + 2508 +-- This will allow regular `require` to work with Fennel: 2509 +-- table.insert(package.loaders, fennel.searcher) 2510 +module.searcher = module.makeSearcher() 2511 +module.make_searcher = module.makeSearcher -- oops backwards compatibility 2512 + 2513 +local function makeCompilerEnv(ast, scope, parent) 2514 + return setmetatable({ 2515 + -- State of compiler if needed 2516 + _SCOPE = scope, 2517 + _CHUNK = parent, 2518 + _AST = ast, 2519 + _IS_COMPILER = true, 2520 + _SPECIALS = SPECIALS, 2521 + _VARARG = VARARG, 2522 + -- Expose the module in the compiler 2523 + fennel = module, 2524 + -- Useful for macros and meta programming. All of Fennel can be accessed 2525 + -- via fennel.myfun, for example (fennel.eval "(print 1)"). 2526 + list = list, 2527 + sym = sym, 2528 + unpack = unpack, 2529 + gensym = function() return sym(gensym(macroCurrentScope or scope)) end, 2530 + ["list?"] = isList, 2531 + ["multi-sym?"] = isMultiSym, 2532 + ["sym?"] = isSym, 2533 + ["table?"] = isTable, 2534 + ["sequence?"] = isSequence, 2535 + ["varg?"] = isVarg, 2536 + ["get-scope"] = function() 2537 + if io and io.stderr then 2538 + io.stderr: 2539 + write(("-- %s:%s get-scope is deprecated and will be removed\n"): 2540 + format(ast.filename, ast.line)) 2541 + end 2542 + return macroCurrentScope end, 2543 + ["in-scope?"] = function(symbol) 2544 + assertCompile(macroCurrentScope, "must call in-scope? from macro", ast) 2545 + return macroCurrentScope.manglings[tostring(symbol)] 2546 + end 2547 + }, { __index = _ENV or _G }) 2548 +end 2549 + 2550 +local function macroGlobals(env, globals) 2551 + local allowed = currentGlobalNames(env) 2552 + for _, k in pairs(globals or {}) do table.insert(allowed, k) end 2553 + return allowed 2554 +end 2555 + 2556 +local function addMacros(macros, ast, scope) 2557 + assertCompile(isTable(macros), 'expected macros to be table', ast) 2558 + kvmap(macros, function(k, v) return k, macroToSpecial(v) end, scope.specials) 2559 +end 2560 + 2561 +local function loadMacros(modname, ast, scope, parent) 2562 + local filename = assertCompile(searchModule(modname), 2563 + modname .. " not found.", ast) 2564 + local env = makeCompilerEnv(ast, scope, parent) 2565 + local globals = macroGlobals(env, currentGlobalNames()) 2566 + return dofileFennel(filename, { env = env, allowedGlobals = globals, 2567 + useMetadata = rootOptions.useMetadata, 2568 + scope = COMPILER_SCOPE }) 2569 +end 2570 + 2571 +SPECIALS['require-macros'] = function(ast, scope, parent) 2572 + assertCompile(#ast == 2, "Expected one module name argument", ast) 2573 + local modname = ast[2] 2574 + if not macroLoaded[modname] then 2575 + macroLoaded[modname] = loadMacros(modname, ast, scope, parent) 2576 + end 2577 + addMacros(macroLoaded[modname], ast, scope, parent) 2578 +end 2579 +docSpecial('require-macros', {'macro-module-name'}, 2580 + 'Load given module and use its contents as macro definitions in current scope.' 2581 + ..'\nMacro module should return a table of macro functions with string keys.') 2582 + 2583 +SPECIALS['include'] = function(ast, scope, parent, opts) 2584 + assertCompile(#ast == 2, 'expected one argument', ast) 2585 + 2586 + -- Compile mod argument 2587 + local modexpr = compile1(ast[2], scope, parent, {nval = 1})[1] 2588 + if modexpr.type ~= 'literal' or modexpr[1]:byte() ~= 34 then 2589 + if opts.fallback then 2590 + return opts.fallback(modexpr) 2591 + else 2592 + assertCompile(false, 'module name must resolve to a string literal', ast) 2593 + end 2594 + end 2595 + local code = 'return ' .. modexpr[1] 2596 + local mod = loadCode(code)() 2597 + 2598 + -- Check cache 2599 + if scope.includes[mod] then return scope.includes[mod] end 2600 + 2601 + -- Find path to source 2602 + local path = searchModule(mod) 2603 + local isFennel = true 2604 + if not path then 2605 + isFennel = false 2606 + path = searchModule(mod, package.path) 2607 + if not path then 2608 + if opts.fallback then 2609 + return opts.fallback(modexpr) 2610 + else 2611 + assertCompile(false, 'could not find module ' .. mod, ast) 2612 + end 2613 + end 2614 + end 2615 + 2616 + -- Read source 2617 + local f = io.open(path) 2618 + local s = f:read('*all') 2619 + f:close() 2620 + 2621 + -- splice in source and memoize it in compiler AND package.preload 2622 + -- so we can include it again without duplication, even in runtime 2623 + local target = 'package.preload["' .. mod .. '"]' 2624 + local ret = expr('require("' .. mod .. '")', 'statement') 2625 + 2626 + local subChunk, tempChunk = {}, {} 2627 + emit(tempChunk, subChunk, ast) 2628 + -- if lua, simply emit the setting of package.preload 2629 + if not isFennel then 2630 + emit(tempChunk, target .. ' = ' .. target .. ' or function()\n' .. s .. '\nend', ast) 2631 + end 2632 + -- Splice tempChunk to begining of rootChunk 2633 + for i, v in ipairs(tempChunk) do 2634 + table.insert(rootChunk, i, v) 2635 + end 2636 + 2637 + -- For fnl source, compile subChunk AFTER splicing into start of rootChunk. 2638 + if isFennel then 2639 + local subopts = { nval = 1, target = target } 2640 + local subscope = makeScope(rootScope.parent) 2641 + if rootOptions.requireAsInclude then 2642 + subscope.specials.require = requireSpecial 2643 + end 2644 + local targetForm = list(sym('.'), sym('package.preload'), mod) 2645 + -- splice "or" statement in so it uses existing package.preload[modname] 2646 + -- if it's been set by something else, allowing for overrides 2647 + local forms = list(sym('or'), targetForm, list(sym('fn'), sequence())) 2648 + local p = parser(stringStream(s), path) 2649 + for _, val in p do table.insert(forms[3], val) end 2650 + compile1(forms, subscope, subChunk, subopts) 2651 + end 2652 + 2653 + -- Put in cache and return 2654 + rootScope.includes[mod] = ret 2655 + return ret 2656 +end 2657 +docSpecial('include', {'module-name-literal'}, 2658 + 'Like require, but load the target module during compilation and embed it in the\n' 2659 + .. 'Lua output. The module must be a string literal and resolvable at compile time.') 2660 + 2661 +local function requireFallback(e) 2662 + local code = ('require(%s)'):format(tostring(e)) 2663 + return expr(code, 'statement') 2664 +end 2665 + 2666 +requireSpecial = function (ast, scope, parent, opts) 2667 + opts.fallback = requireFallback 2668 + return SPECIALS['include'](ast, scope, parent, opts) 2669 +end 2670 + 2671 +local function evalCompiler(ast, scope, parent) 2672 + local luaSource = compile(ast, { scope = makeScope(COMPILER_SCOPE), 2673 + useMetadata = rootOptions.useMetadata }) 2674 + local loader = loadCode(luaSource, wrapEnv(makeCompilerEnv(ast, scope, parent))) 2675 + return loader() 2676 +end 2677 + 2678 +SPECIALS['macros'] = function(ast, scope, parent) 2679 + assertCompile(#ast == 2, "Expected one table argument", ast) 2680 + local macros = evalCompiler(ast[2], scope, parent) 2681 + addMacros(macros, ast, scope, parent) 2682 +end 2683 +docSpecial('macros', {'{:macro-name-1 (fn [...] ...) ... :macro-name-N macro-body-N}'}, 2684 + 'Define all functions in the given table as macros local to the current scope.') 2685 + 2686 +SPECIALS['eval-compiler'] = function(ast, scope, parent) 2687 + local oldFirst = ast[1] 2688 + ast[1] = sym('do') 2689 + local val = evalCompiler(ast, scope, parent) 2690 + ast[1] = oldFirst 2691 + return val 2692 +end 2693 +docSpecial('eval-compiler', {'...'}, 'Evaluate the body at compile-time.' 2694 + .. ' Use the macro system instead if possible.') 2695 + 2696 +-- Load standard macros 2697 +local stdmacros = [===[ 2698 +{"->" (fn [val ...] 2699 + "Thread-first macro. 2700 +Take the first value and splice it into the second form as its first argument. 2701 +The value of the second form is spliced into the first arg of the third, etc." 2702 + (var x val) 2703 + (each [_ e (ipairs [...])] 2704 + (let [elt (if (list? e) e (list e))] 2705 + (table.insert elt 2 x) 2706 + (set x elt))) 2707 + x) 2708 + "->>" (fn [val ...] 2709 + "Thread-last macro. 2710 +Same as ->, except splices the value into the last position of each form 2711 +rather than the first." 2712 + (var x val) 2713 + (each [_ e (pairs [...])] 2714 + (let [elt (if (list? e) e (list e))] 2715 + (table.insert elt x) 2716 + (set x elt))) 2717 + x) 2718 + "-?>" (fn [val ...] 2719 + "Nil-safe thread-first macro. 2720 +Same as -> except will short-circuit with nil when it encounters a nil value." 2721 + (if (= 0 (select "#" ...)) 2722 + val 2723 + (let [els [...] 2724 + e (table.remove els 1) 2725 + el (if (list? e) e (list e)) 2726 + tmp (gensym)] 2727 + (table.insert el 2 tmp) 2728 + `(let [,tmp ,val] 2729 + (if ,tmp 2730 + (-?> ,el ,(unpack els)) 2731 + ,tmp))))) 2732 + "-?>>" (fn [val ...] 2733 + "Nil-safe thread-last macro. 2734 +Same as ->> except will short-circuit with nil when it encounters a nil value." 2735 + (if (= 0 (select "#" ...)) 2736 + val 2737 + (let [els [...] 2738 + e (table.remove els 1) 2739 + el (if (list? e) e (list e)) 2740 + tmp (gensym)] 2741 + (table.insert el tmp) 2742 + `(let [,tmp ,val] 2743 + (if ,tmp 2744 + (-?>> ,el ,(unpack els)) 2745 + ,tmp))))) 2746 + :doto (fn [val ...] 2747 + "Evaluates val and splices it into the first argument of subsequent forms." 2748 + (let [name (gensym) 2749 + form `(let [,name ,val])] 2750 + (each [_ elt (pairs [...])] 2751 + (table.insert elt 2 name) 2752 + (table.insert form elt)) 2753 + (table.insert form name) 2754 + form)) 2755 + :when (fn [condition body1 ...] 2756 + "Evaluate body for side-effects only when condition is truthy." 2757 + (assert body1 "expected body") 2758 + `(if ,condition 2759 + (do ,body1 ,...))) 2760 + :partial (fn [f ...] 2761 + "Returns a function with all arguments partially applied to f." 2762 + (let [body (list f ...)] 2763 + (table.insert body _VARARG) 2764 + `(fn [,_VARARG] ,body))) 2765 + :lambda (fn [...] 2766 + "Function literal with arity checking. 2767 +Will throw an exception if a declared argument is passed in as nil, unless 2768 +that argument name begins with ?." 2769 + (let [args [...] 2770 + has-internal-name? (sym? (. args 1)) 2771 + arglist (if has-internal-name? (. args 2) (. args 1)) 2772 + docstring-position (if has-internal-name? 3 2) 2773 + has-docstring? (and (> (# args) docstring-position) 2774 + (= :string (type (. args docstring-position)))) 2775 + arity-check-position (- 4 (if has-internal-name? 0 1) (if has-docstring? 0 1))] 2776 + (fn check! [a] 2777 + (if (table? a) 2778 + (each [_ a (pairs a)] 2779 + (check! a)) 2780 + (and (not (: (tostring a) :match "^?")) 2781 + (not= (tostring a) "&") 2782 + (not= (tostring a) "...")) 2783 + (table.insert args arity-check-position 2784 + `(assert (not= nil ,a) 2785 + (: "Missing argument %s on %s:%s" 2786 + :format ,(tostring a) 2787 + ,(or a.filename "unknown") 2788 + ,(or a.line "?")))))) 2789 + (assert (> (length args) 1) "missing body expression") 2790 + (each [_ a (ipairs arglist)] 2791 + (check! a)) 2792 + `(fn ,(unpack args)))) 2793 + :macro (fn macro [name ...] 2794 + "Define a single macro." 2795 + (assert (sym? name) "expected symbol for macro name") 2796 + (local args [...]) 2797 + `(macros { ,(tostring name) (fn ,name ,(unpack args))})) 2798 + :match 2799 +(fn match [val ...] 2800 + "Perform pattern matching on val. See reference for details." 2801 + ;; this function takes the AST of values and a single pattern and returns a 2802 + ;; condition to determine if it matches as well as a list of bindings to 2803 + ;; introduce for the duration of the body if it does match. 2804 + (fn match-pattern [vals pattern unifications] 2805 + ;; we have to assume we're matching against multiple values here until we 2806 + ;; know we're either in a multi-valued clause (in which case we know the # 2807 + ;; of vals) or we're not, in which case we only care about the first one. 2808 + (let [[val] vals] 2809 + (if (or (and (sym? pattern) ; unification with outer locals (or nil) 2810 + (not= :_ (tostring pattern)) ; never unify _ 2811 + (or (in-scope? pattern) 2812 + (= :nil (tostring pattern)))) 2813 + (and (multi-sym? pattern) 2814 + (in-scope? (. (multi-sym? pattern) 1)))) 2815 + (values `(= ,val ,pattern) []) 2816 + ;; unify a local we've seen already 2817 + (and (sym? pattern) 2818 + (. unifications (tostring pattern))) 2819 + (values `(= ,(. unifications (tostring pattern)) ,val) []) 2820 + ;; bind a fresh local 2821 + (sym? pattern) 2822 + (let [wildcard? (= (tostring pattern) "_")] 2823 + (if (not wildcard?) (tset unifications (tostring pattern) val)) 2824 + (values (if (or wildcard? (: (tostring pattern) :find "^?")) 2825 + true `(not= ,(sym :nil) ,val)) 2826 + [pattern val])) 2827 + ;; guard clause 2828 + (and (list? pattern) (sym? (. pattern 2)) (= :? (tostring (. pattern 2)))) 2829 + (let [(pcondition bindings) (match-pattern vals (. pattern 1) 2830 + unifications) 2831 + condition `(and ,pcondition)] 2832 + (for [i 3 (# pattern)] ; splice in guard clauses 2833 + (table.insert condition (. pattern i))) 2834 + (values `(let ,bindings ,condition) bindings)) 2835 + 2836 + ;; multi-valued patterns (represented as lists) 2837 + (list? pattern) 2838 + (let [condition `(and) 2839 + bindings []] 2840 + (each [i pat (ipairs pattern)] 2841 + (let [(subcondition subbindings) (match-pattern [(. vals i)] pat 2842 + unifications)] 2843 + (table.insert condition subcondition) 2844 + (each [_ b (ipairs subbindings)] 2845 + (table.insert bindings b)))) 2846 + (values condition bindings)) 2847 + ;; table patterns) 2848 + (= (type pattern) :table) 2849 + (let [condition `(and (= (type ,val) :table)) 2850 + bindings []] 2851 + (each [k pat (pairs pattern)] 2852 + (if (and (sym? pat) (= "&" (tostring pat))) 2853 + (do (assert (not (. pattern (+ k 2))) 2854 + "expected rest argument in final position") 2855 + (table.insert bindings (. pattern (+ k 1))) 2856 + (table.insert bindings [`(select ,k ((or _G.unpack table.unpack) 2857 + ,val))])) 2858 + (and (= :number (type k)) 2859 + (= "&" (tostring (. pattern (- k 1))))) 2860 + nil ; don't process the pattern right after &; already got it 2861 + (let [subval `(. ,val ,k) 2862 + (subcondition subbindings) (match-pattern [subval] pat 2863 + unifications)] 2864 + (table.insert condition subcondition) 2865 + (each [_ b (ipairs subbindings)] 2866 + (table.insert bindings b))))) 2867 + (values condition bindings)) 2868 + ;; literal value 2869 + (values `(= ,val ,pattern) [])))) 2870 + (fn match-condition [vals clauses] 2871 + (let [out `(if)] 2872 + (for [i 1 (length clauses) 2] 2873 + (let [pattern (. clauses i) 2874 + body (. clauses (+ i 1)) 2875 + (condition bindings) (match-pattern vals pattern {})] 2876 + (table.insert out condition) 2877 + (table.insert out `(let ,bindings ,body)))) 2878 + out)) 2879 + ;; how many multi-valued clauses are there? return a list of that many gensyms 2880 + (fn val-syms [clauses] 2881 + (let [syms (list (gensym))] 2882 + (for [i 1 (length clauses) 2] 2883 + (if (list? (. clauses i)) 2884 + (each [valnum (ipairs (. clauses i))] 2885 + (if (not (. syms valnum)) 2886 + (tset syms valnum (gensym)))))) 2887 + syms)) 2888 + ;; wrap it in a way that prevents double-evaluation of the matched value 2889 + (let [clauses [...] 2890 + vals (val-syms clauses)] 2891 + (if (not= 0 (% (length clauses) 2)) ; treat odd final clause as default 2892 + (table.insert clauses (length clauses) (sym :_))) 2893 + ;; protect against multiple evaluation of the value, bind against as 2894 + ;; many values as we ever match against in the clauses. 2895 + (list (sym :let) [vals val] 2896 + (match-condition vals clauses)))) 2897 + } 2898 +]===] 2899 +do 2900 + -- docstrings rely on having a place to "put" metadata; we use the module 2901 + -- system for that. but if you try to require the module while it's being 2902 + -- loaded, you get a stack overflow. so we fake out the module for the 2903 + -- purposes of boostrapping the built-in macros here. 2904 + local moduleName = "__fennel-bootstrap__" 2905 + package.preload[moduleName] = function() return module end 2906 + local env = makeCompilerEnv(nil, COMPILER_SCOPE, {}) 2907 + local macros = eval(stdmacros, { 2908 + env = env, 2909 + scope = makeScope(COMPILER_SCOPE), 2910 + -- assume the code to load globals doesn't have any 2911 + -- mistaken globals, otherwise this can be 2912 + -- problematic when loading fennel in contexts 2913 + -- where _G is an empty table with an __index 2914 + -- metamethod. (openresty) 2915 + allowedGlobals = false, 2916 + useMetadata = true, 2917 + filename = "built-ins", 2918 + moduleName = moduleName }) 2919 + kvmap(macros, 2920 + function(name, fn) return name, macroToSpecial(fn, name) end, 2921 + SPECIALS) 2922 + package.preload[moduleName] = nil 2923 +end 2924 +SPECIALS['λ'] = SPECIALS['lambda'] 2925 + 2926 +return module
Added src/worpt/lib/fennelview.lua.
1 +local function view_quote(str) 2 + return ("\"" .. str:gsub("\"", "\\\"") .. "\"") 3 +end 4 +local short_control_char_escapes = {["\11"] = "\\v", ["\12"] = "\\f", ["\13"] = "\\r", ["\7"] = "\\a", ["\8"] = "\\b", ["\9"] = "\\t", ["\n"] = "\\n"} 5 +local long_control_char_escapes = nil 6 +do 7 + local long = {} 8 + for i = 0, 31 do 9 + local ch = string.char(i) 10 + if not short_control_char_escapes[ch] then 11 + short_control_char_escapes[ch] = ("\\" .. i) 12 + long[ch] = ("\\%03d"):format(i) 13 + end 14 + end 15 + long_control_char_escapes = long 16 +end 17 +local function escape(str) 18 + local str0 = str:gsub("\\", "\\\\") 19 + local str1 = str0:gsub("(%c)%f[0-9]", long_control_char_escapes) 20 + return str1:gsub("%c", short_control_char_escapes) 21 +end 22 +local function sequence_key_3f(k, len) 23 + return ((type(k) == "number") and (1 <= k) and (k <= len) and (math.floor(k) == k)) 24 +end 25 +local type_order = {["function"] = 5, boolean = 2, number = 1, string = 3, table = 4, thread = 7, userdata = 6} 26 +local function sort_keys(a, b) 27 + local ta = type(a) 28 + local tb = type(b) 29 + if ((ta == tb) and (ta ~= "boolean") and ((ta == "string") or (ta == "number"))) then 30 + return (a < b) 31 + else 32 + local dta = type_order[a] 33 + local dtb = type_order[b] 34 + if (dta and dtb) then 35 + return (dta < dtb) 36 + elseif dta then 37 + return true 38 + elseif dtb then 39 + return false 40 + elseif "else" then 41 + return (ta < tb) 42 + end 43 + end 44 +end 45 +local function get_sequence_length(t) 46 + local len = 1 47 + for i in ipairs(t) do 48 + len = i 49 + end 50 + return len 51 +end 52 +local function get_nonsequential_keys(t) 53 + local keys = {} 54 + local sequence_length = get_sequence_length(t) 55 + for k in pairs(t) do 56 + if not sequence_key_3f(k, sequence_length) then 57 + table.insert(keys, k) 58 + end 59 + end 60 + table.sort(keys, sort_keys) 61 + return keys, sequence_length 62 +end 63 +local function count_table_appearances(t, appearances) 64 + if (type(t) == "table") then 65 + if not appearances[t] then 66 + appearances[t] = 1 67 + for k, v in pairs(t) do 68 + count_table_appearances(k, appearances) 69 + count_table_appearances(v, appearances) 70 + end 71 + end 72 + else 73 + if (t and (t == t)) then 74 + appearances[t] = ((appearances[t] or 0) + 1) 75 + end 76 + end 77 + return appearances 78 +end 79 +local put_value = nil 80 +local function puts(self, ...) 81 + for _, v in ipairs({...}) do 82 + table.insert(self.buffer, v) 83 + end 84 + return nil 85 +end 86 +local function tabify(self) 87 + return puts(self, "\n", (self.indent):rep(self.level)) 88 +end 89 +local function already_visited_3f(self, v) 90 + return (self.ids[v] ~= nil) 91 +end 92 +local function get_id(self, v) 93 + local id = self.ids[v] 94 + if not id then 95 + local tv = type(v) 96 + id = ((self["max-ids"][tv] or 0) + 1) 97 + self["max-ids"][tv] = id 98 + self.ids[v] = id 99 + end 100 + return tostring(id) 101 +end 102 +local function put_sequential_table(self, t, len) 103 + puts(self, "[") 104 + self.level = (self.level + 1) 105 + for i = 1, len do 106 + local _0_ = (1 + len) 107 + if ((1 < i) and (i < _0_)) then 108 + puts(self, " ") 109 + end 110 + put_value(self, t[i]) 111 + end 112 + self.level = (self.level - 1) 113 + return puts(self, "]") 114 +end 115 +local function put_key(self, k) 116 + if ((type(k) == "string") and k:find("^[-%w?\\^_!$%&*+./@:|<=>]+$")) then 117 + return puts(self, ":", k) 118 + else 119 + return put_value(self, k) 120 + end 121 +end 122 +local function put_kv_table(self, t, ordered_keys) 123 + puts(self, "{") 124 + self.level = (self.level + 1) 125 + for _, k in ipairs(ordered_keys) do 126 + tabify(self) 127 + put_key(self, k) 128 + puts(self, " ") 129 + put_value(self, t[k]) 130 + end 131 + for i, v in ipairs(t) do 132 + tabify(self) 133 + put_key(self, i) 134 + puts(self, " ") 135 + put_value(self, v) 136 + end 137 + self.level = (self.level - 1) 138 + tabify(self) 139 + return puts(self, "}") 140 +end 141 +local function put_table(self, t) 142 + local metamethod = nil 143 + do 144 + local _0_0 = t 145 + if _0_0 then 146 + local _1_0 = getmetatable(_0_0) 147 + if _1_0 then 148 + metamethod = _1_0.__fennelview 149 + else 150 + metamethod = _1_0 151 + end 152 + else 153 + metamethod = _0_0 154 + end 155 + end 156 + if (already_visited_3f(self, t) and self["detect-cycles?"]) then 157 + return puts(self, "#<table ", get_id(self, t), ">") 158 + elseif (self.level >= self.depth) then 159 + return puts(self, "{...}") 160 + elseif metamethod then 161 + return puts(self, metamethod(t, self.fennelview)) 162 + elseif "else" then 163 + local non_seq_keys, len = get_nonsequential_keys(t) 164 + local id = get_id(self, t) 165 + if ((1 < (self.appearances[t] or 0)) and self["detect-cycles?"]) then 166 + return puts(self, "#<table", id, ">") 167 + elseif ((#non_seq_keys == 0) and (#t == 0)) then 168 + return puts(self, "{}") 169 + elseif (#non_seq_keys == 0) then 170 + return put_sequential_table(self, t, len) 171 + elseif "else" then 172 + return put_kv_table(self, t, non_seq_keys) 173 + end 174 + end 175 +end 176 +local function _0_(self, v) 177 + local tv = type(v) 178 + if (tv == "string") then 179 + return puts(self, view_quote(escape(v))) 180 + elseif ((tv == "number") or (tv == "boolean") or (tv == "nil")) then 181 + return puts(self, tostring(v)) 182 + elseif (tv == "table") then 183 + return put_table(self, v) 184 + elseif "else" then 185 + return puts(self, "#<", tostring(v), ">") 186 + end 187 +end 188 +put_value = _0_ 189 +local function one_line(str) 190 + local ret = str:gsub("\n", " "):gsub("%[ ", "["):gsub(" %]", "]"):gsub("%{ ", "{"):gsub(" %}", "}"):gsub("%( ", "("):gsub(" %)", ")") 191 + return ret 192 +end 193 +local function fennelview(x, options) 194 + local options0 = (options or {}) 195 + local inspector = nil 196 + local function _1_(_241) 197 + return fennelview(_241, options0) 198 + end 199 + local function _2_() 200 + if options0["one-line"] then 201 + return "" 202 + else 203 + return " " 204 + end 205 + end 206 + inspector = {["detect-cycles?"] = not (false == options0["detect-cycles?"]), ["max-ids"] = {}, appearances = count_table_appearances(x, {}), buffer = {}, depth = (options0.depth or 128), fennelview = _1_, ids = {}, indent = (options0.indent or _2_()), level = 0} 207 + put_value(inspector, x) 208 + do 209 + local str = table.concat(inspector.buffer) 210 + if options0["one-line"] then 211 + return one_line(str) 212 + else 213 + return str 214 + end 215 + end 216 +end 217 +return fennelview
Added src/worpt/lib/lume.lua.
1 +-- 2 +-- lume 3 +-- 4 +-- Copyright (c) 2018 rxi 5 +-- 6 +-- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 +-- this software and associated documentation files (the "Software"), to deal in 8 +-- the Software without restriction, including without limitation the rights to 9 +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 +-- of the Software, and to permit persons to whom the Software is furnished to do 11 +-- so, subject to the following conditions: 12 +-- 13 +-- The above copyright notice and this permission notice shall be included in all 14 +-- copies or substantial portions of the Software. 15 +-- 16 +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 +-- SOFTWARE. 23 +-- 24 + 25 +local lume = { _version = "2.3.0" } 26 + 27 +local pairs, ipairs = pairs, ipairs 28 +local type, assert, unpack = type, assert, unpack or table.unpack 29 +local tostring, tonumber = tostring, tonumber 30 +local math_floor = math.floor 31 +local math_ceil = math.ceil 32 +local math_atan2 = math.atan2 or math.atan 33 +local math_sqrt = math.sqrt 34 +local math_abs = math.abs 35 + 36 +local noop = function() 37 +end 38 + 39 +local identity = function(x) 40 + return x 41 +end 42 + 43 +local patternescape = function(str) 44 + return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1") 45 +end 46 + 47 +local absindex = function(len, i) 48 + return i < 0 and (len + i + 1) or i 49 +end 50 + 51 +local iscallable = function(x) 52 + if type(x) == "function" then return true end 53 + local mt = getmetatable(x) 54 + return mt and mt.__call ~= nil 55 +end 56 + 57 +local getiter = function(x) 58 + if lume.isarray(x) then 59 + return ipairs 60 + elseif type(x) == "table" then 61 + return pairs 62 + end 63 + error("expected table", 3) 64 +end 65 + 66 +local iteratee = function(x) 67 + if x == nil then return identity end 68 + if iscallable(x) then return x end 69 + if type(x) == "table" then 70 + return function(z) 71 + for k, v in pairs(x) do 72 + if z[k] ~= v then return false end 73 + end 74 + return true 75 + end 76 + end 77 + return function(z) return z[x] end 78 +end 79 + 80 + 81 + 82 +function lume.clamp(x, min, max) 83 + return x < min and min or (x > max and max or x) 84 +end 85 + 86 + 87 +function lume.round(x, increment) 88 + if increment then return lume.round(x / increment) * increment end 89 + return x >= 0 and math_floor(x + .5) or math_ceil(x - .5) 90 +end 91 + 92 + 93 +function lume.sign(x) 94 + return x < 0 and -1 or 1 95 +end 96 + 97 + 98 +function lume.lerp(a, b, amount) 99 + return a + (b - a) * lume.clamp(amount, 0, 1) 100 +end 101 + 102 + 103 +function lume.smooth(a, b, amount) 104 + local t = lume.clamp(amount, 0, 1) 105 + local m = t * t * (3 - 2 * t) 106 + return a + (b - a) * m 107 +end 108 + 109 + 110 +function lume.pingpong(x) 111 + return 1 - math_abs(1 - x % 2) 112 +end 113 + 114 + 115 +function lume.distance(x1, y1, x2, y2, squared) 116 + local dx = x1 - x2 117 + local dy = y1 - y2 118 + local s = dx * dx + dy * dy 119 + return squared and s or math_sqrt(s) 120 +end 121 + 122 + 123 +function lume.angle(x1, y1, x2, y2) 124 + return math_atan2(y2 - y1, x2 - x1) 125 +end 126 + 127 + 128 +function lume.vector(angle, magnitude) 129 + return math.cos(angle) * magnitude, math.sin(angle) * magnitude 130 +end 131 + 132 + 133 +function lume.random(a, b) 134 + if not a then a, b = 0, 1 end 135 + if not b then b = 0 end 136 + return a + math.random() * (b - a) 137 +end 138 + 139 + 140 +function lume.randomchoice(t) 141 + return t[math.random(#t)] 142 +end 143 + 144 + 145 +function lume.weightedchoice(t) 146 + local sum = 0 147 + for _, v in pairs(t) do 148 + assert(v >= 0, "weight value less than zero") 149 + sum = sum + v 150 + end 151 + assert(sum ~= 0, "all weights are zero") 152 + local rnd = lume.random(sum) 153 + for k, v in pairs(t) do 154 + if rnd < v then return k end 155 + rnd = rnd - v 156 + end 157 +end 158 + 159 + 160 +function lume.isarray(x) 161 + return type(x) == "table" and x[1] ~= nil 162 +end 163 + 164 + 165 +function lume.push(t, ...) 166 + local n = select("#", ...) 167 + for i = 1, n do 168 + t[#t + 1] = select(i, ...) 169 + end 170 + return ... 171 +end 172 + 173 + 174 +function lume.remove(t, x) 175 + local iter = getiter(t) 176 + for i, v in iter(t) do 177 + if v == x then 178 + if lume.isarray(t) then 179 + table.remove(t, i) 180 + break 181 + else 182 + t[i] = nil 183 + break 184 + end 185 + end 186 + end 187 + return x 188 +end 189 + 190 + 191 +function lume.clear(t) 192 + local iter = getiter(t) 193 + for k in iter(t) do 194 + t[k] = nil 195 + end 196 + return t 197 +end 198 + 199 + 200 +function lume.extend(t, ...) 201 + for i = 1, select("#", ...) do 202 + local x = select(i, ...) 203 + if x then 204 + for k, v in pairs(x) do 205 + t[k] = v 206 + end 207 + end 208 + end 209 + return t 210 +end 211 + 212 + 213 +function lume.shuffle(t) 214 + local rtn = {} 215 + for i = 1, #t do 216 + local r = math.random(i) 217 + if r ~= i then 218 + rtn[i] = rtn[r] 219 + end 220 + rtn[r] = t[i] 221 + end 222 + return rtn 223 +end 224 + 225 + 226 +function lume.sort(t, comp) 227 + local rtn = lume.clone(t) 228 + if comp then 229 + if type(comp) == "string" then 230 + table.sort(rtn, function(a, b) return a[comp] < b[comp] end) 231 + else 232 + table.sort(rtn, comp) 233 + end 234 + else 235 + table.sort(rtn) 236 + end 237 + return rtn 238 +end 239 + 240 + 241 +function lume.array(...) 242 + local t = {} 243 + for x in ... do t[#t + 1] = x end 244 + return t 245 +end 246 + 247 + 248 +function lume.each(t, fn, ...) 249 + local iter = getiter(t) 250 + if type(fn) == "string" then 251 + for _, v in iter(t) do v[fn](v, ...) end 252 + else 253 + for _, v in iter(t) do fn(v, ...) end 254 + end 255 + return t 256 +end 257 + 258 + 259 +function lume.map(t, fn) 260 + fn = iteratee(fn) 261 + local iter = getiter(t) 262 + local rtn = {} 263 + for k, v in iter(t) do rtn[k] = fn(v) end 264 + return rtn 265 +end 266 + 267 + 268 +function lume.all(t, fn) 269 + fn = iteratee(fn) 270 + local iter = getiter(t) 271 + for _, v in iter(t) do 272 + if not fn(v) then return false end 273 + end 274 + return true 275 +end 276 + 277 + 278 +function lume.any(t, fn) 279 + fn = iteratee(fn) 280 + local iter = getiter(t) 281 + for _, v in iter(t) do 282 + if fn(v) then return true end 283 + end 284 + return false 285 +end 286 + 287 + 288 +function lume.reduce(t, fn, first) 289 + local acc = first 290 + local started = first and true or false 291 + local iter = getiter(t) 292 + for _, v in iter(t) do 293 + if started then 294 + acc = fn(acc, v) 295 + else 296 + acc = v 297 + started = true 298 + end 299 + end 300 + assert(started, "reduce of an empty table with no first value") 301 + return acc 302 +end 303 + 304 + 305 +function lume.unique(t) 306 + local rtn = {} 307 + for k in pairs(lume.invert(t)) do 308 + rtn[#rtn + 1] = k 309 + end 310 + return rtn 311 +end 312 + 313 + 314 +function lume.filter(t, fn, retainkeys) 315 + fn = iteratee(fn) 316 + local iter = getiter(t) 317 + local rtn = {} 318 + if retainkeys then 319 + for k, v in iter(t) do 320 + if fn(v) then rtn[k] = v end 321 + end 322 + else 323 + for _, v in iter(t) do 324 + if fn(v) then rtn[#rtn + 1] = v end 325 + end 326 + end 327 + return rtn 328 +end 329 + 330 + 331 +function lume.reject(t, fn, retainkeys) 332 + fn = iteratee(fn) 333 + local iter = getiter(t) 334 + local rtn = {} 335 + if retainkeys then 336 + for k, v in iter(t) do 337 + if not fn(v) then rtn[k] = v end 338 + end 339 + else 340 + for _, v in iter(t) do 341 + if not fn(v) then rtn[#rtn + 1] = v end 342 + end 343 + end 344 + return rtn 345 +end 346 + 347 + 348 +function lume.merge(...) 349 + local rtn = {} 350 + for i = 1, select("#", ...) do 351 + local t = select(i, ...) 352 + local iter = getiter(t) 353 + for k, v in iter(t) do 354 + rtn[k] = v 355 + end 356 + end 357 + return rtn 358 +end 359 + 360 + 361 +function lume.concat(...) 362 + local rtn = {} 363 + for i = 1, select("#", ...) do 364 + local t = select(i, ...) 365 + if t ~= nil then 366 + local iter = getiter(t) 367 + for _, v in iter(t) do 368 + rtn[#rtn + 1] = v 369 + end 370 + end 371 + end 372 + return rtn 373 +end 374 + 375 + 376 +function lume.find(t, value) 377 + local iter = getiter(t) 378 + for k, v in iter(t) do 379 + if v == value then return k end 380 + end 381 + return nil 382 +end 383 + 384 + 385 +function lume.match(t, fn) 386 + fn = iteratee(fn) 387 + local iter = getiter(t) 388 + for k, v in iter(t) do 389 + if fn(v) then return v, k end 390 + end 391 + return nil 392 +end 393 + 394 + 395 +function lume.count(t, fn) 396 + local count = 0 397 + local iter = getiter(t) 398 + if fn then 399 + fn = iteratee(fn) 400 + for _, v in iter(t) do 401 + if fn(v) then count = count + 1 end 402 + end 403 + else 404 + if lume.isarray(t) then 405 + return #t 406 + end 407 + for _ in iter(t) do count = count + 1 end 408 + end 409 + return count 410 +end 411 + 412 + 413 +function lume.slice(t, i, j) 414 + i = i and absindex(#t, i) or 1 415 + j = j and absindex(#t, j) or #t 416 + local rtn = {} 417 + for x = i < 1 and 1 or i, j > #t and #t or j do 418 + rtn[#rtn + 1] = t[x] 419 + end 420 + return rtn 421 +end 422 + 423 + 424 +function lume.first(t, n) 425 + if not n then return t[1] end 426 + return lume.slice(t, 1, n) 427 +end 428 + 429 + 430 +function lume.last(t, n) 431 + if not n then return t[#t] end 432 + return lume.slice(t, -n, -1) 433 +end 434 + 435 + 436 +function lume.invert(t) 437 + local rtn = {} 438 + for k, v in pairs(t) do rtn[v] = k end 439 + return rtn 440 +end 441 + 442 + 443 +function lume.pick(t, ...) 444 + local rtn = {} 445 + for i = 1, select("#", ...) do 446 + local k = select(i, ...) 447 + rtn[k] = t[k] 448 + end 449 + return rtn 450 +end 451 + 452 + 453 +function lume.keys(t) 454 + local rtn = {} 455 + local iter = getiter(t) 456 + for k in iter(t) do rtn[#rtn + 1] = k end 457 + return rtn 458 +end 459 + 460 + 461 +function lume.clone(t) 462 + local rtn = {} 463 + for k, v in pairs(t) do rtn[k] = v end 464 + return rtn 465 +end 466 + 467 + 468 +function lume.fn(fn, ...) 469 + assert(iscallable(fn), "expected a function as the first argument") 470 + local args = { ... } 471 + return function(...) 472 + local a = lume.concat(args, { ... }) 473 + return fn(unpack(a)) 474 + end 475 +end 476 + 477 + 478 +function lume.once(fn, ...) 479 + local f = lume.fn(fn, ...) 480 + local done = false 481 + return function(...) 482 + if done then return end 483 + done = true 484 + return f(...) 485 + end 486 +end 487 + 488 + 489 +local memoize_fnkey = {} 490 +local memoize_nil = {} 491 + 492 +function lume.memoize(fn) 493 + local cache = {} 494 + return function(...) 495 + local c = cache 496 + for i = 1, select("#", ...) do 497 + local a = select(i, ...) or memoize_nil 498 + c[a] = c[a] or {} 499 + c = c[a] 500 + end 501 + c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)} 502 + return unpack(c[memoize_fnkey]) 503 + end 504 +end 505 + 506 + 507 +function lume.combine(...) 508 + local n = select('#', ...) 509 + if n == 0 then return noop end 510 + if n == 1 then 511 + local fn = select(1, ...) 512 + if not fn then return noop end 513 + assert(iscallable(fn), "expected a function or nil") 514 + return fn 515 + end 516 + local funcs = {} 517 + for i = 1, n do 518 + local fn = select(i, ...) 519 + if fn ~= nil then 520 + assert(iscallable(fn), "expected a function or nil") 521 + funcs[#funcs + 1] = fn 522 + end 523 + end 524 + return function(...) 525 + for _, f in ipairs(funcs) do f(...) end 526 + end 527 +end 528 + 529 + 530 +function lume.call(fn, ...) 531 + if fn then 532 + return fn(...) 533 + end 534 +end 535 + 536 + 537 +function lume.time(fn, ...) 538 + local start = os.clock() 539 + local rtn = {fn(...)} 540 + return (os.clock() - start), unpack(rtn) 541 +end 542 + 543 + 544 +local lambda_cache = {} 545 + 546 +function lume.lambda(str) 547 + if not lambda_cache[str] then 548 + local args, body = str:match([[^([%w,_ ]-)%->(.-)$]]) 549 + assert(args and body, "bad string lambda") 550 + local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend" 551 + lambda_cache[str] = lume.dostring(s) 552 + end 553 + return lambda_cache[str] 554 +end 555 + 556 + 557 +local serialize 558 + 559 +local serialize_map = { 560 + [ "boolean" ] = tostring, 561 + [ "nil" ] = tostring, 562 + [ "string" ] = function(v) return string.format("%q", v) end, 563 + [ "number" ] = function(v) 564 + if v ~= v then return "0/0" -- nan 565 + elseif v == 1 / 0 then return "1/0" -- inf 566 + elseif v == -1 / 0 then return "-1/0" end -- -inf 567 + return tostring(v) 568 + end, 569 + [ "table" ] = function(t, stk) 570 + stk = stk or {} 571 + if stk[t] then error("circular reference") end 572 + local rtn = {} 573 + stk[t] = true 574 + for k, v in pairs(t) do 575 + rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk) 576 + end 577 + stk[t] = nil 578 + return "{" .. table.concat(rtn, ",") .. "}" 579 + end 580 +} 581 + 582 +setmetatable(serialize_map, { 583 + __index = function(_, k) error("unsupported serialize type: " .. k) end 584 +}) 585 + 586 +serialize = function(x, stk) 587 + return serialize_map[type(x)](x, stk) 588 +end 589 + 590 +function lume.serialize(x) 591 + return serialize(x) 592 +end 593 + 594 + 595 +function lume.deserialize(str) 596 + return lume.dostring("return " .. str) 597 +end 598 + 599 + 600 +function lume.split(str, sep) 601 + if not sep then 602 + return lume.array(str:gmatch("([%S]+)")) 603 + else 604 + assert(sep ~= "", "empty separator") 605 + local psep = patternescape(sep) 606 + return lume.array((str..sep):gmatch("(.-)("..psep..")")) 607 + end 608 +end 609 + 610 + 611 +function lume.trim(str, chars) 612 + if not chars then return str:match("^[%s]*(.-)[%s]*$") end 613 + chars = patternescape(chars) 614 + return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$") 615 +end 616 + 617 + 618 +function lume.wordwrap(str, limit) 619 + limit = limit or 72 620 + local check 621 + if type(limit) == "number" then 622 + check = function(s) return #s >= limit end 623 + else 624 + check = limit 625 + end 626 + local rtn = {} 627 + local line = "" 628 + for word, spaces in str:gmatch("(%S+)(%s*)") do 629 + local s = line .. word 630 + if check(s) then 631 + table.insert(rtn, line .. "\n") 632 + line = word 633 + else 634 + line = s 635 + end 636 + for c in spaces:gmatch(".") do 637 + if c == "\n" then 638 + table.insert(rtn, line .. "\n") 639 + line = "" 640 + else 641 + line = line .. c 642 + end 643 + end 644 + end 645 + table.insert(rtn, line) 646 + return table.concat(rtn) 647 +end 648 + 649 + 650 +function lume.format(str, vars) 651 + if not vars then return str end 652 + local f = function(x) 653 + return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}") 654 + end 655 + return (str:gsub("{(.-)}", f)) 656 +end 657 + 658 + 659 +function lume.trace(...) 660 + local info = debug.getinfo(2, "Sl") 661 + local t = { info.short_src .. ":" .. info.currentline .. ":" } 662 + for i = 1, select("#", ...) do 663 + local x = select(i, ...) 664 + if type(x) == "number" then 665 + x = string.format("%g", lume.round(x, .01)) 666 + end 667 + t[#t + 1] = tostring(x) 668 + end 669 + print(table.concat(t, " ")) 670 +end 671 + 672 + 673 +function lume.dostring(str) 674 + return assert((loadstring or load)(str))() 675 +end 676 + 677 + 678 +function lume.uuid() 679 + local fn = function(x) 680 + local r = math.random(16) - 1 681 + r = (x == "x") and (r + 1) or (r % 4) + 9 682 + return ("0123456789abcdef"):sub(r, r) 683 + end 684 + return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn)) 685 +end 686 + 687 + 688 +function lume.hotswap(modname) 689 + local oldglobal = lume.clone(_G) 690 + local updated = {} 691 + local function update(old, new) 692 + if updated[old] then return end 693 + updated[old] = true 694 + local oldmt, newmt = getmetatable(old), getmetatable(new) 695 + if oldmt and newmt then update(oldmt, newmt) end 696 + for k, v in pairs(new) do 697 + if type(v) == "table" then update(old[k], v) else old[k] = v end 698 + end 699 + end 700 + local err = nil 701 + local function onerror(e) 702 + for k in pairs(_G) do _G[k] = oldglobal[k] end 703 + err = lume.trim(e) 704 + end 705 + local ok, oldmod = pcall(require, modname) 706 + oldmod = ok and oldmod or nil 707 + xpcall(function() 708 + package.loaded[modname] = nil 709 + local newmod = require(modname) 710 + if type(oldmod) == "table" then update(oldmod, newmod) end 711 + for k, v in pairs(oldglobal) do 712 + if v ~= _G[k] and type(v) == "table" then 713 + update(v, _G[k]) 714 + _G[k] = v 715 + end 716 + end 717 + end, onerror) 718 + package.loaded[modname] = oldmod 719 + if err then return nil, err end 720 + return oldmod 721 +end 722 + 723 + 724 +local ripairs_iter = function(t, i) 725 + i = i - 1 726 + local v = t[i] 727 + if v ~= nil then 728 + return i, v 729 + end 730 +end 731 + 732 +function lume.ripairs(t) 733 + return ripairs_iter, t, (#t + 1) 734 +end 735 + 736 + 737 +function lume.color(str, mul) 738 + mul = mul or 1 739 + local r, g, b, a 740 + r, g, b = str:match("#(%x%x)(%x%x)(%x%x)") 741 + if r then 742 + r = tonumber(r, 16) / 0xff 743 + g = tonumber(g, 16) / 0xff 744 + b = tonumber(b, 16) / 0xff 745 + a = 1 746 + elseif str:match("rgba?%s*%([%d%s%.,]+%)") then 747 + local f = str:gmatch("[%d.]+") 748 + r = (f() or 0) / 0xff 749 + g = (f() or 0) / 0xff 750 + b = (f() or 0) / 0xff 751 + a = f() or 1 752 + else 753 + error(("bad color string '%s'"):format(str)) 754 + end 755 + return r * mul, g * mul, b * mul, a * mul 756 +end 757 + 758 + 759 +local chain_mt = {} 760 +chain_mt.__index = lume.map(lume.filter(lume, iscallable, true), 761 + function(fn) 762 + return function(self, ...) 763 + self._value = fn(self._value, ...) 764 + return self 765 + end 766 + end) 767 +chain_mt.__index.result = function(x) return x._value end 768 + 769 +function lume.chain(value) 770 + return setmetatable({ _value = value }, chain_mt) 771 +end 772 + 773 +setmetatable(lume, { 774 + __call = function(_, ...) 775 + return lume.chain(...) 776 + end 777 +}) 778 + 779 + 780 +return lume
Added src/worpt/lib/stdio.fnl.
1 +(require "love.event") 2 +(local view (require "lib.fennelview")) 3 + 4 +;; This module exists in order to expose stdio over a channel so that it 5 +;; can be used in a non-blocking way from another thread. 6 + 7 +(local (event channel) ...) 8 + 9 +(when channel 10 + (let [prompt (fn [] (io.write "> ") (io.flush) (io.read "*l"))] 11 + ((fn looper [input] 12 + (when input 13 + ;; This is consumed by love.handlers[event] 14 + (love.event.push event input) 15 + (let [output (: channel :demand)] 16 + ;; There is probably a more efficient way of determining an error 17 + (if (and (. output 2) (= "Error:" (. output 2))) 18 + (print (view output)) 19 + (each [_ ret (ipairs output)] 20 + (print ret)))) 21 + (io.flush) 22 + (looper (prompt)))) (prompt)))) 23 + 24 +{:start (fn start-repl [] 25 + 26 + (let [code (love.filesystem.read "stdio.fnl") 27 + luac (if code 28 + (love.filesystem.newFileData 29 + (fennel.compileString code) "io") 30 + (love.filesystem.read "lib/stdio.lua")) 31 + thread (love.thread.newThread luac) 32 + io-channel (love.thread.newChannel) 33 + coro (coroutine.create fennel.repl) 34 + out (fn [val] 35 + (: io-channel :push val)) 36 + options {:readChunk coroutine.yield 37 + :onValues out 38 + :onError (fn [kind ...] (out [kind "Error:" ...])) 39 + :pp view 40 + :moduleName "lib.fennel"}] 41 + ;; this thread will send "eval" events for us to consume: 42 + (coroutine.resume coro options) 43 + (: thread :start "eval" io-channel) 44 + (set love.handlers.eval 45 + (fn [input] 46 + (coroutine.resume coro (.. input "\n"))))))}
Added src/worpt/lib/stdio.lua.
1 + require("love.event") 2 + local view = require("lib.fennelview") local event, channel = ... local function _0_(...) if channel then 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + local prompt = nil local function _0_() io.write("> ") io.flush() return io.read("*l") end prompt = _0_ 11 + local function looper(input) if input then 12 + 13 + 14 + love.event.push(event, input) 15 + do local output = channel:demand() 16 + 17 + if (output[2] and ("Error:" == output[2])) then 18 + print(view(output)) else 19 + for _, ret in ipairs(output) do 20 + print(ret) end end end 21 + io.flush() 22 + return looper(prompt()) end end return looper(prompt()) end end _0_(...) 23 + 24 + local function start_repl() 25 + 26 + local code = love.filesystem.read("stdio.fnl") local luac = nil 27 + if code then 28 + luac = love.filesystem.newFileData(fennel.compileString(code), "io") else 29 + 30 + luac = love.filesystem.read("lib/stdio.lua") end 31 + local thread = love.thread.newThread(luac) 32 + local io_channel = love.thread.newChannel() 33 + local coro = coroutine.create(fennel.repl) local out = nil 34 + local function _2_(val) 35 + return io_channel:push(val) end out = _2_ local options = nil 36 + 37 + 38 + local function _3_(kind, ...) return out({kind, "Error:", ...}) end options = {moduleName = "lib.fennel", onError = _3_, onValues = out, pp = view, readChunk = coroutine.yield} 39 + 40 + 41 + 42 + coroutine.resume(coro, options) 43 + thread:start("eval", io_channel) 44 + 45 + local function _4_(input) 46 + return coroutine.resume(coro, (input .. "\n")) end love.handlers.eval = _4_ return nil end return {start = start_repl}
Added src/worpt/lib/sti/graphics.lua.
1 +local lg = _G.love.graphics 2 +local graphics = { isCreated = lg and true or false } 3 + 4 +function graphics.newSpriteBatch(...) 5 + if graphics.isCreated then 6 + return lg.newSpriteBatch(...) 7 + end 8 +end 9 + 10 +function graphics.newCanvas(...) 11 + if graphics.isCreated then 12 + return lg.newCanvas(...) 13 + end 14 +end 15 + 16 +function graphics.newImage(...) 17 + if graphics.isCreated then 18 + return lg.newImage(...) 19 + end 20 +end 21 + 22 +function graphics.newQuad(...) 23 + if graphics.isCreated then 24 + return lg.newQuad(...) 25 + end 26 +end 27 + 28 +function graphics.getCanvas(...) 29 + if graphics.isCreated then 30 + return lg.getCanvas(...) 31 + end 32 +end 33 + 34 +function graphics.setCanvas(...) 35 + if graphics.isCreated then 36 + return lg.setCanvas(...) 37 + end 38 +end 39 + 40 +function graphics.clear(...) 41 + if graphics.isCreated then 42 + return lg.clear(...) 43 + end 44 +end 45 + 46 +function graphics.push(...) 47 + if graphics.isCreated then 48 + return lg.push(...) 49 + end 50 +end 51 + 52 +function graphics.origin(...) 53 + if graphics.isCreated then 54 + return lg.origin(...) 55 + end 56 +end 57 + 58 +function graphics.scale(...) 59 + if graphics.isCreated then 60 + return lg.scale(...) 61 + end 62 +end 63 + 64 +function graphics.translate(...) 65 + if graphics.isCreated then 66 + return lg.translate(...) 67 + end 68 +end 69 + 70 +function graphics.pop(...) 71 + if graphics.isCreated then 72 + return lg.pop(...) 73 + end 74 +end 75 + 76 +function graphics.draw(...) 77 + if graphics.isCreated then 78 + return lg.draw(...) 79 + end 80 +end 81 + 82 +function graphics.rectangle(...) 83 + if graphics.isCreated then 84 + return lg.rectangle(...) 85 + end 86 +end 87 + 88 +function graphics.getColor(...) 89 + if graphics.isCreated then 90 + return lg.getColor(...) 91 + end 92 +end 93 + 94 +function graphics.setColor(...) 95 + if graphics.isCreated then 96 + return lg.setColor(...) 97 + end 98 +end 99 + 100 +function graphics.line(...) 101 + if graphics.isCreated then 102 + return lg.line(...) 103 + end 104 +end 105 + 106 +function graphics.polygon(...) 107 + if graphics.isCreated then 108 + return lg.polygon(...) 109 + end 110 +end 111 + 112 +function graphics.points(...) 113 + if graphics.isCreated then 114 + return lg.points(...) 115 + end 116 +end 117 + 118 +function graphics.getWidth() 119 + if graphics.isCreated then 120 + return lg.getWidth() 121 + end 122 + return 0 123 +end 124 + 125 +function graphics.getHeight() 126 + if graphics.isCreated then 127 + return lg.getHeight() 128 + end 129 + return 0 130 +end 131 + 132 +return graphics
Added src/worpt/lib/sti/init.lua.
1 +--- Simple and fast Tiled map loader and renderer. 2 +-- @module sti 3 +-- @author Landon Manning 4 +-- @copyright 2019 5 +-- @license MIT/X11 6 + 7 +local STI = { 8 + _LICENSE = "MIT/X11", 9 + _URL = "https://github.com/karai17/Simple-Tiled-Implementation", 10 + _VERSION = "1.2.3.0", 11 + _DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.", 12 + cache = {} 13 +} 14 +STI.__index = STI 15 + 16 +local love = _G.love 17 +local cwd = (...):gsub('%.init$', '') .. "." 18 +local utils = require(cwd .. "utils") 19 +local ceil = math.ceil 20 +local floor = math.floor 21 +local lg = require(cwd .. "graphics") 22 +local Map = {} 23 +Map.__index = Map 24 + 25 +local function new(map, plugins, ox, oy) 26 + local dir = "" 27 + 28 + if type(map) == "table" then 29 + map = setmetatable(map, Map) 30 + else 31 + -- Check for valid map type 32 + local ext = map:sub(-4, -1) 33 + assert(ext == ".lua", string.format( 34 + "Invalid file type: %s. File must be of type: lua.", 35 + ext 36 + )) 37 + 38 + -- Get directory of map 39 + dir = map:reverse():find("[/\\]") or "" 40 + if dir ~= "" then 41 + dir = map:sub(1, 1 + (#map - dir)) 42 + end 43 + 44 + -- Load map 45 + map = setmetatable(assert(love.filesystem.load(map))(), Map) 46 + end 47 + 48 + map:init(dir, plugins, ox, oy) 49 + 50 + return map 51 +end 52 + 53 +--- Instance a new map. 54 +-- @param map Path to the map file or the map table itself 55 +-- @param plugins A list of plugins to load 56 +-- @param ox Offset of map on the X axis (in pixels) 57 +-- @param oy Offset of map on the Y axis (in pixels) 58 +-- @return table The loaded Map 59 +function STI.__call(_, map, plugins, ox, oy) 60 + return new(map, plugins, ox, oy) 61 +end 62 + 63 +--- Flush image cache. 64 +function STI:flush() 65 + self.cache = {} 66 +end 67 + 68 +--- Map object 69 + 70 +--- Instance a new map 71 +-- @param path Path to the map file 72 +-- @param plugins A list of plugins to load 73 +-- @param ox Offset of map on the X axis (in pixels) 74 +-- @param oy Offset of map on the Y axis (in pixels) 75 +function Map:init(path, plugins, ox, oy) 76 + if type(plugins) == "table" then 77 + self:loadPlugins(plugins) 78 + end 79 + 80 + self:resize() 81 + self.objects = {} 82 + self.tiles = {} 83 + self.tileInstances = {} 84 + self.drawRange = { 85 + sx = 1, 86 + sy = 1, 87 + ex = self.width, 88 + ey = self.height, 89 + } 90 + self.offsetx = ox or 0 91 + self.offsety = oy or 0 92 + 93 + self.freeBatchSprites = {} 94 + setmetatable(self.freeBatchSprites, { __mode = 'k' }) 95 + 96 + -- Set tiles, images 97 + local gid = 1 98 + for i, tileset in ipairs(self.tilesets) do 99 + assert(tileset.image, "STI does not support Tile Collections.\nYou need to create a Texture Atlas.") 100 + 101 + -- Cache images 102 + if lg.isCreated then 103 + local formatted_path = utils.format_path(path .. tileset.image) 104 + 105 + if not STI.cache[formatted_path] then 106 + utils.fix_transparent_color(tileset, formatted_path) 107 + utils.cache_image(STI, formatted_path, tileset.image) 108 + else 109 + tileset.image = STI.cache[formatted_path] 110 + end 111 + end 112 + 113 + gid = self:setTiles(i, tileset, gid) 114 + end 115 + 116 + local layers = {} 117 + for _, layer in ipairs(self.layers) do 118 + self:groupAppendToList(layers, layer) 119 + end 120 + self.layers = layers 121 + 122 + -- Set layers 123 + for _, layer in ipairs(self.layers) do 124 + self:setLayer(layer, path) 125 + end 126 +end 127 + 128 +--- Layers from the group are added to the list 129 +-- @param layers List of layers 130 +-- @param layer Layer data 131 +function Map:groupAppendToList(layers, layer) 132 + if layer.type == "group" then 133 + for _, groupLayer in pairs(layer.layers) do 134 + groupLayer.name = layer.name .. "." .. groupLayer.name 135 + groupLayer.visible = layer.visible 136 + groupLayer.opacity = layer.opacity * groupLayer.opacity 137 + groupLayer.offsetx = layer.offsetx + groupLayer.offsetx 138 + groupLayer.offsety = layer.offsety + groupLayer.offsety 139 + 140 + for key, property in pairs(layer.properties) do 141 + if groupLayer.properties[key] == nil then 142 + groupLayer.properties[key] = property 143 + end 144 + end 145 + 146 + self:groupAppendToList(layers, groupLayer) 147 + end 148 + else 149 + table.insert(layers, layer) 150 + end 151 +end 152 + 153 +--- Load plugins 154 +-- @param plugins A list of plugins to load 155 +function Map:loadPlugins(plugins) 156 + for _, plugin in ipairs(plugins) do 157 + local pluginModulePath = cwd .. 'plugins.' .. plugin 158 + local ok, pluginModule = pcall(require, pluginModulePath) 159 + if ok then 160 + for k, func in pairs(pluginModule) do 161 + if not self[k] then 162 + self[k] = func 163 + end 164 + end 165 + end 166 + end 167 +end 168 + 169 +--- Create Tiles 170 +-- @param index Index of the Tileset 171 +-- @param tileset Tileset data 172 +-- @param gid First Global ID in Tileset 173 +-- @return number Next Tileset's first Global ID 174 +function Map:setTiles(index, tileset, gid) 175 + local quad = lg.newQuad 176 + local imageW = tileset.imagewidth 177 + local imageH = tileset.imageheight 178 + local tileW = tileset.tilewidth 179 + local tileH = tileset.tileheight 180 + local margin = tileset.margin 181 + local spacing = tileset.spacing 182 + local w = utils.get_tiles(imageW, tileW, margin, spacing) 183 + local h = utils.get_tiles(imageH, tileH, margin, spacing) 184 + 185 + for y = 1, h do 186 + for x = 1, w do 187 + local id = gid - tileset.firstgid 188 + local quadX = (x - 1) * tileW + margin + (x - 1) * spacing 189 + local quadY = (y - 1) * tileH + margin + (y - 1) * spacing 190 + local type = "" 191 + local properties, terrain, animation, objectGroup 192 + 193 + for _, tile in pairs(tileset.tiles) do 194 + if tile.id == id then 195 + properties = tile.properties 196 + animation = tile.animation 197 + objectGroup = tile.objectGroup 198 + type = tile.type 199 + 200 + if tile.terrain then 201 + terrain = {} 202 + 203 + for i = 1, #tile.terrain do 204 + terrain[i] = tileset.terrains[tile.terrain[i] + 1] 205 + end 206 + end 207 + end 208 + end 209 + 210 + local tile = { 211 + id = id, 212 + gid = gid, 213 + tileset = index, 214 + type = type, 215 + quad = quad( 216 + quadX, quadY, 217 + tileW, tileH, 218 + imageW, imageH 219 + ), 220 + properties = properties or {}, 221 + terrain = terrain, 222 + animation = animation, 223 + objectGroup = objectGroup, 224 + frame = 1, 225 + time = 0, 226 + width = tileW, 227 + height = tileH, 228 + sx = 1, 229 + sy = 1, 230 + r = 0, 231 + offset = tileset.tileoffset, 232 + } 233 + 234 + self.tiles[gid] = tile 235 + gid = gid + 1 236 + end 237 + end 238 + 239 + return gid 240 +end 241 + 242 +--- Create Layers 243 +-- @param layer Layer data 244 +-- @param path (Optional) Path to an Image Layer's image 245 +function Map:setLayer(layer, path) 246 + if layer.encoding then 247 + if layer.encoding == "base64" then 248 + assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".") 249 + local fd = love.data.decode("string", "base64", layer.data) 250 + 251 + if not layer.compression then 252 + layer.data = utils.get_decompressed_data(fd) 253 + else 254 + assert(love.data.decompress, "zlib and gzip compression require LOVE 11.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".") 255 + 256 + if layer.compression == "zlib" then 257 + local data = love.data.decompress("string", "zlib", fd) 258 + layer.data = utils.get_decompressed_data(data) 259 + end 260 + 261 + if layer.compression == "gzip" then 262 + local data = love.data.decompress("string", "gzip", fd) 263 + layer.data = utils.get_decompressed_data(data) 264 + end 265 + end 266 + end 267 + end 268 + 269 + layer.x = (layer.x or 0) + layer.offsetx + self.offsetx 270 + layer.y = (layer.y or 0) + layer.offsety + self.offsety 271 + layer.update = function() end 272 + 273 + if layer.type == "tilelayer" then 274 + self:setTileData(layer) 275 + self:setSpriteBatches(layer) 276 + layer.draw = function() self:drawTileLayer(layer) end 277 + elseif layer.type == "objectgroup" then 278 + self:setObjectData(layer) 279 + self:setObjectCoordinates(layer) 280 + self:setObjectSpriteBatches(layer) 281 + layer.draw = function() self:drawObjectLayer(layer) end 282 + elseif layer.type == "imagelayer" then 283 + layer.draw = function() self:drawImageLayer(layer) end 284 + 285 + if layer.image ~= "" then 286 + local formatted_path = utils.format_path(path .. layer.image) 287 + if not STI.cache[formatted_path] then 288 + utils.cache_image(STI, formatted_path) 289 + end 290 + 291 + layer.image = STI.cache[formatted_path] 292 + layer.width = layer.image:getWidth() 293 + layer.height = layer.image:getHeight() 294 + end 295 + end 296 + 297 + self.layers[layer.name] = layer 298 +end 299 + 300 +--- Add Tiles to Tile Layer 301 +-- @param layer The Tile Layer 302 +function Map:setTileData(layer) 303 + if layer.chunks then 304 + for _, chunk in ipairs(layer.chunks) do 305 + self:setTileData(chunk) 306 + end 307 + return 308 + end 309 + 310 + local i = 1 311 + local map = {} 312 + 313 + for y = 1, layer.height do 314 + map[y] = {} 315 + for x = 1, layer.width do 316 + local gid = layer.data[i] 317 + 318 + -- NOTE: Empty tiles have a GID of 0 319 + if gid > 0 then 320 + map[y][x] = self.tiles[gid] or self:setFlippedGID(gid) 321 + end 322 + 323 + i = i + 1 324 + end 325 + end 326 + 327 + layer.data = map 328 +end 329 + 330 +--- Add Objects to Layer 331 +-- @param layer The Object Layer 332 +function Map:setObjectData(layer) 333 + for _, object in ipairs(layer.objects) do 334 + object.layer = layer 335 + self.objects[object.id] = object 336 + end 337 +end 338 + 339 +--- Correct position and orientation of Objects in an Object Layer 340 +-- @param layer The Object Layer 341 +function Map:setObjectCoordinates(layer) 342 + for _, object in ipairs(layer.objects) do 343 + local x = layer.x + object.x 344 + local y = layer.y + object.y 345 + local w = object.width 346 + local h = object.height 347 + local cos = math.cos(math.rad(object.rotation)) 348 + local sin = math.sin(math.rad(object.rotation)) 349 + 350 + if object.shape == "rectangle" and not object.gid then 351 + object.rectangle = {} 352 + 353 + local vertices = { 354 + { x=x, y=y }, 355 + { x=x + w, y=y }, 356 + { x=x + w, y=y + h }, 357 + { x=x, y=y + h }, 358 + } 359 + 360 + for _, vertex in ipairs(vertices) do 361 + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 362 + table.insert(object.rectangle, { x = vertex.x, y = vertex.y }) 363 + end 364 + elseif object.shape == "ellipse" then 365 + object.ellipse = {} 366 + local vertices = utils.convert_ellipse_to_polygon(x, y, w, h) 367 + 368 + for _, vertex in ipairs(vertices) do 369 + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 370 + table.insert(object.ellipse, { x = vertex.x, y = vertex.y }) 371 + end 372 + elseif object.shape == "polygon" then 373 + for _, vertex in ipairs(object.polygon) do 374 + vertex.x = vertex.x + x 375 + vertex.y = vertex.y + y 376 + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 377 + end 378 + elseif object.shape == "polyline" then 379 + for _, vertex in ipairs(object.polyline) do 380 + vertex.x = vertex.x + x 381 + vertex.y = vertex.y + y 382 + vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin) 383 + end 384 + end 385 + end 386 +end 387 + 388 +--- Convert tile location to tile instance location 389 +-- @param layer Tile layer 390 +-- @param tile Tile 391 +-- @param x Tile location on X axis (in tiles) 392 +-- @param y Tile location on Y axis (in tiles) 393 +-- @return number Tile instance location on X axis (in pixels) 394 +-- @return number Tile instance location on Y axis (in pixels) 395 +function Map:getLayerTilePosition(layer, tile, x, y) 396 + local tileW = self.tilewidth 397 + local tileH = self.tileheight 398 + local tileX, tileY 399 + 400 + if self.orientation == "orthogonal" then 401 + local tileset = self.tilesets[tile.tileset] 402 + tileX = (x - 1) * tileW + tile.offset.x 403 + tileY = (y - 0) * tileH + tile.offset.y - tileset.tileheight 404 + tileX, tileY = utils.compensate(tile, tileX, tileY, tileW, tileH) 405 + elseif self.orientation == "isometric" then 406 + tileX = (x - y) * (tileW / 2) + tile.offset.x + layer.width * tileW / 2 - self.tilewidth / 2 407 + tileY = (x + y - 2) * (tileH / 2) + tile.offset.y 408 + else 409 + local sideLen = self.hexsidelength or 0 410 + if self.staggeraxis == "y" then 411 + if self.staggerindex == "odd" then 412 + if y % 2 == 0 then 413 + tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x 414 + else 415 + tileX = (x - 1) * tileW + tile.offset.x 416 + end 417 + else 418 + if y % 2 == 0 then 419 + tileX = (x - 1) * tileW + tile.offset.x 420 + else 421 + tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x 422 + end 423 + end 424 + 425 + local rowH = tileH - (tileH - sideLen) / 2 426 + tileY = (y - 1) * rowH + tile.offset.y 427 + else 428 + if self.staggerindex == "odd" then 429 + if x % 2 == 0 then 430 + tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y 431 + else 432 + tileY = (y - 1) * tileH + tile.offset.y 433 + end 434 + else 435 + if x % 2 == 0 then 436 + tileY = (y - 1) * tileH + tile.offset.y 437 + else 438 + tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y 439 + end 440 + end 441 + 442 + local colW = tileW - (tileW - sideLen) / 2 443 + tileX = (x - 1) * colW + tile.offset.x 444 + end 445 + end 446 + 447 + return tileX, tileY 448 +end 449 + 450 +--- Place new tile instance 451 +-- @param layer Tile layer 452 +-- @param chunk Layer chunk 453 +-- @param tile Tile 454 +-- @param number Tile location on X axis (in tiles) 455 +-- @param number Tile location on Y axis (in tiles) 456 +function Map:addNewLayerTile(layer, chunk, tile, x, y) 457 + local tileset = tile.tileset 458 + local image = self.tilesets[tile.tileset].image 459 + local batches 460 + local size 461 + 462 + if chunk then 463 + batches = chunk.batches 464 + size = chunk.width * chunk.height 465 + else 466 + batches = layer.batches 467 + size = layer.width * layer.height 468 + end 469 + 470 + batches[tileset] = batches[tileset] or lg.newSpriteBatch(image, size) 471 + 472 + local batch = batches[tileset] 473 + local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) 474 + 475 + local instance = { 476 + layer = layer, 477 + chunk = chunk, 478 + gid = tile.gid, 479 + x = tileX, 480 + y = tileY, 481 + r = tile.r, 482 + oy = 0 483 + } 484 + 485 + -- NOTE: STI can run headless so it is not guaranteed that a batch exists. 486 + if batch then 487 + instance.batch = batch 488 + instance.id = batch:add(tile.quad, tileX, tileY, tile.r, tile.sx, tile.sy) 489 + end 490 + 491 + self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} 492 + table.insert(self.tileInstances[tile.gid], instance) 493 +end 494 + 495 +function Map:set_batches(layer, chunk) 496 + if chunk then 497 + chunk.batches = {} 498 + else 499 + layer.batches = {} 500 + end 501 + 502 + if self.orientation == "orthogonal" or self.orientation == "isometric" then 503 + local offsetX = chunk and chunk.x or 0 504 + local offsetY = chunk and chunk.y or 0 505 + 506 + local startX = 1 507 + local startY = 1 508 + local endX = chunk and chunk.width or layer.width 509 + local endY = chunk and chunk.height or layer.height 510 + local incrementX = 1 511 + local incrementY = 1 512 + 513 + -- Determine order to add tiles to sprite batch 514 + -- Defaults to right-down 515 + if self.renderorder == "right-up" then 516 + startY, endY, incrementY = endY, startY, -1 517 + elseif self.renderorder == "left-down" then 518 + startX, endX, incrementX = endX, startX, -1 519 + elseif self.renderorder == "left-up" then 520 + startX, endX, incrementX = endX, startX, -1 521 + startY, endY, incrementY = endY, startY, -1 522 + end 523 + 524 + for y = startY, endY, incrementY do 525 + for x = startX, endX, incrementX do 526 + -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil 527 + local tile 528 + if chunk then 529 + tile = chunk.data[y][x] 530 + else 531 + tile = layer.data[y][x] 532 + end 533 + 534 + if tile then 535 + self:addNewLayerTile(layer, chunk, tile, x + offsetX, y + offsetY) 536 + end 537 + end 538 + end 539 + else 540 + if self.staggeraxis == "y" then 541 + for y = 1, (chunk and chunk.height or layer.height) do 542 + for x = 1, (chunk and chunk.width or layer.width) do 543 + -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil 544 + local tile 545 + if chunk then 546 + tile = chunk.data[y][x] 547 + else 548 + tile = layer.data[y][x] 549 + end 550 + 551 + if tile then 552 + self:addNewLayerTile(layer, chunk, tile, x, y) 553 + end 554 + end 555 + end 556 + else 557 + local i = 0 558 + local _x 559 + 560 + if self.staggerindex == "odd" then 561 + _x = 1 562 + else 563 + _x = 2 564 + end 565 + 566 + while i < (chunk and chunk.width * chunk.height or layer.width * layer.height) do 567 + for _y = 1, (chunk and chunk.height or layer.height) + 0.5, 0.5 do 568 + local y = floor(_y) 569 + 570 + for x = _x, (chunk and chunk.width or layer.width), 2 do 571 + i = i + 1 572 + 573 + -- NOTE: Cannot short circuit this since it is valid for tile to be assigned nil 574 + local tile 575 + if chunk then 576 + tile = chunk.data[y][x] 577 + else 578 + tile = layer.data[y][x] 579 + end 580 + 581 + if tile then 582 + self:addNewLayerTile(layer, chunk, tile, x, y) 583 + end 584 + end 585 + 586 + if _x == 1 then 587 + _x = 2 588 + else 589 + _x = 1 590 + end 591 + end 592 + end 593 + end 594 + end 595 +end 596 + 597 +--- Batch Tiles in Tile Layer for improved draw speed 598 +-- @param layer The Tile Layer 599 +function Map:setSpriteBatches(layer) 600 + if layer.chunks then 601 + for _, chunk in ipairs(layer.chunks) do 602 + self:set_batches(layer, chunk) 603 + end 604 + return 605 + end 606 + 607 + self:set_batches(layer) 608 +end 609 + 610 +--- Batch Tiles in Object Layer for improved draw speed 611 +-- @param layer The Object Layer 612 +function Map:setObjectSpriteBatches(layer) 613 + local newBatch = lg.newSpriteBatch 614 + local batches = {} 615 + 616 + if layer.draworder == "topdown" then 617 + table.sort(layer.objects, function(a, b) 618 + return a.y + a.height < b.y + b.height 619 + end) 620 + end 621 + 622 + for _, object in ipairs(layer.objects) do 623 + if object.visible ~= false and object.gid then 624 + local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid) 625 + local tileset = tile.tileset 626 + local image = self.tilesets[tileset].image 627 + 628 + batches[tileset] = batches[tileset] or newBatch(image) 629 + 630 + local sx = object.width / tile.width 631 + local sy = object.height / tile.height 632 + 633 + -- Tiled rotates around bottom left corner, where love2D rotates around top left corner 634 + local ox = 0 635 + local oy = tile.height 636 + 637 + local batch = batches[tileset] 638 + local tileX = object.x + tile.offset.x 639 + local tileY = object.y + tile.offset.y 640 + local tileR = math.rad(object.rotation) 641 + 642 + -- Compensation for scale/rotation shift 643 + if tile.sx == -1 then 644 + tileX = tileX + object.width 645 + 646 + if tileR ~= 0 then 647 + tileX = tileX - object.width 648 + ox = ox + tile.width 649 + end 650 + end 651 + 652 + if tile.sy == -1 then 653 + tileY = tileY - object.height 654 + 655 + if tileR ~= 0 then 656 + tileY = tileY + object.width 657 + oy = oy - tile.width 658 + end 659 + end 660 + 661 + local instance = { 662 + id = batch:add(tile.quad, tileX, tileY, tileR, tile.sx * sx, tile.sy * sy, ox, oy), 663 + batch = batch, 664 + layer = layer, 665 + gid = tile.gid, 666 + x = tileX, 667 + y = tileY - oy, 668 + r = tileR, 669 + oy = oy 670 + } 671 + 672 + self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} 673 + table.insert(self.tileInstances[tile.gid], instance) 674 + end 675 + end 676 + 677 + layer.batches = batches 678 +end 679 + 680 +--- Create a Custom Layer to place userdata in (such as player sprites) 681 +-- @param name Name of Custom Layer 682 +-- @param index Draw order within Layer stack 683 +-- @return table Custom Layer 684 +function Map:addCustomLayer(name, index) 685 + index = index or #self.layers + 1 686 + local layer = { 687 + type = "customlayer", 688 + name = name, 689 + visible = true, 690 + opacity = 1, 691 + properties = {}, 692 + } 693 + 694 + function layer.draw() end 695 + function layer.update() end 696 + 697 + table.insert(self.layers, index, layer) 698 + self.layers[name] = self.layers[index] 699 + 700 + return layer 701 +end 702 + 703 +--- Convert another Layer into a Custom Layer 704 +-- @param index Index or name of Layer to convert 705 +-- @return table Custom Layer 706 +function Map:convertToCustomLayer(index) 707 + local layer = assert(self.layers[index], "Layer not found: " .. index) 708 + 709 + layer.type = "customlayer" 710 + layer.x = nil 711 + layer.y = nil 712 + layer.width = nil 713 + layer.height = nil 714 + layer.encoding = nil 715 + layer.data = nil 716 + layer.chunks = nil 717 + layer.objects = nil 718 + layer.image = nil 719 + 720 + function layer.draw() end 721 + function layer.update() end 722 + 723 + return layer 724 +end 725 + 726 +--- Remove a Layer from the Layer stack 727 +-- @param index Index or name of Layer to remove 728 +function Map:removeLayer(index) 729 + local layer = assert(self.layers[index], "Layer not found: " .. index) 730 + 731 + if type(index) == "string" then 732 + for i, l in ipairs(self.layers) do 733 + if l.name == index then 734 + table.remove(self.layers, i) 735 + self.layers[index] = nil 736 + break 737 + end 738 + end 739 + else 740 + local name = self.layers[index].name 741 + table.remove(self.layers, index) 742 + self.layers[name] = nil 743 + end 744 + 745 + -- Remove layer batches 746 + if layer.batches then 747 + for _, batch in pairs(layer.batches) do 748 + self.freeBatchSprites[batch] = nil 749 + end 750 + end 751 + 752 + -- Remove chunk batches 753 + if layer.chunks then 754 + for _, chunk in ipairs(layer.chunks) do 755 + for _, batch in pairs(chunk.batches) do 756 + self.freeBatchSprites[batch] = nil 757 + end 758 + end 759 + end 760 + 761 + -- Remove tile instances 762 + if layer.type == "tilelayer" then 763 + for _, tiles in pairs(self.tileInstances) do 764 + for i = #tiles, 1, -1 do 765 + local tile = tiles[i] 766 + if tile.layer == layer then 767 + table.remove(tiles, i) 768 + end 769 + end 770 + end 771 + end 772 + 773 + -- Remove objects 774 + if layer.objects then 775 + for i, object in pairs(self.objects) do 776 + if object.layer == layer then 777 + self.objects[i] = nil 778 + end 779 + end 780 + end 781 +end 782 + 783 +--- Animate Tiles and update every Layer 784 +-- @param dt Delta Time 785 +function Map:update(dt) 786 + for _, tile in pairs(self.tiles) do 787 + local update = false 788 + 789 + if tile.animation then 790 + tile.time = tile.time + dt * 1000 791 + 792 + while tile.time > tonumber(tile.animation[tile.frame].duration) do 793 + update = true 794 + tile.time = tile.time - tonumber(tile.animation[tile.frame].duration) 795 + tile.frame = tile.frame + 1 796 + 797 + if tile.frame > #tile.animation then tile.frame = 1 end 798 + end 799 + 800 + if update and self.tileInstances[tile.gid] then 801 + for _, j in pairs(self.tileInstances[tile.gid]) do 802 + local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid] 803 + j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy) 804 + end 805 + end 806 + end 807 + end 808 + 809 + for _, layer in ipairs(self.layers) do 810 + layer:update(dt) 811 + end 812 +end 813 + 814 +--- Draw every Layer 815 +-- @param tx Translate on X 816 +-- @param ty Translate on Y 817 +-- @param sx Scale on X 818 +-- @param sy Scale on Y 819 +function Map:draw(tx, ty, sx, sy) 820 + local current_canvas = lg.getCanvas() 821 + lg.setCanvas(self.canvas) 822 + lg.clear() 823 + 824 + -- Scale map to 1.0 to draw onto canvas, this fixes tearing issues 825 + -- Map is translated to correct position so the right section is drawn 826 + lg.push() 827 + lg.origin() 828 + lg.translate(math.floor(tx or 0), math.floor(ty or 0)) 829 + 830 + for _, layer in ipairs(self.layers) do 831 + if layer.visible and layer.opacity > 0 then 832 + self:drawLayer(layer) 833 + end 834 + end 835 + 836 + lg.pop() 837 + 838 + -- Draw canvas at 0,0; this fixes scissoring issues 839 + -- Map is scaled to correct scale so the right section is shown 840 + lg.push() 841 + lg.origin() 842 + lg.scale(sx or 1, sy or sx or 1) 843 + 844 + lg.setCanvas(current_canvas) 845 + lg.draw(self.canvas) 846 + 847 + lg.pop() 848 +end 849 + 850 +--- Draw an individual Layer 851 +-- @param layer The Layer to draw 852 +function Map.drawLayer(_, layer) 853 + local r,g,b,a = lg.getColor() 854 + lg.setColor(r, g, b, a * layer.opacity) 855 + layer:draw() 856 + lg.setColor(r,g,b,a) 857 +end 858 + 859 +--- Default draw function for Tile Layers 860 +-- @param layer The Tile Layer to draw 861 +function Map:drawTileLayer(layer) 862 + if type(layer) == "string" or type(layer) == "number" then 863 + layer = self.layers[layer] 864 + end 865 + 866 + assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer") 867 + 868 + -- NOTE: This does not take into account any sort of draw range clipping and will always draw every chunk 869 + if layer.chunks then 870 + for _, chunk in ipairs(layer.chunks) do 871 + for _, batch in pairs(chunk.batches) do 872 + lg.draw(batch, 0, 0) 873 + end 874 + end 875 + 876 + return 877 + end 878 + 879 + for _, batch in pairs(layer.batches) do 880 + lg.draw(batch, floor(layer.x), floor(layer.y)) 881 + end 882 +end 883 + 884 +--- Default draw function for Object Layers 885 +-- @param layer The Object Layer to draw 886 +function Map:drawObjectLayer(layer) 887 + if type(layer) == "string" or type(layer) == "number" then 888 + layer = self.layers[layer] 889 + end 890 + 891 + assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup") 892 + 893 + local line = { 160, 160, 160, 255 * layer.opacity } 894 + local fill = { 160, 160, 160, 255 * layer.opacity * 0.5 } 895 + local r,g,b,a = lg.getColor() 896 + local reset = { r, g, b, a * layer.opacity } 897 + 898 + local function sortVertices(obj) 899 + local vertex = {} 900 + 901 + for _, v in ipairs(obj) do 902 + table.insert(vertex, v.x) 903 + table.insert(vertex, v.y) 904 + end 905 + 906 + return vertex 907 + end 908 + 909 + local function drawShape(obj, shape) 910 + local vertex = sortVertices(obj) 911 + 912 + if shape == "polyline" then 913 + lg.setColor(line) 914 + lg.line(vertex) 915 + return 916 + elseif shape == "polygon" then 917 + lg.setColor(fill) 918 + if not love.math.isConvex(vertex) then 919 + local triangles = love.math.triangulate(vertex) 920 + for _, triangle in ipairs(triangles) do 921 + lg.polygon("fill", triangle) 922 + end 923 + else 924 + lg.polygon("fill", vertex) 925 + end 926 + else 927 + lg.setColor(fill) 928 + lg.polygon("fill", vertex) 929 + end 930 + 931 + lg.setColor(line) 932 + lg.polygon("line", vertex) 933 + end 934 + 935 + for _, object in ipairs(layer.objects) do 936 + if object.visible ~= false then 937 + if object.shape == "rectangle" and not object.gid then 938 + drawShape(object.rectangle, "rectangle") 939 + elseif object.shape == "ellipse" then 940 + drawShape(object.ellipse, "ellipse") 941 + elseif object.shape == "polygon" then 942 + drawShape(object.polygon, "polygon") 943 + elseif object.shape == "polyline" then 944 + drawShape(object.polyline, "polyline") 945 + elseif object.shape == "point" then 946 + lg.points(object.x, object.y) 947 + end 948 + end 949 + end 950 + 951 + lg.setColor(reset) 952 + for _, batch in pairs(layer.batches) do 953 + lg.draw(batch, 0, 0) 954 + end 955 + lg.setColor(r,g,b,a) 956 +end 957 + 958 +--- Default draw function for Image Layers 959 +-- @param layer The Image Layer to draw 960 +function Map:drawImageLayer(layer) 961 + if type(layer) == "string" or type(layer) == "number" then 962 + layer = self.layers[layer] 963 + end 964 + 965 + assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer") 966 + 967 + if layer.image ~= "" then 968 + lg.draw(layer.image, layer.x, layer.y) 969 + end 970 +end 971 + 972 +--- Resize the drawable area of the Map 973 +-- @param w The new width of the drawable area (in pixels) 974 +-- @param h The new Height of the drawable area (in pixels) 975 +function Map:resize(w, h) 976 + if lg.isCreated then 977 + w = w or lg.getWidth() 978 + h = h or lg.getHeight() 979 + 980 + self.canvas = lg.newCanvas(w, h) 981 + self.canvas:setFilter("nearest", "nearest") 982 + end 983 +end 984 + 985 +--- Create flipped or rotated Tiles based on bitop flags 986 +-- @param gid The flagged Global ID 987 +-- @return table Flipped Tile 988 +function Map:setFlippedGID(gid) 989 + local bit31 = 2147483648 990 + local bit30 = 1073741824 991 + local bit29 = 536870912 992 + local flipX = false 993 + local flipY = false 994 + local flipD = false 995 + local realgid = gid 996 + 997 + if realgid >= bit31 then 998 + realgid = realgid - bit31 999 + flipX = not flipX 1000 + end 1001 + 1002 + if realgid >= bit30 then 1003 + realgid = realgid - bit30 1004 + flipY = not flipY 1005 + end 1006 + 1007 + if realgid >= bit29 then 1008 + realgid = realgid - bit29 1009 + flipD = not flipD 1010 + end 1011 + 1012 + local tile = self.tiles[realgid] 1013 + local data = { 1014 + id = tile.id, 1015 + gid = gid, 1016 + tileset = tile.tileset, 1017 + frame = tile.frame, 1018 + time = tile.time, 1019 + width = tile.width, 1020 + height = tile.height, 1021 + offset = tile.offset, 1022 + quad = tile.quad, 1023 + properties = tile.properties, 1024 + terrain = tile.terrain, 1025 + animation = tile.animation, 1026 + sx = tile.sx, 1027 + sy = tile.sy, 1028 + r = tile.r, 1029 + } 1030 + 1031 + if flipX then 1032 + if flipY and flipD then 1033 + data.r = math.rad(-90) 1034 + data.sy = -1 1035 + elseif flipY then 1036 + data.sx = -1 1037 + data.sy = -1 1038 + elseif flipD then 1039 + data.r = math.rad(90) 1040 + else 1041 + data.sx = -1 1042 + end 1043 + elseif flipY then 1044 + if flipD then 1045 + data.r = math.rad(-90) 1046 + else 1047 + data.sy = -1 1048 + end 1049 + elseif flipD then 1050 + data.r = math.rad(90) 1051 + data.sy = -1 1052 + end 1053 + 1054 + self.tiles[gid] = data 1055 + 1056 + return self.tiles[gid] 1057 +end 1058 + 1059 +--- Get custom properties from Layer 1060 +-- @param layer The Layer 1061 +-- @return table List of properties 1062 +function Map:getLayerProperties(layer) 1063 + local l = self.layers[layer] 1064 + 1065 + if not l then 1066 + return {} 1067 + end 1068 + 1069 + return l.properties 1070 +end 1071 + 1072 +--- Get custom properties from Tile 1073 +-- @param layer The Layer that the Tile belongs to 1074 +-- @param x The X axis location of the Tile (in tiles) 1075 +-- @param y The Y axis location of the Tile (in tiles) 1076 +-- @return table List of properties 1077 +function Map:getTileProperties(layer, x, y) 1078 + local tile = self.layers[layer].data[y][x] 1079 + 1080 + if not tile then 1081 + return {} 1082 + end 1083 + 1084 + return tile.properties 1085 +end 1086 + 1087 +--- Get custom properties from Object 1088 +-- @param layer The Layer that the Object belongs to 1089 +-- @param object The index or name of the Object 1090 +-- @return table List of properties 1091 +function Map:getObjectProperties(layer, object) 1092 + local o = self.layers[layer].objects 1093 + 1094 + if type(object) == "number" then 1095 + o = o[object] 1096 + else 1097 + for _, v in ipairs(o) do 1098 + if v.name == object then 1099 + o = v 1100 + break 1101 + end 1102 + end 1103 + end 1104 + 1105 + if not o then 1106 + return {} 1107 + end 1108 + 1109 + return o.properties 1110 +end 1111 + 1112 +--- Change a tile in a layer to another tile 1113 +-- @param layer The Layer that the Tile belongs to 1114 +-- @param x The X axis location of the Tile (in tiles) 1115 +-- @param y The Y axis location of the Tile (in tiles) 1116 +-- @param gid The gid of the new tile 1117 +function Map:setLayerTile(layer, x, y, gid) 1118 + layer = self.layers[layer] 1119 + 1120 + layer.data[y] = layer.data[y] or {} 1121 + local tile = layer.data[y][x] 1122 + local instance 1123 + if tile then 1124 + local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y) 1125 + for _, inst in pairs(self.tileInstances[tile.gid]) do 1126 + if inst.x == tileX and inst.y == tileY then 1127 + instance = inst 1128 + break 1129 + end 1130 + end 1131 + end 1132 + 1133 + if tile == self.tiles[gid] then 1134 + return 1135 + end 1136 + 1137 + tile = self.tiles[gid] 1138 + 1139 + if instance then 1140 + self:swapTile(instance, tile) 1141 + else 1142 + self:addNewLayerTile(layer, tile, x, y) 1143 + end 1144 + layer.data[y][x] = tile 1145 +end 1146 + 1147 +--- Swap a tile in a spritebatch 1148 +-- @param instance The current Instance object we want to replace 1149 +-- @param tile The Tile object we want to use 1150 +-- @return none 1151 +function Map:swapTile(instance, tile) 1152 + -- Update sprite batch 1153 + if instance.batch then 1154 + if tile then 1155 + instance.batch:set( 1156 + instance.id, 1157 + tile.quad, 1158 + instance.x, 1159 + instance.y, 1160 + tile.r, 1161 + tile.sx, 1162 + tile.sy 1163 + ) 1164 + else 1165 + instance.batch:set( 1166 + instance.id, 1167 + instance.x, 1168 + instance.y, 1169 + 0, 1170 + 0) 1171 + 1172 + self.freeBatchSprites[instance.batch] = self.freeBatchSprites[instance.batch] or {} 1173 + table.insert(self.freeBatchSprites[instance.batch], instance) 1174 + end 1175 + end 1176 + 1177 + -- Remove old tile instance 1178 + for i, ins in ipairs(self.tileInstances[instance.gid]) do 1179 + if ins.batch == instance.batch and ins.id == instance.id then 1180 + table.remove(self.tileInstances[instance.gid], i) 1181 + break 1182 + end 1183 + end 1184 + 1185 + -- Add new tile instance 1186 + if tile then 1187 + self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {} 1188 + 1189 + local freeBatchSprites = self.freeBatchSprites[instance.batch] 1190 + local newInstance 1191 + if freeBatchSprites and #freeBatchSprites > 0 then 1192 + newInstance = freeBatchSprites[#freeBatchSprites] 1193 + freeBatchSprites[#freeBatchSprites] = nil 1194 + else 1195 + newInstance = {} 1196 + end 1197 + 1198 + newInstance.layer = instance.layer 1199 + newInstance.batch = instance.batch 1200 + newInstance.id = instance.id 1201 + newInstance.gid = tile.gid or 0 1202 + newInstance.x = instance.x 1203 + newInstance.y = instance.y 1204 + newInstance.r = tile.r or 0 1205 + newInstance.oy = tile.r ~= 0 and tile.height or 0 1206 + table.insert(self.tileInstances[tile.gid], newInstance) 1207 + end 1208 +end 1209 + 1210 +--- Convert tile location to pixel location 1211 +-- @param x The X axis location of the point (in tiles) 1212 +-- @param y The Y axis location of the point (in tiles) 1213 +-- @return number The X axis location of the point (in pixels) 1214 +-- @return number The Y axis location of the point (in pixels) 1215 +function Map:convertTileToPixel(x,y) 1216 + if self.orientation == "orthogonal" then 1217 + local tileW = self.tilewidth 1218 + local tileH = self.tileheight 1219 + return 1220 + x * tileW, 1221 + y * tileH 1222 + elseif self.orientation == "isometric" then 1223 + local mapH = self.height 1224 + local tileW = self.tilewidth 1225 + local tileH = self.tileheight 1226 + local offsetX = mapH * tileW / 2 1227 + return 1228 + (x - y) * tileW / 2 + offsetX, 1229 + (x + y) * tileH / 2 1230 + elseif self.orientation == "staggered" or 1231 + self.orientation == "hexagonal" then 1232 + local tileW = self.tilewidth 1233 + local tileH = self.tileheight 1234 + local sideLen = self.hexsidelength or 0 1235 + 1236 + if self.staggeraxis == "x" then 1237 + return 1238 + x * tileW, 1239 + ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0) 1240 + else 1241 + return 1242 + ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0), 1243 + y * tileH 1244 + end 1245 + end 1246 +end 1247 + 1248 +--- Convert pixel location to tile location 1249 +-- @param x The X axis location of the point (in pixels) 1250 +-- @param y The Y axis location of the point (in pixels) 1251 +-- @return number The X axis location of the point (in tiles) 1252 +-- @return number The Y axis location of the point (in tiles) 1253 +function Map:convertPixelToTile(x, y) 1254 + if self.orientation == "orthogonal" then 1255 + local tileW = self.tilewidth 1256 + local tileH = self.tileheight 1257 + return 1258 + x / tileW, 1259 + y / tileH 1260 + elseif self.orientation == "isometric" then 1261 + local mapH = self.height 1262 + local tileW = self.tilewidth 1263 + local tileH = self.tileheight 1264 + local offsetX = mapH * tileW / 2 1265 + return 1266 + y / tileH + (x - offsetX) / tileW, 1267 + y / tileH - (x - offsetX) / tileW 1268 + elseif self.orientation == "staggered" then 1269 + local staggerX = self.staggeraxis == "x" 1270 + local even = self.staggerindex == "even" 1271 + 1272 + local function topLeft(x, y) 1273 + if staggerX then 1274 + if ceil(x) % 2 == 1 and even then 1275 + return x - 1, y 1276 + else 1277 + return x - 1, y - 1 1278 + end 1279 + else 1280 + if ceil(y) % 2 == 1 and even then 1281 + return x, y - 1 1282 + else 1283 + return x - 1, y - 1 1284 + end 1285 + end 1286 + end 1287 + 1288 + local function topRight(x, y) 1289 + if staggerX then 1290 + if ceil(x) % 2 == 1 and even then 1291 + return x + 1, y 1292 + else 1293 + return x + 1, y - 1 1294 + end 1295 + else 1296 + if ceil(y) % 2 == 1 and even then 1297 + return x + 1, y - 1 1298 + else 1299 + return x, y - 1 1300 + end 1301 + end 1302 + end 1303 + 1304 + local function bottomLeft(x, y) 1305 + if staggerX then 1306 + if ceil(x) % 2 == 1 and even then 1307 + return x - 1, y + 1 1308 + else 1309 + return x - 1, y 1310 + end 1311 + else 1312 + if ceil(y) % 2 == 1 and even then 1313 + return x, y + 1 1314 + else 1315 + return x - 1, y + 1 1316 + end 1317 + end 1318 + end 1319 + 1320 + local function bottomRight(x, y) 1321 + if staggerX then 1322 + if ceil(x) % 2 == 1 and even then 1323 + return x + 1, y + 1 1324 + else 1325 + return x + 1, y 1326 + end 1327 + else 1328 + if ceil(y) % 2 == 1 and even then 1329 + return x + 1, y + 1 1330 + else 1331 + return x, y + 1 1332 + end 1333 + end 1334 + end 1335 + 1336 + local tileW = self.tilewidth 1337 + local tileH = self.tileheight 1338 + 1339 + if staggerX then 1340 + x = x - (even and tileW / 2 or 0) 1341 + else 1342 + y = y - (even and tileH / 2 or 0) 1343 + end 1344 + 1345 + local halfH = tileH / 2 1346 + local ratio = tileH / tileW 1347 + local referenceX = ceil(x / tileW) 1348 + local referenceY = ceil(y / tileH) 1349 + local relativeX = x - referenceX * tileW 1350 + local relativeY = y - referenceY * tileH 1351 + 1352 + if (halfH - relativeX * ratio > relativeY) then 1353 + return topLeft(referenceX, referenceY) 1354 + elseif (-halfH + relativeX * ratio > relativeY) then 1355 + return topRight(referenceX, referenceY) 1356 + elseif (halfH + relativeX * ratio < relativeY) then 1357 + return bottomLeft(referenceX, referenceY) 1358 + elseif (halfH * 3 - relativeX * ratio < relativeY) then 1359 + return bottomRight(referenceX, referenceY) 1360 + end 1361 + 1362 + return referenceX, referenceY 1363 + elseif self.orientation == "hexagonal" then 1364 + local staggerX = self.staggeraxis == "x" 1365 + local even = self.staggerindex == "even" 1366 + local tileW = self.tilewidth 1367 + local tileH = self.tileheight 1368 + local sideLenX = 0 1369 + local sideLenY = 0 1370 + 1371 + local colW = tileW / 2 1372 + local rowH = tileH / 2 1373 + if staggerX then 1374 + sideLenX = self.hexsidelength 1375 + x = x - (even and tileW or (tileW - sideLenX) / 2) 1376 + colW = colW - (colW - sideLenX / 2) / 2 1377 + else 1378 + sideLenY = self.hexsidelength 1379 + y = y - (even and tileH or (tileH - sideLenY) / 2) 1380 + rowH = rowH - (rowH - sideLenY / 2) / 2 1381 + end 1382 + 1383 + local referenceX = ceil(x) / (colW * 2) 1384 + local referenceY = ceil(y) / (rowH * 2) 1385 + 1386 + -- If in staggered line, then shift reference by 0.5 of other axes 1387 + if staggerX then 1388 + if (floor(referenceX) % 2 == 0) == even then 1389 + referenceY = referenceY - 0.5 1390 + end 1391 + else 1392 + if (floor(referenceY) % 2 == 0) == even then 1393 + referenceX = referenceX - 0.5 1394 + end 1395 + end 1396 + 1397 + local relativeX = x - referenceX * colW * 2 1398 + local relativeY = y - referenceY * rowH * 2 1399 + local centers 1400 + 1401 + if staggerX then 1402 + local left = sideLenX / 2 1403 + local centerX = left + colW 1404 + local centerY = tileH / 2 1405 + 1406 + centers = { 1407 + { x = left, y = centerY }, 1408 + { x = centerX, y = centerY - rowH }, 1409 + { x = centerX, y = centerY + rowH }, 1410 + { x = centerX + colW, y = centerY }, 1411 + } 1412 + else 1413 + local top = sideLenY / 2 1414 + local centerX = tileW / 2 1415 + local centerY = top + rowH 1416 + 1417 + centers = { 1418 + { x = centerX, y = top }, 1419 + { x = centerX - colW, y = centerY }, 1420 + { x = centerX + colW, y = centerY }, 1421 + { x = centerX, y = centerY + rowH } 1422 + } 1423 + end 1424 + 1425 + local nearest = 0 1426 + local minDist = math.huge 1427 + 1428 + local function len2(ax, ay) 1429 + return ax * ax + ay * ay 1430 + end 1431 + 1432 + for i = 1, 4 do 1433 + local dc = len2(centers[i].x - relativeX, centers[i].y - relativeY) 1434 + 1435 + if dc < minDist then 1436 + minDist = dc 1437 + nearest = i 1438 + end 1439 + end 1440 + 1441 + local offsetsStaggerX = { 1442 + { x = 1, y = 1 }, 1443 + { x = 2, y = 0 }, 1444 + { x = 2, y = 1 }, 1445 + { x = 3, y = 1 }, 1446 + } 1447 + 1448 + local offsetsStaggerY = { 1449 + { x = 1, y = 1 }, 1450 + { x = 0, y = 2 }, 1451 + { x = 1, y = 2 }, 1452 + { x = 1, y = 3 }, 1453 + } 1454 + 1455 + local offsets = staggerX and offsetsStaggerX or offsetsStaggerY 1456 + 1457 + return 1458 + referenceX + offsets[nearest].x, 1459 + referenceY + offsets[nearest].y 1460 + end 1461 +end 1462 + 1463 +--- A list of individual layers indexed both by draw order and name 1464 +-- @table Map.layers 1465 +-- @see TileLayer 1466 +-- @see ObjectLayer 1467 +-- @see ImageLayer 1468 +-- @see CustomLayer 1469 + 1470 +--- A list of individual tiles indexed by Global ID 1471 +-- @table Map.tiles 1472 +-- @see Tile 1473 +-- @see Map.tileInstances 1474 + 1475 +--- A list of tile instances indexed by Global ID 1476 +-- @table Map.tileInstances 1477 +-- @see TileInstance 1478 +-- @see Tile 1479 +-- @see Map.tiles 1480 + 1481 +--- A list of no-longer-used batch sprites, indexed by batch 1482 +--@table Map.freeBatchSprites 1483 + 1484 +--- A list of individual objects indexed by Global ID 1485 +-- @table Map.objects 1486 +-- @see Object 1487 + 1488 +--- @table TileLayer 1489 +-- @field name The name of the layer 1490 +-- @field x Position on the X axis (in pixels) 1491 +-- @field y Position on the Y axis (in pixels) 1492 +-- @field width Width of layer (in tiles) 1493 +-- @field height Height of layer (in tiles) 1494 +-- @field visible Toggle if layer is visible or hidden 1495 +-- @field opacity Opacity of layer 1496 +-- @field properties Custom properties 1497 +-- @field data A tileWo dimensional table filled with individual tiles indexed by [y][x] (in tiles) 1498 +-- @field update Update function 1499 +-- @field draw Draw function 1500 +-- @see Map.layers 1501 +-- @see Tile 1502 + 1503 +--- @table ObjectLayer 1504 +-- @field name The name of the layer 1505 +-- @field x Position on the X axis (in pixels) 1506 +-- @field y Position on the Y axis (in pixels) 1507 +-- @field visible Toggle if layer is visible or hidden 1508 +-- @field opacity Opacity of layer 1509 +-- @field properties Custom properties 1510 +-- @field objects List of objects indexed by draw order 1511 +-- @field update Update function 1512 +-- @field draw Draw function 1513 +-- @see Map.layers 1514 +-- @see Object 1515 + 1516 +--- @table ImageLayer 1517 +-- @field name The name of the layer 1518 +-- @field x Position on the X axis (in pixels) 1519 +-- @field y Position on the Y axis (in pixels) 1520 +-- @field visible Toggle if layer is visible or hidden 1521 +-- @field opacity Opacity of layer 1522 +-- @field properties Custom properties 1523 +-- @field image Image to be drawn 1524 +-- @field update Update function 1525 +-- @field draw Draw function 1526 +-- @see Map.layers 1527 + 1528 +--- Custom Layers are used to place userdata such as sprites within the draw order of the map. 1529 +-- @table CustomLayer 1530 +-- @field name The name of the layer 1531 +-- @field x Position on the X axis (in pixels) 1532 +-- @field y Position on the Y axis (in pixels) 1533 +-- @field visible Toggle if layer is visible or hidden 1534 +-- @field opacity Opacity of layer 1535 +-- @field properties Custom properties 1536 +-- @field update Update function 1537 +-- @field draw Draw function 1538 +-- @see Map.layers 1539 +-- @usage 1540 +-- -- Create a Custom Layer 1541 +-- local spriteLayer = map:addCustomLayer("Sprite Layer", 3) 1542 +-- 1543 +-- -- Add data to Custom Layer 1544 +-- spriteLayer.sprites = { 1545 +-- player = { 1546 +-- image = lg.newImage("assets/sprites/player.png"), 1547 +-- x = 64, 1548 +-- y = 64, 1549 +-- r = 0, 1550 +-- } 1551 +-- } 1552 +-- 1553 +-- -- Update callback for Custom Layer 1554 +-- function spriteLayer:update(dt) 1555 +-- for _, sprite in pairs(self.sprites) do 1556 +-- sprite.r = sprite.r + math.rad(90 * dt) 1557 +-- end 1558 +-- end 1559 +-- 1560 +-- -- Draw callback for Custom Layer 1561 +-- function spriteLayer:draw() 1562 +-- for _, sprite in pairs(self.sprites) do 1563 +-- local x = math.floor(sprite.x) 1564 +-- local y = math.floor(sprite.y) 1565 +-- local r = sprite.r 1566 +-- lg.draw(sprite.image, x, y, r) 1567 +-- end 1568 +-- end 1569 + 1570 +--- @table Tile 1571 +-- @field id Local ID within Tileset 1572 +-- @field gid Global ID 1573 +-- @field tileset Tileset ID 1574 +-- @field quad Quad object 1575 +-- @field properties Custom properties 1576 +-- @field terrain Terrain data 1577 +-- @field animation Animation data 1578 +-- @field frame Current animation frame 1579 +-- @field time Time spent on current animation frame 1580 +-- @field width Width of tile 1581 +-- @field height Height of tile 1582 +-- @field sx Scale value on the X axis 1583 +-- @field sy Scale value on the Y axis 1584 +-- @field r Rotation of tile (in radians) 1585 +-- @field offset Offset drawing position 1586 +-- @field offset.x Offset value on the X axis 1587 +-- @field offset.y Offset value on the Y axis 1588 +-- @see Map.tiles 1589 + 1590 +--- @table TileInstance 1591 +-- @field batch Spritebatch the Tile Instance belongs to 1592 +-- @field id ID within the spritebatch 1593 +-- @field gid Global ID 1594 +-- @field x Position on the X axis (in pixels) 1595 +-- @field y Position on the Y axis (in pixels) 1596 +-- @see Map.tileInstances 1597 +-- @see Tile 1598 + 1599 +--- @table Object 1600 +-- @field id Global ID 1601 +-- @field name Name of object (non-unique) 1602 +-- @field shape Shape of object 1603 +-- @field x Position of object on X axis (in pixels) 1604 +-- @field y Position of object on Y axis (in pixels) 1605 +-- @field width Width of object (in pixels) 1606 +-- @field height Heigh tof object (in pixels) 1607 +-- @field rotation Rotation of object (in radians) 1608 +-- @field visible Toggle if object is visible or hidden 1609 +-- @field properties Custom properties 1610 +-- @field ellipse List of verticies of specific shape 1611 +-- @field rectangle List of verticies of specific shape 1612 +-- @field polygon List of verticies of specific shape 1613 +-- @field polyline List of verticies of specific shape 1614 +-- @see Map.objects 1615 + 1616 +return setmetatable({}, STI)
Added src/worpt/lib/sti/plugins/box2d.lua.
1 +--- Box2D plugin for STI 2 +-- @module box2d 3 +-- @author Landon Manning 4 +-- @copyright 2019 5 +-- @license MIT/X11 6 + 7 +local love = _G.love 8 +local utils = require((...):gsub('plugins.box2d', 'utils')) 9 +local lg = require((...):gsub('plugins.box2d', 'graphics')) 10 + 11 +return { 12 + box2d_LICENSE = "MIT/X11", 13 + box2d_URL = "https://github.com/karai17/Simple-Tiled-Implementation", 14 + box2d_VERSION = "2.3.2.7", 15 + box2d_DESCRIPTION = "Box2D hooks for STI.", 16 + 17 + --- Initialize Box2D physics world. 18 + -- @param world The Box2D world to add objects to. 19 + box2d_init = function(map, world) 20 + assert(love.physics, "To use the Box2D plugin, please enable the love.physics module.") 21 + 22 + local body = love.physics.newBody(world, map.offsetx, map.offsety) 23 + local collision = { 24 + body = body, 25 + } 26 + 27 + local function addObjectToWorld(objshape, vertices, userdata, object) 28 + local shape 29 + 30 + if objshape == "polyline" then 31 + if #vertices == 4 then 32 + shape = love.physics.newEdgeShape(unpack(vertices)) 33 + else 34 + shape = love.physics.newChainShape(false, unpack(vertices)) 35 + end 36 + else 37 + shape = love.physics.newPolygonShape(unpack(vertices)) 38 + end 39 + 40 + local currentBody = body 41 + 42 + if userdata.properties.dynamic == true then 43 + currentBody = love.physics.newBody(world, map.offsetx, map.offsety, 'dynamic') 44 + end 45 + 46 + local fixture = love.physics.newFixture(currentBody, shape) 47 + fixture:setUserData(userdata) 48 + 49 + -- Set some custom properties from userdata (or use default set by box2d) 50 + fixture:setFriction(userdata.properties.friction or 0.2) 51 + fixture:setRestitution(userdata.properties.restitution or 0.0) 52 + fixture:setSensor(userdata.properties.sensor or false) 53 + fixture:setFilterData( 54 + userdata.properties.categories or 1, 55 + userdata.properties.mask or 65535, 56 + userdata.properties.group or 0 57 + ) 58 + 59 + local obj = { 60 + object = object, 61 + body = currentBody, 62 + shape = shape, 63 + fixture = fixture, 64 + } 65 + 66 + table.insert(collision, obj) 67 + end 68 + 69 + local function getPolygonVertices(object) 70 + local vertices = {} 71 + for _, vertex in ipairs(object.polygon) do 72 + table.insert(vertices, vertex.x) 73 + table.insert(vertices, vertex.y) 74 + end 75 + 76 + return vertices 77 + end 78 + 79 + local function calculateObjectPosition(object, tile) 80 + local o = { 81 + shape = object.shape, 82 + x = (object.dx or object.x) + map.offsetx, 83 + y = (object.dy or object.y) + map.offsety, 84 + w = object.width, 85 + h = object.height, 86 + polygon = object.polygon or object.polyline or object.ellipse or object.rectangle 87 + } 88 + 89 + local userdata = { 90 + object = o, 91 + properties = object.properties 92 + } 93 + 94 + o.r = object.rotation or 0 95 + if o.shape == "rectangle" then 96 + local cos = math.cos(math.rad(o.r)) 97 + local sin = math.sin(math.rad(o.r)) 98 + local oy = 0 99 + 100 + if object.gid then 101 + local tileset = map.tilesets[map.tiles[object.gid].tileset] 102 + local lid = object.gid - tileset.firstgid 103 + local t = {} 104 + 105 + -- This fixes a height issue 106 + o.y = o.y + map.tiles[object.gid].offset.y 107 + oy = o.h 108 + 109 + for _, tt in ipairs(tileset.tiles) do 110 + if tt.id == lid then 111 + t = tt 112 + break 113 + end 114 + end 115 + 116 + if t.objectGroup then 117 + for _, obj in ipairs(t.objectGroup.objects) do 118 + -- Every object in the tile 119 + calculateObjectPosition(obj, object) 120 + end 121 + 122 + return 123 + else 124 + o.w = map.tiles[object.gid].width 125 + o.h = map.tiles[object.gid].height 126 + end 127 + end 128 + 129 + o.polygon = { 130 + { x=o.x+0, y=o.y+0 }, 131 + { x=o.x+o.w, y=o.y+0 }, 132 + { x=o.x+o.w, y=o.y+o.h }, 133 + { x=o.x+0, y=o.y+o.h } 134 + } 135 + 136 + for _, vertex in ipairs(o.polygon) do 137 + vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin, oy) 138 + end 139 + 140 + local vertices = getPolygonVertices(o) 141 + addObjectToWorld(o.shape, vertices, userdata, tile or object) 142 + elseif o.shape == "ellipse" then 143 + if not o.polygon then 144 + o.polygon = utils.convert_ellipse_to_polygon(o.x, o.y, o.w, o.h) 145 + end 146 + local vertices = getPolygonVertices(o) 147 + local triangles = love.math.triangulate(vertices) 148 + 149 + for _, triangle in ipairs(triangles) do 150 + addObjectToWorld(o.shape, triangle, userdata, tile or object) 151 + end 152 + elseif o.shape == "polygon" then 153 + -- Recalculate collision polygons inside tiles 154 + if tile then 155 + local cos = math.cos(math.rad(o.r)) 156 + local sin = math.sin(math.rad(o.r)) 157 + for _, vertex in ipairs(o.polygon) do 158 + vertex.x = vertex.x + o.x 159 + vertex.y = vertex.y + o.y 160 + vertex.x, vertex.y = utils.rotate_vertex(map, vertex, o.x, o.y, cos, sin) 161 + end 162 + end 163 + 164 + local vertices = getPolygonVertices(o) 165 + local triangles = love.math.triangulate(vertices) 166 + 167 + for _, triangle in ipairs(triangles) do 168 + addObjectToWorld(o.shape, triangle, userdata, tile or object) 169 + end 170 + elseif o.shape == "polyline" then 171 + local vertices = getPolygonVertices(o) 172 + addObjectToWorld(o.shape, vertices, userdata, tile or object) 173 + end 174 + end 175 + 176 + for _, tile in pairs(map.tiles) do 177 + if map.tileInstances[tile.gid] then 178 + for _, instance in ipairs(map.tileInstances[tile.gid]) do 179 + -- Every object in every instance of a tile 180 + if tile.objectGroup then 181 + for _, object in ipairs(tile.objectGroup.objects) do 182 + if object.properties.collidable == true then 183 + object = utils.deepCopy(object) 184 + object.dx = instance.x + object.x 185 + object.dy = instance.y + object.y 186 + calculateObjectPosition(object, instance) 187 + end 188 + end 189 + end 190 + 191 + -- Every instance of a tile 192 + if tile.properties.collidable == true then 193 + local object = { 194 + shape = "rectangle", 195 + x = instance.x, 196 + y = instance.y, 197 + width = map.tilewidth, 198 + height = map.tileheight, 199 + properties = tile.properties 200 + } 201 + 202 + calculateObjectPosition(object, instance) 203 + end 204 + end 205 + end 206 + end 207 + 208 + for _, layer in ipairs(map.layers) do 209 + -- Entire layer 210 + if layer.properties.collidable == true then 211 + if layer.type == "tilelayer" then 212 + for gid, tiles in pairs(map.tileInstances) do 213 + local tile = map.tiles[gid] 214 + local tileset = map.tilesets[tile.tileset] 215 + 216 + for _, instance in ipairs(tiles) do 217 + if instance.layer == layer then 218 + local object = { 219 + shape = "rectangle", 220 + x = instance.x, 221 + y = instance.y, 222 + width = tileset.tilewidth, 223 + height = tileset.tileheight, 224 + properties = tile.properties 225 + } 226 + 227 + calculateObjectPosition(object, instance) 228 + end 229 + end 230 + end 231 + elseif layer.type == "objectgroup" then 232 + for _, object in ipairs(layer.objects) do 233 + calculateObjectPosition(object) 234 + end 235 + elseif layer.type == "imagelayer" then 236 + local object = { 237 + shape = "rectangle", 238 + x = layer.x or 0, 239 + y = layer.y or 0, 240 + width = layer.width, 241 + height = layer.height, 242 + properties = layer.properties 243 + } 244 + 245 + calculateObjectPosition(object) 246 + end 247 + end 248 + 249 + -- Individual objects 250 + if layer.type == "objectgroup" then 251 + for _, object in ipairs(layer.objects) do 252 + if object.properties.collidable == true then 253 + calculateObjectPosition(object) 254 + end 255 + end 256 + end 257 + end 258 + 259 + map.box2d_collision = collision 260 + end, 261 + 262 + --- Remove Box2D fixtures and shapes from world. 263 + -- @param index The index or name of the layer being removed 264 + box2d_removeLayer = function(map, index) 265 + local layer = assert(map.layers[index], "Layer not found: " .. index) 266 + local collision = map.box2d_collision 267 + 268 + -- Remove collision objects 269 + for i = #collision, 1, -1 do 270 + local obj = collision[i] 271 + 272 + if obj.object.layer == layer then 273 + obj.fixture:destroy() 274 + table.remove(collision, i) 275 + end 276 + end 277 + end, 278 + 279 + --- Draw Box2D physics world. 280 + -- @param tx Translate on X 281 + -- @param ty Translate on Y 282 + -- @param sx Scale on X 283 + -- @param sy Scale on Y 284 + box2d_draw = function(map, tx, ty, sx, sy) 285 + local collision = map.box2d_collision 286 + 287 + lg.push() 288 + lg.scale(sx or 1, sy or sx or 1) 289 + lg.translate(math.floor(tx or 0), math.floor(ty or 0)) 290 + 291 + for _, obj in ipairs(collision) do 292 + local points = {obj.body:getWorldPoints(obj.shape:getPoints())} 293 + local shape_type = obj.shape:getType() 294 + 295 + if shape_type == "edge" or shape_type == "chain" then 296 + love.graphics.line(points) 297 + elseif shape_type == "polygon" then 298 + love.graphics.polygon("line", points) 299 + else 300 + error("sti box2d plugin does not support "..shape_type.." shapes") 301 + end 302 + end 303 + 304 + lg.pop() 305 + end 306 +} 307 + 308 +--- Custom Properties in Tiled are used to tell this plugin what to do. 309 +-- @table Properties 310 +-- @field collidable set to true, can be used on any Layer, Tile, or Object 311 +-- @field sensor set to true, can be used on any Tile or Object that is also collidable 312 +-- @field dynamic set to true, can be used on any Tile or Object 313 +-- @field friction can be used to define the friction of any Object 314 +-- @field restitution can be used to define the restitution of any Object 315 +-- @field categories can be used to set the filter Category of any Object 316 +-- @field mask can be used to set the filter Mask of any Object 317 +-- @field group can be used to set the filter Group of any Object
Added src/worpt/lib/sti/plugins/bump.lua.
1 +--- Bump.lua plugin for STI 2 +-- @module bump.lua 3 +-- @author David Serrano (BobbyJones|FrenchFryLord) 4 +-- @copyright 2019 5 +-- @license MIT/X11 6 + 7 +local lg = require((...):gsub('plugins.bump', 'graphics')) 8 + 9 +return { 10 + bump_LICENSE = "MIT/X11", 11 + bump_URL = "https://github.com/karai17/Simple-Tiled-Implementation", 12 + bump_VERSION = "3.1.7.0", 13 + bump_DESCRIPTION = "Bump hooks for STI.", 14 + 15 + --- Adds each collidable tile to the Bump world. 16 + -- @param world The Bump world to add objects to. 17 + -- @return collidables table containing the handles to the objects in the Bump world. 18 + bump_init = function(map, world) 19 + local collidables = {} 20 + 21 + for _, tileset in ipairs(map.tilesets) do 22 + for _, tile in ipairs(tileset.tiles) do 23 + local gid = tileset.firstgid + tile.id 24 + 25 + if map.tileInstances[gid] then 26 + for _, instance in ipairs(map.tileInstances[gid]) do 27 + -- Every object in every instance of a tile 28 + if tile.objectGroup then 29 + for _, object in ipairs(tile.objectGroup.objects) do 30 + if object.properties.collidable == true then 31 + local t = { 32 + name = object.name, 33 + type = object.type, 34 + x = instance.x + map.offsetx + object.x, 35 + y = instance.y + map.offsety + object.y, 36 + width = object.width, 37 + height = object.height, 38 + layer = instance.layer, 39 + properties = object.properties 40 + 41 + } 42 + 43 + world:add(t, t.x, t.y, t.width, t.height) 44 + table.insert(collidables, t) 45 + end 46 + end 47 + end 48 + 49 + -- Every instance of a tile 50 + if tile.properties and tile.properties.collidable == true then 51 + local t = { 52 + x = instance.x + map.offsetx, 53 + y = instance.y + map.offsety, 54 + width = map.tilewidth, 55 + height = map.tileheight, 56 + layer = instance.layer, 57 + properties = tile.properties 58 + } 59 + 60 + world:add(t, t.x, t.y, t.width, t.height) 61 + table.insert(collidables, t) 62 + end 63 + end 64 + end 65 + end 66 + end 67 + 68 + for _, layer in ipairs(map.layers) do 69 + -- Entire layer 70 + if layer.properties.collidable == true then 71 + if layer.type == "tilelayer" then 72 + for y, tiles in ipairs(layer.data) do 73 + for x, tile in pairs(tiles) do 74 + 75 + if tile.objectGroup then 76 + for _, object in ipairs(tile.objectGroup.objects) do 77 + if object.properties.collidable == true then 78 + local t = { 79 + name = object.name, 80 + type = object.type, 81 + x = ((x-1) * map.tilewidth + tile.offset.x + map.offsetx) + object.x, 82 + y = ((y-1) * map.tileheight + tile.offset.y + map.offsety) + object.y, 83 + width = object.width, 84 + height = object.height, 85 + layer = layer, 86 + properties = object.properties 87 + } 88 + 89 + world:add(t, t.x, t.y, t.width, t.height) 90 + table.insert(collidables, t) 91 + end 92 + end 93 + end 94 + 95 + 96 + local t = { 97 + x = (x-1) * map.tilewidth + tile.offset.x + map.offsetx, 98 + y = (y-1) * map.tileheight + tile.offset.y + map.offsety, 99 + width = tile.width, 100 + height = tile.height, 101 + layer = layer, 102 + properties = tile.properties 103 + } 104 + 105 + world:add(t, t.x, t.y, t.width, t.height) 106 + table.insert(collidables, t) 107 + end 108 + end 109 + elseif layer.type == "imagelayer" then 110 + world:add(layer, layer.x, layer.y, layer.width, layer.height) 111 + table.insert(collidables, layer) 112 + end 113 + end 114 + 115 + -- individual collidable objects in a layer that is not "collidable" 116 + -- or whole collidable objects layer 117 + if layer.type == "objectgroup" then 118 + for _, obj in ipairs(layer.objects) do 119 + if layer.properties.collidable == true or obj.properties.collidable == true then 120 + if obj.shape == "rectangle" then 121 + local t = { 122 + name = obj.name, 123 + type = obj.type, 124 + x = obj.x + map.offsetx, 125 + y = obj.y + map.offsety, 126 + width = obj.width, 127 + height = obj.height, 128 + layer = layer, 129 + properties = obj.properties 130 + } 131 + 132 + if obj.gid then 133 + t.y = t.y - obj.height 134 + end 135 + 136 + world:add(t, t.x, t.y, t.width, t.height) 137 + table.insert(collidables, t) 138 + end -- TODO implement other object shapes? 139 + end 140 + end 141 + end 142 + 143 + end 144 + map.bump_collidables = collidables 145 + end, 146 + 147 + --- Remove layer 148 + -- @param index to layer to be removed 149 + -- @param world bump world the holds the tiles 150 + -- @param tx Translate on X 151 +-- @param ty Translate on Y 152 +-- @param sx Scale on X 153 +-- @param sy Scale on Y 154 + bump_removeLayer = function(map, index, world) 155 + local layer = assert(map.layers[index], "Layer not found: " .. index) 156 + local collidables = map.bump_collidables 157 + 158 + -- Remove collision objects 159 + for i = #collidables, 1, -1 do 160 + local obj = collidables[i] 161 + 162 + if obj.layer == layer 163 + and ( 164 + layer.properties.collidable == true 165 + or obj.properties.collidable == true 166 + ) then 167 + world:remove(obj) 168 + table.remove(collidables, i) 169 + end 170 + end 171 + end, 172 + 173 + --- Draw bump collisions world. 174 + -- @param world bump world holding the tiles geometry 175 + -- @param tx Translate on X 176 + -- @param ty Translate on Y 177 + -- @param sx Scale on X 178 + -- @param sy Scale on Y 179 + bump_draw = function(map, world, tx, ty, sx, sy) 180 + lg.push() 181 + lg.scale(sx or 1, sy or sx or 1) 182 + lg.translate(math.floor(tx or 0), math.floor(ty or 0)) 183 + 184 + for _, collidable in pairs(map.bump_collidables) do 185 + lg.rectangle("line", world:getRect(collidable)) 186 + end 187 + 188 + lg.pop() 189 + end 190 +} 191 + 192 +--- Custom Properties in Tiled are used to tell this plugin what to do. 193 +-- @table Properties 194 +-- @field collidable set to true, can be used on any Layer, Tile, or Object
Added src/worpt/lib/sti/utils.lua.
1 +-- Some utility functions that shouldn't be exposed. 2 +local utils = {} 3 + 4 +-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286 5 +function utils.format_path(path) 6 + local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP' 7 + local np_pat1, np_pat2 = np_gen1:gsub('SEP','/'), np_gen2:gsub('SEP','/') 8 + local k 9 + 10 + repeat -- /./ -> / 11 + path,k = path:gsub(np_pat2,'/',1) 12 + until k == 0 13 + 14 + repeat -- A/../ -> (empty) 15 + path,k = path:gsub(np_pat1,'',1) 16 + until k == 0 17 + 18 + if path == '' then path = '.' end 19 + 20 + return path 21 +end 22 + 23 +-- Compensation for scale/rotation shift 24 +function utils.compensate(tile, tileX, tileY, tileW, tileH) 25 + local compx = 0 26 + local compy = 0 27 + 28 + if tile.sx < 0 then compx = tileW end 29 + if tile.sy < 0 then compy = tileH end 30 + 31 + if tile.r > 0 then 32 + tileX = tileX + tileH - compy 33 + tileY = tileY + tileH + compx - tileW 34 + elseif tile.r < 0 then 35 + tileX = tileX + compy 36 + tileY = tileY - compx + tileH 37 + else 38 + tileX = tileX + compx 39 + tileY = tileY + compy 40 + end 41 + 42 + return tileX, tileY 43 +end 44 + 45 +-- Cache images in main STI module 46 +function utils.cache_image(sti, path, image) 47 + image = image or love.graphics.newImage(path) 48 + image:setFilter("nearest", "nearest") 49 + sti.cache[path] = image 50 +end 51 + 52 +-- We just don't know. 53 +function utils.get_tiles(imageW, tileW, margin, spacing) 54 + imageW = imageW - margin 55 + local n = 0 56 + 57 + while imageW >= tileW do 58 + imageW = imageW - tileW 59 + if n ~= 0 then imageW = imageW - spacing end 60 + if imageW >= 0 then n = n + 1 end 61 + end 62 + 63 + return n 64 +end 65 + 66 +-- Decompress tile layer data 67 +function utils.get_decompressed_data(data) 68 + local ffi = require "ffi" 69 + local d = {} 70 + local decoded = ffi.cast("uint32_t*", data) 71 + 72 + for i = 0, data:len() / ffi.sizeof("uint32_t") do 73 + table.insert(d, tonumber(decoded[i])) 74 + end 75 + 76 + return d 77 +end 78 + 79 +-- Convert a Tiled ellipse object to a LOVE polygon 80 +function utils.convert_ellipse_to_polygon(x, y, w, h, max_segments) 81 + local ceil = math.ceil 82 + local cos = math.cos 83 + local sin = math.sin 84 + 85 + local function calc_segments(segments) 86 + local function vdist(a, b) 87 + local c = { 88 + x = a.x - b.x, 89 + y = a.y - b.y, 90 + } 91 + 92 + return c.x * c.x + c.y * c.y 93 + end 94 + 95 + segments = segments or 64 96 + local vertices = {} 97 + 98 + local v = { 1, 2, ceil(segments/4-1), ceil(segments/4) } 99 + 100 + local m 101 + if love and love.physics then 102 + m = love.physics.getMeter() 103 + else 104 + m = 32 105 + end 106 + 107 + for _, i in ipairs(v) do 108 + local angle = (i / segments) * math.pi * 2 109 + local px = x + w / 2 + cos(angle) * w / 2 110 + local py = y + h / 2 + sin(angle) * h / 2 111 + 112 + table.insert(vertices, { x = px / m, y = py / m }) 113 + end 114 + 115 + local dist1 = vdist(vertices[1], vertices[2]) 116 + local dist2 = vdist(vertices[3], vertices[4]) 117 + 118 + -- Box2D threshold 119 + if dist1 < 0.0025 or dist2 < 0.0025 then 120 + return calc_segments(segments-2) 121 + end 122 + 123 + return segments 124 + end 125 + 126 + local segments = calc_segments(max_segments) 127 + local vertices = {} 128 + 129 + table.insert(vertices, { x = x + w / 2, y = y + h / 2 }) 130 + 131 + for i = 0, segments do 132 + local angle = (i / segments) * math.pi * 2 133 + local px = x + w / 2 + cos(angle) * w / 2 134 + local py = y + h / 2 + sin(angle) * h / 2 135 + 136 + table.insert(vertices, { x = px, y = py }) 137 + end 138 + 139 + return vertices 140 +end 141 + 142 +function utils.rotate_vertex(map, vertex, x, y, cos, sin, oy) 143 + if map.orientation == "isometric" then 144 + x, y = utils.convert_isometric_to_screen(map, x, y) 145 + vertex.x, vertex.y = utils.convert_isometric_to_screen(map, vertex.x, vertex.y) 146 + end 147 + 148 + vertex.x = vertex.x - x 149 + vertex.y = vertex.y - y 150 + 151 + return 152 + x + cos * vertex.x - sin * vertex.y, 153 + y + sin * vertex.x + cos * vertex.y - (oy or 0) 154 +end 155 + 156 +--- Project isometric position to cartesian position 157 +function utils.convert_isometric_to_screen(map, x, y) 158 + local mapW = map.width 159 + local tileW = map.tilewidth 160 + local tileH = map.tileheight 161 + local tileX = x / tileH 162 + local tileY = y / tileH 163 + local offsetX = mapW * tileW / 2 164 + 165 + return 166 + (tileX - tileY) * tileW / 2 + offsetX, 167 + (tileX + tileY) * tileH / 2 168 +end 169 + 170 +function utils.hex_to_color(hex) 171 + if hex:sub(1, 1) == "#" then 172 + hex = hex:sub(2) 173 + end 174 + 175 + return { 176 + r = tonumber(hex:sub(1, 2), 16) / 255, 177 + g = tonumber(hex:sub(3, 4), 16) / 255, 178 + b = tonumber(hex:sub(5, 6), 16) / 255 179 + } 180 +end 181 + 182 +function utils.pixel_function(_, _, r, g, b, a) 183 + local mask = utils._TC 184 + 185 + if r == mask.r and 186 + g == mask.g and 187 + b == mask.b then 188 + return r, g, b, 0 189 + end 190 + 191 + return r, g, b, a 192 +end 193 + 194 +function utils.fix_transparent_color(tileset, path) 195 + local image_data = love.image.newImageData(path) 196 + tileset.image = love.graphics.newImage(image_data) 197 + 198 + if tileset.transparentcolor then 199 + utils._TC = utils.hex_to_color(tileset.transparentcolor) 200 + 201 + image_data:mapPixel(utils.pixel_function) 202 + tileset.image = love.graphics.newImage(image_data) 203 + end 204 +end 205 + 206 +function utils.deepCopy(t) 207 + local copy = {} 208 + for k,v in pairs(t) do 209 + if type(v) == "table" then 210 + v = utils.deepCopy(v) 211 + end 212 + copy[k] = v 213 + end 214 + return copy 215 +end 216 + 217 +return utils
Added src/worpt/license.txt.
1 + GNU GENERAL PUBLIC LICENSE 2 + Version 3, 29 June 2007 3 + 4 + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> 5 + Everyone is permitted to copy and distribute verbatim copies 6 + of this license document, but changing it is not allowed. 7 + 8 + Preamble 9 + 10 + The GNU General Public License is a free, copyleft license for 11 +software and other kinds of works. 12 + 13 + The licenses for most software and other practical works are designed 14 +to take away your freedom to share and change the works. By contrast, 15 +the GNU General Public License is intended to guarantee your freedom to 16 +share and change all versions of a program--to make sure it remains free 17 +software for all its users. We, the Free Software Foundation, use the 18 +GNU General Public License for most of our software; it applies also to 19 +any other work released this way by its authors. You can apply it to 20 +your programs, too. 21 + 22 + When we speak of free software, we are referring to freedom, not 23 +price. Our General Public Licenses are designed to make sure that you 24 +have the freedom to distribute copies of free software (and charge for 25 +them if you wish), that you receive source code or can get it if you 26 +want it, that you can change the software or use pieces of it in new 27 +free programs, and that you know you can do these things. 28 + 29 + To protect your rights, we need to prevent others from denying you 30 +these rights or asking you to surrender the rights. Therefore, you have 31 +certain responsibilities if you distribute copies of the software, or if 32 +you modify it: responsibilities to respect the freedom of others. 33 + 34 + For example, if you distribute copies of such a program, whether 35 +gratis or for a fee, you must pass on to the recipients the same 36 +freedoms that you received. You must make sure that they, too, receive 37 +or can get the source code. And you must show them these terms so they 38 +know their rights. 39 + 40 + Developers that use the GNU GPL protect your rights with two steps: 41 +(1) assert copyright on the software, and (2) offer you this License 42 +giving you legal permission to copy, distribute and/or modify it. 43 + 44 + For the developers' and authors' protection, the GPL clearly explains 45 +that there is no warranty for this free software. For both users' and 46 +authors' sake, the GPL requires that modified versions be marked as 47 +changed, so that their problems will not be attributed erroneously to 48 +authors of previous versions. 49 + 50 + Some devices are designed to deny users access to install or run 51 +modified versions of the software inside them, although the manufacturer 52 +can do so. This is fundamentally incompatible with the aim of 53 +protecting users' freedom to change the software. The systematic 54 +pattern of such abuse occurs in the area of products for individuals to 55 +use, which is precisely where it is most unacceptable. Therefore, we 56 +have designed this version of the GPL to prohibit the practice for those 57 +products. If such problems arise substantially in other domains, we 58 +stand ready to extend this provision to those domains in future versions 59 +of the GPL, as needed to protect the freedom of users. 60 + 61 + Finally, every program is threatened constantly by software patents. 62 +States should not allow patents to restrict development and use of 63 +software on general-purpose computers, but in those that do, we wish to 64 +avoid the special danger that patents applied to a free program could 65 +make it effectively proprietary. To prevent this, the GPL assures that 66 +patents cannot be used to render the program non-free. 67 + 68 + The precise terms and conditions for copying, distribution and 69 +modification follow. 70 + 71 + TERMS AND CONDITIONS 72 + 73 + 0. Definitions. 74 + 75 + "This License" refers to version 3 of the GNU General Public License. 76 + 77 + "Copyright" also means copyright-like laws that apply to other kinds of 78 +works, such as semiconductor masks. 79 + 80 + "The Program" refers to any copyrightable work licensed under this 81 +License. Each licensee is addressed as "you". "Licensees" and 82 +"recipients" may be individuals or organizations. 83 + 84 + To "modify" a work means to copy from or adapt all or part of the work 85 +in a fashion requiring copyright permission, other than the making of an 86 +exact copy. The resulting work is called a "modified version" of the 87 +earlier work or a work "based on" the earlier work. 88 + 89 + A "covered work" means either the unmodified Program or a work based 90 +on the Program. 91 + 92 + To "propagate" a work means to do anything with it that, without 93 +permission, would make you directly or secondarily liable for 94 +infringement under applicable copyright law, except executing it on a 95 +computer or modifying a private copy. Propagation includes copying, 96 +distribution (with or without modification), making available to the 97 +public, and in some countries other activities as well. 98 + 99 + To "convey" a work means any kind of propagation that enables other 100 +parties to make or receive copies. Mere interaction with a user through 101 +a computer network, with no transfer of a copy, is not conveying. 102 + 103 + An interactive user interface displays "Appropriate Legal Notices" 104 +to the extent that it includes a convenient and prominently visible 105 +feature that (1) displays an appropriate copyright notice, and (2) 106 +tells the user that there is no warranty for the work (except to the 107 +extent that warranties are provided), that licensees may convey the 108 +work under this License, and how to view a copy of this License. If 109 +the interface presents a list of user commands or options, such as a 110 +menu, a prominent item in the list meets this criterion. 111 + 112 + 1. Source Code. 113 + 114 + The "source code" for a work means the preferred form of the work 115 +for making modifications to it. "Object code" means any non-source 116 +form of a work. 117 + 118 + A "Standard Interface" means an interface that either is an official 119 +standard defined by a recognized standards body, or, in the case of 120 +interfaces specified for a particular programming language, one that 121 +is widely used among developers working in that language. 122 + 123 + The "System Libraries" of an executable work include anything, other 124 +than the work as a whole, that (a) is included in the normal form of 125 +packaging a Major Component, but which is not part of that Major 126 +Component, and (b) serves only to enable use of the work with that 127 +Major Component, or to implement a Standard Interface for which an 128 +implementation is available to the public in source code form. A 129 +"Major Component", in this context, means a major essential component 130 +(kernel, window system, and so on) of the specific operating system 131 +(if any) on which the executable work runs, or a compiler used to 132 +produce the work, or an object code interpreter used to run it. 133 + 134 + The "Corresponding Source" for a work in object code form means all 135 +the source code needed to generate, install, and (for an executable 136 +work) run the object code and to modify the work, including scripts to 137 +control those activities. However, it does not include the work's 138 +System Libraries, or general-purpose tools or generally available free 139 +programs which are used unmodified in performing those activities but 140 +which are not part of the work. For example, Corresponding Source 141 +includes interface definition files associated with source files for 142 +the work, and the source code for shared libraries and dynamically 143 +linked subprograms that the work is specifically designed to require, 144 +such as by intimate data communication or control flow between those 145 +subprograms and other parts of the work. 146 + 147 + The Corresponding Source need not include anything that users 148 +can regenerate automatically from other parts of the Corresponding 149 +Source. 150 + 151 + The Corresponding Source for a work in source code form is that 152 +same work. 153 + 154 + 2. Basic Permissions. 155 + 156 + All rights granted under this License are granted for the term of 157 +copyright on the Program, and are irrevocable provided the stated 158 +conditions are met. This License explicitly affirms your unlimited 159 +permission to run the unmodified Program. The output from running a 160 +covered work is covered by this License only if the output, given its 161 +content, constitutes a covered work. This License acknowledges your 162 +rights of fair use or other equivalent, as provided by copyright law. 163 + 164 + You may make, run and propagate covered works that you do not 165 +convey, without conditions so long as your license otherwise remains 166 +in force. You may convey covered works to others for the sole purpose 167 +of having them make modifications exclusively for you, or provide you 168 +with facilities for running those works, provided that you comply with 169 +the terms of this License in conveying all material for which you do 170 +not control copyright. Those thus making or running the covered works 171 +for you must do so exclusively on your behalf, under your direction 172 +and control, on terms that prohibit them from making any copies of 173 +your copyrighted material outside their relationship with you. 174 + 175 + Conveying under any other circumstances is permitted solely under 176 +the conditions stated below. Sublicensing is not allowed; section 10 177 +makes it unnecessary. 178 + 179 + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 + 181 + No covered work shall be deemed part of an effective technological 182 +measure under any applicable law fulfilling obligations under article 183 +11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 +similar laws prohibiting or restricting circumvention of such 185 +measures. 186 + 187 + When you convey a covered work, you waive any legal power to forbid 188 +circumvention of technological measures to the extent such circumvention 189 +is effected by exercising rights under this License with respect to 190 +the covered work, and you disclaim any intention to limit operation or 191 +modification of the work as a means of enforcing, against the work's 192 +users, your or third parties' legal rights to forbid circumvention of 193 +technological measures. 194 + 195 + 4. Conveying Verbatim Copies. 196 + 197 + You may convey verbatim copies of the Program's source code as you 198 +receive it, in any medium, provided that you conspicuously and 199 +appropriately publish on each copy an appropriate copyright notice; 200 +keep intact all notices stating that this License and any 201 +non-permissive terms added in accord with section 7 apply to the code; 202 +keep intact all notices of the absence of any warranty; and give all 203 +recipients a copy of this License along with the Program. 204 + 205 + You may charge any price or no price for each copy that you convey, 206 +and you may offer support or warranty protection for a fee. 207 + 208 + 5. Conveying Modified Source Versions. 209 + 210 + You may convey a work based on the Program, or the modifications to 211 +produce it from the Program, in the form of source code under the 212 +terms of section 4, provided that you also meet all of these conditions: 213 + 214 + a) The work must carry prominent notices stating that you modified 215 + it, and giving a relevant date. 216 + 217 + b) The work must carry prominent notices stating that it is 218 + released under this License and any conditions added under section 219 + 7. This requirement modifies the requirement in section 4 to 220 + "keep intact all notices". 221 + 222 + c) You must license the entire work, as a whole, under this 223 + License to anyone who comes into possession of a copy. This 224 + License will therefore apply, along with any applicable section 7 225 + additional terms, to the whole of the work, and all its parts, 226 + regardless of how they are packaged. This License gives no 227 + permission to license the work in any other way, but it does not 228 + invalidate such permission if you have separately received it. 229 + 230 + d) If the work has interactive user interfaces, each must display 231 + Appropriate Legal Notices; however, if the Program has interactive 232 + interfaces that do not display Appropriate Legal Notices, your 233 + work need not make them do so. 234 + 235 + A compilation of a covered work with other separate and independent 236 +works, which are not by their nature extensions of the covered work, 237 +and which are not combined with it such as to form a larger program, 238 +in or on a volume of a storage or distribution medium, is called an 239 +"aggregate" if the compilation and its resulting copyright are not 240 +used to limit the access or legal rights of the compilation's users 241 +beyond what the individual works permit. Inclusion of a covered work 242 +in an aggregate does not cause this License to apply to the other 243 +parts of the aggregate. 244 + 245 + 6. Conveying Non-Source Forms. 246 + 247 + You may convey a covered work in object code form under the terms 248 +of sections 4 and 5, provided that you also convey the 249 +machine-readable Corresponding Source under the terms of this License, 250 +in one of these ways: 251 + 252 + a) Convey the object code in, or embodied in, a physical product 253 + (including a physical distribution medium), accompanied by the 254 + Corresponding Source fixed on a durable physical medium 255 + customarily used for software interchange. 256 + 257 + b) Convey the object code in, or embodied in, a physical product 258 + (including a physical distribution medium), accompanied by a 259 + written offer, valid for at least three years and valid for as 260 + long as you offer spare parts or customer support for that product 261 + model, to give anyone who possesses the object code either (1) a 262 + copy of the Corresponding Source for all the software in the 263 + product that is covered by this License, on a durable physical 264 + medium customarily used for software interchange, for a price no 265 + more than your reasonable cost of physically performing this 266 + conveying of source, or (2) access to copy the 267 + Corresponding Source from a network server at no charge. 268 + 269 + c) Convey individual copies of the object code with a copy of the 270 + written offer to provide the Corresponding Source. This 271 + alternative is allowed only occasionally and noncommercially, and 272 + only if you received the object code with such an offer, in accord 273 + with subsection 6b. 274 + 275 + d) Convey the object code by offering access from a designated 276 + place (gratis or for a charge), and offer equivalent access to the 277 + Corresponding Source in the same way through the same place at no 278 + further charge. You need not require recipients to copy the 279 + Corresponding Source along with the object code. If the place to 280 + copy the object code is a network server, the Corresponding Source 281 + may be on a different server (operated by you or a third party) 282 + that supports equivalent copying facilities, provided you maintain 283 + clear directions next to the object code saying where to find the 284 + Corresponding Source. Regardless of what server hosts the 285 + Corresponding Source, you remain obligated to ensure that it is 286 + available for as long as needed to satisfy these requirements. 287 + 288 + e) Convey the object code using peer-to-peer transmission, provided 289 + you inform other peers where the object code and Corresponding 290 + Source of the work are being offered to the general public at no 291 + charge under subsection 6d. 292 + 293 + A separable portion of the object code, whose source code is excluded 294 +from the Corresponding Source as a System Library, need not be 295 +included in conveying the object code work. 296 + 297 + A "User Product" is either (1) a "consumer product", which means any 298 +tangible personal property which is normally used for personal, family, 299 +or household purposes, or (2) anything designed or sold for incorporation 300 +into a dwelling. In determining whether a product is a consumer product, 301 +doubtful cases shall be resolved in favor of coverage. For a particular 302 +product received by a particular user, "normally used" refers to a 303 +typical or common use of that class of product, regardless of the status 304 +of the particular user or of the way in which the particular user 305 +actually uses, or expects or is expected to use, the product. A product 306 +is a consumer product regardless of whether the product has substantial 307 +commercial, industrial or non-consumer uses, unless such uses represent 308 +the only significant mode of use of the product. 309 + 310 + "Installation Information" for a User Product means any methods, 311 +procedures, authorization keys, or other information required to install 312 +and execute modified versions of a covered work in that User Product from 313 +a modified version of its Corresponding Source. The information must 314 +suffice to ensure that the continued functioning of the modified object 315 +code is in no case prevented or interfered with solely because 316 +modification has been made. 317 + 318 + If you convey an object code work under this section in, or with, or 319 +specifically for use in, a User Product, and the conveying occurs as 320 +part of a transaction in which the right of possession and use of the 321 +User Product is transferred to the recipient in perpetuity or for a 322 +fixed term (regardless of how the transaction is characterized), the 323 +Corresponding Source conveyed under this section must be accompanied 324 +by the Installation Information. But this requirement does not apply 325 +if neither you nor any third party retains the ability to install 326 +modified object code on the User Product (for example, the work has 327 +been installed in ROM). 328 + 329 + The requirement to provide Installation Information does not include a 330 +requirement to continue to provide support service, warranty, or updates 331 +for a work that has been modified or installed by the recipient, or for 332 +the User Product in which it has been modified or installed. Access to a 333 +network may be denied when the modification itself materially and 334 +adversely affects the operation of the network or violates the rules and 335 +protocols for communication across the network. 336 + 337 + Corresponding Source conveyed, and Installation Information provided, 338 +in accord with this section must be in a format that is publicly 339 +documented (and with an implementation available to the public in 340 +source code form), and must require no special password or key for 341 +unpacking, reading or copying. 342 + 343 + 7. Additional Terms. 344 + 345 + "Additional permissions" are terms that supplement the terms of this 346 +License by making exceptions from one or more of its conditions. 347 +Additional permissions that are applicable to the entire Program shall 348 +be treated as though they were included in this License, to the extent 349 +that they are valid under applicable law. If additional permissions 350 +apply only to part of the Program, that part may be used separately 351 +under those permissions, but the entire Program remains governed by 352 +this License without regard to the additional permissions. 353 + 354 + When you convey a copy of a covered work, you may at your option 355 +remove any additional permissions from that copy, or from any part of 356 +it. (Additional permissions may be written to require their own 357 +removal in certain cases when you modify the work.) You may place 358 +additional permissions on material, added by you to a covered work, 359 +for which you have or can give appropriate copyright permission. 360 + 361 + Notwithstanding any other provision of this License, for material you 362 +add to a covered work, you may (if authorized by the copyright holders of 363 +that material) supplement the terms of this License with terms: 364 + 365 + a) Disclaiming warranty or limiting liability differently from the 366 + terms of sections 15 and 16 of this License; or 367 + 368 + b) Requiring preservation of specified reasonable legal notices or 369 + author attributions in that material or in the Appropriate Legal 370 + Notices displayed by works containing it; or 371 + 372 + c) Prohibiting misrepresentation of the origin of that material, or 373 + requiring that modified versions of such material be marked in 374 + reasonable ways as different from the original version; or 375 + 376 + d) Limiting the use for publicity purposes of names of licensors or 377 + authors of the material; or 378 + 379 + e) Declining to grant rights under trademark law for use of some 380 + trade names, trademarks, or service marks; or 381 + 382 + f) Requiring indemnification of licensors and authors of that 383 + material by anyone who conveys the material (or modified versions of 384 + it) with contractual assumptions of liability to the recipient, for 385 + any liability that these contractual assumptions directly impose on 386 + those licensors and authors. 387 + 388 + All other non-permissive additional terms are considered "further 389 +restrictions" within the meaning of section 10. If the Program as you 390 +received it, or any part of it, contains a notice stating that it is 391 +governed by this License along with a term that is a further 392 +restriction, you may remove that term. If a license document contains 393 +a further restriction but permits relicensing or conveying under this 394 +License, you may add to a covered work material governed by the terms 395 +of that license document, provided that the further restriction does 396 +not survive such relicensing or conveying. 397 + 398 + If you add terms to a covered work in accord with this section, you 399 +must place, in the relevant source files, a statement of the 400 +additional terms that apply to those files, or a notice indicating 401 +where to find the applicable terms. 402 + 403 + Additional terms, permissive or non-permissive, may be stated in the 404 +form of a separately written license, or stated as exceptions; 405 +the above requirements apply either way. 406 + 407 + 8. Termination. 408 + 409 + You may not propagate or modify a covered work except as expressly 410 +provided under this License. Any attempt otherwise to propagate or 411 +modify it is void, and will automatically terminate your rights under 412 +this License (including any patent licenses granted under the third 413 +paragraph of section 11). 414 + 415 + However, if you cease all violation of this License, then your 416 +license from a particular copyright holder is reinstated (a) 417 +provisionally, unless and until the copyright holder explicitly and 418 +finally terminates your license, and (b) permanently, if the copyright 419 +holder fails to notify you of the violation by some reasonable means 420 +prior to 60 days after the cessation. 421 + 422 + Moreover, your license from a particular copyright holder is 423 +reinstated permanently if the copyright holder notifies you of the 424 +violation by some reasonable means, this is the first time you have 425 +received notice of violation of this License (for any work) from that 426 +copyright holder, and you cure the violation prior to 30 days after 427 +your receipt of the notice. 428 + 429 + Termination of your rights under this section does not terminate the 430 +licenses of parties who have received copies or rights from you under 431 +this License. If your rights have been terminated and not permanently 432 +reinstated, you do not qualify to receive new licenses for the same 433 +material under section 10. 434 + 435 + 9. Acceptance Not Required for Having Copies. 436 + 437 + You are not required to accept this License in order to receive or 438 +run a copy of the Program. Ancillary propagation of a covered work 439 +occurring solely as a consequence of using peer-to-peer transmission 440 +to receive a copy likewise does not require acceptance. However, 441 +nothing other than this License grants you permission to propagate or 442 +modify any covered work. These actions infringe copyright if you do 443 +not accept this License. Therefore, by modifying or propagating a 444 +covered work, you indicate your acceptance of this License to do so. 445 + 446 + 10. Automatic Licensing of Downstream Recipients. 447 + 448 + Each time you convey a covered work, the recipient automatically 449 +receives a license from the original licensors, to run, modify and 450 +propagate that work, subject to this License. You are not responsible 451 +for enforcing compliance by third parties with this License. 452 + 453 + An "entity transaction" is a transaction transferring control of an 454 +organization, or substantially all assets of one, or subdividing an 455 +organization, or merging organizations. If propagation of a covered 456 +work results from an entity transaction, each party to that 457 +transaction who receives a copy of the work also receives whatever 458 +licenses to the work the party's predecessor in interest had or could 459 +give under the previous paragraph, plus a right to possession of the 460 +Corresponding Source of the work from the predecessor in interest, if 461 +the predecessor has it or can get it with reasonable efforts. 462 + 463 + You may not impose any further restrictions on the exercise of the 464 +rights granted or affirmed under this License. For example, you may 465 +not impose a license fee, royalty, or other charge for exercise of 466 +rights granted under this License, and you may not initiate litigation 467 +(including a cross-claim or counterclaim in a lawsuit) alleging that 468 +any patent claim is infringed by making, using, selling, offering for 469 +sale, or importing the Program or any portion of it. 470 + 471 + 11. Patents. 472 + 473 + A "contributor" is a copyright holder who authorizes use under this 474 +License of the Program or a work on which the Program is based. The 475 +work thus licensed is called the contributor's "contributor version". 476 + 477 + A contributor's "essential patent claims" are all patent claims 478 +owned or controlled by the contributor, whether already acquired or 479 +hereafter acquired, that would be infringed by some manner, permitted 480 +by this License, of making, using, or selling its contributor version, 481 +but do not include claims that would be infringed only as a 482 +consequence of further modification of the contributor version. For 483 +purposes of this definition, "control" includes the right to grant 484 +patent sublicenses in a manner consistent with the requirements of 485 +this License. 486 + 487 + Each contributor grants you a non-exclusive, worldwide, royalty-free 488 +patent license under the contributor's essential patent claims, to 489 +make, use, sell, offer for sale, import and otherwise run, modify and 490 +propagate the contents of its contributor version. 491 + 492 + In the following three paragraphs, a "patent license" is any express 493 +agreement or commitment, however denominated, not to enforce a patent 494 +(such as an express permission to practice a patent or covenant not to 495 +sue for patent infringement). To "grant" such a patent license to a 496 +party means to make such an agreement or commitment not to enforce a 497 +patent against the party. 498 + 499 + If you convey a covered work, knowingly relying on a patent license, 500 +and the Corresponding Source of the work is not available for anyone 501 +to copy, free of charge and under the terms of this License, through a 502 +publicly available network server or other readily accessible means, 503 +then you must either (1) cause the Corresponding Source to be so 504 +available, or (2) arrange to deprive yourself of the benefit of the 505 +patent license for this particular work, or (3) arrange, in a manner 506 +consistent with the requirements of this License, to extend the patent 507 +license to downstream recipients. "Knowingly relying" means you have 508 +actual knowledge that, but for the patent license, your conveying the 509 +covered work in a country, or your recipient's use of the covered work 510 +in a country, would infringe one or more identifiable patents in that 511 +country that you have reason to believe are valid. 512 + 513 + If, pursuant to or in connection with a single transaction or 514 +arrangement, you convey, or propagate by procuring conveyance of, a 515 +covered work, and grant a patent license to some of the parties 516 +receiving the covered work authorizing them to use, propagate, modify 517 +or convey a specific copy of the covered work, then the patent license 518 +you grant is automatically extended to all recipients of the covered 519 +work and works based on it. 520 + 521 + A patent license is "discriminatory" if it does not include within 522 +the scope of its coverage, prohibits the exercise of, or is 523 +conditioned on the non-exercise of one or more of the rights that are 524 +specifically granted under this License. You may not convey a covered 525 +work if you are a party to an arrangement with a third party that is 526 +in the business of distributing software, under which you make payment 527 +to the third party based on the extent of your activity of conveying 528 +the work, and under which the third party grants, to any of the 529 +parties who would receive the covered work from you, a discriminatory 530 +patent license (a) in connection with copies of the covered work 531 +conveyed by you (or copies made from those copies), or (b) primarily 532 +for and in connection with specific products or compilations that 533 +contain the covered work, unless you entered into that arrangement, 534 +or that patent license was granted, prior to 28 March 2007. 535 + 536 + Nothing in this License shall be construed as excluding or limiting 537 +any implied license or other defenses to infringement that may 538 +otherwise be available to you under applicable patent law. 539 + 540 + 12. No Surrender of Others' Freedom. 541 + 542 + If conditions are imposed on you (whether by court order, agreement or 543 +otherwise) that contradict the conditions of this License, they do not 544 +excuse you from the conditions of this License. If you cannot convey a 545 +covered work so as to satisfy simultaneously your obligations under this 546 +License and any other pertinent obligations, then as a consequence you may 547 +not convey it at all. For example, if you agree to terms that obligate you 548 +to collect a royalty for further conveying from those to whom you convey 549 +the Program, the only way you could satisfy both those terms and this 550 +License would be to refrain entirely from conveying the Program. 551 + 552 + 13. Use with the GNU Affero General Public License. 553 + 554 + Notwithstanding any other provision of this License, you have 555 +permission to link or combine any covered work with a work licensed 556 +under version 3 of the GNU Affero General Public License into a single 557 +combined work, and to convey the resulting work. The terms of this 558 +License will continue to apply to the part which is the covered work, 559 +but the special requirements of the GNU Affero General Public License, 560 +section 13, concerning interaction through a network will apply to the 561 +combination as such. 562 + 563 + 14. Revised Versions of this License. 564 + 565 + The Free Software Foundation may publish revised and/or new versions of 566 +the GNU General Public License from time to time. Such new versions will 567 +be similar in spirit to the present version, but may differ in detail to 568 +address new problems or concerns. 569 + 570 + Each version is given a distinguishing version number. If the 571 +Program specifies that a certain numbered version of the GNU General 572 +Public License "or any later version" applies to it, you have the 573 +option of following the terms and conditions either of that numbered 574 +version or of any later version published by the Free Software 575 +Foundation. If the Program does not specify a version number of the 576 +GNU General Public License, you may choose any version ever published 577 +by the Free Software Foundation. 578 + 579 + If the Program specifies that a proxy can decide which future 580 +versions of the GNU General Public License can be used, that proxy's 581 +public statement of acceptance of a version permanently authorizes you 582 +to choose that version for the Program. 583 + 584 + Later license versions may give you additional or different 585 +permissions. However, no additional obligations are imposed on any 586 +author or copyright holder as a result of your choosing to follow a 587 +later version. 588 + 589 + 15. Disclaimer of Warranty. 590 + 591 + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 + 600 + 16. Limitation of Liability. 601 + 602 + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 +SUCH DAMAGES. 611 + 612 + 17. Interpretation of Sections 15 and 16. 613 + 614 + If the disclaimer of warranty and limitation of liability provided 615 +above cannot be given local legal effect according to their terms, 616 +reviewing courts shall apply local law that most closely approximates 617 +an absolute waiver of all civil liability in connection with the 618 +Program, unless a warranty or assumption of liability accompanies a 619 +copy of the Program in return for a fee. 620 + 621 + END OF TERMS AND CONDITIONS 622 + 623 + How to Apply These Terms to Your New Programs 624 + 625 + If you develop a new program, and you want it to be of the greatest 626 +possible use to the public, the best way to achieve this is to make it 627 +free software which everyone can redistribute and change under these terms. 628 + 629 + To do so, attach the following notices to the program. It is safest 630 +to attach them to the start of each source file to most effectively 631 +state the exclusion of warranty; and each file should have at least 632 +the "copyright" line and a pointer to where the full notice is found. 633 + 634 + <one line to give the program's name and a brief idea of what it does.> 635 + Copyright (C) <year> <name of author> 636 + 637 + This program is free software: you can redistribute it and/or modify 638 + it under the terms of the GNU General Public License as published by 639 + the Free Software Foundation, either version 3 of the License, or 640 + (at your option) any later version. 641 + 642 + This program is distributed in the hope that it will be useful, 643 + but WITHOUT ANY WARRANTY; without even the implied warranty of 644 + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 + GNU General Public License for more details. 646 + 647 + You should have received a copy of the GNU General Public License 648 + along with this program. If not, see <https://www.gnu.org/licenses/>. 649 + 650 +Also add information on how to contact you by electronic and paper mail. 651 + 652 + If the program does terminal interaction, make it output a short 653 +notice like this when it starts in an interactive mode: 654 + 655 + <program> Copyright (C) <year> <name of author> 656 + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 + This is free software, and you are welcome to redistribute it 658 + under certain conditions; type `show c' for details. 659 + 660 +The hypothetical commands `show w' and `show c' should show the appropriate 661 +parts of the General Public License. Of course, your program's commands 662 +might be different; for a GUI interface, you would use an "about box". 663 + 664 + You should also get your employer (if you work as a programmer) or school, 665 +if any, to sign a "copyright disclaimer" for the program, if necessary. 666 +For more information on this, and how to apply and follow the GNU GPL, see 667 +<https://www.gnu.org/licenses/>. 668 + 669 + The GNU General Public License does not permit incorporating your program 670 +into proprietary programs. If your program is a subroutine library, you 671 +may consider it more useful to permit linking proprietary applications with 672 +the library. If this is what you want to do, use the GNU Lesser General 673 +Public License instead of this License. But first, please read 674 +<https://www.gnu.org/philosophy/why-not-lgpl.html>.
Added src/worpt/love-release.sh.
1 +#!/usr/bin/env bash 2 + 3 +## For users on macos 4 +## #!/usr/local/Cellar/bash/4.4.23/bin/bash 5 + 6 +# LÖVE version 7 +LOVE_DEF_VERSION=0.11.3 8 + 9 + 10 + 11 +# Helper functions 12 + 13 +# Dependencies check 14 +check_deps () { 15 + command -v curl > /dev/null 2>&1 || { 16 + >&2 echo "curl is not installed. Aborting." 17 + local EXIT=true 18 + } 19 + command -v zip > /dev/null 2>&1 || { 20 + >&2 echo "zip is not installed. Aborting." 21 + local EXIT=true 22 + } 23 + command -v unzip > /dev/null 2>&1 || { 24 + >&2 echo "unzip is not installed. Aborting." 25 + local EXIT=true 26 + } 27 + command -v getopt > /dev/null 2>&1 || { 28 + local opt=false 29 + } && { 30 + unset GETOPT_COMPATIBLE 31 + local out=$(getopt -T) 32 + if (( $? != 4 )) && [[ -n $out ]]; then 33 + local opt=false 34 + fi 35 + } 36 + if [[ $opt == false ]]; then 37 + >&2 echo "GNU getopt is not installed. Aborting." 38 + local EXIT=true 39 + fi 40 + if ! command -v readlink > /dev/null 2>&1 || ! readlink -m / > /dev/null 2>&1; then 41 + command -v greadlink > /dev/null 2>&1 || { 42 + >&2 echo "GNU readlink is not installed. Aborting." 43 + local EXIT=true 44 + } && { 45 + readlink () { 46 + greadlink "$@" 47 + } 48 + } 49 + fi 50 + 51 + command -v lua > /dev/null 2>&1 || { 52 + echo "lua is not installed. Install it to ease your releases." 53 + } && { 54 + LUA=true 55 + } 56 + if [[ $EXIT == true ]]; then 57 + exit_module "deps" 58 + fi 59 +} 60 + 61 +# Get user confirmation, simple Yes/No question 62 +## $1: message, usually just a question 63 +## $2: default choice, 0 - yes; 1 - no, default - yes 64 +## return: 0 - yes, 1 - no 65 +get_user_confirmation () { 66 + if [[ -z $2 || $2 == "0" ]]; then 67 + read -n 1 -p "$1 [Y/n]: " yn 68 + local default=0 69 + else 70 + read -n 1 -p "$1 [y/N]: " yn 71 + local default=1 72 + fi 73 + case $yn in 74 + [Yy]* ) 75 + echo; return 0;; 76 + [Nn]* ) 77 + echo; return 1;; 78 + "" ) 79 + return $default;; 80 + * ) 81 + echo; return $default;; 82 + esac 83 +} 84 + 85 + 86 +# Generate LÖVE version variables 87 +## $1: LÖVE version string 88 +## return: 0 - string matched, 1 - else 89 +gen_version () { 90 + if [[ $1 =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))?$ ]]; then 91 + LOVE_VERSION=$1 92 + LOVE_VERSION_MAJOR=${BASH_REMATCH[1]} 93 + LOVE_VERSION_MINOR=${BASH_REMATCH[2]} 94 + LOVE_VERSION_REVISION=${BASH_REMATCH[4]} 95 + return 0 96 + fi 97 + return 1 98 +} 99 + 100 + 101 +# Compare two LÖVE versions 102 +## $1: First LÖVE version 103 +## $2: comparison operator 104 +## "ge", "le", "gt" "lt" 105 +## ">=", "<=", ">", "<" 106 +## $3: Second LÖVE version 107 +## return: 0 - true, 1 - false 108 +compare_version () { 109 + if [[ $1 =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then 110 + local v1_maj=${BASH_REMATCH[1]} 111 + local v1_min=${BASH_REMATCH[2]} 112 + local v1_rev=${BASH_REMATCH[3]} 113 + else 114 + return 1 115 + fi 116 + if [[ $3 =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then 117 + local v2_maj=${BASH_REMATCH[1]} 118 + local v2_min=${BASH_REMATCH[2]} 119 + local v2_rev=${BASH_REMATCH[3]} 120 + else 121 + return 1 122 + fi 123 + 124 + case $2 in 125 + ge|\>= ) 126 + if (( $v1_maj >= $v2_maj && $v1_min >= $v2_min && $v1_rev >= $v2_rev )); then 127 + return 0 128 + else 129 + return 1 130 + fi 131 + ;; 132 + le|\<= ) 133 + if (( $v1_maj <= $v2_maj && $v1_min <= $v2_min && $v1_rev <= $v2_rev )); then 134 + return 0 135 + else 136 + return 1 137 + fi 138 + ;; 139 + gt|\> ) 140 + if (( $v1_maj > $v2_maj || ( $v1_max == $v2_max && $v1_min > $v2_min ) || 141 + ( $v1_max == $v2_max && $v1_min == $v2_min && $v1_rev > $v2_rev ) )); then 142 + return 0 143 + else 144 + return 1 145 + fi 146 + ;; 147 + lt|\< ) 148 + if (( $v1_maj < $v2_maj || ( $v1_max == $v2_max && $v1_min < $v2_min ) || 149 + ( $v1_max == $v2_max && $v1_min == $v2_min && $v1_rev < $v2_rev ) )); then 150 + return 0 151 + else 152 + return 1 153 + fi 154 + ;; 155 + esac 156 +} 157 + 158 + 159 +# Read configuration 160 +## $1: system name 161 +read_config () { 162 + if [[ $LUA == true ]] && [[ -f "conf.lua" ]]; then 163 + local var=$(lua - <<EOF 164 +f = loadfile("conf.lua") 165 +t, love = {window = {}, modules = {}, screen = {}}, {} 166 +f() 167 +love.conf(t) 168 + 169 +-- "love", "windows", "osx", "debian" or "android" 170 +os = "$1" 171 + 172 +fields = { 173 + "identity", "version", "game_version", "icon", "exclude", 174 + "title", "author", "email", "url", "description", } 175 + 176 +for _, f in ipairs(fields) do 177 + t[f] = t[f] or "" 178 +end 179 + 180 +t.os = t.os or {} 181 +for _, v in ipairs(t.os) do 182 + t.os[v] = {} 183 +end 184 + 185 +if os == "default" then 186 + t.os.default = {} 187 +end 188 + 189 +if t.os[os] then 190 + print(os:upper()..'=true') 191 + for _, f in ipairs(fields) do 192 + t.os[os][f] = t.os[os][f] or t[f] 193 + if type(t.os[os][f]) == "table" then 194 + str = f:upper()..'=(' 195 + for _, v in ipairs(t.os[os][f]) do 196 + str = str..' "'..v..'"' 197 + end 198 + str = str..' )' 199 + print(str) 200 + else 201 + print(f:upper()..'="'..t.os[os][f]..'"') 202 + end 203 + end 204 +else 205 + print(os:upper()..'=false') 206 +end 207 + 208 +if t.os.windows and os == "windows" then 209 + t.os.windows.x32 = t.os.windows.x32 and true or false 210 + t.os.windows.x64 = t.os.windows.x64 and true or false 211 + t.os.windows.installer = t.os.windows.installer and true or false 212 + t.os.windows.appid = t.os.windows.appid or "" 213 + print("X32="..tostring(t.os.windows.x32)) 214 + print("X64="..tostring(t.os.windows.x64)) 215 + print("INSTALLER="..tostring(t.os.windows.installer)) 216 + print("APPID="..t.os.windows.appid) 217 +end 218 +EOF 219 +) 220 + eval "$var" 221 + fi 222 +} 223 + 224 + 225 +# Read script options 226 +## $1: options prefix 227 +read_options () { 228 + local pre="$1" 229 + eval set -- "$ARGS" 230 + while true; do 231 + case "$1" in 232 + -a|--${pre}author ) AUTHOR="$2"; shift 2 ;; 233 + --${pre}clean ) rm -rf "$CACHE_DIR"; shift ;; 234 + -d|--${pre}description ) DESCRIPTION="$2"; shift 2 ;; 235 + -e|--${pre}email ) EMAIL="$2"; shift 2 ;; 236 + -h|--${pre}help ) short_help; exit 0 ;; 237 + -i|--${pre}icon ) 238 + ICON="$2" 239 + if [[ -d $ICON ]]; then 240 + local icon="$(readlink -m "$ICON")" 241 + local wd="$(readlink -m "$PWD")" 242 + EXCLUDE+=( "${icon//$wd\/}/*" ) 243 + elif [[ -f $ICON ]]; then 244 + EXCLUDE+=( "$ICON" ) 245 + fi 246 + shift 2 ;; 247 + -l|--${pre}love ) if ! gen_version "$2"; then exit_module "version"; fi; shift 2 ;; 248 + -f|--${pre}lovefile ) LOVEFILE="$2"; shift 2 ;; 249 + -p|--${pre}pkg ) IDENTITY="$2"; shift 2 ;; 250 + -r|--${pre}release ) RELEASE_DIR="$2"; shift 2 ;; 251 + -t|--${pre}title ) TITLE="$2"; shift 2 ;; 252 + -u|--${pre}url ) URL="$2"; shift 2 ;; 253 + -v|--${pre}version ) GAME_VERSION="$2"; shift 2 ;; 254 + -x|--${pre}exclude ) EXCLUDE+=( "$2" ); shift 2 ;; 255 + -- ) shift; break ;; 256 + * ) shift ;; 257 + esac 258 + done 259 +} 260 + 261 + 262 +# Test if default module should be executed 263 +default_module () { 264 + if [[ $? -ne 2 ]]; then 265 + DEFAULT_MODULE=false 266 + fi 267 +} 268 + 269 +# Print short help 270 +short_help () { 271 + cat <<EndOfSHelp 272 +Usage: love-release.sh [options...] [files...] 273 +Options: 274 + -h Print short help 275 + -t <title> Set the project's title 276 + -r <release> Set the release directory 277 + -v <version> Set the LÖVE version 278 +Modules: 279 + -L LÖVE 280 +\ -A Android\ 281 +\ -D Debian\ 282 +\ -M Mac OS X\ 283 +\ -W Windows 284 +EndOfSHelp 285 +} 286 + 287 +dump_var () { 288 + echo "LOVE_VERSION=$LOVE_VERSION" 289 + echo "LOVE_DEF_VERSION=$LOVE_DEF_VERSION" 290 + echo "LOVE_WEB_VERSION=$LOVE_WEB_VERSION" 291 + echo 292 + echo "RELEASE_DIR=$RELEASE_DIR" 293 + echo "CACHE_DIR=$CACHE_DIR" 294 + echo 295 + echo "IDENTITY=$IDENTITY" 296 + echo "GAME_VERSION=$GAME_VERSION" 297 + echo "ICON=$ICON" 298 + echo 299 + echo "TITLE=$TITLE" 300 + echo "AUTHOR=$AUTHOR" 301 + echo "EMAIL=$EMAIL" 302 + echo "URL=$URL" 303 + echo "DESCRIPTION=$DESCRIPTION" 304 + echo 305 + echo "${FILES[@]}" 306 +} 307 + 308 + 309 +# Modules functions 310 + 311 +# Init module 312 +## $1: Pretty module name 313 +## $2: Configuration module name 314 +## $3: Module option 315 +## return: 0 - if module should be executed, else exit 2 316 +init_module () { 317 + ( 318 + opt="$3" 319 + if (( ${#opt} == 1 )); then opt="-$opt" 320 + elif (( ${#opt} >= 2 )); then opt="--$opt"; fi 321 + eval set -- "$ARGS" 322 + while true; do 323 + case "$1" in 324 + $opt ) exit 0 ;; 325 + -- ) exit 1 ;; 326 + * ) shift ;; 327 + esac 328 + done 329 + ) 330 + local opt=$? 331 + local module="$2" 332 + read_config "$module" 333 + module=${module^^} 334 + if (( $opt == 0 )); then 335 + if [[ ${!module} == false ]]; then 336 + read_config "default" 337 + fi 338 + else 339 + if [[ ${!module} == false ]]; then 340 + exit_module "execute" 341 + fi 342 + fi 343 + gen_version $VERSION 344 + unset VERSION 345 + MODULE="$1" 346 + CACHE_DIR="$CACHE_DIR/$2" 347 + read_options "$3" 348 + LOVE_FILE="${TITLE}.love" 349 + mkdir -p "$RELEASE_DIR" "$CACHE_DIR" 350 + echo "Generating $TITLE with LÖVE $LOVE_VERSION for ${MODULE}..." 351 + return 0 352 +} 353 + 354 +# Create the LÖVE file 355 +## $1: Compression level 0-9 356 +create_love_file () { 357 + if [[ -r $LOVEFILE ]]; then 358 + cp "$LOVEFILE" $RELEASE_DIR/$LOVE_FILE 359 + else 360 + local dotfiles=() 361 + for file in .*; do 362 + if [[ $file == '.' || $file == '..' ]]; then continue; fi 363 + if [[ -d $file ]]; then file="$file/*"; fi 364 + dotfiles+=( "$file" ) 365 + done 366 + local release_dir="$(readlink -m "$RELEASE_DIR")" 367 + local wd="$(readlink -m "$PWD")" 368 + zip -FS -$1 -r "$RELEASE_DIR/$LOVE_FILE" \ 369 + -x "$0" "${release_dir//$wd\/}/*" "${dotfiles[@]}" "${EXCLUDE[@]}" @ \ 370 + "${FILES[@]}" 371 + fi 372 +} 373 + 374 +# Exit module 375 +## $1: optional error identifier 376 +## $2: optional error message 377 +exit_module () { 378 + if [[ -z $1 ]]; then 379 + echo "Done !" 380 + exit 0 381 + fi 382 + if [[ -n $2 ]]; then 383 + >&2 echo -e "$2" 384 + fi 385 + case $1 in 386 + execute ) 387 + exit 2 ;; 388 + binary ) 389 + >&2 echo "LÖVE $LOVE_VERSION could not be found or downloaded." 390 + exit 3 ;; 391 + options ) 392 + exit 4 ;; 393 + version ) 394 + >&2 echo "LÖVE version string is invalid." 395 + exit 5 ;; 396 + deps ) 397 + exit 6 ;; 398 + undef|* ) 399 + exit 1 ;; 400 + esac 401 +} 402 + 403 + 404 + 405 +# Main 406 + 407 +check_deps 408 + 409 +# Get latest LÖVE version number 410 +gen_version $LOVE_DEF_VERSION 411 +LOVE_WEB_VERSION=$(curl -s https://love2d.org/releases.xml | grep -m 2 "<title>" | tail -n 1 | grep -Eo "[0-9]+.[0-9]+.[0-9]+") 412 +gen_version $LOVE_WEB_VERSION 413 + 414 +INSTALLED=false 415 +EMBEDDED=true 416 + 417 +DEFAULT_MODULE=true 418 + 419 +TITLE="$(basename $(pwd))" 420 +PROJECT_DIR="$PWD" 421 +RELEASE_DIR=releases 422 +CACHE_DIR=~/.cache/love-release 423 +FILES=() 424 +EXCLUDE=() 425 + 426 +OPTIONS="W::MDALa:d:e:hi:l:p:r:t:u:v:x:" 427 +LONG_OPTIONS="Wauthor:,Wclean,Wdescription:,Wemail:,Wexclude:,Whelp,Wicon:,Wlove:,Wlovefile:,Wpkg:,Wrelease:,Wtitle:,Wurl:,Wversion:,Wappid:,Winstaller,Mauthor:,Mclean,Mdescription:,Memail:,Mexclude:,Mhelp,Micon:,Mlove:,Mlovefile:,Mpkg:,Mrelease:,Mtitle:,Murl:,Mversion:,Dauthor:,Dclean,Ddescription:,Demail:,Dexclude:,Dhelp,Dicon:,Dlove:,Dlovefile:,Dpkg:,Drelease:,Dtitle:,Durl:,Dversion:,Aauthor:,Aclean,Adescription:,Aemail:,Aexclude:,Ahelp,Aicon:,Alove:,Alovefile:,Apkg:,Arelease:,Atitle:,Aurl:,Aversion:,Aactivity:,Aupdate,author:,clean,description:,email:,exclude:,help,icon:,love:,lovefile:,pkg:,release:,title:,url:,version:" 428 +ARGS=$(getopt -o "$OPTIONS" -l "$LONG_OPTIONS" -n 'love-release' -- "$@") 429 +if (( $? != 0 )); then short_help; exit_module "options"; fi 430 +eval set -- "$ARGS" 431 +read_options 432 +while [[ $1 != '--' ]]; do shift; done; shift 433 +for arg do 434 + FILES+=( "$arg" ) 435 +done 436 +if (( ${#FILES} == 0 )); then FILES+=( "." ); fi 437 +eval set -- "$ARGS" 438 + 439 +if [[ $INSTALLED == false && $EMBEDDED == false ]]; then 440 + exit_module "undef" "love-release has not been installed, and is not embedded into one script." 441 +fi 442 + 443 +if [[ ! -f "main.lua" ]]; then 444 + >&2 echo "No main.lua file was found." 445 + exit_module 1 446 +fi 447 + 448 +if [[ $EMBEDDED == true ]]; then 449 + : # include_scripts_here 450 +(source <(cat <<\EndOfModule 451 +# Android debug package 452 +init_module "Android" "android" "A" 453 +OPTIONS="A" 454 +LONG_OPTIONS="activity:,update" 455 + 456 + 457 +IDENTITY=$(echo $TITLE | sed -e 's/[^-a-zA-Z0-9_]/-/g' | tr '[:upper:]' '[:lower:]') 458 +ACTIVITY=$(echo $TITLE | sed -e 's/[^a-zA-Z0-9_]/_/g') 459 + 460 + 461 +# Options 462 +while true; do 463 + case "$1" in 464 + --Aactivity ) ACTIVITY="$2"; shift 2 ;; 465 + --Aupdate ) UPDATE_ANDROID=true; shift ;; 466 + -- ) break ;; 467 + * ) shift ;; 468 + esac 469 +done 470 + 471 + 472 +# Android 473 +missing_info=false 474 +missing_deps=false 475 +error_msg="Could not build Android package." 476 +if ! command -v git > /dev/null 2>&1; then 477 + missing_deps=true 478 + error_msg="$error_msg\ngit was not found." 479 +fi 480 +if ! command -v ndk-build > /dev/null 2>&1; then 481 + missing_deps=true 482 + error_msg="$error_msg\nndk-build was not found." 483 +fi 484 +if ! command -v ant > /dev/null 2>&1; then 485 + missing_deps=true 486 + error_msg="$error_msg\nant was not found." 487 +fi 488 +if [[ $missing_deps == true ]]; then 489 + exit_module "deps" "$error_msg" 490 +fi 491 + 492 +if [[ -z $GAME_VERSION ]]; then 493 + missing_info=true 494 + error_msg="$error_msg\nMissing project's version. Use -v or --Aversion." 495 +fi 496 +if [[ -z $AUTHOR ]]; then 497 + missing_info=true 498 + error_msg="$error_msg\nMissing maintainer's name. Use -a or --Aauthor." 499 +fi 500 +if [[ $missing_info == true ]]; then 501 + exit_module "options" "$error_msg" 502 +fi 503 + 504 + 505 +create_love_file 0 506 + 507 + 508 +LOVE_ANDROID_DIR="$CACHE_DIR/love-android-sdl2" 509 +if [[ -d $LOVE_ANDROID_DIR ]]; then 510 + cd "$LOVE_ANDROID_DIR" 511 + git checkout -- . 512 + rm -rf src/com bin gen 513 + if [[ $UPDATE_ANDROID = true ]]; then 514 + LOCAL=$(git rev-parse @) 515 + REMOTE=$(git rev-parse @{u}) 516 + BASE=$(git merge-base @ @{u}) 517 + if [[ $LOCAL == $REMOTE ]]; then 518 + echo "love-android-sdl2 is already up-to-date." 519 + elif [[ $LOCAL == $BASE ]]; then 520 + git pull 521 + ndk-build --jobs $(( $(nproc) + 1)) 522 + fi 523 + fi 524 +else 525 + cd "$CACHE_DIR" 526 + git clone https://bitbucket.org/MartinFelis/love-android-sdl2.git 527 + cd "$LOVE_ANDROID_DIR" 528 + ndk-build --jobs $(( $(nproc) + 1)) 529 +fi 530 + 531 +ANDROID_VERSION=$(grep -Eo -m 1 "[0-9]+.[0-9]+.[0-9]+[a-z]*" "$LOVE_ANDROID_DIR"/AndroidManifest.xml) 532 +ANDROID_LOVE_VERSION=$(echo "$ANDROID_VERSION" | grep -Eo "[0-9]+.[0-9]+.[0-9]+") 533 + 534 +if [[ "$LOVE_VERSION" != "$ANDROID_LOVE_VERSION" ]]; then 535 + exit_module 1 "Love version ($LOVE_VERSION) differs from love-android-sdl2 version ($ANDROID_LOVE_VERSION). Could not create package." 536 +fi 537 + 538 +mkdir -p assets 539 +cd "$PROJECT_DIR" 540 +cd "$RELEASE_DIR" 541 +cp "$LOVE_FILE" "$LOVE_ANDROID_DIR/assets/game.love" 542 +cd "$LOVE_ANDROID_DIR" 543 + 544 +sed -i.bak -e "s/org.love2d.android/com.${AUTHOR}.${IDENTITY}/" \ 545 + -e "s/$ANDROID_VERSION/${ANDROID_VERSION}-${IDENTITY}-v${GAME_VERSION}/" \ 546 + -e "0,/LÖVE for Android/s//$TITLE $GAME_VERSION/" \ 547 + -e "s/LÖVE for Android/$TITLE/" \ 548 + -e "s/GameActivity/$ACTIVITY/" \ 549 + AndroidManifest.xml 550 + 551 +mkdir -p "src/com/$AUTHOR/$IDENTITY" 552 +cat > "src/com/$AUTHOR/$IDENTITY/${ACTIVITY}.java" <<EOF 553 +package com.${AUTHOR}.${IDENTITY}; 554 +import org.love2d.android.GameActivity; 555 + 556 +public class $ACTIVITY extends GameActivity {} 557 +EOF 558 + 559 +if [[ -d "$ICON" ]]; then 560 + cd "$PROJECT_DIR" 561 + cd "$ICON" 562 + 563 + for icon in *; do 564 + RES=$(echo "$icon" | grep -Eo "[0-9]+x[0-9]+") 565 + EXT=$(echo "$icon" | sed -e 's/.*\.//g') 566 + if [[ $RES == "42x42" ]]; then 567 + cp "$icon" "$LOVE_ANDROID_DIR/res/drawable-mdpi/ic_launcher.png" 568 + elif [[ $RES == "72x72" ]]; then 569 + cp "$icon" "$LOVE_ANDROID_DIR/res/drawable-hdpi/ic_launcher.png" 570 + elif [[ $RES == "96x96" ]]; then 571 + cp "$icon" "$LOVE_ANDROID_DIR/res/drawable-xhdpi/ic_launcher.png" 572 + elif [[ $RES == "144x144" ]]; then 573 + cp "$icon" "$LOVE_ANDROID_DIR/res/drawable-xxhdpi/ic_launcher.png" 574 + elif [[ "$RES" == "732x412" ]]; then 575 + cp "$icon" "$LOVE_ANDROID_DIR/res/drawable-xhdpi/ouya_icon.png" 576 + fi 577 + done 578 + if [[ -f "drawable-mdpi/ic_launcher.png" ]]; then 579 + cp "drawable-mdpi/ic_launcher.png" "$LOVE_ANDROID_DIR/res/drawable-mdpi/ic_launcher.png" 580 + fi 581 + if [[ -f "drawable-hdpi/ic_launcher.png" ]]; then 582 + cp "drawable-hdpi/ic_launcher.png" "$LOVE_ANDROID_DIR/res/drawable-hdpi/ic_launcher.png" 583 + fi 584 + if [[ -f "drawable-xhdpi/ic_launcher.png" ]]; then 585 + cp "drawable-xhdpi/ic_launcher.png" "$LOVE_ANDROID_DIR/res/drawable-xhdpi/ic_launcher.png" 586 + fi 587 + if [[ -f "drawable-xxhdpi/ic_launcher.png" ]]; then 588 + cp "drawable-xxhdpi/ic_launcher.png" "$LOVE_ANDROID_DIR/res/drawable-xxhdpi/ic_launcher.png" 589 + fi 590 + if [[ -f "drawable-xhdpi/ouya_icon.png" ]]; then 591 + cp "drawable-xhdpi/ouya_icon.png" "$LOVE_ANDROID_DIR/res/drawable-xhdpi/ouya_icon.png" 592 + fi 593 + 594 + cd "$LOVE_ANDROID_DIR" 595 +fi 596 + 597 + 598 +ant debug 599 +cd "$PROJECT_DIR" 600 +cp "$LOVE_ANDROID_DIR/bin/love_android_sdl2-debug.apk" "$RELEASE_DIR" 601 +git checkout -- . 602 +rm -rf src/com bin gen 603 + 604 + 605 +exit_module 606 +EndOfModule 607 +)) 608 +default_module 609 + 610 + 611 +(source <(cat <<\EndOfModule 612 +# Debian package 613 +init_module "Debian" "debian" "D" 614 +OPTIONS="D" 615 +LONG_OPTIONS="" 616 + 617 + 618 +IDENTITY=$(echo $TITLE | sed -e 's/[^-a-zA-Z0-9_]/-/g' | tr '[:upper:]' '[:lower:]') 619 + 620 +# Debian 621 +missing_info=false 622 +error_msg="Could not build Debian package." 623 +if [[ -z $GAME_VERSION ]]; then 624 + missing_info=true 625 + error_msg="$error_msg\nMissing project's version. Use -v or --Dversion." 626 +fi 627 +if [[ -z $URL ]]; then 628 + missing_info=true 629 + error_msg="$error_msg\nMissing project's homepage. Use -u or -Durl." 630 +fi 631 +if [[ -z $DESCRIPTION ]]; then 632 + missing_info=true 633 + error_msg="$error_msg\nMissing project's description. Use -d or --Ddescription." 634 +fi 635 +if [[ -z $AUTHOR ]]; then 636 + missing_info=true 637 + error_msg="$error_msg\nMissing maintainer's name. Use -a or --Dauthor." 638 +fi 639 +if [[ -z $EMAIL ]]; then 640 + missing_info=true 641 + error_msg="$error_msg\nMissing maintainer's email. Use -e or --Demail." 642 +fi 643 +if [[ $missing_info == true ]]; then 644 + exit_module "options" "$error_msg" 645 +fi 646 + 647 + 648 +create_love_file 9 649 +cd "$RELEASE_DIR" 650 + 651 + 652 +TEMP="$(mktemp -d)" 653 +umask 0022 654 + 655 +mkdir -p "$TEMP/DEBIAN" 656 +cat > "$TEMP/DEBIAN/control" <<EOF 657 +Package: $IDENTITY 658 +Version: $GAME_VERSION 659 +Architecture: all 660 +Maintainer: $AUTHOR <$EMAIL> 661 +Installed-Size: $(( $(stat -c %s "$LOVE_FILE") / 1024 )) 662 +Depends: love (>= $LOVE_VERSION) 663 +Priority: extra 664 +Homepage: $URL 665 +Description: $DESCRIPTION 666 +EOF 667 + 668 +mkdir -p "$TEMP/usr/share/applications" 669 +cat > "$TEMP/usr/share/applications/${IDENTITY}.desktop" <<EOF 670 +[Desktop Entry] 671 +Name=$TITLE 672 +Comment=$DESCRIPTION 673 +Exec=$IDENTITY 674 +Type=Application 675 +Categories=Game; 676 +EOF 677 + 678 +mkdir -p "$TEMP/usr/bin" 679 +cat <(echo -ne '#!/usr/bin/env love\n') "$LOVE_FILE" > "$TEMP/usr/bin/$IDENTITY" 680 +chmod +x "$TEMP/usr/bin/$IDENTITY" 681 + 682 +if [[ -d $ICON ]]; then 683 + ICON_LOC=$TEMP/usr/share/icons/hicolor 684 + mkdir -p $ICON_LOC 685 + echo "Icon=$IDENTITY" >> "$TEMP/usr/share/applications/${IDENTITY}.desktop" 686 + 687 + cd "$ICON" 688 + for file in *; do 689 + RES=$(echo "$file" | grep -Eo "[0-9]+x[0-9]+") 690 + EXT=$(echo "$file" | sed -e 's/.*\.//g') 691 + if [[ $EXT == "svg" ]]; then 692 + mkdir -p "$ICON_LOC/scalable/apps" 693 + cp "$file" "$ICON_LOC/scalable/apps/${IDENTITY}.svg" 694 + chmod 0644 "$ICON_LOC/scalable/apps/${IDENTITY}.svg" 695 + elif [[ -n $RES ]]; then 696 + mkdir -p "$ICON_LOC/$RES/apps" 697 + cp "$file" "$ICON_LOC/$RES/apps/${IDENTITY}.$EXT" 698 + chmod 0644 "$ICON_LOC/$RES/apps/${IDENTITY}.$EXT" 699 + fi 700 + done 701 +else 702 + echo "Icon=love" >> "$TEMP/usr/share/applications/${IDENTITY}.desktop" 703 +fi 704 + 705 +cd "$TEMP" 706 +find "usr" -type f -exec md5sum {} \; | sed -E "s/^([0-9a-f]{32} )/\1\//g" > "$TEMP/DEBIAN/md5sums" 707 +cd "$PROJECT_DIR" 708 + 709 +fakeroot dpkg-deb -b "$TEMP" "$RELEASE_DIR/$IDENTITY-${GAME_VERSION}_all.deb" 710 +rm -rf "$TEMP" 711 + 712 + 713 +exit_module 714 +EndOfModule 715 +)) 716 +default_module 717 + 718 + 719 +(source <(cat <<\EndOfModule 720 +# Mac OS X 721 +init_module "Mac OS X" "osx" "M" 722 +OPTIONS="M" 723 +LONG_OPTIONS="" 724 + 725 + 726 +IDENTITY=$(echo $TITLE | sed -e 's/[^-a-zA-Z0-9_]/-/g' | tr '[:upper:]' '[:lower:]') 727 + 728 +if [[ -z $AUTHOR ]]; then 729 + exit_module "options" "Missing maintainer's name. Use -a or --Mauthor." 730 +fi 731 +if [[ -z $GAME_VERSION ]]; then 732 + GAME_VERSION="$LOVE_VERSION" 733 +fi 734 + 735 +if [[ -n $ICON ]]; then 736 + if [[ -d $ICON ]]; then 737 + for file in $ICON/*.icns; do 738 + if [[ -f $file ]]; then 739 + ICON="$file" 740 + break 741 + else 742 + found=false 743 + fi 744 + done 745 + fi 746 + if [[ $found == false || ! -f $ICON ]]; then 747 + >&2 echo "OS X icon was not found in ${ICON}." 748 + icon=Love.icns 749 + ICON= 750 + else 751 + icon="${IDENTITY}.icns" 752 + fi 753 +fi 754 + 755 + 756 +create_love_file 9 757 +cd "$RELEASE_DIR" 758 + 759 + 760 +## MacOS ## 761 +if [[ ! -f "$CACHE_DIR/love-$LOVE_VERSION-macos.zip" ]]; then 762 + curl -L -C - -o $CACHE_DIR/love-$LOVE_VERSION-macos.zip https://bitbucket.org/rude/love/downloads/love-$LOVE_VERSION-macos.zip 763 +fi 764 +unzip -qq "$CACHE_DIR/love-$LOVE_VERSION-macos.zip" 765 + 766 +rm -rf "$TITLE-macos.zip" 2> /dev/null 767 +mv love.app "${TITLE}.app" 768 +cp "$LOVE_FILE" "${TITLE}.app/Contents/Resources" 769 +if [[ -n $ICON ]]; then 770 + cd "$PROJECT_DIR" 771 + cp "$ICON" "$RELEASE_DIR/$icon" 772 + cd "$RELEASE_DIR" 773 + mv "$icon" "${TITLE}.app/Contents/Resources" 774 +fi 775 + 776 +sed -i.bak -e '/<key>UTExportedTypeDeclarations<\/key>/,/^\t<\/array>/d' \ 777 + -e "s/>org.love2d.love</>org.${AUTHOR}.$IDENTITY</" \ 778 + -e "s/$LOVE_VERSION/$GAME_VERSION/" \ 779 + -e "s/Love.icns/$icon/" \ 780 + -e "s/>LÖVE</>$TITLE</" \ 781 + "${TITLE}.app/Contents/Info.plist" 782 +rm "${TITLE}.app/Contents/Info.plist.bak" 783 + 784 +zip -9 -qyr "${TITLE}-macos.zip" "${TITLE}.app" 785 +rm -rf love-$LOVE_VERSION-macos.zip "${TITLE}.app" __MACOSX 786 + 787 +exit_module 788 +EndOfModule 789 +)) 790 +default_module 791 + 792 + 793 +(source <(cat <<\EndOfModule 794 +# Windows 795 +init_module "Windows" "windows" "W" 796 +OPTIONS="W::" 797 +LONG_OPTIONS="appid:,installer" 798 + 799 +if [[ -z $IDENTITY ]]; then 800 + IDENTITY=$(echo $IDENTITY | sed -e 's/[^-a-zA-Z0-9_]/-/g' | tr '[:upper:]' '[:lower:]') 801 +fi 802 + 803 +while true; do 804 + case "$1" in 805 + --Wappid ) APPID="$2"; shift 2 ;; 806 + --Winstaller ) INSTALLER=true; shift ;; 807 + -W ) if [[ -z "$2" ]]; then X32=true; X64=true; 808 + elif (( "$2" == 32 )); then X32=true; 809 + elif (( "$2" == 64 )); then X64=true; 810 + fi; shift ;; 811 + -- ) break ;; 812 + * ) shift ;; 813 + esac 814 +done 815 + 816 + 817 +FOUND_WINE=true 818 +command -v wine >/dev/null 2>&1 || { FOUND_WINE=false; } && { WINEPREFIX="$CACHE_DIR/wine"; } 819 + 820 + 821 +if [[ -n $ICON ]]; then 822 + if [[ $FOUND_WINE == true ]]; then 823 + if [[ -d $ICON ]]; then 824 + for file in $ICON/*.ico; do 825 + if [[ -f $file ]]; then 826 + ICON="$file" 827 + break 828 + else 829 + found=false 830 + fi 831 + done 832 + fi 833 + if [[ $found == false || ! -f $ICON ]]; then 834 + >&2 echo "Windows icon was not found in ${ICON}." 835 + ICON= 836 + else 837 + RESHACKER="$WINEPREFIX/drive_c/Program Files (x86)/Resource Hacker/ResourceHacker.exe" 838 + if [[ ! -f $RESHACKER ]]; then 839 + curl -L -C - -o "$WINEPREFIX/drive_c/reshacker_setup.exe" "http://www.angusj.com/resourcehacker/reshacker_setup.exe" 840 + WINEPREFIX="$WINEPREFIX" wine "$WINEPREFIX/drive_c/reshacker_setup.exe" 2>&1 /dev/null 841 + fi 842 + fi 843 + else 844 + >&2 echo "Can not set Windows icon without Wine." 845 + fi 846 +fi 847 + 848 + 849 +if [[ $INSTALLER == true ]]; then 850 + missing_opt=false 851 + error_msg="Could not build Windows installer." 852 + if [[ $FOUND_WINE == false ]]; then 853 + >&2 echo "Can not build Windows installer without Wine." 854 + exit_module "deps" 855 + fi 856 + if [[ -z $AUTHOR ]]; then 857 + missing_opt=true 858 + error_msg="$error_msg\nMissing project author. Use -a or --Wauthor." 859 + fi 860 + if [[ -z $URL ]]; then 861 + missing_opt=true 862 + error_msg="$error_msg\nMissing project url. Use -u or --Wurl." 863 + fi 864 + if [[ -z $GAME_VERSION ]]; then 865 + missing_opt=true 866 + error_msg="$error_msg\nMissing project version. Use -v or --Wversion." 867 + fi 868 + if [[ -z $APPID ]]; then 869 + missing_opt=true 870 + error_msg="$error_msg\nMissing application GUID. Use --Wappid." 871 + fi 872 + if [[ $missing_opt == true ]]; then 873 + exit_module "options" "$error_msg" 874 + fi 875 + 876 + INNOSETUP="$WINEPREFIX/drive_c/Program Files (x86)/Inno Setup 5/ISCC.exe" 877 + if [[ ! -f $INNOSETUP ]]; then 878 + curl -L -C - -o "$WINEPREFIX/drive_c/is-unicode.exe" "http://www.jrsoftware.org/download.php/is-unicode.exe" 879 + WINEPREFIX="$WINEPREFIX" wine "$WINEPREFIX/drive_c/is-unicode.exe" 2>&1 /dev/null 880 + fi 881 + 882 +# Inno Setup 883 +# $1: Path to game exe directory 884 +# $2: true if 64 bits release 885 +create_installer () { 886 + ln -s "$1" "$WINEPREFIX/drive_c/game" 887 + if [[ -n $ICON ]]; then 888 + cd "$PROJECT_DIR" 889 + ln -s "$ICON" "$WINEPREFIX/drive_c/game.ico" 890 + cd "$RELEASE_DIR" 891 + else 892 + ln -s "$1/game.ico" "$WINEPREFIX/drive_c/game.ico" 893 + fi 894 + 895 + cat > "$WINEPREFIX/drive_c/innosetup.iss" <<EOF 896 +#define MyAppName "$TITLE" 897 +#define MyAppVersion "$GAME_VERSION" 898 +#define MyAppPublisher "$AUTHOR" 899 +#define MyAppURL "$URL" 900 +#define MyAppExeName "${TITLE}.exe" 901 + 902 +[Setup] 903 +;ArchitecturesInstallIn64BitMode=x64 ia64 904 +AppId={{$APPID} 905 +AppName={#MyAppName} 906 +AppVersion={#MyAppVersion} 907 +;AppVerName={#MyAppName} {#MyAppVersion} 908 +AppPublisher={#MyAppPublisher} 909 +AppPublisherURL={#MyAppURL} 910 +AppSupportURL={#MyAppURL} 911 +AppUpdatesURL={#MyAppURL} 912 +DefaultDirName={pf}\{#MyAppName} 913 +DefaultGroupName={#MyAppName} 914 +AllowNoIcons=yes 915 +OutputBaseFilename=${IDENTITY}-setup 916 +SetupIconFile=C:\\game.ico 917 +Compression=lzma 918 +SolidCompression=yes 919 + 920 +[Languages] 921 +Name: "english"; MessagesFile: "compiler:Default.isl" 922 +Name: "french"; MessagesFile: "compiler:Languages\French.isl" 923 +Name: "german"; MessagesFile: "compiler:Languages\German.isl" 924 + 925 +[Tasks] 926 +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 927 + 928 +[Icons] 929 +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 930 +Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" 931 +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 932 + 933 +[Run] 934 +Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 935 + 936 +[Files] 937 +EOF 938 + if [[ $2 == true ]]; then 939 + sed -i 's/;ArchitecturesInstallIn64BitMode/ArchitecturesInstallIn64BitMode/' "$WINEPREFIX/drive_c/innosetup.iss" 940 + fi 941 + 942 + for file in $1; do 943 + echo "Source: \"C:\\game\\$file\"; DestDir: \"{app}\"; Flags: ignoreversion" \ 944 + >> "$WINEPREFIX"/drive_c/innosetup.iss 945 + done 946 + 947 + WINEPREFIX="$WINEPREFIX" wine "$INNOSETUP" /Q 'c:\innosetup.iss' 948 + mv "$WINEPREFIX/drive_c/Output/$IDENTITY-setup.exe" . 949 + rm -rf "$WINEPREFIX/drive_c/{game,game.ico,innosetup.iss,Output}" 950 +} 951 + 952 +fi 953 + 954 +${X32:=false} 955 +${X64:=false} 956 +if [[ $X32 == false && $X64 == false ]]; then 957 + X32=true 958 + X64=true 959 +fi 960 + 961 + 962 +create_love_file 9 963 +cd "$RELEASE_DIR" 964 + 965 + 966 +# Windows 32-bits 967 +if [[ $X32 == true ]]; then 968 + 969 + if [[ ! -f "$CACHE_DIR/love-$LOVE_VERSION-win32.zip" ]]; then 970 + curl -L -C - -o "$CACHE_DIR/love-$LOVE_VERSION-win32.zip" "https://bitbucket.org/rude/love/downloads/love-$LOVE_VERSION-win32.zip" 971 + fi 972 + 973 + unzip -qq "$CACHE_DIR/love-$LOVE_VERSION-win32.zip" 974 + 975 + if [[ -n $ICON ]]; then 976 + WINEPREFIX="$WINEPREFIX" wine "$RESHACKER" \ 977 + -addoverwrite "love-$LOVE_VERSION-win32/love.exe,love-$LOVE_VERSION-win32/love.exe,$ICON,ICONGROUP,MAINICON,0" 2>&1 /dev/null 978 + fi 979 + 980 + # version number is incorrect inside zip file; oops 981 + LOVE_VERSION="$LOVE_VERSION".0 982 + cat love-$LOVE_VERSION-win32/love.exe "$LOVE_FILE" > "love-$LOVE_VERSION-win32/${TITLE}.exe" 983 + rm love-$LOVE_VERSION-win32/love.exe 984 + mv love-$LOVE_VERSION-win32 "$TITLE"-win32 985 + if [[ $INSTALLER == true ]]; then 986 + rm -rf "$IDENTITY-setup-win32.exe" 2> /dev/null 987 + create_installer "$TITLE-win32" 988 + mv "$IDENTITY-setup.exe" "$IDENTITY-setup-win32.exe" 989 + else 990 + zip -FS -9 -qr "$TITLE-win32.zip" "$TITLE-win32" 991 + fi 992 + rm -rf "$TITLE-win32" 993 +fi 994 + 995 +## Windows 64-bits ## 996 +if [[ $X64 == true ]] && compare_version "$LOVE_VERSION" '>=' '0.8.0'; then 997 + 998 + if [[ ! -f "$CACHE_DIR/love-$LOVE_VERSION-win64.zip" ]]; then 999 + if compare_version "$LOVE_VERSION" '>=' '0.9.0'; then 1000 + curl -L -C - -o "$CACHE_DIR/love-$LOVE_VERSION-win64.zip" "https://bitbucket.org/rude/love/downloads/love-$LOVE_VERSION-win64.zip" 1001 + else 1002 + curl -L -C - -o "$CACHE_DIR/love-$LOVE_VERSION-win-x64.zip" "https://bitbucket.org/rude/love/downloads/love-$LOVE_VERSION-win-x64.zip" 1003 + fi 1004 + fi 1005 + 1006 + unzip -qq "$CACHE_DIR/love-$LOVE_VERSION-win64.zip" 1007 + 1008 + if [[ -n $ICON ]]; then 1009 + WINEPREFIX="$WINEPREFIX" wine "$RESHACKER" \ 1010 + -addoverwrite "love-$LOVE_VERSION-win64/love.exe,love-$LOVE_VERSION-win64/love.exe,$ICON,ICONGROUP,MAINICON,0" 2>&1 /dev/null 1011 + fi 1012 + 1013 + cat love-$LOVE_VERSION-win64/love.exe "$LOVE_FILE" > "love-$LOVE_VERSION-win64/${TITLE}.exe" 1014 + rm love-$LOVE_VERSION-win64/love.exe 1015 + mv love-$LOVE_VERSION-win64 "$TITLE-win64" 1016 + if [[ $INSTALLER == true ]]; then 1017 + rm -rf "$IDENTITY-setup-win64.exe" 2> /dev/null 1018 + create_installer "$TITLE-win64" "true" 1019 + mv "$IDENTITY-setup.exe" "$IDENTITY-setup-win64.exe" 1020 + else 1021 + zip -FS -9 -qr "$TITLE-win64.zip" "$TITLE-win64" 1022 + fi 1023 + rm -rf "$TITLE-win64" 1024 +fi 1025 + 1026 + 1027 +exit_module 1028 +EndOfModule 1029 +)) 1030 +default_module 1031 + 1032 + 1033 +elif [[ $INSTALLED == true ]]; then 1034 + SCRIPTS_DIR="scripts" 1035 + for file in "$SCRIPTS_DIR"/*.sh; do 1036 + (source "$file") 1037 + default_module 1038 + done 1039 +fi 1040 + 1041 + 1042 +( 1043 + init_module "LÖVE" "love" "L" 1044 + create_love_file 9 1045 + exit_module 1046 +) 1047 +if [[ $? -ne 0 && $DEFAULT_MODULE == true ]]; then 1048 +( 1049 + init_module "LÖVE" "default" 1050 + create_love_file 9 1051 + exit_module 1052 +) 1053 +fi 1054 + 1055 +exit 0
Added src/worpt/main.lua.
1 +-- bootstrap the compiler 2 +fennel = require("lib.fennel") 3 +table.insert(package.loaders, fennel.make_searcher({correlate=true})) 4 +pp = function(x) print(require("lib.fennelview")(x)) end 5 +pps = function(x) return require("lib.fennelview")(x) end 6 +lume = require("lib.lume") 7 +log = function(...) print(string.format(...)) end 8 + 9 +require("wrap")
Added src/worpt/makefile.
1 +VERSION=0.1.3 2 +LOVE_VERSION=11.3 3 +NAME=worpt 4 +ITCH_ACCOUNT=chewbranca 5 +URL='https://chewbranca.com/dir?ci=6f88a252fe2ba468&name=src/worpt' 6 +AUTHOR=Russell Branca {@chewbranca} <chewbranca@gmail.com> 7 +DESCRIPTION=Worpt is a prototype platformer using the awesome _Warped City_ art assets from https://ansimuz.itch.io/warped-city . Currently this is an engine capable of running the map.json file from the _Warped City_ demo game. The goal was for this to be the intro level and then to create new levels, and the progress is that intro level is playable and nearly complete. 8 +LIBS := $(wildcard lib/*) 9 +LUA := $(wildcard *.lua) 10 +SRC := $(wildcard *.fnl) 11 +OUT := $(patsubst %.fnl,%.lua,$(SRC)) 12 + 13 + 14 +run: $(OUT) ; love . 15 + 16 +count: ; cloc *.fnl --force-lang=clojure 17 + 18 +clean: ; rm -rf releases/* $(OUT) 19 + 20 +cleansrc: ; rm -rf $(OUT) 21 + 22 +%.lua: %.fnl; lua lib/fennel --compile --correlate $< > $@ 23 + 24 +LOVEFILE=releases/$(NAME)-$(VERSION).love 25 + 26 +$(LOVEFILE): $(LUA) $(OUT) $(LIBS) assets #text 27 + mkdir -p releases/ 28 + find $^ -type f | LC_ALL=C sort | env TZ=UTC zip -r -q -9 -X $@ -@ 29 + 30 +love: $(LOVEFILE) 31 + 32 +# platform-specific distributables 33 + 34 +REL=$(PWD)/love-release.sh # https://p.hagelb.org/love-release.sh 35 +FLAGS=-a "$(AUTHOR)" --description $(DESCRIPTION) \ 36 + --love $(LOVE_VERSION) --url $(URL) --version $(VERSION) --lovefile $(LOVEFILE) 37 + 38 +releases/$(NAME)-$(VERSION)-x86_64.AppImage: $(LOVEFILE) 39 + cd appimage && ./build.sh $(LOVE_VERSION) $(PWD)/$(LOVEFILE) 40 + mv appimage/game-x86_64.AppImage $@ 41 + 42 +releases/$(NAME)-$(VERSION)-macos.zip: $(LOVEFILE) 43 + $(REL) $(FLAGS) -M 44 + mv releases/$(NAME)-macos.zip $@ 45 + 46 +releases/$(NAME)-$(VERSION)-win.zip: $(LOVEFILE) 47 + $(REL) $(FLAGS) -W32 48 + mv releases/$(NAME)-win32.zip $@ 49 + 50 +linux: releases/$(NAME)-$(VERSION)-x86_64.AppImage 51 +mac: releases/$(NAME)-$(VERSION)-macos.zip 52 +windows: releases/$(NAME)-$(VERSION)-win.zip 53 + 54 +# If you release on itch.io, you should install butler: 55 +# https://itch.io/docs/butler/installing.html 56 + 57 +uploadlinux: releases/$(NAME)-$(VERSION)-x86_64.AppImage 58 + butler push $^ $(ITCH_ACCOUNT)/$(NAME):linux --userversion $(VERSION) 59 +uploadmac: releases/$(NAME)-$(VERSION)-macos.zip 60 + butler push $^ $(ITCH_ACCOUNT)/$(NAME):mac --userversion $(VERSION) 61 +uploadwindows: releases/$(NAME)-$(VERSION)-win.zip 62 + butler push $^ $(ITCH_ACCOUNT)/$(NAME):windows --userversion $(VERSION) 63 + 64 +upload: uploadlinux uploadmac uploadwindows 65 + 66 +release: linux mac windows upload cleansrc
Added src/worpt/map.lua.
1 +-- based on assets/warped_city/maps/map.json 2 +return { 3 + version = "1.2", 4 + luaversion = "5.1", 5 + tiledversion = "1.3.3", 6 + orientation = "orthogonal", 7 + renderorder = "right-down", 8 + width = 120, 9 + height = 28, 10 + tilewidth = 16, 11 + tileheight = 16, 12 + nextlayerid = 4, 13 + nextobjectid = 45, 14 + backgroundcolor = { 0, 0, 255 }, 15 + properties = {}, 16 + tilesets = { 17 + { 18 + name = "tileset", 19 + firstgid = 1, 20 + tilewidth = 16, 21 + tileheight = 16, 22 + spacing = 0, 23 + margin = 0, 24 + columns = 24, 25 + image = "assets/warped_city/environment/tileset.png", 26 + imagewidth = 384, 27 + imageheight = 256, 28 + tileoffset = { 29 + x = 0, 30 + y = 0 31 + }, 32 + grid = { 33 + orientation = "orthogonal", 34 + width = 16, 35 + height = 16 36 + }, 37 + properties = {}, 38 + terrains = {}, 39 + tilecount = 384, 40 + tiles = {} 41 + } 42 + }, 43 + layers = { 44 + { 45 + type = "tilelayer", 46 + id = 1, 47 + name = "Collisions Layer", 48 + x = 0, 49 + y = 0, 50 + width = 120, 51 + height = 28, 52 + visible = true, 53 + opacity = 1, 54 + offsetx = 0, 55 + offsety = 0, 56 + properties = {collidable = true}, 57 + encoding = "lua", 58 + data = { 59 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 62 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66 + 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 67 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71 + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 72 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 77 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80 + 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81 + 1, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 82 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 83 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 86 + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 87 + } 88 + }, 89 + { 90 + type = "tilelayer", 91 + id = 2, 92 + name = "Main Layer", 93 + x = 0, 94 + y = 0, 95 + width = 120, 96 + height = 28, 97 + visible = true, 98 + opacity = 1, 99 + offsetx = 0, 100 + offsety = 0, 101 + properties = {}, 102 + encoding = "lua", 103 + data = { 104 + 0, 26, 76, 77, 78, 29, 29, 76, 77, 78, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 39, 40, 41, 37, 159, 160, 161, 37, 279, 280, 281, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 76, 77, 78, 245, 29, 76, 77, 78, 80, 105 + 0, 50, 100, 101, 102, 29, 29, 100, 101, 102, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 303, 304, 305, 61, 63, 64, 65, 67, 68, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 100, 101, 102, 269, 29, 100, 101, 102, 56, 106 + 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 207, 208, 209, 85, 327, 328, 329, 61, 87, 88, 89, 91, 92, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 107 + 0, 26, 28, 29, 30, 245, 163, 28, 29, 30, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 111, 112, 113, 109, 231, 232, 233, 85, 351, 352, 353, 61, 111, 112, 113, 115, 116, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 225, 224, 224, 224, 0, 0, 0, 224, 224, 224, 225, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 28, 29, 30, 228, 229, 28, 29, 30, 32, 108 + 0, 50, 52, 53, 54, 269, 187, 52, 53, 54, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 52, 53, 54, 252, 253, 52, 53, 54, 32, 109 + 0, 74, 76, 77, 78, 245, 29, 76, 77, 78, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 39, 40, 41, 37, 159, 160, 161, 37, 39, 40, 41, 37, 279, 280, 281, 43, 44, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 29, 29, 29, 29, 165, 147, 29, 228, 229, 43, 44, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 76, 77, 78, 167, 29, 76, 77, 78, 56, 110 + 0, 26, 100, 101, 102, 269, 29, 100, 101, 102, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 63, 64, 65, 61, 303, 304, 305, 67, 68, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 189, 189, 29, 29, 29, 147, 29, 252, 253, 67, 68, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 74, 100, 101, 102, 191, 29, 100, 101, 102, 80, 111 + 0, 50, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 87, 88, 89, 109, 207, 208, 209, 85, 87, 88, 89, 61, 327, 328, 329, 91, 92, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 165, 189, 29, 29, 29, 147, 29, 29, 29, 91, 92, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 273, 272, 272, 272, 272, 272, 272, 272, 272, 272, 112 + 0, 74, 28, 29, 30, 228, 229, 28, 29, 30, 32, 0, 0, 0, 0, 0, 153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 111, 112, 113, 85, 231, 232, 233, 85, 111, 112, 113, 61, 351, 352, 353, 115, 116, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 29, 29, 29, 29, 29, 147, 29, 29, 29, 115, 116, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 50, 28, 29, 30, 167, 245, 28, 29, 30, 56, 113 + 0, 26, 52, 53, 54, 252, 253, 52, 53, 54, 32, 0, 0, 0, 0, 0, 177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 147, 272, 272, 272, 272, 272, 274, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 83, 155, 156, 157, 29, 29, 147, 29, 189, 165, 67, 68, 0, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 50, 52, 53, 54, 191, 245, 52, 53, 54, 56, 114 + 0, 50, 76, 77, 78, 29, 29, 76, 77, 78, 56, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 35, 279, 280, 281, 37, 159, 160, 161, 37, 39, 40, 41, 147, 159, 160, 161, 43, 44, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 107, 179, 180, 181, 29, 29, 147, 29, 189, 189, 91, 92, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 26, 76, 77, 78, 167, 245, 76, 77, 78, 32, 115 + 0, 74, 100, 101, 102, 29, 29, 100, 101, 102, 80, 0, 0, 0, 224, 224, 225, 224, 224, 224, 224, 224, 224, 224, 0, 0, 0, 0, 58, 59, 303, 304, 305, 61, 183, 184, 185, 61, 63, 64, 65, 147, 183, 184, 185, 67, 68, 0, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 0, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 147, 272, 272, 272, 272, 272, 274, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 50, 100, 101, 102, 191, 269, 100, 101, 102, 56, 116 + 271, 273, 272, 272, 272, 147, 272, 272, 272, 272, 272, 274, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 82, 83, 327, 328, 329, 85, 207, 208, 209, 85, 87, 88, 89, 147, 207, 208, 209, 91, 92, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 0, 0, 34, 35, 228, 229, 29, 37, 29, 228, 229, 37, 228, 229, 29, 147, 167, 29, 29, 43, 44, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 117 + 0, 50, 28, 29, 30, 147, 245, 28, 29, 30, 56, 0, 0, 0, 26, 228, 229, 245, 149, 29, 163, 228, 229, 32, 0, 0, 0, 0, 106, 107, 351, 352, 353, 85, 231, 232, 233, 85, 111, 112, 113, 147, 231, 232, 233, 115, 116, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 0, 0, 58, 59, 252, 253, 29, 61, 29, 252, 253, 61, 252, 253, 29, 147, 191, 29, 29, 67, 68, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 26, 28, 29, 30, 245, 163, 28, 29, 30, 32, 118 + 0, 50, 52, 53, 54, 147, 245, 52, 53, 54, 56, 0, 0, 0, 50, 252, 253, 245, 173, 29, 187, 252, 253, 56, 0, 0, 0, 271, 272, 272, 272, 272, 272, 61, 46, 46, 46, 46, 46, 46, 46, 147, 272, 272, 272, 272, 272, 274, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 0, 0, 82, 83, 165, 165, 29, 109, 29, 189, 189, 61, 189, 189, 29, 147, 167, 29, 189, 91, 92, 0, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 0, 224, 224, 225, 224, 224, 0, 0, 0, 50, 52, 53, 54, 269, 187, 52, 53, 54, 56, 119 + 0, 26, 76, 77, 78, 147, 245, 76, 77, 78, 32, 0, 0, 0, 74, 29, 29, 245, 173, 29, 29, 29, 167, 80, 0, 0, 0, 0, 34, 35, 39, 40, 41, 61, 228, 229, 29, 29, 29, 228, 229, 147, 39, 40, 41, 43, 44, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 0, 0, 106, 107, 189, 189, 29, 61, 29, 189, 165, 61, 189, 189, 29, 147, 191, 29, 29, 115, 116, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 271, 272, 272, 272, 272, 272, 274, 0, 0, 74, 76, 77, 78, 245, 29, 76, 77, 78, 80, 120 + 0, 50, 100, 101, 102, 147, 269, 100, 101, 102, 56, 0, 0, 0, 26, 163, 29, 245, 173, 29, 29, 29, 191, 32, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 252, 253, 29, 29, 29, 252, 253, 147, 63, 64, 65, 67, 68, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 0, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 147, 46, 46, 46, 46, 46, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 0, 26, 245, 167, 29, 56, 0, 0, 0, 26, 100, 101, 102, 269, 29, 100, 101, 102, 56, 121 + 0, 46, 46, 46, 46, 147, 46, 46, 46, 46, 46, 0, 0, 0, 50, 187, 29, 245, 173, 155, 156, 157, 29, 56, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 165, 29, 155, 156, 157, 29, 29, 147, 87, 88, 89, 91, 92, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 0, 0, 34, 35, 279, 280, 281, 37, 159, 160, 161, 37, 39, 40, 41, 147, 159, 160, 161, 43, 44, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 0, 50, 245, 191, 29, 32, 0, 0, 0, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 122 + 0, 26, 28, 29, 30, 147, 29, 28, 29, 30, 32, 0, 0, 0, 74, 29, 189, 189, 173, 179, 180, 181, 29, 80, 0, 0, 0, 0, 106, 107, 111, 112, 113, 109, 189, 29, 179, 180, 181, 29, 29, 171, 111, 112, 113, 115, 116, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 0, 0, 58, 59, 303, 304, 305, 61, 183, 184, 185, 61, 63, 64, 65, 147, 183, 184, 185, 67, 68, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 0, 74, 245, 167, 29, 56, 0, 0, 0, 74, 28, 29, 30, 228, 229, 28, 29, 30, 32, 123 + 0, 50, 52, 53, 54, 147, 29, 52, 53, 54, 56, 0, 0, 0, 26, 29, 163, 163, 173, 228, 229, 167, 29, 32, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 0, 0, 82, 83, 327, 328, 329, 85, 207, 208, 209, 85, 87, 88, 89, 147, 207, 208, 209, 91, 92, 0, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 0, 26, 245, 191, 29, 56, 0, 0, 0, 26, 52, 53, 54, 252, 253, 52, 53, 54, 32, 124 + 0, 74, 76, 77, 78, 147, 163, 76, 77, 78, 80, 0, 0, 0, 50, 29, 187, 187, 197, 252, 253, 191, 29, 56, 0, 0, 0, 0, 34, 35, 159, 160, 161, 37, 159, 160, 161, 37, 279, 280, 281, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 0, 0, 106, 107, 351, 352, 353, 85, 231, 232, 233, 85, 111, 112, 113, 171, 231, 232, 233, 115, 116, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 0, 50, 245, 167, 29, 56, 0, 0, 0, 50, 76, 77, 78, 29, 167, 76, 77, 78, 56, 125 + 0, 26, 100, 101, 102, 171, 187, 100, 101, 102, 56, 0, 0, 0, 74, 29, 245, 245, 29, 173, 29, 167, 29, 80, 0, 0, 0, 0, 58, 59, 183, 184, 185, 61, 183, 184, 185, 61, 303, 304, 305, 109, 63, 64, 65, 67, 68, 0, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 0, 74, 245, 191, 29, 80, 0, 0, 0, 74, 100, 101, 102, 29, 191, 100, 101, 102, 80, 126 + 0, 50, 272, 272, 272, 272, 272, 272, 272, 147, 272, 274, 0, 0, 26, 29, 245, 245, 29, 173, 29, 191, 29, 32, 0, 0, 0, 0, 82, 83, 207, 208, 209, 85, 207, 208, 209, 85, 327, 328, 329, 61, 87, 88, 89, 91, 92, 0, 0, 0, 0, 26, 29, 167, 29, 56, 0, 0, 0, 0, 0, 34, 35, 39, 40, 41, 61, 159, 160, 161, 61, 159, 160, 161, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 26, 29, 167, 29, 56, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 0, 26, 228, 229, 29, 56, 0, 0, 0, 273, 272, 272, 272, 272, 272, 272, 272, 272, 272, 127 + 0, 74, 28, 29, 30, 228, 229, 28, 29, 147, 32, 0, 0, 0, 50, 29, 245, 245, 29, 173, 189, 189, 189, 56, 0, 0, 0, 0, 106, 107, 231, 232, 233, 85, 231, 232, 233, 85, 351, 352, 353, 61, 111, 112, 113, 115, 116, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 183, 184, 185, 61, 63, 64, 65, 67, 68, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 0, 50, 252, 253, 29, 56, 0, 0, 0, 50, 28, 29, 30, 29, 245, 28, 29, 30, 56, 128 + 0, 26, 52, 53, 54, 252, 253, 52, 53, 147, 32, 0, 0, 0, 74, 29, 245, 245, 29, 173, 189, 189, 189, 80, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 74, 29, 167, 29, 56, 0, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 207, 208, 209, 61, 207, 208, 209, 109, 87, 88, 89, 91, 92, 0, 0, 0, 0, 74, 29, 167, 29, 56, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 0, 74, 29, 167, 29, 80, 0, 0, 0, 50, 52, 53, 54, 29, 245, 52, 53, 54, 56, 129 + 0, 50, 76, 77, 78, 29, 29, 76, 77, 147, 56, 0, 0, 0, 26, 29, 197, 245, 29, 173, 29, 29, 29, 32, 0, 0, 0, 0, 34, 35, 39, 40, 41, 37, 159, 160, 161, 37, 39, 40, 41, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 0, 106, 107, 111, 112, 113, 109, 231, 232, 233, 61, 231, 232, 233, 61, 111, 112, 113, 115, 116, 0, 0, 0, 0, 50, 29, 191, 29, 56, 0, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 0, 26, 29, 191, 29, 56, 0, 0, 0, 26, 76, 77, 78, 29, 245, 76, 77, 78, 32, 130 + 0, 74, 100, 101, 102, 29, 29, 100, 101, 171, 80, 0, 0, 0, 50, 155, 156, 157, 29, 173, 29, 228, 229, 56, 0, 0, 0, 0, 58, 59, 63, 64, 65, 61, 183, 184, 185, 61, 63, 64, 65, 61, 63, 64, 65, 67, 68, 0, 0, 0, 0, 50, 155, 156, 157, 80, 0, 0, 0, 0, 271, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272, 274, 0, 0, 0, 50, 155, 156, 157, 80, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 0, 50, 29, 167, 29, 56, 0, 0, 0, 50, 100, 101, 102, 29, 269, 100, 101, 102, 56, 131 + 271, 273, 273, 273, 273, 273, 272, 272, 272, 272, 272, 274, 0, 0, 74, 179, 180, 181, 29, 173, 29, 252, 253, 80, 0, 0, 0, 0, 82, 83, 87, 88, 89, 85, 207, 208, 209, 85, 87, 88, 89, 61, 87, 88, 89, 91, 92, 0, 0, 0, 0, 50, 179, 180, 181, 56, 0, 0, 0, 0, 0, 34, 35, 159, 160, 161, 37, 159, 160, 161, 37, 279, 280, 281, 37, 39, 40, 41, 43, 44, 0, 0, 0, 0, 50, 179, 180, 181, 56, 0, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 0, 74, 29, 191, 29, 56, 0, 0, 0, 272, 272, 272, 272, 272, 272, 272, 272, 272, 272 132 + } 133 + }, 134 + { 135 + type = "objectgroup", 136 + id = 3, 137 + name = "Object Layer", 138 + visible = true, 139 + opacity = 1, 140 + offsetx = 0, 141 + offsety = 0, 142 + draworder = "topdown", 143 + properties = {}, 144 + objects = { 145 + { 146 + id = 12, 147 + name = "", 148 + type = "", 149 + shape = "rectangle", 150 + x = 294.667, 151 + y = 382.333, 152 + width = 16, 153 + height = 16, 154 + rotation = 0, 155 + gid = 5, 156 + visible = false, 157 + properties = {} 158 + }, 159 + { 160 + id = 13, 161 + name = "", 162 + type = "", 163 + shape = "rectangle", 164 + x = 240.333, 165 + y = 300, 166 + width = 16, 167 + height = 16, 168 + rotation = 0, 169 + gid = 6, 170 + visible = false, 171 + properties = {} 172 + }, 173 + { 174 + id = 14, 175 + name = "", 176 + type = "", 177 + shape = "rectangle", 178 + x = 19.6667, 179 + y = 142.667, 180 + width = 16, 181 + height = 16, 182 + rotation = 0, 183 + gid = 7, 184 + visible = false, 185 + properties = {} 186 + }, 187 + { 188 + id = 17, 189 + name = "", 190 + type = "", 191 + shape = "rectangle", 192 + x = 166, 193 + y = 415.667, 194 + width = 16, 195 + height = 16, 196 + rotation = 0, 197 + gid = 8, 198 + visible = false, 199 + properties = {} 200 + }, 201 + { 202 + id = 19, 203 + name = "", 204 + type = "", 205 + shape = "rectangle", 206 + x = 161, 207 + y = 247.333, 208 + width = 16, 209 + height = 16, 210 + rotation = 0, 211 + gid = 9, 212 + visible = false, 213 + properties = {} 214 + }, 215 + { 216 + id = 21, 217 + name = "", 218 + type = "", 219 + shape = "rectangle", 220 + x = 32.6667, 221 + y = 417.667, 222 + width = 16, 223 + height = 16, 224 + rotation = 0, 225 + gid = 11, 226 + visible = false, 227 + properties = {} 228 + }, 229 + { 230 + id = 22, 231 + name = "", 232 + type = "", 233 + shape = "rectangle", 234 + x = 30.3333, 235 + y = 285, 236 + width = 16, 237 + height = 16, 238 + rotation = 0, 239 + gid = 11, 240 + visible = false, 241 + properties = {} 242 + }, 243 + { 244 + id = 23, 245 + name = "", 246 + type = "", 247 + shape = "rectangle", 248 + x = 248.333, 249 + y = 425, 250 + width = 16, 251 + height = 16, 252 + rotation = 0, 253 + gid = 10, 254 + visible = false, 255 + properties = {} 256 + }, 257 + { 258 + id = 29, 259 + name = "", 260 + type = "", 261 + shape = "rectangle", 262 + x = 737.333, 263 + y = 299.333, 264 + width = 16, 265 + height = 16, 266 + rotation = 0, 267 + gid = 6, 268 + visible = false, 269 + properties = {} 270 + }, 271 + { 272 + id = 30, 273 + name = "", 274 + type = "", 275 + shape = "rectangle", 276 + x = 442, 277 + y = 44.6667, 278 + width = 16, 279 + height = 16, 280 + rotation = 0, 281 + gid = 5, 282 + visible = false, 283 + properties = {} 284 + }, 285 + { 286 + id = 31, 287 + name = "", 288 + type = "", 289 + shape = "rectangle", 290 + x = 811.333, 291 + y = 203.333, 292 + width = 16, 293 + height = 16, 294 + rotation = 0, 295 + gid = 9, 296 + visible = false, 297 + properties = {} 298 + }, 299 + { 300 + id = 32, 301 + name = "", 302 + type = "", 303 + shape = "rectangle", 304 + x = 730, 305 + y = 174, 306 + width = 16, 307 + height = 16, 308 + rotation = 0, 309 + gid = 10, 310 + visible = false, 311 + properties = {} 312 + }, 313 + { 314 + id = 33, 315 + name = "", 316 + type = "", 317 + shape = "rectangle", 318 + x = 1264, 319 + y = 313.333, 320 + width = 16, 321 + height = 16, 322 + rotation = 0, 323 + gid = 5, 324 + visible = false, 325 + properties = {} 326 + }, 327 + { 328 + id = 34, 329 + name = "", 330 + type = "", 331 + shape = "rectangle", 332 + x = 1480.67, 333 + y = 291.333, 334 + width = 16, 335 + height = 16, 336 + rotation = 0, 337 + gid = 5, 338 + visible = false, 339 + properties = {} 340 + }, 341 + { 342 + id = 35, 343 + name = "", 344 + type = "", 345 + shape = "rectangle", 346 + x = 1062.67, 347 + y = 90.6667, 348 + width = 16, 349 + height = 16, 350 + rotation = 0, 351 + gid = 6, 352 + visible = false, 353 + properties = {} 354 + }, 355 + { 356 + id = 36, 357 + name = "", 358 + type = "", 359 + shape = "rectangle", 360 + x = 1646, 361 + y = 268, 362 + width = 16, 363 + height = 16, 364 + rotation = 0, 365 + gid = 10, 366 + visible = false, 367 + properties = {} 368 + }, 369 + { 370 + id = 37, 371 + name = "", 372 + type = "", 373 + shape = "rectangle", 374 + x = 1039.33, 375 + y = 264, 376 + width = 16, 377 + height = 16, 378 + rotation = 0, 379 + gid = 11, 380 + visible = false, 381 + properties = {} 382 + }, 383 + { 384 + id = 38, 385 + name = "", 386 + type = "", 387 + shape = "rectangle", 388 + x = 1370, 389 + y = 222.667, 390 + width = 16, 391 + height = 16, 392 + rotation = 0, 393 + gid = 11, 394 + visible = false, 395 + properties = {} 396 + }, 397 + { 398 + id = 39, 399 + name = "", 400 + type = "", 401 + shape = "rectangle", 402 + x = 1129.33, 403 + y = 264, 404 + width = 16, 405 + height = 16, 406 + rotation = 0, 407 + gid = 11, 408 + visible = false, 409 + properties = {} 410 + }, 411 + { 412 + id = 40, 413 + name = "", 414 + type = "", 415 + shape = "rectangle", 416 + x = 741.333, 417 + y = 390.667, 418 + width = 16, 419 + height = 16, 420 + rotation = 0, 421 + gid = 9, 422 + visible = false, 423 + properties = {} 424 + }, 425 + { 426 + id = 41, 427 + name = "", 428 + type = "", 429 + shape = "rectangle", 430 + x = 323.333, 431 + y = 298.667, 432 + width = 16, 433 + height = 16, 434 + rotation = 0, 435 + gid = 9, 436 + visible = false, 437 + properties = {} 438 + }, 439 + { 440 + id = 42, 441 + name = "", 442 + type = "", 443 + shape = "rectangle", 444 + x = 1798.67, 445 + y = 318, 446 + width = 16, 447 + height = 16, 448 + rotation = 0, 449 + gid = 11, 450 + visible = false, 451 + properties = {} 452 + }, 453 + { 454 + id = 43, 455 + name = "", 456 + type = "", 457 + shape = "rectangle", 458 + x = 1869.33, 459 + y = 104.667, 460 + width = 16, 461 + height = 16, 462 + rotation = 0, 463 + gid = 11, 464 + visible = false, 465 + properties = {} 466 + }, 467 + { 468 + id = 44, 469 + name = "", 470 + type = "", 471 + shape = "rectangle", 472 + x = 1908, 473 + y = 186.667, 474 + width = 16, 475 + height = 16, 476 + rotation = 0, 477 + gid = 9, 478 + visible = false, 479 + properties = {} 480 + } 481 + } 482 + } 483 + } 484 +}
Added src/worpt/mode-intro.fnl.
1 +(var counter 0) 2 +(var time 0) 3 + 4 +(local dev-mode? true) 5 +(local play-game :play) 6 +;;(local play-game :play-osms-part1) 7 + 8 +{:draw (fn draw [message] 9 + (love.graphics.print (: "This window should close in %0.2f seconds" 10 + :format (- 3 time)) 32 16)) 11 + :update (fn update [dt set-mode] 12 + (if (< counter 65535) 13 + (set counter (+ counter 1)) 14 + (set counter 0)) 15 + (set time (+ time dt)) 16 + (when (or (> time 3) dev-mode?) 17 + (set-mode play-game))) 18 + :keypressed (fn keypressed [key set-mode] 19 + (love.event.quit))}
Added src/worpt/play-osms-part1.fnl.
1 +;; This is a Fennel implementation of http://www.osmstudios.com/tutorials/love2d-platformer-tutorial-part-1-the-basics 2 + 3 +(local bump (require "lib.bump")) 4 +(local player-img (love.graphics.newImage "assets/osmstudios_character_block.png")) 5 + 6 +(local player { 7 + :x 16 8 + :y 16 9 + ;; The first set of values are for our rudimentary physics system 10 + :xVelocity 0 ;; current velocity on x, y axes 11 + :yVelocity 0 12 + :acc 100 ;; the acceleration of our player 13 + :maxSpeed 600 ;; the top speed 14 + :friction 20 ;; slow our player down - we could toggle this situationally to create icy or slick platforms 15 + :gravity 80 ;; we will accelerate towards the bottom 16 + 17 + ;; These are values applying specifically to jumping 18 + :isJumping false ;; are we in the process of jumping? 19 + :isGrounded false ;; are we on the ground? 20 + :hasReachedMax false ;; is this as high as we can go? 21 + :jumpAcc 500 ;; how fast do we accelerate towards the top 22 + :jumpMaxSpeed 9.5 ;; our speed limit while jumping 23 + 24 + ;; Here are some incidental storage areas 25 + :img player-img}) ;; store the sprite we'll be drawing 26 + 27 +(local world (bump.newWorld 16)) 28 +(local ground-0 {}) 29 +(local ground-1 {}) 30 + 31 +(: world :add player player.x player.y (: player.img :getWidth) (: player.img :getHeight)) 32 +;;(: world :add ground-0 20 360 640 16) ; blog version 33 +(: world :add ground-0 120 360 640 16) ; github version 34 +(: world :add ground-1 0 448 640 32) 35 + 36 +(set player.filter (fn [item other] 37 + (let [(x y w h) (: world :getRect other) 38 + (px py pw ph) (: world :getRect item) 39 + player-bottom (+ py ph) 40 + other-bottom (+ y h)] 41 + (if (<= player-bottom y) 42 + :slide)))) 43 + 44 +(fn update [dt set-mode] 45 + (let [goal-x (+ player.x player.xVelocity) 46 + goal-y (+ player.y player.yVelocity) 47 + (x y cols) (: world :move player goal-x goal-y player.filter)] 48 + (set player.x (lume.clamp x 5 1000)) 49 + (set player.y y) 50 + (each [i col (ipairs cols)] 51 + (if 52 + (> col.touch.y goal-y) 53 + (do 54 + (set player.hasReachedMax true) 55 + (set player.isGrounded false)) 56 + (< col.normal.y 0) 57 + (do 58 + (set player.hasReachedMax false) 59 + (set player.isGrounded true))))) 60 + 61 + ;; apply friction 62 + (let [df (- 1 (math.min 1 (* dt player.friction)))] 63 + (set player.xVelocity (* player.xVelocity df)) 64 + (set player.yVelocity (* player.yVelocity df))) 65 + 66 + ;; apply gravity 67 + (set player.yVelocity (+ player.yVelocity (* player.gravity dt))) 68 + 69 + ;; handle horizontal movement 70 + (if 71 + (and (love.keyboard.isDown "left" "a") (> player.xVelocity (- player.maxSpeed))) 72 + (set player.xVelocity (- player.xVelocity (* player.acc dt))) 73 + (and (love.keyboard.isDown "right" "d") (< player.xVelocity player.maxSpeed)) 74 + (set player.xVelocity (+ player.xVelocity (* player.acc dt)))) 75 + 76 + ;; jump up jump up and get down 77 + (if (love.keyboard.isDown "up" "w") 78 + (do 79 + (if 80 + (and (< (- player.yVelocity) player.jumpMaxSpeed) (not player.hasReachedMax)) 81 + (do 82 + ;; (log "{%s} Setting player velocity{%s} to {%s} using a={%s}" dt player.yVelocity (- player.yVelocity (* player.jumpAcc dt)) player.jumpAcc) 83 + (set player.yVelocity (- player.yVelocity (* player.jumpAcc dt)))) 84 + ;; (log "Approaching terminal velocity %s ----> %s" (math.abs player.yVelocity) player.jumpMaxSpeed) 85 + (> (math.abs player.yVelocity) player.jumpMaxSpeed) 86 + (set player.hasReachedMax true)) 87 + 88 + (set player.isGrounded false)))) 89 + 90 +(fn keypressed [key set-mode]) 91 + 92 +(fn draw [player world] 93 + (love.graphics.draw player.img player.x player.y) 94 + (let [(x0 y0 w0 h0) (: world :getRect ground-0) 95 + (x1 y1 w1 h1) (: world :getRect ground-1)] 96 + (love.graphics.rectangle "fill" x0 y0 w0 h0) 97 + (love.graphics.rectangle "fill" x1 y1 w1 h1))) 98 + 99 +{:draw (partial draw player world) 100 + :update update 101 + :keypressed keypressed}
Added src/worpt/play.fnl.
1 +;; This is a Fennel implementation of http://www.osmstudios.com/tutorials/love2d-platformer-tutorial-part-1-the-basics 2 +;; combined with the awesome art assets and the demo game from 3 +;; https://ansimuz.itch.io/warped-city 4 + 5 +;; TODO 6 +;; - [ ] swap animation directions for -x movement 7 +;; # the moonwalking is pretty cool though... 8 + 9 +(local debug? false) 10 + 11 +(local anim8 (require "lib.anim8")) 12 +(local bump (require "lib.bump")) 13 +(local sti (require "lib.sti")) 14 + 15 +(local game-width 384) 16 +(local game-height 224) 17 + 18 +(local idle-speed 0.001) 19 + 20 +(local bg-1 (love.graphics.newImage "assets/warped_city/environment/bg-1.png")) 21 +(local bg-2 (love.graphics.newImage "assets/warped_city/environment/bg-2.png")) 22 +(local bg-3 (love.graphics.newImage "assets/warped_city/environment/bg-3.png")) 23 + 24 +(local player-img (love.graphics.newImage "assets/osmstudios_character_block.png")) 25 +(local player { 26 + :x 16 27 + :y 330 28 + ;; The first set of values are for our rudimentary physics system 29 + :xVelocity 0 ;; current velocity on x, y axes 30 + :yVelocity 0 31 + :acc 10 ;; the acceleration of our player 32 + :maxSpeed 600 ;; the top speed 33 + :friction 20 ;; slow our player down - we could toggle this situationally to create icy or slick platforms 34 + :gravity 30 ;; we will accelerate towards the bottom 35 + 36 + ;; These are values applying specifically to jumping 37 + :isJumping false ;; are we in the process of jumping? 38 + :isGrounded false ;; are we on the ground? 39 + :hasReachedMax false ;; is this as high as we can go? 40 + :jumpAcc 40 ;; how fast do we accelerate towards the top 41 + :jumpMaxSpeed 9.5 ;; our speed limit while jumping 42 + 43 + ;; Here are some incidental storage areas 44 + :state :idle ;; player animation 45 + :img player-img}) ;; store the sprite we'll be drawing 46 + 47 +(local atlas-data (require "assets/warped_city/atlas/atlas")) 48 + 49 +(local atlas-tile (love.graphics.newImage "assets/warped_city/atlas/atlas.png")) 50 +;; apparently this helps for scaling 51 +(: atlas-tile :setFilter "nearest" "linear") 52 + 53 +;;> Created animations: ["enemy-explosion" "control-box" "climb" "shot" "back-jump" "banner-coke" "shot-hit" "banner-scroll" "banner-sushi" "monitor-face" "run" "drone" "banner-side" "walk" "turret" "banner-big" "run-shoot" "idle" "banner-neon" "jump"] 54 + 55 +(local gid->am { 56 + 5 :banner-coke 57 + 6 :banner-big 58 + 7 :banner-neon 59 + 8 :banner-scroll 60 + 9 :banner-side 61 + 10 :banner-sushi 62 + 11 :monitor-face}) 63 + 64 +(local am->gid {}) 65 +(each [gid am (pairs gid->am)] (tset am->gid am gid)) 66 + 67 +(local atlas-quads {}) 68 +(local atlas-imgs {}) 69 +(local atlas-ams {}) 70 +(local atlas-ams-imgs {}) 71 + 72 +;; process atlas definition 73 +(let [(tw th) (: atlas-tile :getDimensions)] 74 + (each [i frame (ipairs atlas-data.frames)] 75 + (let [name frame.filename 76 + x frame.frame.x 77 + y frame.frame.y 78 + w frame.frame.w 79 + h frame.frame.h 80 + quad (love.graphics.newQuad x y w h tw th)] 81 + (if 82 + ;; annimation images end in the frame number 83 + (string.match name "^(.*)(-%d+)$") 84 + (let [(group scount) (string.match name "^(.*)-(%d+)$") 85 + count (tonumber scount)] 86 + (when (not (. atlas-ams-imgs group)) (tset atlas-ams-imgs group [])) 87 + (table.insert (. atlas-ams-imgs group) quad) 88 + (assert (= (length (. atlas-ams-imgs group)) count))) 89 + ;; other images 90 + (string.match name "[a-z]$") 91 + (tset atlas-imgs name quad))))) 92 + 93 +;; create animations 94 +(each [name quads (pairs atlas-ams-imgs)] 95 + (let [duration (if (. am->gid name) 0.5 0.15) 96 + am (anim8.newAnimation quads duration)] 97 + (tset atlas-ams name am))) 98 + 99 +(log "Created animations: %s" (pps (lume.keys atlas-ams))) 100 + 101 +(local map (sti "map.lua" ["bump"])) ;; TODO: add lint wrapper 102 +(local world (bump.newWorld 16)) 103 + 104 +(: map :bump_init world) 105 +(: world :add player player.x player.y (: player.img :getWidth) (: player.img :getHeight)) 106 + 107 +;;(log "MAP PROPERTIES: %s" (pps (lume.keys map.layers))) 108 + 109 +(local entity-layer (: map :addCustomLayer "Entities" (+ 1 (length map.layers)))) 110 +(set entity-layer.entities {}) 111 +(fn entity-layer.update [self dt] 112 + (each [_i ent (ipairs self.entities)] 113 + (when ent.update (ent.update ent dt)))) 114 +(fn entity-layer.draw [self dt] 115 + (each [_i ent (ipairs self.entities)] 116 + (when ent.draw (ent.draw ent)))) 117 + 118 +(fn player.draw [self] 119 + (let [am (. atlas-ams player.state) 120 + x (+ player.x 0) y (+ player.y 14)] 121 + ;; TODO: switch to drawing with a spritebatch 122 + (: am :draw atlas-tile x y))) 123 + 124 +(local ground-0 {}) 125 +(local ground-1 {}) 126 + 127 +(fn draw-object [obj] 128 + (let [x obj.x y obj.y am obj.am] 129 + (: am :draw atlas-tile x y))) 130 + 131 +(fn update-object [obj dt] 132 + (let [am obj.am] 133 + (: am :update dt))) 134 + 135 +(each [_i obj (pairs map.objects)] 136 + (let [id obj.id 137 + gid obj.gid 138 + am-name (. gid->am gid) 139 + am (. atlas-ams am-name) 140 + o-x obj.x o-y obj.y o-w obj.width o-h obj.height 141 + (a-w a-h) (: am :getDimensions) 142 + x (+ o-x o-w) y (- o-y o-h a-h) 143 + am-obj {:x x :y y :am am :draw draw-object :update update-object}] 144 + ;;(log "Adding animation object %s-%s %s" id gid am-name) 145 + (table.insert entity-layer.entities am-obj))) 146 + 147 +;; Insert player last so it's rendered on top; who needs z-index'ing amirite? 148 +(table.insert entity-layer.entities player) 149 + 150 +(set player.filter (fn [item other] 151 + ;;(log "ITEM{%s} COLLIDED WITH OTHER{%s}" (pps item) (pps (lume.keys other.properties))) 152 + (let [(x y w h) (: world :getRect other) 153 + (px py pw ph) (: world :getRect item) 154 + player-bottom (+ py ph) 155 + other-bottom (+ y h)] 156 + (if (<= player-bottom y) 157 + :slide)))) 158 + 159 +(fn update [map world player dt set-mode] 160 + (let [goal-x (+ player.x player.xVelocity) 161 + goal-y (+ player.y player.yVelocity) 162 + (x y cols) (: world :move player goal-x goal-y player.filter) 163 + am (. atlas-ams player.state)] 164 + (: am :update dt) 165 + (: map :update dt) 166 + (set player.x (lume.clamp x 0 (* game-width 16))) 167 + (set player.y y) 168 + (each [i col (ipairs cols)] 169 + (if 170 + (> col.touch.y goal-y) 171 + (do 172 + (set player.hasReachedMax true) 173 + (set player.isGrounded false) 174 + (set player.state :jump)) 175 + (< col.normal.y 0) 176 + (do 177 + (set player.hasReachedMax false) 178 + (set player.isGrounded true) 179 + (set player.state :walk)))) ;; TODO: don't just default to :walk 180 + 181 + ;; apply friction 182 + (let [df (- 1 (math.min 1 (* dt player.friction)))] 183 + (set player.xVelocity (* player.xVelocity df)) 184 + (set player.yVelocity (* player.yVelocity df))) 185 + 186 + ;; apply gravity 187 + (set player.yVelocity (+ player.yVelocity (* player.gravity dt))) 188 + 189 + ;; handle horizontal movement 190 + (if 191 + (and (love.keyboard.isDown "left" "a") (> player.xVelocity (- player.maxSpeed))) 192 + (set player.xVelocity (- player.xVelocity (* player.acc dt))) 193 + (and (love.keyboard.isDown "right" "d") (< player.xVelocity player.maxSpeed)) 194 + (set player.xVelocity (+ player.xVelocity (* player.acc dt)))) 195 + 196 + (if 197 + (< (math.abs player.xVelocity) idle-speed) 198 + (when (not (= player.state :idle)) (set player.state :idle)) 199 + (match player.state 200 + :idle (set player.state :walk))) 201 + 202 + ;; jump up jump up and get down 203 + (if (love.keyboard.isDown "up" "w") 204 + (do 205 + (if 206 + (and (< (- player.yVelocity) player.jumpMaxSpeed) (not player.hasReachedMax)) 207 + (do 208 + (set player.yVelocity (- player.yVelocity (* player.jumpAcc dt)))) 209 + (>= (math.abs player.yVelocity) player.jumpMaxSpeed) 210 + (set player.hasReachedMax true)) 211 + 212 + (set player.isGrounded false) 213 + (set player.state :jump))))) 214 + 215 +(fn keypressed [key set-mode]) 216 + 217 +(fn draw [map world player] 218 + (let [w (love.graphics.getWidth) 219 + h (love.graphics.getHeight) 220 + iw (: bg-1 :getWidth) 221 + ih (: bg-1 :getHeight) 222 + sw (/ w iw) 223 + sh (/ h ih) 224 + tx (math.max 0 (math.floor (- player.x (/ w 4)))) 225 + ty (math.max 0 (math.floor (- player.y (/ h 4)))) 226 + tdx 1] 227 + (love.graphics.draw bg-1 0 0 0 sw sh) 228 + (love.graphics.draw bg-2 0 0 0 sw sh) 229 + (love.graphics.draw bg-3 0 0 0 sw sh) 230 + 231 + (: map :draw (- tx) (- ty)))) 232 + 233 +(fn draw-debug [] 234 + (when debug? 235 + (let [msg-fps (string.format "Current FPS: %s" (love.timer.getFPS)) 236 + msg-state (string.format "STATE: J?|G?|M? -- %s|%s|%s" player.isJumping player.isGrounded player.hasReachedMax) 237 + msg-xy (string.format "{X,Y}: {%.2f, %.2f} || {dx: %.2f, dy: %.2f}" player.x player.y player.xVelocity player.yVelocity)] 238 + (love.graphics.print msg-fps 10 10) 239 + (love.graphics.print msg-xy 10 30) 240 + (love.graphics.print msg-state 10 50)))) 241 + 242 +{:draw (partial draw map world player) 243 + :draw-debug (partial draw-debug map world player) 244 + :update (partial update map world player) 245 + :keypressed keypressed}
Added src/worpt/readme.md.
1 +# Chewbranca's Worpt 2 + 3 +Worpt is a prototype platformer using the awesome _Warped City_ art assets from https://ansimuz.itch.io/warped-city and the platforming physics from http://www.osmstudios.com/tutorials/love2d-platformer-tutorial-part-1-the-basics . Currently this is an engine capable of running the map.json file from the _Warped City_ demo game. The goal was for this to be the intro level and then to create new levels, and the progress is that intro level is playable and nearly complete. 4 + 5 +## This Project is Based On: 6 + 7 +### Project Structure 8 + 9 + - technomancy: https://gitlab.com/technomancy/exo-encounter-667 10 + - alexjgriffith: https://gitlab.com/alexjgriffith/min-love2d-fennel 11 + 12 +## Platforming Physics 13 + 14 + - OSM Studios Tutorial: http://www.osmstudios.com/tutorials/love2d-platformer-tutorial-part-1-the-basics 15 + 16 +## Game Art Assets, Map File, and Sample Game 17 + 18 + The wonderful game assets from: 19 + 20 + - Warped City: https://ansimuz.itch.io/warped-city 21 + 22 +## Original readme.{org,md} follows: 23 + 24 +----- 25 + 26 +# Minimal Fennel Love2D Setup 27 + 28 +In a lead up to the annual [Autumn Lisp Game Jam](https://itch.io/jam/autumn-lisp-game-jam-2018) I thought I'd look into Phil Hegelberg's approach to last Aprils Jam, using [love2d](https://love2d.org/) in concert with [fennel](https://fennel-lang.org/). Phil outlines his approach on his [blog](https://love2d.org/). 29 + 30 +The first step is to see how fennel works as a standalone execution environment, then slowly integrate some of the love2d API. 31 + 32 +This repo contains the minimal viable setup to get started with Phil Hegelberg's game design process, plus some additional libraries. 33 + 34 +``` 35 +PROJECT=project-name 36 +git clone https://gitlab.com/alexjgriffith/min-love2d-fennel.git $PROJECT 37 +cd $PROJECT 38 +rm -rf .git 39 +``` 40 + 41 +# Phil's Modal Callbacks (PMC) 42 +Phil Hegelberg's [exo-encounter-667](https://gitlab.com/technomancy/exo-encounter-667/) is structured using a modal callback system. Each game state has a mode and each mode has a series of specific callbacks. 43 + 44 +If you design your game as a series of states in a very simple state machine, for example *start-screen*, *play* and *end*, with unidirectional progression, you can easily separate the logic for each state into state/mode specific callbacks. As an example, in order to have state dependant rendering that differs between start-screen,play and end you could provide a *draw* callback for each of those states. Similarly if we need state dependent logic and keyboard input we could provide *update* and *keyboard* callbacks. As you iterate you can add and remove callbacks and states/modes as needed with very little friction. 45 + 46 + 47 +# Emacs Setup 48 +Once you install the latest version of [fennel-mode](https://gitlab.com/technomancy/fennel-mode), you can run 49 +`C-u M-x fennel-repl` followed by `love .` to launch a repl.
Added src/worpt/wrap.fnl.
1 +(local repl (require "lib.stdio")) 2 +(local canvas (let [(w h) (love.window.getMode)] 3 + (love.graphics.newCanvas w h))) 4 + 5 +(var scale 2) 6 + 7 +;; set the first mode 8 +(var mode (require "mode-intro")) 9 + 10 +(fn set-mode [mode-name ...] 11 + (set mode (require mode-name)) 12 + (when mode.activate 13 + (mode.activate ...))) 14 + 15 +(fn love.load [] 16 + (canvas:setFilter "nearest" "nearest") 17 + (repl.start)) 18 + 19 +(fn love.draw [] 20 + ;; the canvas allows you to get sharp pixel-art style scaling; if you 21 + ;; don't want that, just skip that and call mode.draw directly. 22 + (love.graphics.setCanvas canvas) 23 + (love.graphics.clear) 24 + (love.graphics.setColor 1 1 1) 25 + (mode.draw) 26 + (love.graphics.setCanvas) 27 + (love.graphics.setColor 1 1 1) 28 + (love.graphics.draw canvas 0 0 0 scale scale) 29 + (if mode.draw-debug (mode.draw-debug))) 30 + 31 +(fn love.update [dt] 32 + (mode.update dt set-mode)) 33 + 34 +(fn love.keypressed [key] 35 + (if (and (love.keyboard.isDown "lctrl" "rctrl" "capslock") (= key "q")) 36 + (love.event.quit) 37 + ;; add what each keypress should do in each mode 38 + (mode.keypressed key set-mode)))