lychees's picture
Upload 569 files
87b3b3a
function Map(display, __game) {
/* private variables */
var __player;
var __grid;
var __dynamicObjects = [];
var __objectDefinitions;
var __lines;
var __dom;
var __domCSS = '';
var __allowOverwrite;
var __keyDelay;
var __refreshRate;
var __intervals = [];
var __chapterHideTimeout;
/* unexposed variables */
this._properties = {};
this._display = display;
this._dummy = false; // overridden by dummyMap in validate.js
this._status = '';
/* wrapper */
function wrapExposedMethod(f, map) {
return function () {
var args = arguments;
return __game._callUnexposedMethod(function () {
return f.apply(map, args);
});
};
};
/* unexposed getters */
this._getObjectDefinition = function(objName) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getObjectDefinition()';}
return __objectDefinitions[objName];
};
this._getObjectDefinitions = function() {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getObjectDefinitions()';}
return __objectDefinitions;
};
this._getGrid = function () {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getGrid()';}
return __grid;
};
this._getLines = function() {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._getLines()';}
return __lines;
};
/* exposed getters */
this.getDynamicObjects = function () {
// copy dynamic object list to fix issue#166
var copy = [];
for (var i = 0; i < __dynamicObjects.length; i++) {
copy[i] = __dynamicObjects[i];
}
return copy;
};
this.getPlayer = function () { return __player; };
this.getWidth = function () { return __game._dimensions.width; };
this.getHeight = function () { return __game._dimensions.height; };
/* unexposed methods */
this._reset = function () {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._reset()';}
__objectDefinitions = __game.getListOfObjects();
this._display.clear();
__grid = new Array(__game._dimensions.width);
for (var x = 0; x < __game._dimensions.width; x++) {
__grid[x] = new Array(__game._dimensions.height);
for (var y = 0; y < __game._dimensions.height; y++) {
__grid[x][y] = {type: 'empty'};
}
}
this.getDynamicObjects().forEach(function (obj) {
obj._destroy(true);
});
__dynamicObjects = [];
__player = null;
this._clearIntervals();
__lines = [];
__dom = '';
this._overrideKeys = {};
// preload stylesheet for DOM level
$.get('styles/dom.css', function (css) {
__domCSS = css;
});
this.finalLevel = false;
this._callbackValidationFailed = false;
};
this._clearIntervals = function() {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._clearIntervals()';}
for (var i = 0; i < __intervals.length; i++) {
clearInterval(__intervals[i]);
}
__intervals = [];
};
this._ready = function () {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._ready()';}
// set refresh rate if one is specified
if (__refreshRate) {
// wrapExposedMethod is necessary here to prevent the `_moveToNextLevel`
// call from breaking
this.startTimer(wrapExposedMethod(function () {
// refresh the map
this.refresh();
// check for nonstandard victory condition
__game._checkObjective()
}, this), __refreshRate);
}
};
this._setProperties = function (mapProperties) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._setProperties()';}
// set defaults
this._properties = {};
__allowOverwrite = false;
__keyDelay = 0;
__refreshRate = null;
// now set any properties that were passed in
if (mapProperties) {
this._properties = mapProperties;
if (mapProperties.allowOverwrite === true) {
__allowOverwrite = true;
}
if (mapProperties.keyDelay) {
__keyDelay = mapProperties.keyDelay;
}
if (mapProperties.refreshRate) {
__refreshRate = mapProperties.refreshRate;
}
}
};
this._canMoveTo = function (x, y, myType) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._canMoveTo()';}
var x = Math.floor(x); var y = Math.floor(y);
if (x < 0 || x >= __game._dimensions.width || y < 0 || y >= __game._dimensions.height) {
return false;
}
// look for static objects that can serve as obstacles
var objType = __grid[x][y].type;
var object = __objectDefinitions[objType];
if (object.impassable) {
if (myType && object.passableFor && object.passableFor.indexOf(myType) > -1) {
// this object is of a type that can pass the obstacle
return true;
} else if (typeof object.impassable === 'function') {
// the obstacle is impassable only in certain circumstances
return this._validateCallback(function () {
return !object.impassable(__player, object);
});
} else {
// the obstacle is always impassable
return false;
}
} else if (myType && object.impassableFor && object.impassableFor.indexOf(myType) > -1) {
// this object is of a type that cannot pass the obstacle
return false;
} else {
// no obstacle
return true;
}
};
// Returns the object of the given type closest to target coordinates
this._findNearestToPoint = function (type, targetX, targetY) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._findNearestToPoint()';}
var foundObjects = [];
// look for static objects
for (var x = 0; x < this.getWidth(); x++) {
for (var y = 0; y < this.getHeight(); y++) {
if (__grid[x][y].type === type) {
foundObjects.push({x: x, y: y});
}
}
}
// look for dynamic objects
for (var i = 0; i < __dynamicObjects.length; i++) {
var object = __dynamicObjects[i];
if (object.getType() === type) {
foundObjects.push({x: object.getX(), y: object.getY()});
}
}
// look for player
if (type === 'player') {
foundObjects.push({x: __player.getX(), y: __player.getY()});
}
var dists = [];
for (var i = 0; i < foundObjects.length; i++) {
var obj = foundObjects[i];
dists[i] = Math.sqrt(Math.pow(targetX - obj.x, 2) + Math.pow(targetY - obj.y, 2));
// We want to find objects distinct from ourselves
if (dists[i] === 0) {
dists[i] = 999;
}
}
var minDist = Math.min.apply(Math, dists);
var closestTarget = foundObjects[dists.indexOf(minDist)];
return closestTarget;
};
this._isPointOccupiedByDynamicObject = function (x, y) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._isPointOccupiedByDynamicObject()';}
var x = Math.floor(x); var y = Math.floor(y);
for (var i = 0; i < __dynamicObjects.length; i++) {
var object = __dynamicObjects[i];
if (object.getX() === x && object.getY() === y) {
return true;
}
}
return false;
};
this._findDynamicObjectAtPoint = function (x, y) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._findDynamicObjectAtPoint()';}
var x = Math.floor(x); var y = Math.floor(y);
for (var i = 0; i < __dynamicObjects.length; i++) {
var object = __dynamicObjects[i];
if (object.getX() === x && object.getY() === y) {
return object;
}
}
return false;
};
this._moveAllDynamicObjects = function () {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._moveAllDynamicObjects()';}
// the way things work right now, teleporters must take precedence
// over all other objects -- otherwise, pointers.jsx will not work
// correctly.
// TODO: make this not be the case
// "move" teleporters
__dynamicObjects.filter(function (object) {
return (object.getType() === 'teleporter');
}).forEach(function(object) {
object._onTurn();
});
// move everything else
__dynamicObjects.filter(function (object) {
return (object.getType() !== 'teleporter');
}).forEach(function(object) {
object._onTurn();
});
// refresh only at the end
this.refresh();
};
this._removeItemFromMap = function (x, y, klass) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._removeItemFromMap()';}
var x = Math.floor(x); var y = Math.floor(y);
if (__grid[x][y].type === klass) {
__grid[x][y].type = 'empty';
}
};
this._reenableMovementForPlayer = function (player) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._reenableMovementForPlayer()';}
if (!this._callbackValidationFailed) {
setTimeout(function () {
player._canMove = true;
}, __keyDelay);
}
};
this._hideChapter = function() {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._hideChapter()';}
// start fading out chapter immediately
// unless it's a death message, in which case wait 2.5 sec
clearInterval(__chapterHideTimeout);
__chapterHideTimeout = setTimeout(function () {
$('#chapter').fadeOut(1000);
}, $('#chapter').hasClass('death') ? 2500 : 0);
};
this._refreshDynamicObjects = function() {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._refreshDynamicObjects()';}
__dynamicObjects = __dynamicObjects.filter(function (obj) { return !obj.isDestroyed(); });
};
this._countTimers = function() {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._countTimers()';}
return __intervals.length;
}
/* (unexposed) wrappers for game methods */
this._startOfStartLevelReached = function() {
__game._startOfStartLevelReached = true;
};
this._endOfStartLevelReached = function() {
__game._endOfStartLevelReached = true;
};
this._playSound = function (sound) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._playSound()';}
__game.sound.playSound(sound);
};
this._validateCallback = function (callback) {
if (__game._isPlayerCodeRunning()) { throw 'Forbidden method call: map._validateCallback()';}
return __game.validateCallback(callback);
};
/* exposed methods */
this.refresh = wrapExposedMethod(function () {
if (__dom) {
this._display.clear();
var domHTML = __dom[0].outerHTML
.replace(/"/g, "'")
.replace(/<hr([^>]*)>/g, '<hr $1 />')
.replace(/<img([^>]*)>/g, '<img $1 />');
this._display.renderDom(domHTML, __domCSS);
} else {
this._display.drawAll(this);
}
// rewrite any status messages
if (this._status) {
this._display.writeStatus(this._status);
}
__game.drawInventory();
}, this);
this.countObjects = wrapExposedMethod(function (type) {
var count = 0;
// count static objects
for (var x = 0; x < this.getWidth(); x++) {
for (var y = 0; y < this.getHeight(); y++) {
if (__grid[x][y].type === type) {
count++;
}
}
}
// count dynamic objects
__dynamicObjects.forEach(function (obj) {
if (obj.getType() === type) {
count++;
}
})
return count;
}, this);
this.placeObject = wrapExposedMethod(function (x, y, type) {
var x = Math.floor(x); var y = Math.floor(y);
if (!__objectDefinitions[type]) {
throw "There is no type of object named " + type + "!";
}
var minLevel = __objectDefinitions[type].minimumLevel
if (minLevel && __game._currentLevel < minLevel) {
throw type.capitalize() + "s are not available until level " + minLevel;
}
if (__player && x == __player.getX() && y == __player.getY()) {
throw "Can't place object on top of player!";
}
if (typeof(__grid[x]) === 'undefined' || typeof(__grid[x][y]) === 'undefined') {
return;
// throw "Not a valid location to place an object!";
}
if (__objectDefinitions[type].type === 'dynamic') {
// dynamic object
__dynamicObjects.push(new DynamicObject(this, type, x, y, __game));
} else {
// static object
if (__grid[x][y].type === 'empty' || __grid[x][y].type === type || __allowOverwrite) {
__grid[x][y].type = type;
} else {
throw "There is already an object at (" + x + ", " + y + ")!";
}
}
}, this);
this.placePlayer = wrapExposedMethod(function (x, y) {
var x = Math.floor(x); var y = Math.floor(y);
if (__player) {
throw "Can't place player twice!";
}
__player = new __game._playerPrototype(x, y, this, __game);
this._display.drawAll(this);
}, this);
this.createFromGrid = wrapExposedMethod(function (grid, tiles, xOffset, yOffset) {
for (var y = 0; y < grid.length; y++) {
var line = grid[y];
for (var x = 0; x < line.length; x++) {
var tile = line[x];
var type = tiles[tile];
if (type === 'player') {
this.placePlayer(x + xOffset, y + yOffset);
} else if (type) {
this.placeObject(x + xOffset, y + yOffset, type);
}
}
}
}, this);
this.setSquareColor = wrapExposedMethod(function (x, y, bgColor) {
var x = Math.floor(x); var y = Math.floor(y);
__grid[x][y].bgColor = bgColor;
}, this);
this.defineObject = wrapExposedMethod(function (name, properties) {
if (__objectDefinitions[name]) {
throw "There is already a type of object named " + name + "!";
}
if (properties.interval && properties.interval < 100) {
throw "defineObject(): minimum interval is 100 milliseconds";
}
__objectDefinitions[name] = properties;
}, this);
this.getObjectTypeAt = wrapExposedMethod(function (x, y) {
var x = Math.floor(x); var y = Math.floor(y);
// Bazek: We should always check, if the coordinates are inside of map!
if (x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight())
return __grid[x][y].type;
else
return '';
}, this);
this.getAdjacentEmptyCells = wrapExposedMethod(function (x, y) {
var x = Math.floor(x); var y = Math.floor(y);
var map = this;
var actions = ['right', 'down', 'left', 'up'];
var adjacentEmptyCells = [];
$.each(actions, function (i, action) {
switch (actions[i]) {
case 'right':
var child = [x+1, y];
break;
case 'left':
var child = [x-1, y];
break;
case 'down':
var child = [x, y+1];
break;
case 'up':
var child = [x, y-1];
break;
}
// Bazek: We need to check, if child is inside of map!
var childInsideMap = child[0] >= 0 && child[0] < map.getWidth() && child[1] >= 0 && child[1] < map.getHeight();
if (childInsideMap && map.getObjectTypeAt(child[0], child[1]) === 'empty') {
adjacentEmptyCells.push([child, action]);
}
});
return adjacentEmptyCells;
}, this);
this.startTimer = wrapExposedMethod(function(timer, delay) {
if (!delay) {
throw "startTimer(): delay not specified"
} else if (delay < 25) {
throw "startTimer(): minimum delay is 25 milliseconds";
}
var validate = this._validateCallback;
__intervals.push(setInterval(function(){validate(timer)}, delay));
}, this);
this.timeout = wrapExposedMethod(function(timer, delay) {
if (!delay) {
throw "timeout(): delay not specified"
} else if (delay < 25) {
throw "timeout(): minimum delay is 25 milliseconds";
}
var validate = this._validateCallback;
__intervals.push(setTimeout(function(){validate(timer)}, delay));
}, this);
this.displayChapter = wrapExposedMethod(function(chapterName, cssClass) {
if (__game._displayedChapters.indexOf(chapterName) === -1) {
$('#chapter').html(chapterName.replace("\n","<br>"));
$('#chapter').removeClass().show();
if (cssClass) {
$('#chapter').addClass(cssClass);
} else {
__game._displayedChapters.push(chapterName);
}
setTimeout(function () {
$('#chapter').fadeOut();
}, 5 * 1000);
}
}, this);
this.writeStatus = wrapExposedMethod(function(status) {
if (this._status) {
// refresh to hide the old status message
this._status = "";
this.refresh();
}
this._status = status;
this._display.writeStatus(status);
}, this);
// used by validators
// returns true iff called at the start of the level (that is, on DummyMap)
// returns false iff called by validateCallback (that is, on the actual map)
this.isStartOfLevel = wrapExposedMethod(function () {
return this._dummy;
}, this);
/* canvas-related stuff */
this.getCanvasContext = wrapExposedMethod(function() {
var ctx = $('#drawingCanvas')[0].getContext('2d');
if(!this._dummy) {
var opts = this._display.getOptions();
ctx.font = opts.fontSize+"px " +opts.fontFamily;
}
return ctx;
}, this);
this.getCanvasCoords = wrapExposedMethod(function() {
var x, y;
if(arguments.length == 1) {
var obj = arguments[0];
x = obj.getX();
y = obj.getY();
} else {
x = arguments[0];
y = arguments[1];
}
var canvas = $('#drawingCanvas')[0];
return {
x: (x + 0.5) * canvas.width / __game._dimensions.width,
y: (y + 0.5) * canvas.height / __game._dimensions.height
};
}, this);
this.getRandomColor = wrapExposedMethod(function(start, end) {
var mean = [
Math.floor((start[0] + end[0]) / 2),
Math.floor((start[1] + end[1]) / 2),
Math.floor((start[2] + end[2]) / 2)
];
var std = [
Math.floor((end[0] - start[0]) / 2),
Math.floor((end[1] - start[1]) / 2),
Math.floor((end[2] - start[2]) / 2)
];
return ROT.Color.toHex(ROT.Color.randomize(mean, std));
}, this);
this.createLine = wrapExposedMethod(function(start, end, callback) {
__lines.push({'start': start, 'end': end, 'callback': callback});
}, this);
this.testLineCollisions = wrapExposedMethod(function(player) {
var threshold = 7;
var playerCoords = this.getCanvasCoords(player);
__lines.forEach(function (line) {
if (pDistance(playerCoords.x, playerCoords.y,
line.start[0], line.start[1],
line.end[0], line.end[1]) < threshold) {
__game.validateCallback(function() {
line.callback(__player);
});
}
})
}, this);
/* for DOM manipulation level */
this.getDOM = wrapExposedMethod(function () {
return __dom;
})
this.createFromDOM = wrapExposedMethod(function(dom) {
__dom = dom;
}, this);
this.updateDOM = wrapExposedMethod(function(dom) {
__dom = dom;
}, this);
this.overrideKey = wrapExposedMethod(function(keyName, callback) {
this._overrideKeys[keyName] = callback;
}, this);
/* validators */
this.validateAtLeastXObjects = wrapExposedMethod(function(num, type) {
var count = this.countObjects(type);
if (count < num) {
throw 'Not enough ' + type + 's on the map! Expected: ' + num + ', found: ' + count;
}
}, this);
this.validateAtMostXObjects = wrapExposedMethod(function(num, type) {
var count = this.countObjects(type);
if (count > num) {
throw 'Too many ' + type + 's on the map! Expected: ' + num + ', found: ' + count;
}
}, this);
this.validateExactlyXManyObjects = wrapExposedMethod(function(num, type) {
var count = this.countObjects(type);
if (count != num) {
throw 'Wrong number of ' + type + 's on the map! Expected: ' + num + ', found: ' + count;
}
}, this);
this.validateAtMostXDynamicObjects = wrapExposedMethod(function(num) {
var count = this.getDynamicObjects().length;
if (count > num) {
throw 'Too many dynamic objects on the map! Expected: ' + num + ', found: ' + count;
}
}, this);
this.validateNoTimers = wrapExposedMethod(function() {
var count = this._countTimers();
if (count > 0) {
throw 'Too many timers set on the map! Expected: 0, found: ' + count;
}
}, this);
this.validateAtLeastXLines = wrapExposedMethod(function(num) {
var count = this._getLines().length;
if (count < num) {
throw 'Not enough lines on the map! Expected: ' + num + ', found: ' + count;
}
}, this);
/* initialization */
this._reset();
// call secureObject to prevent user code from tampering with private attributes
__game.secureObject(this, "map");
}