UNPKG

hobgoblin

Version:

A framework for writing roguelike games in JavaScript, implementing ROT.js

349 lines (317 loc) 14.1 kB
Game.Screen = {}; Game.Screen.basicScreen = function(properties) { var requiredMethods = [ 'enter', 'exit', 'render', 'handleInput' ]; // Make sure they have the required methods... for(var i = 0; i < requiredMethods.length; i++) { var method = requiredMethods[i]; if(properties[method] === undefined) throw new Error("'" + method + "' is a missing from your properties list and is required for this type of screen"); } // Set properties for the screen if(properties) { for(var p in properties) { if(!this[p]) { this[p] = properties[p]; } } } }; // Item Listing Game.Screen.ItemListScreen = function(template) { // Set up based on the template this._caption = template['caption']; this._okFunction = template['ok']; // By default, we use the identity function this._isAcceptableFunction = template['isAcceptable'] || function(x) { return x; }; // Can the user select items at all? this._canSelectItem = template['canSelect']; // Can they select multiple items? this._canSelectMultipleItems = template['canSelectMultipleItems']; // Whether a 'no item' option should appear. this._hasNoItemOption = template['hasNoItemOption']; }; Game.Screen.ItemListScreen.prototype.setup = function(player, items) { this._player = player; // Should be called before switching to the screen. var count = 0; // Iterate over each item, keeping only the aceptable ones and counting the number of acceptable items. var that = this; this._items = items.map(function(item) { // Transform the item into null if it's not acceptable if (that._isAcceptableFunction(item)) { count++; return item; } else { return null; } }); // Clean set of selected indices this._selectedIndices = {}; return count; }; Game.Screen.ItemListScreen.prototype.render = function(display) { var letters = 'abcdefghijklmnopqrstuvwxyz'; // Render the no item row if enabled if (this._hasNoItemOption) { display.drawText(0, 1, '0 - no item'); } // Render the caption in the top row display.drawText(0, 0, this._caption); var row = 0; for(var i = 0; i < this._items.length; i++) { // If we have an item, we want to render it if(this._items[i]) { // Get the letter corresponding to the item's index var letter = letters.substring(i, i + 1); // If the item is selected, show a +, otherwise show a dash, then the item's name var selectionState = (this._canSelectItem && this._canSelectMultipleItems && this._selectedIndices[i]) ? '+' : '-'; // If the item is stackable, show the number we are currently holding var stack = this._items[i].hasMixin('Stackable') ? ' (' + this._items[i].amount() + ')' : ''; // Render at the correct row and add 2 display.drawText(0, 2 + row, letter + ' ' + selectionState + ' ' + this._items[i].describe() + stack); row++; } } }; Game.Screen.ItemListScreen.prototype.executeOkFunction = function() { // Gather the selected items. var selectedItems = {}; for (var key in this._selectedIndices) { selectedItems[key] = this._items[key]; } // Switch back to play screen Game.Screen.playScreen.setSubScreen(undefined); // Return the result of the okFunction return this._okFunction ? this._okFunction(selectedItems) : false; }; Game.Screen.ItemListScreen.prototype.handleInput = function(inputType, inputData) { var command = Game.Input.handleInput("ItemListScreen", inputType, inputData); // Execute the command, and caputure return value var unlock = command ? command() : false; // If the return value is true, unlock the engine (player turn over) if(unlock) this._player.getMap().getEngine().unlock(); else Game.refresh(); }; // Targeting Screen Game.Screen.TargetBasedScreen = function(template) { template = template || {}; // By default, our ok return does nothing and does not consume a turn. this._okFunction = template['okFunction'] || function(x, y) { return false; }; // The default caption function returns a description of the tiles or creatures. this._captionFunction = template['captionFunction'] || function(x, y) { var z = this._player.getZ(); var map = this._player.getMap(); // If the tile is explored, we can give a better caption if (map.isExplored(x, y, z)) { // If the tile isn't explored, we have to check if we can actually // see it before testing if there's an entity or item. if (this._visibleCells[x + ',' + y]) { var items = map.getItemsAt(x, y, z); // If we have items, we want to render the top most item if (items) { var item = items[items.length - 1]; return String.format('%s - %s (%s)', item.getRepresentation(), item.describeA(true), item.details()); // Else check if there's an entity } else if (map.getEntityAt(x, y, z)) { var entity = map.getEntityAt(x, y, z); return String.format('%s - %s (%s)', entity.getRepresentation(), entity.describeA(true), entity.details()); } } // If there was no entity/item or the tile wasn't visible, then use // the tile information. return String.format('%s - %s', map.getTile(x, y, z).getRepresentation(), map.getTile(x, y, z).getDescription()); } else { var nullTile = Game.TileRepository.create('null'); // If the tile is not explored, show the null tile description. return String.format('%s - %s', nullTile.getRepresentation(), nullTile.getDescription()); } }; }; Game.Screen.TargetBasedScreen.prototype.setup = function(player, startX, startY, offsetX, offsetY) { this._player = player; // Store original position. Subtract the offset so we don't always have to remove it. this._startX = startX - offsetX; this._startY = startY - offsetY; // Store current cursor position this._cursorX = this._startX; this._cursorY = this._startY; // Store map offsets this._offsetX = offsetX; this._offsetY = offsetY; // Cache the FOV var visibleCells = {}; this._player.getMap().getFov(this._player.getZ()).compute( this._player.getX(), this._player.getY(), this._player.getSightRadius(), function(x, y, radius, visibility) { visibleCells[x + "," + y] = true; }); this._visibleCells = visibleCells; }; Game.Screen.TargetBasedScreen.prototype.render = function(display) { Game.Screen.playScreen.renderTiles.call(Game.Screen.playScreen, display); // Draw a line from the start to the cursor. var points = Game.Geometry.getLine(this._startX, this._startY, this._cursorX, this._cursorY); // Render stars along the line. for (var i = 1, l = points.length; i < l; i++) { if(i == l - 1) { display.drawText(points[i].x, points[i].y, '%c{white}X'); } else { display.drawText(points[i].x, points[i].y, '%c{white}*'); } } // Render the caption at the bottom. display.drawText(0, Game.getScreenHeight() - 1, this._captionFunction(this._cursorX + this._offsetX, this._cursorY + this._offsetY)); }; Game.Screen.TargetBasedScreen.prototype.handleInput = function(inputType, inputData) { var command = Game.Input.handleInput("TargetBasedScreen", inputType, inputData); var unlock = command ? command() : false; // If the return value is true, unlock the engine (player turn over) if(unlock) this._player.getMap().getEngine().unlock(); else Game.refresh(); }; Game.Screen.TargetBasedScreen.prototype.moveCursor = function(dx, dy) { // Make sure we stay within bounds. this._cursorX = Math.max(0, Math.min(this._cursorX + dx, Game.getScreenWidth())); // We have to save the last line for the caption. this._cursorY = Math.max(0, Math.min(this._cursorY + dy, Game.getScreenHeight() - 1)); }; Game.Screen.TargetBasedScreen.prototype.executeOkFunction = function() { if(this._okFunction) return this._okFunction(this._cursorX + this._offsetX, this._cursorY + this._offsetY); else return false; }; // Menu screens Game.Screen.MenuScreen = function(template) { template = template || {}; this._player = null; // Display settings this._caption = template['caption'] || 'Menu'; this._outerPadding = template['outerPadding'] || 4; this._innerPadding = template['innerPadding'] || 2; this._width = template['width'] || Game.getScreenWidth() - this._outerPadding; this._height = template['height'] || Game.getScreenHeight() - this._outerPadding; this._textWidth = this._width - this._innerPadding; this._verticalChar = template['verticalChar'] || '|'; this._horizontalChar = template['horizontalChar'] || '-'; this._cornerChar = template['cornerChar'] || '+'; this._highlightColor = template['highlightColor'] || Game.Palette.blue; // Menu item settings this._currentIndex = template['currentIndex'] || 0; this._menuItems = template['menuItems'] || []; this._menuActions = template['menuActions'] || []; this._buildMenuItems = template['buildMenuItems'] || function() { // The the value of each menu item should be an array of arrays, where the first value of each sub array is a function reference, and the second value is an array of parameters, such that the menu action can be called via menuAction[i][0].apply(this, menuAction[i][1]). This data structure allows for as many function calls with as many arguments to be called sequentially by a single menu action. var exampleMenuItem = { 'Example 1': [[console.log, ['This is an example', ', and another.']], [console.log, ['And another!']]], 'Example 2': [[console.log, ['This is another example', ', and another.']], [console.log, ['And another!!']]] }; for(var item in exampleMenuItem) { this._menuItems.push(item); this._menuActions.push(exampleMenuItem[item]); } }; this._okFunction = template['ok'] || function() { var menuActions = this._menuActions[this._currentIndex]; for (var i = 0; i < menuActions.length; i++) { if(menuActions[i].length !== 2 && menuActions[i].length !== 3) throw new Error('Incorrectly formatted action type:', menuActions[i]); var actionFunc = menuActions[i][0], actionArgs = menuActions[i][1], actionContext = (menuActions[i].length === 3) ? menuActions[i][2] : actionFunc; actionFunc.apply(actionContext, actionArgs); } return true; }; }; Game.Screen.MenuScreen.prototype.setup = function(player, builderArgs) { this._player = player; this._currentIndex = 0; // reset current index to 0 this._menuItems = []; // clear out old menu items; this._menuActions = []; // clear out old menu items; this._buildMenuItems.apply(this, builderArgs); }; Game.Screen.MenuScreen.prototype.render = function(display) { var startX = this._outerPadding, startY = this._outerPadding; // Draw caption display.drawText( Math.round(this._width / 2) - Math.round(this._caption.length / 2), startY - 1, '%c{' + Game.Palette.blue + '}' + this._caption + '%c{}' ); // Draw menu box for (var row = 0; row < this._height; row++) { if(row === 0 || row === this._height - 1) { display.drawText( startX, startY + row, this._cornerChar.rpad(this._horizontalChar, this._width - 2) + this._cornerChar, this._width ); } else { display.drawText( startX, startY + row, this._verticalChar.rpad(" ", this._width - 2) + this._verticalChar, this._width ); } } // Draw menu items for (var item = 0; item < this._menuItems.length; item++) { var highlight; if(item === this._currentIndex) highlight = '%b{' + this._highlightColor + '}'; else highlight = '%b{}'; display.drawText( startX + this._innerPadding, startY + this._innerPadding + item, highlight + this._menuItems[item] ); } }; Game.Screen.MenuScreen.prototype.handleInput = function(inputType, inputData) { var command = Game.Input.handleInput("MenuScreen", inputType, inputData); var unlock = command ? command() : false; // If the return value is true, unlock the engine (player turn over) if(unlock) this._player.getMap().getEngine().unlock(); else Game.refresh(); }; Game.Screen.MenuScreen.prototype.executeOkFunction = function() { if(this._okFunction) return this._okFunction(); else return false; }; Game.Screen.MenuScreen.prototype.moveMenuIndex = function(amount) { this._currentIndex += amount; }