jrpg-stellar-engine
Version:
Retro JRPG browser game engine.
988 lines (900 loc) • 51.1 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>JRPG Stellar Engine Demo</title>
<style type="text/css">
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
overflow: hidden;
background-color: #0ec4e1;
user-select: none;
}
#canvas {
display: block;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: -o-crisp-edges;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor;
}
#touchpad {
display: none;
position: absolute;
width: 80px;
height: 80px;
left: calc(50% - 40px);
top: calc(50% - 40px);
border-radius: 50%;
background-color: rgba(255,255,255,0.5);
border: 4px solid rgba(255,255,255,0.5);
}
#fps {
position: absolute;
bottom: 10px;
left: 10px;
color: white;
font-size: 14px;
}
</style>
</head>
<body>
<!-- Scripts -->
<script src="../src/cache.js"></script>
<script src="../src/spawnpoint.js"></script>
<script src="../src/respawnpoint.js"></script>
<script src="../src/stairs.js"></script>
<script src="../src/sprite.js"></script>
<script src="../src/animsprite.js"></script>
<script src="../src/tileset.js"></script>
<script src="../src/actor.js"></script>
<script src="../src/mob.js"></script>
<script src="../src/level.js"></script>
<script src="../src/loader-acx.js"></script>
<script src="../src/loader-tsx.js"></script>
<script src="../src/loader-tmx.js"></script>
<script src="../src/utils.js"></script>
<script src="../src/view.js"></script>
<!-- Resources -->
<div style="display: none;">
<img id="char1" src="../public/sprites/characters/chara_0.png">
<img id="mob1" src="../public/sprites/characters/pinguin_22x22.png">
<img id="blob1" src="../public/sprites/characters/blob_green.png">
<img id="jinn" src="../public/sprites/characters/jinn.png">
<img id="env1" src="../public/sprites/environment/ground.png">
<img id="env2" src="../public/sprites/environment/water.png">
<img id="props" src="../public/sprites/environment/props.png">
<img id="sky" src="../public/sprites/environment/sky.png">
<img id="chest1" src="../public/sprites/items/chest.png">
</div>
<!-- TSX -->
<script id="test-tsx" type="text/xml">
<tileset version="1.10" tiledversion="1.10.2" name="test" tilewidth="16" tileheight="16" tilecount="87" columns="29">
<image source="../public/sprites/environment/ground.png" width="464" height="48"/>
</tileset>
</script>
<script id="water-tsx" type="text/xml">
<tileset version="1.10" tiledversion="1.10.2" name="water" tilewidth="16" tileheight="16" tilecount="9" columns="3">
<image source="../public/sprites/environment/water.png" width="48" height="48"/>
</tileset>
</script>
<script id="props-tsx" type="text/xml">
<tileset version="1.10" tiledversion="1.10.2" name="props" tilewidth="16" tileheight="16" tilecount="4" columns="4">
<image source="../public/sprites/environment/props.png" width="64" height="16"/>
<tile id="0">
<animation>
<frame tileid="0" duration="100"/>
<frame tileid="1" duration="100"/>
<frame tileid="2" duration="100"/>
<frame tileid="3" duration="100"/>
</animation>
</tile>
</tileset>
</script>
<!-- TMX -->
<script id="test-tmx" type="text/xml">
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="30" height="30" tilewidth="16" tileheight="16" infinite="0" nextlayerid="14" nextobjectid="13">
<tileset firstgid="1" source="test.tsx"/>
<tileset firstgid="88" source="water.tsx"/>
<tileset firstgid="97" source="props.tsx"/>
<imagelayer id="9" name="Sky" locked="1" parallaxx="0" parallaxy="0" repeatx="1" repeaty="1">
<image source="../public/sprites/environment/sky.png" width="256" height="256"/>
</imagelayer>
<imagelayer id="11" name="Sun" locked="1" offsetx="0" offsety="-120">
<image source="../public/sprites/environment/sun.png" width="256" height="256"/>
</imagelayer>
<imagelayer id="10" name="Clouds" locked="1" parallaxx="0.5" parallaxy="0.2" repeatx="1" repeaty="1">
<image source="../public/sprites/environment/clouds.png" width="256" height="256"/>
</imagelayer>
<layer id="8" name="WaterSW" width="30" height="30" locked="1" offsetx="-240" offsety="240">
<data encoding="csv">
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="7" name="WaterSE" width="30" height="30" locked="1" offsetx="240" offsety="240">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="6" name="WaterNE" width="30" height="30" locked="1" offsetx="240" offsety="-240">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,34,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,34,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,34,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0
</data>
</layer>
<layer id="5" name="WaterNW" width="30" height="30" locked="1" offsetx="-240" offsety="-240">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,34,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,34,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,34,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,88,89,90,88,89,90,88,89,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,91,92,93,91,92,93,91,92,93,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,94,95,96,94,95,96,94,95,96,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="1" name="Ground" width="30" height="30" locked="1">
<data encoding="csv">
34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,
34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,
46,34,7,8,8,8,9,34,46,34,34,34,34,34,34,34,34,34,34,34,34,34,34,7,8,8,8,9,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,34,34,34,34,34,46,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,46,34,34,34,34,45,34,34,34,34,47,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,45,34,34,34,34,47,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,45,34,34,34,34,47,34,34,34,34,46,36,37,37,37,38,34,46,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,78,34,34,34,34,34,46,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,1,2,2,2,2,2,2,2,2,2,2,3,34,34,36,37,37,37,38,34,34,
34,34,65,66,66,66,67,34,34,30,31,31,31,31,31,31,31,31,31,31,32,34,34,65,66,66,66,67,34,34,
34,34,34,34,34,34,34,34,34,30,31,31,31,31,31,31,31,31,31,31,32,34,34,34,34,34,34,34,34,34,
34,34,34,34,34,34,34,34,34,30,31,31,4,5,5,5,5,6,31,31,32,34,34,34,34,34,34,34,46,34,
34,34,34,34,34,34,34,34,46,30,31,31,33,34,34,34,34,35,31,31,32,34,34,34,46,34,34,34,34,34,
34,34,34,34,34,34,34,34,34,30,31,31,33,34,34,34,34,35,31,31,32,34,34,34,34,34,34,34,34,34,
34,34,34,34,34,46,34,34,34,30,31,31,33,34,34,34,34,35,31,31,32,34,34,34,34,34,34,34,34,34,
34,34,34,46,34,34,34,34,34,30,31,31,33,34,34,34,34,35,31,31,32,34,34,34,46,34,34,34,34,34,
34,34,34,34,34,34,34,34,34,30,31,31,62,63,63,63,63,64,31,31,32,34,34,34,34,34,34,34,34,34,
34,34,34,34,34,34,34,34,34,30,31,31,31,31,31,31,31,31,31,31,32,34,34,34,34,34,34,34,34,34,
34,34,7,8,8,8,9,34,34,30,31,31,31,31,31,31,31,31,31,31,32,46,34,7,8,8,8,9,34,34,
34,34,36,37,37,37,38,34,34,59,60,60,60,60,60,60,60,60,60,60,61,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,34,34,34,34,46,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,46,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,36,37,37,37,38,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,37,37,37,38,34,34,
34,34,65,66,66,66,67,34,46,34,34,34,34,34,34,34,34,34,46,46,34,34,46,65,66,66,66,67,34,34,
34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,
34,34,46,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34
</data>
</layer>
<layer id="2" name="Colliders" class="Colliders" width="30" height="30" locked="1">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,19,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,20,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,16,17,17,17,17,18,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,34,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,68,69,70,0,0,0,0,0,0,74,75,75,75,0,76,0,0,0,0,0,0,68,69,70,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,48,17,17,17,17,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,17,17,17,17,49,0,
0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,
0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,
0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,
0,0,71,72,73,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,71,72,73,0,0,
0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,
0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,0,0,0,0,0,
0,19,75,75,77,75,76,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,74,75,77,75,75,20,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,68,69,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,68,69,70,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,0,
0,48,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,49,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="4" name="Objects" locked="1">
<object id="2" name="player" type="Spawn" x="168" y="240">
<point/>
</object>
<object id="3" name="mob:penguin-acx" type="Spawn" x="312" y="240">
<point/>
</object>
<object id="4" name="Slope-E" type="Stairs" x="272.25" y="48.375">
<polygon points="0,0 15.625,15.5 15.75,79.25 -0.25,63.75"/>
</object>
<object id="5" name="Slope-W" type="Stairs" x="207.875" y="47.875">
<polygon points="0,0 -15.75,16 -15.75,79.75 0.125,64.125"/>
</object>
<object id="6" name="cave-tmx:East" type="Portal" x="400" y="294" width="16" height="12"/>
<object id="7" name="cave-tmx:West" type="Portal" x="64" y="294" width="16" height="12"/>
<object id="8" name="Out-W" type="Spawn" x="72" y="320">
<point/>
</object>
<object id="9" name="Out-E" type="Spawn" x="408" y="320">
<point/>
</object>
<object id="10" name="item:chest-acx" type="Spawn" x="232" y="90">
<point/>
</object>
</objectgroup>
<layer id="3" name="Cover" width="30" height="30" locked="1">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,10,11,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,11,12,0,0,0,
0,0,0,39,40,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,40,41,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,14,15,0,0,
0,0,42,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,42,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,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,10,11,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,11,12,0,0,0,
0,0,0,39,40,41,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,40,41,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="12" name=".Hidden" width="30" height="30" locked="1" offsetx="0" offsety="-240">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,34,0,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,34,34,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,34,0,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<objectgroup id="13" name="Air">
<object id="12" name="mob:jinn-acx" type="Spawn" x="128" y="96">
<point/>
</object>
</objectgroup>
</map>
</script>
<!-- TMX -->
<script id="cave-tmx" type="text/xml">
<map version="1.10" tiledversion="1.10.2" orientation="orthogonal" renderorder="right-down" width="17" height="14" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="8">
<tileset firstgid="1" source="props.tsx"/>
<tileset firstgid="5" source="test.tsx"/>
<imagelayer id="5" name="Background" locked="1" offsetx="-512" offsety="-512" repeatx="1" repeaty="1">
<image source="../public/sprites/environment/soil.png" width="16" height="16"/>
</imagelayer>
<layer id="1" name="Ground" width="17" height="14" locked="1">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,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,83,27,83,56,56,87,57,83,87,28,83,83,83,83,0,
0,83,27,54,54,87,83,86,83,83,28,83,83,83,54,25,0,
0,83,83,58,27,54,54,54,83,83,83,25,25,83,54,25,0,
0,83,25,83,83,83,83,83,83,83,83,83,83,83,83,25,0,
83,83,83,83,83,83,83,25,83,83,83,83,83,83,83,83,83,
83,83,83,83,83,83,54,83,83,83,83,83,83,83,54,83,83,
0,83,54,54,83,83,83,83,83,25,83,83,25,25,54,83,0,
0,25,25,54,83,83,83,54,83,83,83,83,83,83,83,83,0,
0,25,83,27,54,83,83,83,83,83,83,27,87,83,25,25,0,
0,83,27,27,58,83,83,83,83,83,27,56,56,86,56,54,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="2" name="Colliders" class="Colliders" width="17" height="14" locked="1">
<data encoding="csv">
61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,
31,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,30,
31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,
31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,
60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,
89,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,
0,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,
30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,
30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,
30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,
30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,
59,61,61,61,61,61,61,61,61,61,61,61,61,61,61,61,60,
88,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,89
</data>
</layer>
<objectgroup id="4" name="Objects" locked="1">
<object id="1" name="East" type="Spawn" x="240" y="112">
<point/>
</object>
<object id="2" name="West" type="Spawn" x="32" y="112">
<point/>
</object>
<object id="3" name="test-tmx:Out-E" type="Portal" x="256" y="92" width="16" height="32"/>
<object id="4" name="test-tmx:Out-W" type="Portal" x="0" y="93" width="16" height="32"/>
<object id="7" name="mob:blob-acx" type="Spawn" x="112" y="80">
<point/>
</object>
</objectgroup>
<layer id="3" name="Cover" width="17" height="14" locked="1">
<data encoding="csv">
33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,33,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
</map>
</script>
<!-- ACX -->
<script id="player-acx" type="text/xml">
<actor version="0.2" name="player" type="Actor" resource="#char1" width="48" height="80" cols="3" rows="4">
<movement speed="80" />
<collider x="4" y="16" width="8" height="4"/>
<animation name="idleUp">
<frame tileid="10" duration="100"/>
</animation>
<animation name="idleDown">
<frame tileid="1" duration="100"/>
</animation>
<animation name="idleLeft">
<frame tileid="4" duration="100"/>
</animation>
<animation name="idleRight">
<frame tileid="7" duration="100"/>
</animation>
<animation name="moveUp">
<frame tileid="9" duration="100"/>
<frame tileid="10" duration="100"/>
<frame tileid="11" duration="100"/>
</animation>
<animation name="moveDown">
<frame tileid="0" duration="100"/>
<frame tileid="1" duration="100"/>
<frame tileid="2" duration="100"/>
</animation>
<animation name="moveLeft">
<frame tileid="3" duration="100"/>
<frame tileid="4" duration="100"/>
<frame tileid="5" duration="100"/>
</animation>
<animation name="moveRight">
<frame tileid="6" duration="100"/>
<frame tileid="7" duration="100"/>
<frame tileid="8" duration="100"/>
</animation>
</actor>
</script>
<script id="penguin-acx" type="text/xml">
<actor version="0.2" name="penguin1" type="MOB" resource="#mob1" width="88" height="88" cols="4" rows="4">
<movement speed="20" />
<collider x="4" y="15" width="14" height="6"/>
<animation name="idle">
<frame tileid="1" duration="100"/>
</animation>
<animation name="moveUp">
<frame tileid="8" duration="100"/>
<frame tileid="9" duration="100"/>
<frame tileid="10" duration="100"/>
</animation>
<animation name="moveDown">
<frame tileid="0" duration="100"/>
<frame tileid="1" duration="100"/>
<frame tileid="2" duration="100"/>
</animation>
<animation name="moveLeft">
<frame tileid="12" duration="100"/>
<frame tileid="13" duration="100"/>
<frame tileid="14" duration="100"/>
</animation>
<animation name="moveRight">
<frame tileid="4" duration="100"/>
<frame tileid="5" duration="100"/>
<frame tileid="6" duration="100"/>
</animation>
</actor>
</script>
<script id="blob-acx" type="text/xml">
<actor version="0.2" name="blob1" type="MOB" resource="#blob1" width="57" height="57" cols="3" rows="3">
<movement speed="20" />
<collider x="0" y="0" width="19" height="19"/>
<animation name="idle">
<frame tileid="0" duration="100"/>
<frame tileid="1" duration="100"/>
<frame tileid="2" duration="100"/>
</animation>
<animation name="moveUp">
<frame tileid="0" duration="100"/>
<frame tileid="1" duration="100"/>
<frame tileid="2" duration="100"/>
</animation>
<animation name="moveDown">
<frame tileid="0" duration="100"/>
<frame tileid="1" duration="100"/>
<frame tileid="2" duration="100"/>
</animation>
<animation name="moveLeft">
<frame tileid="3" duration="100"/>
<frame tileid="4" duration="100"/>
<frame tileid="5" duration="100"/>
</animation>
<animation name="moveRight">
<frame tileid="6" duration="100"/>
<frame tileid="7" duration="100"/>
<frame tileid="8" duration="100"/>
</animation>
</actor>
</script>
<script id="jinn-acx" type="text/xml">
<actor version="0.2" name="jinn" type="MOB" resource="#jinn" width="144" height="192" cols="3" rows="4">
<movement speed="40" />
<collider x="12" y="32" width="24" height="16"/>
<animation name="idle">
<frame tileid="1" duration="100"/>
</animation>
<animation name="moveUp">
<frame tileid="9" duration="100"/>
<frame tileid="10" duration="100"/>
<frame tileid="11" duration="100"/>
</animation>
<animation name="moveDown">
<frame tileid="0" duration="100"/>
<frame tileid="1" duration="100"/>
<frame tileid="2" duration="100"/>
</animation>
<animation name="moveLeft">
<frame tileid="3" duration="100"/>
<frame tileid="4" duration="100"/>
<frame tileid="5" duration="100"/>
</animation>
<animation name="moveRight">
<frame tileid="6" duration="100"/>
<frame tileid="7" duration="100"/>
<frame tileid="8" duration="100"/>
</animation>
</actor>
</script>
<script id="chest-acx" type="text/xml">
<actor version="0.2" name="chest" type="Actor" resource="#chest1" width="32" height="16" cols="2" rows="1">
<collider x="-4" y="-4" width="24" height="24"/>
<animation name="open">
<frame tileid="1" duration="1000"/>
</animation>
<animation name="close">
<frame tileid="0" duration="1000"/>
</animation>
</actor>
</script>
<!-- Main canvas window -->
<canvas id="main"></canvas>
<!-- Mobile joystick -->
<div id="touchpad"></div>
<!-- Frames per second counter -->
<div id="fps">FPS:</div>
<!-- Init -->
<script type="text/javascript">
// Loop control
let pause = false;
// Rendering
let level = null;
let view = null;
const scale = 4;
// Loaders
let loaderTSX = null;
let loaderTMX = null;
let loaderACX = null;
// Player character
let player = null;
// Input actions
const input = {
up: false,
down: false,
left: false,
right: false,
action: false
};
// Mobile input
const mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const touchCenter = {x: 0, y: 0};
const touchPad = document.getElementById('touchpad');
const keyDownHandler = (event) => {
if (event.code == 'ArrowRight' || event.code == 'KeyD') {
input.right = true;
}
else if (event.code == 'ArrowLeft' || event.code == 'KeyA') {
input.left = true;
}
if (event.code == 'ArrowUp' || event.code == 'KeyW') {
input.up = true;
}
else if (event.code == 'ArrowDown' || event.code == 'KeyS') {
input.down = true;
}
if (event.code == 'Space' || event.code == 'ControlRight' || event.code == 'ControlLeft' || event.key == 'Enter') {
input.action = true;
}
};
const keyUpHandler = (event) => {
if (event.code == 'ArrowRight' || event.code == 'KeyD') {
input.right = false;
}
if (event.code == 'ArrowLeft' || event.code == 'KeyA') {
input.left = false;
}
if (event.code == 'ArrowUp' || event.code == 'KeyW') {
input.up = false;
}
if (event.code == 'ArrowDown' || event.code == 'KeyS') {
input.down = false;
}
if (event.code == 'Space' || event.code == 'ControlRight' || event.code == 'ControlLeft' || event.key == 'Enter') {
input.action = false;
}
player.idle();
};
const touchStartHandler = (event) => {
const firstTouch = event.touches[0];
touchPad.style.display = 'block';
touchCenter.x = firstTouch.pageX;
touchCenter.y = firstTouch.pageY;
touchPad.style.left = `${touchCenter.x - 44}px`;
touchPad.style.top = `${touchCenter.y - 44}px`;
};
const touchMoveHandler = (event) => {
event.preventDefault();
const firstTouch = event.touches[0];
const posX = firstTouch.pageX;
const posY = firstTouch.pageY;
touchPad.style.left = `${posX - 44}px`;
touchPad.style.top = `${posY - 44}px`;
input.right = false;
input.left = false;
input.up = false;
input.down = false;
const threshold = 10;
if (posX + threshold < touchCenter.x) {
input.left = true;
// touchCenter.y = posY;
}
else if (posX - threshold > touchCenter.x) {
input.right = true;
// touchCenter.y = posY;
}
if (posY + threshold < touchCenter.y) {
input.up = true;
// touchCenter.x = posX;
}
else if (posY - threshold > touchCenter.y) {
input.down = true;
// touchCenter.x = posX;
}
};
const touchEndHandler = (event) => {
touchPad.style.display = 'none';
input.right = false;
input.left = false;
input.up = false;
input.down = false;
};
// Keyboard input
if (!mobile) {
document.addEventListener('keydown', keyDownHandler);
document.addEventListener('keyup', keyUpHandler);
}
// Mobile touch
else {
document.addEventListener('touchstart', touchStartHandler);
document.addEventListener('touchmove', touchMoveHandler);
document.addEventListener('touchend', touchEndHandler);
}
// Disable native context menu
window.addEventListener('contextmenu', function(event) {
event.preventDefault();
});
// Load Level
const loadLevel = async (map, spawn) => {
// Load Map
level = await loaderTMX.parseLevel({ xml: document.getElementById(map).innerText, scale, view, prefetch: false });
// Insert player in layer
level.actors['player'] = {
'player': player
};
level.getLayer('objects').actors.push('player');
// Move player to spawn point
const {x, y} = level.getSpawnPoint(spawn.toLowerCase(), {x: 0, y: 0});
player.level = level;
player.position(x, y);
player.idle();
// Centre view context on player
view.centre(player.transform);
};
// Init
window.addEventListener('load', () => {
// Initialize view context
view = new View({
canvas: document.getElementById('main'),
bounds: {top: -150, bottom: 150, left: -150, right: 150},
debug: true
});
// Loaders
loaderACX = new LoaderACX();
loaderTSX = new LoaderTSX();
// Load tielsets
const tilesets = {'test.tsx': null, 'water.tsx': null, 'props.tsx': null};
loaderTSX.parseTileSet({ xml: document.getElementById('test-tsx').innerText, resource: '#env1', scale }).then(result => {
tilesets['test.tsx'] = result;
loaderTSX.parseTileSet({ xml: document.getElementById('water-tsx').innerText, resource: '#env2', scale }).then(result => {
tilesets['water.tsx'] = result;
loaderTSX.parseTileSet({ xml: document.getElementById('props-tsx').innerText, resource: '#props', scale }).then(result => {
tilesets['props.tsx'] = result;
// Load level data
loaderTMX = new LoaderTMX({tilesets});
// Load Player
player = loaderACX.parseActor({
xml: document.getElementById('player-acx').innerText,
scale
});
player.view = view;
// Load Level
loadLevel('test-tmx', 'player').then(() => start());
});
});
});
});
// Player movement
const playerMoveUp = (deltaTime) => {
// Direction
player.transform.vec.y = -1;
// Collide
const [x, y] = player.collide(level.getColliders(), deltaTime);
// Move
player.move(x, y);
// Animate
player.animate('moveUp', deltaTime);
// Scrol view
if (player.transform.y + view.offset.y < view.bounds.top) view.offset.y -= y;
};
const playerMoveDown = (deltaTime) => {
// Direction
player.transform.vec.y = 1;
// Collide
const [x, y] = player.collide(level.getColliders(), deltaTime);
// Move
player.move(x, y);
// Animate
player.animate('moveDown', deltaTime);
// Scrol view
if (player.transform.y + view.offset.y > view.bounds.bottom) view.offset.y -= y;
};
const playerMoveLeft = (deltaTime) => {
// Direction
player.transform.vec.x = -1;
// Collide
let [x, y] = player.collide(level.getColliders(), deltaTime);
// Stairs
const [stx, sty] = player.stairsLeft(level.getStairs(), deltaTime);
x += stx;
y += sty;
// Move
player.move(x, y);
// Animate
player.animate('moveLeft', deltaTime);
// Scrol view
if (player.transform.x + view.offset.x < view.bounds.left) view.offset.x -= x;
};
const playerMoveRight = (deltaTime) => {
// Direction
player.transform.vec.x = 1;
// Collide
let [x, y] = player.collide(level.getColliders(), deltaTime);
// Stairs
const [stx, sty] = player.stairsRight(level.getStairs(), deltaTime);
x += stx;
y += sty;
// Move
player.move(x, y);
// Animate
player.animate('moveRight', deltaTime);
// Scrol view
if (player.transform.x + view.offset.x > view.bounds.right) view.offset.x -= x;
};
// Start loop
const start = () => {
pause = false;
requestAnimationFrame(function(currentTime) {
lastTime = currentTime;
mainLoop(currentTime);
});
};
// Stop loop
const stop = () => {
pause = true;
};
// Main loop
let lastTime = 0;
let fpsContainer = document.querySelector('#fps');
const mainLoop = (currentTime) => {
// Universal frame time
const deltaTime = Math.min((currentTime - lastTime) / 1000, 0.1);
// Calculate FPS
fpsContainer.textContent = `FPS: ${Math.round(1 / deltaTime)}`;
// Clear screen
view.cls();
// Player movement
if (input.up) {
playerMoveUp(deltaTime);
}
else if (input.down) {
playerMoveDown(deltaTime);
}
else if (input.left) {
playerMoveLeft(deltaTime);
}
el