import standard; import node; import random; //consts var TILE_WIDTH: int const = 32; var TILE_HEIGHT: int const = 32; var MAP_WIDTH: int const = 16; var MAP_HEIGHT: int const = 16; var tilemap: opaque = null; var player: opaque = null; var stepCounter: opaque = null; var enemies: [opaque] = []; var entities: [opaque] = null; //full list of entities var collisionMap: [bool] = null; //cache this, since it won't change during a level var rng: opaque = null; //debugging tools var debugStepCounter: int = 0; var debugDrawCounter: int = 0; //lifecycle functions fn onLoad(node: opaque) { tilemap = node.loadChild("scripts:/gameplay/tilemap.toy"); player = node.loadChild("scripts:/gameplay/lejana.toy"); stepCounter = node.loadChild("scripts:/gameplay/step-counter.toy"); } fn onInit(node: opaque) { node.generateLevel(clock().hash(), MAP_WIDTH, MAP_HEIGHT); stepCounter.callNodeFn("setMaxSteps", 100); } fn onFree(node: opaque) { rng.freeRandomGenerator(); } fn onStep(node: opaque) { if (++debugStepCounter >= 30) { print "FPS: " + string debugDrawCounter + " / 30"; debugStepCounter = 0; debugDrawCounter = 0; } entities = entities.sort(depthComparator); } fn onDraw(node: opaque) { debugDrawCounter++; //call each child's custom draw fn (with parent shifting) tilemap.callNodeFn("customOnDraw", 0, 32); for (var i = 0; i < entities.length(); i++) { entities[i].callNodeFn("customOnDraw", 0, 32); } stepCounter.callNodeFn("customOnDraw", 0, 0, TILE_WIDTH * MAP_WIDTH, 32); } //utils - polyfills fn loadChild(parent: opaque, fname: string) { var child: opaque = loadNode(fname); parent.pushNode(child); return child; } //gameplay functions fn generateLevel(node: opaque, seed: int, width: int, height: int) { //reset the array enemies = []; entities = [player]; //assume the player exists already if (rng != null) rng.freeRandomGenerator(); rng = createRandomGenerator(seed); //place player player.callNodeFn("setGridPos", 1, 1); //generate map tilemap.callNodeFn("generateFromRng", rng, 16, 16); collisionMap = tilemap.callNodeFn("getCollisionMap"); //generate drones var droneCount: int const = rng.generateRandomNumber() % 2 + 20; for (var i = 0; i < droneCount; i++) { var d = node.loadChild("scripts:/gameplay/drone.toy"); d.initNode(); enemies.push(d); entities.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.getCollisionAt(x, y) == false || (x < (width / 6 * 2) && y < (height / 6 * 2))) { //TODO: toy bug - no short-circuiting? x = rng.generateRandomNumber() % width; y = rng.generateRandomNumber() % height; } d.callNodeFn("setGridPos", x, y); d.callNodeFn("setRealPos", (width -1) * TILE_WIDTH, (height - 1) * TILE_HEIGHT); } } fn getCollisionAt(node: opaque, x: int, y: int) { if (collisionMap == null || x == null || y == null) { return false; } //entities var pos = player.callNodeFn("getGridPos"); if (x == pos[0] && y == pos[1]) { return false; } for (var i = 0; i < enemies.length(); i++) { var pos = enemies[i].callNodeFn("getGridPos"); if (x == pos[0] && y == pos[1]) { return false; } } //default return collisionMap[y * MAP_WIDTH + x]; } fn runAI(node: opaque) { for (var i = 0; i < enemies.length(); i++) { enemies[i].callNodeFn("runAI", rng); } stepCounter.callNodeFn("alterRemainingSteps", -1); } fn depthComparator(lhs: opaque, rhs: opaque) { //for sorting by depth var lhsPos = lhs.callNodeFn("getRealPos"); var rhsPos = rhs.callNodeFn("getRealPos"); if (lhsPos[1] == rhsPos[1]) { //BUGFIX: prevent z-fighting return lhsPos[0] < rhsPos[0]; } return lhsPos[1] < rhsPos[1]; }