9 package micropolisj.engine;
11 import static micropolisj.engine.TileConstants.ALLBITS;
12 import static micropolisj.engine.TileConstants.CHANNEL;
13 import static micropolisj.engine.TileConstants.COMBASE;
14 import static micropolisj.engine.TileConstants.DIRT;
15 import static micropolisj.engine.TileConstants.FIRE;
16 import static micropolisj.engine.TileConstants.FLOOD;
17 import static micropolisj.engine.TileConstants.HHTHR;
18 import static micropolisj.engine.TileConstants.INDBASE;
19 import static micropolisj.engine.TileConstants.LASTZONE;
20 import static micropolisj.engine.TileConstants.LHTHR;
21 import static micropolisj.engine.TileConstants.LOMASK;
22 import static micropolisj.engine.TileConstants.NUCLEAR;
23 import static micropolisj.engine.TileConstants.PORTBASE;
24 import static micropolisj.engine.TileConstants.POWERPLANT;
25 import static micropolisj.engine.TileConstants.PWRBIT;
26 import static micropolisj.engine.TileConstants.RADTILE;
27 import static micropolisj.engine.TileConstants.RESCLR;
28 import static micropolisj.engine.TileConstants.RIVER;
29 import static micropolisj.engine.TileConstants.RUBBLE;
30 import static micropolisj.engine.TileConstants.commercialZonePop;
31 import static micropolisj.engine.TileConstants.getDescriptionNumber;
32 import static micropolisj.engine.TileConstants.getPollutionValue;
33 import static micropolisj.engine.TileConstants.getTileBehavior;
34 import static micropolisj.engine.TileConstants.getZoneSizeFor;
35 import static micropolisj.engine.TileConstants.industrialZonePop;
36 import static micropolisj.engine.TileConstants.isAnimated;
37 import static micropolisj.engine.TileConstants.isArsonable;
38 import static micropolisj.engine.TileConstants.isCombustible;
39 import static micropolisj.engine.TileConstants.isConductive;
40 import static micropolisj.engine.TileConstants.isConstructed;
41 import static micropolisj.engine.TileConstants.isFloodable;
42 import static micropolisj.engine.TileConstants.isRiverEdge;
43 import static micropolisj.engine.TileConstants.isVulnerable;
44 import static micropolisj.engine.TileConstants.isZoneCenter;
45 import static micropolisj.engine.TileConstants.residentialZonePop;
47 import java.io.DataInputStream;
48 import java.io.DataOutputStream;
50 import java.io.FileInputStream;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.OutputStream;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.HashMap;
58 import java.util.List;
60 import java.util.Random;
61 import java.util.Stack;
63 import micropolisj.gui.MainWindow;
64 import micropolisj.util.Utilities;
71 static final Random DEFAULT_PRNG =
new Random();
79 protected char[][]
map;
143 int researchDelayCharger = 0;
144 int researchDelay = 5;
147 int factorIncome = 10;
148 int factorRoadFund = 5;
156 static final int DEFAULT_WIDTH = 120;
157 static final int DEFAULT_HEIGHT = 100;
162 public Stack<CityLocation>
powerPlants =
new Stack<CityLocation>();
188 protected List<Sprite>
sprites =
new ArrayList<Sprite>();
190 static final int VALVERATE = 2;
192 static final int TAXFREQ = 48;
204 this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
217 protected void init(
int width,
int height) {
218 map =
new char[height][width];
219 powerMap =
new boolean[height][width];
221 int hX = (width + 1) / 2;
222 int hY = (height + 1) / 2;
224 landValueMem =
new int[hY][hX];
228 trfDensity =
new int[hY][hX];
230 int qX = (width + 3) / 4;
231 int qY = (height + 3) / 4;
233 terrainMem =
new int[qY][qX];
235 int smX = (width + 7) / 8;
236 int smY = (height + 7) / 8;
239 fireStMap =
new int[smY][smX];
240 policeMap =
new int[smY][smX];
243 comRate =
new int[smY][smX];
262 l.cityMessage(message, loc);
267 for(Listener l : listeners) {
268 l.citySound(sound, loc);
278 void fireEarthquakeStarted() {
280 l.earthquakeStarted();
286 l.evaluationChanged();
298 l.mapOverlayDataChanged(overlayDataType);
302 void fireOptionsChanged() {
303 for(Listener l : listeners) {
308 void fireSpriteMoved(Sprite sprite) {
309 for(MapListener l : mapListeners) {
310 l.spriteMoved(sprite);
316 l.tileChanged(xpos, ypos);
326 ArrayList<Listener> listeners =
new ArrayList<Listener>();
327 ArrayList<MapListener> mapListeners =
new ArrayList<MapListener>();
328 ArrayList<EarthquakeListener> earthquakeListeners =
new ArrayList<EarthquakeListener>();
331 this.listeners.add(l);
335 this.listeners.remove(l);
339 this.earthquakeListeners.add(l);
343 this.earthquakeListeners.remove(l);
347 this.mapListeners.add(l);
351 this.mapListeners.remove(l);
356 int tempelListenerId = 0;
359 tempelListeners[tempelListenerId] = listener;
363 for(
int id=0;
id<tempelListenerId;
id++){
368 for(
int id=0;
id<tempelListenerId;
id++){
369 tempelListeners[id].
onEnd();
415 return map[0].length;
423 return (
char) (
map[ypos][xpos] & LOMASK);
427 return map[ypos][xpos];
431 int myTile = eff.
getTile(0, 0);
437 if(ts.
owner != null) {
442 return !(ts.
owner.tileNumber == baseTile);
448 boolean isTileDozeable(
int xpos,
int ypos) {
449 return isTileDozeable(
new ToolEffect(
this, xpos, ypos,
getPlayerID()));
453 return (
getTileRaw(xpos, ypos) & PWRBIT) == PWRBIT;
456 public void setTile(
int xpos,
int ypos,
char newTile) {
457 if(
map[ypos][xpos] != newTile) {
461 map[ypos][xpos] = newTile;
466 public void setTile(
int xpos,
int ypos,
char newTile,
int playerID) {
471 return xpos >= 0 && xpos < getWidth() && ypos >= 0 && ypos <
getHeight();
474 final boolean hasPower(
int x,
int y) {
475 return powerMap[y][x];
483 return (
cityTime != 0 && (
cityTime % TAXFREQ) == 0 && ((fcycle + 1) % 16) == 10 && ((acycle + 1) % 2) == 0);
487 fcycle = (fcycle + 1) % 1024;
488 simulate(fcycle % 16);
515 for(
int y = 0; y < fireStMap.length; y++) {
516 for(
int x = 0; x < fireStMap[y].length; x++) {
523 void simulate(
int mod16) {
528 scycle = (scycle + 1) % 1024;
530 if(scycle % 2 == 0) {
593 if(scycle % 5 == 0) {
633 throw new Error(
"unreachable");
639 return doFreePop(x, y);
642 return residentialZonePop(tile);
645 return commercialZonePop(tile) * 8;
648 return industrialZonePop(tile) * 8;
654 final int h = tem.length;
655 final int w = tem[0].length;
656 int[][] tem2 =
new int[h][w];
658 for(
int y = 0; y < h; y++) {
659 for(
int x = 0; x < w; x++) {
689 int[][] tem =
new int[(height + 1) / 2][(width + 1) / 2];
691 for(
int x = 0; x < width; x++) {
692 for(
int y = 0; y < height; y++) {
694 if(isZoneCenter(tile)) {
698 tem[y / 2][x / 2] = den;
711 for(
int x = 0; x < (width + 1) / 2; x++) {
712 for(
int y = 0; y < (height + 1) / 2; y++) {
734 for(
int y = 0; y < comRate.length; y++) {
735 for(
int x = 0; x < comRate[y].length; x++) {
736 int z = getDisCC(x * 4, y * 4);
746 for(
int y = 0; y <
rateOGMem.length; y++) {
747 for(
int x = 0; x <
rateOGMem[y].length; x++) {
773 for(
int y = 0; y < trfDensity.length; y++) {
774 for(
int x = 0; x < trfDensity[y].length; x++) {
775 int z = trfDensity[y][x];
778 trfDensity[y][x] = z - 34;
780 trfDensity[y][x] = z - 24;
782 trfDensity[y][x] = 0;
793 for(
int sy = 0; sy < policeMap.length; sy++) {
794 for(
int sx = 0; sx < policeMap[sy].length; sx++) {
802 for(
int hy = 0; hy < landValueMem.length; hy++) {
803 for(
int hx = 0; hx < landValueMem[hy].length; hx++) {
804 int val = landValueMem[hy][hx];
808 z = Math.min(300, z);
809 z -= policeMap[hy / 4][hx / 4];
810 z = Math.min(250, z);
815 if(z > cmax || (z == cmax && PRNG.nextInt(4) == 0)) {
840 final int[] DisChance = {
846 if(PRNG.nextInt(DisChance[
gameLevel] + 1) != 0)
849 switch(PRNG.nextInt(9)) {
876 int smX = omap[0].length;
877 int smY = omap.length;
878 int[][] nmap =
new int[smY][smX];
879 for(
int sy = 0; sy < smY; sy++) {
880 for(
int sx = 0; sx < smX; sx++) {
883 edge += omap[sy][sx - 1];
886 edge += omap[sy][sx + 1];
889 edge += omap[sy - 1][sx];
892 edge += omap[sy + 1][sx];
894 edge = edge / 4 + omap[sy][sx];
895 nmap[sy][sx] = edge / 2;
901 void fireAnalysis() {
905 for(
int sy = 0; sy < fireStMap.length; sy++) {
906 for(
int sx = 0; sx < fireStMap[sy].length; sx++) {
907 fireRate[sy][sx] = fireStMap[sy][sx];
921 rv = (isConductive(t) && t != NUCLEAR && t != POWERPLANT && !hasPower(loc.
x, loc.
y));
969 for(
boolean[] bb : powerMap) {
970 Arrays.fill(bb,
false);
993 if(++numPower > maxPower) {
999 powerMap[loc.
y][loc.
x] =
true;
1003 while(dir < 4 && conNum < 2) {
1026 void addTraffic(
int mapX,
int mapY,
int traffic) {
1027 int z = trfDensity[mapY / 2][mapX / 2];
1034 if(z > 240 && PRNG.nextInt(6) == 0) {
1040 if(copter != null) {
1041 copter.destX = mapX;
1042 copter.destY = mapY;
1046 trfDensity[mapY / 2][mapX / 2] = z;
1051 return fireRate[ypos / 8][xpos / 8];
1057 return landValueMem[ypos / 2][xpos / 2];
1066 return trfDensity[ypos / 2][xpos / 2];
1075 final int qX = (
getWidth() + 3) / 4;
1077 int[][] qtem =
new int[qY][qX];
1079 int landValueTotal = 0;
1080 int landValueCount = 0;
1082 final int HWLDX = (
getWidth() + 1) / 2;
1083 final int HWLDY = (
getHeight() + 1) / 2;
1084 int[][] tem =
new int[HWLDY][HWLDX];
1085 for(
int x = 0; x < HWLDX; x++) {
1086 for(
int y = 0; y < HWLDY; y++) {
1092 for(
int mx = zx; mx <= zx + 1; mx++) {
1093 for(
int my = zy; my <= zy + 1; my++) {
1099 qtem[y / 2][x / 2] += 15;
1102 plevel += getPollutionValue(tile,
this);
1103 if(isConstructed(tile))
1120 int dis = 34 - getDisCC(x, y);
1122 dis += terrainMem[y / 2][x / 2];
1131 landValueMem[y][x] = dis;
1132 landValueTotal += dis;
1136 landValueMem[y][x] = 0;
1149 for(
int x = 0; x < HWLDX; x++) {
1150 for(
int y = 0; y < HWLDY; y++) {
1158 if(z > pmax || (z == pmax && PRNG.nextInt(4) == 0)) {
1169 terrainMem = smoothTerrain(qtem);
1179 static final int[] TaxTable = {
1180 200, 150, 120, 100, 80, 50, 30, 0, -10, -40, -100, -150, -200, -250, -300, -350, -400, -450, -500, -550, -600
1183 public static class History {
1185 public int[] res =
new int[240];
1186 public int[] com =
new int[240];
1187 public int[] ind =
new int[240];
1188 public int[] money =
new int[240];
1189 public int[] pollution =
new int[240];
1190 public int[] crime =
new int[240];
1203 if(normResPop != 0.0) {
1210 double migration = normResPop * (employment - 1);
1211 final double BIRTH_RATE = 0.02;
1212 double births = (double) normResPop * BIRTH_RATE;
1213 double projectedResPop = normResPop + migration + births;
1218 laborBase =
history.res[1] / temp;
1225 laborBase = Math.max(0.0, Math.min(1.3, laborBase));
1228 double projectedComPop = internalMarket * laborBase;
1245 if(projectedIndPop < 5.0)
1246 projectedIndPop = 5.0;
1249 if(normResPop != 0) {
1250 resRatio = (double) projectedResPop / (
double) normResPop;
1260 comRatio = projectedComPop;
1266 indRatio = projectedIndPop;
1281 resRatio = (resRatio - 1) * 600 + TaxTable[z2];
1282 comRatio = (comRatio - 1) * 600 + TaxTable[z2];
1283 indRatio = (indRatio - 1) * 600 + TaxTable[z2];
1323 int[][] smoothTerrain(
int[][] qtem) {
1324 final int QWX = qtem[0].length;
1325 final int QWY = qtem.length;
1327 int[][] mem =
new int[QWY][QWX];
1328 for(
int y = 0; y < QWY; y++) {
1329 for(
int x = 0; x < QWX; x++) {
1332 z += qtem[y][x - 1];
1334 z += qtem[y][x + 1];
1336 z += qtem[y - 1][x];
1338 z += qtem[y + 1][x];
1339 mem[y][x] = z / 4 + qtem[y][x] / 2;
1347 int getDisCC(
int x,
int y) {
1348 assert x >= 0 && x <=
getWidth() / 2;
1354 int z = (xdis + ydis);
1361 Map<String, TileBehavior> tileBehaviors;
1363 void initTileBehaviors() {
1364 HashMap<String, TileBehavior> bb;
1365 bb =
new HashMap<String, TileBehavior>();
1367 bb.put(
"FIRE",
new TerrainBehavior(
this, TerrainBehavior.B.FIRE));
1368 bb.put(
"FLOOD",
new TerrainBehavior(
this, TerrainBehavior.B.FLOOD));
1369 bb.put(
"RADIOACTIVE",
new TerrainBehavior(
this, TerrainBehavior.B.RADIOACTIVE));
1370 bb.put(
"ROAD",
new TerrainBehavior(
this, TerrainBehavior.B.ROAD));
1371 bb.put(
"RAIL",
new TerrainBehavior(
this, TerrainBehavior.B.RAIL));
1372 bb.put(
"EXPLOSION",
new TerrainBehavior(
this, TerrainBehavior.B.EXPLOSION));
1373 bb.put(
"RESIDENTIAL",
new MapScanner(
this, MapScanner.B.RESIDENTIAL));
1374 bb.put(
"HOSPITAL_CHURCH",
new MapScanner(
this, MapScanner.B.HOSPITAL_CHURCH));
1375 bb.put(
"COMMERCIAL",
new MapScanner(
this, MapScanner.B.COMMERCIAL));
1376 bb.put(
"INDUSTRIAL",
new MapScanner(
this, MapScanner.B.INDUSTRIAL));
1377 bb.put(
"COAL",
new MapScanner(
this, MapScanner.B.COAL));
1378 bb.put(
"NUCLEAR",
new MapScanner(
this, MapScanner.B.NUCLEAR));
1379 bb.put(
"FIRESTATION",
new MapScanner(
this, MapScanner.B.FIRESTATION));
1380 bb.put(
"POLICESTATION",
new MapScanner(
this, MapScanner.B.POLICESTATION));
1381 bb.put(
"STADIUM_EMPTY",
new MapScanner(
this, MapScanner.B.STADIUM_EMPTY));
1382 bb.put(
"STADIUM_FULL",
new MapScanner(
this, MapScanner.B.STADIUM_FULL));
1383 bb.put(
"AIRPORT",
new MapScanner(
this, MapScanner.B.AIRPORT));
1384 bb.put(
"SEAPORT",
new MapScanner(
this, MapScanner.B.SEAPORT));
1385 bb.put(
"UNIVERSITY",
new MapScanner(
this, MapScanner.B.UNIVERSITY));
1387 MapScanner tempelScanner =
new MapScanner(
this, MapScanner.B.TEMPEL);
1389 bb.put(
"TEMPEL",
new MapScanner(
this, MapScanner.B.TEMPEL));
1392 this.tileBehaviors = bb;
1396 for(
int x = x0; x < x1; x++) {
1403 void mapScanTile(
int xpos,
int ypos) {
1404 int tile =
getTile(xpos, ypos);
1405 String behaviorStr = getTileBehavior(tile);
1406 if(behaviorStr == null) {
1410 TileBehavior b = tileBehaviors.get(behaviorStr);
1412 b.processTile(xpos, ypos);
1415 throw new Error(
"Unknown behavior: " + behaviorStr);
1420 void generateShip() {
1421 int edge = PRNG.nextInt(4);
1424 for(
int x = 4; x <
getWidth() - 2; x++) {
1425 if(
getTile(x, 0) == CHANNEL) {
1426 makeShipAt(x, 0, ShipSprite.NORTH_EDGE);
1431 else if(edge == 1) {
1432 for(
int y = 1; y <
getHeight() - 2; y++) {
1433 if(
getTile(0, y) == CHANNEL) {
1434 makeShipAt(0, y, ShipSprite.EAST_EDGE);
1439 else if(edge == 2) {
1440 for(
int x = 4; x <
getWidth() - 2; x++) {
1442 makeShipAt(x,
getHeight() - 1, ShipSprite.SOUTH_EDGE);
1448 for(
int y = 1; y <
getHeight() - 2; y++) {
1450 makeShipAt(
getWidth() - 1, y, ShipSprite.EAST_EDGE);
1457 Sprite getSprite(SpriteKind kind) {
1465 boolean hasSprite(SpriteKind kind) {
1466 return getSprite(kind) != null;
1469 void makeShipAt(
int xpos,
int ypos,
int edge) {
1470 assert !hasSprite(SpriteKind.SHI);
1472 sprites.add(
new ShipSprite(
this, xpos, ypos, edge));
1475 void generateCopter(
int xpos,
int ypos) {
1476 if(!hasSprite(SpriteKind.COP)) {
1477 sprites.add(
new HelicopterSprite(
this, xpos, ypos));
1481 void generatePlane(
int xpos,
int ypos) {
1482 if(!hasSprite(SpriteKind.AIR)) {
1483 sprites.add(
new AirplaneSprite(
this, xpos, ypos));
1487 void generateTrain(
int xpos,
int ypos) {
1488 if(
playerInfo.
totalPop > 20 && !hasSprite(SpriteKind.TRA) && PRNG.nextInt(26) == 0) {
1489 sprites.add(
new TrainSprite(
this, xpos, ypos));
1494 int doFreePop(
int xpos,
int ypos) {
1497 for(
int x = xpos - 1; x <= xpos + 1; x++) {
1498 for(
int y = ypos - 1; y <= ypos + 1; y++) {
1501 if(loc >= LHTHR && loc <= HHTHR)
1511 void generateRocket(
int xpos,
int ypos,
int xDest,
int yDest,
int ownerID) {
1513 RocketSprite rocket =
new RocketSprite(
this, xpos, ypos, xDest, yDest, ownerID);
1514 sprites.add(rocket);
1526 for(
int i = 118; i >= 0; i--) {
1567 if(moneyScaled > 255)
1569 history.money[0] = moneyScaled;
1602 for(
int i = 238; i >= 120; i--) {
1629 static final double[] RLevels = {
1636 static final double[] FLevels = {
1650 if (researchDelayCharger >= researchDelay) {
1653 researchDelayCharger = 0;
1655 researchDelayCharger++;
1659 void collectTaxPartial() {
1686 public static class FinancialHistory {
1688 public int totalFunds;
1689 public int taxIncome;
1690 public int operatingExpenses;
1700 FinancialHistory hist =
new FinancialHistory();
1702 hist.taxIncome = revenue;
1703 hist.operatingExpenses = expenses;
1720 static final int POLICE_STATION_MAINTENANCE = 100;
1723 static final int FIRE_STATION_MAINTENANCE = 100;
1726 static final int RESEARCH_STATION_MAINTENANCE = 500;
1758 assert yumDuckets >= 0;
1822 int getPopulationDensity(
int xpos,
int ypos) {
1823 return popDensity[ypos / 2][xpos / 2];
1826 void doMeltdown(
int xpos,
int ypos) {
1827 meltdownLocation =
new CityLocation(xpos, ypos);
1829 makeExplosion(xpos - 1, ypos - 1);
1830 makeExplosion(xpos - 1, ypos + 2);
1831 makeExplosion(xpos + 2, ypos - 1);
1832 makeExplosion(xpos + 2, ypos + 2);
1834 for(
int x = xpos - 1; x < xpos + 3; x++) {
1835 for(
int y = ypos - 1; y < ypos + 3; y++) {
1836 setTile(x, y, (
char) (FIRE + PRNG.nextInt(4)));
1840 for(
int z = 0; z < 200; z++) {
1841 int x = xpos - 20 + PRNG.nextInt(41);
1842 int y = ypos - 15 + PRNG.nextInt(31);
1847 if(isZoneCenter(t)) {
1850 if(isCombustible(t) || t == DIRT) {
1856 sendMessageAt(MicropolisMessage.MELTDOWN_REPORT, xpos, ypos);
1859 static final int[] MltdwnTab = {
1863 void loadHistoryArray(
int[] array, DataInputStream dis)
throws IOException {
1864 for(
int i = 0; i < 240; i++) {
1865 array[i] = dis.readShort();
1869 void writeHistoryArray(
int[] array, DataOutputStream out)
throws IOException {
1870 for(
int i = 0; i < 240; i++) {
1871 out.writeShort(array[i]);
1876 void loadMisc(DataInputStream dis)
throws IOException {
1880 playerInfo.resPop = dis.readShort();
1881 playerInfo.comPop = dis.readShort();
1882 playerInfo.indPop = dis.readShort();
1883 playerInfo.resValve = dis.readShort();
1884 playerInfo.comValve = dis.readShort();
1885 playerInfo.indValve = dis.readShort();
1887 playerInfo.crimeRamp = dis.readShort();
1888 playerInfo.polluteRamp = dis.readShort();
1889 playerInfo.landValueAverage = dis.readShort();
1890 playerInfo.crimeAverage = dis.readShort();
1891 playerInfo.pollutionAverage = dis.readShort();
1893 playerInfo.evaluation.cityClass = dis.readShort();
1894 playerInfo.evaluation.cityScore = dis.readShort();
1896 for(
int i = 18; i < 50; i++) {
1900 playerInfo.budget.totalFunds = dis.readInt();
1903 autoGo = dis.readShort() != 0;
1905 playerInfo.cityTax = dis.readShort();
1906 playerInfo.taxEffect = playerInfo.cityTax;
1907 int simSpeedAsInt = dis.readShort();
1908 if(simSpeedAsInt >= 0 && simSpeedAsInt <= 4)
1909 simSpeed = Speed.values()[simSpeedAsInt];
1915 int n = dis.readInt();
1916 playerInfo.policePercent = n / 65536.0;
1918 playerInfo.firePercent = n / 65536.0;
1920 playerInfo.roadPercent = n / 65536.0;
1923 playerInfo.researchPercent = n / 65536.0;
1926 for(
int i = 66; i < 120; i++) {
1933 if(playerInfo.cityTax < 0 || playerInfo.cityTax > 20) {
1934 playerInfo.cityTax = 7;
1936 if(gameLevel < 0 || gameLevel > 2) {
1939 if(playerInfo.evaluation.cityClass < 0 || playerInfo.evaluation.cityClass > 5) {
1940 playerInfo.evaluation.cityClass = 0;
1942 if(playerInfo.evaluation.cityScore < 1 || playerInfo.evaluation.cityScore > 999) {
1943 playerInfo.evaluation.cityScore = 500;
1946 playerInfo.resCap =
false;
1947 playerInfo.comCap =
false;
1948 playerInfo.indCap =
false;
1954 void writeMisc(DataOutputStream out)
throws IOException {
1958 out.writeShort(playerInfo.resPop);
1959 out.writeShort(playerInfo.comPop);
1960 out.writeShort(playerInfo.indPop);
1961 out.writeShort(playerInfo.resValve);
1962 out.writeShort(playerInfo.comValve);
1963 out.writeShort(playerInfo.indValve);
1966 out.writeShort(playerInfo.crimeRamp);
1967 out.writeShort(playerInfo.polluteRamp);
1969 out.writeShort(playerInfo.landValueAverage);
1970 out.writeShort(playerInfo.crimeAverage);
1971 out.writeShort(playerInfo.pollutionAverage);
1974 out.writeShort(playerInfo.evaluation.cityClass);
1975 out.writeShort(playerInfo.evaluation.cityScore);
1978 for(
int i = 18; i < 50; i++) {
1985 out.writeInt(playerInfo.budget.totalFunds);
1989 out.writeShort(autoGo ? 1 : 0);
1991 out.writeShort(playerInfo.cityTax);
1992 out.writeShort(
simSpeed.ordinal());
1995 out.writeInt((
int) (playerInfo.policePercent * 65536));
1996 out.writeInt((
int) (playerInfo.firePercent * 65536));
1997 out.writeInt((
int) (playerInfo.roadPercent * 65536));
1998 out.writeInt((
int) (playerInfo.researchPercent * 65536));
2001 for(
int i = 66; i < 120; i++) {
2008 void loadMap(DataInputStream dis)
throws IOException {
2009 for(
int x = 0; x < DEFAULT_WIDTH; x++) {
2010 for(
int y = 0; y < DEFAULT_HEIGHT; y++) {
2011 int z = dis.readShort();
2013 map[y][x] = (char) z;
2018 void writeMap(DataOutputStream out)
throws IOException {
2019 for(
int x = 0; x < DEFAULT_WIDTH; x++) {
2020 for(
int y = 0; y < DEFAULT_HEIGHT; y++) {
2042 public void load(File filename)
throws IOException {
2043 FileInputStream fis =
new FileInputStream(filename);
2044 if(fis.getChannel().size() > 27120) {
2050 byte[] bbHeader =
new byte[128];
2056 void checkPowerMap() {
2057 playerInfo.coalCount = 0;
2058 playerInfo.nuclearCount = 0;
2061 for(
int y = 0; y <
map.length; y++) {
2062 for(
int x = 0; x <
map[y].length; x++) {
2064 if(tile == NUCLEAR) {
2065 playerInfo.nuclearCount++;
2068 else if(tile == POWERPLANT) {
2069 playerInfo.coalCount++;
2076 playerInfo.newPower =
true;
2079 public void load(InputStream inStream)
throws IOException {
2080 DataInputStream dis =
new DataInputStream(inStream);
2081 loadHistoryArray(
history.res, dis);
2082 loadHistoryArray(
history.com, dis);
2083 loadHistoryArray(
history.ind, dis);
2084 loadHistoryArray(
history.crime, dis);
2085 loadHistoryArray(
history.pollution, dis);
2086 loadHistoryArray(
history.money, dis);
2099 public void save(File filename)
throws IOException {
2100 save(
new FileOutputStream(filename));
2103 public void save(OutputStream outStream)
throws IOException {
2104 DataOutputStream out =
new DataOutputStream(outStream);
2105 writeHistoryArray(
history.res, out);
2106 writeHistoryArray(
history.com, out);
2107 writeHistoryArray(
history.ind, out);
2108 writeHistoryArray(
history.crime, out);
2109 writeHistoryArray(
history.pollution, out);
2110 writeHistoryArray(
history.money, out);
2118 fireOptionsChanged();
2123 fireOptionsChanged();
2128 fireOptionsChanged();
2133 fireOptionsChanged();
2137 this.acycle = (this.acycle + 1) % 960;
2138 if(this.acycle % 2 == 0) {
2150 return sprites.toArray(
new Sprite[0]);
2156 if(sprite.frame == 0) {
2157 sprites.remove(sprite);
2163 for(
int y = 0; y <
map.length; y++) {
2164 for(
int x = 0; x <
map[y].length; x++) {
2165 char tilevalue =
map[y][x];
2167 if(spec != null && spec.animNext != null) {
2168 int flags = tilevalue & ALLBITS;
2169 setTile(x, y, (
char) (spec.animNext.tileNumber | flags));
2183 void makeSound(
int x,
int y,
Sound sound) {
2190 fireEarthquakeStarted();
2193 int time = PRNG.nextInt(701) + 300;
2194 for(
int z = 0; z < time; z++) {
2199 if(isVulnerable(
getTile(x, y))) {
2200 if(PRNG.nextInt(4) != 0) {
2201 setTile(x, y, (
char) (RUBBLE + PRNG.nextInt(4)));
2204 setTile(x, y, (
char) (FIRE + PRNG.nextInt(8)));
2215 if(isArsonable(t)) {
2216 setTile(x, y, (
char) (FIRE + PRNG.nextInt(8)));
2224 for(
int t = 0; t < 40; t++) {
2228 if(!isZoneCenter(tile) && isCombustible(tile)) {
2229 if(tile > 21 && tile < LASTZONE) {
2230 setTile(x, y, (
char) (FIRE + PRNG.nextInt(8)));
2244 ArrayList<CityLocation> candidates =
new ArrayList<CityLocation>();
2245 for(
int y = 0; y <
map.length; y++) {
2246 for(
int x = 0; x <
map[y].length; x++) {
2247 if(
getTile(x, y) == NUCLEAR) {
2253 if(candidates.isEmpty()) {
2258 int i = PRNG.nextInt(candidates.size());
2260 doMeltdown(p.
x, p.
y);
2266 if(monster != null) {
2268 monster.soundCount = 1;
2269 monster.count = 1000;
2270 monster.flag =
false;
2271 monster.destX = playerInfo.pollutionMaxLocationX;
2272 monster.destY = playerInfo.pollutionMaxLocationY;
2278 for(
int i = 0; i < 300; i++) {
2279 int x = PRNG.nextInt(
getWidth() - 19) + 10;
2280 int y = PRNG.nextInt(
getHeight() - 9) + 5;
2283 makeMonsterAt(x, y);
2292 void makeMonsterAt(
int xpos,
int ypos) {
2299 if(tornado != null) {
2302 tornado.count = 200;
2307 int xpos = PRNG.nextInt(
getWidth() - 19) + 10;
2308 int ypos = PRNG.nextInt(
getHeight() - 19) + 10;
2321 for(
int z = 0; z < 300; z++) {
2325 if(isRiverEdge(tile)) {
2326 for(
int t = 0; t < 4; t++) {
2330 int c =
map[yy][xx];
2331 if(isFloodable(c)) {
2352 void killZone(
int xpos,
int ypos,
int zoneTile) {
2355 assert isZoneCenter(zoneTile);
2358 assert dim.
width >= 3;
2361 int zoneBase = (zoneTile & LOMASK) - 1 - dim.
width;
2364 shutdownZone(xpos, ypos, dim);
2374 void powerZone(
int xpos,
int ypos, CityDimension zoneSize) {
2375 assert zoneSize.width >= 3;
2376 assert zoneSize.height >= 3;
2378 for(
int dx = 0; dx < zoneSize.width; dx++) {
2379 for(
int dy = 0; dy < zoneSize.height; dy++) {
2380 int x = xpos - 1 + dx;
2381 int y = ypos - 1 + dy;
2383 TileSpec ts = Tiles.get(tile & LOMASK);
2384 if(ts != null && ts.onPower != null) {
2385 setTile(x, y, (
char) (ts.onPower.tileNumber | (tile & ALLBITS)));
2398 void shutdownZone(
int xpos,
int ypos, CityDimension zoneSize) {
2399 assert zoneSize.width >= 3;
2400 assert zoneSize.height >= 3;
2402 for(
int dx = 0; dx < zoneSize.width; dx++) {
2403 for(
int dy = 0; dy < zoneSize.height; dy++) {
2404 int x = xpos - 1 + dx;
2405 int y = ypos - 1 + dy;
2407 TileSpec ts = Tiles.get(tile & LOMASK);
2408 if(ts != null && ts.onShutdown != null) {
2409 setTile(x, y, (
char) (ts.onShutdown.tileNumber | (tile & ALLBITS)));
2415 void makeExplosion(
int xpos,
int ypos) {
2416 makeExplosionAt(xpos * 16 + 8, ypos * 16 + 8);
2419 void makeExplosionAt(
int x,
int y) {
2420 sprites.add(
new ExplosionSprite(
this, x, y));
2423 void makeGiantExplosionAt(
int x,
int y) {
2424 makeGiantExplosionAt(x, y, 2);
2427 void makeGiantExplosionAt(
int x,
int y,
int radius) {
2429 for(
int dx = -radius; dx <= radius; dx++) {
2430 int nradius = radius - Math.abs(dx);
2431 for(
int dy = -nradius; dy <= nradius; dy++) {
2433 if(dx == 0 || dy == 0) {
2434 sprites.add(
new ExplosionSprite(
this, x + dx * off, y + dy * off,
false,
true));
2438 sprites.add(
new ExplosionSprite(
this, x + dx * off, y + dy * off,
false,
false));
2448 void checkGrowth() {
2450 int newPop = (playerInfo.resPop + playerInfo.comPop * 8 + playerInfo.indPop * 8) * 20;
2451 if(playerInfo.lastCityPop != 0) {
2452 MicropolisMessage z = null;
2453 if(playerInfo.lastCityPop < 500000 && newPop >= 500000) {
2454 z = MicropolisMessage.POP_500K_REACHED;
2456 else if(playerInfo.lastCityPop < 100000 && newPop >= 100000) {
2457 z = MicropolisMessage.POP_100K_REACHED;
2459 else if(playerInfo.lastCityPop < 50000 && newPop >= 50000) {
2460 z = MicropolisMessage.POP_50K_REACHED;
2462 else if(playerInfo.lastCityPop < 10000 && newPop >= 10000) {
2463 z = MicropolisMessage.POP_10K_REACHED;
2465 else if(playerInfo.lastCityPop < 2000 && newPop >= 2000) {
2466 z = MicropolisMessage.POP_2K_REACHED;
2472 playerInfo.lastCityPop = newPop;
2481 int totalZoneCount = playerInfo.resZoneCount + playerInfo.comZoneCount + playerInfo.indZoneCount;
2482 int powerCount = playerInfo.nuclearCount + playerInfo.coalCount;
2487 if(totalZoneCount / 4 >= playerInfo.resZoneCount) {
2488 sendMessage(MicropolisMessage.NEED_RES);
2492 if(totalZoneCount / 8 >= playerInfo.comZoneCount) {
2493 sendMessage(MicropolisMessage.NEED_COM);
2497 if(totalZoneCount / 8 >= playerInfo.indZoneCount) {
2498 sendMessage(MicropolisMessage.NEED_IND);
2502 if(totalZoneCount > 10 && totalZoneCount * 2 > playerInfo.roadTotal) {
2503 sendMessage(MicropolisMessage.NEED_ROADS);
2507 if(totalZoneCount > 50 && totalZoneCount > playerInfo.railTotal) {
2508 sendMessage(MicropolisMessage.NEED_RAILS);
2512 if(totalZoneCount > 10 && powerCount == 0) {
2513 sendMessage(MicropolisMessage.NEED_POWER);
2517 playerInfo.resCap = (playerInfo.resPop > 500 && playerInfo.stadiumCount == 0);
2518 if(playerInfo.resCap) {
2519 sendMessage(MicropolisMessage.NEED_STADIUM);
2523 playerInfo.indCap = (playerInfo.indPop > 70 && playerInfo.seaportCount == 0);
2524 if(playerInfo.indCap) {
2525 sendMessage(MicropolisMessage.NEED_SEAPORT);
2529 playerInfo.comCap = (playerInfo.comPop > 100 && playerInfo.airportCount == 0);
2530 if(playerInfo.comCap) {
2531 sendMessage(MicropolisMessage.NEED_AIRPORT);
2535 int TM = playerInfo.poweredZoneCount + playerInfo.poweredZoneCount;
2537 if((
double) playerInfo.poweredZoneCount / (
double) TM < 0.7) {
2538 sendMessage(MicropolisMessage.BLACKOUTS);
2543 if(playerInfo.pollutionAverage > 60) {
2546 sendMessage(MicropolisMessage.HIGH_POLLUTION);
2550 if(playerInfo.crimeAverage > 100) {
2551 sendMessage(MicropolisMessage.HIGH_CRIME);
2555 if(playerInfo.totalPop > 60 && playerInfo.fireStationCount == 0) {
2556 sendMessage(MicropolisMessage.NEED_FIRESTATION);
2560 if(playerInfo.totalPop > 60 && playerInfo.policeCount == 0) {
2561 sendMessage(MicropolisMessage.NEED_POLICE);
2565 if(playerInfo.totalPop > 150 && playerInfo.researchCount == 0) {
2566 sendMessage(MicropolisMessage.NEED_RESEARCH);
2570 if(playerInfo.cityTax > 12) {
2571 sendMessage(MicropolisMessage.HIGH_TAXES);
2575 if(playerInfo.roadEffect < 20 && playerInfo.roadTotal > 30) {
2576 sendMessage(MicropolisMessage.ROADS_NEED_FUNDING);
2580 if(playerInfo.fireEffect < 700 && playerInfo.totalPop > 20) {
2581 sendMessage(MicropolisMessage.FIRE_NEED_FUNDING);
2585 if(playerInfo.policeEffect < 700 && playerInfo.totalPop > 20) {
2586 sendMessage(MicropolisMessage.POLICE_NEED_FUNDING);
2590 if(playerInfo.trafficAverage > 60) {
2591 sendMessage(MicropolisMessage.HIGH_TRAFFIC);
2606 void sendMessage(MicropolisMessage message) {
2610 void sendMessageAt(MicropolisMessage message,
int x,
int y) {
2619 z = (popDensity[ypos / 2][xpos / 2] / 64) % 4;
2622 z = landValueMem[ypos / 2][xpos / 2];
2623 z = z < 30 ? 4 : z < 80 ? 5 : z < 150 ? 6 : 7;
2626 z = ((
crimeMem[ypos / 2][xpos / 2] / 64) % 4) + 8;
2629 z = Math.max(13, ((
pollutionMem[ypos / 2][xpos / 2] / 64) % 4) + 12);
2633 z = z < 0 ? 16 : z == 0 ? 17 : z <= 100 ? 18 : 19;
2655 fireOptionsChanged();
2666 public void setBudgetNumbers(
int newTaxRate,
double roadPct,
double newRoadPct,
double newPolicePct,
double newFirePct,
double newResearchPct) {