diff --git a/xtrn/lemons/commands.js b/xtrn/lemons/commands.js new file mode 100644 index 0000000000000000000000000000000000000000..d66382e8bd881b010e3f2c36192f65aa4039f3bf --- /dev/null +++ b/xtrn/lemons/commands.js @@ -0,0 +1,37 @@ +/* Lemons JSON DB module commands filter + Any commands sent by clients to the Lemons JSON DB module are passed + through the methods defined here before any changes are made to the DB. */ + +// Restrict what parts of the DB clients can write to +this.QUERY = function(client, packet) { + + // Read-only commands are okay + var openOpers = [ + "SUBSCRIBE", + "UNSUBSCRIBE", + "READ", + "KEYS", + "SLICE" + ]; + + var location = packet.location.split("."); + + // It's okay for clients to write to *.LATEST + if(packet.oper == "WRITE" && location.length == 2 && location[1] == "LATEST") + return false; // Command not handled (the JSON service can continue processing this command) + + /* Only sysops can use unapproved commands everywhere else. + service.js identifies as the host BBS' sysop. */ + if(openOpers.indexOf(packet.oper) >= 0 || admin.verify(client, packet, 90)) + return false; // Command not handled (the JSON service can continue processing this command) + + // If the command didn't pass our tests, mark it as handled and log + log(LOG_ERR, + format( + "Invalid command %s from client %s", + packet.oper, client.remote_ip_address + ) + ); + return true; // Command has been handled + +} \ No newline at end of file diff --git a/xtrn/lemons/defs.js b/xtrn/lemons/defs.js new file mode 100644 index 0000000000000000000000000000000000000000..ce423b43416af6e77ca585f75efac9c54dd0982b --- /dev/null +++ b/xtrn/lemons/defs.js @@ -0,0 +1,38 @@ +// Magic 'state' numbers used by most Lemons modules and the main loop. +const STATE_MENU = 0, + STATE_PLAY = 1, + STATE_PAUSE = 2, + STATE_HELP = 3, + STATE_SCORES = 4, + STATE_EXIT = 5; + +/* Magic numbers returned by Level.cycle() and Level.getcmd(), to be + evaluated by the Game object. */ +const LEVEL_DEAD = 0, + LEVEL_TIME = 1, + LEVEL_NEXT = 2, + LEVEL_CONTINUE = 3; + +// Magic numbers for state-tracking within the Game object +const GAME_STATE_CHOOSELEVEL = 0, + GAME_STATE_NORMAL = 1, + GAME_STATE_POPUP = 2; + +/* These are used by the Level object during colorization and lemonization of + skilled lemons, and also to colorize the skill list in the status bar. You + can change these values if you want. */ +const COLOUR_LEMON = YELLOW, + COLOUR_BASHER = LIGHTMAGENTA, + COLOUR_BLOCKER = GREEN, + COLOUR_BOMBER = LIGHTRED, + COLOUR_BUILDER = LIGHTGREEN, + COLOUR_CLIMBER = LIGHTCYAN, + COLOUR_DIGGER = CYAN, + COLOUR_NUKED = LIGHTRED, + COLOUR_DYING = LIGHTGRAY, + // The default status bar colours + COLOUR_STATUSBAR_BG = BG_BLUE, + COLOUR_STATUSBAR_FG = WHITE; + +// The name of the JSON database. You probably don't need to change this. +const DBNAME = "LEMONS"; \ No newline at end of file diff --git a/xtrn/lemons/game.js b/xtrn/lemons/game.js new file mode 100644 index 0000000000000000000000000000000000000000..0fb27b10a9b40ee083c29c89f8c6aefbdaa94b9f --- /dev/null +++ b/xtrn/lemons/game.js @@ -0,0 +1,329 @@ +// The Game object tracks a user's progress and score across multiple levels +var Game = function() { + + // We want the user to start by choosing an initial level, if applicable. + var gameState = GAME_STATE_CHOOSELEVEL; + + // Some values to be tracked throughout this gaming session + var stats = { + 'level' : 0, // Dummy value + 'score' : 0, // Index into the 'levels' array (see below) + 'turns' : 5 // How many turns to start the player off with + }; + + // Just an initial value + var levelState = LEVEL_CONTINUE; + + // This will always either be false or an instance of PopUp (lemons.js) + var popUp = false; + + /* This will always either be false (if not paused) or an array of stored + Timer events (if paused). */ + var events = false; + + // Load the JSON-DB server details if available + if(file_exists(js.exec_dir + "server.ini")) { + var f = new File(js.exec_dir + "server.ini"); + f.open("r"); + var ini = f.iniGetObject(); + f.close(); + ini.port = parseInt(ini.port); + // Or just set some default values + } else { + var ini = { 'host' : '127.0.0.1', 'port' : 10088 }; + } + + // Create a ScoreBoard object, which we'll use for a few things later + var scoreBoard = new ScoreBoard(ini.host, ini.port); + var l = scoreBoard.getHighestLevel(); + + // This will be a LevelChooser if we're picking a level + var levelChooser = false; + + // Make the paused/unpaused status of this Game publically accessible + this.paused = false; + + // Load the levels JSON file and parse it into the 'levels' array + var f = new File(js.exec_dir + "levels.json"); + f.open("r"); + var levels = JSON.parse(f.read()); + f.close(); + + // A custom prompt that works with this game's input & state model + var LevelChooser = function() { + + var lcFrame = new Frame( + frame.x + Math.ceil((frame.width - 32) / 2), + frame.y + Math.ceil((frame.height - 3) / 2), + 32, + 3, + BG_BLACK|WHITE, + frame + ); + + var lcSubFrame = new Frame( + lcFrame.x + 1, + lcFrame.y + 1, + lcFrame.width - 2, + 1, + BG_BLACK|WHITE, + lcFrame + ); + + var inputFrame = new Frame( + lcSubFrame.x + lcSubFrame.width - 5, + lcSubFrame.y, + 3, + 1, + BG_BLUE|WHITE, + lcSubFrame + ); + + lcFrame.drawBorder([CYAN, LIGHTCYAN, WHITE]); + lcSubFrame.putmsg("Start at level: (1 - " + (l + 1) + ") "); + + var input = (l + 1) + ""; + inputFrame.putmsg(input); + + this.getcmd = function(userInput) { + + // We're only looking for numbers, backspace, or enter + if( userInput == "" + || + ( userInput != "\x08" + && + userInput != "\r" + && + userInput != "\n" + && + isNaN(parseInt(userInput)) + ) + ) { + return; + } + + // Handle backspace + if(userInput == "\x08" && input.length > 0) { + + input = input.substr(0, input.length - 1); + + // Handle enter + } else if(userInput == "\r" || userInput == "\n") { + + var ret = parseInt(input); + + input = ""; + inputFrame.clear(); + inputFrame.putmsg(input); + + // Don't return an invalid number + if(input.length > 3 || isNaN(ret) || ret > (l + 1) || ret < 1) + return; + else + return (ret - 1); + + // Anything else that's gotten this far can be appended + } else { + + input += userInput; + + } + + // If we've gotten this far, the input box needs a refresh + inputFrame.clear(); + inputFrame.putmsg(input); + + return; + + } + + this.close = function() { + lcFrame.delete(); + } + + lcFrame.open(); + + } + + var level; // We'll need this variable within some of the following methods + + // Handle user input passed to us by the parent script + this.getcmd = function(userInput) { + + // If the user should be choosing a level ... + if(gameState == GAME_STATE_CHOOSELEVEL) { + + // If this is a new user or they haven't beat level 0, start there + if(l == 0) { + + level = new Level(levels[stats.level], stats.level); + gameState = GAME_STATE_NORMAL; + + // Otherwise let them start at up to their highest level + } else if(!levelChooser) { + + levelChooser = new LevelChooser(); + + // And if a chooser is already open, use it + } else { + + var ll = levelChooser.getcmd(userInput); + // The chooser should return a valid level number, or nothing + if(typeof ll != "undefined") { + + levelChooser.close(); + levelChooser = false; + stats.level = ll; + level = new Level(levels[stats.level], stats.level); + gameState = GAME_STATE_NORMAL; + return true; + + } + + } + + /* If gameplay is in progress, pass user input to the Level object. + If Level.getcmd returns false, the user hit 'Q' to quit. */ + } else if(gameState == GAME_STATE_NORMAL && !level.getcmd(userInput)) { + + level.close(); + return false; + + // If a pop-up is on the screen, remove it if the user hits a key + } else if(gameState == GAME_STATE_POPUP && userInput != "") { + + gameState = GAME_STATE_NORMAL; + popUp.close(); + + } + + // Game.getcmd will return true unless the user hit 'Q' during gameplay + return true; + + } + + /* This is called during the main loop when in gameplay state, and returns + true unless the user runs out of turns or beats the game. */ + this.cycle = function() { + + // If we're waiting for the player to choose a level + if(gameState == GAME_STATE_CHOOSELEVEL) { + + return true; + + // If we're waiting for them to dismiss a pop-up message + } else if(gameState == GAME_STATE_POPUP) { + + return true; + + // If they failed to rescue enough lemons, or ran out of time + } else if(levelState == LEVEL_DEAD || levelState == LEVEL_TIME) { + + // Close the level, perchance to reopen it and start again + level.close(); + // If the user has run out of turns ... + if(stats.turns < 1) { + stats.score = stats.score + level.score; + return false; + // If the user has turns left, let them try again + } else { + level = new Level(levels[stats.level], stats.level); + } + + // If they beat the level, move on to the next one if it exists + } else if(levelState == LEVEL_NEXT) { + + level.close(); + if(stats.level < levels.length - 1) { + stats.level++; + level = new Level(levels[stats.level], stats.level); + } else { + return false; + } + + } + + // Call the Level.cycle housekeeping method, respond as necessary + levelState = level.cycle(); + switch(levelState) { + + // The user failed to rescue enough lemons. Raise a pop-up. + case LEVEL_DEAD: + stats.turns--; + popUp = new PopUp( + [ "What a sour outcome - less than 50% of your lemons survived!", + "Score: " + stats.score, + stats.turns + " turns remaining", + "[ Press any key to continue ]" + ] + ); + gameState = GAME_STATE_POPUP; + break; + + // The user ran out of time. Raise a pop-up. + case LEVEL_TIME: + stats.turns--; + popUp = new PopUp( + [ "You ran out of time! Now the lemons are going to miss their party.", + "Score: " + stats.score, + stats.turns + " turns remaining", + "[ Press any key to continue ]" + ] + ); + gameState = GAME_STATE_POPUP; + break; + + // The user beat the level. Raise a pop-up. + case LEVEL_NEXT: + stats.score += level.score; + popUp = new PopUp( + [ "You saved some lemons. Savour this pointless victory!", + "Score: " + stats.score, + "[ Press any key to continue ]" + ] + ); + gameState = GAME_STATE_POPUP; + break; + + // The level can continue cycling. Don't do shit. + case LEVEL_CONTINUE: + break; + + default: + break; + + } + + return true; // Everything's satisfactory in the neighbourhood + + } + + // Pause or resume the level + this.pause = function() { + + /* If the level isn't paused, pause it and store the timed events + that it returns. */ + if(!events) { + + events = level.pause(); + this.paused = true; + + /* If the level is paused, unpause it by passing in the array of + stored events. */ + } else { + + level.pause(events); + events = false; + this.paused = false; + + } + + } + + // If the user is done with this gaming session, save their score + this.close = function() { + scoreBoard.addScore(stats.score, stats.level); + scoreBoard.close(); + } + +} \ No newline at end of file diff --git a/xtrn/lemons/help.bin b/xtrn/lemons/help.bin new file mode 100644 index 0000000000000000000000000000000000000000..b79ac845e9039f5b2b4659f8730b9931ff33cbaa --- /dev/null +++ b/xtrn/lemons/help.bin @@ -0,0 +1,14 @@ +��� ��.� ��+� In each level, your goal is to guide a herd of lemons from��� -> ��� -> ���. the e n t r a n c e door to the e +x +i +t + door. The average lemon is ��� �n� ��� not very clever, and will need assistance along the way. - A lemon will turn and walk the other way if it encounters an obstacle. - Beacuse they are stupid pieces of fruit who don't know any better, lemons walk off of the edges of platforms, even when there's nothing to land on. - Lemons will die if they fall into water, lava, or slime. Lemons aren't entirely hopeless. Though their long-term memory is poor they are able to acquire new skills and remember them for brief periods. You can teach a lemon to b a s h or dig through �F�F� blocks, climb over certain obstacles, b +u +i +l +d + staircases, block other lemons from following a path, or even to explode and cause damage to their surroundings. To teach a lemon new tricks, use your arrow keys to hover the cursor over the lemon, then press the number-key that corresponds with the skill you wish to assign (as shown in the status-bar during gameplay.) To pass each level, you must guide at least 50% of the lemons to the e +x +i +t + before time runs out. If all hope is lost, you can always hit N to nuke any lemons remaining on the screen. \ No newline at end of file diff --git a/xtrn/lemons/help.js b/xtrn/lemons/help.js new file mode 100644 index 0000000000000000000000000000000000000000..3f40133455e13b24d5aedcabee0bce1da83491a2 --- /dev/null +++ b/xtrn/lemons/help.js @@ -0,0 +1,34 @@ +// The Help object loads the help screen into the terminal. +var Help = function() { + + var helpFrame = new Frame( + frame.x, + frame.y, + frame.width, + frame.height, + BG_BLACK|WHITE, + frame + ); + + var helpSubFrame = new Frame( + helpFrame.x + 1, + helpFrame.y + 1, + helpFrame.width - 2, + helpFrame.height - 2, + BG_BLACK|WHITE, + helpFrame + ); + + helpFrame.open(); + helpFrame.drawBorder([CYAN, LIGHTCYAN, WHITE]); + helpFrame.home(); + helpFrame.center(ascii(180) + "Lemons - Help" + ascii(195)); + helpFrame.end(); + helpFrame.center(ascii(180) + "Press any key to continue" + ascii(195)); + helpSubFrame.load(js.exec_dir + "help.bin", 76, 22); + + this.close = function() { + helpFrame.delete(); + } + +} \ No newline at end of file diff --git a/xtrn/lemons/lemons.bin b/xtrn/lemons/lemons.bin new file mode 100644 index 0000000000000000000000000000000000000000..ff30b062724d009186366684d45693fd2b740437 --- /dev/null +++ b/xtrn/lemons/lemons.bin @@ -0,0 +1 @@ +�����������������s�s�s�s�������������������������������������������������������������s�s�s�>���>�����>���>�����>���>�����>���>�����>���>�����>���>���s�������������������s�s�s������������ ������� �����s�� �� ���� �� ���� � ���� �����s���������������������������������>� �����>�>� �����>�>� �� ��>�>� �� ��.�.� � ��>�>���� ��>���������������s�s�s�������s�s�s��������>�����>���>�����>���>�����>���>�����.���.��������>�����>���������s�s�������s���������������� ?b?y? ?e?c?h?i?c?k?e?n?�s���������s�s�������� ������� �����������������s����������������������������������s�������������s�������� ���b� �����������s������������������������s�������������������������������� ����������n� ���������s�s�������������������s�s�s�s���������������������� ���� ��� ���������������� ���������s���������������������s�s�s��������������������� �/�/�� � ���N�O��O�O�N����������� ��������������������������������������������������� ��� ������O ��O���������������������������������������������������������������� ����.������N�O���O�N������������ ���s�s�s�������s�s�s���������������������������s�s���������� ������������������N�O��O�O�N�� ����s���������s�����������������������������s�s�����������������������������O� ��O��������������������������s�s�s��������������������������� �������������������N�O�O��O�N� ��������������������������s��������s������������������������ ��N������������������� ��������s������������������������������������� � ����� ��� ��������� � �/�/�� �������s�s�s��������������������������������������� �.������N�N�N���� �������.������ �������������������������s�s��������������������� ����������������������� ��� �������������������������������������������s�s��s����� ����� ������������� ����������������������������������������������������s�s������ ��� ��� ������� �������������������������s�s�s������������s�s������������������������������ ���� �� ������s�s��������s�s�s����������������������������������������������������� �������� �������������s�s�s�s�s��������������s�s�s��������������������������������s�s������� ���� �����������������s�s�s�������������������������s�s��� \ No newline at end of file diff --git a/xtrn/lemons/lemons.js b/xtrn/lemons/lemons.js new file mode 100644 index 0000000000000000000000000000000000000000..019efc1a43ce74f459a331d0e5aa16b17bffaa54 --- /dev/null +++ b/xtrn/lemons/lemons.js @@ -0,0 +1,261 @@ +// Dependencies +load('sbbsdefs.js'); +load('frame.js'); +load('tree.js'); +load('event-timer.js'); +load('json-client.js'); +load('sprite.js'); + +// Lemons modules +load(js.exec_dir + "defs.js"); +load(js.exec_dir + "game.js"); +load(js.exec_dir + "level.js"); +load(js.exec_dir + "menu.js"); +load(js.exec_dir + "help.js"); +load(js.exec_dir + "scoreboard.js"); + +var status, // A place to store bbs.sys_status until exit + attributes; // A place to store console attributes until exit + +// The Lemons modules expect these globals: +var state, // What we're currently doing + frame; // The parent frame + +/* Draw a border of colour 'color' inside of a frame. + 'color' can be a number or an array of colours. */ +Frame.prototype.drawBorder = function(color) { + var theColor = color; + if(Array.isArray(color)); + var sectionLength = Math.round(this.width / color.length); + this.pushxy(); + for(var y = 1; y <= this.height; y++) { + for(var x = 1; x <= this.width; x++) { + if(x > 1 && x < this.width && y > 1 && y < this.height) + continue; + var msg; + this.gotoxy(x, y); + if(y == 1 && x == 1) + msg = ascii(218); + else if(y == 1 && x == this.width) + msg = ascii(191); + else if(y == this.height && x == 1) + msg = ascii(192); + else if(y == this.height && x == this.width) + msg = ascii(217); + else if(x == 1 || x == this.width) + msg = ascii(179); + else + msg = ascii(196); + if(Array.isArray(color)) { + if(x == 1) + theColor = color[0]; + else if(x % sectionLength == 0 && x < this.width) + theColor = color[x / sectionLength]; + else if(x == this.width) + theColor = color[color.length - 1]; + } + this.putmsg(msg, theColor); + } + } + this.popxy(); +} + +/* Pop a message up near the centre of frame 'frame': + var popUp = new PopUp(["Line 1", "Line 2" ...]); + console.getkey(); + popUp.close(); */ +var PopUp = function(message) { + + var longestLine = 0; + for(var m = 0; m < message.length; m++) { + if(message[m].length > longestLine) + longestLine = message[m].length; + } + + this.frame = new Frame( + frame.x + Math.ceil((frame.width - longestLine - 4) / 2), + frame.y + Math.ceil((frame.height - message.length - 2) / 2), + longestLine + 4, + message.length + 2, + BG_BLACK|WHITE, + frame + ); + + this.frame.open(); + this.frame.drawBorder([CYAN, LIGHTCYAN, WHITE]); + this.frame.gotoxy(1, 2); + for(var m = 0; m < message.length; m++) + this.frame.center(message[m] + "\r\n"); + + this.close = function() { + this.frame.delete(); + } + +} + +// Prepare the display, set the initial state +var init = function() { + + status = bbs.sys_status; // We'll restore this on cleanup + bbs.sys_status|=SS_MOFF; // Turn off node message delivery + + attributes = console.attributes; // We'll restore this on cleanup + console.clear(BG_BLACK|WHITE); // Wipe away any poops + + // Set up the root frame + frame = new Frame( + Math.ceil((console.screen_columns - 79) / 2), + Math.ceil((console.screen_rows - 23) / 2), + 80, + 24, + BG_BLACK|LIGHTGRAY + ); + frame.open(); + + // We start off at the menu screen + state = STATE_MENU; + +} + +// Return the display & system status to normal +var cleanUp = function() { + frame.delete(); + bbs.sys_status = status; + console.clear(attributes); +} + +// Get input from the user, make things happen +var main = function() { + + // These will always be either 'false' or an instance of a Lemons module + var game = false, + menu = false, + help = false, + scoreboard = false; + + while(!js.terminated) { + + // Refresh the user's terminal and bury the (real) cursor if necessary + if(frame.cycle()) + console.gotoxy(console.screen_columns, console.screen_rows); + + /* This is the only place where we take input from the user. + Input is passed to the current (as per state) module from here. */ + var userInput = console.inkey(K_UPPER, 5); + + switch(state) { + + case STATE_MENU: + // Load the menu if it isn't already showing + if(!menu) + menu = new Menu(); + // Pass user input to the menu + menu.getcmd(userInput); + // If we've left the menu, close and falsify it + if(state != STATE_MENU) { + menu.close(); + menu = false; + } + break; + + case STATE_PLAY: + // Create a new Game if we're not already in one + if(!game) + game = new Game(); + // Pass user input to the game module + if(!game.getcmd(userInput)) { + // If Game.getcmd returns false, the user has hit 'Q' + game.close(); + game = false; + // Return to the menu on the next loop + state = STATE_MENU; + } else if(!game.cycle()) { + /* If Game.cycle returns false, the user is out of turns + or has beat the last level. */ + game.close(); + game = false; + // Return to the menu on the next loop + state = STATE_MENU; + } + break; + + case STATE_PAUSE: + // If the game isn't paused, pause it + if(!game.paused) + game.pause(); + // If the user hit a key and the game is paused, unpause it + if(userInput != "" && game.paused) { + game.pause(); + // Return to gameplay on the next loop + state = STATE_PLAY; + } + break; + + case STATE_HELP: + // If a game is in progress, pause it + if(game instanceof Game && !game.paused) + game.pause(); + // If the help screen isn't showing, load it + if(!help) + help = new Help(); + /* If a game is in progress and the user hit a key, unpause + the game, remove the help screen, and return to gameplay. */ + if( userInput != "" + && + game instanceof Game && game.paused + ) { + help.close(); + help = false; + game.pause(); + state = STATE_PLAY; + /* If there's no game in progress, close the help screen and + return to the menu. */ + } else if(userInput != "") { + help.close(); + help = false; + state = STATE_MENU; + } + break; + + case STATE_SCORES: + // Bring up the scoreboard if it isn't showing already + if(!scoreboard) { + if(file_exists(js.exec_dir + "server.ini")) { + var f = new File(js.exec_dir + "server.ini"); + f.open("r"); + var ini = f.iniGetObject(); + f.close(); + } else { + var ini = { 'host' : "127.0.0.1", 'port' : 10088 }; + } + scoreboard = new ScoreBoard(ini.host, ini.port); + scoreboard.open(); + // Remove the scoreboard when the user hits a key + } else if(userInput != "") { + scoreboard.close(); + state = STATE_MENU; + scoreboard = false; + } + break; + + case STATE_EXIT: + // Break the main loop + return; + break; // I just can't not. :| + + default: + break; + + } + + } + +} + +// Prepare the things +init(); +// Do the things +main(); +// Remove the things +cleanUp(); +// We're done \ No newline at end of file diff --git a/xtrn/lemons/level.js b/xtrn/lemons/level.js new file mode 100644 index 0000000000000000000000000000000000000000..384fcb696cc43fe95e4baf78c57a477f17db78d4 --- /dev/null +++ b/xtrn/lemons/level.js @@ -0,0 +1,1218 @@ +// This is the actual game. It's a disaster, but it gets the job done. +var Level = function(l, n) { + + // Scope some variables for use by functions and methods in this object + var timer = new Timer(), // A Timer + // A place to organize this level's frames + frames = { + 'counters' : {} + }, + cursor, // The 1x1 cursor frame + countDown, // Level timeout + lost = 0, // Lemons lost + saved = 0, // Lemons saved + total = 0, // Total lemon count for this level + // Placeholder values for how many of each skill may be assigned + quotas = { + 'basher' : 0, + 'blocker' : 0, + 'bomber' : 0, + 'builder' : 0, + 'climber' : 0, + 'digger' : 0 + }; + + // The parent Game object will want to read this + this.score = 0; + + /* Change the colour of a lemon's peel without altering the colour of its + shoes or 'nuked' position. */ + var colorize = function(sprite, colour) { + var fgmask = (1<<0)|(1<<1)|(1<<2)|(1<<3); + for(var y = 0; y < sprite.frame.data.length; y++) { + for(var x = 0; x < 9; x++) { + sprite.frame.data[y][x].attr&=~fgmask; + if((y == 2 || y == 5) && (x == 0 || x == 2 || x == 3 || x == 5 || x == 6 || x == 8)) + sprite.frame.data[y][x].attr=BG_BLACK|BROWN; + else + sprite.frame.data[y][x].attr|=colour; + } + } + sprite.frame.invalidate(); + } + + // Turn skilled lemon back into an ordinary zombie-lemon. + var lemonize = function(sprite) { + colorize(sprite, COLOUR_LEMON); + sprite.ini.constantmotion = 1; + sprite.ini.gravity = 1; + sprite.ini.speed = .25; + sprite.ini.skill = "lemon"; + } + + // Remove the lemon from the screen + var remove = function(sprite) { + if(!sprite.open) + return; + sprite.remove(); + } + + /* If a lemon encounters blocks stacked >= 2 characters high it + will turn and start walking the other way. + If a lemon encounters a block 1 character high, it will climb + on top of it (stairs, etc.) + Normal, bomber, and builder lemons should be passed to this function. + */ + var turnOrClimbIfObstacle = function(sprite) { + + var beside = (sprite.bearing == "w") ? Sprite.checkLeft(sprite) : Sprite.checkRight(sprite); + if(!beside) + beside = Sprite.checkOverlap(sprite); + if(!beside) + return; + + for(var b = 0; b < beside.length; b++) { + + if( beside[b].ini.type != "block" + && + beside[b].ini.type != "lemon" + && + beside[b].ini.type != "hazard" + ) { + continue; + } + + if( beside[b].ini.type == "block" + && + beside[b].y == sprite.y + sprite.ini.height - 1 + ) { + sprite.moveTo( + (sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1), + sprite.y - 1 + ); + if(Sprite.checkOverlap(sprite)) { + sprite.moveTo( + (sprite.bearing == "w") ? (sprite.x + 1) : (sprite.x - 1), + sprite.y + 1 + ); + sprite.turnTo((sprite.bearing == "w") ? "e" : "w"); + if(sprite.ini.skill == "builder") + lemonize(sprite); + } + break; + + } else { + sprite.turnTo((sprite.bearing == "w") ? "e" : "w"); + var overlaps = Sprite.checkOverlap(sprite); + if(overlaps) { + for(var o = 0; o < overlaps.length; o++) { + if( overlaps[o].ini.type != "block" + && + overlaps[o].ini.type != "lemon" + && + overlaps[o].ini.type != "hazard" + ) { + continue; + } + if(sprite.bearing == "w") { + while(sprite.x < overlaps[o].x + overlaps[o].frame.width) { + sprite.move("reverse"); + } + } else if(sprite.bearing == "e") { + while(sprite.x + sprite.ini.width > overlaps[o].x) { + sprite.move("reverse"); + } + } + } + } + if(sprite.ini.skill == "builder") + lemonize(sprite); + break; + } + + } + + } + + /* This function is strictly for climber lemons. The lemon keeps + scaling the wall until it is able to continue moving along its + original bearing. If the climb is only one cell in height, this + was just a single block (stairs, perhaps) and the 'climber' skill + is retained for later use. Otherwise convert back to normal lemon + status. */ + var climbIfObstacle = function(sprite) { + + if(system.timer - sprite.lastYMove < sprite.ini.speed) + return; + + var beside = (sprite.bearing == "w") ? Sprite.checkLeft(sprite) : Sprite.checkRight(sprite); + if(!beside) + beside = Sprite.checkOverlap(sprite); + if(!beside) + return; + + if(typeof sprite.ini.climbStart == "undefined") + sprite.ini.climbStart = sprite.y; + + sprite.ini.gravity = 0; + sprite.ini.constantmotion = 0; + + for(var b = 0; b < beside.length; b++) { + + if( beside[b].ini.type == "entrance" + || + beside[b].ini.type == "exit" + || + beside[b].ini.type == "projectile" + ) { + lemonize(sprite); + delete sprite.ini.climbStart; + return; + } else if(beside[b].ini.type == "lemon") { + sprite.turnTo((sprite.bearing == "w") ? "e" : "w"); + sprite.ini.gravity = 1; + sprite.ini.constantmotion = 1; + return; + } + + } + + sprite.moveTo( + (sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1), + sprite.y - 1 + ); + sprite.lastYMove = system.timer; + var overlaps = Sprite.checkOverlap(sprite); + if(overlaps) { + for(var o = 0; o < overlaps.length; o++) { + if(overlaps[o].y != sprite.y + sprite.ini.height - 1) + continue; + if(sprite.bearing == "w") { + while(sprite.x < overlaps[o].x + overlaps[o].frame.width) { + sprite.move("reverse"); + } + } else if(sprite.bearing == "e") { + while(sprite.x + sprite.ini.width > overlaps[o].x) { + sprite.move("reverse"); + } + } + break; + } + } else if( + sprite.ini.climbStart > sprite.y + && + sprite.ini.climbStart - sprite.y > 1 + ) { + lemonize(sprite); + delete sprite.ini.climbStart; + } + + } + + /* The Sprite class actually takes care of most aspects of falling + for us. We just need to enforce straight vertical drops, and make + sure certain types don't start moving again once they land. + All lemons should be passed to this function. */ + var fallIfNoFloor = function(sprite) { + + if(sprite.inFall && sprite.ini.constantmotion == 1) { + // Stop lemons from moving horizontally when falling + sprite.ini.constantmotion = 0; + } else if( + !sprite.inFall + && + sprite.ini.constantmotion == 0 + && + sprite.ini.gravity == 1 + && + sprite.ini.skill != "blocker" + && + sprite.ini.skill != "digger" + && + sprite.ini.skill != "basher" + && + sprite.ini.skill != "builder" + && + sprite.ini.skill != "dying" + ) { + + if(typeof sprite.ini.forceDrop == "boolean") + delete sprite.ini.forceDrop; + + if( typeof sprite.ini.ticker == "undefined" + && + sprite.ini.skill != "climber" + ) { + lemonize(sprite); + } else { + sprite.ini.constantmotion = 1; + } + } + + } + + /* If a lemon has reached the exit, remove it and update counters + accordingly. + All but blocker lemons should be passed to this function. */ + var removeIfAtExit = function(sprite) { + var overlaps = Sprite.checkOverlap(sprite); + if(!overlaps) + return; + for(var o = 0; o < overlaps.length; o++) { + if(overlaps[o].ini.type != "exit") + continue; + sprite.remove(); + saved++; + frames.counters.lostSaved.clear(); + frames.counters.lostSaved.putmsg(lost + "/" + saved); + break; + } + } + + /* If any blocks get in the way of a 'basher' lemon, it will bash + through them until it reaches free space again, at which point + it loses its 'basher' skill. + It's too easy to just remove an entire block from the screen, + so we will produce a nice effect by dismantling the block by + one half-cell every ~ .25 seconds. */ + var bashersGonnaBash = function(sprite) { + + if(typeof sprite.ini.lastDig == "undefined") + sprite.ini.lastDig = system.timer; + if(system.timer - sprite.ini.lastDig < .5) + return; + + var beside = (sprite.bearing == "e") ? Sprite.checkRight(sprite) : Sprite.checkLeft(sprite); + if(!beside) + beside = Sprite.checkOverlap(sprite); + if(!beside) { + sprite.ini.constantmotion = 1; + return; + } + + sprite.ini.constantmotion = 0; + + var blockFound = false; + var columnClear = false; + for(var b = 0; b < beside.length; b++) { + + if(beside[b].ini.type == "lemon") { + sprite.turnTo((sprite.bearing == "e") ? "w" : "e"); + return; + } + + if( beside[b].ini.type != "block" + || + !beside[b].open + ) { + continue; + } + + if(beside[b].y == sprite.y + sprite.ini.height - 1) { + sprite.moveTo( + (sprite.bearing == "w") ? (sprite.x - 1) : (sprite.x + 1), + sprite.y - 1 + ); + if(!Sprite.checkOverlap(sprite)) { + sprite.ini.constantmotion = 1; + return; + } + sprite.moveTo( + (sprite.bearing == "w") ? (sprite.x + 1) : (sprite.x - 1), + sprite.y + 1 + ); + } + + blockFound = true; + + if(beside[b].ini.material == "metal") { + lemonize(sprite); + sprite.turnTo((sprite.bearing == "e") ? "w" : "e"); + return; + } + + if(sprite.bearing == "e") + var x = (beside[b].x - sprite.x == 3) ? 0 : ((beside[b].x - sprite.x == 2) ? 1 : 2); + else + var x = (sprite.x - beside[b].x == 3) ? 2 : ((sprite.x - beside[b].x == 2) ? 1 : 0); + + beside[b].frame.invalidate(); + sprite.ini.lastDig = system.timer; + sprite.lastMove = system.timer; + + if(beside[b].frame.data[0][x].ch == ascii(220)) { + beside[b].frame.data[0][x].ch = " "; + beside[b].frame.data[0][x].attr = BG_BLACK; + if( (sprite.bearing == "e" && x == 2) + || + (sprite.bearing == "w" && x == 0) + ) { + beside[b].remove(); + } + columnClear = (b == beside.length - 1); + break; + } + + if(beside[b].frame.data[0][x].ch == ascii(223)) { + beside[b].frame.data[0][x].ch = ascii(220); + beside[b].frame.data[0][x].attr = BG_BLACK|BROWN; + break; + } + + if(beside[b].frame.data[0][x].ch == ascii(219)) { + beside[b].frame.data[0][x].ch = ascii(220); + beside[b].frame.data[0][x].attr = BG_BLACK|RED; + break; + } + + } + + if(columnClear) { + sprite.move("forward"); + if( ((sprite.bearing == "e" && !Sprite.checkRight(sprite)) + || + (sprite.bearing == "w" && !Sprite.checkRight(sprite))) + && + !Sprite.checkOverlap(sprite) + ) { + lemonize(sprite); + return; + } + } else if(!blockFound) { + lemonize(sprite); + } + + } + + // Like bashersGonnaBash, but for digging downward. + var diggersGonnaDig = function(sprite) { + + if(typeof sprite.ini.lastDig == "undefined") + sprite.ini.lastDig = system.timer; + if(system.timer - sprite.ini.lastDig < .5) + return; + + var below = Sprite.checkBelow(sprite); + if(!below) + below = Sprite.checkOverlap(sprite); + if(!below) + return; + + sprite.ini.constantmotion = 0; + + var clear = below.length; + for(var b = 0; b < below.length; b++) { + + if( below[b].ini.type != "block" + || + !below[b].open + ) { + clear--; + continue; + } + + if(below[b].ini.material == "metal") { + lemonize(sprite); + return; + } + + var cleared = true; + for(var c = 0; c < 3; c++) { + if(below[b].frame.data[0][c].ch != " ") + cleared = false; + } + if(cleared) { + below[b].remove(); + clear--; + continue; + } + + below[b].frame.invalidate(); + sprite.ini.lastDig = system.timer; + + for(var c = 0; c < 3; c++) { + + if(below[b].frame.data[0][c].ch == ascii(220)) { + below[b].frame.data[0][c].ch = " "; + below[b].frame.data[0][c].attr = 0; + break; + } + + if(below[b].frame.data[0][c].ch == ascii(223)) { + below[b].frame.data[0][c].ch = ascii(220); + below[b].frame.data[0][c].attr = BG_BLACK|BROWN; + break; + } + + if(below[b].frame.data[0][c].ch == ascii(219)) { + below[b].frame.data[0][c].ch = ascii(220); + below[b].frame.data[0][c].attr = BG_BLACK|RED; + break; + } + + } + + } + + if(clear == 0) { + sprite.moveTo(sprite.x, sprite.y + 1); + if(!Sprite.checkBelow(sprite)) { + lemonize(sprite); + sprite.lastMove = system.timer; + } + } + + } + + // Build a staircase four blocks high, or until an obstacle is encountered + var buildersGonnaBuild = function(sprite) { + + if(typeof sprite.ini.lastBuild == "undefined") { + sprite.ini.lastBuild = system.timer; + sprite.ini.buildCount = 0; + sprite.ini.constantmotion = 0; + } + if(system.timer - sprite.ini.lastBuild < .5) + return; + + if(sprite.ini.buildCount == 4) { + lemonize(sprite); + delete sprite.ini.buildCount; + delete sprite.ini.lastBuild; + return; + } + + sprite.ini.buildCount++; + sprite.ini.lastBuild = system.timer; + + var block = new Sprite.Profile( + "brick", + frames.field, + (sprite.bearing == "e") ? (sprite.x + sprite.ini.width) : (sprite.x - 4), + sprite.y + sprite.ini.height - 1, + "e", + "normal" + ); + var overlap = Sprite.checkOverlap(block); + if(overlap) { + block.remove(); + Sprite.profiles.splice(block.index, 1); + lemonize(sprite); + delete sprite.ini.lastBuild; + delete sprite.ini.buildCount; + return; + } + block.frame.open(); + sprite.moveTo( + (sprite.bearing == "e") ? (sprite.x + 3) : (sprite.x - 3), + sprite.y - 1 + ); + + } + + // Draw the time left until explosion at the centre of each of a lemon's positions + var ticker = function(sprite) { + sprite.ini.ticker--; + sprite.frame.data[1][1].ch = sprite.ini.ticker; + sprite.frame.data[1][4].ch = sprite.ini.ticker; + sprite.frame.data[1][7].ch = sprite.ini.ticker; + sprite.frame.data[4][1].ch = sprite.ini.ticker; + sprite.frame.data[4][4].ch = sprite.ini.ticker; + sprite.frame.data[4][7].ch = sprite.ini.ticker; + } + + // Make the lemon look explodey, cause some damage + var explode = function(sprite) { + if(!sprite.open) + return; + sprite.changePosition("nuked"); + sprite.ini.speed = 2000; + var overlap = Sprite.checkOverlap(sprite, 1); + for(var o = 0; overlap && o < overlap.length; o++) { + if(overlap[o].ini.type == "lemon") + continue; + overlap[o].remove(); + } + lost++; + frames.counters.lostSaved.clear(); + frames.counters.lostSaved.putmsg(lost + "/" + saved); + } + + // Redden a lemon, and prepare it for obliteration + var nuke = function(sprite) { + sprite.ini.ticker = 5; + timer.addEvent(5000, false, explode, [sprite]); + timer.addEvent(6000, false, remove, [sprite]); + timer.addEvent(1000, 5, ticker, [sprite]); + colorize(sprite, COLOUR_NUKED); + ticker(sprite); + } + + // Make a lemon tap its foot + var tapFoot = function(sprite) { + sprite.changePosition( + (sprite.position == "normal") ? "normal2" : "normal" + ); + } + + /* Populate the terminal with a status bar, the level's static sprites, + and set up timed events for releasing lemons and level timeout. */ + var loadLevel = function(level, frame) { + + // The parent frame for all sprites & frames created by Level + frames.game = new Frame( + frame.x, + frame.y, + frame.width, + frame.height, + BG_BLACK|LIGHTGRAY, + frame + ); + + // The gameplay area + frames.field = new Frame( + frames.game.x, + frames.game.y, + frames.game.width, + frames.game.height - 2, + BG_BLACK|LIGHTGRAY, + frames.game + ); + + // The status bar and its various subframes + frames.statusBar = new Frame( + frames.game.x, + frames.game.y + frames.game.height - 2, + frames.game.width, + 2, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.game + ); + + frames.counters.basher = new Frame( + frames.statusBar.x + 10, + frames.statusBar.y, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.bomber = new Frame( + frames.statusBar.x + 24, + frames.statusBar.y, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.climber = new Frame( + frames.statusBar.x + 38, + frames.statusBar.y, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.blocker = new Frame( + frames.statusBar.x + 10, + frames.statusBar.y + 1, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.builder = new Frame( + frames.statusBar.x + 24, + frames.statusBar.y + 1, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.digger = new Frame( + frames.statusBar.x + 38, + frames.statusBar.y + 1, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.remaining = new Frame( + frames.statusBar.x + 69, + frames.statusBar.y, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.time = new Frame( + frames.statusBar.x + 76, + frames.statusBar.y + 1, + 3, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + frames.counters.lostSaved = new Frame( + frames.statusBar.x + 69, + frames.statusBar.y + 1, + 4, + 1, + COLOUR_STATUSBAR_BG|COLOUR_STATUSBAR_FG, + frames.statusBar + ); + + // Populate the status bar's static text fields + frames.statusBar.putmsg("1) Bash : ", COLOUR_BASHER|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("3) Bomb : ", COLOUR_BOMBER|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("5) Climb: ", COLOUR_CLIMBER|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("N)uke ", COLOUR_NUKED|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("H)elp ", WHITE|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg(" Remaining: Time:\r\n", WHITE|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("2) Block: ", COLOUR_BLOCKER|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("4) Build: ", COLOUR_BUILDER|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("6) Dig : ", COLOUR_DIGGER|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("P)ause ", WHITE|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("Q)uit ", WHITE|COLOUR_STATUSBAR_BG); + frames.statusBar.putmsg("Lost/Saved: ", WHITE|COLOUR_STATUSBAR_BG); + + // Display the initial lost/saved value + frames.counters.lostSaved.putmsg(lost + "/" + saved); + + // Set the quota values + if(typeof level.quotas == "undefined") + level.quotas = {}; + for(var q in level.quotas) { + quotas[q] = level.quotas[q]; + frames.counters[q].putmsg(quotas[q]); + } + + // Add bricks, etc. to the screen + for(var b = 0; b < level.blocks.length; b++) { + new Sprite.Profile( + level.blocks[b].type, + frames.field, + frames.field.x + level.blocks[b].x - 1, + frames.field.y + level.blocks[b].y - 1, + "e", + "normal" + ); + } + + // Add hazards (water, slime, lava) to the screen + for(var h = 0; h < level.hazards.length; h++) { + new Sprite.Profile( + level.hazards[h].type, + frames.field, + frames.field.x + level.hazards[h].x - 1, + frames.field.y + level.hazards[h].y - 1, + "e", + "normal" + ); + } + + // Add any shooters + for(var s = 0; s < level.shooters.length; s++) { + new Sprite.Profile( + level.shooters[s].type, + frames.field, + frames.field.x + level.shooters[s].x - 1, + frames.field.y + level.shooters[s].y - 1, + (level.shooters[s].type == "shooter-e") ? "e" : "w", + "normal" + ); + } + + // Create the cursor "sprite" + cursor = new Sprite.Platform( + frames.field, + frames.field.x + 39, + frames.field.y + 10, + 1, + 1, + ascii(219), + WHITE, + false, + false, + 0 + ); + cursor.ini = {}; + cursor.open = false; // Exempt it from collision-detection + + // Add the in & out doors to the screen + new Sprite.Profile( + "entrance", + frames.field, + frames.field.x + level.entrance.x - 1, + frames.field.y + level.entrance.y - 1, + "e", + "normal" + ); + + new Sprite.Profile( + "exit", + frames.field, + frames.field.x + level.exit.x - 1, + frames.field.y + level.exit.y - 1, + "e", + "normal" + ); + + // Set up a timed lemon-release event and "remaining lemons" counter + var remaining = level.lemons; + var releaseLemon = function(x, y) { + new Sprite.Profile( + "lemon", + frames.field, + x, + y, + "e", + "normal" + ); + Sprite.profiles[Sprite.profiles.length - 1].frame.open(); + Sprite.profiles[Sprite.profiles.length - 1].ini.skill = "lemon"; + remaining--; + frames.counters.remaining.clear(); + frames.counters.remaining.putmsg(remaining); + } + frames.counters.remaining.putmsg(remaining); + + timer.addEvent( + 3000, + level.lemons, + releaseLemon, + [ frames.field.x + level.entrance.x - 1, + frames.field.y + level.entrance.y - 1 + ] + ); + total = level.lemons; // When lost + saved == total, the level is done + + // Set up a timed event to update the clock + countDown = level.time + 3; // Don't penalize users for delayed release + var tickTock = function() { + countDown--; + frames.counters.time.clear(); + frames.counters.time.putmsg(countDown); + } + timer.addEvent(1000, level.time, tickTock); + + // Open the top frame and all its chilluns + frames.game.open(); + + // Display the level number and name for a few sex + var popUp = new PopUp(["Level " + (n + 1) + ": " + level.name]); + timer.addEvent(3000, false, popUp.close, [], popUp); + + } + + /* Make the lemons behave according to skillset, or die if necessary. + Return a LEVEL_ value to the parent script indicating whether the level + is complete (and why) or if it is in progress. */ + this.cycle = function() { + + /* Record the index of the first lemon that overlaps with the cursor, + if any, for use when assigning skills during this.getcmd. + Make the cursor red if there's an overlap, white if not. */ + cursor.frame.top(); + var overlaps = Sprite.checkOverlap(cursor); + if(overlaps) { + for(var o = 0; o < overlaps.length; o++) { + if(overlaps[o].ini.type != "lemon") + continue; + cursor.frame.data[0][0].attr = LIGHTRED; + cursor.ini.hoveringOver = overlaps[o].index; + break; + } + } else if(typeof cursor.ini.hoveringOver != "undefined") { + cursor.frame.data[0][0].attr = WHITE; + delete cursor.ini.hoveringOver; + } + + for(var s = 0; s < Sprite.profiles.length; s++) { + + if(Sprite.profiles[s].ini.type == "shooter") { + Sprite.profiles[s].putWeapon(); + continue; + } + + if( Sprite.profiles[s].ini.type == "projectile" + && + Sprite.profiles[s].open + ) { + var overlaps = Sprite.checkOverlap(Sprite.profiles[s]); + for(var o = 0; o < overlaps.length; o++) { + if(overlaps[o].ini.type != "lemon") + continue; + overlaps[o].remove(); + lost++; + frames.counters.lostSaved.clear(); + frames.counters.lostSaved.putmsg(lost + "/" + saved); + } + } + + if(Sprite.profiles[s].ini.type != "lemon" || !Sprite.profiles[s].open) + continue; + + // Remove any lemons that have gone off the screen + if( !Sprite.profiles[s].open + || + Sprite.profiles[s].y + Sprite.profiles[s].ini.height > frames.field.y + frames.field.height + || + Sprite.profiles[s].y + Sprite.profiles[s].ini.height <= frames.field.y + || + Sprite.profiles[s].x + Sprite.profiles[s].ini.width <= frames.field.x + || + Sprite.profiles[s].x >= frames.field.x + frames.field.width + ) { + Sprite.profiles[s].remove(); + lost++; + frames.counters.lostSaved.clear(); + frames.counters.lostSaved.putmsg(lost + "/" + saved); + } + + // Don't stand on top of a door, start a death march if on a hazard + var below = Sprite.checkBelow(Sprite.profiles[s]); + if(below) { + for(var b = 0; b < below.length; b++) { + if( below[b].ini.type == "exit" + || + below[b].ini.type == "entrance" + ) { + Sprite.profiles[s].moveTo( + Sprite.profiles[s].x, + Sprite.profiles[s].y + 1 + ); + break; + } else if( + below[b].ini.type == "hazard" + && + Sprite.profiles[s].ini.skill != "dying" + ) { + Sprite.profiles[s].ini.constantmotion = 0; + lost++; + frames.counters.lostSaved.clear(); + frames.counters.lostSaved.putmsg(lost + "/" + saved); + Sprite.profiles[s].ini.skill = "dying"; + colorize(Sprite.profiles[s], COLOUR_DYING); + timer.addEvent( + 1000, + false, + remove, + [Sprite.profiles[s]] + ); + break; + } + } + } else if( + Sprite.profiles[s].ini.skill != "digger" + && + typeof Sprite.profiles[s].ini.forceDrop == "undefined" + && + Sprite.profiles[s].ini.skill != "basher" + ) { + /* Sprite() moves things horizontally first, then vertically. + In certain circumstances (a hole of only the same width as + the sprite has just appeared beneath it) we need to force + the sprite to drop one cell to initiate a fall. */ + Sprite.profiles[s].moveTo( + Sprite.profiles[s].x, + Sprite.profiles[s].y + 1 + ); + // But we only want to do it once, not every .cycle() + Sprite.profiles[s].ini.forceDrop = true; + /* This will be cleared in fallIfNoFloor once the lemon + hits bottom. */ + } + + // Animate the lemon's walk, if applicable + if( (( !Sprite.profiles[s].inFall + && + system.timer - Sprite.profiles[s].lastMove > Sprite.profiles[s].ini.speed + ) + || + ( Sprite.profiles[s].inFall + && + system.timer - Sprite.profiles[s].lastYMove > Sprite.profiles[s].ini.speed + )) + && + Sprite.profiles[s].ini.gravity == 1 + && + Sprite.profiles[s].ini.skill != "blocker" + && + Sprite.profiles[s].position != "nuked" + && + Sprite.profiles[s].ini.skill != "builder" + && + Sprite.profiles[s].ini.skill != "digger" + ) { + Sprite.profiles[s].changePosition( + (Sprite.profiles[s].position == "normal") ? "normal2" : "normal" + ); + } + + // Make the different types of lemons behave as they should + switch(Sprite.profiles[s].ini.skill) { + + case "lemon": + turnOrClimbIfObstacle(Sprite.profiles[s]); + fallIfNoFloor(Sprite.profiles[s]); + removeIfAtExit(Sprite.profiles[s]); + break; + + case "basher": + bashersGonnaBash(Sprite.profiles[s]); + fallIfNoFloor(Sprite.profiles[s]); + removeIfAtExit(Sprite.profiles[s]); + break; + + case "blocker": + fallIfNoFloor(Sprite.profiles[s]); + break; + + case "bomber": + turnOrClimbIfObstacle(Sprite.profiles[s]); + fallIfNoFloor(Sprite.profiles[s]); + removeIfAtExit(Sprite.profiles[s]); + break; + + case "builder": + turnOrClimbIfObstacle(Sprite.profiles[s]); + buildersGonnaBuild(Sprite.profiles[s]); + fallIfNoFloor(Sprite.profiles[s]); + removeIfAtExit(Sprite.profiles[s]); + break; + + case "climber": + climbIfObstacle(Sprite.profiles[s]); + fallIfNoFloor(Sprite.profiles[s]); + removeIfAtExit(Sprite.profiles[s]); + break; + + case "digger": + diggersGonnaDig(Sprite.profiles[s]); + fallIfNoFloor(Sprite.profiles[s]); + break; + + default: + break; + + } + + } + + // Cycle the other cyclable things + timer.cycle(); + Sprite.cycle(); + + // Tell the parent script how to proceed + if(countDown <= 0) { + if(saved < (total / 2)) + return LEVEL_TIME; + this.score = (saved * 100); + return LEVEL_NEXT; + } else if(lost + saved == total && saved >= (total / 2)) { + this.score = (saved * 100) + (countDown * 10); + return LEVEL_NEXT; + } else if(lost + saved == total && saved < (total / 2)) { + return LEVEL_DEAD; + } else { + return LEVEL_CONTINUE; + } + + } + + // Do what the user asked us to do, if it's valid + this.getcmd = function(userInput) { + + var ret = true; + + switch(userInput.toUpperCase()) { + + case "": + break; + + // Quit + case "Q": + ret = false; + break; + + // Pause + case "P": + state = STATE_PAUSE; + break; + + // Help + case "H": + state = STATE_HELP; + break; + + // Cursor movement + case KEY_UP: + if(cursor.y > frames.field.y) + cursor.moveTo(cursor.x, cursor.y - 1); + break; + + case KEY_DOWN: + if(cursor.y < frames.field.y + frames.field.height - 1) + cursor.moveTo(cursor.x, cursor.y + 1); + break; + + case KEY_LEFT: + if(cursor.x > frames.field.x) + cursor.moveTo(cursor.x - 1, cursor.y); + break; + + case KEY_RIGHT: + if(cursor.x < frames.field.x + frames.field.width - 1) + cursor.moveTo(cursor.x + 1, cursor. y); + break; + + // Nuke the h'wales + case "N": + for(var s = 0; s < Sprite.profiles.length; s++) { + if(Sprite.profiles[s].ini.type != "lemon" || !Sprite.profiles[s].open) + continue; + nuke(Sprite.profiles[s]); + } + break; + + // Basher + case "1": + if(typeof cursor.ini.hoveringOver == "undefined") + break; + if(quotas.basher < 1) + break; + quotas.basher--; + frames.counters.basher.clear(); + frames.counters.basher.putmsg(quotas.basher); + Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "basher"; + colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_BASHER); + break; + + // Blocker + case "2": + if(typeof cursor.ini.hoveringOver == "undefined") + break; + if(quotas.blocker < 1) + break; + quotas.blocker--; + frames.counters.blocker.clear(); + frames.counters.blocker.putmsg(quotas.blocker); + Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "blocker"; + Sprite.profiles[cursor.ini.hoveringOver].ini.constantmotion = 0; + Sprite.profiles[cursor.ini.hoveringOver].changePosition("fall"); + colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_BLOCKER); + timer.addEvent(1000, true, tapFoot, [Sprite.profiles[cursor.ini.hoveringOver]]); + break; + + // Bomber + case "3": + if(typeof cursor.ini.hoveringOver == "undefined") + break; + if(quotas.bomber < 1) + break; + quotas.bomber--; + frames.counters.bomber.clear(); + frames.counters.bomber.putmsg(quotas.bomber); + Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "bomber"; + nuke(Sprite.profiles[cursor.ini.hoveringOver]); + break; + + // Builder + case "4": + if(typeof cursor.ini.hoveringOver == "undefined") + break; + if(quotas.builder < 1) + break; + quotas.builder--; + frames.counters.builder.clear(); + frames.counters.builder.putmsg(quotas.builder); + Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "builder"; + colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_BUILDER); + break; + + // Climber + case "5": + if(typeof cursor.ini.hoveringOver == "undefined") + break; + if(quotas.climber < 1) + break; + quotas.climber--; + frames.counters.climber.clear(); + frames.counters.climber.putmsg(quotas.climber); + Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "climber"; + colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_CLIMBER); + break; + + // Digger + case "6": + if(typeof cursor.ini.hoveringOver == "undefined") + break; + if(quotas.digger < 1) + break; + quotas.digger--; + frames.counters.digger.clear(); + frames.counters.digger.putmsg(quotas.digger); + Sprite.profiles[cursor.ini.hoveringOver].ini.skill = "digger"; + Sprite.profiles[cursor.ini.hoveringOver].ini.constantmotion = 0; + Sprite.profiles[cursor.ini.hoveringOver].changePosition("fall"); + colorize(Sprite.profiles[cursor.ini.hoveringOver], COLOUR_DIGGER); + break; + + default: + break; + } + + return ret; + + } + + /* Halt or resume all timed events. + This doesn't actually block - that should be done in the main loop. + If no argument is specified, events are removed from the timer and an + array of these events is returned. + If an argument is specified, it is assumed to be an array of events, + and they are added back into the timer. */ + this.pause = function(events) { + if(typeof events == "undefined") { + var events = timer.events; + timer.events = []; + return events; + } else { + for(var e = 0; e < events.length; e++) { + timer.addEvent( + events[e].interval, + events[e].repeat, + events[e].action, + events[e].arguments + ); + } + } + } + + // Reset the Sprite globals, clean up the display + this.close = function() { + + cursor.remove(); + Sprite.platforms = []; + + for(var s = 0; s < Sprite.profiles.length; s++) { + Sprite.profiles[s].remove(); + } + Sprite.profiles = []; + + frames.game.delete(); + + } + + loadLevel(l, frame); + +} \ No newline at end of file diff --git a/xtrn/lemons/leveledit.js b/xtrn/lemons/leveledit.js new file mode 100644 index 0000000000000000000000000000000000000000..2685778dc4049f3e07a09493aff8ce30a163b720 --- /dev/null +++ b/xtrn/lemons/leveledit.js @@ -0,0 +1,112 @@ +/* Uses the LevelEditor object from leveleditor.js to make changes to the + levels.json file. This whole setup is quick and dirty, and was only + made so that I could easily add levels to the game. It isn't user- + friendly and I don't expect (or want to support) other people using it. */ + +load("sbbsdefs.js"); +load("frame.js"); +load("tree.js"); +load(js.exec_dir + "leveleditor.js"); + +var frame, headFrame, footFrame, treeFrame, tree, treeItems = []; + +var loadLevels = function() { + if(!file_exists(js.exec_dir + "levels.json")) + return []; + var f = new File(js.exec_dir + "levels.json"); + f.open("r"); + var levels = JSON.parse(f.read()); + f.close(); + return levels; +} + +var saveLevels = function(levels) { + file_backup(js.exec_dir + "levels.json"); + var f = new File(js.exec_dir + "levels.json"); + f.open("w"); + f.write(JSON.stringify(levels)); + f.close(); +} + +var initFrames = function() { + console.clear(); + frame = new Frame(1, 1, 80, 24, WHITE); + headFrame = new Frame(1, 1, 80, 1, BG_BLUE|WHITE, frame); + treeFrame = new Frame(1, 2, 80, 22, BG_BLACK|WHITE, frame); + footFrame = new Frame(1, 24, 80, 1, BG_BLUE|WHITE, frame); + headFrame.putmsg("Lemons Level Editor"); + footFrame.putmsg("[Enter], A)dd, D)elete, [ESC] quit"); + tree = new Tree(treeFrame); + frame.open(); + tree.open(); +} + +var clearTree = function() { + for(var item in treeItems) + tree.deleteItem(tree.trace(treeItems[item].hash)); + treeItems = []; +} + +var initMenu = function() { + clearTree(); + var levels = loadLevels(); + for(var l = 0; l < levels.length; l++) + treeItems.push(tree.addItem(levels[l].name, editLevel, l)); + tree.index = 0; +} + +var editLevel = function(level) { + var levels = loadLevels(); + var editor = new LevelEditor(frame); + editor.open(); + if(typeof level != "undefined") + editor.loadLevel(levels[level]); + while(!js.terminated) { + var userInput = console.inkey(K_UPPER, 5); + if(ascii(userInput) == 27) + break; + editor.getcmd(userInput); + editor.cycle(); + if(frame.cycle()) + console.gotoxy(80, 24); + } + var result = editor.close(); + if(typeof level == "undefined") + levels.push(result); + else + levels[level] = result; + saveLevels(levels); + frame.invalidate(); +} + +var main = function() { + initFrames(); + initMenu(); + while(!js.terminated) { + var userInput = console.inkey(K_UPPER, 5); + if(ascii(userInput) == 27) + break; + if(userInput == "A") { + editLevel(); + initMenu(); + } else if(userInput == "D") { + log(tree.index); + var levels = loadLevels(); + levels.splice(tree.index, 1); + saveLevels(levels); + initMenu(); + } else { + tree.getcmd(userInput); + } + if(frame.cycle()) + console.gotoxy(80, 24); + } +} + +var cleanUp = function() { + tree.close(); + frame.close(); +} + +main(); +cleanUp(); diff --git a/xtrn/lemons/leveleditor.js b/xtrn/lemons/leveleditor.js new file mode 100644 index 0000000000000000000000000000000000000000..b98f7e97332fd65bd6b4c4c3bda653430375ddcf --- /dev/null +++ b/xtrn/lemons/leveleditor.js @@ -0,0 +1,524 @@ +/* This is a terrible modification of the terrible Chicken Delivery + Level Editor. This script provides the LevelEditor object, but + doesn't actually do anything on its own. See leveledit.js. */ + +load("sbbsdefs.js"); +load("frame.js"); +load("tree.js"); +load("funclib.js"); + +var LevelEditor = function(parentFrame) { + + var frame, + fieldFrame, + cursorFrame, + headFrame, + footFrame, + treeFrame, + treeSubFrame; + + var stuff = { + name : "", + time : 120, + blocks : [], + hazards : [], + shooters : [], + entrance : false, + exit : false + }; + + var state = { + 'block' : false, + 'entrance' : false, + 'exit' : false, + 'hazard' : false, + 'shooter' : false + }; + + var entrance, + exit, + blocks = [], + hazards = [] + shooters = []; + + var loadSprites = function() { + var files = directory(js.exec_dir + "sprites/*.ini"); + for(var f = 0; f < files.length; f++) { + var file = new File(files[f]); + file.open("r"); + var ini = file.iniGetObject(); + file.close(); + var shortName = file_getname(files[f]).replace(/\.ini$/, ""); + if(ini.type == "entrance") + entrance = shortName; + else if(ini.type == "exit") + exit = shortName; + else if(ini.type == "block") + blocks.push(shortName); + else if(ini.type == "hazard") + hazards.push(shortName); + else if(ini.type == "shooter") + shooters.push(shortName); + } + } + + var noYes = function(question) { + var nyFrame = new Frame( + Math.floor((fieldFrame.width - question.length - 9) / 2), + Math.floor((fieldFrame.height - 1) / 2), + question.length + 9, + 1, + BG_BLUE|WHITE, + fieldFrame + ); + nyFrame.open(); + nyFrame.putmsg(question + "? Y/N: "); + frame.cycle(); + var ret = console.getkeys("YN"); + nyFrame.delete(); + return (ret == "Y"); + } + + var moveUp = function(subFrame, frame) { + if(subFrame.y > frame.y) + subFrame.move(0, -1); + } + + var moveDown = function(subFrame, frame) { + if(subFrame.y + subFrame.height - 1 < frame.y + frame.height - 1) + subFrame.move(0, 1); + } + + var moveLeft = function(subFrame, frame) { + if(subFrame.x > frame.x) + subFrame.move(-1, 0); + } + + var moveRight = function(subFrame, frame) { + if(subFrame.x + subFrame.width - 1 < frame.x + frame.width - 1) + subFrame.move(1, 0); + } + + var checkDelete = function(frame) { + if(cursorFrame.x < frame.x) + return false; + if(cursorFrame.x >= frame.x + frame.width) + return false; + if(cursorFrame.y < frame.y) + return false; + if(cursorFrame.y >= frame.y + frame.height) + return false; + return true; + } + + var blockChooser = function() { + var tree = new Tree(treeSubFrame); + for(var b in blocks) + tree.addItem(blocks[b], blocks[b]); + treeFrame.top(); + tree.open(); + var choice = ""; + while(blocks.indexOf(choice) < 0) { + choice = console.inkey(K_NONE, 5); + if(ascii(choice) == 27) + break; + choice = tree.getcmd(choice); + if(frame.cycle()) + console.gotoxy(console.screen_columns, console.screen_rows); + } + tree.close(); + treeFrame.bottom(); + if(typeof choice != "string") + return false; + var f = loadItem(choice); + f.type = choice; + return f; + } + + var hazardChooser = function() { + var tree = new Tree(treeSubFrame); + for(var h in hazards) + tree.addItem(hazards[h], hazards[h]); + treeFrame.top(); + tree.open(); + var choice = ""; + while(hazards.indexOf(choice) < 0) { + choice = console.inkey(K_NONE, 5); + if(ascii(choice) == 27) + break; + choice = tree.getcmd(choice); + if(frame.cycle()) + console.gotoxy(console.screen_columns, console.screen_rows); + } + tree.close(); + treeFrame.bottom(); + if(typeof choice != "string") + return false; + var f = loadItem(choice); + f.type = choice; + return f; + } + + var shooterChooser = function() { + var tree = new Tree(treeSubFrame); + for(var s in shooters) + tree.addItem(shooters[s], shooters[s]); + treeFrame.top(); + tree.open(); + var choice = ""; + while(shooters.indexOf(choice) < 0) { + choice = console.inkey(K_NONE, 5); + if(ascii(choice) == 27) + break; + choice = tree.getcmd(choice); + if(frame.cycle()) + console.gotoxy(console.screen_columns, console.screen_rows); + } + tree.close(); + treeFrame.bottom(); + if(typeof choice != "string") + return false; + var f = loadItem(choice); + f.type = choice; + return f; + } + + var initFrames = function() { + + console.clear(LIGHTGRAY); + + frame = new Frame(1, 1, 80, 24, LIGHTGRAY); + + headFrame = new Frame( + frame.x, + frame.y, + frame.width, + 1, + BG_BLUE|WHITE, + frame + ); + + fieldFrame = new Frame( + frame.x, + frame.y, + frame.width, + frame.height - 2, + LIGHTGRAY, + frame + ); + + footFrame = new Frame( + frame.x, + frame.y + frame.height - 1, + frame.width, + 1, + BG_BLUE|WHITE, + frame + ); + + cursorFrame = new Frame( + fieldFrame.x, + fieldFrame.y, + 1, + 1, + WHITE, + fieldFrame + ); + + treeFrame = new Frame( + Math.floor(fieldFrame.x + (fieldFrame.width / 4)), + fieldFrame.y + 2, + Math.floor(fieldFrame.width / 2), + fieldFrame.height - 4, + BG_BLUE|WHITE, + frame + ); + + treeSubFrame = new Frame( + treeFrame.x + 1, + treeFrame.y + 1, + treeFrame.width - 2, + treeFrame.height - 2, + WHITE, + treeFrame + ); + + headFrame.putmsg("Lemons Level Editor"); + footFrame.putmsg("B)lock, H)azard, S)hooter, E)ntrance, e(X)it, [DEL], [ENTER], [ESC]"); + treeFrame.center("Choose an item"); + cursorFrame.putmsg(ascii(219)); + + frame.open(); + treeFrame.bottom(); + + } + + var loadItem = function(item) { + var f = new File(js.exec_dir + "sprites/" + item + ".ini"); + f.open("r"); + var ini = f.iniGetObject(); + f.close(); + ini.width = parseInt(ini.width); + ini.height = parseInt(ini.height); + if(cursorFrame.x + ini.width > fieldFrame.x + fieldFrame.width) + return false; + if(cursorFrame.y + ini.height > fieldFrame.y + fieldFrame.height) + return false; + var f = new Frame(cursorFrame.x, cursorFrame.y, ini.width, ini.height, 0, fieldFrame); + f.open(); + f.load(js.exec_dir + "sprites/" + item + ".bin", ini.width, ini.height); + f.type = item; + return f; + } + + this.open = function() { + initFrames(); + loadSprites(); + } + + this.loadLevel = function(level) { + stuff.name = level.name; + stuff.time = (typeof level.time == "undefined") ? 60 : level.time; + stuff.blocks = []; + stuff.hazards = []; + stuff.shooters = []; + stuff.entrance = { 'x' : fieldFrame.x, 'y' : fieldFrame.y }; + stuff.exit = { 'x' : fieldFrame.x, 'y' : fieldFrame.y }; + for(var property in state) + state[property] = false; + fieldFrame.clear(); + cursorFrame.moveTo(level.entrance.x, level.entrance.y); + stuff.entrance = loadItem(entrance); + cursorFrame.moveTo(level.exit.x, level.exit.y); + stuff.exit = loadItem(exit); + for(var b = 0; b < level.blocks.length; b++) { + cursorFrame.moveTo(level.blocks[b].x, level.blocks[b].y); + stuff.blocks.push(loadItem(level.blocks[b].type)); + } + for(var h = 0; h < level.hazards.length; h++) { + cursorFrame.moveTo(level.hazards[h].x, level.hazards[h].y); + stuff.hazards.push(loadItem(level.hazards[h].type)); + } + for(var s = 0; s < level.shooters.length; s++) { + cursorFrame.moveTo(level.shooters[s].x, level.shooters[s].y); + stuff.shooters.push(loadItem(level.shooters[s].type)); + } + } + + this.getcmd = function(userInput) { + + switch(userInput) { + case KEY_UP: + moveUp(cursorFrame, fieldFrame); + if(state.block) + moveUp(state.block, fieldFrame); + else if(state.hazard) + moveUp(state.hazard, fieldFrame); + else if(state.entrance) + moveUp(state.entrance, fieldFrame); + else if(state.exit) + moveUp(state.exit, fieldFrame); + else if(state.shooter) + moveUp(state.shooter, fieldFrame); + break; + case KEY_DOWN: + moveDown(cursorFrame, fieldFrame); + if(state.block) + moveDown(state.block, fieldFrame); + else if(state.hazard) + moveDown(state.hazard, fieldFrame); + else if(state.entrance) + moveDown(state.entrance, fieldFrame); + else if(state.exit) + moveDown(state.exit, fieldFrame); + else if(state.shooter) + moveDown(state.shooter, fieldFrame); + break; + case KEY_LEFT: + moveLeft(cursorFrame, fieldFrame); + if(state.block) + moveLeft(state.block, fieldFrame); + else if(state.hazard) + moveLeft(state.hazard, fieldFrame); + else if(state.entrance) + moveLeft(state.entrance, fieldFrame); + else if(state.exit) + moveLeft(state.exit, fieldFrame); + else if(state.shooter) + moveLeft(state.shooter, fieldFrame); + break; + case KEY_RIGHT: + moveRight(cursorFrame, fieldFrame); + if(state.block) + moveRight(state.block, fieldFrame); + else if(state.hazard) + moveRight(state.hazard, fieldFrame); + else if(state.entrance) + moveRight(state.entrance, fieldFrame); + else if(state.exit) + moveRight(state.exit, fieldFrame); + else if(state.shooter) + moveRight(state.shooter, fieldFrame); + break; + case KEY_DEL: + if(stuff.entrance && checkDelete(stuff.entrance)) { + stuff.entrance.delete(); + stuff.entrance = false; + } + if(stuff.exit && checkDelete(stuff.exit)) { + stuff.exit.delete(); + stuff.exit = false; + } + for(var b = 0; b < stuff.blocks.length; b++) { + if(!checkDelete(stuff.blocks[b])) + continue; + var block = stuff.blocks.splice(b, 1)[0]; + block.delete(); + } + for(var h = 0; h < stuff.hazards.length; h++) { + if(!checkDelete(stuff.hazards[h])) + continue; + var hazard = stuff.hazards.splice(h, 1)[0]; + hazard.delete(); + } + for(var s = 0; s < stuff.shooters.length; s++) { + if(!checkDelete(stuff.shooters[s])) + continue; + var shooter = stuff.shooters.splice(s, 1)[0]; + shooter.delete(); + } + break; + case "B": + if(state.shooter || state.block || state.entrance || state.exit || state.hazard) + break; + state.block = blockChooser(); + if(!state.block) + break; + state.block.moveTo(cursorFrame.x, cursorFrame.y); + break; + case "H": + if(state.shooter || state.hazard || state.entrance || state.exit || state.block) + break; + state.hazard = hazardChooser(); + if(!state.hazard) + break; + state.hazard.moveTo(cursorFrame.x, cursorFrame.y); + break; + case "E": + if(state.shooter || state.entrance || stuff.entrance || state.exit || state.block || state.hazard) + break; + state.entrance = loadItem(entrance); + break; + case "X": + if(state.shooter || state.entrance || stuff.exit || state.exit || state.block || state.hazard) + break; + state.exit = loadItem(exit); + break; + case "I": + // "I" don't know what this is for + break; + case "S": + if(state.shooter || state.entrance || state.exit || state.hazard || state.block) + break; + state.shooter = shooterChooser(); + if(!state.shooter) + break; + state.shooter.moveTo(cursorFrame.x, cursorFrame.y); + break; + case "\r": + if(state.block) { + stuff.blocks.push(state.block); + state.block = false; + } else if(state.hazard) { + stuff.hazards.push(state.hazard); + state.hazard = false; + } else if(state.entrance) { + stuff.entrance = state.entrance; + state.entrance = false; + } else if(state.exit) { + stuff.exit = state.exit; + state.exit = false; + } else if(state.shooter) { + stuff.shooters.push(state.shooter); + state.shooter = false; + } + break; + default: + break; + + } + } + + this.cycle = function() { + if(frame.cycle()) + console.gotoxy(console.screen_columns, console.screen_rows); + cursorFrame.top(); + } + + this.close = function() { + var ret = { + 'name' : stuff.name, + 'time' : stuff.time, + 'author' : user.alias, + 'system' : system.name, + 'date' : time(), + 'blocks' : [], + 'hazards' : [], + 'shooters' : [], + 'lemons' : 1, + 'quotas' : { + 'basher' : 5, + 'blocker' : 5, + 'bomber' : 5, + 'builder' : 5, + 'climber' : 5, + 'digger' : 5 + }, + 'entrance' : (!stuff.entrance) ? {'x' : fieldFrame.x , 'y' : fieldFrame.y } : { 'x' : stuff.entrance.x, 'y' : stuff.entrance.y }, + 'exit' : (!stuff.exit) ? {'x' : fieldFrame.x , 'y' : fieldFrame.y } : { 'x' : stuff.exit.x, 'y' : stuff.exit.y } + }; + for(var b in stuff.blocks) { + ret.blocks.push( + { 'x' : stuff.blocks[b].x, + 'y' : stuff.blocks[b].y, + 'type' : stuff.blocks[b].type + } + ); + } + for(var h in stuff.hazards) { + ret.hazards.push( + { 'x' : stuff.hazards[h].x, + 'y' : stuff.hazards[h].y, + 'type' : stuff.hazards[h].type + } + ); + } + for(var s in stuff.shooters) { + ret.shooters.push( + { 'x' : stuff.shooters[s].x, + 'y' : stuff.shooters[s].y, + 'type' : stuff.shooters[s].type + } + ); + } + if(typeof frame.parent != "undefined") + frame.parent.invalidate(); + frame.close(); + console.clear(LIGHTGRAY); + console.putmsg("How many lemons: (1 - 99) "); + ret.lemons = console.getnum(99); + console.putmsg("Time limit, in seconds: (1 - 600) "); + ret.time = console.getnum(600); + console.putmsg("Quotas (1 - 99 of each skill type) \r\n"); + for(var q in ret.quotas) { + console.putmsg("\t" + q + ": "); + ret.quotas[q] = console.getnum(99); + } + console.putmsg("Name this level: "); + ret.name = console.getstr(ret.name, 60, K_LINE|K_EDIT); + if(console.strlen(ret.name.replace(/\s/g, "")) < 1) + ret.name = "Untitled"; + return ret; + } + +} diff --git a/xtrn/lemons/levels.json b/xtrn/lemons/levels.json new file mode 100644 index 0000000000000000000000000000000000000000..8d1973acacfd5ac7c35ce94bda9370009ad4b49c --- /dev/null +++ b/xtrn/lemons/levels.json @@ -0,0 +1 @@ +[{"name":"Lemon Party","time":180,"author":"echicken","system":"electronic chicken bbs","date":1426565307,"blocks":[{"x":4,"y":4,"type":"brick"},{"x":7,"y":4,"type":"brick"},{"x":10,"y":4,"type":"brick"},{"x":13,"y":4,"type":"brick"},{"x":16,"y":4,"type":"brick"},{"x":19,"y":4,"type":"brick"},{"x":22,"y":4,"type":"brick"},{"x":25,"y":4,"type":"brick"},{"x":28,"y":4,"type":"brick"},{"x":31,"y":4,"type":"brick"},{"x":34,"y":4,"type":"brick"},{"x":37,"y":4,"type":"brick"},{"x":40,"y":4,"type":"brick"},{"x":43,"y":4,"type":"brick"},{"x":46,"y":4,"type":"brick"},{"x":49,"y":4,"type":"brick"},{"x":52,"y":4,"type":"brick"},{"x":55,"y":4,"type":"brick"},{"x":58,"y":4,"type":"brick"},{"x":61,"y":4,"type":"brick"},{"x":64,"y":4,"type":"brick"},{"x":67,"y":4,"type":"brick"},{"x":70,"y":4,"type":"brick"},{"x":73,"y":4,"type":"brick"},{"x":76,"y":1,"type":"metal"},{"x":76,"y":2,"type":"metal"},{"x":76,"y":3,"type":"metal"},{"x":76,"y":4,"type":"metal"},{"x":1,"y":1,"type":"metal"},{"x":1,"y":2,"type":"metal"},{"x":1,"y":3,"type":"metal"},{"x":1,"y":4,"type":"metal"},{"x":75,"y":22,"type":"brick"},{"x":72,"y":22,"type":"brick"},{"x":69,"y":22,"type":"brick"},{"x":66,"y":22,"type":"brick"},{"x":63,"y":22,"type":"brick"},{"x":60,"y":22,"type":"brick"},{"x":57,"y":22,"type":"brick"},{"x":54,"y":22,"type":"brick"},{"x":51,"y":22,"type":"brick"},{"x":36,"y":22,"type":"brick"},{"x":33,"y":22,"type":"brick"},{"x":30,"y":22,"type":"brick"},{"x":27,"y":22,"type":"brick"},{"x":24,"y":22,"type":"brick"},{"x":21,"y":22,"type":"brick"},{"x":18,"y":22,"type":"brick"},{"x":15,"y":22,"type":"brick"},{"x":12,"y":22,"type":"brick"},{"x":9,"y":22,"type":"brick"},{"x":6,"y":22,"type":"brick"},{"x":3,"y":22,"type":"brick"},{"x":5,"y":11,"type":"brick"},{"x":8,"y":11,"type":"brick"},{"x":11,"y":11,"type":"brick"},{"x":14,"y":11,"type":"brick"},{"x":17,"y":11,"type":"brick"},{"x":23,"y":11,"type":"brick"},{"x":26,"y":11,"type":"brick"},{"x":29,"y":11,"type":"brick"},{"x":32,"y":11,"type":"brick"},{"x":35,"y":11,"type":"brick"},{"x":41,"y":11,"type":"brick"},{"x":44,"y":11,"type":"brick"},{"x":47,"y":11,"type":"brick"},{"x":50,"y":11,"type":"brick"},{"x":53,"y":11,"type":"brick"},{"x":56,"y":11,"type":"brick"},{"x":59,"y":11,"type":"brick"},{"x":62,"y":11,"type":"brick"},{"x":65,"y":11,"type":"brick"},{"x":68,"y":11,"type":"brick"},{"x":71,"y":11,"type":"brick"},{"x":74,"y":11,"type":"brick"},{"x":77,"y":10,"type":"metal"},{"x":77,"y":11,"type":"metal"},{"x":77,"y":9,"type":"metal"},{"x":77,"y":8,"type":"metal"},{"x":2,"y":11,"type":"metal"},{"x":2,"y":10,"type":"metal"},{"x":2,"y":9,"type":"metal"},{"x":2,"y":8,"type":"metal"},{"x":29,"y":10,"type":"brick"},{"x":32,"y":10,"type":"brick"},{"x":35,"y":10,"type":"brick"},{"x":38,"y":10,"type":"brick"},{"x":41,"y":10,"type":"brick"},{"x":44,"y":10,"type":"brick"},{"x":47,"y":10,"type":"brick"},{"x":5,"y":12,"type":"brick"},{"x":8,"y":12,"type":"brick"},{"x":11,"y":12,"type":"brick"},{"x":14,"y":12,"type":"brick"},{"x":23,"y":12,"type":"brick"},{"x":26,"y":12,"type":"brick"},{"x":65,"y":12,"type":"metal"},{"x":68,"y":12,"type":"metal"},{"x":71,"y":12,"type":"metal"},{"x":74,"y":12,"type":"metal"},{"x":77,"y":12,"type":"metal"},{"x":2,"y":12,"type":"metal"},{"x":38,"y":11,"type":"metal"},{"x":20,"y":11,"type":"metal"},{"x":20,"y":12,"type":"metal"},{"x":17,"y":12,"type":"metal"},{"x":39,"y":22,"type":"brick"},{"x":78,"y":18,"type":"metal"},{"x":78,"y":19,"type":"metal"},{"x":78,"y":20,"type":"metal"},{"x":78,"y":21,"type":"metal"},{"x":78,"y":22,"type":"metal"},{"x":38,"y":17,"type":"brick"},{"x":41,"y":17,"type":"brick"},{"x":44,"y":17,"type":"brick"},{"x":62,"y":12,"type":"brick"},{"x":59,"y":12,"type":"brick"},{"x":56,"y":12,"type":"brick"},{"x":53,"y":12,"type":"brick"},{"x":50,"y":12,"type":"brick"},{"x":47,"y":12,"type":"brick"},{"x":44,"y":12,"type":"brick"},{"x":29,"y":12,"type":"brick"}],"hazards":[],"shooters":[],"lemons":4,"quotas":{"basher":10,"blocker":10,"bomber":10,"builder":10,"climber":10,"digger":10},"entrance":{"x":4,"y":1},"exit":{"x":3,"y":19}},{"name":"Blocker Party","time":180,"author":"echicken","system":"electronic chicken bbs","date":1426654242,"blocks":[{"x":14,"y":4,"type":"metal"},{"x":17,"y":4,"type":"metal"},{"x":20,"y":4,"type":"metal"},{"x":23,"y":4,"type":"metal"},{"x":23,"y":10,"type":"brick"},{"x":26,"y":10,"type":"brick"},{"x":29,"y":10,"type":"brick"},{"x":32,"y":10,"type":"brick"},{"x":35,"y":10,"type":"brick"},{"x":38,"y":10,"type":"brick"},{"x":41,"y":10,"type":"brick"},{"x":44,"y":10,"type":"brick"},{"x":47,"y":10,"type":"brick"},{"x":50,"y":10,"type":"brick"},{"x":14,"y":22,"type":"brick"},{"x":17,"y":22,"type":"brick"},{"x":20,"y":22,"type":"brick"},{"x":23,"y":22,"type":"brick"},{"x":26,"y":22,"type":"brick"},{"x":29,"y":22,"type":"brick"},{"x":32,"y":22,"type":"brick"},{"x":35,"y":22,"type":"brick"},{"x":38,"y":22,"type":"brick"},{"x":41,"y":22,"type":"brick"},{"x":44,"y":22,"type":"brick"},{"x":47,"y":22,"type":"brick"},{"x":50,"y":22,"type":"brick"},{"x":53,"y":22,"type":"brick"},{"x":56,"y":22,"type":"brick"},{"x":59,"y":22,"type":"brick"},{"x":14,"y":10,"type":"metal"},{"x":17,"y":10,"type":"metal"},{"x":20,"y":10,"type":"metal"},{"x":53,"y":10,"type":"metal"},{"x":56,"y":10,"type":"metal"},{"x":59,"y":10,"type":"metal"},{"x":47,"y":4,"type":"metal"},{"x":50,"y":4,"type":"metal"},{"x":53,"y":4,"type":"metal"},{"x":56,"y":4,"type":"metal"},{"x":59,"y":4,"type":"metal"},{"x":26,"y":4,"type":"metal"},{"x":29,"y":4,"type":"metal"},{"x":32,"y":4,"type":"brick"},{"x":35,"y":4,"type":"brick"},{"x":38,"y":4,"type":"brick"},{"x":41,"y":4,"type":"brick"},{"x":44,"y":4,"type":"brick"},{"x":11,"y":4,"type":"metal"},{"x":8,"y":4,"type":"metal"},{"x":62,"y":4,"type":"metal"},{"x":65,"y":4,"type":"metal"},{"x":68,"y":4,"type":"metal"},{"x":71,"y":4,"type":"metal"},{"x":5,"y":4,"type":"metal"}],"hazards":[],"shooters":[],"lemons":7,"quotas":{"basher":7,"blocker":7,"bomber":7,"builder":7,"climber":7,"digger":7},"entrance":{"x":23,"y":1},"exit":{"x":59,"y":19}},{"name":"Basher Bash","time":180,"author":"echicken","system":"electronic chicken bbs","date":1426572644,"blocks":[{"x":1,"y":1,"type":"metal"},{"x":1,"y":2,"type":"metal"},{"x":1,"y":3,"type":"metal"},{"x":1,"y":4,"type":"metal"},{"x":22,"y":22,"type":"metal"},{"x":34,"y":21,"type":"metal"},{"x":34,"y":22,"type":"metal"},{"x":37,"y":21,"type":"metal"},{"x":37,"y":20,"type":"metal"},{"x":40,"y":20,"type":"metal"},{"x":40,"y":19,"type":"metal"},{"x":43,"y":19,"type":"metal"},{"x":43,"y":18,"type":"metal"},{"x":46,"y":18,"type":"metal"},{"x":46,"y":17,"type":"metal"},{"x":49,"y":17,"type":"metal"},{"x":52,"y":17,"type":"metal"},{"x":55,"y":17,"type":"metal"},{"x":58,"y":17,"type":"metal"},{"x":61,"y":17,"type":"metal"},{"x":64,"y":17,"type":"metal"},{"x":67,"y":17,"type":"metal"},{"x":70,"y":17,"type":"metal"},{"x":73,"y":17,"type":"metal"},{"x":76,"y":17,"type":"metal"},{"x":4,"y":4,"type":"metal"},{"x":7,"y":4,"type":"metal"},{"x":7,"y":5,"type":"metal"},{"x":10,"y":5,"type":"metal"},{"x":10,"y":6,"type":"metal"},{"x":13,"y":6,"type":"metal"},{"x":19,"y":6,"type":"metal"},{"x":19,"y":7,"type":"metal"},{"x":19,"y":8,"type":"metal"},{"x":19,"y":9,"type":"metal"},{"x":19,"y":10,"type":"metal"},{"x":16,"y":10,"type":"metal"},{"x":16,"y":11,"type":"metal"},{"x":13,"y":11,"type":"metal"},{"x":13,"y":12,"type":"metal"},{"x":10,"y":12,"type":"metal"},{"x":4,"y":12,"type":"metal"},{"x":4,"y":13,"type":"metal"},{"x":4,"y":14,"type":"metal"},{"x":4,"y":15,"type":"metal"},{"x":4,"y":16,"type":"metal"},{"x":7,"y":16,"type":"metal"},{"x":7,"y":17,"type":"metal"},{"x":10,"y":17,"type":"metal"},{"x":10,"y":18,"type":"metal"},{"x":13,"y":18,"type":"metal"},{"x":13,"y":19,"type":"metal"},{"x":16,"y":19,"type":"metal"},{"x":16,"y":20,"type":"metal"},{"x":19,"y":20,"type":"metal"},{"x":19,"y":21,"type":"metal"},{"x":22,"y":21,"type":"metal"},{"x":19,"y":5,"type":"metal"},{"x":4,"y":11,"type":"metal"},{"x":4,"y":10,"type":"metal"},{"x":4,"y":9,"type":"metal"},{"x":4,"y":8,"type":"metal"},{"x":4,"y":7,"type":"metal"},{"x":4,"y":6,"type":"metal"},{"x":4,"y":5,"type":"metal"},{"x":19,"y":4,"type":"metal"},{"x":61,"y":16,"type":"brick"},{"x":61,"y":15,"type":"brick"},{"x":61,"y":14,"type":"brick"},{"x":61,"y":13,"type":"brick"},{"x":61,"y":12,"type":"brick"},{"x":61,"y":11,"type":"brick"},{"x":61,"y":10,"type":"brick"},{"x":61,"y":9,"type":"brick"},{"x":61,"y":8,"type":"brick"},{"x":61,"y":7,"type":"brick"},{"x":61,"y":6,"type":"brick"},{"x":61,"y":5,"type":"brick"},{"x":61,"y":4,"type":"brick"},{"x":64,"y":16,"type":"brick"},{"x":64,"y":15,"type":"brick"},{"x":64,"y":14,"type":"brick"},{"x":64,"y":13,"type":"brick"},{"x":64,"y":12,"type":"brick"},{"x":64,"y":11,"type":"brick"},{"x":64,"y":10,"type":"brick"},{"x":64,"y":9,"type":"brick"},{"x":64,"y":8,"type":"brick"},{"x":64,"y":7,"type":"brick"},{"x":64,"y":6,"type":"brick"},{"x":64,"y":5,"type":"brick"},{"x":64,"y":4,"type":"brick"},{"x":25,"y":22,"type":"brick"},{"x":28,"y":22,"type":"brick"},{"x":31,"y":22,"type":"brick"}],"hazards":[],"shooters":[],"lemons":5,"quotas":{"basher":5,"blocker":2,"bomber":5,"builder":2,"climber":10,"digger":5},"entrance":{"x":4,"y":1},"exit":{"x":76,"y":14}},{"name":"Digger Diversion","time":180,"author":"echicken","system":"electronic chicken bbs","date":1426653696,"blocks":[{"x":39,"y":4,"type":"brick"},{"x":42,"y":4,"type":"brick"},{"x":45,"y":4,"type":"brick"},{"x":48,"y":4,"type":"brick"},{"x":51,"y":4,"type":"brick"},{"x":54,"y":4,"type":"brick"},{"x":57,"y":4,"type":"brick"},{"x":60,"y":4,"type":"brick"},{"x":63,"y":4,"type":"brick"},{"x":66,"y":4,"type":"brick"},{"x":78,"y":4,"type":"metal"},{"x":78,"y":5,"type":"metal"},{"x":69,"y":4,"type":"metal"},{"x":69,"y":5,"type":"metal"},{"x":30,"y":4,"type":"brick"},{"x":27,"y":4,"type":"brick"},{"x":24,"y":4,"type":"brick"},{"x":21,"y":4,"type":"brick"},{"x":18,"y":4,"type":"brick"},{"x":18,"y":1,"type":"brick"},{"x":18,"y":2,"type":"brick"},{"x":18,"y":3,"type":"brick"},{"x":1,"y":10,"type":"brick"},{"x":4,"y":11,"type":"brick"},{"x":7,"y":12,"type":"brick"},{"x":10,"y":13,"type":"brick"},{"x":1,"y":9,"type":"brick"},{"x":1,"y":8,"type":"brick"},{"x":13,"y":14,"type":"brick"},{"x":16,"y":15,"type":"brick"},{"x":22,"y":16,"type":"brick"},{"x":25,"y":16,"type":"brick"},{"x":28,"y":16,"type":"brick"},{"x":31,"y":16,"type":"brick"},{"x":67,"y":12,"type":"brick"},{"x":70,"y":12,"type":"brick"},{"x":75,"y":17,"type":"brick"},{"x":72,"y":18,"type":"brick"},{"x":69,"y":19,"type":"brick"},{"x":66,"y":20,"type":"brick"},{"x":63,"y":21,"type":"brick"},{"x":60,"y":22,"type":"brick"},{"x":57,"y":22,"type":"brick"},{"x":54,"y":22,"type":"brick"},{"x":51,"y":22,"type":"brick"},{"x":48,"y":22,"type":"brick"},{"x":45,"y":22,"type":"brick"},{"x":42,"y":22,"type":"brick"},{"x":39,"y":22,"type":"brick"},{"x":36,"y":22,"type":"brick"},{"x":33,"y":22,"type":"brick"},{"x":30,"y":22,"type":"brick"},{"x":27,"y":22,"type":"brick"},{"x":24,"y":22,"type":"brick"},{"x":21,"y":22,"type":"brick"},{"x":18,"y":22,"type":"brick"},{"x":15,"y":22,"type":"brick"},{"x":12,"y":22,"type":"brick"},{"x":9,"y":22,"type":"brick"},{"x":6,"y":22,"type":"brick"},{"x":3,"y":22,"type":"brick"},{"x":3,"y":18,"type":"brick"},{"x":6,"y":18,"type":"brick"},{"x":6,"y":19,"type":"brick"},{"x":6,"y":20,"type":"brick"},{"x":6,"y":21,"type":"brick"},{"x":34,"y":16,"type":"metal"},{"x":37,"y":16,"type":"metal"},{"x":33,"y":4,"type":"metal"},{"x":36,"y":4,"type":"metal"},{"x":40,"y":16,"type":"metal"},{"x":43,"y":16,"type":"metal"},{"x":46,"y":16,"type":"metal"},{"x":49,"y":16,"type":"metal"},{"x":52,"y":16,"type":"metal"},{"x":55,"y":15,"type":"metal"},{"x":58,"y":14,"type":"metal"},{"x":61,"y":13,"type":"metal"},{"x":64,"y":12,"type":"metal"},{"x":73,"y":12,"type":"brick"},{"x":76,"y":12,"type":"brick"},{"x":76,"y":11,"type":"brick"},{"x":76,"y":10,"type":"brick"},{"x":76,"y":9,"type":"brick"},{"x":76,"y":8,"type":"brick"},{"x":76,"y":7,"type":"brick"},{"x":76,"y":6,"type":"brick"},{"x":75,"y":13,"type":"brick"},{"x":75,"y":14,"type":"brick"},{"x":75,"y":15,"type":"brick"},{"x":75,"y":16,"type":"brick"},{"x":1,"y":11,"type":"brick"},{"x":4,"y":12,"type":"brick"},{"x":7,"y":13,"type":"brick"},{"x":10,"y":14,"type":"brick"},{"x":13,"y":15,"type":"brick"},{"x":22,"y":15,"type":"brick"},{"x":25,"y":15,"type":"brick"},{"x":28,"y":15,"type":"brick"},{"x":31,"y":15,"type":"brick"},{"x":34,"y":5,"type":"metal"},{"x":34,"y":6,"type":"metal"},{"x":34,"y":7,"type":"metal"},{"x":34,"y":8,"type":"metal"},{"x":34,"y":9,"type":"metal"},{"x":34,"y":10,"type":"metal"},{"x":34,"y":11,"type":"metal"},{"x":34,"y":12,"type":"metal"},{"x":34,"y":13,"type":"metal"},{"x":34,"y":14,"type":"metal"},{"x":34,"y":15,"type":"metal"},{"x":31,"y":15,"type":"metal"},{"x":16,"y":16,"type":"metal"},{"x":19,"y":15,"type":"metal"},{"x":19,"y":16,"type":"metal"},{"x":15,"y":5,"type":"brick"},{"x":12,"y":6,"type":"brick"},{"x":9,"y":7,"type":"brick"}],"hazards":[{"x":72,"y":4,"type":"water"}],"shooters":[],"lemons":5,"quotas":{"basher":5,"blocker":5,"bomber":5,"builder":2,"climber":2,"digger":10},"entrance":{"x":39,"y":1},"exit":{"x":3,"y":19}},{"name":"Climber Carousal","time":180,"author":"echicken","system":"electronic chicken bbs","date":1426655291,"blocks":[{"x":1,"y":22,"type":"brick"},{"x":1,"y":21,"type":"brick"},{"x":1,"y":20,"type":"brick"},{"x":1,"y":19,"type":"brick"},{"x":1,"y":18,"type":"brick"},{"x":1,"y":17,"type":"brick"},{"x":1,"y":16,"type":"brick"},{"x":1,"y":15,"type":"brick"},{"x":1,"y":14,"type":"brick"},{"x":1,"y":13,"type":"brick"},{"x":4,"y":22,"type":"brick"},{"x":7,"y":22,"type":"brick"},{"x":10,"y":22,"type":"brick"},{"x":13,"y":22,"type":"brick"},{"x":16,"y":22,"type":"brick"},{"x":19,"y":22,"type":"brick"},{"x":22,"y":22,"type":"brick"},{"x":25,"y":22,"type":"brick"},{"x":28,"y":22,"type":"brick"},{"x":31,"y":22,"type":"brick"},{"x":34,"y":22,"type":"brick"},{"x":37,"y":22,"type":"brick"},{"x":40,"y":22,"type":"brick"},{"x":43,"y":22,"type":"brick"},{"x":46,"y":22,"type":"brick"},{"x":46,"y":21,"type":"brick"},{"x":46,"y":20,"type":"brick"},{"x":46,"y":19,"type":"brick"},{"x":46,"y":18,"type":"brick"},{"x":46,"y":17,"type":"brick"},{"x":46,"y":16,"type":"brick"},{"x":46,"y":15,"type":"brick"},{"x":46,"y":14,"type":"brick"},{"x":46,"y":13,"type":"brick"},{"x":46,"y":12,"type":"brick"},{"x":46,"y":11,"type":"brick"},{"x":49,"y":15,"type":"brick"},{"x":52,"y":15,"type":"brick"},{"x":55,"y":15,"type":"brick"},{"x":58,"y":15,"type":"brick"},{"x":61,"y":15,"type":"brick"},{"x":64,"y":15,"type":"brick"},{"x":67,"y":15,"type":"brick"},{"x":70,"y":15,"type":"brick"},{"x":73,"y":15,"type":"brick"},{"x":73,"y":14,"type":"brick"},{"x":73,"y":13,"type":"brick"},{"x":73,"y":12,"type":"brick"},{"x":73,"y":11,"type":"brick"},{"x":73,"y":10,"type":"brick"},{"x":73,"y":9,"type":"brick"},{"x":73,"y":8,"type":"brick"},{"x":73,"y":7,"type":"brick"},{"x":73,"y":6,"type":"brick"},{"x":73,"y":5,"type":"brick"},{"x":73,"y":4,"type":"brick"},{"x":76,"y":4,"type":"brick"},{"x":46,"y":10,"type":"brick"},{"x":46,"y":9,"type":"brick"},{"x":46,"y":8,"type":"brick"},{"x":10,"y":21,"type":"brick"},{"x":13,"y":21,"type":"brick"},{"x":16,"y":21,"type":"brick"},{"x":19,"y":21,"type":"brick"},{"x":13,"y":20,"type":"brick"},{"x":16,"y":20,"type":"brick"},{"x":19,"y":20,"type":"brick"},{"x":22,"y":21,"type":"brick"},{"x":16,"y":19,"type":"brick"}],"hazards":[],"shooters":[],"lemons":5,"quotas":{"basher":10,"blocker":5,"bomber":10,"builder":5,"climber":20,"digger":5},"entrance":{"x":1,"y":10},"exit":{"x":76,"y":1}},{"name":"Bomber Blast","time":180,"author":"echicken","system":"electronic chicken bbs","date":1426656508,"blocks":[{"x":1,"y":22,"type":"metal"},{"x":4,"y":22,"type":"metal"},{"x":7,"y":22,"type":"metal"},{"x":10,"y":22,"type":"metal"},{"x":13,"y":22,"type":"metal"},{"x":16,"y":22,"type":"metal"},{"x":19,"y":22,"type":"metal"},{"x":22,"y":22,"type":"metal"},{"x":25,"y":22,"type":"metal"},{"x":28,"y":22,"type":"metal"},{"x":31,"y":22,"type":"metal"},{"x":34,"y":22,"type":"metal"},{"x":37,"y":22,"type":"metal"},{"x":40,"y":22,"type":"metal"},{"x":43,"y":22,"type":"metal"},{"x":46,"y":22,"type":"metal"},{"x":49,"y":22,"type":"metal"},{"x":52,"y":22,"type":"metal"},{"x":55,"y":22,"type":"metal"},{"x":58,"y":22,"type":"metal"},{"x":61,"y":22,"type":"metal"},{"x":64,"y":22,"type":"metal"},{"x":67,"y":22,"type":"metal"},{"x":70,"y":22,"type":"metal"},{"x":73,"y":22,"type":"metal"},{"x":76,"y":22,"type":"metal"},{"x":76,"y":21,"type":"metal"},{"x":76,"y":20,"type":"metal"},{"x":76,"y":19,"type":"metal"},{"x":76,"y":18,"type":"metal"},{"x":76,"y":17,"type":"metal"},{"x":1,"y":21,"type":"metal"},{"x":1,"y":20,"type":"metal"},{"x":1,"y":19,"type":"metal"},{"x":1,"y":18,"type":"metal"},{"x":1,"y":17,"type":"metal"},{"x":4,"y":21,"type":"metal"},{"x":7,"y":21,"type":"metal"},{"x":10,"y":21,"type":"metal"},{"x":13,"y":21,"type":"metal"},{"x":16,"y":21,"type":"metal"},{"x":19,"y":21,"type":"metal"},{"x":22,"y":21,"type":"metal"},{"x":25,"y":21,"type":"metal"},{"x":28,"y":21,"type":"metal"},{"x":31,"y":21,"type":"metal"},{"x":34,"y":21,"type":"metal"},{"x":37,"y":21,"type":"metal"},{"x":40,"y":21,"type":"metal"},{"x":43,"y":21,"type":"metal"},{"x":46,"y":21,"type":"metal"},{"x":49,"y":21,"type":"metal"},{"x":52,"y":21,"type":"metal"},{"x":55,"y":21,"type":"metal"},{"x":58,"y":21,"type":"metal"},{"x":61,"y":21,"type":"metal"},{"x":64,"y":21,"type":"metal"},{"x":67,"y":21,"type":"metal"},{"x":70,"y":21,"type":"metal"},{"x":73,"y":21,"type":"metal"},{"x":4,"y":20,"type":"metal"},{"x":7,"y":20,"type":"metal"},{"x":49,"y":20,"type":"metal"},{"x":52,"y":20,"type":"metal"},{"x":55,"y":20,"type":"metal"},{"x":70,"y":20,"type":"metal"},{"x":73,"y":20,"type":"metal"},{"x":55,"y":19,"type":"metal"},{"x":55,"y":18,"type":"metal"},{"x":55,"y":17,"type":"metal"},{"x":55,"y":16,"type":"metal"},{"x":55,"y":15,"type":"metal"},{"x":55,"y":14,"type":"metal"},{"x":46,"y":20,"type":"metal"},{"x":43,"y":20,"type":"metal"},{"x":40,"y":20,"type":"metal"},{"x":43,"y":19,"type":"metal"},{"x":46,"y":19,"type":"metal"},{"x":49,"y":19,"type":"metal"},{"x":52,"y":19,"type":"metal"},{"x":37,"y":20,"type":"metal"},{"x":55,"y":13,"type":"metal"},{"x":55,"y":12,"type":"metal"},{"x":52,"y":12,"type":"metal"},{"x":49,"y":12,"type":"metal"},{"x":34,"y":20,"type":"metal"},{"x":31,"y":20,"type":"metal"},{"x":37,"y":19,"type":"metal"},{"x":40,"y":19,"type":"metal"},{"x":43,"y":18,"type":"metal"},{"x":46,"y":18,"type":"metal"},{"x":49,"y":18,"type":"metal"},{"x":52,"y":18,"type":"metal"},{"x":46,"y":17,"type":"metal"},{"x":49,"y":17,"type":"metal"},{"x":52,"y":17,"type":"metal"},{"x":40,"y":18,"type":"metal"},{"x":34,"y":19,"type":"metal"},{"x":28,"y":20,"type":"metal"}],"hazards":[],"shooters":[],"lemons":7,"quotas":{"basher":7,"blocker":7,"bomber":7,"builder":7,"climber":7,"digger":7},"entrance":{"x":4,"y":17},"exit":{"x":73,"y":17}},{"name":"Builder Blowout","time":180,"author":"echicken","system":"electronic chicken bbs","date":1426657279,"blocks":[{"x":1,"y":4,"type":"brick"},{"x":4,"y":5,"type":"brick"},{"x":7,"y":6,"type":"brick"},{"x":10,"y":7,"type":"brick"},{"x":13,"y":8,"type":"brick"},{"x":13,"y":9,"type":"brick"},{"x":13,"y":10,"type":"brick"},{"x":13,"y":11,"type":"brick"},{"x":13,"y":12,"type":"brick"},{"x":13,"y":13,"type":"brick"},{"x":13,"y":14,"type":"brick"},{"x":13,"y":15,"type":"brick"},{"x":13,"y":16,"type":"brick"},{"x":13,"y":17,"type":"brick"},{"x":13,"y":18,"type":"brick"},{"x":13,"y":19,"type":"brick"},{"x":13,"y":20,"type":"brick"},{"x":13,"y":21,"type":"brick"},{"x":13,"y":22,"type":"brick"},{"x":16,"y":22,"type":"brick"},{"x":19,"y":22,"type":"brick"},{"x":22,"y":22,"type":"brick"},{"x":25,"y":22,"type":"brick"},{"x":28,"y":22,"type":"brick"},{"x":31,"y":22,"type":"brick"},{"x":34,"y":22,"type":"brick"},{"x":37,"y":22,"type":"brick"},{"x":40,"y":22,"type":"brick"},{"x":43,"y":22,"type":"brick"},{"x":46,"y":22,"type":"brick"},{"x":49,"y":22,"type":"brick"},{"x":52,"y":22,"type":"brick"},{"x":55,"y":22,"type":"brick"},{"x":58,"y":22,"type":"brick"},{"x":61,"y":22,"type":"brick"},{"x":64,"y":22,"type":"brick"},{"x":64,"y":21,"type":"brick"},{"x":64,"y":20,"type":"brick"},{"x":67,"y":20,"type":"brick"},{"x":70,"y":20,"type":"brick"},{"x":73,"y":20,"type":"brick"},{"x":76,"y":20,"type":"brick"},{"x":43,"y":21,"type":"brick"},{"x":43,"y":20,"type":"brick"}],"hazards":[],"shooters":[],"lemons":3,"quotas":{"basher":1,"blocker":6,"bomber":1,"builder":9,"climber":1,"digger":1},"entrance":{"x":1,"y":1},"exit":{"x":76,"y":17}}] \ No newline at end of file diff --git a/xtrn/lemons/menu.js b/xtrn/lemons/menu.js new file mode 100644 index 0000000000000000000000000000000000000000..54b16087aabe563c3adf8f142f9a807156045e0c --- /dev/null +++ b/xtrn/lemons/menu.js @@ -0,0 +1,65 @@ +// The Menu object displays the Lemons splash screen and a small Tree menu. +var Menu = function() { + + var frames = {}; + + frames.splash = new Frame( + frame.x, + frame.y, + frame.width, + frame.height, + BG_BLACK|WHITE, + frame + ); + + frames.treeFrame = new Frame( + frames.splash.x + 50, + frames.splash.y + 10, + 20, + 6, + BG_BLACK|WHITE, + frames.splash + ); + + frames.treeSubFrame = new Frame( + frames.treeFrame.x + 1, + frames.treeFrame.y + 1, + frames.treeFrame.width - 2, + frames.treeFrame.height - 2, + BG_BLACK|WHITE, + frames.treeFrame + ); + + frames.splash.open(); + frames.splash.load(js.exec_dir + "lemons.bin", 80, 24) + frames.splash.top(); + frames.treeFrame.drawBorder([CYAN, LIGHTCYAN, WHITE]); + frames.treeFrame.open(); + + var changeState = function(s) { + state = s; + } + + var tree = new Tree(frames.treeSubFrame); + tree.colors.fg = LIGHTGRAY; + tree.colors.bg = BG_BLACK; + tree.colors.lfg = WHITE; + tree.colors.lbg = BG_BLUE; + tree.colors.kfg = LIGHTCYAN; + tree.addItem("|Play", changeState, STATE_PLAY); + tree.addItem("|Help", changeState, STATE_HELP); + tree.addItem("|Scores", changeState, STATE_SCORES); + tree.addItem("|Quit", changeState, STATE_EXIT); + tree.open(); + + this.getcmd = function(userInput) { + tree.getcmd(userInput); + } + + this.close = function() { + tree.close(); + frames.treeFrame.delete(); + frames.splash.delete(); + } + +} \ No newline at end of file diff --git a/xtrn/lemons/readme.txt b/xtrn/lemons/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..3833b69d0d64227f71cc5723dcf7ab66fd55ccda --- /dev/null +++ b/xtrn/lemons/readme.txt @@ -0,0 +1,124 @@ +Lemons +------ +A game for Synchronet BBS 3.16+ +by echicken -at- bbs.electronicchicken.com, March 2015 + + +Contents +-------- + +1) About +2) Requirements +3) Installation + 3.1) Connect to the networked scoreboard + 3.2) Host your own scoreboard +4) Support + + +1) About +-------- + +Sometimes this is how games are created: + +<MegaloYeti> someone needs to make a new lemmings game +<mcmlxxix> bbs lemmings? +<mcmlxxix> hmmm +<echicken> mhm +<echicken> little @ signs walking off of cliffs +<echicken> or wee lemming sprites +<echicken> actually that would be fairly easy +<echicken> claimed +<mcmlxxix> :| + +Okay, so it's "Lemons" and it's pared down somewhat, but the basic premise is +the same. Just try it, read the help screen, and you'll get the idea. + + +2) Requirements +--------------- + +This game may run on Synchronet 3.15, but has not been tested with anything +less than 3.16. + +Ensure that you have the latest copies of the following files in your exec/load/ +directory. You can grab them one by one, or do a CVS update. (If you choose +to update everything, backing up your BBS is a good idea.) + +- frame.js: + http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/exec/load/frame.js +- tree.js: + http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/exec/load/tree.js +- event-timer.js: + http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/exec/load/event-timer.js +- json-client.js: + http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/exec/load/json-client.js +- sprite.js: + http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/exec/load/sprite.js + + +3) Installation +--------------- + +In 'scfg' (that's BBS->Configure from the Synchronet Control Panel in Windows), +go to "External Programs -> Online Programs (Doors)" and select the area you +wish to add this game to. Create a new entry, and set it up as follows: + +Name: Lemons +Internal Code: LEMONS +Start-up Directory: ../xtrn/lemons/ +Command Line: ?lemons.js +Multiple Concurrent Users: Yes + +(All other options can be left at their default values.) + + 3.1) Connect to the networked scoreboard + ---------------------------------------- + + You're welcome to connect your local game to the networked scoreboard that + I host. To do so, Create a file called 'server.ini' in the Lemons game + directory, and put the following two lines in it: + + host = bbs.electronicchicken.com + port = 10088 + + + 3.2) Host your own scoreboard + ----------------------------- + + If you prefer not to connect to my scoreboard, you will need to set up your + own or the game will not work. To do so, ensure that the JSON service is + enabled via the following entry in your 'ctrl/services.ini' file: + + [JSON] + Port=10088 + Options=STATIC | LOOP + Command=json-service.js + + If you've just added the above to your services.ini file, you may need to + restart your services (just restart your BBS if you don't know how) in + order for the change to take effect. + + You will also need to add the following to your 'ctrl/json-service.ini' + file (create one if it doesn't already exist): + + [lemons] + dir=../xtrn/lemons/ + + In the absence of a 'server.ini' file in its directory, Lemons will attempt + to connect to a JSON-DB server at 127.0.0.1 on port 10088. If your JSON + server binds to another port or address, create a 'server.ini' file with + the appropriate values. + + You can allow other systems to connect to your scoreboard by opening your + JSON-DB server's port to them. They will need to create or modify their + 'server.ini' files accordingly. + + +4) Support +---------- + +There are three easy ways to reach me: + +- Post a message to echicken in the Synchronet Sysops sub on DOVE-Net +- Find me in #synchronet on irc.synchro.net +- Contact me on my BBS at bbs.electronicchicken.com \ No newline at end of file diff --git a/xtrn/lemons/scoreboard.js b/xtrn/lemons/scoreboard.js new file mode 100644 index 0000000000000000000000000000000000000000..b025f4f3692f5d525364082b059a61975215a522 --- /dev/null +++ b/xtrn/lemons/scoreboard.js @@ -0,0 +1,127 @@ +// An interface to the Lemons JSON DB. Slightly more than a scoreboard. +var ScoreBoard = function(host, port) { + + var frames = {}; + var jsonClient = new JSONClient(host, port); + + /* Add a score to the database. Impress nobody by faking an awesome + score in Lemons! */ + this.addScore = function(score, level) { + + if(!jsonClient.connected) + jsonClient.connect(host, port); + + var entry = { + 'player' : user.alias, + 'system' : system.name, + 'level' : level, + 'score' : score + }; + + jsonClient.write(DBNAME, "SCORES.LATEST", entry, 2); + + } + + // Returns the high scores array (20 most recent scores.) + this.getScores = function() { + + if(!jsonClient.connected) + jsonClient.connect(host, port); + + var scores = jsonClient.read(DBNAME, "SCORES.HIGH", 1); + + if(!scores) + scores = []; + + return scores; + + } + + /* Returns the number of the highest level the current user has reached. + (Numbering starts at zero; returns zero if this is a new player or a + an empty database.) */ + this.getHighestLevel = function() { + + if(!jsonClient.connected) + jsonClient.connect(host, port); + + var player = jsonClient.read( + DBNAME, + "PLAYERS." + base64_encode(user.alias + "@" + system.name), + 1 + ); + + if(!player) + return 0; + + return player.LEVEL; + + } + + // Display the scoreboard. + this.open = function() { + + if(!jsonClient.connected) + jsonClient.connect(host, port); + + frames.scoreFrame = new Frame( + frame.x, + frame.y, + frame.width, + frame.height, + BG_BLACK|WHITE, + frame + ); + + frames.scoreSubFrame = new Frame( + frames.scoreFrame.x + 1, + frames.scoreFrame.y + 1, + frames.scoreFrame.width - 2, + frames.scoreFrame.height -2, + BG_BLACK|WHITE, + frames.scoreFrame + ); + + var putLine = function(player, system, level, score) { + frames.scoreSubFrame.putmsg( + format( + "%-25s %-30s %5s %14s\r\n", + player, system, level, score + ) + ); + } + + frames.scoreFrame.drawBorder([CYAN, LIGHTCYAN, WHITE]); + frames.scoreFrame.home(); + frames.scoreFrame.center(ascii(180) + "Lemons - High Scores" + ascii(195)); + + frames.scoreSubFrame.gotoxy(1, 2); + frames.scoreSubFrame.attr = BG_BLACK|LIGHTCYAN; + putLine("Player", "System", "Level", "Score"); + frames.scoreSubFrame.attr = BG_BLACK|WHITE; + + var scores = this.getScores(); + for(var s = 0; s < scores.length; s++) { + putLine( + scores[s].player, + scores[s].system, + (scores[s].level + 1), + scores[s].score + ); + } + + frames.scoreFrame.end(); + frames.scoreFrame.center(ascii(180) + "Press any key to continue" + ascii(195)); + + frames.scoreFrame.open(); + + } + + // Close the scoreboard if it's open, disconnect the JSON-DB client. + this.close = function() { + if(typeof frames.scoreFrame != "undefined") + frames.scoreFrame.delete(); + jsonClient.disconnect(); + } + +} \ No newline at end of file diff --git a/xtrn/lemons/service.js b/xtrn/lemons/service.js new file mode 100644 index 0000000000000000000000000000000000000000..7fdf047c82f8d9daa4dc95c419ce5ba47babdef9 --- /dev/null +++ b/xtrn/lemons/service.js @@ -0,0 +1,213 @@ +/* Lemons JSON DB module service script + This is loaded automatically by the JSON service, and runs in the + background. */ + +// Loop forever +js.branch_limit = 0; +js.time_limit = 0; + +// The JSON service passes the module's working directory as argv[0] +var root = argv[0]; + +load("sbbsdefs.js"); +load("json-client.js"); +load(root + "defs.js"); + +// We'll need a JSON client handle in various functions within this script +var jsonClient; + +// This will be called when we receive an update to a valid location +var updateScores = function(data) { + + // Make sure that the data provided is sane + if( typeof data.player != "string" + || + typeof data.system != "string" + || + typeof data.level != "number" + || + typeof data.score != "number" + ) { + return; + } + + // Make a unique ID / valid property name for the player + data.uid = base64_encode(data.player + "@" + data.system, true); + data.date = time(); + + // Grab the current high scores list + var hs = jsonClient.read(DBNAME, "SCORES.HIGH", 1); + var changed = false; // We'll toggle this if we need to overwrite the list + + /* If this player's score is higher than any score in the list, shove + it into the list before that score. */ + for(var s = 0; s < hs.length; s++) { + if(data.score < hs[s].score) + continue; + hs.splice(s, 0, data); + changed = true; + break; + } + + /* If this player's score is the lowest in the list, but the list isn't + fully populated, tack this record onto the end of the list. */ + if(!changed && hs.length < 20) { + hs.push(data); + changed = true; + } + + // If we flagged a DB update, then update the DB. + if(changed) { + while(hs.length > 20) + hs.pop(); + jsonClient.write(DBNAME, "SCORES.HIGH", hs, 2); + } + + // In any event, we want to update this player's record + updatePlayer(data); + +} + +// This will be called via updateScores +var updatePlayer = function(data) { + + // Read the player's record from the DB + var player = jsonClient.read(DBNAME, "PLAYERS." + data.uid, 1); + + // Or populate said record if it doesn't exist + if(!player) { + jsonClient.write( + DBNAME, + "PLAYERS." + data.uid, + { 'LEVEL' : data.level, 'SCORES' : [] }, + 2 + ); + /* If the player has reached a new level, update their level number so + they can start there next time. */ + } else if(player.LEVEL < data.level) { + jsonClient.write( + DBNAME, + "PLAYERS." + data.uid + ".LEVEL", + data.level, + 2 + ); + } + + /* Tack this score onto the player's 'scores' array. We're not doing + anything with this data at the moment, but we could. */ + jsonClient.push( + DBNAME, + "PLAYERS." + data.uid + ".SCORES", + { 'level' : data.level, 'score' : data.score, 'date' : data.date }, + 2 + ); + +} + +/* We'll set this as the JSON client's callback function, and it will be + called when an update to a subscribed location is received. */ +var processUpdate = function(update) { + + // We're not interested in any updates that aren't writes + if(update.oper != "WRITE") + return; + + // Additionally, the update must be to a *.LATEST location + var location = update.location.split("."); + if(location.length != "2" || location[1] != "LATEST") + return; + + switch(location[0].toUpperCase()) { + + // We're only subscribing to SCORES.LATEST right now + case "SCORES": + updateScores(update.data); + break; + + // But maybe we'll want to watch this in the future + case "PLAYERS": + updatePlayer(update.data); + break; + + default: + break; + + } + +} + +// Set things up +var init = function() { + + // Load the server config if it exists, or fake it if not + if(file_exists(root + "server.ini")) { + var f = new File(root + "server.ini"); + f.open("r"); + var ini = f.iniGetObject(); + f.close(); + ini.port = parseInt(ini.port); + } else { + var ini = { 'host' : "127.0.0.1", 'port' : 10088 }; + } + + // Create our JSON client handle (declared outside this function) + jsonClient = new JSONClient(ini.host, ini.port); + + // Authenticate to the service as this board's sysop + var usr = new User(1); + jsonClient.ident("ADMIN", usr.alias, usr.security.password); + + // Subscribe to updates to SCORES.LATEST + jsonClient.subscribe(DBNAME, "SCORES.LATEST"); + + // If SCORES doesn't exist, create it with dummy data + if(!jsonClient.read(DBNAME, "SCORES", 1)) { + jsonClient.write( + DBNAME, + "SCORES", + { 'LATEST' : {}, 'HIGH' : [] }, + 2 + ); + } + + // If PLAYERS doesn't exist, create it with dummy data + if(!jsonClient.read(DBNAME, "PLAYERS", 1)) { + jsonClient.write( + DBNAME, + "PLAYERS", + { 'LATEST' : {} }, + 2 + ); + } + + // Call processUpdate when an update is received + jsonClient.callback = processUpdate; + +} + +// Keep things rolling until we're told to stop +var main = function() { + + while(!js.terminated) { + mswait(5); + jsonClient.cycle(); + } + +} + +// Clean things up +var cleanUp = function() { + jsonClient.disconnect(); +} + +// Try to do things, log an error if necessary +try { + init(); + main(); + cleanUp(); +} catch(err) { + log(LOG_ERR, err); +} + +// This is implied, but hey, why not? +exit(); \ No newline at end of file diff --git a/xtrn/lemons/sprites/brick.bin b/xtrn/lemons/sprites/brick.bin new file mode 100644 index 0000000000000000000000000000000000000000..1b8ddc7e9997ac014ddbc63f3c859f9258fb6875 --- /dev/null +++ b/xtrn/lemons/sprites/brick.bin @@ -0,0 +1 @@ +�d�d� \ No newline at end of file diff --git a/xtrn/lemons/sprites/brick.ini b/xtrn/lemons/sprites/brick.ini new file mode 100644 index 0000000000000000000000000000000000000000..d1cc7957ec46e7c5cd81fc9aa9e23e31abdc7ff9 --- /dev/null +++ b/xtrn/lemons/sprites/brick.ini @@ -0,0 +1,9 @@ +width = 3 +height = 1 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = block +material = brick diff --git a/xtrn/lemons/sprites/entrance.bin b/xtrn/lemons/sprites/entrance.bin new file mode 100644 index 0000000000000000000000000000000000000000..3a04f95cce67a7572c6801bbcb78c0f9e9b24523 --- /dev/null +++ b/xtrn/lemons/sprites/entrance.bin @@ -0,0 +1 @@ +��������� \ No newline at end of file diff --git a/xtrn/lemons/sprites/entrance.ini b/xtrn/lemons/sprites/entrance.ini new file mode 100644 index 0000000000000000000000000000000000000000..2938a998e5e23b82e2d20208883e72de4dd1d89e --- /dev/null +++ b/xtrn/lemons/sprites/entrance.ini @@ -0,0 +1,8 @@ +width = 3 +height = 3 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = entrance diff --git a/xtrn/lemons/sprites/exit.bin b/xtrn/lemons/sprites/exit.bin new file mode 100644 index 0000000000000000000000000000000000000000..4552d21f369806faef0b9ba8d89e6cfef19f9e2c --- /dev/null +++ b/xtrn/lemons/sprites/exit.bin @@ -0,0 +1 @@ +��+����.����� \ No newline at end of file diff --git a/xtrn/lemons/sprites/exit.ini b/xtrn/lemons/sprites/exit.ini new file mode 100644 index 0000000000000000000000000000000000000000..44c1e3e6af949e65df779a595aa8033c6239b533 --- /dev/null +++ b/xtrn/lemons/sprites/exit.ini @@ -0,0 +1,8 @@ +width = 3 +height = 3 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = exit diff --git a/xtrn/lemons/sprites/lava.bin b/xtrn/lemons/sprites/lava.bin new file mode 100644 index 0000000000000000000000000000000000000000..5d2b391c874986bd6901bfaca0516af8e2d4d0e4 --- /dev/null +++ b/xtrn/lemons/sprites/lava.bin @@ -0,0 +1 @@ +�L�L�L��L�L�x�x�x�x�x�x \ No newline at end of file diff --git a/xtrn/lemons/sprites/lava.ini b/xtrn/lemons/sprites/lava.ini new file mode 100644 index 0000000000000000000000000000000000000000..7511ab737b6fcb00cf8e3f1f98d947f20d9c4187 --- /dev/null +++ b/xtrn/lemons/sprites/lava.ini @@ -0,0 +1,8 @@ +width = 6 +height = 2 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = hazard diff --git a/xtrn/lemons/sprites/lemon.bin b/xtrn/lemons/sprites/lemon.bin new file mode 100644 index 0000000000000000000000000000000000000000..b5000d011c9d8095297a437311c4688d3646f2ad --- /dev/null +++ b/xtrn/lemons/sprites/lemon.bin @@ -0,0 +1 @@ +��.���.���.���������������O� �n� �n���������.���.���.���������������O���n ��n ������ \ No newline at end of file diff --git a/xtrn/lemons/sprites/lemon.ini b/xtrn/lemons/sprites/lemon.ini new file mode 100644 index 0000000000000000000000000000000000000000..52925ec5244fbff0378fe15d8d6e74b64422f6a5 --- /dev/null +++ b/xtrn/lemons/sprites/lemon.ini @@ -0,0 +1,8 @@ +width = 3 +height = 3 +bearings = e,w +positions = normal,normal2,fall,nuked +constantmotion = 1 +speed = .25 +gravity = 1 +type = lemon diff --git a/xtrn/lemons/sprites/metal.bin b/xtrn/lemons/sprites/metal.bin new file mode 100644 index 0000000000000000000000000000000000000000..141d914b7d2c373aff478250e251b46dbc519cf8 --- /dev/null +++ b/xtrn/lemons/sprites/metal.bin @@ -0,0 +1 @@ +�x�x�x�x \ No newline at end of file diff --git a/xtrn/lemons/sprites/metal.ini b/xtrn/lemons/sprites/metal.ini new file mode 100644 index 0000000000000000000000000000000000000000..b8248e00b8b9f49b50b35ab0d1c7a1f657977742 --- /dev/null +++ b/xtrn/lemons/sprites/metal.ini @@ -0,0 +1,9 @@ +width = 3 +height = 1 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = block +material = metal diff --git a/xtrn/lemons/sprites/shooter-e.bin b/xtrn/lemons/sprites/shooter-e.bin new file mode 100644 index 0000000000000000000000000000000000000000..c16e39f2eef57bbbec0fd230fd2af6de36283640 --- /dev/null +++ b/xtrn/lemons/sprites/shooter-e.bin @@ -0,0 +1 @@ +��������� \ No newline at end of file diff --git a/xtrn/lemons/sprites/shooter-e.ini b/xtrn/lemons/sprites/shooter-e.ini new file mode 100644 index 0000000000000000000000000000000000000000..1ba2042d905b0469dd482cc1a2fcd1d0694e8e80 --- /dev/null +++ b/xtrn/lemons/sprites/shooter-e.ini @@ -0,0 +1,10 @@ +width = 3 +height = 3 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = shooter +weapon = shot +attackspeed = 5 diff --git a/xtrn/lemons/sprites/shooter-w.bin b/xtrn/lemons/sprites/shooter-w.bin new file mode 100644 index 0000000000000000000000000000000000000000..8dcc0adf734d1bfd1f8b9227b4eab8877cd7d014 --- /dev/null +++ b/xtrn/lemons/sprites/shooter-w.bin @@ -0,0 +1 @@ +��������� \ No newline at end of file diff --git a/xtrn/lemons/sprites/shooter-w.ini b/xtrn/lemons/sprites/shooter-w.ini new file mode 100644 index 0000000000000000000000000000000000000000..96cc87479b29d280f86c6ae8450b67bf400e1e20 --- /dev/null +++ b/xtrn/lemons/sprites/shooter-w.ini @@ -0,0 +1,10 @@ +width = 3 +height = 3 +bearings = w +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = shooter +weapon = shot +attackspeed = 5 diff --git a/xtrn/lemons/sprites/shot.bin b/xtrn/lemons/sprites/shot.bin new file mode 100644 index 0000000000000000000000000000000000000000..3e3ec673c02ac9892170b3eb5285aa1f85be789d --- /dev/null +++ b/xtrn/lemons/sprites/shot.bin @@ -0,0 +1 @@ +��L���L���L���L� \ No newline at end of file diff --git a/xtrn/lemons/sprites/shot.ini b/xtrn/lemons/sprites/shot.ini new file mode 100644 index 0000000000000000000000000000000000000000..23002b990d1fa299f01458c8aa5f1288af3aadec --- /dev/null +++ b/xtrn/lemons/sprites/shot.ini @@ -0,0 +1,9 @@ +width = 3 +height = 2 +bearings = e,w +positions = normal +constantmotion = 1 +speed = .15 +gravity = 0 +type = projectile +range = 15 diff --git a/xtrn/lemons/sprites/slime.bin b/xtrn/lemons/sprites/slime.bin new file mode 100644 index 0000000000000000000000000000000000000000..329fac618319d112368157341a67f8971168e866 --- /dev/null +++ b/xtrn/lemons/sprites/slime.bin @@ -0,0 +1 @@ +�*�*�*�*�*�*�x�x�x�x�x�x \ No newline at end of file diff --git a/xtrn/lemons/sprites/slime.ini b/xtrn/lemons/sprites/slime.ini new file mode 100644 index 0000000000000000000000000000000000000000..7511ab737b6fcb00cf8e3f1f98d947f20d9c4187 --- /dev/null +++ b/xtrn/lemons/sprites/slime.ini @@ -0,0 +1,8 @@ +width = 6 +height = 2 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = hazard diff --git a/xtrn/lemons/sprites/water.bin b/xtrn/lemons/sprites/water.bin new file mode 100644 index 0000000000000000000000000000000000000000..54bbeffc853d4498740903c74634b31a4733316a --- /dev/null +++ b/xtrn/lemons/sprites/water.bin @@ -0,0 +1 @@ +� � �� �� �x�x�x�x�x�x \ No newline at end of file diff --git a/xtrn/lemons/sprites/water.ini b/xtrn/lemons/sprites/water.ini new file mode 100644 index 0000000000000000000000000000000000000000..7511ab737b6fcb00cf8e3f1f98d947f20d9c4187 --- /dev/null +++ b/xtrn/lemons/sprites/water.ini @@ -0,0 +1,8 @@ +width = 6 +height = 2 +bearings = e +positions = normal +constantmotion = 0 +speed = 0 +gravity = 0 +type = hazard