// This file is part of MicropolisJ. // Copyright (C) 2013 Jason Long // Portions Copyright (C) 1989-2007 Electronic Arts Inc. // // MicropolisJ is free software; you can redistribute it and/or modify // it under the terms of the GNU GPLv3, with additional terms. // See the README file, included in this distribution, for details. package micropolisj.engine; import java.util.*; import static micropolisj.engine.TileConstants.*; import static micropolisj.engine.Micropolis.ZoneType; import static micropolisj.engine.Animate.Smoke; /** * Process individual tiles of the map for each cycle. * In each sim cycle each tile will get activated, and this * class contains the activation code. */ class MapScanner { final Micropolis city; final Random PRNG; int xpos; int ypos; char cchr; char cchr9; MapScanner(Micropolis city) { this.city = city; this.PRNG = city.PRNG; } /** * Activate the tile identified by xpos and ypos properties. */ public void scanTile() { if (isFire(cchr)) { city.firePop++; if (PRNG.nextInt(4) == 0) { // one in four times doFire(); } return; } else if (isFlood(cchr)) { doFlood(); return; } else if (isRadioactive(cchr)) { doRadioactiveTile(); return; } cchr9 = (char) (cchr & LOMASK); if (city.newPower && ((cchr & CONDBIT) != 0)) { setZonePower(); } if (isRoad(cchr)) { doRoad(); return; } if (isZoneCenter(cchr)) { doZone(); return; } if (isRail(cchr)) { doRail(); return; } if (isTinyExplosion(cchr)) { // clear AniRubble city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT)); } } /** * Called when the current tile is a radioactive tile. */ void doRadioactiveTile() { if (PRNG.nextInt(4096) == 0) { // radioactive decay city.setTile(xpos, ypos, DIRT); } } static int [] TRAFFIC_DENSITY_TAB = { ROADBASE, LTRFBASE, HTRFBASE }; /** * Called when the current tile is a road tile. */ void doRoad() { city.roadTotal++; if (city.roadEffect < 30) { // deteriorating roads if (PRNG.nextInt(512) == 0) { if ((cchr & CONDBIT) == 0) { if (city.roadEffect < PRNG.nextInt(32)) { if (isOverWater(cchr)) city.setTile(xpos, ypos, RIVER); else city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT)); return; } } } } if (!isCombustible(cchr)) //bridge { city.roadTotal += 4; if (doBridge()) return; } int tden; if ((cchr & LOMASK) < LTRFBASE) tden = 0; else if ((cchr & LOMASK) < HTRFBASE) tden = 1; else { city.roadTotal++; tden = 2; } int trafficDensity = city.trfDensity[ypos/2][xpos/2]; int newLevel = trafficDensity < 64 ? 0 : trafficDensity < 192 ? 1 : 2; assert newLevel >= 0 && newLevel < TRAFFIC_DENSITY_TAB.length; if (tden != newLevel) { int z = (((cchr & LOMASK) - ROADBASE) & 15) + TRAFFIC_DENSITY_TAB[newLevel]; z += cchr & ALLBITS; city.setTile(xpos, ypos, (char) z); } } /** * Called when the current tile is an active fire. */ void doFire() { final int [] DX = { 0, 1, 0, -1 }; final int [] DY = { -1, 0, 1, 0 }; for (int dir = 0; dir < 4; dir++) { if (PRNG.nextInt(8) == 0) { int xtem = xpos + DX[dir]; int ytem = ypos + DY[dir]; if (!city.testBounds(xtem, ytem)) continue; int c = city.map[ytem][xtem]; if (isCombustible(c)) { if (isZoneCenter(c)) { city.killZone(xtem, ytem, c); if ((c & LOMASK) > IZB) { //explode city.makeExplosion(xtem, ytem); } } city.setTile(xtem, ytem, (char)(FIRE + PRNG.nextInt(4))); } } } int cov = city.fireRate[ypos/8][xpos/8]; //fire station coverage int rate = cov > 100 ? 1 : cov > 20 ? 2 : cov != 0 ? 3 : 10; if (PRNG.nextInt(rate+1) == 0) { city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT)); } } /** * Called when the current tile is a flooding tile. */ void doFlood() { final int [] DX = { 0, 1, 0, -1 }; final int [] DY = { -1, 0, 1, 0 }; if (city.floodCnt != 0) { for (int z = 0; z < 4; z++) { if (PRNG.nextInt(8) == 0) { int xx = xpos + DX[z]; int yy = ypos + DY[z]; if (city.testBounds(xx, yy)) { int c = city.getTile(xx, yy); int t = c & LOMASK; if (isCombustible(c) || c == DIRT || (t >= WOODS5 && t < FLOOD)) { if (isZoneCenter(c)) { city.killZone(xx, yy, c); } city.setTile(xx, yy, (char)(FLOOD + PRNG.nextInt(3))); } } } } } else { if (PRNG.nextInt(16) == 0) { city.setTile(xpos, ypos, DIRT); } } } /** * Called when the current tile is railroad. */ void doRail() { city.railTotal++; city.generateTrain(xpos, ypos); if (city.roadEffect < 30) { // deteriorating rail if (PRNG.nextInt(512) == 0) { if ((cchr & CONDBIT) == 0) { if (city.roadEffect < PRNG.nextInt(32)) { if (isOverWater(cchr)) { city.setTile(xpos,ypos,RIVER); } else { city.setTile(xpos,ypos,(char)(RUBBLE + PRNG.nextInt(4)+BULLBIT)); } } } } } } /** * Called when the current tile is a road bridge over water. * Handles the draw bridge. For the draw bridge to appear, * there must be a boat on the water, the boat must be * within a certain distance of the bridge, it must be where * the map generator placed 'channel' tiles (these are tiles * that look just like regular river tiles but have a different * numeric value), and you must be a little lucky. * * @return true if the draw bridge is open; false otherwise */ boolean doBridge() { final int HDx[] = { -2, 2, -2, -1, 0, 1, 2 }; final int HDy[] = { -1, -1, 0, 0, 0, 0, 0 }; final char HBRTAB[] = { HBRDG1 | BULLBIT, HBRDG3 | BULLBIT, HBRDG0 | BULLBIT, RIVER, BRWH | BULLBIT, RIVER, HBRDG2 | BULLBIT }; final char HBRTAB2[] = { RIVER, RIVER, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT }; final int VDx[] = { 0, 1, 0, 0, 0, 0, 1 }; final int VDy[] = { -2, -2, -1, 0, 1, 2, 2 }; final char VBRTAB[] = { VBRDG0 | BULLBIT, VBRDG1 | BULLBIT, RIVER, BRWV | BULLBIT, RIVER, VBRDG2 | BULLBIT, VBRDG3 | BULLBIT }; final char VBRTAB2[] = { VBRIDGE | BULLBIT, RIVER, VBRIDGE | BULLBIT, VBRIDGE | BULLBIT, VBRIDGE | BULLBIT, VBRIDGE | BULLBIT, RIVER }; if (cchr9 == BRWV) { // vertical bridge, open if (PRNG.nextInt(4) == 0 && getBoatDis() > 340/16) { //close the bridge applyBridgeChange(VDx, VDy, VBRTAB, VBRTAB2); } return true; } else if (cchr9 == BRWH) { // horizontal bridge, open if (PRNG.nextInt(4) == 0 && getBoatDis() > 340/16) { // close the bridge applyBridgeChange(HDx, HDy, HBRTAB, HBRTAB2); } return true; } if (getBoatDis() < 300/16 && PRNG.nextInt(8) == 0) { if ((cchr & 1) != 0) { // vertical bridge if (xpos < city.getWidth()-1) { // look for CHANNEL tile to right of // bridge. the CHANNEL tiles are only // found in the very center of the // river if (city.getTile(xpos+1,ypos) == CHANNEL) { // vertical bridge, open it up applyBridgeChange(VDx, VDy, VBRTAB2, VBRTAB); return true; } } return false; } else { // horizontal bridge if (ypos > 0) { // look for CHANNEL tile just above // bridge. the CHANNEL tiles are only // found in the very center of the // river if (city.getTile(xpos, ypos-1) == CHANNEL) { // open it up applyBridgeChange(HDx, HDy, HBRTAB2, HBRTAB); return true; } } return false; } } return false; } /** * Helper function for doBridge- it toggles the draw-bridge. */ private void applyBridgeChange(int [] Dx, int [] Dy, char [] fromTab, char [] toTab) { for (int z = 0; z < 7; z++) { int x = xpos + Dx[z]; int y = ypos + Dy[z]; if (city.testBounds(x,y)) { if ((city.map[y][x] & LOMASK) == (fromTab[z] & LOMASK) || (city.map[y][x] == CHANNEL) ) { city.setTile(x, y, toTab[z]); } } } } /** * Calculate how far away the boat currently is from the * current tile. */ int getBoatDis() { int dist = 99999; for (Sprite s : city.sprites) { if (s.isVisible() && s.kind == SpriteKind.SHI) { int x = s.x / 16; int y = s.y / 16; int d = Math.abs(xpos-x) + Math.abs(ypos-y); dist = Math.min(d, dist); } } return dist; } /** * Called when the current tile is the key tile of an airport. */ void doAirport() { if (PRNG.nextInt(6) == 0) { city.generatePlane(xpos, ypos); } if (PRNG.nextInt(13) == 0) { city.generateCopter(xpos, ypos); } } /** * Called when the current tile is the key tile of any zone. */ void doZone() { // set power bit in map, from powermap boolean zonePwrFlag = setZonePower(); if (zonePwrFlag) { city.poweredZoneCount++; } else { city.unpoweredZoneCount++; } if (isSpecialZone(cchr)) { doSpecialZone(zonePwrFlag); return; } if (isResidentialZone(cchr)) { doResidential(zonePwrFlag); return; } if (isHospitalOrChurch(cchr)) { doHospitalChurch(zonePwrFlag); return; } if (isCommercialZone(cchr)) { doCommercial(zonePwrFlag); return; } assert isIndustrialZone(cchr); doIndustrial(zonePwrFlag); return; } boolean setZonePower() { // refresh cchr, cchr9, since this can get called after the // tile's been changed cchr = city.map[ypos][xpos]; cchr9 = (char) (cchr & LOMASK); if (cchr9 == NUCLEAR || cchr9 == POWERPLANT || city.hasPower(xpos,ypos)) { city.setTile(xpos, ypos, (char) (cchr | PWRBIT)); return true; } else { city.setTile(xpos, ypos, (char) (cchr & (~PWRBIT))); return false; } } /** * Place a 3x3 zone on to the map, centered on the current location. * Note: nothing is done if part of this zone is off the edge * of the map or is being flooded or radioactive. * * @param base The first/north-western tile value for this zone. * @return true iff the zone was actually placed. */ boolean zonePlop(int base) { //FIXME- does this function have a caller that actually //pays attention to the return value? if (!city.testBounds(xpos-1, ypos-1)) return false; if (!city.testBounds(xpos+1, ypos+1)) return false; for (int y = ypos-1; y <= ypos+1; y++) { for (int x = xpos-1; x <= xpos+1; x++) { char c = (char)(city.map[y][x] & LOMASK); if (c >= FLOOD && c < ROADBASE) { // radioactive, on fire, or flooded return false; } } } for (int y = ypos-1; y <= ypos+1; y++) { for (int x = xpos-1; x <= xpos+1; x++) { city.setTile(x, y, (char)(base | BNCNBIT | (x == xpos && y == ypos ? ZONEBIT + BULLBIT : 0))); base++; } } setZonePower(); return true; } /** * Called when the current tile is the key tile of a "special" zone. * @param powerOn indicates whether the building has power */ void doSpecialZone(boolean powerOn) { switch (cchr9) { case POWERPLANT: city.coalCount++; if ((city.cityTime % 8) == 0) { repairZone(POWERPLANT, 4); } city.powerPlants.add(new CityLocation(xpos,ypos)); coalSmoke(); return; case NUCLEAR: if (!city.noDisasters && PRNG.nextInt(city.MltdwnTab[city.gameLevel]+1) == 0) { city.doMeltdown(xpos, ypos); return; } city.nuclearCount++; if ((city.cityTime % 8) == 0) { repairZone(NUCLEAR, 4); } city.powerPlants.add(new CityLocation(xpos, ypos)); return; case FIRESTATION: { city.fireStationCount++; if ((city.cityTime % 8) == 0) { repairZone(FIRESTATION, 3); } int z; if (powerOn) { z = city.fireEffect; //if powered, get effect } else { z = city.fireEffect/2; // from the funding ratio } city.traffic.mapX = xpos; city.traffic.mapY = ypos; if (!city.traffic.findPerimeterRoad()) { z /= 2; } city.fireStMap[ypos/8][xpos/8] += z; return; } case POLICESTATION: { city.policeCount++; if ((city.cityTime % 8) == 0) { repairZone(POLICESTATION, 3); } int z; if (powerOn) { z = city.policeEffect; } else { z = city.policeEffect / 2; } city.traffic.mapX = xpos; city.traffic.mapY = ypos; if (!city.traffic.findPerimeterRoad()) { z /= 2; } city.policeMap[ypos/8][xpos/8] += z; return; } case STADIUM: city.stadiumCount++; if ((city.cityTime % 16) == 0) { repairZone(STADIUM, 4); } if (powerOn) { if (((city.cityTime + xpos + ypos) % 32) == 0) { drawStadium(FULLSTADIUM); city.setTile(xpos+1,ypos, (char)(FOOTBALLGAME1)); city.setTile(xpos+1,ypos+1,(char)(FOOTBALLGAME2)); } } return; case FULLSTADIUM: city.stadiumCount++; if (((city.cityTime + xpos + ypos) % 8) == 0) { drawStadium(STADIUM); } return; case AIRPORT: city.airportCount++; if ((city.cityTime % 8) == 0) { repairZone(AIRPORT, 6); } if (powerOn) { if ((city.map[ypos-1][xpos+1] & LOMASK) == RADAR) { city.setTile(xpos+1,ypos-1, (char) (RADAR_ANIM + CONDBIT + BURNBIT) ); } } else { city.setTile(xpos+1,ypos-1,(char)(RADAR + CONDBIT + BURNBIT)); } if (powerOn) { doAirport(); } return; case PORT: city.seaportCount++; if ((city.cityTime % 16) == 0) { repairZone(PORT, 4); } if (powerOn && !city.hasSprite(SpriteKind.SHI)) { city.generateShip(); } return; default: // should not happen assert false; } } /** * Place hospital or church if needed. */ void makeHospital() { if (city.needHospital > 0) { zonePlop(HOSPITAL - 4); city.needHospital = 0; } //FIXME- should be 'else if' if (city.needChurch > 0) { zonePlop(CHURCH - 4); city.needChurch = 0; } } /** * Called when the current tile is the key tile of a * hospital or church. * @param powerOn indicates whether the building has power */ void doHospitalChurch(boolean powerOn) { if (cchr9 == HOSPITAL) { city.hospitalCount++; if (city.cityTime % 16 == 0) { repairZone(HOSPITAL, 3); } if (city.needHospital == -1) //too many hospitals { if (PRNG.nextInt(21) == 0) { zonePlop(RESBASE); } } } else if (cchr9 == CHURCH) { city.churchCount++; if (city.cityTime % 16 == 0) { repairZone(CHURCH, 3); } if (city.needChurch == -1) //too many churches { if (PRNG.nextInt(21) == 0) { zonePlop(RESBASE); } } } } /** * Regenerate the tiles that make up the zone, repairing from * fire, etc. * Only tiles that are not rubble, radioactive, flooded, or * on fire will be regenerated. * @param zoneCenter the tile value for the "center" tile of the zone * @param zoneSize integer (3-6) indicating the width/height of * the zone. */ void repairZone(char zoneCenter, int zoneSize) { // from the given center tile, figure out what the // northwest tile should be int zoneBase = zoneCenter - 1 - zoneSize; for (int y = 0; y < zoneSize; y++) { for (int x = 0; x < zoneSize; x++, zoneBase++) { int xx = xpos - 1 + x; int yy = ypos - 1 + y; if (city.testBounds(xx, yy)) { int thCh = city.map[yy][xx]; if (isZoneCenter(thCh)) { continue; } if (isAnimated(thCh)) continue; thCh &= LOMASK; if (thCh < RUBBLE || thCh >= ROADBASE) { //not rubble, radiactive, on fire or flooded city.setTile(xx,yy,(char) (zoneBase+CONDBIT+BURNBIT) ); } } } } } /** * Called when the current tile is the key tile of a commercial * zone. * @param powerOn indicates whether the building has power */ void doCommercial(boolean powerOn) { city.comZoneCount++; int tpop = commercialZonePop(cchr); city.comPop += tpop; int trafficGood; if (tpop > PRNG.nextInt(6)) { trafficGood = city.makeTraffic(xpos, ypos, ZoneType.COMMERCIAL); } else { trafficGood = 1; } if (trafficGood == -1) { int value = getCRValue(); doCommercialOut(tpop, value); return; } if (PRNG.nextInt(8) == 0) { int locValve = evalCommercial(trafficGood); int zscore = city.comValve + locValve; if (!powerOn) zscore = -500; if (trafficGood != 0 && zscore > -350 && zscore - 26380 > (PRNG.nextInt(0x10000)-0x8000)) { int value = getCRValue(); doCommercialIn(tpop, value); return; } if (zscore < 350 && zscore + 26380 < (PRNG.nextInt(0x10000)-0x8000)) { int value = getCRValue(); doCommercialOut(tpop, value); } } } /** * Animate tiles in an industrial zone. * Note: pollution is not accumulated here; it is instead * accumulated in ptlScan(), by adding up each tile's * pollution value. * * @param powerOn indicates whether the building has power */ void setSmoke(boolean powerOn) { int cchr9 = cchr & LOMASK; if (cchr9 < IZB) return; int z = ((cchr9 - IZB) / 8) % 8; if (Smoke.AniThis[z]) { int xx = xpos + Smoke.DX1[z]; int yy = ypos + Smoke.DY1[z]; if (city.testBounds(xx, yy)) { int t = city.map[yy][xx] & LOMASK; if (powerOn) { if (t == Smoke.AniTabC[z]) //expected non-animated tile { city.setTile(xx,yy,(char)(Smoke.ASCBIT | (SMOKEBASE + Smoke.AniTabA[z]))); } } else { if (t > Smoke.AniTabC[z]) { city.setTile(xx,yy,(char)(Smoke.REGBIT | Smoke.AniTabC[z])); } } } xx = xpos + Smoke.DX2[z]; yy = ypos + Smoke.DY2[z]; if (city.testBounds(xx, yy) && !(Smoke.DX1[z] == Smoke.DX2[z] && Smoke.DY1[z] == Smoke.DY2[z])) { int t = city.map[yy][xx] & LOMASK; if (powerOn) { if (t == Smoke.AniTabD[z]) { city.setTile(xx,yy,(char)(Smoke.ASCBIT | (SMOKEBASE + Smoke.AniTabB[z]))); } } else { if (t > Smoke.AniTabD[z]) { city.setTile(xx,yy,(char)(Smoke.REGBIT | Smoke.AniTabD[z])); } } } } } /** * Called when the current tile is the key tile of an * industrial zone. * @param powerOn indicates whether the building has power */ void doIndustrial(boolean powerOn) { city.indZoneCount++; setSmoke(powerOn); int tpop = industrialZonePop(cchr); city.indPop += tpop; int trafficGood; if (tpop > PRNG.nextInt(6)) { trafficGood = city.makeTraffic(xpos, ypos, ZoneType.INDUSTRIAL); } else { trafficGood = 1; } if (trafficGood == -1) { doIndustrialOut(tpop, PRNG.nextInt(2)); return; } if (PRNG.nextInt(8) == 0) { int locValve = evalIndustrial(trafficGood); int zscore = city.indValve + locValve; if (!powerOn) zscore = -500; if (zscore > -350 && zscore - 26380 > (PRNG.nextInt(0x10000)-0x8000)) { int value = PRNG.nextInt(2); doIndustrialIn(tpop, value); return; } if (zscore < 350 && zscore + 26380 < (PRNG.nextInt(0x10000)-0x8000)) { int value = PRNG.nextInt(2); doIndustrialOut(tpop, value); } } } /** * Called when the current tile is the key tile of a * residential zone. * @param powerOn indicates whether the building has power */ void doResidential(boolean powerOn) { city.resZoneCount++; int tpop; //population of this zone if (cchr9 == FREEZ) { tpop = city.doFreePop(xpos, ypos); } else { tpop = residentialZonePop(cchr); } city.resPop += tpop; int trafficGood; if (tpop > PRNG.nextInt(36)) { trafficGood = city.makeTraffic(xpos, ypos, ZoneType.RESIDENTIAL); } else { trafficGood = 1; } if (trafficGood == -1) { int value = getCRValue(); doResidentialOut(tpop, value); return; } if (cchr9 == FREEZ || PRNG.nextInt(8) == 0) { int locValve = evalResidential(trafficGood); int zscore = city.resValve + locValve; if (!powerOn) zscore = -500; if (zscore > -350 && zscore - 26380 > (PRNG.nextInt(0x10000)-0x8000)) { if (tpop == 0 && PRNG.nextInt(4) == 0) { makeHospital(); return; } int value = getCRValue(); doResidentialIn(tpop, value); return; } if (zscore < 350 && zscore + 26380 < (PRNG.nextInt(0x10000)-0x8000)) { int value = getCRValue(); doResidentialOut(tpop, value); } } } /** * Consider the value of building a single-lot house at certain * coordinates. * @return integer; positive number indicates good place for * house to go; zero or a negative number indicates a bad place. */ int evalLot(int x, int y) { // test for clear lot int tmp = city.getTile(x,y) & LOMASK; if (tmp != DIRT && (tmp < RESBASE || tmp > RESBASE+8)) { return -1; } int score = 1; final int [] DX = { 0, 1, 0, -1 }; final int [] DY = { -1, 0, 1, 0 }; for (int z = 0; z < 4; z++) { int xx = x + DX[z]; int yy = y + DY[z]; if (city.testBounds(xx, yy) && city.map[yy][xx] != DIRT && ((city.map[yy][xx] & LOMASK) <= LASTROAD)) //look for road { score++; } } return score; } /** * Build a single-lot house on the current residential zone. */ private void buildHouse(int value) { assert value >= 0 && value <= 3; final int [] ZeX = { 0, -1, 0, 1, -1, 1, -1, 0, 1 }; final int [] ZeY = { 0, -1, -1, -1, 0, 0, 1, 1, 1 }; int bestLoc = 0; int hscore = 0; for (int z = 1; z < 9; z++) { int xx = xpos + ZeX[z]; int yy = ypos + ZeY[z]; if (city.testBounds(xx, yy)) { int score = evalLot(xx, yy); if (score != 0) { if (score > hscore) { hscore = score; bestLoc = z; } if ((score == hscore) && PRNG.nextInt(8) == 0) { bestLoc = z; } } } } if (bestLoc != 0) { int xx = xpos + ZeX[bestLoc]; int yy = ypos + ZeY[bestLoc]; int houseNumber = value * 3 + PRNG.nextInt(3); assert houseNumber >= 0 && houseNumber < 12; assert city.testBounds(xx, yy); city.setTile(xx, yy, (char)(HOUSE + houseNumber + BLBNCNBIT)); } } private void doCommercialIn(int pop, int value) { int z = city.landValueMem[ypos/2][xpos/2] / 32; if (pop > z) return; if (pop < 5) { comPlop(pop, value); adjustROG(8); } } private void doIndustrialIn(int pop, int value) { if (pop < 4) { indPlop(pop, value); adjustROG(8); } } private void doResidentialIn(int pop, int value) { assert value >= 0 && value <= 3; int z = city.pollutionMem[ypos/2][xpos/2]; if (z > 128) return; int cchr9 = cchr & LOMASK; if (cchr9 == FREEZ) { if (pop < 8) { buildHouse(value); adjustROG(1); return; } if (city.getPopulationDensity(xpos, ypos) > 64) { residentialPlop(0, value); adjustROG(8); return; } return; } if (pop < 40) { residentialPlop(pop / 8 - 1, value); adjustROG(8); } } void comPlop(int density, int value) { int base = (value * 5 + density) * 9 + CZB - 4; zonePlop(base); } void indPlop(int density, int value) { int base = (value * 4 + density) * 9 + (IZB - 4); zonePlop(base); } void residentialPlop(int density, int value) { int base = (value * 4 + density) * 9 + RZB - 4; zonePlop(base); } private void doCommercialOut(int pop, int value) { if (pop > 1) { comPlop(pop-2, value); adjustROG(-8); } else if (pop == 1) { zonePlop(COMBASE); adjustROG(-8); } } private void doIndustrialOut(int pop, int value) { if (pop > 1) { indPlop(pop-2, value); adjustROG(-8); } else if (pop == 1) { zonePlop(INDCLR-4); adjustROG(-8); } } private void doResidentialOut(int pop, int value) { assert value >= 0 && value < 4; final char [] Brdr = { 0, 3, 6, 1, 4, 7, 2, 5, 8 }; if (pop == 0) return; if (pop > 16) { // downgrade to a lower-density full-size residential zone residentialPlop((pop-24) / 8, value); adjustROG(-8); return; } if (pop == 16) { // downgrade from full-size zone to 8 little houses city.setTile(xpos, ypos, (char)(FREEZ | BLBNCNBIT | ZONEBIT)); for (int x = xpos-1; x <= xpos+1; x++) { for (int y = ypos-1; y <= ypos+1; y++) { if (city.testBounds(x,y)) { if (!(x == xpos && y == ypos)) { // pick a random small house int houseNumber = value * 3 + PRNG.nextInt(3); city.setTile(x, y, (char) (HOUSE + houseNumber + BLBNCNBIT)); } } } } adjustROG(-8); return; } if (pop < 16) { // remove one little house adjustROG(-1); int z = 0; for (int x = xpos-1; x <= xpos+1; x++) { for (int y = ypos-1; y <= ypos+1; y++) { if (city.testBounds(x,y)) { int loc = city.map[y][x] & LOMASK; if (loc >= LHTHR && loc <= HHTHR) { //little house city.setTile(x, y, (char)(Brdr[z] + BLBNCNBIT + FREEZ - 4)); return; } } z++; } } } } /** * Evaluates the zone value of the current commercial zone location. * @return an integer between -3000 and 3000 * Same meaning as evalResidential. */ int evalCommercial(int traf) { if (traf < 0) return -3000; return city.comRate[ypos/8][xpos/8]; } /** * Evaluates the zone value of the current industrial zone location. * @return an integer between -3000 and 3000. * Same meaning as evalResidential. */ int evalIndustrial(int traf) { if (traf < 0) return -1000; else return 0; } /** * Evaluates the zone value of the current residential zone location. * @return an integer between -3000 and 3000. The higher the * number, the more likely the zone is to GROW; the lower the * number, the more likely the zone is to SHRINK. */ int evalResidential(int traf) { if (traf < 0) return -3000; int value = city.landValueMem[ypos/2][xpos/2]; value -= city.pollutionMem[ypos/2][xpos/2]; if (value < 0) value = 0; //cap at 0 else value *= 32; if (value > 6000) value = 6000; //cap at 6000 return value - 3000; } /** * Gets the land-value class (0-3) for the current * residential or commercial zone location. * @return integer from 0 to 3, 0 is the lowest-valued * zone, and 3 is the highest-valued zone. */ int getCRValue() { int lval = city.landValueMem[ypos/2][xpos/2]; lval -= city.pollutionMem[ypos/2][xpos/2]; if (lval < 30) return 0; if (lval < 80) return 1; if (lval < 150) return 2; return 3; } /** * Process the coal powerplant animation. * Note: pollution is not accumulated here; see ptlScan() * instead. */ void coalSmoke() { final int [] SmTb = { COALSMOKE1, COALSMOKE2, COALSMOKE3, COALSMOKE4 }; final int [] dx = { 1, 2, 1, 2 }; final int [] dy = { -1, -1, 0, 0 }; for (int z = 0; z < 4; z++) { int tile = city.getTile(xpos+dx[z], ypos+dy[z]) & LOMASK; if (tile >= COALBASE && tile < COALBASE + 4*4) { city.setTile(xpos + dx[z], ypos + dy[z], (char) (SmTb[z] | CONDBIT | PWRBIT | BURNBIT) ); } } } /** * Record a zone's population change to the rate-of-growth * map. * An adjustment of +/- 1 corresponds to one little house. * An adjustment of +/- 8 corresponds to a full-size zone. * * @param amount the positive or negative adjustment to record. */ void adjustROG(int amount) { city.rateOGMem[ypos/8][xpos/8] += 4*amount; } /** * Place tiles for a stadium (full or empty). * @param zoneCenter either STADIUM or FULLSTADIUM */ void drawStadium(int zoneCenter) { int zoneBase = zoneCenter - 1 - 4; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++, zoneBase++) { city.setTile(xpos - 1 + x, ypos - 1 + y, (char) (zoneBase | BNCNBIT | (x == 1 && y == 1 ? (ZONEBIT|PWRBIT) : 0))); } } } }