whisper.cat/weboasis/arcade/3dcity/build/city.3d.js

7750 lines
289 KiB
JavaScript
Raw Normal View History

2023-10-05 23:28:32 +11:00
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
var Micro = {};
//Game
/*Micro.nextFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
null;*/
//Simulation
Micro.speedPowerScan = [2, 4, 5];
Micro.speedPollutionTerrainLandValueScan = [2, 7, 17];
Micro.speedCrimeScan = [1, 8, 18];
Micro.speedPopulationDensityScan = [1, 9, 19];
Micro.speedFireAnalysis = [1, 10, 20];
Micro.CENSUS_FREQUENCY_10 = 4;
Micro.CENSUS_FREQUENCY_120 = Micro.CENSUS_FREQUENCY_10 * 10;
Micro.TAX_FREQUENCY = 48;
//MapCanvas
Micro.MAP_WIDTH = 128;//128;//120;
Micro.MAP_HEIGHT = 128;//128;//100;
Micro.MAP_DEFAULT_WIDTH = Micro.MAP_WIDTH*3;
Micro.MAP_DEFAULT_HEIGHT = Micro.MAP_HEIGHT*3;
Micro.MAP_BIG_DEFAULT_WIDTH = Micro.MAP_WIDTH*16;
Micro.MAP_BIG_DEFAULT_HEIGHT = Micro.MAP_HEIGHT*16;
Micro.MAP_BIG_DEFAULT_ID = "bigMap";
Micro.MAP_PARENT_ID = "splashContainer";
Micro.MAP_DEFAULT_ID = "SplashCanvas";
//GameCanvas
Micro.DEFAULT_WIDTH = 400;
Micro.DEFAULT_HEIGHT = 400;
Micro.DEFAULT_ID = "MicropolisCanvas";
Micro.RCI_DEFAULT_ID = "RCICanvas";
//Census
Micro.arrs = ['res', 'com', 'ind', 'crime', 'money', 'pollution'];
var M_ARRAY_TYPE;
if(!M_ARRAY_TYPE) { M_ARRAY_TYPE = (typeof Float32Array !== 'undefined') ? Float32Array : Array; }
/*Micro.MouseBox = {
draw: function( c, pos, width, height, options ) {
var lineWidth = options.lineWidth || 3.0;
var strokeStyle = options.colour || 'yellow';
var shouldOutline = (('outline' in options) && options.outline === true) || false;
var startModifier = -1;
var endModifier = 1;
if (!shouldOutline) {
startModifier = 1;
endModifier = -1;
}
var startX = pos.x + startModifier * lineWidth * 0.5;
width = width + endModifier * lineWidth;
var startY = pos.y + startModifier * lineWidth * 0.5;
height = height + endModifier * lineWidth;
var ctx = c.getContext('2d');
ctx.lineWidth = lineWidth;
ctx.strokeStyle = strokeStyle;
ctx.strokeRect(startX, startY, width, height);
}
}*/
// MiscUtils,
Micro.clamp = function(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
Micro.makeConstantDescriptor = function(value) {
return {configurable: false, enumerable: false, writeable: false, value: value};
}
Micro.rotate10Arrays = function() {
for (var i = 0; i < Micro.arrs.length; i++) {
var name10 = Micro.arrs[i] + 'Hist10';
//this[name10] = [0].concat(this[name10].slice(0, -1));
this[name10].pop();
this[name10].unshift(0);
}
}
Micro.rotate120Arrays = function() {
for (var i = 0; i < Micro.arrs.length; i++) {
var name120 = Micro.arrs[i] + 'Hist120';
//this[name120] = [0].concat(this[name120].slice(0, -1));
this[name120].pop();
this[name120].unshift(0)
}
}
Micro.isCallable = function( f ) {
return typeof(f) === 'function';
};
// Simulation
Micro.LEVEL_EASY = 0;
Micro.LEVEL_MED = 1;
Micro.LEVEL_HARD = 2;
Micro.SPEED_PAUSED = 0;
Micro.SPEED_SLOW = 1;
Micro.SPEED_MED = 2;
Micro.SPEED_FAST = 3;
// Traffic
Micro.ROUTE_FOUND = 1;
Micro.NO_ROUTE_FOUND = 0;
Micro.NO_ROAD_FOUND = -1;
Micro.MAX_TRAFFIC_DISTANCE = 30;
Micro.perimX = [-1, 0, 1, 2, 2, 2, 1, 0,-1,-2,-2,-2];
Micro.perimY = [-2,-2,-2,-1, 0, 1, 2, 2, 2, 1, 0,-1];
//SpriteConstants
Micro.SPRITE_TRAIN = 1;
Micro.SPRITE_HELICOPTER = 2;
Micro.SPRITE_AIRPLANE = 3;
Micro.SPRITE_SHIP = 4;
Micro.SPRITE_MONSTER = 5;
Micro.SPRITE_TORNADO = 6;
Micro.SPRITE_EXPLOSION = 7;
// Evaluation
Micro.CC_VILLAGE = 'VILLAGE';
Micro.CC_TOWN = 'TOWN';
Micro.CC_CITY = 'CITY';
Micro.CC_CAPITAL = 'CAPITAL';
Micro.CC_METROPOLIS = 'METROPOLIS';
Micro.CC_MEGALOPOLIS = 'MEGALOPOLIS';
Micro.CRIME = 0;
Micro.POLLUTION = 1
Micro.HOUSING = 2;
Micro.TAXES = 3;
Micro.TRAFFIC = 4;
Micro.UNEMPLOYMENT = 5;
Micro.FIRE = 6;
// Valves
Micro.RES_VALVE_RANGE = 2000;
Micro.COM_VALVE_RANGE = 1500;
Micro.IND_VALVE_RANGE = 1500;
Micro.taxTable = [ 200, 150, 120, 100, 80, 50, 30, 0, -10, -40, -100, -150, -200, -250, -300, -350, -400, -450, -500, -550, -600];
Micro.extMarketParamTable = [1.2, 1.1, 0.98];
// Budget
Micro.RLevels = [0.7, 0.9, 1.2];
Micro.FLevels = [1.4, 1.2, 0.8];
Micro.MAX_ROAD_EFFECT = 32;
Micro.MAX_POLICESTATION_EFFECT = 1000;
Micro.MAX_FIRESTATION_EFFECT = 1000;
// PowerManager
Micro.COAL_POWER_STRENGTH = 700;
Micro.NUCLEAR_POWER_STRENGTH = 2000;
//DisasterWindow
Micro.DISASTER_NONE='None';
Micro.DISASTER_MONSTER='Monster';
Micro.DISASTER_FIRE='Fire';
Micro.DISASTER_FLOOD='Flood';
Micro.DISASTER_CRASH='Crash';
Micro.DISASTER_MELTDOWN='Meltdown';
Micro.DISASTER_TORNADO='Tornado';
// storage
Micro.CURRENT_VERSION = 3;
Micro.KEY = 'micropolisJSGame';
///Micro.canStore = window.localStorage;
//Micro.localStorage = null;
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.lerp = function (a, b, percent) { return a + (b - a) * percent; };
Micro.rand = function (a, b) { return Micro.lerp(a, b, Math.random()); };
Micro.randInt = function (a, b, p) { return Micro.lerp(a, b, Math.random()).toFixed(p || 0)*1; }
Micro.Random = function(){}
Micro.Random.prototype = {
constructor: Micro.Random,
getChance : function(chance) {
return (this.getRandom16() & chance) === 0;
},
getERandom : function( max ) {
var r1 = this.getRandom(max);
var r2 = this.getRandom(max);
return Math.min(r1, r2);
},
getRandom : function( max ) {
//return Micro.randInt( 0, max );
return Math.floor(Math.random() * (max + 1));
},
getRandom16 : function() {
return this.getRandom(65535);
},
getRandom16Signed : function() {
var value = this.getRandom16();
if (value >= 32768) value = 32768 - value;
return value;
}
}
var Random = new Micro.Random();
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Direction = function(){};
Micro.Direction.prototype = {
constructor: Micro.Direction,
"INVALID": -1,
"NORTH": 0,
"NORTHEAST": 1,
"EAST": 2,
"SOUTHEAST": 3,
"SOUTH": 4,
"SOUTHWEST": 5,
"WEST": 6,
"NORTHWEST": 7,
"BEGIN": 0,
"END": 8,
// Move direction clockwise by 45 degrees. No bounds checking
// i.e. result could be >= END. Has no effect on INVALID. Undefined
// when dir >= END
increment45: function(dir, count) {
if (arguments.length < 1) throw new TypeError();
if (dir == this.INVALID) return dir;
if (!count && count !== 0) count = 1;
return dir + count;
},
// Move direction clockwise by 90 degrees. No bounds checking
// i.e. result could be >= END. Has no effect on INVALID. Undefined
// when dir >= END
increment90: function(dir) {
if (arguments.length < 1) throw new TypeError();
return this.increment45(dir, 2);
},
// Move direction clockwise by 45 degrees, taking the direction modulo 8
// if necessary to force it into valid bounds. Has no effect on INVALID.
rotate45: function(dir, count) {
if (arguments.length < 1) throw new TypeError();
if (dir == this.INVALID) return dir;
if (!count && count !== 0) count = 1;
return ((dir - this.NORTH + count) & 7) + this.NORTH;
},
// Move direction clockwise by 90 degrees, taking the direction modulo 8
// if necessary to force it into valid bounds. Has no effect on INVALID.
rotate90: function(dir) {
if (arguments.length < 1) throw new TypeError();
return this.rotate45(dir, 2);
},
// Move direction clockwise by 180 degrees, taking the direction modulo 8
// if necessary to force it into valid bounds. Has no effect on INVALID.
rotate180: function(dir) {
if (arguments.length < 1) throw new TypeError();
return this.rotate45(dir, 4);
}
}
var Direction = new Micro.Direction();
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
var messageData = {
AUTOBUDGET_CHANGED: Micro.makeConstantDescriptor('Autobudget changed'),
BUDGET_NEEDED: Micro.makeConstantDescriptor('User needs to budget'),
BLACKOUTS_REPORTED: Micro.makeConstantDescriptor('Blackouts reported'),
DATE_UPDATED: Micro.makeConstantDescriptor('Date changed'),
DID_TOOL: Micro.makeConstantDescriptor('Tool applied'),
EARTHQUAKE: Micro.makeConstantDescriptor('Earthquake'),
EXPLOSION_REPORTED: Micro.makeConstantDescriptor('Explosion Reported'),
EVAL_UPDATED: Micro.makeConstantDescriptor('Evaluation Updated'),
FIRE_REPORTED: Micro.makeConstantDescriptor('Fire!'),
FIRE_STATION_NEEDS_FUNDING: Micro.makeConstantDescriptor('Fire station needs funding'),
FLOODING_REPORTED: Micro.makeConstantDescriptor('Flooding reported'),
FUNDS_CHANGED: Micro.makeConstantDescriptor('Total funds has changed'),
HEAVY_TRAFFIC: Micro.makeConstantDescriptor('Total funds has changed'),
HELICOPTER_CRASHED: Micro.makeConstantDescriptor('Helicopter crashed'),
HIGH_CRIME: Micro.makeConstantDescriptor('High crime'),
HIGH_POLLUTION: Micro.makeConstantDescriptor('High pollution'),
MONSTER_SIGHTED: Micro.makeConstantDescriptor('Monster sighted'),
NEED_ELECTRICITY: Micro.makeConstantDescriptor('More power needed'),
NEED_FIRE_STATION: Micro.makeConstantDescriptor('Fire station needed'),
NEED_MORE_COMMERCIAL: Micro.makeConstantDescriptor('More commercial zones needed'),
NEED_MORE_INDUSTRIAL: Micro.makeConstantDescriptor('More industrial zones needed'),
NEED_MORE_RESIDENTIAL: Micro.makeConstantDescriptor('More residential needed'),
NEED_MORE_RAILS: Micro.makeConstantDescriptor('More railways needed'),
NEED_MORE_ROADS: Micro.makeConstantDescriptor('More roads needed'),
NEED_POLICE_STATION: Micro.makeConstantDescriptor('Police station needed'),
NEED_AIRPORT: Micro.makeConstantDescriptor('Airport needed'),
NEED_SEAPORT: Micro.makeConstantDescriptor('Seaport needed'),
NEED_STADIUM: Micro.makeConstantDescriptor('Stadium needed'),
NO_MONEY: Micro.makeConstantDescriptor('No money'),
NOT_ENOUGH_POWER: Micro.makeConstantDescriptor('Not enough power'),
NUCLEAR_MELTDOWN: Micro.makeConstantDescriptor('Nuclear Meltdown'),
PLANE_CRASHED: Micro.makeConstantDescriptor('Plane crashed'),
POLICE_NEEDS_FUNDING: Micro.makeConstantDescriptor('Police need funding'),
QUERY_WINDOW_NEEDED: Micro.makeConstantDescriptor('Query window needed'),
REACHED_CAPITAL: Micro.makeConstantDescriptor('Now a capital'),
REACHED_CITY: Micro.makeConstantDescriptor('Now a city'),
REACHED_METROPOLIS: Micro.makeConstantDescriptor('Now a metropolis'),
REACHED_MEGALOPOLIS: Micro.makeConstantDescriptor('Now a megalopolis'),
REACHED_TOWN: Micro.makeConstantDescriptor('Now a town'),
REACHED_VILLAGE: Micro.makeConstantDescriptor('Now a village'),
ROAD_NEEDS_FUNDING: Micro.makeConstantDescriptor('Roads need funding'),
SHIP_CRASHED: Micro.makeConstantDescriptor('Shipwrecked'),
SOUND_EXPLOSIONHIGH: Micro.makeConstantDescriptor('Explosion! Bang!'),
SOUND_EXPLOSIONLOW: Micro.makeConstantDescriptor('Explosion! Bang!'),
SOUND_HEAVY_TRAFFIC: Micro.makeConstantDescriptor('Heavy Traffic sound'),
SOUND_HONKHONK: Micro.makeConstantDescriptor('HonkHonk sound'),
SOUND_MONSTER: Micro.makeConstantDescriptor('Monster sound'),
TAX_TOO_HIGH: Micro.makeConstantDescriptor('Tax too high'),
TORNADO_SIGHTED: Micro.makeConstantDescriptor('Tornado sighted'),
TRAFFIC_JAMS: Micro.makeConstantDescriptor('Traffic jams reported'),
TRAIN_CRASHED: Micro.makeConstantDescriptor('Train crashed'),
VALVES_UPDATED: Micro.makeConstantDescriptor('Valves updated'),
WELCOME: Micro.makeConstantDescriptor('Welcome to 3D city'),
WELCOMEBACK: Micro.makeConstantDescriptor('Welcome back to your 3D city')
};
var Messages = Object.defineProperties({}, messageData);
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.MessageManager = function(){
this.data = [];
}
Micro.MessageManager.prototype = {
constructor: Micro.MessageManager,
sendMessage : function(message, data) {
this.data.push({message: message, data: data});
},
clear : function() {
this.data = [];
},
getMessages : function() {
return this.data.slice();
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Text = function(){
// TODO Some kind of rudimentary L20N based on navigator.language?
// Query tool strings
var densityStrings = ['Low', 'Medium', 'High', 'Very High'];
var landValueStrings = ['Slum', 'Lower Class', 'Middle Class', 'High'];
var crimeStrings = ['Safe', 'Light', 'Moderate', 'Dangerous'];
var pollutionStrings = ['None', 'Moderate', 'Heavy', 'Very Heavy'];
var rateStrings = ['Declining', 'Stable', 'Slow Growth', 'Fast Growth'];
var zoneTypes = ['Clear', 'Water', 'Trees', 'Rubble', 'Flood', 'Radioactive Waste',
'Fire', 'Road', 'Power', 'Rail', 'Residential', 'Commercial',
'Industrial', 'Seaport', 'Airport', 'Coal Power', 'Fire Department',
'Police Department', 'Stadium', 'Nuclear Power', 'Draw Bridge',
'Radar Dish', 'Fountain', 'Industrial', 'Steelers 38 Bears 3',
'Draw Bridge', 'Ur 238'];
// Evaluation window
var gameLevel = {};
gameLevel['' + Micro.LEVEL_EASY] = 'Easy';
gameLevel['' + Micro.LEVEL_MED] = 'Medium';
gameLevel['' + Micro.LEVEL_HARD] = 'Hard';
var cityClass = {};
cityClass[Micro.CC_VILLAGE] = 'VILLAGE';
cityClass[Micro.CC_TOWN] = 'TOWN';
cityClass[Micro.CC_CITY] = 'CITY';
cityClass[Micro.CC_CAPITAL] = 'CAPITAL';
cityClass[Micro.CC_METROPOLIS] = 'METROPOLIS';
cityClass[Micro.CC_MEGALOPOLIS] = 'MEGALOPOLIS';
var problems = {};
problems[Micro.CRIME] = 'Crime';
problems[Micro.POLLUTION] = 'Pollution';
problems[Micro.HOUSING] = 'Housing';
problems[Micro.TAXES] = 'Taxes';
problems[Micro.TRAFFIC] = 'Traffic';
problems[Micro.UNEMPLOYMENT] = 'Unemployment';
problems[Micro.FIRE] = 'Fire';
// months
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// Tool strings
var toolMessages = {
noMoney: 'Insufficient funds to build that',
needsDoze: 'Area must be bulldozed first'
};
// Message strings
var neutralMessages = {};
neutralMessages[Messages.FIRE_STATION_NEEDS_FUNDING] = 'Fire departments need funding';
neutralMessages[Messages.NEED_AIRPORT] = 'Commerce requires an Airport';
neutralMessages[Messages.NEED_FIRE_STATION] = 'Citizens demand a Fire Department';
neutralMessages[Messages.NEED_ELECTRICITY] = 'Build a Power Plant';
neutralMessages[Messages.NEED_MORE_INDUSTRIAL] = 'More industrial zones needed';
neutralMessages[Messages.NEED_MORE_COMMERCIAL] = 'More commercial zones needed';
neutralMessages[Messages.NEED_MORE_RESIDENTIAL] = 'More residential zones needed';
neutralMessages[Messages.NEED_MORE_RAILS] = 'Inadequate rail system';
neutralMessages[Messages.NEED_MORE_ROADS] = 'More roads required';
neutralMessages[Messages.NEED_POLICE_STATION] = 'Citizens demand a Police Department';
neutralMessages[Messages.NEED_SEAPORT] = 'Industry requires a Sea Port';
neutralMessages[Messages.NEED_STADIUM] = 'Residents demand a Stadium';
neutralMessages[Messages.ROAD_NEEDS_FUNDING] = 'Roads deteriorating, due to lack of funds';
neutralMessages[Messages.POLICE_NEEDS_FUNDING] = 'Police departments need funding';
neutralMessages[Messages.WELCOME] = 'Welcome to 3D City';
neutralMessages[Messages.WELCOMEBACK] = 'Welcome to 3D City';
var badMessages = {};
badMessages[Messages.BLACKOUTS_REPORTED] = 'Brownouts, build another Power Plant';
badMessages[Messages.COPTER_CRASHED] = 'A helicopter crashed ';
badMessages[Messages.EARTHQUAKE] = 'Major earthquake reported !!';
badMessages[Messages.EXPLOSION_REPORTED] = 'Explosion detected ';
badMessages[Messages.FLOODING_REPORTED] = 'Flooding reported !';
badMessages[Messages.FIRE_REPORTED] = 'Fire reported ';
badMessages[Messages.HEAVY_TRAFFIC] = 'Heavy Traffic reported';
badMessages[Messages.HIGH_CRIME] = 'Crime very high';
badMessages[Messages.HIGH_POLLUTION] = 'Pollution very high';
badMessages[Messages.MONSTER_SIGHTED] = 'A Monster has been sighted !';
badMessages[Messages.NO_MONEY] = 'YOUR CITY HAS GONE BROKE';
badMessages[Messages.NOT_ENOUGH_POWER] = 'Blackouts reported. insufficient power capacity';
badMessages[Messages.NUCLEAR_MELTDOWN] = 'A Nuclear Meltdown has occurred !!';
badMessages[Messages.PLANE_CRASHED] = 'A plane has crashed ';
badMessages[Messages.SHIP_CRASHED] = 'Shipwreck reported ';
badMessages[Messages.TAX_TOO_HIGH] = 'Citizens upset. The tax rate is too high';
badMessages[Messages.TORNADO_SIGHTED] = 'Tornado reported !';
badMessages[Messages.TRAFFIC_JAMS] = 'Frequent traffic jams reported';
badMessages[Messages.TRAIN_CRASHED] = 'A train crashed ';
var goodMessages = ' {}';
goodMessages[Messages.REACHED_CAPITAL] = 'Population has reached 50,000';
goodMessages[Messages.REACHED_CITY] = 'Population has reached 10,000';
goodMessages[Messages.REACHED_MEGALOPOLIS] = 'Population has reached 500,000';
goodMessages[Messages.REACHED_METROPOLIS] = 'Population has reached 100,000';
goodMessages[Messages.REACHED_TOWN] = 'Population has reached 2,000';
return {
badMessages: badMessages,
cityClass: cityClass,
crimeStrings: crimeStrings,
densityStrings: densityStrings,
gameLevel: gameLevel,
goodMessages: goodMessages,
landValueStrings: landValueStrings,
months: months,
neutralMessages: neutralMessages,
problems: problems,
pollutionStrings: pollutionStrings,
rateStrings: rateStrings,
toolMessages: toolMessages,
zoneTypes: zoneTypes
}
};
var TXT = new Micro.Text();
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.CensusProps = ['resPop', 'comPop', 'indPop', 'crimeRamp', 'pollutionRamp', 'landValueAverage', 'pollutionAverage',
'crimeAverage', 'totalPop', 'resHist10', 'resHist120', 'comHist10', 'comHist120', 'indHist10',
'indHist120', 'crimeHist10', 'crimeHist120', 'moneyHist10', 'moneyHist120', 'pollutionHist10',
'pollutionHist120'];
Micro.Census = function(){
this.clearCensus();
this.changed = false;
this.crimeRamp = 0;
this.pollutionRamp = 0;
// Set externally
this.landValueAverage = 0;
this.pollutionAverage = 0;
this.crimeAverage = 0;
this.totalPop = 0;
var createArray = function(arrName) {
//this[arrName] = new M_ARRAY_TYPE(120);
this[arrName] = [];
//for (var a = 0; a < 120; a++) this[arrName][a] = 0;
var a = 120;
while(a--) this[arrName][a] = 0;
}
for (var i = 0; i < Micro.arrs.length; i++) {
var name10 = Micro.arrs[i] + 'Hist10';
var name120 = Micro.arrs[i] + 'Hist120';
createArray.call(this, name10);
createArray.call(this, name120);
}
}
Micro.Census.prototype = {
constructor: Micro.Census,
save : function(saveData) {
for (var i = 0, l = Micro.CensusProps.length; i < l; i++)
saveData[Micro.CensusProps[i]] = this[Micro.CensusProps[i]];
},
load : function(saveData) {
for (var i = 0, l = Micro.CensusProps.length; i < l; i++)
this[Micro.CensusProps[i]] = saveData[Micro.CensusProps[i]];
},
clearCensus : function() {
this.poweredZoneCount = 0;
this.unpoweredZoneCount = 0;
this.firePop = 0;
this.roadTotal = 0;
this.railTotal = 0;
this.resPop = 0;
this.comPop = 0;
this.indPop = 0;
this.resZonePop = 0;
this.comZonePop = 0;
this.indZonePop = 0;
this.hospitalPop = 0;
this.churchPop = 0;
this.policeStationPop = 0;
this.fireStationPop = 0;
this.stadiumPop = 0;
this.coalPowerPop = 0;
this.nuclearPowerPop = 0;
this.seaportPop = 0;
this.airportPop = 0;
},
take10Census : function(budget) {
var resPopDenom = 8;
Micro.rotate10Arrays.call(this);
this.resHist10[0] = Math.floor(this.resPop / resPopDenom);
this.comHist10[0] = this.comPop;
this.indHist10[0] = this.indPop;
this.crimeRamp += Math.floor((this.crimeAverage - this.crimeRamp) / 4);
this.crimeHist10[0] = Math.min(this.crimeRamp, 255);
this.pollutionRamp += Math.floor((this.pollutionAverage - this.pollutionRamp) / 4);
this.pollutionHist10[0] = Math.min(this.pollutionRamp, 255);
var x = Math.floor(budget.cashFlow / 20) + 128;
this.moneyHist10[0] = Micro.clamp(x, 0, 255);
var resPopScaled = this.resPop >> 8;
if (this.hospitalPop < this.resPopScaled) this.needHospital = 1;
else if (this.hospitalPop > this.resPopScaled) this.needHospital = -1;
else if (this.hospitalPop === this.resPopScaled) this.needHospital = 0;
this.changed = true;
},
take120Census : function() {
Micro.rotate120Arrays.call(this);
var resPopDenom = 8;
this.resHist120[0] = Math.floor(this.resPop / resPopDenom);
this.comHist120[0] = this.comPop;
this.indHist120[0] = this.indPop;
this.crimeHist120[0] = this.crimeHist10[0];
this.pollutionHist120[0] = this.pollutionHist10[0];
this.moneyHist120[0] = this.moneyHist10[0];
this.changed = true;
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.PROBLEMS = ['CVP_CRIME', 'CVP_POLLUTION', 'CVP_HOUSING', 'CVP_TAXES', 'CVP_TRAFFIC', 'CVP_UNEMPLOYMENT', 'CVP_FIRE'];
Micro.NUMPROBLEMS = Micro.PROBLEMS.length;
Micro.NUM_COMPLAINTS = 4;
Micro.EvalProps = ['cityClass', 'cityScore'];
Micro.getTrafficAverage = function( blockMaps ) {
var trafficDensityMap = blockMaps.trafficDensityMap;
var landValueMap = blockMaps.landValueMap;
var trafficTotal = 0;
var count = 1;
for (var x = 0; x < landValueMap.gameMapWidth; x += landValueMap.blockSize) {
for (var y = 0; y < landValueMap.gameMapHeight; y += landValueMap.blockSize) {
if (landValueMap.worldGet(x, y) > 0) {
trafficTotal += trafficDensityMap.worldGet(x, y);
count++;
}
}
}
var trafficAverage = Math.floor(trafficTotal / count) * 2.4;
return trafficAverage;
};
Micro.getUnemployment = function(census) {
var b = (census.comPop + census.indPop) * 8;
if (b === 0) return 0;
// Ratio total people / working. At least 1.
var r = census.resPop / b;
b = Math.round((r - 1) * 255);
return Math.min(b, 255);
};
Micro.getFireSeverity = function(census) {
return Math.min(census.firePop * 5, 255);
};
Micro.Evaluation = function (gameLevel, SIM) {
this.sim = SIM;
this.problemVotes = [];
this.problemOrder = [];
this.evalInit();
this.gameLevel = '' + gameLevel;
this.changed = false;
}
Micro.Evaluation.prototype = {
constructor: Micro.Evaluation,
save : function(saveData) {
for (var i = 0, l = Micro.EvalProps.length; i < l; i++)
saveData[Micro.EvalProps[i]] = this[Micro.EvalProps[i]];
},
load : function(saveData) {
for (var i = 0, l = Micro.EvalProps.length; i < l; i++)
this[Micro.EvalProps[i]] = saveData[Micro.EvalProps[i]];
},
cityEvaluation : function() {
var census = this.sim.census;
if (census.totalPop > 0) {
var problemTable = [];
for (var i = 0; i < Micro.NUMPROBLEMS; i++) problemTable.push(0);
this.getAssessedValue(census);
this.doPopNum(census);
this.doProblems(this.sim.census, this.sim.budget, this.sim.blockMaps, problemTable);
this.getScore(problemTable);
this.doVotes();
this.changeEval();
} else {
this.evalInit();
this.cityYes = 50;
this.changeEval();
}
},
/*cityEvaluation : function(simData) {
var census = simData.census;
if (census.totalPop > 0) {
var problemTable = [];
for (var i = 0; i < Micro.NUMPROBLEMS; i++) problemTable.push(0);
this.getAssessedValue(census);
this.doPopNum(census);
this.doProblems(simData.census, simData.budget, simData.blockMaps, problemTable);
this.getScore(simData, problemTable);
this.doVotes();
this.changeEval();
} else {
this.evalInit();
this.cityYes = 50;
this.changeEval();
}
},*/
evalInit : function() {
this.cityYes = 0;
this.cityPop = 0;
this.cityPopDelta = 0;
this.cityAssessedValue = 0;
this.cityClass = Micro.CC_VILLAGE;
this.cityScore = 500;
this.cityScoreDelta = 0;
for (var i = 0; i < Micro.NUMPROBLEMS; i++) this.problemVotes[i] = 0;
for (i = 0; i < Micro.NUM_COMPLAINTS; i++) this.problemOrder[i] = Micro.NUMPROBLEMS;
},
getAssessedValue: function( census ) {
var value;
value = census.roadTotal * 5;
value += census.railTotal * 10;
value += census.policeStationPop * 1000;
value += census.fireStationPop * 1000;
value += census.hospitalPop * 400;
value += census.stadiumPop * 3000;
value += census.seaportPop * 5000;
value += census.airportPop * 10000;
value += census.coalPowerPop * 3000;
value += census.nuclearPowerPop * 6000;
this.cityAssessedValue = value * 1000;
},
getPopulation : function( census ) {
var population = (census.resPop + (census.comPop + census.indPop) * 8) * 20;
return population;
},
doPopNum : function(census) {
var oldCityPop = this.cityPop;
this.cityPop = this.getPopulation(census);
if (oldCityPop == -1)
oldCityPop = this.cityPop;
this.cityPopDelta = this.cityPop - oldCityPop;
this.cityClass = this.getCityClass(this.cityPop);
},
getCityClass : function(cityPopulation) {
this.cityClassification = Micro.CC_VILLAGE;
if (cityPopulation > 2000) this.cityClassification = Micro.CC_TOWN;
if (this.cityPopulation > 10000) this.cityClassification = Micro.CC_CITY;
if (this.cityPopulation > 50000) this.cityClassification = Micro.CC_CAPITAL;
if (this.cityPopulation > 100000) this.cityClassification = Micro.CC_METROPOLIS;
if (this.cityPopulation > 500000) this.cityClassification = Micro.CC_MEGALOPOLIS;
return this.cityClassification;
},
voteProblems : function(problemTable) {
for (var i = 0; i < Micro.NUMPROBLEMS; i++) this.problemVotes[i] = 0;
var problem = 0;
var voteCount = 0;
var loopCount = 0;
while (voteCount < 100 && loopCount < 600) {
if (Random.getRandom(300) < problemTable[problem]) {
this.problemVotes[problem]++;
voteCount++;
}
problem++;
if (problem > Micro.NUMPROBLEMS) {
problem = 0;
}
loopCount++;
}
},
doProblems : function(census, budget, blockMaps, problemTable) {
var problemTaken = [];
for (var i = 0; i < Micro.NUMPROBLEMS; i++) {
problemTaken[i] = false;
problemTable[i] = 0;
}
problemTable[Micro.CRIME] = census.crimeAverage;
problemTable[Micro.POLLUTION] = census.pollutionAverage;
problemTable[Micro.HOUSING] = census.landValueAverage * 7 / 10;
problemTable[Micro.TAXES] = budget.cityTax * 10;
problemTable[Micro.TRAFFIC] = Micro.getTrafficAverage(blockMaps);
problemTable[Micro.UNEMPLOYMENT] = Micro.getUnemployment(census);
problemTable[Micro.FIRE] = Micro.getFireSeverity(census);
this.voteProblems(problemTable);
for (i = 0; i < Micro.NUM_COMPLAINTS; i++) {
// Find biggest problem not taken yet
var maxVotes = 0;
var bestProblem = Micro.NUMPROBLEMS;
for (var j = 0; j < Micro.NUMPROBLEMS; j++) {
if ((this.problemVotes[j] > maxVotes) && (!problemTaken[j])) {
bestProblem = j;
maxVotes = this.problemVotes[j];
}
}
// bestProblem == NUMPROBLEMS means no problem found
this.problemOrder[i] = bestProblem;
if (bestProblem < Micro.NUMPROBLEMS) {
problemTaken[bestProblem] = true;
}
}
},
//getScore : function(simData, problemTable) {
getScore : function(problemTable) {
var census = this.sim.census;
var budget = this.sim.budget;
var valves = this.sim.valves;
var cityScoreLast;
cityScoreLast = this.cityScore;
var score = 0;
for (var i = 0; i < Micro.NUMPROBLEMS; i++) score += problemTable[i];
score = Math.floor(score / 3);
score = Math.min(score, 256);
score = Micro.clamp((256 - score) * 4, 0, 1000);
if (valves.resCap) score = Math.round(score * 0.85);
if (valves.comCap) score = Math.round(score * 0.85);
if (valves.indCap) score = Math.round(score * 0.85);
if (budget.roadEffect < budget.MAX_ROAD_EFFECT) score -= budget.MAX_ROAD_EFFECT - budget.roadEffect;
if (budget.policeEffect < budget.MAX_POLICE_STATION_EFFECT) {
score = Math.round(score * (0.9 + (budget.policeEffect / (10.0001 * budget.MAX_POLICE_STATION_EFFECT))));
}
if (budget.fireEffect < budget.MAX_FIRE_STATION_EFFECT) {
score = Math.round(score * (0.9 + (budget.fireEffect / (10.0001 * budget.MAX_FIRE_STATION_EFFECT))));
}
if (valves.resValve < -1000) score = Math.round(score * 0.85);
if (valves.comValve < -1000) score = Math.round(score * 0.85);
if (valves.indValve < -1000) score = Math.round(score * 0.85);
var scale = 1.0;
if (this.cityPop === 0 || this.cityPopDelta === 0) {
scale = 1.0; // there is nobody or no migration happened
} else if (this.cityPopDelta == this.cityPop) {
scale = 1.0; // city sprang into existence or doubled in size
} else if (this.cityPopDelta > 0) {
scale = (this.cityPopDelta / this.cityPop) + 1.0;
} else if (this.cityPopDelta < 0) {
scale = 0.95 + Math.floor(this.cityPopDelta / (this.cityPop - this.cityPopDelta));
}
score = Math.round(score * scale);
score = score - Micro.getFireSeverity(census) - budget.cityTax; // dec score for fires and tax
scale = census.unpoweredZoneCount + census.poweredZoneCount; // dec score for unpowered zones
if (scale > 0.0) score = Math.round(score * (census.poweredZoneCount / scale));
score = Micro.clamp(score, 0, 1000);
this.cityScore = Math.round((this.cityScore + score) / 2);
this.cityScoreDelta = this.cityScore - cityScoreLast;
},
doVotes : function() {
var i;
this.cityYes = 0;
for (i = 0; i < 100; i++) {
if (Random.getRandom(1000) < this.cityScore) this.cityYes++;
}
},
changeEval : function() {
this.changed = true;
},
countProblems : function() {
var i;
for (i = 0; i < Micro.NUM_COMPLAINTS; i++) {
if (this.problemOrder[i] === Micro.NUMPROBLEMS) break;
}
return i;
},
getProblemNumber : function(i) {
if (i < 0 || i >= Micro.NUM_COMPLAINTS || this.problemOrder[i] === Micro.NUMPROBLEMS) return -1;
else return this.problemOrder[i];
},
getProblemVotes : function(i) {
if (i < 0 || i >= Micro.NUM_COMPLAINTS || this.problemOrder[i] == Micro.NUMPROBLEMS) return -1;
else return this.problemVotes[this.problemOrder[i]];
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.BudgetProps = ['autoBudget', 'totalFunds', 'policePercent', 'roadPercent', 'firePercent', 'roadSpend',
'policeSpend', 'fireSpend', 'roadMaintenanceBudget', 'policeMaintenanceBudget',
'fireMaintenanceBudget', 'cityTax', 'roadEffect', 'policeEffect', 'fireEffect'];
Micro.Budget = function () {
this.roadEffect = Micro.MAX_ROAD_EFFECT;
this.policeEffect = Micro.MAX_POLICESTATION_EFFECT;
this.fireEffect = Micro.MAX_FIRESTATION_EFFECT;
this.totalFunds = 0;
this.cityTax = 7;
this.cashFlow = 0;
this.taxFund = 0;
// The 'fund's respresent the cost of funding all these services on
// the map 100%
this.roadFund = 0;
this.fireFund = 0;
this.policeFund = 0;
// Percentage of budget used
this.roadPercent = 1;
this.firePercent = 1;
this.policePercent = 1;
// Cash value of spending. Should equal Math.round(_Fund * _Percent)
this.roadSpend = 0;
this.fireSpend = 0;
this.policeSpend = 0;
this.autoBudget = true;
}
Micro.Budget.prototype = {
constructor: Micro.Budget,
save : function(saveData) {
for (var i = 0, l = Micro.BudgetProps.length; i < l; i++)
saveData[Micro.BudgetProps[i]] = this[Micro.BudgetProps[i]];
},
load : function(saveData, messageManager) {
for (var i = 0, l = Micro.BudgetProps.length; i < l; i++)
this[Micro.BudgetProps[i]] = saveData[Micro.BudgetProps[i]];
if (messageManager !== undefined) messageManager.sendMessage(Messages.AUTOBUDGET_CHANGED, this.autoBudget);
if (messageManager !== undefined) messageManager.sendMessage(Messages.FUNDS_CHANGED, this.totalFunds);
},
doBudget : function(messageManager) {
return this.doBudgetNow(false, messageManager);
},
// User initiated budget
doBudgetMenu : function(messageManager) {
return this.doBudgetNow(false, messageManager);
},
doBudgetNow : function(fromWindow, messageManager) {
// How much would we be spending based on current percentages?
this.roadSpend = Math.round(this.roadFund * this.roadPercent);
this.fireSpend = Math.round(this.fireFund * this.firePercent);
this.policeSpend = Math.round(this.policeFund * this.policePercent);
var total = this.roadSpend + this.fireSpend + this.policeSpend;
// If we don't have any services on the map, we can bail early
if (total === 0) {
this.roadPercent = 1;
this.firePercent = 1;
this.policePercent = 1;
this.roadEffect = this.MAX_ROAD_EFFECT;
this.policeEffect = this.MAX_POLICESTATION_EFFECT;
this.fireEffect = this.MAX_FIRESTATION_EFFECT;
}
var cashAvailable = this.totalFunds + this.taxFund;
var cashRemaining = cashAvailable;
// How much are we actually going to spend?
var roadValue = 0;
var fireValue = 0;
var policeValue = 0;
// Spending priorities: road, fire, police
if (cashRemaining >= this.roadSpend) roadValue = this.roadSpend;
else roadValue = cashRemaining;
cashRemaining -= roadValue;
if (cashRemaining >= this.fireSpend) fireValue = this.fireSpend;
else fireValue = cashRemaining;
cashRemaining -= fireValue;
if (cashRemaining >= this.policeSpend) policeValue = this.policeSpend;
else policeValue = cashRemaining;
cashRemaining -= policeValue;
if (this.roadFund > 0) this.roadPercent = new Number(roadValue / this.roadFund).toPrecision(2) - 0;
else this.roadPercent = 1;
if (this.fireFund > 0) this.firePercent = new Number(fireValue / this.fireFund).toPrecision(2) - 0;
else this.fireFund = 1;
if (this.policeFund > 0) this.policePercent = new Number(policeValue / this.policeFund).toPrecision(2) - 0;
else this.policeFund = 1;
if (!this.autoBudget || fromWindow) {
// If we were called as the result of a manual budget,
// go ahead and spend the money
if (!fromWindow) {
this.doBudgetSpend(roadValue, fireValue, policeValue, this.cityTax, messageManager);
}
return;
}
// Autobudget
if (cashAvailable >= total) {
// We were able to fully fund services. Go ahead and spend
this.doBudgetSpend(roadValue, fireValue, policeValue, this.cityTax, messageManager);
return;
}
// Uh-oh. Not enough money. Make this the user's problem.
// They don't know it yet, but they're about to get a budget window.
this.autoBudget = false;
messageManager.sendMessage(Messages.AUTOBUDGET_CHANGED, this.autoBudget);
messageManager.sendMessage(Messages.BUDGET_NEEDED);
messageManager.sendMessage(Messages.NO_MONEY);
},
doBudgetSpend : function(roadValue, fireValue, policeValue, taxRate, messageManager) {
this.roadSpend = roadValue;
this.fireSpend = fireValue;
this.policeSpend = policeValue;
this.setTax(taxRate);
var total = this.roadSpend + this.fireSpend + this.policeSpend;
this.spend(-(this.taxFund - total), messageManager);
this.updateFundEffects();
},
updateFundEffects : function() {
// Update effects
this.roadEffect = Micro.MAX_ROAD_EFFECT;
this.policeEffect = Micro.MAX_POLICESTATION_EFFECT;
this.fireEffect = Micro.MAX_FIRESTATION_EFFECT;
if (this.roadFund > 0) this.roadEffect = Math.floor(this.roadEffect * this.roadSpend / this.roadFund);
if (this.fireFund > 0) this.fireEffect = Math.floor(this.fireEffect * this.fireSpend / this.fireFund);
if (this.policeFund > 0) this.policeEffect = Math.floor(this.policeEffect * this.policeSpend / this.policeFund);
},
collectTax : function(gameLevel, census, messageManager) {
this.cashFlow = 0;
this.policeFund = census.policeStationPop * 100;
this.fireFund = census.fireStationPop * 100;
this.roadFund = Math.floor((census.roadTotal + (census.railTotal * 2)) * Micro.RLevels[gameLevel]);
this.taxFund = Math.floor(Math.floor(census.totalPop * census.landValueAverage / 120) * this.cityTax * Micro.FLevels[gameLevel]);
if (census.totalPop > 0) {
this.cashFlow = this.taxFund - (this.policeFund + this.fireFund + this.roadFund);
this.doBudget(messageManager);
} else {
// We don't want roads etc deteriorating when population hasn't yet been established
// (particularly early game)
this.roadEffect = Micro.MAX_ROAD_EFFECT;
this.policeEffect = Micro.MAX_POLICESTATION_EFFECT;
this.fireEffect = Micro.MAX_FIRESTATION_EFFECT;
}
},
setTax : function(amount, messageManager) {
if (amount === this.cityTax) return;
this.cityTax = amount;
if (messageManager !== undefined) messageManager.sendMessage(Messages.TAXRATE_CHANGED, this.cityTax);
},
setFunds : function(amount, messageManager) {
if (amount === this.totalFunds) return;
this.totalFunds = Math.max(0, amount);
if (messageManager !== undefined) messageManager.sendMessage(Messages.FUNDS_CHANGED, this.totalFunds);
},
spend : function(amount, messageManager) {
this.setFunds(this.totalFunds - amount, messageManager);
},
shouldDegradeRoad : function() {
return this.roadEffect < Math.floor(15 * this.MAX_ROAD_EFFECT / 16);
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Valves = function () {
this.changed = false;
this.resValve = 0;
this.comValve = 0;
this.indValve = 0;
this.resCap = false;
this.comCap = false;
this.indCap = false;
}
Micro.Valves.prototype = {
constructor: Micro.Valves,
save : function(saveData) {
saveData.resValve = this.resValve;
saveData.comValve = this.comValve;
saveData.indValve = this.indValve;
},
load : function(saveData, messageManager) {
this.resValve = saveData.resValve;
this.comValve = saveData.comValve;
this.indValve = saveData.indValve;
this.changed = true;
if (messageManager !== undefined) messageManager.sendMessage(Messages.VALVES_UPDATED);
},
setValves : function(gameLevel, census, budget) {
var resPopDenom = 8;
var birthRate = 0.02;
var labourBaseMax = 1.3;
var internalMarketDenom = 3.7;
var projectedIndPopMin = 5.0;
var resRatioDefault = 1.3;
var resRatioMax = 2;
var comRatioMax = 2;
var indRatioMax = 2;
var taxMax = 20;
var taxTableScale = 600;
var employment, labourBase;
var normalizedResPop = census.resPop / resPopDenom;
census.totalPop = Math.round(normalizedResPop + census.comPop + census.indPop);
if (census.resPop > 0) employment = (census.comHist10[1] + census.indHist10[1]) / normalizedResPop;
else employment = 1;
var migration = normalizedResPop * (employment - 1);
var births = normalizedResPop * birthRate;
var projectedResPop = normalizedResPop + migration + births;
// Compute labourBase
var temp = census.comHist10[1] + census.indHist10[1];
if (temp > 0.0) labourBase = (census.resHist10[1] / temp);
else labourBase = 1;
labourBase = Micro.clamp(labourBase, 0.0, labourBaseMax);
var internalMarket = (normalizedResPop + census.comPop + census.indPop) / internalMarketDenom;
var projectedComPop = internalMarket * labourBase;
var projectedIndPop = census.indPop * labourBase * Micro.extMarketParamTable[gameLevel];
projectedIndPop = Math.max(projectedIndPop, projectedIndPopMin);
var resRatio;
if (normalizedResPop > 0) resRatio = projectedResPop / normalizedResPop;
else resRatio = resRatioDefault;
var comRatio;
if (census.comPop > 0) comRatio = projectedComPop / census.comPop;
else comRatio = projectedComPop;
var indRatio;
if (census.indPop > 0) indRatio = projectedIndPop / census.indPop;
else indRatio = projectedIndPop;
resRatio = Math.min(resRatio, resRatioMax);
comRatio = Math.min(comRatio, comRatioMax);
indRatio = Math.min(indRatio, indRatioMax);
// Global tax and game level effects.
var z = Math.min((budget.cityTax + gameLevel), taxMax);
resRatio = (resRatio - 1) * taxTableScale + Micro.taxTable[z];
comRatio = (comRatio - 1) * taxTableScale + Micro.taxTable[z];
indRatio = (indRatio - 1) * taxTableScale + Micro.taxTable[z];
// Ratios are velocity changes to valves.
this.resValve = Micro.clamp(this.resValve + Math.round(resRatio), -Micro.RES_VALVE_RANGE, Micro.RES_VALVE_RANGE);
this.comValve = Micro.clamp(this.comValve + Math.round(comRatio), -Micro.COM_VALVE_RANGE, Micro.COM_VALVE_RANGE);
this.indValve = Micro.clamp(this.indValve + Math.round(indRatio), -Micro.IND_VALVE_RANGE, Micro.IND_VALVE_RANGE);
if (this.resCap && this.resValve > 0) this.resValve = 0;
if (this.comCap && this.comValve > 0) this.comValve = 0;
if (this.indCap && this.indValve > 0) this.indValve = 0;
this.changed = true;
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Tile = function(tileValue, bitMask){
if (!(this instanceof Micro.Tile)) return new Micro.Tile();
//if (arguments.length && (typeof(tileValue) !== 'number' || (arguments.length > 1 && typeof(bitMask) !== 'number') || tileValue < 0)) throw new Error('Invalid parameter');
//if (arguments.length > 1 && (tileValue >= Tile.TILE_COUNT || bitMask < Tile.BIT_START || bitMask >= (Tile.BIT_END << 1))) throw new Error('Invalid parameter');
this._value = tileValue;
// If no value supplied, default to Tile.DIRT
if (this._value === undefined) this._value = Tile.DIRT;
if (arguments.length > 1) this._value |= bitMask;
}
Micro.Tile.prototype = {
constructor: Micro.Tile,
getValue : function() {
return this._value & Tile.BIT_MASK;
},
setValue : function( tileValue ) {
if ( arguments.length === 0 || typeof(tileValue) !== 'number' || tileValue < 0) throw new Error('Invalid parameter');
var existingFlags = 0;
if ( tileValue < Tile.BIT_START ) existingFlags = this._value & Tile.ALLBITS;//this.getFlags();
this._value = tileValue | existingFlags;
},
isBulldozable : function() {
return (this._value & Tile.BULLBIT) > 0;
},
isAnimated : function() {
return (this._value & Tile.ANIMBIT) > 0;
},
isConductive : function() {
return (this._value & Tile.CONDBIT) > 0;
},
isCombustible : function() {
return (this._value & Tile.BURNBIT) > 0;
},
isPowered : function() {
return (this._value & Tile.POWERBIT) > 0;
},
isZone : function() {
return (this._value & Tile.ZONEBIT) > 0;
},
addFlags : function(bitMask) {
if (!arguments.length || typeof(bitMask) !== 'number' || bitMask < Tile.BIT_START || bitMask >= Tile.BIT_END << 1) throw new Error('Invalid parameter');
this._value |= bitMask;
},
removeFlags : function(bitMask) {
if (!arguments.length || typeof(bitMask) !== 'number' || bitMask < Tile.BIT_START || bitMask >= Tile.BIT_END << 1) throw new Error('Invalid parameter');
this._value &= ~bitMask;
},
setFlags : function(bitMask) {
//if (typeof(bitMask) !== 'number' || bitMask < Tile.BIT_START || bitMask >= (Tile.BIT_END << 1)) throw new Error('Invalid parameter');
//if (arguments.length === 0) throw new Error('Tile setFlags called with no arguments');
//if (typeof(bitMask) !== 'number') throw new Error('Tile setFlags called with invalid bitmask ' + bitMask);
// if (bitMask < Tile.BIT_START || bitMask >= (Tile.BIT_END << 1)) throw new Error('Tile setFlags called with out-of-range bitmask ' + bitMask);
var existingValue = this._value & ~Tile.ALLBITS;
this._value = existingValue | bitMask;
},
getFlags : function() {
return this._value & Tile.ALLBITS;
},
getRawValue : function() {
return this._value;
},
set : function(tileValue, bitMask) {
if (arguments.length < 2 || typeof(tileValue) !== 'number' || typeof(bitMask) !== 'number' || tileValue >= Tile.TILE_COUNT) throw new Error('Invalid parameter');
this.setValue(tileValue);
this.setFlags(bitMask);
},
toString : function() {
var value = this.getValue();
var s = 'Tile# ' + value;
s += this.isCombustible() ? ' burning' : '';
s += this.isPowered() ? ' powered' : '';
s += this.isAnimated() ? ' animated' : '';
s += this.isConductive() ? ' conductive' : '';
s += this.isZone() ? ' zone' : '';
s += this.isBulldozable() ? ' bulldozeable' : '';
return s;
}
}
var Tile = {};
// Bit-masks for statusBits
Tile.POWERBIT = 0x8000; // bit 15, tile has power.
Tile.CONDBIT = 0x4000; // bit 14. tile can conduct electricity.
Tile.BURNBIT = 0x2000; // bit 13, tile can be lit.
Tile.BULLBIT = 0x1000; // bit 12, tile is bulldozable.
Tile.ANIMBIT = 0x0800; // bit 11, tile is animated.
Tile.ZONEBIT = 0x0400; // bit 10, tile is the center tile of the zone.
Tile.BLBNBIT = Tile.BULLBIT | Tile.BURNBIT;
Tile.BLBNCNBIT = Tile.BULLBIT | Tile.BURNBIT | Tile.CONDBIT;
Tile.BNCNBIT = Tile.BURNBIT | Tile.CONDBIT;
Tile.ASCBIT = Tile.ANIMBIT | Tile.CONDBIT | Tile.BURNBIT;
Tile.ALLBITS = Tile.POWERBIT | Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT | Tile.ANIMBIT | Tile.ZONEBIT;
Tile.BIT_START = 0x400;
Tile.BIT_END = 0x8000;
Tile.BIT_MASK = Tile.BIT_START - 1;
// TODO Add comment for each tile
Tile.DIRT = 0; // Clear tile
// tile 1 ?
/* Water */
Tile.RIVER = 2;
Tile.REDGE = 3;
Tile.CHANNEL = 4;
Tile.FIRSTRIVEDGE = 5;
// tile 6 -- 19 ?
Tile.LASTRIVEDGE = 20;
Tile.WATER_LOW = Tile.RIVER; // First water tile
Tile.WATER_HIGH = Tile.LASTRIVEDGE; // Last water tile (inclusive)
Tile.TREEBASE = 21;
Tile.WOODS_LOW = Tile.TREEBASE;
Tile.LASTTREE = 36;
Tile.WOODS = 37;
Tile.UNUSED_TRASH1 = 38;
Tile.UNUSED_TRASH2 = 39;
Tile.WOODS_HIGH = Tile.UNUSED_TRASH2; // Why is an 'UNUSED' tile used?
Tile.WOODS2 = 40;
Tile.WOODS3 = 41;
Tile.WOODS4 = 42;
Tile.WOODS5 = 43;
/* Rubble (4 tiles) */
Tile.RUBBLE = 44;
Tile.LASTRUBBLE = 47;
Tile.FLOOD = 48;
// tile 49, 50 ?
Tile.LASTFLOOD = 51;
Tile.RADTILE = 52; // Radio-active contaminated tile
Tile.UNUSED_TRASH3 = 53;
Tile.UNUSED_TRASH4 = 54;
Tile.UNUSED_TRASH5 = 55;
/* Fire animation (8 tiles) */
Tile.FIRE = 56;
Tile.FIREBASE = Tile.FIRE;
Tile.LASTFIRE = 63;
Tile.HBRIDGE = 64; // Horizontal bridge
Tile.ROADBASE = Tile.HBRIDGE;
Tile.VBRIDGE = 65; // Vertical bridge
Tile.ROADS = 66;
Tile.ROADS2 = 67;
Tile.ROADS3 = 68;
Tile.ROADS4 = 69;
Tile.ROADS5 = 70;
Tile.ROADS6 = 71;
Tile.ROADS7 = 72;
Tile.ROADS8 = 73;
Tile.ROADS9 = 74;
Tile.ROADS10 = 75;
Tile.INTERSECTION = 76;
Tile.HROADPOWER = 77;
Tile.VROADPOWER = 78;
Tile.BRWH = 79;
Tile.LTRFBASE = 80; // First tile with low traffic
// tile 81 -- 94 ?
Tile.BRWV = 95;
// tile 96 -- 110 ?
Tile.BRWXXX1 = 111;
// tile 96 -- 110 ?
Tile.BRWXXX2 = 127;
// tile 96 -- 110 ?
Tile.BRWXXX3 = 143;
Tile.HTRFBASE = 144; // First tile with high traffic
// tile 145 -- 158 ?
Tile.BRWXXX4 = 159;
// tile 160 -- 174 ?
Tile.BRWXXX5 = 175;
// tile 176 -- 190 ?
Tile.BRWXXX6 = 191;
// tile 192 -- 205 ?
Tile.LASTROAD = 206;
Tile.BRWXXX7 = 207;
/* Power lines */
Tile.HPOWER = 208;
Tile.VPOWER = 209;
Tile.LHPOWER = 210;
Tile.LVPOWER = 211;
Tile.LVPOWER2 = 212;
Tile.LVPOWER3 = 213;
Tile.LVPOWER4 = 214;
Tile.LVPOWER5 = 215;
Tile.LVPOWER6 = 216;
Tile.LVPOWER7 = 217;
Tile.LVPOWER8 = 218;
Tile.LVPOWER9 = 219;
Tile.LVPOWER10 = 220;
Tile.RAILHPOWERV = 221; // Horizontal rail, vertical power
Tile.RAILVPOWERH = 222; // Vertical rail, horizontal power
Tile.POWERBASE = Tile.HPOWER;
Tile.LASTPOWER = Tile.RAILVPOWERH;
Tile.UNUSED_TRASH6 = 223;
/* Rail */
Tile.HRAIL = 224;
Tile.VRAIL = 225;
Tile.LHRAIL = 226;
Tile.LVRAIL = 227;
Tile.LVRAIL2 = 228;
Tile.LVRAIL3 = 229;
Tile.LVRAIL4 = 230;
Tile.LVRAIL5 = 231;
Tile.LVRAIL6 = 232;
Tile.LVRAIL7 = 233;
Tile.LVRAIL8 = 234;
Tile.LVRAIL9 = 235;
Tile.LVRAIL10 = 236;
Tile.HRAILROAD = 237;
Tile.VRAILROAD = 238;
Tile.RAILBASE = Tile.HRAIL;
Tile.LASTRAIL = 238;
Tile.ROADVPOWERH = 239; /* bogus? */
// Residential zone tiles
Tile.RESBASE = 240; // Empty residential, tiles 240--248
Tile.FREEZ = 244; // center-tile of 3x3 empty residential
Tile.HOUSE = 249; // Single tile houses until 260
Tile.LHTHR = Tile.HOUSE;
Tile.HHTHR = 260;
Tile.RZB = 265; // center tile first 3x3 tile residential
Tile.HOSPITALBASE = 405; // Center of hospital (tiles 405--413)
Tile.HOSPITAL = 409; // Center of hospital (tiles 405--413)
Tile.CHURCHBASE = 414; // Center of church (tiles 414--422)
Tile.CHURCH0BASE = 414; // numbered alias
Tile.CHURCH = 418; // Center of church (tiles 414--422)
Tile.CHURCH0 = 418; // numbered alias
// Commercial zone tiles
Tile.COMBASE = 423; // Empty commercial, tiles 423--431
// tile 424 -- 426 ?
Tile.COMCLR = 427;
// tile 428 -- 435 ?
Tile.CZB = 436;
// tile 437 -- 608 ?
Tile.COMLAST = 609;
// tile 610, 611 ?
// Industrial zone tiles.
Tile.INDBASE = 612; // Top-left tile of empty industrial zone.
Tile.INDCLR = 616; // Center tile of empty industrial zone.
Tile.LASTIND = 620; // Last tile of empty industrial zone.
// Industrial zone population 0, value 0: 621 -- 629
Tile.IND1 = 621; // Top-left tile of first non-empty industry zone.
Tile.IZB = 625; // Center tile of first non-empty industry zone.
// Industrial zone population 1, value 0: 630 -- 638
// Industrial zone population 2, value 0: 639 -- 647
Tile.IND2 = 641;
Tile.IND3 = 644;
// Industrial zone population 3, value 0: 648 -- 656
Tile.IND4 = 649;
Tile.IND5 = 650;
// Industrial zone population 0, value 1: 657 -- 665
// Industrial zone population 1, value 1: 666 -- 674
// Industrial zone population 2, value 1: 675 -- 683
Tile.IND6 = 676;
Tile.IND7 = 677;
// Industrial zone population 3, value 1: 684 -- 692
Tile.IND8 = 686;
Tile.IND9 = 689;
// Seaport
Tile.PORTBASE = 693; // Top-left tile of the seaport.
Tile.PORT = 698; // Center tile of the seaport.
Tile.LASTPORT = 708; // Last tile of the seaport.
Tile.AIRPORTBASE = 709;
// tile 710 ?
Tile.RADAR = 711;
// tile 712 -- 715 ?
Tile.AIRPORT = 716;
// tile 717 -- 744 ?
// Coal power plant (4x4).
Tile.COALBASE = 745; // First tile of coal power plant.
Tile.POWERPLANT = 750; // 'Center' tile of coal power plant.
Tile.LASTPOWERPLANT = 760; // Last tile of coal power plant.
// Fire station (3x3).
Tile.FIRESTBASE = 761; // First tile of fire station.
Tile.FIRESTATION = 765; // 'Center tile' of fire station.
// 769 last tile fire station.
Tile.POLICESTBASE = 770;
// tile 771 -- 773 ?
Tile.POLICESTATION = 774;
// tile 775 -- 778 ?
// Stadium (4x4).
Tile.STADIUMBASE = 779; // First tile stadium.
Tile.STADIUM = 784; // 'Center tile' stadium.
// Last tile stadium 794.
// tile 785 -- 799 ?
Tile.FULLSTADIUM = 800;
// tile 801 -- 810 ?
// Nuclear power plant (4x4).
Tile.NUCLEARBASE = 811; // First tile nuclear power plant.
Tile.NUCLEAR = 816; // 'Center' tile nuclear power plant.
Tile.LASTZONE = 826; // Also last tile nuclear power plant.
Tile.LIGHTNINGBOLT = 827;
Tile.HBRDG0 = 828;
Tile.HBRDG1 = 829;
Tile.HBRDG2 = 830;
Tile.HBRDG3 = 831;
Tile.HBRDG_END = 832;
Tile.RADAR0 = 832;
Tile.RADAR1 = 833;
Tile.RADAR2 = 834;
Tile.RADAR3 = 835;
Tile.RADAR4 = 836;
Tile.RADAR5 = 837;
Tile.RADAR6 = 838;
Tile.RADAR7 = 839;
Tile.FOUNTAIN = 840;
// tile 841 -- 843: fountain animation.
Tile.INDBASE2 = 844;
Tile.TELEBASE = 844;
// tile 845 -- 850 ?
Tile.TELELAST = 851;
Tile.SMOKEBASE = 852;
// tile 853 -- 859 ?
Tile.TINYEXP = 860;
// tile 861 -- 863 ?
Tile.SOMETINYEXP = 864;
// tile 865 -- 866 ?
Tile.LASTTINYEXP = 867;
// tile 868 -- 882 ?
Tile.TINYEXPLAST = 883;
// tile 884 -- 915 ?
Tile.COALSMOKE1 = 916; // Chimney animation at coal power plant (2, 0).
// 919 last animation tile for chimney at coal power plant (2, 0).
Tile.COALSMOKE2 = 920; // Chimney animation at coal power plant (3, 0).
// 923 last animation tile for chimney at coal power plant (3, 0).
Tile.COALSMOKE3 = 924; // Chimney animation at coal power plant (2, 1).
// 927 last animation tile for chimney at coal power plant (2, 1).
Tile.COALSMOKE4 = 928; // Chimney animation at coal power plant (3, 1).
// 931 last animation tile for chimney at coal power plant (3, 1).
Tile.FOOTBALLGAME1 = 932;
// tile 933 -- 939 ?
Tile.FOOTBALLGAME2 = 940;
// tile 941 -- 947 ?
Tile.VBRDG0 = 948;
Tile.VBRDG1 = 949;
Tile.VBRDG2 = 950;
Tile.VBRDG3 = 951;
Tile.NUKESWIRL1 = 952;
Tile.NUKESWIRL2 = 953;
Tile.NUKESWIRL3 = 954;
Tile.NUKESWIRL4 = 955;
// Tiles 956-959 unused (originally)
// TILE_COUNT = 960;
// Extended zones: 956-1019
Tile.CHURCH1BASE = 956;
Tile.CHURCH1 = 960;
Tile.CHURCH2BASE = 965;
Tile.CHURCH2 = 969;
Tile.CHURCH3BASE = 974;
Tile.CHURCH3 = 978;
Tile.CHURCH4BASE = 983;
Tile.CHURCH4 = 987;
Tile.CHURCH5BASE = 992;
Tile.CHURCH5 = 996;
Tile.CHURCH6BASE = 1001;
Tile.CHURCH6 = 1005;
Tile.CHURCH7BASE = 1010;
Tile.CHURCH7 = 1014;
Tile.CHURCH7LAST = 1018;
// Tiles 1020-1023 unused
Tile.TILE_COUNT = 1024;
Tile.TILE_INVALID = -1; // Invalid tile (not used in the world map).
Tile.MIN_SIZE = 16; // Minimum size of tile in pixels
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.PositionMaker = function (width, height) {
if (arguments.length < 2 || typeof(width) !== 'number' || typeof(height) !== 'number' || width < 0 || height < 0) throw new Error('Invalid parameter');
function isNumber(param) {
return typeof(param) === 'number';
}
var validDirs = [Direction.NORTH, Direction.NORTHEAST, Direction.EAST, Direction.SOUTHEAST,
Direction.SOUTH, Direction.SOUTHWEST, Direction.WEST, Direction.NORTHWEST,
Direction.INVALID];
function isDirection(param) {
return isNumber(param) && validDirs.indexOf(param) !== -1;
};
var Position = function(pos, deltaX, deltaY) {
if (arguments.length === 0) {
this.x = 0;
this.y = 0;
return this;
}
// This overloaded constructor accepts the following parameters
// Position(x, y) - positive integral coordinates
// Position(Position p) - assign from existing position
// Position(Position p, Direction d) - assign from existing position and move in direction d
// Position(Position p, deltaX, deltaY) - assign from p and then adjust x/y coordinates
// Check for the possible combinations of arguments, and error out for invalid arguments
if ((arguments.length === 1 || arguments.length === 3) && !(pos instanceof Position)) throw new Error('Invalid parameter');
if (arguments.length === 3 && (!isNumber(deltaX) || !isNumber(deltaY))) throw new Error('Invalid parameter');
if (arguments.length === 2 && ((isNumber(pos) && !isNumber(deltaX)) || (pos instanceof Position && !isNumber(deltaX)) || (pos instanceof Position && isNumber(deltaX) && !isDirection(deltaX)) || (!isNumber(pos) && !(pos instanceof Position)))) throw new Error('Invalid parameter');
var moveOK = true;
if (isNumber(pos)) {
// Coordinates
this.x = pos;
this.y = deltaX;
} else {
this._assignFrom(pos);
if (arguments.length === 2) moveOK = this.move(deltaX);
else if (arguments.length === 3) {
this.x += deltaX;
this.y += deltaY;
}
}
if (this.x < 0 || this.x >= width || this.y < 0 || this.y >= height || !moveOK) throw new Error('Invalid parameter');
};
Position.prototype._assignFrom = function(from) {
this.x = from.x;
this.y = from.y;
};
Position.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ')';
};
Position.prototype.toInt = function() {
return this.y * width + this.x;
};
Position.prototype.move = function(dir) {
switch (dir) {
case Direction.INVALID: return true;
case Direction.NORTH: if (this.y > 0) { this.y--; return true; } break;
case Direction.NORTHEAST: if (this.y > 0 && this.x < width - 1) { this.y--; this.x++; return true; } break;
case Direction.EAST: if (this.x < width - 1) { this.x++; return true; } break;
case Direction.SOUTHEAST: if (this.y < height - 1 && this.x < width - 1) { this.x++; this.y++; return true; } break;
case Direction.SOUTH: if (this.y < height - 1) { this.y++; return true; } break;
case Direction.SOUTHWEST: if (this.y < height - 1 && this.x > 0) { this.y++; this.x--; return true; } break;
case Direction.WEST: if (this.x > 0) { this.x--; return true; } break;
case Direction.NORTHWEST: if (this.y > 0 && this.x > 0) { this.y--; this.x--; return true; } break;
}
return false;
};
return Position;
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.GameMapProps = ['cityCentreX', 'cityCentreY', 'pollutionMaxX', 'pollutionMaxY', 'width', 'height'];
Micro.GameMap = function( width, height, defaultValue ){
/*if (!(this instanceof Micro.GameMap)) return new Micro.GameMap(width, height, defaultValue);
var e = new Error('Invalid parameter');
if (arguments.length > 1 && typeof(width) === 'number' && (width < 1 || height < 1)) throw e;
// Argument shuffling
if (arguments.length === 0) {
width = 120;
height = 100;
defaultValue = new Micro.Tile().getValue();
} else if (arguments.length === 1) {
if (typeof(width) === 'number') {
// Default value
defaultValue = width;
} else {
// Tile
defaultValue = width.getValue();
}
width = 120;
height = 100;
} else if (arguments.length === 2) {
defaultValue = new Micro.Tile().getValue();
} else if (arguments.length === 3) {
if (typeof(defaultValue) === 'object')
defaultValue = defaultValue.getValue();
}
*/
this.isIsland = false;
this.Direction = new Micro.Direction();
this.Position = new Micro.PositionMaker( width, height );
this.width = width;
this.height = height;
this.fsize = this.width * this.height;
/*Object.defineProperties(this,
{width: new Micro.makeConstantDescriptor(width),
height: new Micro.makeConstantDescriptor(height)});*/
this.defaultValue = new Micro.Tile().getValue();
this.data = [];//new Array(this.fsize);
this.tilesData = new M_ARRAY_TYPE(this.fsize);
this.powerData = new M_ARRAY_TYPE(this.fsize);
var i = this.fsize;
while(i--){
this.data[i] = new Micro.Tile( this.defaultValue );
this.tilesData[i] = this.defaultValue;
}
/*console.log(this.data.length)*/
// Generally set externally
this.cityCentreX = Math.floor(this.width * 0.5);
this.cityCentreY = Math.floor(this.height * 0.5);
this.pollutionMaxX = this.cityCentreX;
this.pollutionMaxY = this.cityCentreY;
}
Micro.GameMap.prototype = {
constructor: Micro.GameMap,
save: function( saveData ) {
var i=0, lng;
// GAME PROPS
lng = Micro.GameMapProps.length;
while( i < lng ){
saveData[Micro.GameMapProps[i]] = this[Micro.GameMapProps[i]];
i++;
}
// MAP DATA
//saveData.map = this.data.map(function(t) { return {value: t.getRawValue()}; });
saveData.map = [];
i = 0;
lng = this.fsize;
while( i < lng ){
saveData.map[i] = this.data[i].getRawValue();
i++;
}
// TILES VALUES
saveData.tileValue = [];
i = 0;
lng = this.fsize;
while( i < lng ){
saveData.tileValue[i] = this.tilesData[i];
i++;
}
//saveData.map = this.data.map(function(t) { return {value: t.getRawValue()};});
//saveData.map = this.data.map(function(t) { return {value: t.getRawValue()}; });
//saveData.map = [];//this.tilesData.map(function(t) { return {value: t };});
/*saveData.tileValue = [];
var j = this.fsize;
while( j-- ) saveData.tileValue[j] = this.tilesData[j];
*/
/*saveData.power = [];
var j = this.fsize;
while(j--){
// saveData.map[j] = this.tilesData[j];
saveData.power[j] = this.powerData[j];
}*/
},
load: function( saveData ) {
var x, y, lng, i = 0, map = saveData.map, tiles = saveData.tileValue;
// GAME PROPS
lng = Micro.GameMapProps.length;
while( i < lng ){
this[Micro.GameMapProps[i]] = saveData[Micro.GameMapProps[i]];
i++;
}
// MAP DATA
var isOld = map[0].value !== undefined ? true : false
i = 0;
lng = this.fsize;
while( i < lng ){
x = i % this.width;
y = Math.floor(i / this.width);
if( isOld ) this.setTileValue( x, y, map[i].value );
else this.setTileValue( x, y, map[i] );
i++;
}
// TILES VALUES
i = 0;
lng = this.fsize;
while( i < lng ){
this.tilesData[i] = tiles[i];
i++;
}
/*for (var i = 0, l = Micro.GameMapProps.length; i < l; i++) this[Micro.GameMapProps[i]] = saveData[Micro.GameMapProps[i]];
var map = saveData.map, value;
for (i = 0, l = map.length; i < l; i++){
this.setTileValue(i % this.width, Math.floor(i / this.width), map[i].value);
//value = map[i] || 0;
//this.setTileValue(i % this.width, Math.floor(i / this.width), map[i]);
//this.data[i].setValue(value);
//this.tilesData[i] = value;
}
for (i = 0, l = saveData.tileValue.length; i < l; i++) this.tilesData[i] = saveData.tileValue[i];*/
/*
var power = saveData.power;
for (i = 0, l = power.length; i < l; i++) this.powerData[i] = power[i];
*/
},
/*genFull : function(){
var i = this.data.length;
while(i--){
this.tilesData[i] = this.data[i].getValue();
}
return this.tilesData;
},*/
_calculateIndex : function(x, y) {
return x + y * this.width;
},
testBounds : function(x, y) {
return x >= 0 && y >= 0 && x < this.width && y < this.height;
},
getTile : function(x, y, newTile) {
//var e = new Error('Invalid parameter');
//if (arguments.length < 1) throw e;
// Argument-shuffling
if (typeof(x) === 'object') { y = x.y; x = x.x; }
//if (!this.testBounds(x, y)) throw e;
var width = this.width;
var height = this.height;
if (x < 0 || y < 0 || x >= width || y >= height) {
console.warn('getTile called with bad bounds', x, y);
return new Tile(Tile.TILE_INVALID);
}
var tileIndex = x + y * width;
var tile = this.data[tileIndex];
//var tileIndex = this._calculateIndex(x, y);
// Return the original tile if we're not given a tile to fill
if (!newTile) return tile;
newTile.set(tile);
return tile;
//if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
//return this.data[tileIndex];
},
getTileValue: function( x, y ) {
var e = new Error('Invalid parameter');
if (arguments.length < 1) throw e;
// Argument-shuffling
if (typeof(x) === 'object') { y = x.y; x = x.x; }
if (!this.testBounds(x, y)) throw e;
var tileIndex = this._calculateIndex(x, y);
if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
return this.data[tileIndex].getValue();
},
getTileFlags: function( x, y ) {
var e = new Error('Invalid parameter');
if (arguments.length < 1) throw e;
// Argument-shuffling
if (typeof(x) === 'object') { y = x.y; x = x.x; }
if (!this.testBounds(x, y)) throw e;
var tileIndex = this._calculateIndex(x, y);
if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
return this.data[tileIndex].getFlags();
},
getTiles: function( x, y, w, h ) {
var e = new Error('Invalid parameter');
if (arguments.length < 3) throw e;
// Argument-shuffling
if (arguments.length === 3) { h = w; w = y; y = x.y; x = x.x; }
if (!this.testBounds(x, y)) throw e;
var res = [];
for (var a = y, ylim = y + h; a < ylim; a++) {
res[a - y] = [];
for (var b = x, xlim = x + w; b < xlim; b++) {
var tileIndex = this._calculateIndex(b, a);
//if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
res[a-y].push(this.data[tileIndex]);
}
}
return res;
},
getTileValues: function(x, y, w, h, result) {
result = result || [];
var e = new Error('Invalid parameter');
if (arguments.length < 3) throw e;
// Argument-shuffling
if (arguments.length === 3) { h = w; w = y; y = x.y; x = x.x; }
//if (!this.testBounds(x, y)) throw e;
var width = this.width;
var height = this.height;
// Result is stored in row-major order
for (var a = y, ylim = y + h; a < ylim; a++) {
for (var b = x, xlim = x + w; b < xlim; b++) {
if (a < 0 || b < 0 || a >= height || b >= width) {
result[(a - y) * w + (b - x)] = Tile.TILE_INVALID;
continue;
}
var tileIndex = b + a * width;
//result[(a - y) * w + (b - x)] = this._data[tileIndex].getRawValue();
result[(a - y) * w + (b - x)] = this.data[tileIndex].getRawValue();
}
}
//var res = [];
/*for (var a = y, ylim = y + h; a < ylim; a++) {
res[a - y] = [];
for (var b = x, xlim = x + w; b < xlim; b++) {
var tileIndex = this._calculateIndex(b, a);
if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
res[a-y].push(this.data[tileIndex].getValue());
}
}*/
return result;
},
getTileFromMapOrDefault: function(pos, dir, defaultTile) {
switch (dir) {
case this.Direction.NORTH:
if (pos.y > 0) return this.getTileValue(pos.x, pos.y - 1);
return defaultTile;
case this.Direction.EAST:
if (pos.x < this.width - 1) return this.getTileValue(pos.x + 1, pos.y);
return defaultTile;
case this.Direction.SOUTH:
if (pos.y < this.height - 1) return this.getTileValue(pos.x, pos.y + 1);
return defaultTile;
case this.Direction.WEST:
if (pos.x > 0) return this.getTileValue(pos.x - 1, pos.y);
return defaultTile;
default:
return defaultTile;
}
},
setTile: function(x, y, value, flags) {
//var e = new Error('Invalid parameter');
//if (arguments.length < 3) throw e;
// Argument-shuffling
if (arguments.length === 3) { flags = value; value = y; y = x.y; x = x.x; }
//if (!this.testBounds(x, y)) throw e;
var id = this._calculateIndex( x, y );
this.data[ id ].set( value, flags );
this.tilesData[ id ] = value;
},
setTo: function( x, y, tile ) {
//var e = new Error('Invalid parameter');
//if (arguments.length < 2) throw e;
// Argument-shuffling
if (tile === undefined) { tile = y; y = x.y; x = x.x; }
//if (!this.testBounds(x, y)) throw e;
var id = this._calculateIndex( x, y );
this.data[ id ] = tile;
this.tilesData[ id ] = tile.getValue();
},
setTileValue: function( x, y, value ) {
//var e = new Error('Invalid parameter');
//if (arguments.length < 2) throw e;
// Argument-shuffling
if (arguments.length === 2) { value = y; y = x.y; x = x.x; }
//if (!this.testBounds(x, y)) throw e;
var id = this._calculateIndex( x, y );
//if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
this.data[ id ].setValue( value );
this.tilesData[ id ] = value;
},
setTileFlags: function(x, y, flags) {
var e = new Error('Invalid parameter');
if (arguments.length < 2) throw e;
// Argument-shuffling
if (arguments.length === 2) { flags = y; y = x.y; x = x.x; }
if (!this.testBounds(x, y)) throw e;
var tileIndex = this._calculateIndex(x, y);
//if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
this.data[tileIndex].setFlags(flags);
},
addTileFlags: function(x, y, flags) {
var e = new Error('Invalid parameter');
if (arguments.length < 2) throw e;
// Argument-shuffling
if (arguments.length === 2) { flags = y; y = x.y; x = x.x; }
if (!this.testBounds(x, y)) throw e;
var tileIndex = this._calculateIndex(x, y);
//if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
this.data[tileIndex].addFlags(flags);
},
removeTileFlags: function(x, y, flags) {
var e = new Error('Invalid parameter');
if (arguments.length < 2) throw e;
// Argument-shuffling
if (arguments.length === 2) { flags = y; y = x.y; x = x.x; }
if (!this.testBounds(x, y)) throw e;
var tileIndex = this._calculateIndex(x, y);
//if (!(tileIndex in this.data)) this.data[tileIndex] = new Micro.Tile(this.defaultValue);
this.data[tileIndex].removeFlags(flags);
},
putZone: function(centreX, centreY, centreTile, size) {
var e = new Error('Invalid parameter');
if (!this.testBounds(centreX, centreY) || !this.testBounds(centreX - 1 + size, centreY - 1 + size)) throw e;
var tile = centreTile - 1 - size;
var startX = centreX - 1;
var startY = centreY - 1;
for (var y = startY; y < startY + size; y++) {
for (var x = startX; x < startX + size; x++) {
if (x === centreX && y === centreY) this.setTo(x, y, new Micro.Tile(tile, Tile.BNCNBIT | Tile.ZONEBIT));
else this.setTo(x, y, new Micro.Tile(tile, Tile.BNCNBIT));
tile += 1;
}
}
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.TERRAIN_CREATE_ISLAND = 0;
Micro.TERRAIN_TREE_LEVEL = -1;//level for tree creation
Micro.TERRAIN_LAKE_LEVEL = -1; //level for river curviness; -1==auto, 0==none, >0==level
Micro.TERRAIN_CURVE_LEVEL = -1;//level for lake creation; -1==auto, 0==none, >0==level
Micro.ISLAND_RADIUS = 18;
Micro.generateMap = function() {
// w = w || Micro.MAP_WIDTH;
// h = h || Micro.MAP_HEIGHT;
this.SRMatrix = [
[ 0, 0, 3, 3, 0, 0 ],
[ 0, 3, 2, 2, 3, 0 ],
[ 3, 2, 2, 2, 2, 3 ],
[ 3, 2, 2, 2, 2, 3 ],
[ 0, 3, 2, 2, 3, 0 ],
[ 0, 0, 3, 3, 0, 0 ]
/*[0, 0, Tile.REDGE, Tile.REDGE, 0, 0],
[0, Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.REDGE, 0],
[Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE],
[Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE],
[0, Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.REDGE, 0],
[0, 0, Tile.REDGE, Tile.REDGE, 0, 0]*/
];
this.BRMatrix = [
[ 0, 0, 0, 3, 3, 3, 0, 0, 0 ],
[ 0, 0, 3, 2, 2, 2, 3, 0, 0 ],
[ 0, 3, 2, 2, 2, 2, 2, 3, 0 ],
[ 3, 2, 2, 2, 2, 2, 2, 2, 3 ],
[ 3, 2, 2, 2, 4, 2, 2, 2, 3 ],
[ 3, 2, 2, 2, 2, 2, 2, 2, 3 ],
[ 0, 3, 2, 2, 2, 2, 2, 3, 0 ],
[ 0, 0, 3, 2, 2, 2, 3, 0, 0 ],
[ 0, 0, 0, 3, 3, 3, 0, 0, 0 ]
/*[0, 0, 0, Tile.REDGE, Tile.REDGE, Tile.REDGE, 0, 0, 0],
[0, 0, Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE, 0, 0],
[0, Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE, 0],
[Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE],
[Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.CHANNEL, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE],
[Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE],
[0, Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE, 0],
[0, 0, Tile.REDGE, Tile.RIVER, Tile.RIVER, Tile.RIVER, Tile.REDGE, 0, 0],
[0, 0, 0, Tile.REDGE, Tile.REDGE, Tile.REDGE, 0, 0, 0]*/
];
this.riverEdge = [
13, 13, 17, 15,
5 , 2 , 19, 17,
9 , 11, 2 , 13,
7 , 9 , 5 , 2
];
/*this.riverEdges = [
13 | Tile.BULLBIT, 13 | Tile.BULLBIT, 17 | Tile.BULLBIT, 15 | Tile.BULLBIT,
5 | Tile.BULLBIT, 2, 19 | Tile.BULLBIT, 17 | Tile.BULLBIT,
9 | Tile.BULLBIT, 11 | Tile.BULLBIT, 2, 13 | Tile.BULLBIT,
7 | Tile.BULLBIT, 9 | Tile.BULLBIT, 5 | Tile.BULLBIT, 2
];*/
//this.map = new Micro.GameMap(w, h);
// Construct land.
/*if (Micro.TERRAIN_CREATE_ISLAND < 0) {
if (Random.getRandom(100) < 10) {
this.makeIsland();
return this.map;
}
}
if (Micro.TERRAIN_CREATE_ISLAND === 1) this.makeNakedIsland();
else this.clearMap();
// Lay a river.
if (Micro.TERRAIN_CURVE_LEVEL !== 0) {
var terrainXStart = 40 + Random.getRandom(this.map.width - 80);
var terrainYStart = 33 + Random.getRandom(this.map.height - 67);
var terrainPos = new this.map.Position(terrainXStart, terrainYStart);
this.doRivers(terrainPos);
}
// Lay a few lakes.
if (Micro.TERRAIN_LAKE_LEVEL !== 0) this.makeLakes();
this.smoothRiver();
// And add trees.
if (Micro.TERRAIN_TREE_LEVEL !== 0) this.doTrees();
return this.map;*/
};
Micro.generateMap.prototype = {
constructor: Micro.generateMap,
construct : function( w, h ){
Micro.TERRAIN_TREE_LEVEL = -1;
Micro.TERRAIN_LAKE_LEVEL = -1;
Micro.TERRAIN_CURVE_LEVEL = -1;
Micro.ISLAND_RADIUS = 18;
//console.time("start newmap");
this.map = new Micro.GameMap( w || Micro.MAP_WIDTH, h || Micro.MAP_HEIGHT );
//this.makeIsland();
//this.makeLand();
//return this.map;
Micro.TERRAIN_CREATE_ISLAND = Random.getRandom(2) - 1;
if (Micro.TERRAIN_CREATE_ISLAND < 0) {
if (Random.getRandom(100) < 10) {
this.makeIsland();
return this.map;
}
}
if (Micro.TERRAIN_CREATE_ISLAND === 1) this.makeNakedIsland();
else this.clearMap();
// Lay a river.
if (Micro.TERRAIN_CURVE_LEVEL !== 0) {
var terrainXStart = 40 + Random.getRandom( this.map.width - 79 );
var terrainYStart = 33 + Random.getRandom( this.map.height - 66 );
var terrainPos = new this.map.Position( terrainXStart, terrainYStart );
this.doRivers( terrainPos );
}
// Lay a few lakes.
if (Micro.TERRAIN_LAKE_LEVEL !== 0) this.makeLakes();
//this.smoothWater();
this.smoothRiver();
// And add trees.
if (Micro.TERRAIN_TREE_LEVEL !== 0) this.doTrees();
//console.timeEnd("start newmap");
return this.map;
},
clearMap : function() {
var x, y, W = this.map.width, H = this.map.height;
for ( x = 0; x < W; x++) {
for ( y = 0; y < H; y++) {
//this.map.setTo(x, y, new Micro.Tile(Tile.DIRT));
this.map.setTile(x, y, Tile.DIRT, 0);
}
}
},
clearUnnatural : function() {
var x, y, tileValue, W = this.map.width, H = this.map.height;
for ( x = 0; x < W; x++) {
for ( y = 0; y < H; y++) {
tileValue = this.map.getTileValue(x, y);
if (tileValue > Tile.WOODS)
this.map.setTile(x, y, Tile.DIRT, 0);
//this.map.setTo( x, y, new Micro.Tile( Tile.DIRT ) );
}
}
},
makeNakedIsland : function() {
this.map.isIsland = true;
var terrainIslandRadius = Micro.ISLAND_RADIUS;
var x, y, mapX, mapY, W = this.map.width, H = this.map.height;
for ( y = 0; y < H; y++)
{
for ( x = 0; x < W; x++)
{
//map[y][x] = RIVER;
//this.map.setTo(x, y, new Micro.Tile(Tile.RIVER));
this.map.setTile(x, y, Tile.RIVER, 0);
}
}
for ( y = 5; y < H - 5; y++)
{
for ( x = 5; x < W - 5; x++)
{
//map[y][x] = DIRT;
//this.map.setTo(x, y, new Micro.Tile(Tile.DIRT));
this.map.setTile(x, y, Tile.DIRT, 0);
}
}
/*for ( x = 0; x < W; x++ ) {
for (y = 0; y < H; y++) {
if ((x < 5) || (x >= W - 5) || (y < 5) || (y >= H - 5)) {
//this.map.setTile(x, y, Tile.RIVER, 0);//
this.map.setTo(x, y, new Micro.Tile(Tile.RIVER));
} else {
//this.map.setTile(x, y, Tile.DIRT, 0);///
this.map.setTo(x, y, new Micro.Tile(Tile.DIRT));
}
}
}*/
for (x = 0; x < W - 5; x += 2) {
mapY = Random.getERandom(terrainIslandRadius+1);
this.plopBRiver(new this.map.Position(x, mapY));
mapY = (H - 10) - Random.getERandom(terrainIslandRadius+1);
this.plopBRiver( new this.map.Position( x, mapY ))
this.plopSRiver( new this.map.Position( x, 0 ));
this.plopSRiver( new this.map.Position( x, H - 6 ));
}
for ( y = 0; y < H - 5; y += 2 ) {
mapX = Random.getERandom( terrainIslandRadius+1 );
this.plopBRiver( new this.map.Position( mapX, y ));
mapX = W - 10 - Random.getERandom(terrainIslandRadius+1);
this.plopBRiver( new this.map.Position( mapX, y ));
this.plopSRiver( new this.map.Position( 0, y ));
this.plopSRiver( new this.map.Position( W - 6, y ));
}
},
/*makeLand : function() {
console.time("start land");
if (Micro.TERRAIN_CURVE_LEVEL !== 0) {
var terrainXStart = 40 + Random.getRandom( this.map.width - 80 );
var terrainYStart = 33 + Random.getRandom( this.map.height - 67 );
var terrainPos = new this.map.Position( terrainXStart, terrainYStart );
console.time("start river");
this.doRivers( terrainPos );
console.timeEnd("start river");
}
// Lay a few lakes.
console.time("start lake");
if (Micro.TERRAIN_LAKE_LEVEL !== 0) this.makeLakes();
console.timeEnd("start lake");
console.time("start smoothWater");
this.smoothWater();
console.timeEnd("start smoothWater");
console.time("start smoothRiver");
this.smoothRiver();
console.timeEnd("start smoothRiver");
// And add trees.
console.time("start tree");
if (Micro.TERRAIN_TREE_LEVEL !== 0) this.doTrees();
console.timeEnd("start tree")
console.timeEnd("start land");
},*/
makeIsland : function() {
//console.time("start island");
this.makeNakedIsland();
//this.smoothWater();
this.smoothRiver();
this.doTrees();
//console.timeEnd("start island");
},
makeLakes : function() {
var x, y, W = this.map.width, H = this.map.height;
var numLakes;
if (Micro.TERRAIN_LAKE_LEVEL < 0) numLakes = Random.getRandom(11);
else numLakes = Micro.TERRAIN_LAKE_LEVEL * 0.5;
while (numLakes > 0) {
x = Random.getRandom( W - 20) + 10;
y = Random.getRandom( H - 19) + 10;
this.makeSingleLake( new this.map.Position(x, y) );
numLakes--;
}
},
makeSingleLake : function( pos ) {
var numPlops = Random.getRandom(13) + 2;
while (numPlops > 0) {
var plopPos = new this.map.Position(pos, Random.getRandom(13) - 6, Random.getRandom(13) - 6);
if (Random.getRandom(5)) this.plopSRiver(plopPos);
else this.plopBRiver( plopPos );
numPlops--;
}
},
treeSplash : function( x, y ) {
var numTrees;
if (Micro.TERRAIN_TREE_LEVEL < 0) numTrees = Random.getRandom(150) + 50;
else numTrees = Random.getRandom(100 + (Micro.TERRAIN_TREE_LEVEL * 2)) + 50;
var treePos = new this.map.Position(x, y);
while (numTrees > 0) {
var dir = Direction.NORTH + Random.getRandom(7);
treePos.move( dir );
// XXX Should use the fact that positions return success/failure for moves
if (!this.map.testBounds(treePos.x, treePos.y)) return;
if ( this.map.getTileValue(treePos) === Tile.DIRT ){
//this.map.setTo(treePos, new Micro.Tile( Tile.WOODS, Tile.BLBNBIT ));
this.map.setTile(treePos, Tile.WOODS, Tile.BLBNBIT);
}
numTrees--;
}
},
doTrees : function() {
var i, x, y , amount, W = this.map.width, H = this.map.height;
if (Micro.TERRAIN_TREE_LEVEL < 0) amount = Random.getRandom(100) + 50;
else amount = Micro.TERRAIN_TREE_LEVEL + 3;
for ( i = 0; i < amount; i++) {
x = Random.getRandom( W - 1);
y = Random.getRandom( H - 1);
this.treeSplash( x, y );
}
this.smoothTrees();
this.smoothTrees();
},
smoothRiver : function() {
var x, y, z, W = this.map.width, H = this.map.height, xt, yt, tt, tb;
var dx = [-1, 0, 1, 0];
var dy = [0, 1, 0, -1];
for ( x = 0; x < W; x++) {
for ( y = 0; y < H; y++) {
if (this.map.getTileValue(x, y) === Tile.REDGE) {
var bitIndex = 0;
for ( z = 0; z < 4; z++) {
bitIndex = bitIndex << 1;
xt = x + dx[z];
yt = y + dy[z];
tb = this.map.testBounds( xt, yt );
if(tb){
tt = this.map.getTileValue( xt, yt );
if( tt !== Tile.DIRT && ( tt < Tile.WOODS_LOW || tt > Tile.WOODS_HIGH ) ) bitIndex++;
}
//if (this.map.testBounds(xTemp, yTemp) && this.map.getTileValue(xTemp, yTemp) !== Tile.DIRT && (this.map.getTileValue(xTemp, yTemp) < Tile.WOODS_LOW || this.map.getTileValue(xTemp, yTemp) > Tile.WOODS_HIGH)) {
// bitIndex++;
//}
}
/*var temp = this.riverEdges[bitIndex & 15];
if ( temp !== Tile.RIVER && Random.getRandom(1) ){ temp++; }
this.map.setTo(x, y, new Micro.Tile(temp));*/
//this.map.setTile( x, y, temp );
//this.map.setTileValue(x, y, temp, 0);
var temp = this.riverEdge[bitIndex & 15];
if ( temp !== Tile.RIVER && Random.getRandom(2) ){ temp++; }
// if ( temp !== Tile.RIVER) this.map.setTo(x, y, new Micro.Tile(temp, Tile.BULLBIT));
//else
this.map.setTile( x, y, temp, Tile.BULLBIT );
//this.map.setTo(x, y, new Micro.Tile(temp));
}
}
}
},
isTree : function(tileValue) {
return tileValue >= Tile.WOODS_LOW && tileValue <= Tile.WOODS_HIGH;
},
smoothTrees : function() {
var x, y, W = this.map.width, H = this.map.height;
for ( x = 0; x < W; x++) {
for ( y = 0; y < H; y++) {
if (this.isTree(this.map.getTileValue(x, y)))
this.smoothTreesAt( x, y, false);
}
}
},
smoothTreesAt : function(x, y, preserve) {
var dx = [-1, 0, 1, 0 ];
var dy = [ 0, 1, 0, -1 ];
var treeTable = [
0, 0, 0, 34,
0, 0, 36, 35,
0, 32, 0, 33,
30, 31, 29, 37
];
if (!this.isTree(this.map.getTileValue(x, y))) return;
var bitIndex = 0;
for (var i = 0; i < 4; i++) {
bitIndex = bitIndex << 1;
var xTemp = x + dx[i];
var yTemp = y + dy[i];
if (this.map.testBounds(xTemp, yTemp) && this.isTree(this.map.getTileValue(xTemp, yTemp))) bitIndex++;
}
var temp = treeTable[ bitIndex & 15 ];
if (temp) {
if (temp !== Tile.WOODS) {
if ((x + y) & 1) temp -= 8;
}
//this.map.setTo(x, y, new Micro.Tile(temp, Tile.BLBNBIT));
this.map.setTile(x, y, temp, Tile.BLBNBIT);
} else {
//if (!preserve) this.map.setTo(x, y, new Micro.Tile(temp));
if (!preserve) this.map.setTile(x, y, temp, 0);
//if (!preserve) this.map.setTileValue(x, y, temp, 0);
}
},
doRivers : function(terrainPos) {
var riverDir = Direction.NORTH + Random.getRandom(3) * 2;
this.doBRiver( terrainPos, riverDir, riverDir );
riverDir = Direction.rotate180(riverDir);
var terrainDir = this.doBRiver( terrainPos, riverDir, riverDir );
riverDir = Direction.NORTH + Random.getRandom(3) * 2;
this.doSRiver( terrainPos, riverDir, terrainDir );
},
doBRiver : function( riverPos, riverDir, terrainDir) {
var rate1, rate2;
if (Micro.TERRAIN_CURVE_LEVEL < 0) {
rate1 = 100;
rate2 = 200;
} else {
rate1 = Micro.TERRAIN_CURVE_LEVEL + 10;
rate2 = Micro.TERRAIN_CURVE_LEVEL + 100;
}
var pos = new this.map.Position(riverPos);
while (this.map.testBounds(pos.x + 4, pos.y + 4)) {
this.plopBRiver(pos);
if (Random.getRandom(rate1+1) < 10) {
terrainDir = riverDir;
} else {
if (Random.getRandom(rate2+1) > 90) terrainDir = Direction.rotate45(terrainDir);
if (Random.getRandom(rate2+1) > 90) terrainDir = Direction.rotate45(terrainDir, 7);
}
pos.move(terrainDir);
}
return terrainDir;
},
doSRiver : function(riverPos, riverDir, terrainDir) {
var rate1, rate2;
if (Micro.TERRAIN_CURVE_LEVEL < 0) {
rate1 = 100;
rate2 = 200;
} else {
rate1 = Micro.TERRAIN_CURVE_LEVEL + 10;
rate2 = Micro.TERRAIN_CURVE_LEVEL + 100;
}
var pos = new this.map.Position(riverPos);
while (this.map.testBounds(pos.x + 3, pos.y + 3)) {
this.plopSRiver(pos);
if (Random.getRandom(rate1+1) < 10) {
terrainDir = riverDir;
} else {
if (Random.getRandom(rate2+1) > 90) terrainDir = Direction.rotate45(terrainDir);
if (Random.getRandom(rate2+1) > 90) terrainDir = Direction.rotate45(terrainDir, 7);
}
pos.move(terrainDir);
}
return terrainDir;
},
putOnMap : function(newVal, x, y) {
if (newVal === 0) return;
if (!this.map.testBounds(x, y)) return;
var tmp = this.map.getTileValue(x, y);
if (tmp !== Tile.DIRT) {
if (tmp === Tile.RIVER && newVal !== Tile.CHANNEL) return;
if (tmp === Tile.CHANNEL) return;
}
//this.map.setTo(x, y, new Micro.Tile(newVal));
this.map.setTile(x, y, newVal, 0);
},
plopBRiver : function(pos) {
for (var x = 0; x < 9; x++) {
for (var y = 0; y < 9; y++) {
this.putOnMap(this.BRMatrix[y][x], pos.x + x, pos.y + y);
}
}
},
plopSRiver : function(pos) {
for (var x = 0; x < 6; x++) {
for (var y = 0; y < 6; y++) {
this.putOnMap(this.SRMatrix[y][x], pos.x + x, pos.y + y);
}
}
},
smoothWater : function() {
var x, y, tile, pos, dir, W = this.map.width, H = this.map.height;
for (x = 0; x < W; x++) {
for (y = 0; y < H; y++) {
tile = this.map.getTileValue(x, y);
// if (tile >= Tile.WATER_Tile.LOW && tile <= Tile.WATER_Tile.HIGH) {
if (tile >= Tile.WATER_LOW && tile <= Tile.WATER_HIGH) {
pos = new this.map.Position(x, y);
for (dir = Direction.BEGIN; dir < Direction.END; dir = Direction.increment90(dir)) {
tile = this.map.getTileFromMapOrDefault(pos, dir, Tile.WATER_LOW);
// If nearest object is not water:
if (tile < Tile.WATER_LOW || tile > Tile.WATER_HIGH) {
//this.map.setTo(x, y, new Micro.Tile(Tile.REDGE)); // set river edge
this.map.setTile(x, y, Tile.REDGE, 0);
break; // Continue with next tile
}
}
}
}
}
for (x = 0; x < W; x++) {
for (y = 0; y < H; y++) {
tile = this.map.getTileValue(x, y);
if (tile !== Tile.CHANNEL && tile >= Tile.WATER_LOW && tile <= Tile.WATER_HIGH) {
var makeRiver = true;
pos = new this.map.Position(x, y);
for (dir = Direction.BEGIN; dir < Direction.END; dir = Direction.increment90(dir)) {
tile = this.map.getTileFromMapOrDefault(pos, dir, Tile.WATER_LOW);
if (tile < Tile.WATER_LOW || tile > Tile.WATER_HIGH) {
makeRiver = false;
break;
}
}
//if (makeRiver) this.map.setTo(x, y, new Micro.Tile(Tile.RIVER));
if (makeRiver) this.map.setTile(x, y, Tile.RIVER, 0);
}
}
}
for (x = 0; x < W; x++) {
for (y = 0; y < H; y++) {
tile = this.map.getTileValue(x, y);
if (tile >= Tile.WOODS_LOW && tile <= Tile.WOODS_HIGH) {
pos = new this.map.Position(x, y);
for (dir = Direction.BEGIN; dir < Direction.END; dir = Direction.increment90(dir)) {
tile = this.map.getTileFromMapOrDefault(pos, dir, Tile.TILE_INVALID);
if (tile === Tile.RIVER || tile === Tile.CHANNEL) {
//this.map.setTo(x, y, new Micro.Tile(Tile.REDGE)); // make it water's edge
this.map.setTile(x, y, Tile.REDGE, 0);
break;
}
}
}
}
}
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.unwrapTile = function(f) {
return function(tile) {
if (tile instanceof Micro.Tile) tile = tile.getValue();
return f.call(null, tile);
}
};
Micro.canBulldoze = Micro.unwrapTile(function(tileValue) {
return (tileValue >= Tile.FIRSTRIVEDGE && tileValue <= Tile.LASTRUBBLE) ||
(tileValue >= Tile.POWERBASE + 2 && tileValue <= Tile.POWERBASE + 12) ||
(tileValue >= Tile.TINYEXP && tileValue <= Tile.LASTTINYEXP + 2);
});
Micro.isCommercial = Micro.unwrapTile(function(tile) { return tile >= Tile.COMBASE && tile < Tile.INDBASE; });
//Micro.isDriveable = Micro.unwrapTile(function(tile) { return (tile >= Tile.ROADBASE && tile <= Tile.LASTRAIL) || tile === Tile.RAILPOWERV || tile === Tile.RAILPOWERH; });
Micro.isDriveable = Micro.unwrapTile(function(tile) { return (tile >= Tile.ROADBASE && tile <= Tile.LASTRAIL) || tile === Tile.RAILHPOWERV || tile === Tile.RAILVPOWERH; });
Micro.isFire = Micro.unwrapTile(function(tile) { return tile >= Tile.FIREBASE && tile < Tile.ROADBASE;});
Micro.isFlood = Micro.unwrapTile(function(tile) { return tile >= Tile.FLOOD && tile < Tile.LASTFLOOD;});
Micro.isIndustrial = Micro.unwrapTile(function(tile) { return tile >= Tile.INDBASE && tile < Tile.PORTBASE; });
Micro.isManualExplosion = Micro.unwrapTile(function(tile) { return tile >= Tile.TINYEXP && tile <= Tile.LASTTINYEXP; });
Micro.isRail = Micro.unwrapTile(function(tile) { return tile >= Tile.RAILBASE && tile < Tile.RESBASE; });
Micro.isResidential = Micro.unwrapTile(function(tile) { return tile >= Tile.RESBASE && tile < Tile.HOSPITALBASE; });
Micro.isRoad = Micro.unwrapTile(function(tile) { return tile >= Tile.ROADBASE && tile < Tile.POWERBASE; });
Micro.normalizeRoad = Micro.unwrapTile(function(tile) { return (tile >= Tile.ROADBASE && tile <= Tile.LASTROAD + 1) ? (tile & 15) + 64 : tile; });
Micro.isCommercialZone = function(tile) { return tile.isZone() && Micro.isCommercial(tile); };
Micro.isIndustrialZone = function(tile) { return tile.isZone() && Micro.isIndustrial(tile); };
Micro.isResidentialZone = function(tile) { return tile.isZone() && Micro.isResidential(tile);};
Micro.randomFire = function() { return new Micro.Tile(Tile.FIRE + (Random.getRandom16() & 3), Tile.ANIMBIT); };
Micro.randomRubble = function() { return new Micro.Tile(Tile.RUBBLE + (Random.getRandom16() & 3), Tile.BULLBIT); };
Micro.HOSPITAL = function() { };
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.checkBigZone = function(tileValue) {
var result;
switch (tileValue) {
case Tile.POWERPLANT:
case Tile.PORT:
case Tile.NUCLEAR:
case Tile.STADIUM:
result = {zoneSize: 4, deltaX: 0, deltaY: 0};
break;
case Tile.POWERPLANT + 1:
case Tile.COALSMOKE3:
case Tile.COALSMOKE3 + 1:
case Tile.COALSMOKE3 + 2:
case Tile.PORT + 1:
case Tile.NUCLEAR + 1:
case Tile.STADIUM + 1:
result = {zoneSize: 4, deltaX: -1, deltaY: 0};
break;
case Tile.POWERPLANT + 4:
case Tile.PORT + 4:
case Tile.NUCLEAR + 4:
case Tile.STADIUM + 4:
result = {zoneSize: 4, deltaX: 0, deltaY: -1};
break;
case Tile.POWERPLANT + 5:
case Tile.PORT + 5:
case Tile.NUCLEAR + 5:
case Tile.STADIUM + 5:
result = {zoneSize: 4, deltaX: -1, deltaY: -1};
break;
case Tile.AIRPORT: result = {zoneSize: 6, deltaX: 0, deltaY: 0}; break;
case Tile.AIRPORT + 1: result = {zoneSize: 6, deltaX: -1, deltaY: 0}; break;
case Tile.AIRPORT + 2: result = {zoneSize: 6, deltaX: -2, deltaY: 0}; break;
case Tile.AIRPORT + 3: result = {zoneSize: 6, deltaX: -3, deltaY: 0}; break;
case Tile.AIRPORT + 6: result = {zoneSize: 6, deltaX: 0, deltaY: -1}; break;
case Tile.AIRPORT + 7: result = {zoneSize: 6, deltaX: -1, deltaY: -1}; break;
case Tile.AIRPORT + 8: result = {zoneSize: 6, deltaX: -2, deltaY: -1}; break;
case Tile.AIRPORT + 9: result = {zoneSize: 6, deltaX: -3, deltaY: -1}; break;
case Tile.AIRPORT + 12: result = {zoneSize: 6, deltaX: 0, deltaY: -2}; break;
case Tile.AIRPORT + 13: result = {zoneSize: 6, deltaX: -1, deltaY: -2}; break;
case Tile.AIRPORT + 14: result = {zoneSize: 6, deltaX: -2, deltaY: -2}; break;
case Tile.AIRPORT + 15: result = {zoneSize: 6, deltaX: -3, deltaY: -2}; break;
case Tile.AIRPORT + 18: result = {zoneSize: 6, deltaX: 0, deltaY: -3}; break;
case Tile.AIRPORT + 19: result = {zoneSize: 6, deltaX: -1, deltaY: -3}; break;
case Tile.AIRPORT + 20: result = {zoneSize: 6, deltaX: -2, deltaY: -3}; break;
case Tile.AIRPORT + 21: result = {zoneSize: 6, deltaX: -3, deltaY: -3}; break;
default: result = {zoneSize: 0, deltaX: 0, deltaY: 0}; break;
}
return result;
};
Micro.checkZoneSize = function(tileValue) {
if ((tileValue >= Tile.RESBASE - 1 && tileValue <= Tile.PORTBASE - 1) ||
(tileValue >= Tile.LASTPOWERPLANT + 1 && tileValue <= Tile.POLICESTATION + 4) ||
(tileValue >= Tile.CHURCH1BASE && tileValue <= Tile.CHURCH7LAST)) {
return 3;
}
if ((tileValue >= Tile.PORTBASE && tileValue <= Tile.LASTPORT) ||
(tileValue >= Tile.COALBASE && tileValue <= Tile.LASTPOWERPLANT) ||
(tileValue >= Tile.STADIUMBASE && tileValue <= Tile.LASTZONE)) {
return 4;
}
return 0;
};
Micro.fireZone = function(map, x, y, blockMaps) {
var tileValue = map.getTileValue(x, y);
var zoneSize = 2;
// A zone being on fire naturally hurts growth
var value = blockMaps.rateOfGrowthMap.worldGet(x, y);
value = Micro.clamp(value - 20, -200, 200);
blockMaps.rateOfGrowthMap.worldSet(x, y, value);
if (tileValue === Tile.AIRPORT) zoneSize = 5;
else if (tileValue >= Tile.PORTBASE) zoneSize = 3;
else if (tileValue < Tile.PORTBASE) zoneSize = 2;
// Make remaining tiles of the zone bulldozable
for (var xDelta = -1; xDelta < zoneSize; xDelta++) {
for (var yDelta = -1; yDelta < zoneSize; yDelta++) {
var xTem = x + xDelta;
var yTem = y + yDelta;
if (!map.testBounds(xTem, yTem)) continue;
if (map.getTileValue(xTem, yTem >= Tile.ROADBASE)) map.addTileFlags(xTem, yTem, Tile.BULLBIT);
}
}
};
Micro.getLandPollutionValue = function(blockMaps, x, y) {
var landValue = blockMaps.landValueMap.worldGet(x, y);
landValue -= blockMaps.pollutionDensityMap.worldGet(x, y);
if (landValue < 30) return 0;
if (landValue < 80) return 1;
if (landValue < 150) return 2;
return 3;
};
Micro.incRateOfGrowth = function(blockMaps, x, y, growthDelta) {
var currentRate = blockMaps.rateOfGrowthMap.worldGet(x, y);
// TODO why the scale of 4 here
var newValue = Micro.clamp(currentRate + growthDelta * 4, -200, 200);
blockMaps.rateOfGrowthMap.worldSet(x, y, newValue);
};
// Calls map.putZone after first checking for flood, fire
// and radiation
Micro.putZone = function(map, x, y, centreTile, isPowered) {
for (var dY = 0; dY < 3; dY++) {
for (var dX = 0; dX < 3; dX++) {
var tileValue = map.getTileValue(x + dX, y + dY);
if (tileValue >= Tile.FLOOD && tileValue < Tile.ROADBASE) return;
}
}
map.putZone(x, y, centreTile, 3);
map.addTileFlags(x, y, Tile.BULLBIT);
if (isPowered) map.addTileFlags(x, y, Tile.POWERBIT);
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.pixToWorld = function(p) {
return p >> 4;
};
Micro.worldToPix = function(w) {
return w << 4;
};
// Attempt to move 45° towards the desired direction, either
// clockwise or anticlockwise, whichever gets us there quicker
Micro.turnTo = function(presentDir, desiredDir) {
if (presentDir === desiredDir)
return presentDir;
if (presentDir < desiredDir) {
// select clockwise or anticlockwise
if (desiredDir - presentDir < 4) presentDir++;
else presentDir--;
} else {
if (presentDir - desiredDir < 4) presentDir--;
else presentDir++;
}
if (presentDir > 8) presentDir = 1;
if (presentDir < 1) presentDir = 8;
return presentDir;
};
Micro.absoluteValue = function(x) {
return Math.abs(x);
};
Micro.getTileValue = function(map, x, y) {
var wX = Micro.pixToWorld(x);
var wY = Micro.pixToWorld(y);
if (wX < 0 || wX >= map.width || wY < 0 || wY >= map.height) return -1;
return map.getTileValue(wX, wY);
};
// Choose the best direction to get from the origin to the destination
// If the destination is equidistant in both x and y deltas, a diagonal
// will be chosen, otherwise the most 'dominant' difference will be selected
// (so if a destination is 4 units north and 2 units east, north will be chosen).
// This code seems to always choose south if we're already there which seems like
// a bug
Micro.directionTable = [0, 3, 2, 1, 3, 4, 5, 7, 6, 5, 7, 8, 1];
Micro.getDir = function(orgX, orgY, destX, destY) {
var deltaX = destX - orgX;
var deltaY = destY - orgY;
var i;
if (deltaX < 0) {
if (deltaY < 0) { i = 11; } else { i = 8; }
} else {
if (deltaY < 0) { i = 2; } else { i = 5; }
}
deltaX = Math.abs(deltaX);
deltaY = Math.abs(deltaY);
if (deltaX * 2 < deltaY) i++;
else if (deltaY * 2 < deltaX) i--;
if (i < 0 || i > 12) i = 0;
return Micro.directionTable[i];
};
Micro.absoluteDistance = function(orgX, orgY, destX, destY) {
var deltaX = destX - orgX;
var deltaY = destY - orgY;
return Math.abs(deltaX) + Math.abs(deltaY);
};
Micro.checkWet = function(tileValue) {
if (tileValue === Tile.HPOWER || tileValue === Tile.VPOWER || tileValue === Tile.HRAIL || tileValue === Tile.VRAIL || tileValue === Tile.BRWH || tileValue === Tile.BRWV) return true;
else return false;
};
Micro.destroyMapTile = function(spriteManager, map, blockMaps, ox, oy) {
var x = Micro.pixToWorld(ox);
var y = Micro.pixToWorld(oy);
if (!map.testBounds(x, y)) return;
var tile = map.getTile(x, y);
var tileValue = tile.getValue();
if (tileValue < Tile.TREEBASE) return;
if (!tile.isCombustible()) {
if (tileValue >= Tile.ROADBASE && tileValue <= Tile.LASTROAD) map.setTo(x, y, new Micro.Tile(Tile.RIVER));
return;
}
if (tile.isZone()) {
Micro.fireZone(map, x, y, blockMaps);
if (tileValue > Tile.RZB) spriteManager.makeExplosionAt(ox, oy);
}
if (Micro.checkWet(tileValue)) map.setTo(x, y, new Micro.Tile(Tile.RIVER));
else map.setTo(x, y, new Micro.Tile(Tile.TINYEXP, Tile.BULLBIT | Tile.ANIMBIT));
};
Micro.getDistance = function(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
};
Micro.checkSpriteCollision = function(s1, s2) {
return s1.frame !== 0 && s2.frame !== 0 && Micro.getDistance(s1.x, s1.y, s2.x, s2.y) < 30;
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK = 0;
Micro.SMOOTH_ALL_THEN_CLAMP = 1;
Micro.smoothMap = function(src, dest, smoothStyle) {
for (var x = 0, width = src.width; x < width; x++) {
for (var y = 0, height = src.height; y < height; y++) {
var edges = 0;
if (x > 0) edges += src.get(x - 1, y);
if (x < src.width - 1) edges += src.get(x + 1, y);
if (y > 0) edges += src.get(x, y - 1);
if (y < src.height - 1) edges += src.get(x, y + 1);
if (smoothStyle === Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK) {
edges = src.get(x, y) + Math.floor(edges / 4);
dest.set(x, y, Math.floor(edges/2));
} else {
edges = (edges + src.get(x, y)) >> 2;
if (edges > 255) edges = 255;
dest.set(x, y, edges);
}
}
}
};
Micro.decRateOfGrowthMap = function(blockMaps) {
var rateOfGrowthMap = blockMaps.rateOfGrowthMap;
for (var x = 0; x < rateOfGrowthMap.width; x++) {
for (var y = 0; y < rateOfGrowthMap.height; y++) {
var rate = rateOfGrowthMap.get(x, y);
if (rate === 0) continue;
if (rate > 0) {
rate--;
rate = Micro.clamp(rate, -200, 200);
rateOfGrowthMap.set(x, y, rate);
continue;
}
if (rate < 0) {
rate++;
rate = Micro.clamp(rate, -200, 200);
rateOfGrowthMap.set(x, y, rate);
}
}
}
};
// Over time, the rate of growth of a neighbourhood should trend towards zero (stable)
Micro.neutraliseRateOfGrowthMap = function(blockMaps) {
var rateOfGrowthMap = blockMaps.rateOfGrowthMap;
for (var x = 0, width = rateOfGrowthMap.width; x < width; x++) {
for (var y = 0, height = rateOfGrowthMap.height; y < height; y++) {
var rate = rateOfGrowthMap.get(x, y);
if (rate === 0) continue;
if (rate > 0) rate--;
else rate++;
rate = Micro.clamp(rate, -200, 200);
rateOfGrowthMap.set(x, y, rate);
}
}
};
Micro.decTrafficMap = function(blockMaps) {
var trafficDensityMap = blockMaps.trafficDensityMap;
for (var x = 0; x < trafficDensityMap.gameMapWidth; x += trafficDensityMap.blockSize) {
for (var y = 0; y < trafficDensityMap.gameMapHeight; y += trafficDensityMap.blockSize) {
var trafficDensity = trafficDensityMap.worldGet(x, y);
if (trafficDensity === 0) continue;
if (trafficDensity <= 24) {
trafficDensityMap.worldSet(x, y, 0);
continue;
}
if (trafficDensity > 200) trafficDensityMap.worldSet(x, y, trafficDensity - 34);
else trafficDensityMap.worldSet(x, y, trafficDensity - 24);
}
}
};
// Over time, traffic density should ease.
Micro.neutraliseTrafficMap = function(blockMaps) {
var trafficDensityMap = blockMaps.trafficDensityMap;
for (var x = 0, width = trafficDensityMap.width; x < width; x++) {
for (var y = 0, height = trafficDensityMap.height; y < height; y++) {
var trafficDensity = trafficDensityMap.get(x, y);
if (trafficDensity === 0) continue;
if (trafficDensity <= 24) trafficDensity = 0;
else if (trafficDensity > 200) trafficDensity = trafficDensity - 34;
else trafficDensity = trafficDensity - 24;
trafficDensityMap.set(x, y, trafficDensity);
}
}
};
Micro.getPollutionValue = function(tileValue) {
if (tileValue < Tile.POWERBASE) {
if (tileValue >= Tile.HTRFBASE) return 75;
if (tileValue >= Tile.LTRFBASE) return 50;
if (tileValue < Tile.ROADBASE) {
if (tileValue > Tile.FIREBASE) return 90;
if (tileValue >= Tile.RADTILE) return 255;
}
return 0;
}
if (tileValue <= Tile.LASTIND) return 0;
if (tileValue < Tile.PORTBASE) return 50;
if (tileValue <= Tile.LASTPOWERPLANT) return 100;
return 0;
};
Micro.getCityCentreDistance = function(map, x, y) {
var xDis, yDis;
if (x > map.cityCentreX) xDis = x - map.cityCentreX;
else xDis = map.cityCentreX - x;
if (y > map.cityCentreY) yDis = y - map.cityCentreY;
else yDis = map.cityCentreY - y;
return Math.min(xDis + yDis, 64);
};
// The original version of this function in the Micropolis code
// takes a ditherFlag. However, as far as I can tell, it was
// never called with a truthy value for the ditherFlag.
/*Micro.smoothDitherMap = function(srcMap, destMap) {
for (var x = 0; x < srcMap.width; x++) {
for (var y = 0; y < srcMap.height; y++) {
var value = 0;
if (x > 0) value += srcMap.get(x - 1, y);
if (x < srcMap.width - 1) value += srcMap.get(x + 1, y);
if (y > 0) value += srcMap.get(x, y - 1);
if (y < (srcMap.height - 1)) value += srcMap.get(x, y + 1);
value = (value + srcMap.get(x, y)) >> 2;
if (value > 255) value = 255;
destMap.set(x, y, value);
}
}
};*/
/*Micro.smoothTemp1ToTemp2 = function(blockMaps) {
Micro.smoothDitherMap(blockMaps.tempMap1, blockMaps.tempMap2);
};
Micro.smoothTemp2ToTemp1 = function(blockMaps) {
Micro.smoothDitherMap(blockMaps.tempMap2, blockMaps.tempMap1);
};*/
// Again, the original version of this function in the Micropolis code
// reads donDither, which is always zero. The dead code has been culled
/*Micro.smoothTerrain = function(blockMaps) {
// Sets each tile to the average of itself and the average of the
// 4 surrounding tiles
var tempMap3 = blockMaps.tempMap3;
var terrainDensityMap = blockMaps.terrainDensityMap;
for (var x = 0; x < terrainDensityMap.width; x++) {
for (var y = 0; y < terrainDensityMap.height; y++) {
var value = 0;
if (x > 0) value += tempMap3.get(x - 1, y);
if (x < (terrainDensityMap.width - 1)) value += tempMap3.get(x + 1, y);
if (y > 0) value += tempMap3.get(x, y - 1);
if (y < (terrainDensityMap.height - 1)) value += tempMap3.get(x, y + 1);
value = Math.floor(Math.floor(value / 4) + tempMap3.get(x, y) / 2);
terrainDensityMap.set(x, y, value);
}
}
};*/
Micro.pollutionTerrainLandValueScan = function(map, census, blockMaps) {
var tempMap1 = blockMaps.tempMap1;
var tempMap2 = blockMaps.tempMap2;
var tempMap3 = blockMaps.tempMap3;
// tempMap3 is a map of development density, smoothed into terrainMap.
tempMap3.clear();
var landValueMap = blockMaps.landValueMap;
var terrainDensityMap = blockMaps.terrainDensityMap;
var pollutionDensityMap = blockMaps.pollutionDensityMap;
var crimeRateMap = blockMaps.crimeRateMap;
var x, y, width, height;
var totalLandValue = 0;
var developedTileCount = 0;
//for (x = 0; x < landValueMap.width; x++) {
// for (y = 0; y < landValueMap.height; y++) {
for (x = 0, width = landValueMap.width; x < width; x++) {
for (y = 0, height = landValueMap.height; y < height; y++) {
var pollutionLevel = 0;
var developed = false;
var worldX = x * 2;
var worldY = y * 2;
for (var mapX = worldX; mapX <= worldX + 1; mapX++) {
for (var mapY = worldY; mapY <= worldY + 1; mapY++) {
var tileValue = map.getTileValue(mapX, mapY);
//if (tileValue === Tile.DIRT) continue;
if (tileValue > Tile.DIRT) {
if (tileValue < Tile.RUBBLE) {
// Undeveloped land: record in tempMap3
var terrainValue = tempMap3.worldGet(mapX, mapY);
tempMap3.worldSet(mapX, mapY, terrainValue + 15);
//var value = tempMap3.get(x >> 1, y >> 1);
//tempMap3.set(x >> 1, y >> 1, value + 15);
continue;
}
pollutionLevel += Micro.getPollutionValue(tileValue);
if (tileValue >= Tile.ROADBASE) {
developed = true;
}
}
}
}
pollutionLevel = Math.min(pollutionLevel, 255);
tempMap1.set(x, y, pollutionLevel);
if (developed) {
var landValue = 34 - Math.floor(Micro.getCityCentreDistance(map, worldX, worldY) / 2);
landValue = landValue << 2;
landValue += terrainDensityMap.get(x >> 1, y >> 1);
landValue -= pollutionDensityMap.get(x, y);
if (crimeRateMap.get(x, y) > 190) { landValue -= 20; }
landValue = Micro.clamp(landValue, 1, 250);
landValueMap.set(x, y, landValue);
totalLandValue += landValue;
developedTileCount++;
} else {
landValueMap.set(x, y, 0);
}
}
}
if (developedTileCount > 0) census.landValueAverage = Math.floor(totalLandValue / developedTileCount);
else census.landValueAverage = 0;
//Micro.smoothTemp1ToTemp2(blockMaps);
//Micro.smoothTemp2ToTemp1(blockMaps);
Micro.smoothMap(tempMap1, tempMap2, Micro.SMOOTH_ALL_THEN_CLAMP);
Micro.smoothMap(tempMap2, tempMap1, Micro.SMOOTH_ALL_THEN_CLAMP);
var maxPollution = 0;
var pollutedTileCount = 0;
var totalPollution = 0;
//for (x = 0; x < pollutionDensityMap.gameMapWidth; x += pollutionDensityMap.blockSize) {
// for (y = 0; y < pollutionDensityMap.gameMapHeight; y += pollutionDensityMap.blockSize) {
for (x = 0, width = map.width; x < width; x += pollutionDensityMap.blockSize) {
for (y = 0, height = map.height; y < height; y += pollutionDensityMap.blockSize) {
var pollution = tempMap1.worldGet(x, y);
pollutionDensityMap.worldSet(x, y, pollution);
if (pollution !== 0) {
pollutedTileCount++;
totalPollution += pollution;
// note location of max pollution for monster
if (pollution > maxPollution || (pollution === maxPollution && Random.getChance(3))) {
maxPollution = pollution;
map.pollutionMaxX = x;
map.pollutionMaxY = y;
}
}
}
}
if (pollutedTileCount) census.pollutionAverage = Math.floor(totalPollution / pollutedTileCount);
else census.pollutionAverage = 0;
//Micro.smoothTerrain(blockMaps);
Micro.smoothMap(tempMap3, terrainDensityMap, Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK);
};
/*Micro.smoothStationMap = function(map) {
var tempMap = new Micro.BlockMap(map);
var lw = tempMap.width;
var lh = tempMap.height
for (var x = 0; x < lw; x++) {
for (var y = 0; y < lh; y++) {
var edge = 0;
if (x > 0) edge += tempMap.get(x - 1, y);
//if (x < lw - 1) edge += tempMap.get(x + 1, y);
if ((x+1) < lw ) edge += tempMap.get(x + 1, y);
if (y > 0) edge += tempMap.get(x, y - 1);
//if (y < lh - 1) edge += tempMap.get(x, y + 1);
if ((y+1) < lh ) edge += tempMap.get(x, y + 1);
//edge = tempMap.get(x, y) + Math.floor(edge / 4);
//map.set(x, y, Math.floor(edge / 2));
edge = tempMap.get(x, y) + Math.floor(edge * 0.25);
map.set(x, y, Math.floor(edge * 0.5));
}
}
};*/
Micro.crimeScan = function(census, blockMaps) {
var policeStationMap = blockMaps.policeStationMap;
var policeStationEffectMap = blockMaps.policeStationEffectMap;
var crimeRateMap = blockMaps.crimeRateMap;
var landValueMap = blockMaps.landValueMap;
var populationDensityMap = blockMaps.populationDensityMap;
//Micro.smoothStationMap(policeStationMap);
//Micro.smoothStationMap(policeStationMap);
//Micro.smoothStationMap(policeStationMap);
Micro.smoothMap(policeStationMap, policeStationEffectMap, Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK);
Micro.smoothMap(policeStationEffectMap, policeStationMap, Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK);
Micro.smoothMap(policeStationMap, policeStationEffectMap, Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK);
var totalCrime = 0;
var crimeZoneCount = 0;
//for (var x = 0; x < crimeRateMap.gameMapWidth; x += crimeRateMap.blockSize) {
// for (var y = 0; y < crimeRateMap.gameMapHeight; y += crimeRateMap.blockSize) {
for (var x = 0, width = crimeRateMap.mapWidth, blockSize = crimeRateMap.blockSize; x < width; x += blockSize) {
for (var y = 0, height = crimeRateMap.mapHeight, b; y < height; y += blockSize) {
var value = landValueMap.worldGet(x, y);
if (value > 0) {
crimeZoneCount += 1;
value = 128 - value;
value += populationDensityMap.worldGet(x, y);
value = Math.min(value, 300);
//value -= policeStationMap.worldGet(x, y);
//value -= policeStationMap.worldGet( Math.floor(x*0.25), Math.floor(y*0.25) );
value -= policeStationMap.worldGet(x, y);
//value -= policeStationEffectMap.worldGet(x, y)
value = Micro.clamp(value, 0, 250);
crimeRateMap.worldSet(x, y, value);
totalCrime += value;
} else {
crimeRateMap.worldSet(x, y, 0);
}
}
}
if (crimeZoneCount > 0) census.crimeAverage = Math.floor(totalCrime / crimeZoneCount);
else census.crimeAverage = 0;
blockMaps.policeStationEffectMap = new Micro.BlockMap(policeStationMap);
};
Micro.computeComRateMap = function(map, blockMaps) {
var comRateMap = blockMaps.comRateMap;
for (var x = 0; x < comRateMap.width; x++) {
for (var y = 0; y < comRateMap.height; y++) {
var value = Math.floor(Micro.getCityCentreDistance(map, x * 8, y * 8) / 2);
value = value * 4;
value = 64 - value;
comRateMap.set(x, y, value);
}
}
};
// Iterate over the map, and score each neighbourhood on its distance from the city centre. Scores are in the range
// -64 to 64. This affects the growth of commercial zones within that neighbourhood.
Micro.fillCityCentreDistScoreMap = function(map, blockMaps) {
var cityCentreDistScoreMap = blockMaps.cityCentreDistScoreMap;
for (var x = 0, width = cityCentreDistScoreMap.width; x < width; x++) {
for (var y = 0, height = cityCentreDistScoreMap.height; y < height; y++) {
// First, we compute the Manhattan distance of the top-left hand corner of the neighbourhood to the city centre
// and half that value. This leaves us a value in the range 0 - 32
var value = Math.floor(Micro.getCityCentreDistance(map, x * 8, y * 8) / 2);
// Now, we scale up by a factor of 4. We're in the range 0 - 128
value = value * 4;
// And finally, subtract from 64, leaving us a score in the range -64 to 64
value = 64 - value;
cityCentreDistScoreMap.set(x, y, value);
}
}
};
Micro.getPopulationDensity = function(map, x, y, tile) {
if (tile < Tile.COMBASE) return Residential.getZonePopulation(map, x, y, tile);
if (tile < Tile.INDBASE) return Commercial.getZonePopulation(map, x, y, tile) * 8;
if (tile < Tile.PORTBASE) return Industrial.getZonePopulation(map, x, y, tile) * 8;
return 0;
};
Micro.populationDensityScan = function(map, blockMaps) {
var tempMap1 = blockMaps.tempMap1;
var tempMap2 = blockMaps.tempMap2;
var populationDensityMap = blockMaps.populationDensityMap;
//tempMap1.clear();
var Xtot = 0;
var Ytot = 0;
var zoneTotal = 0;
//for (var x = 0; x < map.width; x++) {
// for (var y = 0; y < map.height; y++) {
for (var x = 0, width = map.width; x < width; x++) {
for (var y = 0, height = map.height; y < height; y++) {
var tile = map.getTile(x, y);
if (tile.isZone()) {
var tileValue = tile.getValue();
var population = Micro.getPopulationDensity(map, x, y, tileValue) * 8;
population = Math.min(population, 254);
tempMap1.worldSet(x, y, population);
Xtot += x;
Ytot += y;
zoneTotal++;
} else {
tempMap1.worldSet(x, y, 0);
}
}
}
//Micro.smoothTemp1ToTemp2(blockMaps);
//Micro.smoothTemp2ToTemp1(blockMaps);
//Micro.smoothTemp1ToTemp2(blockMaps);
Micro.smoothMap(tempMap1, tempMap2, Micro.SMOOTH_ALL_THEN_CLAMP);
Micro.smoothMap(tempMap2, tempMap1, Micro.SMOOTH_ALL_THEN_CLAMP);
Micro.smoothMap(tempMap1, tempMap2, Micro.SMOOTH_ALL_THEN_CLAMP);
blockMaps.populationDensityMap.copyFrom(tempMap2, function(x) {return x * 2;});
//Micro.fillCityCentreDistScoreMap(map, blockMaps);
// Copy tempMap2 to populationDensityMap, multiplying by 2
//blockMaps.populationDensityMap = new Micro.BlockMap(tempMap2, function(x) {return 2 * x;});
//Micro.computeComRateMap(map, blockMaps);
// Compute new city center
if (zoneTotal > 0) {
map.cityCentreX = Math.floor(Xtot / zoneTotal);
map.cityCentreY = Math.floor(Ytot / zoneTotal);
} else {
map.cityCentreX = Math.floor(map.width * 0.5);
map.cityCentreY = Math.floor(map.height * 0.5);
}
};
Micro.fireAnalysis = function(blockMaps) {
var fireStationMap = blockMaps.fireStationMap;
var fireStationEffectMap = blockMaps.fireStationEffectMap;
//Micro.smoothStationMap(fireStationMap);
//Micro.smoothStationMap(fireStationMap);
//Micro.smoothStationMap(fireStationMap);
Micro.smoothMap(fireStationMap, fireStationEffectMap, Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK);
Micro.smoothMap(fireStationEffectMap, fireStationMap, Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK);
Micro.smoothMap(fireStationMap, fireStationEffectMap, Micro.SMOOTH_NEIGHBOURS_THEN_BLOCK);
blockMaps.fireStationEffectMap = new Micro.BlockMap(fireStationMap);
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Residential = function (SIM) {
var sim = SIM;
// Residential tiles have 'populations' of 16, 24, 32 or 40
// and value from 0 to 3. The tiles are laid out in
// increasing order of land value, cycling through
// each population value
var placeResidential = function(map, x, y, population, lpValue, zonePower) {
var centreTile = ((lpValue * 4) + population) * 9 + Tile.RZB;
Micro.putZone(map, x, y, centreTile, zonePower);
};
// Look for housing in the adjacent 8 tiles
var getFreeZonePopulation = function(map, x, y, tileValue) {
var count = 0;
for (var xx = x - 1; xx <= x + 1; xx++) {
for (var yy = y - 1; yy <= y + 1; yy++) {
if (xx === x && yy === y) continue;
tileValue = map.getTileValue(xx, yy);
if (tileValue >= Tile.LHTHR && tileValue <= Tile.HHTHR) count += 1;
}
}
return count;
};
var getZonePopulation = function(map, x, y, tileValue) {
if (tileValue instanceof Micro.Tile) tileValue = new Micro.Tile().getValue();
if (tileValue === Tile.FREEZ) return getFreeZonePopulation(map, x, y, tileValue);
var populationIndex = Math.floor((tileValue - Tile.RZB) / 9) % 4 + 1;
return populationIndex * 8 + 16;
};
// Assess a tile for suitability for a house. Prefer tiles
// near roads
var evalLot = function(map, x, y) {
var xDelta = [0, 1, 0, -1];
var yDelta = [-1, 0, 1, 0];
var tileValue = map.getTileValue(x, y);
if (tileValue < Tile.RESBASE || tileValue > Tile.RESBASE + 8) return -1;
var score = 1;
for (var i = 0; i < 4; i++) {
//tileValue = map.getTileValue(x + xDelta[i], y + yDelta[i]);
var edgeX = x + xDelta[i];
var edgeY = y + yDelta[i];
if (edgeX < 0 || edgeX >= map.width || edgeY < 0 || edgeY >= map.height) continue;
tileValue = map.getTileValue(edgeX, edgeY);
if (tileValue !== Tile.DIRT && tileValue <= Tile.LASTROAD) score += 1;
}
return score;
};
var buildHouse = function(map, x, y, lpValue) {
var best = 0;
var bestScore = 0;
// Deliberately ordered so that the centre tile is at index 0
var xDelta = [0, -1, 0, 1, -1, 1, -1, 0, 1];
var yDelta = [0, -1, -1, -1, 0, 0, 1, 1, 1];
for (var i = 0; i < 9; i++) {
var xx = x + xDelta[i];
var yy = y + yDelta[i];
var score = evalLot(map, xx, yy);
if (score > bestScore) {
bestScore = score;
best = i;
} else if (score === bestScore && Random.getChance(7)) {
// Ensures we don't always select the same position when we
// have a choice
best = i;
}
}
if (best > 0 && map.testBounds(x + xDelta[best], y + yDelta[best]))
map.setTo(x + xDelta[best], y + yDelta[best], new Micro.Tile(Tile.HOUSE + Random.getRandom(2) + lpValue * 3, Tile.BLBNCNBIT));
//map.setTile(x + xDelta[best], y + yDelta[best], new Micro.Tile(Tile.HOUSE + Random.getRandom(2) + lpValue * 3, Tile.BLBNCNBIT));
};
//var growZone
var growZone = function(map, x, y, blockMaps, population, lpValue, zonePower) {
var pollution = blockMaps.pollutionDensityMap.worldGet(x, y);
// Cough! Too polluted noone wants to move here!
if (pollution > 128) return;
var tileValue = map.getTileValue(x, y);
if (tileValue === Tile.FREEZ) {
if (population < 8) {
// Zone capacity not yet reached: build another house
buildHouse(map, x, y, lpValue);
Micro.incRateOfGrowth(blockMaps, x, y, 1);
return;
}
if (blockMaps.populationDensityMap.worldGet(x, y) > 64) {
// There is local demand for higher density housing
placeResidential(map, x, y, 0, lpValue, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, 8);
return;
}
}
if (population < 40) {
// Zone population not yet maxed out
placeResidential(map, x, y, Math.floor(population / 8) - 1, lpValue, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, 8);
}
};
var freeZone = [0, 3, 6, 1, 4, 7, 2, 5, 8];
var degradeZone = function(map, x, y, blockMaps, population, lpValue, zonePower) {
var xx, yy;
if (population === 0) return;
if (population > 16) {
// Degrade to a lower density block
placeResidential(map, x, y, Math.floor((population - 24) / 8), lpValue, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, -8);
return;
}
if (population === 16) {
// Already at lowest density: degrade to 8 individual houses
map.setTo(x, y, new Micro.Tile(Tile.FREEZ, Tile.BLBNCNBIT | Tile.ZONEBIT));
for (yy = y - 1; yy <= y + 1; yy++) {
for (xx = x - 1; xx <= x + 1; xx++) {
if (xx === x && yy === y) continue;
map.setTo(x, y, new Micro.Tile(Tile.LHTHR + lpValue + Random.getRandom(2), Tile.BLBNCNBIT));
}
}
Micro.incRateOfGrowth(blockMaps, x, y, -8);
return;
}
// Already down to individual houses. Remove one
var i = 0;
Micro.incRateOfGrowth(blockMaps, x, y, -1);
for (xx = x - 1; xx <= x + 1; xx++) {
for (yy = y - 1; yy <= y + 1; yy++) {
var currentValue = map.getTileValue(xx, yy);
if (currentValue >= Tile.LHTHR && currentValue <= Tile.HHTHR) {
// We've found a house. Replace it with the normal free zone tile
map.setTo(xx, yy, new Micro.Tile(freeZone[i] + Tile.RESBASE, Tile.BLBNCNBIT));
return;
}
i += 1;
}
}
};
// Returns a score for the zone in the range -3000 - 3000
var evalResidential = function(blockMaps, x, y, traffic) {
if (traffic === Micro.NO_ROAD_FOUND) return -3000;
var landValue = blockMaps.landValueMap.worldGet(x, y);
landValue -= blockMaps.pollutionDensityMap.worldGet(x, y);
if (landValue < 0) landValue = 0;
else landValue = Math.min(landValue * 32, 6000);
return landValue - 3000;
};
var residentialFound = function(map, x, y, simData) {
// If we choose to grow this zone, we will fill it with an index in the range 0-3 reflecting the land value and
// pollution scores (higher is better). This is then used to select the variant to build
var lpValue;
// Notify the census
sim.census.resZonePop += 1;
// Also, notify the census of our population
var tileValue = map.getTileValue(x, y);
var population = getZonePopulation(map, x, y, tileValue);
sim.census.resPop += population;
var zonePower = map.getTile(x, y).isPowered();
var trafficOK = Micro.ROUTE_FOUND;
// Occasionally check to see if the zone is connected to the road network. The chance of this happening increases
// as the zone's population increases. Note: we will never execute this conditional if the zone is empty, as zero
// will never be be bigger than any of the values Random will generate
if (population > Random.getRandom(35)) {
// Is there a route from this zone to a commercial zone?
trafficOK = sim.traffic.makeTraffic(x, y, sim.blockMaps, Micro.isCommercial);
// If we're not connected to the road network, then going shopping will be a pain. Move out.
if (trafficOK === Micro.NO_ROAD_FOUND) {
lpValue = Micro.getLandPollutionValue(sim.blockMaps, x, y);
degradeZone(map, x, y, sim.blockMaps, population, lpValue, zonePower);
return;
}
}
// Sometimes we will randomly choose to assess this block. However, always assess it if it's empty or contains only single houses.
if (tileValue === Tile.FREEZ || Random.getChance(7)) {
// First, score the individual zone. This is a value in the range -3000 to 3000
// Then take into account global demand for housing.
var locationScore = evalResidential(sim.blockMaps, x, y, trafficOK);
var zoneScore = sim.valves.resValve + locationScore;
// Naturally unpowered zones should be penalized
if (!zonePower) zoneScore = -500;
// The residential demand valve has range -2000 to 2000, so taking into account the "no traffic" and
// "no power" modifiers above, zoneScore must lie in the range -5500 - 5000.
// Now, observe that if there are no roads we will never take this branch, as zoneScore will equal -3000.
// Given the comment above about ranges for zoneScore, zoneScore - 26380, will be in the range -26729 to -20880.
// getRandom16() has a range of 65536 possible numbers, in the range -32768 to 32767.
// Of those, 9.2% will always be below zoneScore and hence will always take this branch and trigger zone growth.
// 81.8% of them are above -20880, so nearly 82% of the time, we will never take this branch.
// Thus, there's approximately a 9% chance that the value will be in the range, and we *might* grow.
//if (trafficOK && (zoneScore > -350) && ((zoneScore - 26380) > Random.getRandom16Signed())) {
if (zoneScore > -350 && (zoneScore - 26380) > Random.getRandom16Signed()) {
// If this zone is empty, and residential demand is strong, we might make a hospital
//if (population === 0 && ((Random.getRandom16() & 3) === 0)) {
if (population === 0 && Random.getChance(3)) {
makeHospital(map, x, y, simData, zonePower);
return;
}
// Get an index in the range 0-3 scoring the land desirability and pollution, and grow the zone to the next
// population rank
lpValue = Micro.getLandPollutionValue(sim.blockMaps, x, y);
growZone(map, x, y, sim.blockMaps, population, lpValue, zonePower);
return;
}
// Again, given the above, zoneScore + 26380 must lie in the range 20880 - 26030.
// There is a 10.2% chance of getRandom16() always yielding a number > 27994 which would take this branch.
// There is a 89.7% chance of the number being below 20880 thus never triggering this branch, which leaves a
// 0.1% chance of this branch being conditional on zoneScore.
if (zoneScore < 350 && ((zoneScore + 26380) < Random.getRandom16Signed())) {
// Get an index in the range 0-3 scoring the land desirability and pollution, and degrade to the next
// lower ranked zone
lpValue = Micro.getLandPollutionValue(sim.blockMaps, x, y);
degradeZone(map, x, y, sim.blockMaps, population, lpValue, zonePower);
}
}
};
var makeHospital = function(map, x, y, simData, zonePower) {
// We only build a hospital if the population requires it
if (sim.census.needHospital > 0) {
Micro.putZone(map, x, y, Tile.HOSPITAL, zonePower);
sim.census.needHospital = 0;
return;
}
};
var hospitalFound = function(map, x, y, simData) {
sim.census.hospitalPop += 1;
// Degrade to an empty zone if a hospital is no longer sustainable
if (sim.census.needHospital === -1) {
if (Random.getRandom(20) === 0) //Micro.putZone(map, x, y, Tile.FREEZ);
Micro.putZone(map, x, y, Tile.FREEZ, map.getTile(x, y).isPowered());
}
};
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Micro.isResidentialZone, residentialFound);
mapScanner.addAction(Micro.HOSPITAL, hospitalFound);
repairManager.addAction(Tile.HOSPITAL, 15, 3);
},
getZonePopulation:getZonePopulation
};
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Commercial = function (SIM) {
var sim = SIM;
// Commercial tiles have 'populations' from 1 to 5,
// and value from 0 to 3. The tiles are laid out in
// increasing order of land value, cycling through
// each population value
var getZonePopulation = function(map, x, y, tileValue) {
if (tileValue instanceof Micro.Tile) tileValue = new Micro.Tile().getValue(); //COMCLEAR)
if (tileValue === Tile.COMCLR) return 0;
return Math.floor((tileValue - Tile.CZB) / 9) % 5 + 1;
};
var placeCommercial = function(map, x, y, population, lpValue, zonePower) {
var centreTile = ((lpValue * 5) + population) * 9 + Tile.CZB;
Micro.putZone(map, x, y, centreTile, zonePower);
};
var doMigrationIn = function(map, x, y, blockMaps, population, lpValue, zonePower) {
var landValue = blockMaps.landValueMap.worldGet(x, y);
landValue = landValue >> 5;
if (population > landValue) return;
// Desirable zone: migrate
if (population < 5) {
placeCommercial(map, x, y, population, lpValue, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, 8);
}
};
var doMigrationOut = function(map, x, y, blockMaps, population, lpValue, zonePower) {
if (population > 1) {
placeCommercial(map, x, y, population - 2, lpValue, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, -8);
return;
}
if (population === 1) {
Micro.putZone(map, x, y, Tile.COMCLR, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, -8);
}
};
var evalCommercial = function(blockMaps, x, y, traffic) {
if (traffic === Micro.NO_ROAD_FOUND) return -3000;
var comRate = blockMaps.comRateMap.worldGet(x, y);
return comRate;
};
var commercialFound = function(map, x, y, simData) {
var lpValue;
sim.census.comZonePop += 1;
var tileValue = map.getTileValue(x, y);
var tilePop = getZonePopulation(map, x, y, tileValue);
sim.census.comPop += tilePop;
var zonePower = map.getTile(x, y).isPowered();
var trafficOK = Micro.ROUTE_FOUND;
if (tilePop > Random.getRandom(5)) {
// Try driving from commercial to industrial
trafficOK = sim.traffic.makeTraffic(x, y, sim.blockMaps, Micro.isIndustrial);
// Trigger outward migration if not connected to road network
if (trafficOK === Micro.NO_ROAD_FOUND) {
lpValue = Micro.getLandPollutionValue(sim.blockMaps, x, y);
doMigrationOut(map, x, y, sim.blockMaps, tilePop, lpValue, zonePower);
return;
}
}
// Occasionally assess and perhaps modify the tile
if (Random.getChance(7)) {
var locationScore = evalCommercial(sim.blockMaps, x, y, trafficOK);
var zoneScore = sim.valves.comValve + locationScore;
if (!zonePower) zoneScore = -500;
if (trafficOK && (zoneScore > -350) && ((zoneScore - 26380) > Random.getRandom16Signed())) {
lpValue = Micro.getLandPollutionValue(sim.blockMaps, x, y);
doMigrationIn(map, x, y, sim.blockMaps, tilePop, lpValue, zonePower);
return;
}
if (zoneScore < 350 && ((zoneScore + 26380) < Random.getRandom16Signed())) {
lpValue = Micro.getLandPollutionValue(sim.blockMaps, x, y);
doMigrationOut(map, x, y, sim.blockMaps, tilePop, lpValue, zonePower);
}
}
};
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Micro.isCommercialZone, commercialFound);
},
getZonePopulation: getZonePopulation
}
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Industrial = function (SIM) {
var sim = SIM;
// Industrial tiles have 'populations' from 1 to 4,
// and value from 0 to 3. The tiles are laid out in
// increasing order of land value, cycling through
// each population value
var getZonePopulation = function(map, x, y, tileValue) {
if (tileValue instanceof Micro.Tile) tileValue = new Micro.Tile().getValue();
if (tileValue === Tile.INDCLR) return 0;
return Math.floor((tileValue - Tile.IZB) / 9) % 4 + 1;
};
var placeIndustrial = function(map, x, y, population, lpValue, zonePower) {
var centreTile = ((lpValue * 4) + population) * 9 + Tile.IZB;
Micro.putZone(map, x, y, centreTile, zonePower);
};
var doMigrationIn = function(map, x, y, blockMaps, population, lpValue, zonePower) {
if (population < 4) {
placeIndustrial(map, x, y, population, lpValue, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, 8);
}
};
var doMigrationOut = function(map, x, y, blockMaps, population, lpValue, zonePower) {
if (population > 1) {
placeIndustrial(map, x, y, population - 2, lpValue, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, -8);
return;
}
if (population === 1) {
Micro.putZone(map, x, y, Tile.INDCLR, zonePower);
Micro.incRateOfGrowth(blockMaps, x, y, -8);
}
};
var evalIndustrial = function(blockMaps, x, y, traffic) {
if (traffic === Micro.NO_ROAD_FOUND) return -1000;
return 0;
};
var animated = [true, false, true, true, false, false, true, true];
var xDelta = [-1, 0, 1, 0, 0, 0, 0, 1];
var yDelta = [-1, 0, -1, -1, 0, 0, -1, -1];
var setSmoke = function(map, x, y, tileValue, isPowered) {
if (tileValue < Tile.IZB) return;
// There are only 7 different types of populated industrial zones.
// As tileValue - IZB will never be 8x9 or more away from IZB, we
// can shift right by 3, and get the same effect as dividing by 9
var i = (tileValue - Tile.IZB) >> 3;
if (animated[i] && isPowered) {
map.addTileFlags(x + xDelta[i], y + yDelta[i], Tile.ASCBIT);
} else {
map.addTileFlags(x + xDelta[i], y + yDelta[i], Tile.BNCNBIT);
map.removeTileFlags(x + xDelta[i], y + yDelta[i], Tile.ANIMBIT);
}
}
var industrialFound = function(map, x, y, simData) {
sim.census.indZonePop += 1;
var tileValue = map.getTileValue(x, y);
var tilePop = getZonePopulation(map, x, y, tileValue);
sim.census.indPop += tilePop;
// Set animation bit if appropriate
var zonePower = map.getTile(x, y).isPowered();
setSmoke(map, x, y, tileValue, zonePower);
var trafficOK = Micro.ROUTE_FOUND;
if (tilePop > Random.getRandom(5)) {
// Try driving from industrial to residential
trafficOK = sim.traffic.makeTraffic(x, y, sim.blockMaps, Micro.isResidential);
// Trigger outward migration if not connected to road network
if (trafficOK === Micro.NO_ROAD_FOUND) {
doMigrationOut(map, x, y, sim.blockMaps, tilePop, Random.getRandom16() & 1, zonePower);
return;
}
}
// Occasionally assess and perhaps modify the tile
if (Random.getChance(7)) {
var locationScore = evalIndustrial(sim.blockMaps, x, y, trafficOK);
var zoneScore = sim.valves.indValve + locationScore;
if (!zonePower) zoneScore = -500;
if (trafficOK && (zoneScore > -350) && ((zoneScore - 26380) > Random.getRandom16Signed())) {
doMigrationIn(map, x, y, sim.blockMaps, tilePop, Random.getRandom16() & 1, zonePower);
return;
}
if (zoneScore < 350 && ((zoneScore + 26380) < Random.getRandom16Signed())) {
doMigrationOut(map, x, y, sim.blockMaps, tilePop, Random.getRandom16() & 1, zonePower);
}
}
};
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Micro.isIndustrialZone, industrialFound);
},
getZonePopulation: getZonePopulation
};
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.MiscTiles = function (SIM) {
var sim = SIM;
var xDelta = [-1, 0, 1, 0 ];
var yDelta = [ 0, -1, 0, 1 ];
var fireFound = function(map, x, y, simData) {
sim.census.firePop += 1;
if ((Random.getRandom16() & 3) !== 0) return;
// Try to set neighbouring tiles on fire as well
for (var i = 0; i < 4; i++) {
if (Random.getChance(7)) {
var xTem = x + xDelta[i];
var yTem = y + yDelta[i];
if (map.testBounds(xTem, yTem)) {
var tile = map.getTile(x, y);
if (!tile.isCombustible()) continue;
if (tile.isZone()) {
// Neighbour is a ione and burnable
Micro.fireZone(map, x, y, sim.blockMaps);
// Industrial zones etc really go boom
if (tile.getValue() > Tile.IZB) sim.spriteManager.makeExplosionAt(x, y);
}
map.setTo(Micro.randomFire());
}
}
}
// Compute likelyhood of fire running out of fuel
var rate = 10; // Likelyhood of extinguishing (bigger means less chance)
i = sim.blockMaps.fireStationEffectMap.worldGet(x, y);
if (i > 100) rate = 1;
else if (i > 20) rate = 2;
else if (i > 0) rate = 3;
// Decide whether to put out the fire.
if (Random.getRandom(rate) === 0) map.setTo(x, y, Micro.randomRubble());
};
var radiationFound = function(map, x, y, simData) {
if (Random.getChance(4095)) map.setTo(x, y, new Micro.Tile(Tile.DIRT));
};
var floodFound = function(map, x, y, simData) {
sim.disasterManager.doFlood(x, y, sim.blockMaps);
};
var explosionFound = function(map, x, y, simData) {
var tileValue = map.getTileValue(x, y);
map.setTo(x, y, Micro.randomRubble());
return;
};
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Micro.isFire, fireFound, true);
mapScanner.addAction(Tile.RADTILE, radiationFound, true);
mapScanner.addAction(Micro.isFlood, floodFound, true);
mapScanner.addAction(Micro.isManualExplosion, explosionFound, true);
}
};
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Road = function (SIM) {
var sim = SIM;
var openBridge = function(map, origX, origY, xDelta, yDelta, oldTiles, newTiles) {
for (var i = 0; i < 7; i++) {
var x = origX + xDelta[i];
var y = origY + yDelta[i];
if (map.testBounds(x, y)) {
if (map.getTileValue(x, y) === (oldTiles[i] & Tile.BIT_MASK)) map.setTileValue(x, y, newTiles[i]);//map.setTileValue(newTiles[i]);
}
}
}
var closeBridge = function(map, origX, origY, xDelta, yDelta, oldTiles, newTiles) {
for (var i = 0; i < 7; i++) {
var x = origX + xDelta[i];
var y = origY + yDelta[i];
if (map.testBounds(x, y)) {
var tileValue = map.getTileValue(x, y);
if (tileValue === Tile.CHANNEL || (tileValue & 15) === (oldTiles[i] & 15)) map.setTileValue(x, y, newTiles[i]);//map.setTileValue(newTiles[i]);
}
}
}
var verticalDeltaX = [0, 1, 0, 0, 0, 0, 1];
var verticalDeltaY = [-2, -2, -1, 0, 1, 2, 2];
var openVertical = [
Tile.VBRDG0 | Tile.BULLBIT, Tile.VBRDG1 | Tile.BULLBIT,
Tile.RIVER, Tile.BRWV | Tile.BULLBIT,
Tile.RIVER, Tile.VBRDG2 | Tile.BULLBIT, Tile.VBRDG3 | Tile.BULLBIT];
var closeVertical = [
Tile.VBRIDGE | Tile.BULLBIT, Tile.RIVER, Tile.VBRIDGE | Tile.BULLBIT,
Tile.VBRIDGE | Tile.BULLBIT, Tile.VBRIDGE | Tile.BULLBIT,
Tile.VBRIDGE | Tile.BULLBIT, Tile.RIVER];
var horizontalDeltaX = [-2, 2, -2, -1, 0, 1, 2];
var horizontalDeltaY = [ -1, -1, 0, 0, 0, 0, 0];
var openHorizontal = [
Tile.HBRDG1 | Tile.BULLBIT, Tile.HBRDG3 | Tile.BULLBIT,
Tile.HBRDG0 | Tile.BULLBIT, Tile.RIVER, Tile.BRWH | Tile.BULLBIT,
Tile.RIVER, Tile.HBRDG2 | Tile.BULLBIT ];
var closeHorizontal = [
Tile.RIVER, Tile.RIVER, Tile.HBRIDGE | Tile.BULLBIT,
Tile.HBRIDGE | Tile.BULLBIT, Tile.HBRIDGE | Tile.BULLBIT,
Tile.HBRIDGE | Tile.BULLBIT, Tile.HBRIDGE | Tile.BULLBIT];
var doBridge = function(map, x, y, currentTile, simData) {
if (currentTile === Tile.BRWV) {
// We have an open vertical bridge. Possibly close it.
if (Random.getChance(3) && sim.spriteManager.getBoatDistance(x, y) > 340)
closeBridge(map, x, y, verticalDeltaX, verticalDeltaY, openVertical, closeVertical);
return true;
}
if (currentTile == Tile.BRWH) {
// We have an open horizontal bridge. Possibly close it.
if (Random.getChance(3) && sim.spriteManager.getBoatDistance(x, y) > 340)
closeBridge(map, x, y, horizontalDeltaX, horizontalDeltaY, openHorizontal, closeHorizontal);
return true;
}
if (sim.spriteManager.getBoatDistance(x, y) < 300 || Random.getChance(7)) {
if (currentTile & 1) {
if (x < map.width - 1) {
if (map.getTileValue(x + 1, y) === Tile.CHANNEL) {
// We have a closed vertical bridge. Open it.
openBridge(map, x, y, verticalDeltaX, verticalDeltaY, closeVertical, openVertical);
return true;
}
}
return false;
} else {
if (y > 0) {
if (map.getTileValue(x, y - 1) === Tile.CHANNEL) {
// We have a closed horizontal bridge. Open it.
//openBridge(map, x, y, horizontalDeltaX, horizontalDeltaY, openVertical, closeVertical);
openBridge(map, x, y, horizontalDeltaX, horizontalDeltaY, closeHorizontal, openHorizontal);
return true;
}
}
}
}
return false;
}
var densityTable = [Tile.ROADBASE, Tile.LTRFBASE, Tile.HTRFBASE];
var roadFound = function(map, x, y, simData) {
sim.census.roadTotal += 1;
var currentTile = map.getTile(x, y);
var tileValue = currentTile.getValue();
if (sim.budget.shouldDegradeRoad()) {
if (Random.getChance(511)) {
currentTile = map.getTile(x, y);
// Don't degrade tiles with power lines
if (!currentTile.isConductive()) {
if (sim.budget.roadEffect < (Random.getRandom16() & 31)) {
var mapValue = currentTile.getValue();
// Replace bridge tiles with water, otherwise rubble
if ((tileValue & 15) < 2 || (tileValue & 15) === 15) map.setTo(x, y, Tile.RIVER);
else map.setTo(x, y, Micro.randomRubble());
return;
}
}
}
}
// Bridges are not combustible
if (!currentTile.isCombustible()) {
// The comment in the original Micropolis code states bridges count for 4
// However, with the increment above, it's actually 5. Bug?
sim.census.roadTotal += 4;
//if (doBridge(map, x, y, tileValue, null)) return;
if (doBridge(map, x, y, tileValue, simData)) return;
}
// Examine traffic density, and modify tile to represent last scanned traffic
// density
var density = 0;
if (tileValue < Tile.LTRFBASE) {
density = 0;
} else if (tileValue < Tile.HTRFBASE) {
density = 1;
} else {
// Heavy traffic counts as two tiles with regards to upkeep cost
// Note, if this is heavy traffic on a bridge, and it wasn't handled above,
// it actually counts for 7 road tiles
sim.census.roadTotal += 1;
density = 2;
}
var currentDensity = sim.blockMaps.trafficDensityMap.worldGet(x, y) >> 6;
// Force currentDensity in range 0-3 (trafficDensityMap values are capped at 240)
if (currentDensity > 1) currentDensity -= 1;
if (currentDensity === density) return;
var newValue = ((tileValue - Tile.ROADBASE) & 15) + densityTable[currentDensity];
// Preserve all bits except animation
var newFlags = currentTile.getFlags() & ~Tile.ANIMBIT;
if (currentDensity > 0) newFlags |= Tile.ANIMBIT;
map.setTo(x, y, new Micro.Tile(newValue, newFlags));
}
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Micro.isRoad, roadFound);
}
}
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Stadia = function (SIM) {
var sim = SIM;
var emptyStadiumFound = function(map, x, y, simData) {
sim.census.stadiumPop += 1;
if (map.getTile(x, y).isPowered()) {
// Occasionally start the big game
if (((sim.cityTime + x + y) & 31) === 0) {
map.putZone(x, y, Tile.FULLSTADIUM, 4);
map.addTileFlags(x, y, Tile.POWERBIT);
map.setTo(x + 1, y, new Micro.Tile(Tile.FOOTBALLGAME1, Tile.ANIMBIT));
map.setTo(x + 1, y + 1, new Micro.Tile(Tile.FOOTBALLGAME2, Tile.ANIMBIT));
}
}
}
var fullStadiumFound = function(map, x, y, simData) {
sim.census.stadiumPop += 1;
var isPowered = map.getTile(x, y).isPowered();
if (((sim.cityTime + x + y) & 7) === 0) {
map.putZone(x, y, Tile.STADIUM, 4);
if (isPowered) map.addTileFlags(x, y, Tile.POWERBIT);
}
}
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Tile.STADIUM, emptyStadiumFound);
mapScanner.addAction(Tile.FULLSTADIUM, fullStadiumFound);
repairManager.addAction(Tile.STADIUM, 15, 4);
}
}
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.EmergencyServices = function (SIM) {
var sim = SIM;
var handleService = function(censusStat, budgetEffect, blockMap) {
return function(map, x, y, simData) {
sim.census[censusStat] += 1;
var effect = sim.budget[budgetEffect];
var isPowered = map.getTile(x, y).isPowered();
// Unpowered buildings are half as effective
if (!isPowered) effect = Math.floor(effect / 2);
var pos = new map.Position(x, y);
var connectedToRoads = sim.traffic.findPerimeterRoad(pos);
if (!connectedToRoads) effect = Math.floor(effect / 2);
var currentEffect = sim.blockMaps[blockMap].worldGet(x, y);
currentEffect += effect;
sim.blockMaps[blockMap].worldSet(x, y, currentEffect);
}
};
var policeStationFound = handleService('policeStationPop', 'policeEffect', 'policeStationMap');
var fireStationFound = handleService('fireStationPop', 'fireEffect', 'fireStationMap');
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Tile.POLICESTATION, policeStationFound);
mapScanner.addAction(Tile.FIRESTATION, fireStationFound);
}
}
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Transport = function (SIM) {
var sim = SIM;
var railFound = function(map, x, y, simData) {
sim.census.railTotal += 1;
sim.spriteManager.generateTrain(sim.census, x, y);
if (sim.budget.shouldDegradeRoad()) {
if (Random.getChance(511)) {
var currentTile = map.getTile(x, y);
// Don't degrade tiles with power lines
if (currentTile.isConductive()) return;
if (sim.budget.roadEffect < (Random.getRandom16() & 31)) {
var mapValue = currentTile.getValue();
// Replace bridge tiles with water, otherwise rubble
var tile = map.getTile(x, y);
if (tile < Tile.RAILBASE + 2) map.setTo(x, y, Tile.RIVER);
else map.setTo(x, y, Micro.randomRubble());
}
}
}
};
var airportFound = function(map, x, y, simData) {
sim.census.airportPop += 1;
var tile = map.getTile(x, y);
if (tile.isPowered()) {
if (map.getTileValue(x + 1, y - 1) === Tile.RADAR) map.setTo(x + 1, y - 1, new Micro.Tile(Tile.RADAR0, Tile.CONDBIT | Tile.ANIMBIT | Tile.BURNBIT));
if (Random.getRandom(5) === 0) {
sim.spriteManager.generatePlane(x, y);
return;
}
if (Random.getRandom(12) === 0) sim.spriteManager.generateCopter(x, y);
} else {
map.setTo(x + 1, y - 1, new Micro.Tile(Tile.RADAR, Tile.CONDBIT | Tile.BURNBIT));
}
};
var portFound = function(map, x, y, simData) {
sim.census.seaportPop += 1;
var tile = map.getTile(x, y);
if (tile.isPowered() && sim.spriteManager.getSprite(Micro.SPRITE_SHIP) === null) sim.spriteManager.generateShip();
};
return {
registerHandlers: function(mapScanner, repairManager) {
mapScanner.addAction(Micro.isRail, railFound);
mapScanner.addAction(Tile.PORT, portFound);
mapScanner.addAction(Tile.AIRPORT, airportFound);
repairManager.addAction(Tile.PORT, 15, 4);
repairManager.addAction(Tile.AIRPORT, 7, 6);
}
}
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.toKey = function(x, y) {
return [x, y].join(',');
};
Micro.fromKey = function(k) {
k = k.split(',');
return {x: k[0] - 0, y: k[1] - 0};
};
Micro.WorldEffects = function (map) {
this._map = map;
this._data = {};
};
Micro.WorldEffects.prototype = {
constructor: Micro.WorldEffects,
clear : function() {
this._data = [];
},
getTile : function(x, y) {
var key = Micro.toKey(x, y);
var tile = this._data[key];
if (tile === undefined) tile = this._map.getTile(x, y);
return tile;
},
getTileValue : function(x, y) {
return this.getTile(x, y).getValue();
},
setTile : function(x, y, value, flags) {
if (flags !== undefined && value instanceof Micro.Tile) throw new Error('Flags supplied with already defined tile');
if (flags === undefined && !(value instanceof Micro.Tile)) value = new Micro.Tile(value);
else if (flags !== undefined) value = new Micro.Tile(value, flags);
var key = Micro.toKey(x, y);
this._data[key] = value;
},
apply : function() {
var keys = Object.keys(this._data);
for (var i = 0, l = keys.length; i < l; i++) {
var coords = Micro.fromKey(keys[i]);
this._map.setTo(coords, this._data[keys[i]]);
}
}
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.BaseTool = function(){
this.TOOLRESULT_OK = 0;
this.TOOLRESULT_FAILED = 1;
this.TOOLRESULT_NO_MONEY = 2;
this.TOOLRESULT_NEEDS_BULLDOZE = 3;
this.autoBulldoze = true;
this.bulldozerCost = 1;
};
Micro.BaseTool.prototype = {
constructor: Micro.BaseTool,
init : function(cost, map, shouldAutoBulldoze, IsDraggable) {
Object.defineProperty(this, 'toolCost', Micro.makeConstantDescriptor(cost));
this.result = null;
this.isDraggable = IsDraggable || false;
this._shouldAutoBulldoze = shouldAutoBulldoze;
this._map = map;
this._worldEffects = new Micro.WorldEffects(map);
this._applicationCost = 0;
},
clear : function() {
this._applicationCost = 0;
this._worldEffects.clear();
//this.result = null;
},
addCost : function(cost) {
this._applicationCost += cost;
},
doAutoBulldoze : function(x, y) {
if (!this._shouldAutoBulldoze) return;
var tile = this._worldEffects.getTile(x, y);
if (tile.isBulldozable()) {
tile = Micro.normalizeRoad(tile);
if ((tile >= Tile.TINYEXP && tile <= Tile.LASTTINYEXP) || (tile < Tile.HBRIDGE && tile !== Tile.DIRT)) {
this.addCost(1);
this._worldEffects.setTile(x, y, Tile.DIRT);
}
}
},
apply : function(budget) {//, messageManager) {
this._worldEffects.apply();
budget.spend(this._applicationCost);//, messageManager);
//messageManager.sendMessage(Messages.DID_TOOL);
this.clear();
},
modifyIfEnoughFunding : function(budget, messageManager) {
if (this.result !== this.TOOLRESULT_OK) { this.clear(); return false; }
if (budget.totalFunds < this._applicationCost) { this.result = this.TOOLRESULT_NO_MONEY; this.clear(); return false; }
this.apply.call(this, budget);//, messageManager);
this.clear();
return true;
},
setAutoBulldoze: function(value) {
this.autoBulldoze = value;
}
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.RoadTable = [
Tile.ROADS, Tile.ROADS2, Tile.ROADS, Tile.ROADS3,
Tile.ROADS2, Tile.ROADS2, Tile.ROADS4, Tile.ROADS8,
Tile.ROADS, Tile.ROADS6, Tile.ROADS, Tile.ROADS7,
Tile.ROADS5, Tile.ROADS10, Tile.ROADS9, Tile.INTERSECTION
];
Micro.RailTable = [
Tile.LHRAIL, Tile.LVRAIL, Tile.LHRAIL, Tile.LVRAIL2,
Tile.LVRAIL, Tile.LVRAIL, Tile.LVRAIL3, Tile.LVRAIL7,
Tile.LHRAIL, Tile.LVRAIL5, Tile.LHRAIL, Tile.LVRAIL6,
Tile.LVRAIL4, Tile.LVRAIL9, Tile.LVRAIL8, Tile.LVRAIL10
];
Micro.WireTable = [
Tile.LHPOWER, Tile.LVPOWER, Tile.LHPOWER, Tile.LVPOWER2,
Tile.LVPOWER, Tile.LVPOWER, Tile.LVPOWER3, Tile.LVPOWER7,
Tile.LHPOWER, Tile.LVPOWER5, Tile.LHPOWER, Tile.LVPOWER6,
Tile.LVPOWER4, Tile.LVPOWER9, Tile.LVPOWER8, Tile.LVPOWER10
];
Micro.BaseToolConnector = function(){
Micro.BaseTool.call( this );
};
Micro.BaseToolConnector.prototype = Object.create( Micro.BaseTool.prototype );
Micro.BaseToolConnector.prototype.fixSingle=function(x, y) {
var adjTile = 0;
var tile = this._worldEffects.getTile(x, y);
tile = Micro.normalizeRoad(tile);
if (tile >= Tile.ROADS && tile <= Tile.INTERSECTION) {
if (y > 0) {
tile = this._worldEffects.getTile(x, y - 1);
tile = Micro.normalizeRoad(tile);
if ((tile === Tile.HRAILROAD || (tile >= Tile.ROADBASE && tile <= Tile.VROADPOWER)) && tile !== Tile.HROADPOWER && tile !== Tile.VRAILROAD && tile !== Tile.ROADBASE) adjTile |= 1;
}
if (x < this._map.width - 1) {
tile = this._worldEffects.getTile(x + 1, y);
tile = Micro.normalizeRoad(tile);
if ((tile === Tile.VRAILROAD || (tile >= Tile.ROADBASE && tile <= Tile.VROADPOWER)) && tile !== Tile.VROADPOWER && tile !== Tile.HRAILROAD && tile !== Tile.VBRIDGE) adjTile |= 2;
}
if (y < this._map.height - 1) {
tile = this._worldEffects.getTile(x, y + 1);
tile = Micro.normalizeRoad(tile);
if ((tile === Tile.HRAILROAD || (tile >= Tile.ROADBASE && tile <= Tile.VROADPOWER)) && tile !== Tile.HROADPOWER && tile !== Tile.VRAILROAD && tile !== Tile.ROADBASE) adjTile |= 4;
}
if (x > 0) {
tile = this._worldEffects.getTile(x - 1, y);
tile = Micro.normalizeRoad(tile);
if ((tile === Tile.VRAILROAD || (tile >= Tile.ROADBASE && tile <= Tile.VROADPOWER)) && tile !== Tile.VROADPOWER && tile !== Tile.HRAILROAD && tile !== Tile.VBRIDGE) adjTile |= 8;
}
this._worldEffects.setTile(x, y, Micro.RoadTable[adjTile] | Tile.BULLBIT | Tile.BURNBIT);
return;
}
if (tile >= Tile.LHRAIL && tile <= Tile.LVRAIL10) {
if (y > 0) {
tile = this._worldEffects.getTile(x, y - 1);
tile = Micro.normalizeRoad(tile);
if (tile >= Tile.RAILHPOWERV && tile <= Tile.VRAILROAD && tile !== Tile.RAILHPOWERV && tile !== Tile.HRAILROAD && tile !== Tile.HRAIL) adjTile |= 1;
}
if (x < this._map.width - 1) {
tile = this._worldEffects.getTile(x + 1, y);
tile = Micro.normalizeRoad(tile);
if (tile >= Tile.RAILHPOWERV && tile <= Tile.VRAILROAD && tile !== Tile.RAILVPOWERH && tile !== Tile.VRAILROAD && tile !== Tile.VRAIL) adjTile |= 2;
}
if (y < this._map.height - 1) {
tile = this._worldEffects.getTile(x, y + 1);
tile = Micro.normalizeRoad(tile);
if (tile >= Tile.RAILHPOWERV && tile <= Tile.VRAILROAD && tile !== Tile.RAILHPOWERV && tile !== Tile.HRAILROAD && tile !== Tile.HRAIL) adjTile |= 4;
}
if (x > 0) {
tile = this._worldEffects.getTile(x - 1, y);
tile = Micro.normalizeRoad(tile);
if (tile >= Tile.RAILHPOWERV && tile <= Tile.VRAILROAD && tile !== Tile.RAILVPOWERH && tile !== Tile.VRAILROAD && tile !== Tile.VRAIL) adjTile |= 8;
}
this._worldEffects.setTile(x, y, Micro.RailTable[adjTile] | Tile.BULLBIT | Tile.BURNBIT);
return;
}
if (tile >= Tile.LHPOWER && tile <= Tile.LVPOWER10) {
if (y > 0) {
tile = this._worldEffects.getTile(x, y - 1);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile !== Tile.VPOWER && tile !== Tile.VROADPOWER && tile !== Tile.RAILVPOWERH) adjTile |= 1;
}
}
if (x < this._map.width - 1) {
tile = this._worldEffects.getTile(x + 1, y);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile !== Tile.HPOWER && tile !== Tile.HROADPOWER && tile !== Tile.RAILHPOWERV) adjTile |= 2;
}
}
if (y < this._map.height - 1) {
tile = this._worldEffects.getTile(x, y + 1);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile !== Tile.VPOWER && tile !== Tile.VROADPOWER && tile !== Tile.RAILVPOWERH) adjTile |= 4;
}
}
if (x > 0) {
tile = this._worldEffects.getTile(x - 1, y);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile !== Tile.HPOWER && tile !== Tile.HROADPOWER && tile !== Tile.RAILHPOWERV) adjTile |= 8;
}
}
this._worldEffects.setTile(x, y, Micro.WireTable[adjTile] | Tile.BLBNCNBIT);
return;
}
};
Micro.BaseToolConnector.prototype.checkZoneConnections = function(x, y) {
this.fixSingle(x, y);
if (y > 0) this.fixSingle(x, y - 1);
if (x < this._map.width - 1) this.fixSingle(x + 1, y);
if (y < this._map.height - 1) this.fixSingle(x, y + 1);
if (x > 0) this.fixSingle(x - 1, y);
}
Micro.BaseToolConnector.prototype.checkBorder = function(x, y, size) {
// Adjust to top left tile
x = x - 1;
y = y - 1;
var i;
for (i = 0; i < size; i++) this.fixZone(x + i, y - 1);
for (i = 0; i < size; i++) this.fixZone(x - 1, y + i);
for (i = 0; i < size; i++) this.fixZone(x + i, y + size);
for (i = 0; i < size; i++) this.fixZone(x + size, y + i);
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.ParkTool = function (map) {
Micro.BaseTool.call( this );
this.init(10, map, true);
};
Micro.ParkTool.prototype = Object.create( Micro.BaseTool.prototype );
Micro.ParkTool.prototype.doTool = function(x, y, messageManager, blockMaps) {
if (this._worldEffects.getTileValue(x, y) !== Tile.DIRT) {
this.result = this.TOOLRESULT_NEEDS_BULLDOZE;
return;
}
var value = Random.getRandom(4);
var tileFlags = Tile.BURNBIT | Tile.BULLBIT;
var tileValue;
if (value === 4) {
tileValue = Tile.FOUNTAIN;
tileFlags |= Tile.ANIMBIT;
} else {
tileValue = value + Tile.WOODS2;
}
this._worldEffects.setTile(x, y, tileValue, tileFlags);
this.addCost(10);
this.result = this.TOOLRESULT_OK;
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.BulldozerTool = function (map) {
Micro.BaseTool.call( this );
this.init(10, map, true);
}
Micro.BulldozerTool.prototype = Object.create( Micro.BaseTool.prototype );
Micro.BulldozerTool.prototype.putRubble = function(x, y, size) {
for (var xx = x; xx < x + size; xx++) {
for (var yy = y; yy < y + size; yy++) {
if (this._map.testBounds(xx, yy)) {
var tile = this._worldEffects.getTile(xx, yy);
if (tile != Tile.RADTILE && tile != Tile.DIRT) this._worldEffects.setTile(xx, yy, Tile.TINYEXP + Random.getRandom(2), Tile.ANIMBIT | Tile.BULLBIT);
}
}
}
};
Micro.BulldozerTool.prototype.layDoze = function(x, y) {
var tile = this._worldEffects.getTile(x, y);
if (!tile.isBulldozable()) return this.TOOLRESULT_FAILED;
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
switch (tile) {
case Tile.HBRIDGE:
case Tile.VBRIDGE:
case Tile.BRWV:
case Tile.BRWH:
case Tile.HBRDG0:
case Tile.HBRDG1:
case Tile.HBRDG2:
case Tile.HBRDG3:
case Tile.VBRDG0:
case Tile.VBRDG1:
case Tile.VBRDG2:
case Tile.VBRDG3:
case Tile.HPOWER:
case Tile.VPOWER:
case Tile.HRAIL:
case Tile.VRAIL:
this._worldEffects.setTile(x, y, Tile.RIVER);
break;
default: this._worldEffects.setTile(x, y, Tile.DIRT); break;
}
this.addCost(1);
return this.TOOLRESULT_OK;
};
Micro.BulldozerTool.prototype.doTool = function(x, y, messageManager, blockMaps) {
if (!this._map.testBounds(x, y)) this.result = this.TOOLRESULT_FAILED;
var tile = this._worldEffects.getTile(x, y);
var tileValue = tile.getValue();
var zoneSize = 0;
var deltaX;
var deltaY;
if (tile.isZone()) {
zoneSize = Micro.checkZoneSize(tileValue);
deltaX = 0;
deltaY = 0;
} else {
var result = Micro.checkBigZone(tileValue);
zoneSize = result.zoneSize;
deltaX = result.deltaX;
deltaY = result.deltaY;
}
if (zoneSize > 0) {
this.addCost(this.bulldozerCost);
var dozeX = x;
var dozeY = y;
var centerX = x + deltaX;
var centerY = y + deltaY;
switch (zoneSize) {
case 3:
messageManager.sendMessage(Messages.SOUND_EXPLOSIONHIGH);
this.putRubble(centerX - 1, centerY - 1, 3);
break;
case 4:
messageManager.sendMessage(Messages.SOUND_EXPLOSIONLOW);
this.putRubble(centerX - 1, centerY - 1, 4);
break;
case 6:
messageManager.sendMessage(Messages.SOUND_EXPLOSIONHIGH);
messageManager.sendMessage(Messages.SOUND_EXPLOSIONLOW);
this.putRubble(centerX - 1, centerY - 1, 6);
break;
}
this.result = this.TOOLRESULT_OK;
}
var toolResult;
if (tileValue === Tile.RIVER || tileValue === Tile.REDGE || tileValue === Tile.CHANNEL) {
toolResult = this.layDoze(x, y);
if (tileValue !== this._worldEffects.getTileValue(x, y)) this.addCost(5);
} else {
toolResult = this.layDoze(x, y);
}
this.result = toolResult;
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.BuildingTool = function(cost, centreTile, map, size, animated) {
Micro.BaseToolConnector.call( this );
this.init(cost, map, false);
this.centreTile = centreTile;
this.size = size;
this.animated = animated;
};
Micro.BuildingTool.prototype = Object.create( Micro.BaseToolConnector.prototype );
Micro.BuildingTool.prototype.putBuilding = function(leftX, topY) {
var posX, posY, tileValue, tileFlags;
var baseTile = this.centreTile - this.size - 1;
for (var dy = 0; dy < this.size; dy++) {
posY = topY + dy;
for (var dx = 0; dx < this.size; dx++) {
posX = leftX + dx;
tileValue = baseTile;
tileFlags = Tile.BNCNBIT;
if (dx === 1) {
if (dy === 1) tileFlags |= Tile.ZONEBIT;
else if (dy === 2 && this.animated) tileFlags |= Tile.ANIMBIT;
}
this._worldEffects.setTile(posX, posY, tileValue, tileFlags);
baseTile++;
}
}
};
Micro.BuildingTool.prototype.prepareBuildingSite = function(leftX, topY) {
// Check that the entire site is on the map
if (leftX < 0 || leftX + this.size > this._map.width) return this.TOOLRESULT_FAILED;
if (topY < 0 || topY + this.size > this._map.height) return this.TOOLRESULT_FAILED;
var posX, posY, tileValue;
// Check whether the tiles are clear
for (var dy = 0; dy < this.size; dy++) {
posY = topY + dy;
for (var dx = 0; dx < this.size; dx++) {
posX = leftX + dx;
tileValue = this._worldEffects.getTileValue(posX, posY);
if (tileValue === Tile.DIRT) continue;
if (!this.autoBulldoze) {
// No Tile.DIRT and no bull-dozer => not buildable
return this.TOOLRESULT_NEEDS_BULLDOZE;
}
if (!Micro.canBulldoze(tileValue)) {
// tilevalue cannot be auto-bulldozed
return this.TOOLRESULT_NEEDS_BULLDOZE;
}
this._worldEffects.setTile(posX, posY, Tile.DIRT);
this.addCost(this.bulldozerCost);
}
}
return this.TOOLRESULT_OK;
};
Micro.BuildingTool.prototype.buildBuilding = function(x, y) {
// Correct to top left
x--;
y--;
var prepareResult = this.prepareBuildingSite(x, y);
if (prepareResult !== this.TOOLRESULT_OK) return prepareResult;
this.addCost(this.toolCost);
this.putBuilding(x, y);
this.checkBorder(x, y);
return this.TOOLRESULT_OK;
};
Micro.BuildingTool.prototype.doTool = function(x, y, messageManager, blockMaps) {
this.result = this.buildBuilding(x, y);
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.RailTool = function (map) {
Micro.BaseToolConnector.call( this );
this.init(20, map, true, true);
}
Micro.RailTool.prototype = Object.create( Micro.BaseToolConnector.prototype );
Micro.RailTool.prototype.layRail = function(x, y) {
this.doAutoBulldoze(x, y);
var cost = 20;
var tile = this._worldEffects.getTileValue(x, y);
tile = Micro.normalizeRoad(tile);
switch (tile) {
case Tile.DIRT: this._worldEffects.setTile(x, y, Tile.LHRAIL | Tile.BULLBIT | Tile.BURNBIT); break;
case Tile.RIVER:
case Tile.REDGE:
case Tile.CHANNEL:
cost = 100;
if (x < this._map.width - 1) {
tile = this._worldEffects.getTileValue(x + 1, y);
tile = Micro.normalizeRoad(tile);
if (tile == Tile.RAILHPOWERV || tile == Tile.HRAIL || (tile >= Tile.LHRAIL && tile <= Tile.HRAILROAD)) {
this._worldEffects.setTile(x, y, Tile.HRAIL, Tile.BULLBIT);
break;
}
}
if (x > 0) {
tile = this._worldEffects.getTileValue(x - 1, y);
tile = Micro.normalizeRoad(tile);
if (tile == Tile.RAILHPOWERV || tile == Tile.HRAIL || (tile > Tile.VRAIL && tile < Tile.VRAILROAD)) {
this._worldEffects.setTile(x, y, Tile.HRAIL, Tile.BULLBIT);
break;
}
}
if (y < this._map.height - 1) {
tile = this._worldEffects.getTileValue(x, y + 1);
tile = Micro.normalizeRoad(tile);
if (tile == Tile.RAILVPOWERH || tile == Tile.VRAILROAD || (tile > Tile.HRAIL && tile < Tile.HRAILROAD)) {
this._worldEffects.setTile(x, y, Tile.VRAIL, Tile.BULLBIT);
break;
}
}
if (y > 0) {
tile = this._worldEffects.getTileValue(x, y - 1);
tile = Micro.normalizeRoad(tile);
if (tile == Tile.RAILVPOWERH || tile == Tile.VRAILROAD || (tile > Tile.HRAIL && tile < Tile.HRAILROAD)) {
this._worldEffects.setTile(x, y, Tile.VRAIL, Tile.BULLBIT);
break;
}
}
return this.TOOLRESULT_FAILED;
case Tile.LHPOWER: this._worldEffects.setTile(x, y, Tile.RAILVPOWERH, Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.LVPOWER: this._worldEffects.setTile(x, y, Tile.RAILHPOWERV, Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.ROADS: this._worldEffects.setTile(x, y, Tile.VRAILROAD, Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.ROADS2: this._worldEffects.setTile(x, y, Tile.HRAILROAD, Tile.BURNBIT | Tile.BULLBIT); break;
default: return this.TOOLRESULT_FAILED;
}
this.addCost(cost);
this.checkZoneConnections(x, y);
return this.TOOLRESULT_OK;
};
Micro.RailTool.prototype.doTool = function(x, y, messageManager, blockMaps) {
this.result = this.layRail(x, y);
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.WireTool = function (map) {
Micro.BaseToolConnector.call( this );
//this.init(20, map, true, true);
this.init(5, map, true, true);
}
Micro.WireTool.prototype = Object.create( Micro.BaseToolConnector.prototype );
Micro.WireTool.prototype.layWire = function(x, y) {
this.doAutoBulldoze(x, y);
var cost = 5;
var tile = this._worldEffects.getTileValue(x, y);
tile = Micro.normalizeRoad(tile);
switch (tile) {
case Tile.DIRT: this._worldEffects.setTile(x, y, Tile.LHPOWER, Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.RIVER: case Tile.REDGE: case Tile.CHANNEL:
cost = 25;
if (x < this._map.width - 1) {
tile = this._worldEffects.getTile(x + 1, y);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile != Tile.HROADPOWER && tile != Tile.RAILHPOWERV && tile != Tile.HPOWER) {
this._worldEffects.setTile(x, y, Tile.VPOWER, Tile.CONDBIT | Tile.BULLBIT);
break;
}
}
}
if (x > 0) {
tile = this._worldEffects.getTile(x - 1, y);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile != Tile.HROADPOWER && tile != Tile.RAILHPOWERV && tile != Tile.HPOWER) {
this._worldEffects.setTile(x, y, Tile.VPOWER, Tile.CONDBIT | Tile.BULLBIT);
break;
}
}
}
if (y < this._map.height - 1) {
tile = this._worldEffects.getTile(x, y + 1);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile != Tile.VROADPOWER && tile != Tile.RAILVPOWERH && tile != Tile.VPOWER) {
this._worldEffects.setTile(x, y, Tile.HPOWER, Tile.CONDBIT | Tile.BULLBIT);
break;
}
}
}
if (y > 0) {
tile = this._worldEffects.getTile(x, y - 1);
if (tile.isConductive()) {
tile = tile.getValue();
tile = Micro.normalizeRoad(tile);
if (tile != Tile.VROADPOWER && tile != Tile.RAILVPOWERH && tile != Tile.VPOWER) {
this._worldEffects.setTile(x, y, Tile.HPOWER, Tile.CONDBIT | Tile.BULLBIT);
break;
}
}
}
return this.TOOLRESULT_FAILED;
case Tile.ROADS: this._worldEffects.setTile(x, y, Tile.HROADPOWER, Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.ROADS2: this._worldEffects.setTile(x, y, Tile.VROADPOWER, Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.LHRAIL: this._worldEffects.setTile(x, y, Tile.RAILHPOWERV, Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.LVRAIL: this._worldEffects.setTile(x, y, Tile.RAILVPOWERH, Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
default: return this.TOOLRESULT_FAILED;
}
this.addCost(cost);
this.checkZoneConnections(x, y);
return this.TOOLRESULT_OK;
};
Micro.WireTool.prototype.doTool = function(x, y, messageManager, blockMaps) {
this.result = this.layWire(x, y);
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.RoadTool = function (map) {
Micro.BaseToolConnector.call( this );
this.init(10, map, true, true);
}
Micro.RoadTool.prototype = Object.create( Micro.BaseToolConnector.prototype );
Micro.RoadTool.prototype.layRoad = function(x, y) {
this.doAutoBulldoze(x, y);
var tile = this._worldEffects.getTileValue(x, y);
var cost = 10;
switch (tile) {
case Tile.DIRT: this._worldEffects.setTile(x, y, Tile.ROADS, Tile.BULLBIT | Tile.BURNBIT); break;
case Tile.RIVER:
case Tile.REDGE:
case Tile.CHANNEL:
cost = 50;
if (x < this._map.width - 1) {
tile = this._worldEffects.getTileValue(x + 1, y);
tile = Micro.normalizeRoad(tile);
if (tile === Tile.VRAILROAD || tile === Tile.HBRIDGE || (tile >= Tile.ROADS && tile <= Tile.HROADPOWER)) {
this._worldEffects.setTile(x, y, Tile.HBRIDGE, Tile.BULLBIT);
break;
}
}
if (x > 0) {
tile = this._worldEffects.getTileValue(x - 1, y);
tile = Micro.normalizeRoad(tile);
if (tile === Tile.VRAILROAD || tile === Tile.HBRIDGE || (tile >= Tile.ROADS && tile <= Tile.INTERSECTION)) {
this._worldEffects.setTile(x, y, Tile.HBRIDGE, Tile.BULLBIT);
break;
}
}
if (y < this._map.height - 1) {
tile = this._worldEffects.getTileValue(x, y + 1);
tile = Micro.normalizeRoad(tile);
if (tile === Tile.HRAILROAD || tile === Tile.VROADPOWER || (tile >= Tile.VBRIDGE && tile <= Tile.INTERSECTION)) {
this._worldEffects.setTile(x, y, Tile.VBRIDGE, Tile.BULLBIT);
break;
}
}
if (y > 0) {
tile = this._worldEffects.getTileValue(x, y - 1);
tile = Micro.normalizeRoad(tile);
if (tile === Tile.HRAILROAD || tile === Tile.VROADPOWER || (tile >= Tile.VBRIDGE && tile <= Tile.INTERSECTION)) {
this._worldEffects.setTile(x, y, Tile.VBRIDGE, Tile.BULLBIT);
break;
}
}
return this.TOOLRESULT_FAILED;
case Tile.LHPOWER: this._worldEffects.setTile(x, y, Tile.VROADPOWER | Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.LVPOWER: this._worldEffects.setTile(x, y, Tile.HROADPOWER | Tile.CONDBIT | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.LHRAIL: this._worldEffects.setTile(x, y, Tile.HRAILROAD | Tile.BURNBIT | Tile.BULLBIT); break;
case Tile.LVRAIL: this._worldEffects.setTile(x, y, Tile.VRAILROAD | Tile.BURNBIT | Tile.BULLBIT); break;
default: return this.TOOLRESULT_FAILED;
}
this.addCost(cost);
this.checkZoneConnections(x, y);
return this.TOOLRESULT_OK;
};
Micro.RoadTool.prototype.doTool = function(x, y, messageManager, blockMaps) {
this.result = this.layRoad(x, y);
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.QueryTool = function (map) {
Micro.BaseTool.call( this );
this.init(0, map, false, false);
this.txt = "";
}
// Keep in sync with QueryWindow
var debug = true;
Micro.QueryTool.prototype = Object.create( Micro.BaseTool.prototype );
Micro.QueryTool.prototype.classifyPopulationDensity = function(x, y, blockMaps) {
var density = blockMaps.populationDensityMap.worldGet(x, y);
//if (debug) document.getElementById("queryDensityRaw").innerHTML=density;
density = density >> 6;
density = density & 3;
this.txt+='Density: '+TXT.densityStrings[density]+'<br>';
//document.getElementById("queryDensity").innerHTML=TXT.densityStrings[density];
};
Micro.QueryTool.prototype.classifyLandValue = function(x, y, blockMaps) {
var landValue = blockMaps.landValueMap.worldGet(x, y);
//if (debug) document.getElementById("queryLandValueRaw").innerHTML=landValue;
var i = 0;
if (landValue >= 150) i = 3;
else if (landValue >= 80) i = 2;
else if (landValue >= 30) i = 1;
//var text = TXT.landValueStrings[i];
this.txt+='Value: '+TXT.landValueStrings[i]+'<br>';
//document.getElementById("queryLandValue").innerHTML=text;
};
Micro.QueryTool.prototype.classifyCrime = function(x, y, blockMaps) {
var crime = blockMaps.crimeRateMap.worldGet(x, y);
//if (debug) document.getElementById("queryCrimeRaw").innerHTML=crime;
crime = crime >> 6;
crime = crime & 3;
this.txt+='Crime: '+TXT.crimeStrings[crime]+'<br>';
//document.getElementById("queryCrime").innerHTML=TXT.crimeStrings[crime];
};
Micro.QueryTool.prototype.classifyPollution = function(x, y, blockMaps) {
var pollution = blockMaps.pollutionDensityMap.worldGet(x, y);
//if (debug) document.getElementById("queryPollutionRaw").innerHTML=pollution;
pollution = pollution >> 6;
pollution = pollution & 3;
this.txt+='Pollution: '+TXT.pollutionStrings[pollution]+'<br>';
//document.getElementById("queryPollution").innerHTML=TXT.pollutionStrings[pollution];
};
Micro.QueryTool.prototype.classifyRateOfGrowth = function(x, y, blockMaps) {
var rate = blockMaps.rateOfGrowthMap.worldGet(x, y);
//if (debug) document.getElementById("queryRateRaw").innerHTML=rate;
rate = rate >> 6;
rate = rate & 3;
this.txt+='Growth: '+TXT.rateStrings[rate];
//document.getElementById("queryRate").innerHTML=TXT.rateStrings[rate];
};
Micro.QueryTool.prototype.classifyDebug = function(x, y, blockMaps) {
if (!debug) return;
/*document.getElementById("queryFireStationRaw").innerHTML=blockMaps.fireStationMap.worldGet(x, y);
document.getElementById("queryFireStationEffectRaw").innerHTML=blockMaps.fireStationEffectMap.worldGet(x, y);
document.getElementById("queryPoliceStationRaw").innerHTML=blockMaps.policeStationMap.worldGet(x, y);
document.getElementById("queryPoliceStationEffectRaw").innerHTML=blockMaps.policeStationEffectMap.worldGet(x, y);
document.getElementById("queryTerrainDensityRaw").innerHTML=blockMaps.terrainDensityMap.worldGet(x, y);
document.getElementById("queryTrafficDensityRaw").innerHTML=blockMaps.trafficDensityMap.worldGet(x, y);
document.getElementById("queryComRateRaw").innerHTML=blockMaps.comRateMap.worldGet(x, y);*/
};
Micro.QueryTool.prototype.classifyZone = function(x, y) {
var baseTiles = [
Tile.DIRT, Tile.RIVER, Tile.TREEBASE, Tile.RUBBLE,
Tile.FLOOD, Tile.RADTILE, Tile.FIRE, Tile.ROADBASE,
Tile.POWERBASE, Tile.RAILBASE, Tile.RESBASE, Tile.COMBASE,
Tile.INDBASE, Tile.PORTBASE, Tile.AIRPORTBASE, Tile.COALBASE,
Tile.FIRESTBASE, Tile.POLICESTBASE, Tile.STADIUMBASE, Tile.NUCLEARBASE,
Tile.HBRDG0, Tile.RADAR0, Tile.FOUNTAIN, Tile.INDBASE2,
Tile.FOOTBALLGAME1, Tile.VBRDG0, 952];
var tileValue = this._map.getTileValue(x, y);
if (tileValue >= Tile.COALSMOKE1 && tileValue < Tile.FOOTBALLGAME1) tileValue = Tile.COALBASE;
var index = 0, l;
for (index = 0, l = baseTiles.length - 1; index < l; index++) {
if (tileValue < baseTiles[index + 1])
break;
}
this.txt='Zone: '+TXT.zoneTypes[index]+'<br>';
//document.getElementById("queryZoneType").innerHTML=TXT.zoneTypes[index];
};
Micro.QueryTool.prototype.getInfo = function() {
return this.txt;
};
Micro.QueryTool.prototype.doTool = function(x, y, messageManager, blockMaps) {
var text = 'Position (' + x + ', ' + y + ')';
text += ' TileValue: ' + this._map.getTileValue(x, y);
if (debug) {
var tile = this._map.getTile(x, y);
/*document.getElementById("queryTile").innerHTML=[x,y].join(', ');
document.getElementById("queryTileValue").innerHTML=tile.getValue();
document.getElementById("queryTileBurnable").innerHTML=tile.isCombustible();
document.getElementById("queryTileBulldozable").innerHTML=tile.isBulldozable();
document.getElementById("queryTileCond").innerHTML=tile.isConductive();
document.getElementById("queryTileAnim").innerHTML=tile.isAnimated();
document.getElementById("queryTilePowered").innerHTML=tile.isPowered();*/
}
this.classifyZone(x, y);
this.classifyPopulationDensity(x, y, blockMaps);
this.classifyLandValue(x, y, blockMaps);
this.classifyCrime(x, y, blockMaps);
this.classifyPollution(x, y, blockMaps);
this.classifyRateOfGrowth(x, y, blockMaps);
this.classifyDebug(x, y, blockMaps);
messageManager.sendMessage(Messages.QUERY_WINDOW_NEEDED);
this.result = this.TOOLRESULT_OK;
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.GameTools = function (map) {
return {
airport: new Micro.BuildingTool(10000, Tile.AIRPORT, map, 6, false),
bulldozer: new Micro.BulldozerTool(map),
coal: new Micro.BuildingTool(3000, Tile.POWERPLANT, map, 4, false),
commercial: new Micro.BuildingTool(100, Tile.COMCLR, map, 3, false),
fire: new Micro.BuildingTool(500, Tile.FIRESTATION, map, 3, false),
industrial: new Micro.BuildingTool(100, Tile.INDCLR, map, 3, false),
nuclear: new Micro.BuildingTool(5000, Tile.NUCLEAR, map, 4, true),
park: new Micro.ParkTool(map),
police: new Micro.BuildingTool(500, Tile.POLICESTATION, map, 3, false),
port: new Micro.BuildingTool(3000, Tile.PORT, map, 4, false),
rail: new Micro.RailTool(map),
residential: new Micro.BuildingTool(100, Tile.FREEZ, map, 3, false),
road: new Micro.RoadTool(map),
query: new Micro.QueryTool(map),
stadium: new Micro.BuildingTool(5000, Tile.STADIUM, map, 4, false),
wire: new Micro.WireTool(map),
};
};
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.BaseSprite = function(){}
Micro.BaseSprite.prototype = {
constructor: Micro.BaseSprite,
init : function(type, map, spriteManager, x, y) {
this.type = type;
this.map = map;
this.spriteManager = spriteManager;
this.x = x;
this.y = y;
this.origX = 0;
this.origY = 0;
this.destX = 0;
this.destY = 0;
this.count = 0;
this.soundCount = 0;
this.dir = 0;
this.newDir = 0;
this.step = 0;
this.flag = 0;
this.turn = 0;
this.accel = 0;
this.speed = 100;
},
getFileName : function() {
return ['obj', this.type, '-', this.frame - 1].join('');
},
spriteNotInBounds : function() {
var x = Micro.pixToWorld(this.x);
var y = Micro.pixToWorld(this.y);
return x < 0 || y < 0 || x >= this.map.width || y >= this.map.height;
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.TrainSprite = function(map, spriteManager, x, y) {
Micro.BaseSprite.call( this );
this.init(Micro.SPRITE_TRAIN, map, spriteManager, x, y);
this.width = 32;
this.height = 32;
this.xOffset = -16;
this.yOffset = -16;
this.frame = 1;
this.dir = 4;
this.tileDeltaX = [ 0, 16, 0, -16];
this.tileDeltaY = [-16, 0, 16, 0 ];
this.xDelta = [ 0, 4, 0, -4, 0];
this.yDelta = [ -4, 0, 4, 0, 0];
this.TrainPic2 = [ 1, 2, 1, 2, 5];
// Frame values
this.NORTHSOUTH = 1;
this.EASTWEST = 2;
this.NWSE = 3;
this.NESW = 4;
this.UNDERWATER = 5;
// Direction values
this.NORTH = 0;
this.EAST = 1;
this.SOUTH = 2;
this.WEST = 3;
this.CANTMOVE = 4;
}
Micro.TrainSprite.prototype = Object.create( Micro.BaseSprite.prototype );
// BaseSprite(TrainSprite);
/*var tileDeltaX = [ 0, 16, 0, -16];
var tileDeltaY = [-16, 0, 16, 0 ];
var xDelta = [ 0, 4, 0, -4, 0];
var yDelta = [ -4, 0, 4, 0, 0];
var TrainPic2 = [ 1, 2, 1, 2, 5];
// Frame values
var NORTHSOUTH = 1;
var EASTWEST = 2;
var NWSE = 3;
var NESW = 4;
var UNDERWATER = 5;
// Direction values
var NORTH = 0;
var EAST = 1;
var SOUTH = 2;
var WEST = 3;
var CANTMOVE = 4;*/
Micro.TrainSprite.prototype.move = function(spriteCycle, messageManager, disasterManager, blockMaps) {
// Trains can only move in the 4 cardinal directions
// Over the course of 4 frames, we move through a tile, so
// ever fourth frame, we try to find a direction to move in
// (excluding the opposite direction from the current direction
// of travel). If there is no possible direction found, our direction
// is set to CANTMOVE. (Thus, if we're in a dead end, we can start heading
// backwards next time round). If we fail to find a destination after 2 attempts,
// we die.
if (this.frame === this.NWSE || this.frame === this.NESW)
this.frame = this.TrainPic2[this.dir];
this.x += this.xDelta[this.dir];
this.y += this.yDelta[this.dir];
// Find a new direction.
if ((spriteCycle & 3) === 0) {
// Choose a random starting point for our search
var dir = Random.getRandom16() & 3;
for (var i = dir; i < dir + 4; i++) {
var dir2 = i & 3;
if (this.dir !== this.CANTMOVE) {
// Avoid the opposite direction
if (dir2 === ((this.dir + 2) & 3))
continue;
}
var tileValue = Micro.getTileValue(this.map, this.x + this.tileDeltaX[dir2], this.y + this.tileDeltaY[dir2]);
if ((tileValue >= Tile.RAILBASE && tileValue <= Tile.LASTRAIL) ||
tileValue === Tile.RAILVPOWERH || tileValue === Tile.RAILHPOWERV) {
if (this.dir !== dir2 && this.dir !== this.CANTMOVE) {
if (this.dir + dir2 === this.WEST)
this.frame = this.NWSE;
else
this.frame = this.NESW;
} else {
this.frame = this.TrainPic2[dir2];
}
if (tileValue === Tile.HRAIL || tileValue === Tile.VRAIL)
this.frame = this.UNDERWATER;
this.dir = dir2;
return;
}
}
// Nowhere to go. Die.
if (this.dir === this.CANTMOVE) {
this.frame = 0;
return;
}
// We didn't find a direction this time. We'll try the opposite
// next time around
this.dir = this.CANTMOVE;
}
};
Micro.TrainSprite.prototype.explodeSprite = function(messageManager) {
this.frame = 0;
this.spriteManager.makeExplosionAt(this.x, this.y);
messageManager.sendMessage(Messages.TRAIN_CRASHED);
};
// Metadata for image loading
/* Object.defineProperties(Micro.TrainSprite,
{ID: Micro.makeConstantDescriptor(1),
width: Micro.makeConstantDescriptor(32),
frames: Micro.makeConstantDescriptor(5)});*/
//return TrainSprite;
//});
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.AirplaneSprite = function(map, spriteManager, x, y) {
Micro.BaseSprite.call( this );
this.init(Micro.SPRITE_AIRPLANE, map, spriteManager, x, y);
this.width = 48;
this.height = 48;
this.xOffset = -24;
this.yOffset = -24;
if (x > Micro.worldToPix(map.width - 20)) {
this.destX = this.x - 200;
this.frame = 7;
} else {
this.destX = this.x + 200;
this.frame = 11;
}
this.destY = this.y;
this.xDelta = [0, 0, 6, 8, 6, 0, -6, -8, -6, 8, 8, 8];
this.yDelta = [0, -8, -6, 0, 6, 8, 6, 0, -6, 0, 0, 0];
}
Micro.AirplaneSprite.prototype = Object.create( Micro.BaseSprite.prototype );
Micro.AirplaneSprite.prototype.move = function(spriteCycle, messageManager, disasterManager, blockMaps) {
var frame = this.frame;
if ((spriteCycle % 5) === 0) {
// Frames > 8 mean the plane is taking off
if (frame > 8) {
frame--;
if (frame < 9) {
// Planes always take off to the east
frame = 3;
}
this.frame = frame;
} else {
var d = Micro.getDir(this.x, this.y, this.destX, this.destY);
frame = Micro.turnTo(frame, d);
this.frame = frame;
}
}
var absDist = Micro.absoluteDistance(this.x, this.y, this.destX, this.destY);
if (absDist < 50) {
// We're pretty close to the destination
this.destX = Random.getRandom(Micro.worldToPix(this.map.width)) + 8;
this.destY = Random.getRandom(Micro.worldToPix(this.map.height)) + 8;
}
if (disasterManager.enableDisasters) {
var explode = false;
var spriteList = this.spriteManager.getSpriteList();
for (var i = 0; i < spriteList.length; i++) {
var s = spriteList[i];
//if (s.frame === 0 || s === sprite) continue;
if (s.frame === 0 ) continue;
if ((s.type === Micro.SPRITE_HELICOPTER ||
s.type === Micro.SPRITE_AIRPLANE) &&
Micro.checkSpriteCollision(this, s)) {
s.explodeSprite(messageManager);
explode = true;
}
}
if (explode)
this.explodeSprite(messageManager);
}
this.x += this.xDelta[frame];
this.y += this.yDelta[frame];
if (this.spriteNotInBounds()) this.frame = 0;
};
Micro.AirplaneSprite.prototype.explodeSprite = function(messageManager) {
this.frame = 0;
this.spriteManager.makeExplosionAt(this.x, this.y);
messageManager.sendMessage(Messages.PLANE_CRASHED);
};
// Metadata for image loading
/*Object.defineProperties(AirplaneSprite,
{ID: Micro.makeConstantDescriptor(3),
width: Micro.makeConstantDescriptor(48),
frames: Micro.makeConstantDescriptor(11)});
*/
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.BoatSprite = function(map, spriteManager, x, y) {
Micro.BaseSprite.call( this );
this.init(Micro.SPRITE_SHIP, map, spriteManager, x, y);
this.width = 48;
this.height = 48;
this.xOffset = -24;
this.yOffset = -24;
if (x < Micro.worldToPix(4)) this.frame = 3;
else if (x >= Micro.worldToPix(map.width - 4)) this.frame = 7;
else if (y < Micro.worldToPix(4)) this.frame = 5;
else if (y >= Micro.worldToPix(map.height - 4)) this.frame = 1;
else this.frame = 3;
this.newDir = this.frame;
this.dir = 10;
this.count = 1;
this.tileDeltaX = [0, 0, 1, 1, 1, 0, -1, -1, -1];
this.tileDeltaY = [0, -1, -1, 0, 1, 1, 1, 0, -1];
this.xDelta = [0, 0, 2, 2, 2, 0, -2, -2, -2];
this.yDelta = [0, -2, -2, 0, 2, 2, 2, 0, -2];
this.tileWhiteList = [Tile.RIVER, Tile.CHANNEL, Tile.POWERBASE, Tile.POWERBASE + 1, Tile.RAILBASE, Tile.RAILBASE + 1, Tile.BRWH, Tile.BRWV];
this.CANTMOVE = 10;
}
Micro.BoatSprite.prototype = Object.create( Micro.BaseSprite.prototype );
Micro.BoatSprite.prototype.move = function(spriteCycle, messageManager, disasterManager, blockMaps) {
var t = Tile.RIVER;
//var tile = Tile.RIVER;
if (this.soundCount > 0) this.soundCount--;
if (this.soundCount === 0) {
if ((Random.getRandom16() & 3) === 1) {
// TODO Scenarios
// TODO Sound
messageManager.sendMessage(Messages.SOUND_HONKHONK);
}
this.soundCount = 200;
}
if (this.count > 0)
this.count--;
if (this.count === 0) {
// Ships turn slowly: only 45° every 9 cycles
this.count = 9;
// If already executing a turn, continue to do so
if (this.frame !== this.newDir) {
this.frame = Micro.turnTo(this.frame, this.newDir);
return;
}
// Otherwise pick a new direction
// Choose a random starting direction to search from
// 0 = N, 1 = NE, ... 7 = NW
var startDir = Random.getRandom16() & 7;
var frame = this.frame;
for (var dir = startDir; dir < (startDir + 8); dir++) {
frame = (dir & 7) + 1;
if (frame === this.dir)
continue;
var x = Micro.pixToWorld(this.x) + this.tileDeltaX[frame];
var y = Micro.pixToWorld(this.y) + this.tileDeltaY[frame];
if (this.map.testBounds(x, y)) {
var tileValue = this.map.getTileValue(x, y);
// Test for a suitable water tile
if (tileValue === Tile.CHANNEL || tileValue === Tile.BRWH ||
tileValue === Tile.BRWV || this.oppositeAndUnderwater(tileValue, this.dir, frame)) {
this.newDir = frame;
this.frame = Micro.turnTo(this.frame, this.newDir);
this.dir = frame + 4;
if (this.dir > 8)
this.dir -= 8;
break;
}
}
}
if (dir === (startDir + 8)) {
this.dir = this.CANTMOVE;
this.newDir = (Random.getRandom16() & 7) + 1;
}
} else {
frame = this.frame;
if (frame === this.newDir) {
this.x += this.xDelta[frame];
this.y += this.yDelta[frame];
}
}
if (this.spriteNotInBounds()) {
this.frame = 0;
return;
}
// If we didn't find a new direction, we might explode
// depending on the last tile we looked at.
for (var i = 0; i < 8; i++) {
if (t === this.tileWhiteList[i]) {
break;
}
if (i === 7) {
this.explodeSprite(messageManager);
Micro.destroyMapTile(this.spriteManager, this.map, blockMaps, this.x, this.y);
}
}
};
Micro.BoatSprite.prototype.explodeSprite = function(messageManager) {
this.frame = 0;
this.spriteManager.makeExplosionAt(this.x, this.y);
messageManager.sendMessage(Messages.SHIP_CRASHED);
};
// This is an odd little function. It returns true if
// oldDir is 180° from newDir and tileValue is underwater
// rail or wire, and returns false otherwise
Micro.BoatSprite.prototype.oppositeAndUnderwater = function(tileValue, oldDir, newDir) {
var opposite = oldDir + 4;
if (opposite > 8) opposite -= 8;
if (newDir != opposite) return false;
if (tileValue == Tile.POWERBASE || tileValue == Tile.POWERBASE + 1 || tileValue == Tile.RAILBASE || tileValue == Tile.RAILBASE + 1) return true;
return false;
};
// Metadata for image loading
/* Object.defineProperties(BoatSprite,
{ID: Micro.makeConstantDescriptor(4),
width: Micro.makeConstantDescriptor(48),
frames: Micro.makeConstantDescriptor(8)});
return BoatSprite;
});*/
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.CopterSprite = function (map, spriteManager, x, y) {
Micro.BaseSprite.call( this );
this.init(Micro.SPRITE_HELICOPTER, map, spriteManager, x, y);
this.width = 32;
this.height = 32;
this.xOffset = -16;
this.yOffset = -16;
this.frame = 5;
this.count = 1500;
this.destX = Random.getRandom(Micro.worldToPix(map.width)) + 8;
this.destY = Random.getRandom(Micro.worldToPix(map.height)) + 8;
this.origX = x;
this.origY = y;
this.xDelta = [0, 0, 3, 5, 3, 0, -3, -5, -3];
this.yDelta = [0, -5, -3, 0, 3, 5, 3, 0, -3];
}
Micro.CopterSprite.prototype = Object.create( Micro.BaseSprite.prototype );
Micro.CopterSprite.prototype.move = function(spriteCycle, messageManager, disasterManager, blockMaps) {
if (this.soundCount > 0)
this.soundCount--;
if (this.count > 0)
this.count--;
if (this.count === 0) {
// Head towards a monster, and certain doom
var s = this.spriteManager.getSprite(Micro.SPRITE_MONSTER);
if (s !== null) {
this.destX = s.x;
this.destY = s.y;
} else {
// No monsters. Hm. I bet flying near that tornado is sensible
s = this.spriteManager.getSprite(Micro.SPRITE_TORNADO);
if (s !== null) {
this.destX = s.x;
this.destY = s.y;
} else {
this.destX = this.origX;
this.destY = this.origY;
}
}
// If near destination, let's get her on the ground
var absDist = Micro.absoluteDistance(this.x, this.y, this.origX, this.origY);
if (absDist < 30) {
this.frame = 0;
return;
}
}
if (this.soundCount === 0) {
var x = Micro.pixToWorld(this.x);
var y = Micro.pixToWorld(this.y);
if (x >= 0 && x < this.map.width && y >= 0 && y < this.map.height) {
if (blockMaps.trafficDensityMap.worldGet(x, y) > 170 && (Random.getRandom16() & 7) === 0) {
messageManager.sendMessage(Messages.HEAVY_TRAFFIC, {x: x, y: y});
messageManager.sendMessage(Messages.SOUND_HEAVY_TRAFFIC);
this.soundCount = 200;
}
}
}
var frame = this.frame;
if ((spriteCycle & 3) === 0) {
var dir = Micro.getDir(this.x, this.y, this.destX, this.destY);
frame = Micro.turnTo(frame, dir);
this.frame = frame;
}
this.x += this.xDelta[frame];
this.y += this.yDelta[frame];
};
Micro.CopterSprite.prototype.explodeSprite = function(messageManager) {
this.frame = 0;
this.spriteManager.makeExplosionAt(this.x, this.y);
messageManager.sendMessage(Messages.HELICOPTER_CRASHED);
};
/*
// Metadata for image loading
Object.defineProperties(CopterSprite,
{ID: Micro.makeConstantDescriptor(2),
width: Micro.makeConstantDescriptor(32),
frames: Micro.makeConstantDescriptor(8)});
return CopterSprite;
});
*/
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.ExplosionSprite = function (map, spriteManager, x, y) {
Micro.BaseSprite.call( this );
this.init(Micro.SPRITE_EXPLOSION, map, spriteManager, x, y);
this.width = 48;
this.height = 48;
this.xOffset = -24;
this.yOffset = -24;
this.frame = 1;
}
Micro.ExplosionSprite.prototype = Object.create( Micro.BaseSprite.prototype );
Micro.ExplosionSprite.prototype.startFire = function(x, y) {
x = Micro.pixToWorld(x);
y = Micro.pixToWorld(y);
if (!this.map.testBounds(x, y))
return;
var tile = this.map.getTile(x, y);
var tileValue = tile.getValue();
if (!tile.isCombustible() && tileValue !== Tile.DIRT)
return;
if (tile.isZone())
return;
this.map.setTo(x, y, Micro.randomFire());
};
Micro.ExplosionSprite.prototype.move = function(spriteCycle, messageManager, disasterManager, blockMaps) {
if ((spriteCycle & 1) === 0) {
if (this.frame === 1) {
// Convert sprite coordinates to tile coordinates.
var explosionX = Micro.pixToWorld(this.x);
var explosionY = Micro.pixToWorld(this.y);
messageManager.sendMessage(Messages.SOUND_EXPLOSIONHIGH);
messageManager.sendMessage(Messages.EXPLOSION_REPORTED, {x: explosionX, y: explosionY});
}
this.frame++;
}
if (this.frame > 6) {
this.frame = 0;
this.startFire(this.x, this.y);
this.startFire(this.x - 16, this.y - 16);
this.startFire(this.x + 16, this.y + 16);
this.startFire(this.x - 16, this.y + 16);
this.startFire(this.x + 16, this.y + 16);
}
};
/*
// Metadata for image loading
Object.defineProperties(ExplosionSprite,
{ID: Micro.makeConstantDescriptor(7),
width: Micro.makeConstantDescriptor(48),
frames: Micro.makeConstantDescriptor(6)});
return ExplosionSprite;
});
*/
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.MonsterSprite = function (map, spriteManager, x, y) {
Micro.BaseSprite.call( this );
this.init(Micro.SPRITE_MONSTER, map, spriteManager, x, y);
this.width = 48;
this.height = 48;
this.xOffset = -24;
this.yOffset = -24;
if (x > Micro.worldToPix(map.width) / 2) {
if (y > Micro.worldToPix(map.height) / 2) this.frame = 10;
else this.frame = 7;
} else if (y > Micro.worldToPix(map.height) / 2) { this.frame = 1;
} else { this.frame = 4;
}
this.flag = 0;
this.count = 1000;
this.destX = Micro.worldToPix(map.pollutionMaxX);
this.destY = Micro.worldToPix(map.pollutionMaxY);
this.origX = this.x;
this.origY = this.y;
this._seenLand = false;
this.xDelta = [ 2, 2, -2, -2, 0];
this.yDelta = [-2, 2, 2, -2, 0];
this.cardinals1 = [ 0, 1, 2, 3];
this.cardinals2 = [ 1, 2, 3, 0];
this.diagonals1 = [ 2, 5, 8, 11];
this.diagonals2 = [11, 2, 5, 8];
}
Micro.MonsterSprite.prototype = Object.create( Micro.BaseSprite.prototype );
Micro.MonsterSprite.prototype.move = function(spriteCycle, messageManager, disasterManager, blockMaps) {
if (this.soundCount > 0)
this.soundCount--;
// Frames 1 - 12 are diagonal sprites, 3 for each direction.
// 1-3 NE, 2-6 SE, etc. 13-16 represent the cardinal directions.
var currentDir = Math.floor((this.frame - 1) / 3);
var frame, dir;
if (currentDir < 4) { /* turn n s e w */
// Calculate how far in the 3 step animation we were,
// move on to the next one
frame = (this.frame - 1) % 3;
if (frame === 2)
this.step = 0;
if (frame === 0)
this.step = 1;
if (this.step)
frame++;
else
frame--;
var absDist = Micro.absoluteDistance(this.x, this.y, this.destX, this.destY);
if (absDist < 60) {
if (this.flag === 0) {
this.flag = 1;
this.destX = this.origX;
this.destY = this.origY;
} else {
this.frame = 0;
return;
}
}
// Perhaps switch to a cardinal direction
dir = Micro.getDir(this.x, this.y, this.destX, this.destY);
dir = Math.floor((dir - 1) / 2);
if (dir !== currentDir && Random.getChance(10)) {
if (Random.getRandom16() & 1)
frame = this.cardinals1[currentDir];
else
frame = this.cardinals2[currentDir];
currentDir = 4;
if (!this.soundCount) {
messageManager.sendMessage(Messages.SOUND_MONSTER);
this.soundCount = 50 + Random.getRandom(100);
}
}
} else {
// Travelling in a cardinal direction. Switch to a diagonal
currentDir = 4;
dir = this.frame;
frame = (dir - 13) & 3;
if (!(Random.getRandom16() & 3)) {
if (Random.getRandom16() & 1)
frame = this.diagonals1[frame];
else
frame = this.diagonals2[frame];
// We mung currentDir and frame here to
// make the assignment below work
currentDir = Math.floor((frame - 1) / 3);
frame = (frame - 1) % 3;
}
}
frame = currentDir * 3 + frame + 1;
if (frame > 16)
frame = 16;
this.frame = frame;
this.x += this.xDelta[currentDir];
this.y += this.yDelta[currentDir];
if (this.count > 0)
this.count--;
var tileValue = Micro.getTileValue(this.map, this.x, this.y);
if (tileValue === -1 || (tileValue === Tile.RIVER && this.count < 500))
this.frame = 0;
if (tileValue === Tile.DIRT || tileValue > Tile.WATER_HIGH)
this._seenLand = true;
var spriteList = this.spriteManager.getSpriteList();
for (var i = 0; i < spriteList.length; i++) {
var s = spriteList[i];
if (s.frame !== 0 &&
(s.type === Micro.SPRITE_AIRPLANE || s.type === Micro.SPRITE_HELICOPTER ||
s.type === Micro.SPRITE_SHIP || s.type === Micro.SPRITE_TRAIN) &&
Micro.checkSpriteCollision(this, s))
s.explodeSprite(messageManager);
}
Micro.destroyMapTile(this.spriteManager, this.map, blockMaps, this.x, this.y);
};
/*
// Metadata for image loading
Object.defineProperties(MonsterSprite,
{ID: Micro.makeConstantDescriptor(5),
width: Micro.makeConstantDescriptor(48),
frames: Micro.makeConstantDescriptor(16)});
return MonsterSprite;
});
*/
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.TornadoSprite = function (map, spriteManager, x, y) {
Micro.BaseSprite.call( this );
this.init(Micro.SPRITE_TORNADO, map, spriteManager, x, y);
this.width = 48;
this.height = 48;
this.xOffset = -24;
this.yOffset = -40;
this.frame = 1;
this.count = 200;
this.xDelta = [2, 3, 2, 0, -2, -3];
this.yDelta = [-2, 0, 2, 3, 2, 0];
}
Micro.TornadoSprite.prototype = Object.create( Micro.BaseSprite.prototype );
Micro.TornadoSprite.prototype.move = function(spriteCycle, messageManager, disasterManager, blockMaps) {
var frame;
frame = this.frame;
// If middle frame, move right or left
// depending on the flag value
// If frame = 1, perhaps die based on flag
// value
if (frame === 2) {
if (this.flag)
frame = 3;
else
frame = 1;
} else {
if (frame === 1)
this.flag = 1;
else
this.flag = 0;
frame = 2;
}
if (this.count > 0)
this.count--;
this.frame = frame;
var spriteList = this.spriteManager.getSpriteList();
for (var i = 0; i < spriteList.length; i++) {
var s = spriteList[i];
// Explode vulnerable sprites
if (s.frame !== 0 &&
(s.type === Micro.SPRITE_AIRPLANE || s.type === Micro.SPRITE_HELICOPTER ||
s.type === Micro.SPRITE_SHIP || s.type === Micro.SPRITE_TRAIN) &&
Micro.checkSpriteCollision(this, s)) {
s.explodeSprite(messageManager);
}
}
frame = Random.getRandom(5);
this.x += this.xDelta[frame];
this.y += this.yDelta[frame];
if (this.spriteNotInBounds())
this.frame = 0;
if (this.count !== 0 && Random.getRandom(500) === 0)
this.frame = 0;
Micro.destroyMapTile(this.spriteManager, this.map, blockMaps, this.x, this.y);
};
/*
// Metadata for image loading
Object.defineProperties(TornadoSprite,
{ID: Micro.makeConstantDescriptor(6),
width: Micro.makeConstantDescriptor(48),
frames: Micro.makeConstantDescriptor(3)});
return TornadoSprite;
});
*/
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.SpriteManager = function (map, SIM) {
this.sim = SIM;
this.spriteList = [];
this.map = map;
this.spriteCycle = 0;
}
Micro.SpriteManager.prototype = {
constructor: Micro.SpriteManager,
getSprite : function(type) {
var filteredList = this.spriteList.filter(function (s) {
return s.frame !== 0 && s.type === type;
});
if (filteredList.length === 0) return null;
return filteredList[0];
},
getSpriteList : function() {
return this.spriteList.slice();
},
getSpritesInView : function(startX, startY, lastX, lastY) {
var sprites = [];
startX = Micro.worldToPix(startX);
startY = Micro.worldToPix(startY);
lastX = Micro.worldToPix(lastX);
lastY = Micro.worldToPix(lastY);
return this.spriteList.filter(function(s) {
return (s.x + s.xOffset >= startX && s.y + s.yOffset >= startY) &&
!(s.x + s.xOffset >= lastX && s.y + s.yOffset >= lastY);
});
},
moveObjects : function() {
var messageManager = this.sim.messageManager;
var disasterManager = this.sim.disasterManager;
var blockMaps = this.sim.blockMaps;
this.spriteCycle += 1;
var list = this.spriteList.slice();
var i = list.length;
while(i--){
//for (var i = 0, l = list.length; i < l; i++) {
var sprite = list[i];
if (sprite.frame === 0) continue;
sprite.move(this.spriteCycle, messageManager, disasterManager, blockMaps);
}
this.pruneDeadSprites();
},
/*moveObjects : function(simData) {
var messageManager = simData.messageManager;
var disasterManager = simData.disasterManager;
var blockMaps = simData.blockMaps;
this.spriteCycle += 1;
var list = this.spriteList.slice();
for (var i = 0, l = list.length; i < l; i++) {
var sprite = list[i];
if (sprite.frame === 0)
continue;
sprite.move(this.spriteCycle, messageManager, disasterManager, blockMaps);
}
this.pruneDeadSprites();
},*/
makeSprite : function(type, x, y) {
this.spriteList.push(new constructors[type](this.map, this, x, y));
},
makeTornado : function(messageManager) {
var sprite = this.getSprite(Micro.SPRITE_TORNADO);
if (sprite !== null) {
sprite.count = 200;
return;
}
var x = Random.getRandom(Micro.worldToPix(this.map.width) - 800) + 400;
var y = Random.getRandom(Micro.worldToPix(this.map.height) - 200) + 100;
this.makeSprite(Micro.SPRITE_TORNADO, x, y);
messageManager.sendMessage(Messages.TORNADO_SIGHTED, {x: Micro.pixToWorld(x), y: Micro.pixToWorld(y)});
},
makeExplosion : function(x, y) {
if (this.map.testBounds(x, y)) this.makeExplosionAt(Micro.worldToPix(x), Micro.worldToPix(y));
},
makeExplosionAt : function(x, y) {
this.makeSprite(Micro.SPRITE_EXPLOSION, x, y);
},
generatePlane : function(x, y) {
if (this.getSprite(Micro.SPRITE_AIRPLANE) !== null) return;
this.makeSprite(Micro.SPRITE_AIRPLANE, Micro.worldToPix(x), Micro.worldToPix(y));
},
generateTrain : function(census, x, y) {
if (census.totalPop > 20 && this.getSprite(Micro.SPRITE_TRAIN) === null && Random.getRandom(25) === 0) this.makeSprite(Micro.SPRITE_TRAIN,Micro.worldToPix(x) + 8, Micro.worldToPix(y) + 8);
},
generateShip : function() {
// XXX This code is borked. The map generator will never
// place a channel tile on the edges of the map
var x,y;
if (Random.getChance(3)) {
for (x = 4; x < this.map.width - 2; x++) {
if (this.map.getTileValue(x, 0) === Tile.CHANNEL) {
this.makeShipHere(x, 0);
return;
}
}
}
if (Random.getChance(3)) {
for (y = 1; y < this.map.height - 2; y++) {
if (this.map.getTileValue(0, y) === Tile.CHANNEL) {
this.makeShipHere(0, y);
return;
}
}
}
if (Random.getChance(3)) {
for (x = 4; x < this.map.width - 2; x++) {
if (this.map.getTileValue(x, this.map.height - 1) === Tile.CHANNEL) {
this.makeShipHere(x, this.map.height - 1);
return;
}
}
}
if (Random.getChance(3)) {
for (y = 1; y < this.map.height - 2; y++) {
if (this.map.getTileValue(this.map.width - 1, y) === Tile.CHANNEL) {
this.makeShipHere(this.map.width - 1, y);
return;
}
}
}
},
getBoatDistance : function(x, y) {
var dist = 99999;
var pixelX = Micro.worldToPix(x) + 8;
var pixelY = Micro.worldToPix(y) + 8;
var sprite;
for (var i = 0, l = this.spriteList.length; i < l; i++) {
sprite = this.spriteList[i];
if (sprite.type === Micro.SPRITE_SHIP && sprite.frame !== 0) {
//var sprDist = Micro.absoluteValue(sprite.x - pixelX) + Micro.absoluteValue(sprite.y - pixelY);
var sprDist = Math.abs(sprite.x - pixelX) + Math.abs(sprite.y - pixelY);
dist = Math.min(dist, sprDist);
}
}
return dist;
},
makeShipHere : function(x, y) {
this.makeSprite(Micro.SPRITE_SHIP,Micro.worldToPix(x),Micro.worldToPix(y));
},
generateCopter : function(x, y) {
if (this.getSprite(Micro.SPRITE_HELICOPTER) !== null) return;
this.makeSprite(Micro.SPRITE_HELICOPTER,Micro.worldToPix(x),Micro.worldToPix(y));
},
makeMonsterAt : function(messageManager, x, y) {
this.makeSprite(Micro.SPRITE_MONSTER,
Micro.worldToPix(x),
Micro.worldToPix(y));
messageManager.sendMessage(Messages.MONSTER_SIGHTED, {x: x, y: y});
},
makeMonster : function(messageManager) {
var sprite = this.getSprite(Micro.SPRITE_MONSTER);
if (sprite !== null) {
sprite.soundCount = 1;
sprite.count = 1000;
sprite.destX = Micro.worldToPix(this.map.pollutionMaxX);
sprite.destY = Micro.worldToPix(this.map.pollutionMaxY);
}
var done = 0;
for (var i = 0; i < 300; i++) {
var x = Random.getRandom(this.map.width - 20) + 10;
var y = Random.getRandom(this.map.height - 10) + 5;
var tile = this.map.getTile(x, y);
if (tile.getValue() === Tile.RIVER) {
this.makeMonsterAt(messageManager, x, y);
done = 1;
break;
}
}
if (done === 0) this.makeMonsterAt(messageManager, 60, 50);
},
pruneDeadSprites : function(type) {
this.spriteList = this.spriteList.filter(function (s) { return s.frame !== 0; });
}
}
var constructors = {};
constructors[Micro.SPRITE_TRAIN] = Micro.TrainSprite;
constructors[Micro.SPRITE_SHIP] = Micro.BoatSprite;
constructors[Micro.SPRITE_MONSTER] = Micro.MonsterSprite;
constructors[Micro.SPRITE_HELICOPTER] = Micro.CopterSprite;
constructors[Micro.SPRITE_AIRPLANE] = Micro.AirplaneSprite;
constructors[Micro.SPRITE_TORNADO] = Micro.TornadoSprite;
constructors[Micro.SPRITE_EXPLOSION] = Micro.ExplosionSprite;
// return SpriteManager;
//});
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.MapScanner = function (map, Sim) {
this._map = map;
this.mapHeight = this._map.height;
this.mapWidth = this._map.width;
this._actions = [];
this.sim = Sim;
}
Micro.MapScanner.prototype = {
constructor: Micro.MapScanner,
addAction : function(criterion, action) {
this._actions.push({criterion: criterion, action: action});
},
mapScan : function(startX, maxX, simData) {
var y, x, i, id, tile, tileValue;
y = this.mapHeight;
while(y--){
// x = maxX;
//while(x==startX){
//for (var y = 0; y < this._map.height; y++) {
for (x = startX; x < maxX; x++) {
id = x + y * this.mapWidth; //this._map._calculateIndex(x, y);
// if (!(id in this._map.data)) this._map.data[id] = new Micro.Tile(this._map.defaultValue);
tile = this._map.data[id] || new Micro.Tile();
tileValue = tile.getValue();
//tile = this._map.getTile(x, y);
//tileValue = tile.getValue();
//var tile = this._map.getTile(x, y);
//var tileValue = tile.getValue();
if (tileValue < Tile.FLOOD) continue;
if (tile.isConductive()) this.sim.powerManager.setTilePower(x, y);
if (tile.isZone()) {
this.sim.repairManager.checkTile(x, y, this.sim._cityTime);
//var powered = tile.isPowered();
if (tile.isPowered()){ this.sim.census.poweredZoneCount += 1; this._map.powerData[id] = 1; }
else {this.sim.census.unpoweredZoneCount += 1; this._map.powerData[id] = 2;// this.sim.needPower.push(id);
}
}
i = this._actions.length;
while(i--){
//for (var i = 0, l = this._actions.length; i < l; i++) {
var current = this._actions[i];
var callable = Micro.isCallable(current.criterion);
if (callable && current.criterion.call(null, tile)) {
current.action.call(null, this._map, x, y, null);
break;
} else if (!callable && current.criterion === tileValue) {
current.action.call(null, this._map, x, y, null);
break;
}
}
}
}
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.PowerManager = function (map, SIM) {
this.sim = SIM;
this._map = map;
this._powerStack = [];
this.powerGridMap = new Micro.BlockMap(this._map.width, this._map.height, 1, 0);
}
Micro.PowerManager.prototype = {
constructor: Micro.PowerManager,
setTilePower : function(x, y) {
var tile = this._map.getTile(x, y);
var tileValue = tile.getValue();
if (tileValue === Tile.NUCLEAR || tileValue === Tile.POWERPLANT || this.powerGridMap.worldGet(x, y) > 0) {
tile.addFlags(Tile.POWERBIT);
return;
}
tile.removeFlags(Tile.POWERBIT);
},
clearPowerStack : function() {
this._powerStackPointer = 0;
this._powerStack = [];
},
testForConductive : function(pos, testDir) {
var movedPos = new this._map.Position(pos);
if (movedPos.move(testDir)) {
if (this._map.getTile(movedPos.x, movedPos.y).isConductive()) {
if (this.powerGridMap.worldGet(movedPos.x, movedPos.y) === 0)
return true;
}
}
return false;
},
// Note: the algorithm is buggy: if you have two adjacent power
// plants, the second will be regarded as drawing power from the first
// rather than as a power source itself
doPowerScan : function(census, messageManager) {
// Clear power this._map.
this.powerGridMap.clear();
// Power that the combined coal and nuclear power plants can deliver.
var maxPower = census.coalPowerPop * Micro.COAL_POWER_STRENGTH + census.nuclearPowerPop * Micro.NUCLEAR_POWER_STRENGTH;
var powerConsumption = 0; // Amount of power used.
while (this._powerStack.length > 0) {
var pos = this._powerStack.pop();
var anyDir = Direction.INVALID;
var conNum;
do {
powerConsumption++;
if (powerConsumption > maxPower) {
messageManager.sendMessage(Messages.NOT_ENOUGH_POWER);
return;
}
if (anyDir !== Direction.INVALID)
pos.move(anyDir);
this.powerGridMap.worldSet(pos.x, pos.y, 1);
conNum = 0;
var dir = Direction.BEGIN;
while (dir < Direction.END && conNum < 2) {
if (this.testForConductive(pos, dir)) {
conNum++;
anyDir = dir;
}
dir = Direction.increment90(dir);
}
if (conNum > 1) this._powerStack.push(new this._map.Position(pos));
} while (conNum);
}
},
coalPowerFound : function(map, x, y, simData) {
this.sim.census.coalPowerPop += 1;
this._powerStack.push(new map.Position(x, y));
// Ensure animation runs
var dX = [-1, 2, 1, 2];
var dY = [-1, -1, 0, 0];
// Ensure animation bits set no animation for 3d
if(!this.sim.is3D)
for (var i = 0; i < 4; i++) map.addTileFlags(x + dX[i], y + dY[i], Tile.ANIMBIT);
},
nuclearPowerFound : function(map, x, y, simData) {
var meltdownTable = [30000, 20000, 10000];
// TODO With the auto repair system, zone gets repaired before meltdown
// In original Micropolis code, we bail and don't repair if melting down
if (this.sim.disasterManager.disastersEnabled && Random.getRandom(meltdownTable[this.sim.gameLevel]) === 0) {
// simData.disasterManager.doMeltdown(messageManager, x, y);
return;
}
this.sim.census.nuclearPowerPop += 1;
this._powerStack.push(new map.Position(x, y));
//console.log(x, y, new map.Position(x, y))
// Ensure animation bits set no animation for 3d
if(!this.sim.is3D)
for (var i = 0; i < 4; i++) map.addTileFlags(x, y, Tile.ANIMBIT | Tile.CONDBIT | Tile.POWERBIT | Tile.BURNBIT);
},
registerHandlers : function(mapScanner, repairManager) {
mapScanner.addAction(Tile.POWERPLANT, this.coalPowerFound.bind(this));
mapScanner.addAction(Tile.NUCLEAR, this.nuclearPowerFound.bind(this));
repairManager.addAction(Tile.POWERPLANT, 7, 4);
repairManager.addAction(Tile.NUCLEAR, 7, 4);
}
}
// var dX = [1, 2, 1, 2];
// var dY = [-1, -1, 0, 0];
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.RepairManager = function (map) {
this._map = map;
this._actions = [];
}
Micro.RepairManager.prototype = {
constructor: Micro.RepairManager,
addAction : function(criterion, period, zoneSize) {
this._actions.push({criterion: criterion, period: period, zoneSize: zoneSize});
},
repairZone : function(x, y, zoneSize) {
var centre = this._map.getTileValue(x, y);
var tileValue = centre - zoneSize - 2;
for (var yy = -1; yy < zoneSize - 1; yy++) {
for (var xx = -1; xx < zoneSize - 1; xx++) {
tileValue++;
var current = this._map.getTile(x + xx, y + yy);
if (current.isZone() || current.isAnimated())
continue;
var currentValue = current.getValue();
if (currentValue < Tile.RUBBLE || currentValue >= Tile.ROADBASE)
this._map.setTo(x + xx, y + yy, new Micro.Tile(tileValue, Tile.CONDBIT | Tile.BURNBIT));
}
}
},
checkTile : function(x, y, cityTime) {
for (var i = 0, l = this._actions.length; i < l; i++) {
var current = this._actions[i];
var period = current.period;
if ((cityTime & period) !== 0) continue;
var tile = this._map.getTile(x, y);
var tileValue = tile.getValue();
var callable = Micro.isCallable(current.criterion);
if (callable && current.criterion.call(null, tile)) this.repairZone(x, y, current.zoneSize);
else if (!callable && current.criterion === tileValue) this.repairZone(x, y, current.zoneSize);
}
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.vulnerable = function(tile) {
var tileValue = tile.getValue();
if (tileValue < Tile.RESBASE || tileValue > Tile.LASTZONE || tile.isZone()) return false;
return true;
};
Micro.DisasterManager = function (map, spriteManager, gameLevel) {
this._map = map;
this._spriteManager = spriteManager;
this._gameLevel = gameLevel;
this._floodCount = 0;
this.DisChance = [4800, 2400, 60];
this.Dx = [ 0, 1, 0, -1];
this.Dy = [-1, 0, 1, 0];
// TODO enable disasters
Object.defineProperty(this, 'disastersEnabled', Micro.makeConstantDescriptor(false));
}
Micro.DisasterManager.prototype = {
constructor: Micro.DisasterManager,
doDisasters : function(census, messageManager) {
if (this._floodCount) this._floodCount--;
// TODO Scenarios
if (!this.disastersEnabled) return;
if (Random.getRandom(this.DisChance[this._gameLevel])) {
switch (Random.getRandom(8)) {
case 0:
case 1: this.setFire(messageManager); break;
case 2:
case 3: this.makeFlood(messageManager); break;
case 4:
break;
case 5: this._spriteManager.makeTornado(messageManager); break;
case 6:
// TODO Earthquakes
//this.makeEarthquake();
break;
case 7:
case 8: if (census.pollutionAverage > 60) this._spriteManager.makeMonster(messageManager); break;
}
}
},
setDifficulty : function(gameLevel) {
this._gameLevel = gameLevel;
},
scenarioDisaster : function() {
// TODO Scenarios
},
// User initiated meltdown: need to find the plant first
makeMeltdown : function(messageManager) {
for (var x = 0; x < (this._map.width - 1); x++) {
for (var y = 0; y < (this._map.height - 1); y++) {
if (this._map.getTileValue(x, y) === Tile.NUCLEAR) {
this.doMeltdown(messageManager, x, y);
return;
}
}
}
},
makeEarthquake : function(messageManager) {
var strength = Random.getRandom(700) + 300;
this.doEarthquake(strength);
messageManager.sendMessage(Messages.EARTHQUAKE, {x: this._map.cityCenterX, y: this._map.cityCenterY});
for (var i = 0; i < strength; i++) {
var x = Random.getRandom(this._map.width - 1);
var y = Random.getRandom(this._map.height - 1);
if (Micro.vulnerable(this._map.getTile(x, y))) {
if ((i & 0x3) !== 0) this._map.setTo(x, y, Micro.randomRubble());
else this._map.setTo(x, y, Micro.randomFire());
}
}
},
setFire : function(messageManager, times, zonesOnly) {
times = times || 1;
zonesOnly = zonesOnly || false;
for (var i = 0; i < times; i++) {
var x = Random.getRandom(this._map.width - 1);
var y = Random.getRandom(this._map.height - 1);
var tile = this._map.getTile(x, y);
if (!tile.isZone()) {
tile = tile.getValue();
var lowerLimit = zonesOnly ? Tile.LHTHR : Tile.TREEBASE;
if (tile > lowerLimit && tile < Tile.LASTZONE) {
this._map.setTo(x, y, Micro.randomFire());
messageManager.sendMessage(Messages.FIRE_REPORTED, {x: x, y: y});
return;
}
}
}
},
makeCrash : function(messageManager) {
var s = this._spriteManager.getSprite(Micro.SPRITE_AIRPLANE);
//var s = this._spriteManager.getSprite(Micro.SPRITE_PLANE);
if (s !== null) { s.explodeSprite(messageManager); return; }
var x = Random.getRandom(this._map.width - 1);
var y = Random.getRandom(this._map.height - 1);
this._spriteManager.generatePlane(x, y);
s = this._spriteManager.getSprite(Micro.SPRITE_AIRPLANE);
s.explodeSprite(messageManager);
},
makeFire : function(messageManager) {
this.setFire(messageManager, 40, false);
},
makeFlood : function(messageManager) {
for (var i = 0; i < 300; i++) {
var x = Random.getRandom(this._map.width - 1);
var y = Random.getRandom(this._map.height - 1);
var tileValue = this._map.getTileValue(x, y);
if (tileValue > Tile.CHANNEL && tileValue <= Tile.WATER_HIGH) {
for (var j = 0; j < 4; j++) {
var xx = x + this.Dx[j];
var yy = y + this.Dy[j];
if (!this._map.testBounds(xx, yy)) continue;
var tile = this._map.getTile(xx, yy);
tileValue = tile.getValue();
if (tile === Tile.DIRT || (tile.isBulldozable() && tile.isCombustible)) {
this._map.setTo(xx, yy, new Tile(Tile.FLOOD));
this._floodCount = 30;
messageManager.sendMessage(Messages.FLOODING_REPORTED, {x: xx, y: yy});
return;
}
}
}
}
},
doFlood : function(x, y, blockMaps) {
if (this._floodCount > 0) {
// Flood is not over yet
for (var i = 0; i < 4; i++) {
if (Random.getChance(7)) {
var xx = x + this.Dx[i];
var yy = y + this.Dy[i];
if (this._map.testBounds(xx, yy)) {
var tile = this._map.getTile(xx, yy);
var tileValue = tile.getValue();
if (tile.isCombustible() || tileValue === Tile.DIRT || (tileValue >= Tile.WOODS5 && tileValue < Tile.FLOOD)) {
if (tile.isZone()) Micro.fireZone(this.map, xx, yy, blockMaps);
this._map.setTo(xx, yy, new Tile(Tile.FLOOD + Random.getRandom(2)));
}
}
}
}
} else {
if (Random.getChance(15)) this._map.setTo(x, y, new Tile(Tile.DIRT));
}
},
doMeltdown : function(messageManager, x, y) {
this._spriteManager.makeExplosion(x - 1, y - 1);
this._spriteManager.makeExplosion(x - 1, y + 2);
this._spriteManager.makeExplosion(x + 2, y - 1);
this._spriteManager.makeExplosion(x + 2, y + 2);
var dY, dX;
// Whole power plant is at fire
for (dX = x - 1; dX < x + 3; dX++) {
for (dY = y - 1; dY < y + 3; dY++) {
this._map.setTo(dX, dY, Micro.randomFire());
}
}
// Add lots of radiation tiles around the plant
for (var i = 0; i < 200; i++) {
dX = x - 20 + Random.getRandom(40);
dY = y - 15 + Random.getRandom(30);
if (!this._map.testBounds(dX, dY)) continue;
var tile = this._map.getTile(dX, dY);
if (tile.isZone()) continue;
if (tile.isCombustible() || tile.getValue() === Tile.DIRT) this._map.setTo(dX, dY, new Tile(Tile.RADTILE));
}
// Report disaster to the user
messageManager.sendMessage(Messages.NUCLEAR_MELTDOWN, {x: x, y: y});
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.InputStatus = function(map){
this.gameTools = new Micro.GameTools(map);
this.canvas = document.getElementById(Micro.DEFAULT_ID);
// Tool clicks
this.clickX = -1;
this.clickY = -1;
// Keyboard Movement
this.up = false;
this.down = false;
this.left = false;
this.right = false;
// Mouse movement
this.mouseX = -1;
this.mouseY = -1;
// Tool buttons
this.toolName = null;
this.currentTool = null;
this.toolWidth = 0;
this.toolColour = '';
// Other buttons
this.budgetRequested = false;
this.evalRequested = false;
this.disasterRequested = false;
// Speed
this.speedChangeRequested = false;
this.requestedSpeed = null;
this.bindKeys();
var _this = this;
this.canvas.addEventListener( 'mouseenter', function(e) { _this.mouseEnterHandler(e); }, false );
this.canvas.addEventListener( 'mouseleave', function(e) { _this.mouseLeaveHandler(e); }, false );
var bb = document.getElementsByClassName('toolButton');
for(var i=0; i<bb.length; i++){
bb[i].addEventListener( 'click', function(e) { _this.toolButtonHandler(e); }, false );
bb[i].addEventListener( 'mouseover', function(e) { _this.toolButtonOver(e); }, false );
}
document.getElementById('evalRequest').addEventListener( 'click', function(e) { _this.evalHandler(e); } , false );
document.getElementById('budgetRequest').addEventListener( 'click', function(e) { _this.budgetHandler(e); } , false );
document.getElementById('disasterRequest').addEventListener( 'click', function(e) { _this.disasterHandler(e); } , false );
document.getElementById('pauseRequest').addEventListener( 'click', function(e) { _this.speedChangeHandler(e); } , false );
}
Micro.InputStatus.prototype = {
constructor: Micro.InputStatus,
bindKeys : function() {
var _this = this;
document.onkeydown = function(e) {
e = e || window.event;
var handled = false;
if (e.keyCode == 38) { _this.up = true; handled = true; }
else if (e.keyCode == 40) { _this.down = true; handled = true; }
else if (e.keyCode == 39) { _this.right = true; handled = true; }
else if (e.keyCode == 37) { _this.left = true; handled = true; }
if (handled) e.preventDefault();
};
document.onkeyup = function(e) {
e = e || window.event;
if (e.keyCode == 38) _this.up = false;
if (e.keyCode == 40) _this.down = false;
if (e.keyCode == 39) _this.right = false;
if (e.keyCode == 37) _this.left = false;
};
// self.focus()
},
clickHandled : function() {
this.clickX = -1;
this.clickY = -1;
this.currentTool.clear();
},
getRelativeCoordinates : function(e) {
var rect = this.canvas.getBoundingClientRect();
var dx = window.innerWidth-200;
var x;
var y;
if (e.x !== undefined && e.y !== undefined) {
x = e.x- rect.left;
y = e.y- rect.top;
} else {
x = e.clientX - rect.left;//+ 0;
y = e.clientY - rect.top;
}
return {x: x, y: y};
},
speedChangeHandled : function() {
this.speedChangeRequested = false;
this.requestedSpeed = null;
},
speedChangeHandler : function(e) {
this.speedChangeRequested = true;
var requestedSpeed = document.getElementById('pauseRequest').innerHTML;
var newRequest = requestedSpeed === 'Pause' ? 'Play' : 'Pause';
document.getElementById('pauseRequest').innerHTML=newRequest;
},
mouseEnterHandler : function(e) {
var _this = this;
this.canvas.addEventListener( 'mousemove', function(e) { _this.mouseMoveHandler(e); }, false );
this.canvas.addEventListener( 'click', function(e) { _this.canvasClickHandler(e); }, false );
},
mouseLeaveHandler : function(e) {
var _this = this;
this.canvas.removeEventListener( 'mousemove', function(e) { _this.mouseMoveHandler(e); }, false );
this.canvas.removeEventListener( 'click', function(e) { _this.canvasClickHandler(e); }, false );
this.mouseX = -1;
this.mouseY = -1;
},
mouseMoveHandler : function(e) {
var coords = this.getRelativeCoordinates(e);
this.mouseX = coords.x;
this.mouseY = coords.y;
},
canvasClickHandler : function(e) {
this.clickX = this.mouseX;
this.clickY = this.mouseY;
e.preventDefault();
},
toolButtonOver : function(e) {
var name = e.target.getAttribute("data-tool");
var price = e.target.getAttribute("data-price");
if(price == 0){ price = ""; name = "info"}
else price += "$";
document.getElementById('buttonsInfos').innerHTML = name +" "+ price;
},
toolButtonHandler : function(e) {
var bb = document.getElementsByClassName('selected');
for(var i=0; i<bb.length; i++){
bb[i].className = bb[i].className.replace("selected", "unselected");
}
e.target.className = e.target.className.replace("unselected", "selected");
this.toolName = e.target.getAttribute("data-tool");
this.toolWidth = e.target.getAttribute("data-size");
this.currentTool = this.gameTools[this.toolName];
this.toolColour = e.target.getAttribute("data-colour");
e.preventDefault();
},
disasterHandler : function(e) {
this.disasterRequested = true;
},
evalHandler : function(e) {
this.evalRequested = true;
},
budgetHandler : function(e) {
this.budgetRequested = true;
},
evalHandled : function(e) {
this.evalRequested = false;
},
disasterHandled : function(e) {
this.disasterRequested = false;
},
budgetHandled : function(e) {
this.budgetRequested = false;
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.Traffic = function(map, spriteManager) {
this._map = map;
this._stack = [];
this._spriteManager = spriteManager;
}
Micro.Traffic.prototype = {
constructor: Micro.Traffic,
makeTraffic : function(x, y, blockMaps, destFn) {
this._stack = [];
var pos = new this._map.Position(x, y);
if (this.findPerimeterRoad(pos)) {
if (this.tryDrive(pos, destFn)) {
this.addToTrafficDensityMap(blockMaps);
return Micro.ROUTE_FOUND;
}
return Micro.NO_ROUTE_FOUND;
} else {
return Micro.NO_ROAD_FOUND;
}
},
addToTrafficDensityMap : function(blockMaps) {
var trafficDensityMap = blockMaps.trafficDensityMap;
while (this._stack.length > 0) {
var pos = this._stack.pop();
// Could this happen?!?
if (!this._map.testBounds(pos.x, pos.y)) continue;
var tileValue = this._map.getTileValue(pos.x, pos.y);
if (tileValue >= Tile.ROADBASE && tileValue < Tile.POWERBASE) {
// Update traffic density.
var traffic = trafficDensityMap.worldGet(pos.x, pos.y);
traffic += 50;
traffic = Math.min(traffic, 240);
trafficDensityMap.worldSet(pos.x, pos.y, traffic);
// Attract traffic copter to the traffic
if (traffic >= 240 && Random.getRandom(5) === 0) {
var sprite = this._spriteManager.getSprite(Micro.SPRITE_HELICOPTER);
if (sprite !== null) {
sprite.destX = Micro.worldToPix(pos.x);
sprite.destY = Micro.worldToPix(pos.y);
}
}
}
}
},
findPerimeterRoad : function(pos) {
for (var i = 0; i < 12; i++) {
var xx = pos.x + Micro.perimX[i];
var yy = pos.y + Micro.perimY[i];
if (this._map.testBounds(xx, yy)) {
if (Micro.isDriveable(this._map.getTileValue(xx, yy))) {
pos.x = xx;
pos.y = yy;
return true;
}
}
}
return false;
},
tryDrive : function(startPos, destFn) {
var dirLast = Direction.INVALID;
var drivePos = new this._map.Position(startPos);
/* Maximum distance to try */
for (var dist = 0; dist < Micro.MAX_TRAFFIC_DISTANCE; dist++) {
var dir = this.tryGo(drivePos, dirLast);
if (dir != Direction.INVALID) {
drivePos.move(dir);
dirLast = Direction.rotate180(dir);
if (dist & 1) this._stack.push(new this._map.Position(drivePos));
if (this.driveDone(drivePos, destFn)) return true;
} else {
if (this._stack.length > 0) {
this._stack.pop();
dist += 3;
} else {
return false;
}
}
}
return false;
},
tryGo : function(pos, dirLast) {
var directions = [];
// Find connections from current position.
var dir = Direction.NORTH;
var count = 0;
for (var i = 0; i < 4; i++) {
if (dir != dirLast && Micro.isDriveable(this._map.getTileFromMapOrDefault(pos, dir, Tile.DIRT))) {
// found a road in an allowed direction
directions[i] = dir;
count++;
} else {
directions[i] = Direction.INVALID;
}
dir = Direction.rotate90(dir);
}
if (count === 0) return Direction.INVALID;
if (count === 1) {
for (i = 0; i < 4; i++) {
if (directions[i] != Direction.INVALID) return directions[i];
}
}
i = Random.getRandom16() & 3;
while (directions[i] === Direction.INVALID) i = (i + 1) & 3;
return directions[i];
},
driveDone : function(pos, destFn) {
if (pos.y > 0) { if (destFn(this._map.getTileValue(pos.x, pos.y - 1))) return true; }
if (pos.x < (this._map.width - 1)) { if (destFn(this._map.getTileValue(pos.x + 1, pos.y))) return true; }
if (pos.y < (this._map.height - 1)) { if (destFn(this._map.getTileValue(pos.x, pos.y + 1))) return true; }
if (pos.x > 0) { if (destFn(this._map.getTileValue(pos.x - 1, pos.y))) return true; }
return false;
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.toKey = function(x, y) {
return [x, y].join(',');
}
Micro.TileHistory = function(){
this.clear();
}
Micro.TileHistory.prototype = {
constructor: Micro.TileHistory,
clear : function() {
this.data = {};
},
getTile : function(x, y) {
var key = Micro.toKey(x, y);
return this.data[key];
},
setTile : function(x, y, value) {
var key = Micro.toKey(x, y);
this.data[key] = value;
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.AnimationManager = function(map, animationPeriod, blinkPeriod){
animationPeriod = animationPeriod || 5;
blinkPeriod = blinkPeriod || 30;
this._map = map;
this.animationPeriod = animationPeriod;
this.blinkPeriod = blinkPeriod;
this.shouldBlink = false;
this.count = 1;
// When painting we keep track of what frames
// have been painted at which map coordinates so we can
// consistently display the correct frame even as the
// canvas moves
this._lastPainted = null;
this._data = [];
this.initArray();
this.registerAnimations();
}
Micro.AnimationManager.prototype = {
constructor: Micro.AnimationManager,
initArray : function() {
// Map all tiles to their own value in case we ever
// look up a tile that is not animated
for (var i = 0; i < Tile.TILE_COUNT; i++) this._data[i] = i;
},
inSequence : function(tileValue, lastValue) {
// It is important that we use the base value as the starting point
// rather than the last painted value: base values often don't recur
// in their sequences
var seen = [tileValue];
var current = this._data[tileValue];
while (seen.indexOf(current) === -1) {
if (current === lastValue) return true;
seen.push(current);
current = this._data[current];
}
return false;
},
getTiles : function(startX, startY, boundX, boundY, isPaused) {
isPaused = isPaused || false;
var shouldChangeAnimation = false;
if (!isPaused)
this.count += 1;
if ((this.count % this.blinkPeriod) === 0)
this.shouldBlink = !this.shouldBlink;
if ((this.count % this.animationPeriod) === 0 && !isPaused)
shouldChangeAnimation = true;
var newPainted = new Micro.TileHistory();
var tilesToPaint = [];
for (var x = startX; x < boundX; x++) {
for (var y = startY; y < boundY; y++) {
if (x < 0 || x >= this._map.width || y < 0 || y >= this._map.height) continue;
var tile = this._map.getTile(x, y);
if (tile.isZone() && !tile.isPowered() && this.shouldBlink) {
tilesToPaint.push({x: x, y: y, tileValue: Tile.LIGHTNINGBOLT});
continue;
}
if (!tile.isAnimated()) continue;
var tileValue = tile.getValue();
var newTile = Tile.TILE_INVALID;
var last;
if (this._lastPainted)
last = this._lastPainted.getTile(x, y);
if (shouldChangeAnimation) {
// Have we painted any of this sequence before? If so, paint the next tile
if (last && this.inSequence(tileValue, last)) {
newTile = this._data[last];
} else {
// Either we haven't painted anything here before, or the last tile painted
// there belongs to a different tile's animation sequence
newTile = this._data[tileValue];
}
} else {
// Have we painted any of this sequence before? If so, paint the same tile
if (last && this.inSequence(tileValue, last)) newTile = last;
}
if (newTile === Tile.TILE_INVALID) continue;
tilesToPaint.push({x: x, y: y, tileValue: newTile});
newPainted.setTile(x, y, newTile);
}
}
this._lastPainted = newPainted;
return tilesToPaint;
},
registerSingleAnimation : function(arr) {
for (var i = 1; i < arr.length; i++) this._data[arr[i - 1]] = arr[i];
},
registerAnimations : function() {
this.registerSingleAnimation([56, 57, 58, 59, 60, 61, 62, 63, 56]);
this.registerSingleAnimation([80, 128, 112, 96, 80]);
this.registerSingleAnimation([81, 129, 113, 97, 81]);
this.registerSingleAnimation([82, 130, 114, 98, 82]);
this.registerSingleAnimation([83, 131, 115, 99, 83]);
this.registerSingleAnimation([84, 132, 116, 100, 84]);
this.registerSingleAnimation([85, 133, 117, 101, 85]);
this.registerSingleAnimation([86, 134, 118, 102, 86]);
this.registerSingleAnimation([87, 135, 119, 103, 87]);
this.registerSingleAnimation([88, 136, 120, 104, 88]);
this.registerSingleAnimation([89, 137, 121, 105, 89]);
this.registerSingleAnimation([90, 138, 122, 106, 90]);
this.registerSingleAnimation([91, 139, 123, 107, 91]);
this.registerSingleAnimation([92, 140, 124, 108, 92]);
this.registerSingleAnimation([93, 141, 125, 109, 93]);
this.registerSingleAnimation([94, 142, 126, 110, 94]);
this.registerSingleAnimation([95, 143, 127, 111, 95]);
this.registerSingleAnimation([144, 192, 176, 160, 144]);
this.registerSingleAnimation([145, 193, 177, 161, 145]);
this.registerSingleAnimation([146, 194, 178, 162, 146]);
this.registerSingleAnimation([147, 195, 179, 163, 147]);
this.registerSingleAnimation([148, 196, 180, 164, 148]);
this.registerSingleAnimation([149, 197, 181, 165, 149]);
this.registerSingleAnimation([150, 198, 182, 166, 150]);
this.registerSingleAnimation([151, 199, 183, 167, 151]);
this.registerSingleAnimation([152, 200, 184, 168, 152]);
this.registerSingleAnimation([153, 201, 185, 169, 153]);
this.registerSingleAnimation([154, 202, 186, 170, 154]);
this.registerSingleAnimation([155, 203, 187, 171, 155]);
this.registerSingleAnimation([156, 204, 188, 172, 156]);
this.registerSingleAnimation([157, 205, 189, 173, 157]);
this.registerSingleAnimation([158, 206, 190, 174, 158]);
this.registerSingleAnimation([159, 207, 191, 175, 159]);
this.registerSingleAnimation([621, 852, 853, 854, 855, 856, 857, 858, 859, 852]);
this.registerSingleAnimation([641, 884, 885, 886, 887, 884]);
this.registerSingleAnimation([644, 888, 889, 890, 891, 888]);
this.registerSingleAnimation([649, 892, 893, 894, 895, 892]);
this.registerSingleAnimation([650, 896, 897, 898, 899, 896]);
this.registerSingleAnimation([676, 900, 901, 902, 903, 900]);
this.registerSingleAnimation([677, 904, 905, 906, 907, 904]);
this.registerSingleAnimation([686, 908, 909, 910, 911, 908]);
this.registerSingleAnimation([689, 912, 913, 914, 915, 912]);
this.registerSingleAnimation([747, 916, 917, 918, 919, 916]);
this.registerSingleAnimation([748, 920, 921, 922, 923, 920]);
this.registerSingleAnimation([751, 924, 925, 926, 927, 924]);
this.registerSingleAnimation([752, 928, 929, 930, 931, 928]);
this.registerSingleAnimation([820, 952, 953, 954, 955, 952]);
this.registerSingleAnimation([832, 833, 834, 835, 836, 837, 838, 839, 832]);
this.registerSingleAnimation([840, 841, 842, 843, 840]);
this.registerSingleAnimation([844, 845, 846, 847, 848, 849, 850, 851, 844]);
this.registerSingleAnimation([932, 933, 934, 935, 936, 937, 938, 939, 932]);
this.registerSingleAnimation([940, 941, 942, 943, 944, 945, 946, 947, 940]);
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
Micro.copyFrom = function(sourceMap, sourceFn) {
var mapFn = function(elem) { return sourceFn(elem); };
//for (var y = 0, l = sourceMap.data.length; y < l; y++) this.data[y] = sourceMap.data[y].map(mapFn);
var i = sourceMap.data.length; while(i--) this.data[i] = sourceMap.data[i].map(mapFn);
}
Micro.makeArrayOf = function(length, value) {
//var result = [];
//var result = new M_ARRAY_TYPE(length);
var result = new Array(length);
//for (var a = 0; a < length; a++) result[a] = value;
var i = length; while(i--) result[i] = value;
return result;
}
Micro.BlockMap = function(gameMapWidth, gameMapHeight, blockSize, defaultValue) {
var sourceMap;
var sourceFunction;
var id = function(x) {return x;};
var e = new Error('Invalid parameters');
if (arguments.length < 3) {
if (!(gameMapWidth instanceof Micro.BlockMap) || (arguments.length === 2 && typeof(gameMapHeight) !== 'function')) throw e;
sourceMap = gameMapWidth;
sourceFunction = gameMapHeight === undefined ? id : gameMapHeight;
}
if (sourceMap !== undefined) {
gameMapWidth = sourceMap.gameMapWidth;
gameMapHeight = sourceMap.gameMapHeight;
blockSize = sourceMap.blockSize;
defaultValue = sourceMap.defaultValue;
}
Object.defineProperties(this,
{gameMapWidth: Micro.makeConstantDescriptor(gameMapWidth),
gameMapHeight: Micro.makeConstantDescriptor(gameMapHeight),
width: Micro.makeConstantDescriptor(Math.floor((gameMapWidth + 1) / blockSize)),
height: Micro.makeConstantDescriptor(Math.floor((gameMapHeight + 1)/ blockSize)),
blockSize: Micro.makeConstantDescriptor(blockSize),
defaultValue: Micro.makeConstantDescriptor(defaultValue)}
);
this.data = [];
if (sourceMap) Micro.copyFrom.call(this, sourceMap, sourceFunction);
else this.clear();
}
Micro.BlockMap.prototype = {
constructor: Micro.BlockMap,
clear : function() {
var maxY = Math.floor(this.gameMapHeight / this.blockSize) + 1;
var maxX = Math.floor(this.gameMapWidth / this.blockSize) + 1;
//for (var y = 0; y < maxY; y++) this.data[y] = Micro.makeArrayOf(maxX, this.defaultValue);
var y = maxY;
while(y--) this.data[y] = Micro.makeArrayOf(maxX, this.defaultValue);
},
copyFrom : function(sourceMap, sourceFn) {
if (sourceMap.width !== this.width || sourceMap.height !== this.height || sourceMap.blockSize !== this.blockSize)
console.warn('Copying from incompatible blockMap!');
for (var y = 0, height = sourceMap.height; y < height; y++) {
for (var x = 0, width = sourceMap.width; x < width; x++)
this.data[width * y + x] = sourceFn(sourceMap.data[width * y + x]);
}
},
get : function(x, y) {
return this.data[y][x];
},
set : function(x, y, value) {
this.data[y][x] = value;
},
toBlock : function(num) {
return Math.floor(num / this.blockSize);
},
worldGet : function(x, y) {
return this.get(this.toBlock(x), this.toBlock(y));
},
worldSet : function(x, y, value) {
this.set(this.toBlock(x), this.toBlock(y), value);
}
}
/* micropolisJS. Adapted by Graeme McCutcheon from Micropolis.
*
* This code is released under the GNU GPL v3, with some additional terms.
* Please see the files LICENSE and COPYING for details. Alternatively,
* consult http://micropolisjs.graememcc.co.uk/LICENSE and
* http://micropolisjs.graememcc.co.uk/COPYING
*
*/
var Residential, Commercial, Industrial, Transport, Road, EmergencyServices, MiscTiles, Stadia;
Micro.savePropsVar = ['cityTime'];
Micro.Simulation = function(gameMap, gameLevel, speed, is3D, savedGame) {
if (gameLevel !== Micro.LEVEL_EASY && gameLevel !== Micro.LEVEL_MED && gameLevel !== Micro.LEVEL_HARD) throw new Error('Invalid level!');
// if (speed !== Micro.SPEED_PAUSED && speed !== Micro.SPEED_SLOW && speed !== Micro.SPEED_MED && speed !== Micro.SPEED_FAST) throw new Error('Invalid speed!');
this.map = gameMap;
this.gameLevel = gameLevel;
this.div = this.map.width / 8;
this.is3D = is3D || false;
//this.needPower = [];
this.speed = speed;
this.speedCycle = 0;
this.phaseCycle = 0;
this.simCycle = 0;
this.doInitialEval = true;
this.cityTime = 50;
this.cityPopLast = 0;
this.messageLast = Messages.VILLAGE_REACHED;
this.startingYear = 1900;
// Last valves updated to the user
this.resValveLast = 0;
this.comValveLast = 0;
this.indValveLast = 0;
// Last date sent to front end
this._cityYearLast = -1;
this._cityMonthLast = -1;
// And now, the main cast of characters
this.evaluation = new Micro.Evaluation(this.gameLevel, this);
this.valves = new Micro.Valves();
this.budget = new Micro.Budget();
this.census = new Micro.Census();
this.messageManager = new Micro.MessageManager();
this.powerManager = new Micro.PowerManager(this.map, this);
this.spriteManager = new Micro.SpriteManager(this.map, this);
this.mapScanner = new Micro.MapScanner(this.map, this);
this.repairManager = new Micro.RepairManager(this.map);
this.traffic = new Micro.Traffic(this.map, this.spriteManager);
this.disasterManager = new Micro.DisasterManager(this.map, this.spriteManager, this.gameLevel);
this.blockMaps = {
comRateMap: new Micro.BlockMap(this.map.width, this.map.height, 8, 0),
crimeRateMap: new Micro.BlockMap(this.map.width, this.map.height, 2, 0),
fireStationMap: new Micro.BlockMap(this.map.width, this.map.height, 8, 0),
fireStationEffectMap: new Micro.BlockMap(this.map.width, this.map.height, 8, 0),
landValueMap: new Micro.BlockMap(this.map.width, this.map.height, 2, 0),
policeStationMap: new Micro.BlockMap(this.map.width, this.map.height, 8, 0),
policeStationEffectMap: new Micro.BlockMap(this.map.width, this.map.height, 8, 0),
pollutionDensityMap: new Micro.BlockMap(this.map.width, this.map.height, 2, 0),
populationDensityMap: new Micro.BlockMap(this.map.width, this.map.height, 2, 0),
rateOfGrowthMap: new Micro.BlockMap(this.map.width, this.map.height, 8, 0),
terrainDensityMap: new Micro.BlockMap(this.map.width, this.map.height, 4, 0),
trafficDensityMap: new Micro.BlockMap(this.map.width, this.map.height, 2, 0),
tempMap1: new Micro.BlockMap(this.map.width, this.map.height, 2, 0),
tempMap2: new Micro.BlockMap(this.map.width, this.map.height, 2, 0),
tempMap3: new Micro.BlockMap(this.map.width, this.map.height, 4, 0)
};
this.clearCensus();
if (savedGame) {
this.load(savedGame);
//this.cityPopLast = savedGame.totalPop;
} else {
this.budget.setFunds(20000);
this.census.totalPop = 1;
}
this.init();
}
Micro.Simulation.prototype = {
constructor: Micro.Simulation,
save : function(saveData) {
for (var i = 0, l = Micro.savePropsVar.length; i < l; i++)
saveData[Micro.savePropsVar[i]] = this[Micro.savePropsVar[i]];
this.map.save(saveData);
this.evaluation.save(saveData);
this.valves.save(saveData);
this.budget.save(saveData);
this.census.save(saveData);
},
load : function(saveData) {
//console.log(saveData)
this.messageManager.clear();
for (var i = 0, l = Micro.savePropsVar.length; i < l; i++)
this[Micro.savePropsVar[i]] = saveData[Micro.savePropsVar[i]];
//this.map.load(saveData);
this.evaluation.load(saveData);
this.valves.load(saveData, this.messageManager);
this.budget.load(saveData, this.messageManager);
this.census.load(saveData);
},
setSpeed : function(s) {
if (s!== Micro.SPEED_PAUSED && s!== Micro.SPEED_SLOW && s!== Micro.SPEED_MED && s!== Micro.SPEED_FAST) throw new Error('Invalid speed!');
this.speed = s;
},
setDifficulty: function(s) {
if (s !== Micro.LEVEL_EASY && s !== Micro.LEVEL_MED && s !== Micro.LEVEL_HARD) throw new Error('Invalid level!');
this.gameLevel = s;
this.disasterManager.setDifficulty(this.gameLevel);
},
isPaused : function() {
return this.speed === Micro.SPEED_PAUSED;
},
/*simTick : function() {
this.simFrame();
// Move sprite objects
//this.spriteManager.moveObjects(this._constructSimData());
this.updateFrontEnd();
// TODO Graphs
return this.messageManager.getMessages();
},*/
simFrame : function() {
if (this.budget.awaitingValues) return;
if (this.speed === 0) return;
if (this.speed === 1 && (this.speedCycle % 5) !== 0) return;
if (this.speed === 2 && (this.speedCycle % 3) !== 0) return;
this.messageManager.clear();
//var simData = this._constructSimData();
//this.simulate(simData);
this.simulate();
},
clearCensus : function() {
this.census.clearCensus();
this.powerManager.clearPowerStack();
this.blockMaps.fireStationMap.clear();
this.blockMaps.policeStationMap.clear();
},
init : function() {
// define
Residential = new Micro.Residential(this);
Commercial = new Micro.Commercial(this);
Industrial = new Micro.Industrial(this);
Road = new Micro.Road(this);
Transport = new Micro.Transport(this);
EmergencyServices = new Micro.EmergencyServices(this);
MiscTiles = new Micro.MiscTiles(this);
Stadia = new Micro.Stadia(this);
// Register actions
Commercial.registerHandlers(this.mapScanner, this.repairManager);
EmergencyServices.registerHandlers(this.mapScanner, this.repairManager);
Industrial.registerHandlers(this.mapScanner, this.repairManager);
MiscTiles.registerHandlers(this.mapScanner, this.repairManager);
this.powerManager.registerHandlers(this.mapScanner, this.repairManager);
Road.registerHandlers(this.mapScanner, this.repairManager);
Residential.registerHandlers(this.mapScanner, this.repairManager);
Stadia.registerHandlers(this.mapScanner, this.repairManager);
Transport.registerHandlers(this.mapScanner, this.repairManager);
//this.budget.setFunds(20000);
//var simData = this._constructSimData();
this.evaluation.evalInit();
this.valves.setValves(this.gameLevel, this.census, this.budget);
this.clearCensus();
//this.mapScanner.mapScan(0, this.map.width, simData);
this.mapScanner.mapScan(0, this.map.width, null);
this.powerManager.doPowerScan(this.census, this.messageManager);
Micro.pollutionTerrainLandValueScan(this.map, this.census, this.blockMaps);
Micro.crimeScan(this.census, this.blockMaps);
Micro.populationDensityScan(this.map, this.blockMaps);
Micro.fireAnalysis(this.blockMaps);
//this.census.totalPop = 1;
// if (savedGame) this.load(savedGame);
},
/*simulate : function() {
var speedIndex = this.speed - 1;
if (++this.simCycle > 1023) this.simCycle = 0;
if (this.doInitialEval) { this.doInitialEval = false; this.evaluation.cityEvaluation(); }
this.cityTime++;
if ((this.simCycle & 1) === 0) this.valves.setValves(this.gameLevel, this.census, this.budget);
this.clearCensus();
this.mapScanner.mapScan(0, this.map.width, null);
if (this.cityTime % Micro.CENSUS_FREQUENCY_10 === 0){this.census.take10Census(this.budget);};
if (this.cityTime % Micro.CENSUS_FREQUENCY_120 === 0) {this.census.take120Census(this.budget);}
if (this.cityTime % Micro.TAX_FREQUENCY === 0) { this.budget.collectTax(this.gameLevel, this.census, this.messageManager); this.evaluation.cityEvaluation(); };
if ((this.simCycle % 5) === 0){ Micro.decRateOfGrowthMap(this.blockMaps);}; Micro.decTrafficMap(this.blockMaps); this.sendMessages();
if ((this.simCycle % Micro.speedPowerScan[speedIndex]) === 0) this.powerManager.doPowerScan(this.census, this.messageManager);
if ((this.simCycle % Micro.speedPollutionTerrainLandValueScan[speedIndex]) === 0) Micro.pollutionTerrainLandValueScan(this.map, this.census, this.blockMaps);
if ((this.simCycle % Micro.speedCrimeScan[speedIndex]) === 0) Micro.crimeScan(this.census, this.blockMaps);
if ((this.simCycle % Micro.speedPopulationDensityScan[speedIndex]) === 0) Micro.populationDensityScan(this.map, this.blockMaps);
if ((this.simCycle % Micro.speedFireAnalysis[speedIndex]) === 0) Micro.fireAnalysis(this.blockMaps); this.disasterManager.doDisasters(this.census, this.messageManager);
},*/
simulate : function() {
this.phaseCycle &= 15;
var speedIndex = this.speed - 1;
switch (this.phaseCycle){
case 0:
//this.needPower = [];
if (++this.simCycle > 1023) this.simCycle = 0;
if (this.doInitialEval) { this.doInitialEval = false; this.evaluation.cityEvaluation(); }
this.cityTime++;
if ((this.simCycle & 1) === 0) this.valves.setValves(this.gameLevel, this.census, this.budget);
this.clearCensus();
break;
case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
this.mapScanner.mapScan((this.phaseCycle - 1) * this.div, this.phaseCycle * this.div, null);
// this.mapScanner.mapScan((this.phaseCycle - 1) * this.map.width / 8, this.phaseCycle * this.map.width / 8, null);
break;
case 9:
if (this.cityTime % Micro.CENSUS_FREQUENCY_10 === 0){this.census.take10Census(this.budget);};
if (this.cityTime % Micro.CENSUS_FREQUENCY_120 === 0) {this.census.take120Census(this.budget);}
if (this.cityTime % Micro.TAX_FREQUENCY === 0) { this.budget.collectTax(this.gameLevel, this.census, this.messageManager); this.evaluation.cityEvaluation(); };
break;
case 10: if ((this.simCycle % 5) === 0){ Micro.decRateOfGrowthMap(this.blockMaps);}; Micro.decTrafficMap(this.blockMaps); this.sendMessages(); break;
case 11: if ((this.simCycle % Micro.speedPowerScan[speedIndex]) === 0) this.powerManager.doPowerScan(this.census, this.messageManager); break;
case 12: if ((this.simCycle % Micro.speedPollutionTerrainLandValueScan[speedIndex]) === 0) Micro.pollutionTerrainLandValueScan(this.map, this.census, this.blockMaps); break;
case 13: if ((this.simCycle % Micro.speedCrimeScan[speedIndex]) === 0) Micro.crimeScan(this.census, this.blockMaps); break;
case 14: if ((this.simCycle % Micro.speedPopulationDensityScan[speedIndex]) === 0) Micro.populationDensityScan(this.map, this.blockMaps); break;
case 15: if ((this.simCycle % Micro.speedFireAnalysis[speedIndex]) === 0) Micro.fireAnalysis(this.blockMaps); this.disasterManager.doDisasters(this.census, this.messageManager); break;
}
// Go on the the next phase.
this.phaseCycle = (this.phaseCycle + 1) & 15;
},
sendMessages : function() {
this.checkGrowth();
var totalZonePop = this.census.resZonePop + this.census.comZonePop + this.census.indZonePop;
var powerPop = this.census.nuclearPowerPop + this.census.coalPowerPop;
switch (this.cityTime & 63) {
case 1: if (Math.floor(totalZonePop / 4) >= this.census.resZonePop) this.messageManager.sendMessage(Messages.NEED_MORE_RESIDENTIAL); break;
case 5: if (Math.floor(totalZonePop / 8) >= this.census.comZonePop) this.messageManager.sendMessage(Messages.NEED_MORE_COMMERCIAL); break;
case 10: if (Math.floor(totalZonePop / 8) >= this.census.indZonePop) this.messageManager.sendMessage(Messages.NEED_MORE_INDUSTRIAL); break;
case 14: if (totalZonePop > 10 && totalZonePop * 2 > this.census.roadTotal) this.messageManager.sendMessage(Messages.NEED_MORE_ROADS); break;
case 18: if (totalZonePop > 50 && totalZonePop > this.census.railTotal) this.messageManager.sendMessage(Messages.NEED_MORE_RAILS); break;
case 22: if (totalZonePop > 10 && powerPop == 0) this.messageManager.sendMessage(Messages.NEED_ELECTRICITY); break;
case 26: if (this.census.resPop > 500 && this.census.stadiumPop === 0) { this.messageManager.sendMessage(Messages.NEED_STADIUM); this.valves.resCap = true; } else { this.valves.resCap = false;}; break;
case 28: if (this.census.indPop > 70 && this.census.seaportPop === 0) { this.messageManager.sendMessage(Messages.NEED_SEAPORT); this.valves.indCap = true; } else { this.valves.indCap = false; }; break;
case 30: if (this.census.comPop > 100 && this.census.airportPop === 0) { this.messageManager.sendMessage(Messages._NEED_AIRPORT); this.valves.comCap = true; } else { this.valves.comCap = false; }; break;
case 32: var zoneCount = this.census.unpoweredZoneCount + this.census.poweredZoneCount; if (zoneCount > 0) { if (this.census.poweredZoneCount / zoneCount < 0.7) this.messageManager.sendMessage(Messages.BLACKOUTS_REPORTED);}; break;
case 35: if (this.census.pollutionAverage > 60) this.messageManager.sendMessage(Messages.HIGH_POLLUTION); break;
case 42: if (this.census.crimeAverage > 100) this.messageManager.sendMessage(Messages.HIGH_CRIME); break;
case 45: if (this.census.totalPop > 60 && this.census.fireStationPop === 0) this.messageManager.sendMessage(Messages.NEED_FIRE_STATION); break;
case 48: if (this.census.totalPop > 60 && this.census.policeStationPop === 0) this.messageManager.sendMessage(Messages.NEED_POLICE_STATION); break;
case 51: if (this.budget.cityTax > 12) this.messageManager.sendMessage(Messages.TAX_TOO_HIGH); break;
case 54: if (this.budget.roadEffect < Math.floor(5 * this.budget.MAX_ROAD_EFFECT / 8) && this.census.roadTotal > 30) this.messageManager.sendMessage(Messages.ROAD_NEEDS_FUNDING); break;
case 57: if (this.budget.fireEffect < Math.floor(7 * this.budget.MAX_FIRE_STATION_EFFECT / 10) && this.census.totalPop > 20) this.messageManager.sendMessage(Messages.FIRE_STATION_NEEDS_FUNDING); break;
case 60: if (this.budget.policeEffect < Math.floor(7 * this.budget.MAX_POLICE_STATION_EFFECT / 10) && this.census.totalPop > 20) this.messageManager.sendMessage(Messages.POLICE_NEEDS_FUNDING); break;
case 63: if (this.census.trafficAverage > 60) this.messageManager.sendMessage(Messages.TRAFFIC_JAMS, -1, -1, true); break;
}
},
checkGrowth : function() {
if ((this.cityTime & 3) === 0) {
var message = '';
var thisCityPop = this.evaluation.getPopulation(this.census);
if (this.cityPopLast > 0) {
var lastClass = this.evaluation.getCityClass(this.cityPopLast);
var newClass = this.evaluation.getCityClass(thisCityPop);
if (lastClass !== newClass) {
switch (newClass) {
case Micro.CC_VILLAGE:
// Don't mention it.
break;
case Micro.CC_TOWN: message = Messages.REACHED_TOWN; break;
case Micro.CC_CITY: message = Messages.REACHED_CITY; break;
case Micro.CC_CAPITAL: message = Messages.REACHED_CAPITAL; break;
case Micro.CC_METROPOLIS: message = Messages.REACHED_METROPOLIS; break;
case Micro.CC_MEGALOPOLIS: message = Messages.REACHED_MEGALOPOLIS; break;
default: break;
}
}
}
if (message !== '' && message !== this.messageLast) {
this.messageManager.sendMessage(message);
this.messageLast = message;
}
this.cityPopLast = thisCityPop;
}
},
updateFrontEnd : function() {
// Have valves changed?
if (this.valves.changed) {
this._resLast = this.valves.resValve;
this._comLast = this.valves.comValve;
this._indLast = this.valves.indValve;
// Note: the valves changed the population
this.messageManager.sendMessage(Messages.VALVES_UPDATED, {residential: this.valves.resValve, commercial: this.valves.comValve, industrial: this.valves.indValve});
this.valves.changed = false;
}
this.updateTime();
if (this.evaluation.changed) {
this.messageManager.sendMessage(Messages.EVAL_UPDATED, {classification: this.evaluation.cityClass, population: this.evaluation.cityPop, score: this.evaluation.cityScore});
this.evaluation.changed = false;
}
},
setYear : function(year) {
if (year < this.startingYear) year = this.startingYear;
year = (year - this.startingYear) - (this.cityTime / 48);
this.cityTime += year * 48;
this.updateTime();
},
updateTime : function() {
var megalinium = 1000000;
var cityYear = Math.floor(this.cityTime / 48) + this.startingYear;
var cityMonth = Math.floor(this.cityTime % 48) >> 2;
if (cityYear >= megalinium) {
this.setYear(this.startingYear);
return;
}
if (this._cityYearLast !== cityYear || this._cityMonthLast !== cityMonth) {
this._cityYearLast = cityYear;
this._cityMonthLast = cityMonth;
this.messageManager.sendMessage(Messages.DATE_UPDATED, {month: cityMonth, year: cityYear});
}
}
}