import standard; import node; import random; //persistent members var tilemap: opaque = null; var stepCounter: opaque = null; var player: opaque = null; var enemies: [opaque] = null; var collisionDataCache: [bool] = null; var rng: opaque = null; //TODO: does this have an opaque tag? //lifecycle functions fn onInit(node: opaque) { //load the map stuff tilemap = node.loadChild("scripts:/gameplay/tilemap.toy"); node.generateScene(clock().hash(), MAP_GRID_WIDTH, MAP_GRID_HEIGHT); //load and initialize the UI (depends on map stuff) stepCounter = node.loadChild("scripts:/gameplay/step-counter.toy"); stepCounter.setNodeRect( 0, 0, floor(TILE_PIXEL_WIDTH * MAP_GRID_WIDTH * tilemap.getNodeWorldScaleX()), //width - stretches along the top of the tilemap stepCounter.getNodeRectH() //default height ); stepCounter.callNodeFn("setMaxSteps", 100); //offset the scene node, so it shows the map outside separate from the UI node.setNodePositionY(16); } fn onFree(node: opaque) { //cleanup if (rng != null) { rng.freeRandomGenerator(); } } //fn onStep(node: opaque) { // // //} fn onDraw(node: opaque) { //use the onDraw to sort - skip too many steps node.sortChildrenNode(depthComparator); } //gameplay functions fn generateScene(node: opaque, seed: int, width: int, height: int) { //reset the array enemies = []; if (rng != null) { rng.freeRandomGenerator(); } rng = createRandomGenerator(seed); //generate map tilemap.callNodeFn("generateFromRng", rng, width, height); collisionDataCache = tilemap.callNodeFn("getCollisionData"); //place player here, so drop dropping can avoid the player player = node.loadChild("scripts:/gameplay/lejana.toy"); player.callNodeFn("setGridPosition", 1, 1); //generate drones var droneCount: int const = rng.generateRandomNumber() % 2 + 20; for (var i = 0; i < droneCount; i++) { var d: opaque = node.loadChild("scripts:/gameplay/drone.toy"); d.initNode(); enemies.push(d); var x = 0; var y = 0; //while generated spot is a collision, or too close to the player while (x == 0 && y == 0 || node.getWalkableAt(x, y) == false || x < width / 6 * 2 && y < height / 6 * 2) { x = rng.generateRandomNumber() % width; y = rng.generateRandomNumber() % height; } d.callNodeFn("setGridPosition", x, y); d.setNodePositionX(MAP_GRID_WIDTH * TILE_PIXEL_WIDTH); d.setNodePositionY(MAP_GRID_HEIGHT * TILE_PIXEL_HEIGHT); } } fn getWalkableAt(node: opaque, x: int, y: int) { if (collisionDataCache == null || x == null || y == null) { return false; } //entities if (x == player.callNodeFn("getGridPositionX") && y == player.callNodeFn("getGridPositionY")) { return false; } for (var i = 0; i < enemies.length(); i++) { if (x == enemies[i].callNodeFn("getGridPositionX") && y == enemies[i].callNodeFn("getGridPositionY")) { return false; } } //outside the grid if (y * MAP_GRID_WIDTH + x < 0 || y * MAP_GRID_WIDTH + x >= collisionDataCache.length()) { return false; } //default return collisionDataCache[y * MAP_GRID_WIDTH + x]; } fn runAI(node: opaque) { for (var i = 0; i < enemies.length(); i++) { enemies[i].callNodeFn("runAI", rng); } stepCounter.callNodeFn("alterRemainingSteps", -1); } //utils - polyfills fn loadChild(parent: opaque, fname: string) { //TODO: add this to the API proper var child: opaque = loadNode(fname); parent.pushNode(child); return child; } fn depthComparator(lhs: opaque, rhs: opaque) { //for sorting by depth var lhsPositionY = lhs.getNodeWorldPositionY(); var rhsPositionY = rhs.getNodeWorldPositionY(); if (lhsPositionY == null || rhsPositionY == null) { //BUGFIX: children without that function return true; } if (lhsPositionY == rhsPositionY) { //BUGFIX: prevent z-fighting var lhsPositionX = lhs.getNodeWorldPositionX(); var rhsPositionX = rhs.getNodeWorldPositionX(); return lhsPositionX < rhsPositionX; } return lhsPositionY < rhsPositionY; }