Methapolis  0.27
 All Classes Namespaces Files Functions Variables Enumerator
Micropolis.java
Go to the documentation of this file.
1 // This file is part of MicropolisJ.
2 // Copyright (C) 2013 Jason Long
3 // Portions Copyright (C) 1989-2007 Electronic Arts Inc.
4 //
5 // MicropolisJ is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU GPLv3, with additional terms.
7 // See the README file, included in this distribution, for details.
8 
9 package micropolisj.engine;
10 
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;
46 
47 import java.io.DataInputStream;
48 import java.io.DataOutputStream;
49 import java.io.File;
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;
59 import java.util.Map;
60 import java.util.Random;
61 import java.util.Stack;
62 
63 import micropolisj.gui.MainWindow;
64 import micropolisj.util.Utilities;
65 
70 public class Micropolis {
71  static final Random DEFAULT_PRNG = new Random();
72 
73  Random PRNG;
74 
75  // full size arrays
79  protected char[][] map;
80  boolean[][] powerMap;
81 
82  // half-size arrays
83 
89  int[][] landValueMem;
90 
96  public int[][] pollutionMem;
97 
103  public int[][] crimeMem;
104 
109  public int[][] popDensity;
110 
117  int[][] trfDensity;
118 
119  // quarter-size arrays
120 
125  int[][] terrainMem;
126 
127  // eighth-size arrays
128 
134  public int[][] rateOGMem; // rate of growth?
135 
136  int[][] fireStMap; // firestations- cleared and rebuilt each sim cycle
137  public int[][] fireRate; // firestations reach- used for overlay graphs
138  int[][] policeMap; // police stations- cleared and rebuilt each sim cycle
139  public int[][] policeMapEffect;// police stations reach- used for overlay
140  // graphs
141  int[][] researchMap; // research labs- cleared and rebuilt each cycle
142  public int[][] researchMapEffect; // Map Overlay = unused atm
143  int researchDelayCharger = 0; // changing variable - should stay at 0
144  int researchDelay = 5; // amount of skipped ticks - higher number = fewer
145  // research points
146 
147  int factorIncome = 10; //factor - higher numbers increase income
148  int factorRoadFund = 5; //divisor - higher numbers decrease funding costs
149 
154  int[][] comRate;
155 
156  static final int DEFAULT_WIDTH = 120;
157  static final int DEFAULT_HEIGHT = 100;
158 
159  // TODO: make it a List/array to hold every players information individually
161 
162  public Stack<CityLocation> powerPlants = new Stack<CityLocation>();
163 
164 
165  public boolean autoBulldoze = true;
166  public boolean autoBudget = false;
168  public boolean noDisasters = true; // custom
169 
170  public int gameLevel;
171 
172  boolean autoGo;
173 
174  CityLocation meltdownLocation; // may be null
175  CityLocation crashLocation; // may be null
176 
177  int floodCnt; // number of turns the flood will last
178  int floodX;
179  int floodY;
180 
181  public int cityTime; // counts "weeks" (actually, 1/48'ths years)
182  int scycle; // same as cityTime, except mod 1024
183  int fcycle; // counts simulation steps (mod 1024)
184  int acycle; // animation cycle (mod 960)
185 
187 
188  protected List<Sprite> sprites = new ArrayList<Sprite>();
189 
190  static final int VALVERATE = 2;
191  public static final int CENSUSRATE = 4;
192  static final int TAXFREQ = 48;
193 
194  public void spend(int amount, PlayerInfo playerInfo) {
195  playerInfo.budget.totalFunds -= amount;
197  }
198 
199  public void spend(int amount) {
200  spend(amount, playerInfo);
201  }
202 
203  public Micropolis() {
204  this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
205  }
206 
207  /***
208  * Constructor(width, height)
209  */
210  public Micropolis(int width, int height) {
211  PRNG = DEFAULT_PRNG;
212  playerInfo = new PlayerInfo(this);
213  init(width, height);
214  initTileBehaviors();
215  }
216 
217  protected void init(int width, int height) {
218  map = new char[height][width];
219  powerMap = new boolean[height][width];
220 
221  int hX = (width + 1) / 2;
222  int hY = (height + 1) / 2;
223 
224  landValueMem = new int[hY][hX];
225  pollutionMem = new int[hY][hX];
226  crimeMem = new int[hY][hX];
227  popDensity = new int[hY][hX];
228  trfDensity = new int[hY][hX];
229 
230  int qX = (width + 3) / 4;
231  int qY = (height + 3) / 4;
232 
233  terrainMem = new int[qY][qX];
234 
235  int smX = (width + 7) / 8;
236  int smY = (height + 7) / 8;
237 
238  rateOGMem = new int[smY][smX];
239  fireStMap = new int[smY][smX];
240  policeMap = new int[smY][smX];
241  policeMapEffect = new int[smY][smX];
242  fireRate = new int[smY][smX];
243  comRate = new int[smY][smX];
244 
245  //TODO check for errors
246  playerInfo.centerMassX = hX;
247  playerInfo.centerMassY = hY;
248  }
249 
250  public List<Sprite> getSprites() {
251  return sprites;
252  }
253 
254  protected void fireCensusChanged() {
255  for(Listener l : listeners) {
256  l.censusChanged();
257  }
258  }
259 
260  protected void fireCityMessage(MicropolisMessage message, CityLocation loc) {
261  for(Listener l : listeners) {
262  l.cityMessage(message, loc);
263  }
264  }
265 
266  void fireCitySound(Sound sound, CityLocation loc) {
267  for(Listener l : listeners) {
268  l.citySound(sound, loc);
269  }
270  }
271 
272  protected void fireDemandChanged() {
273  for(Listener l : listeners) {
274  l.demandChanged();
275  }
276  }
277 
278  void fireEarthquakeStarted() {
279  for(EarthquakeListener l : earthquakeListeners) {
280  l.earthquakeStarted();
281  }
282  }
283 
284  protected void fireEvaluationChanged() {
285  for(Listener l : listeners) {
286  l.evaluationChanged();
287  }
288  }
289 
290  public void fireFundsChanged() {
291  for(Listener l : listeners) {
292  l.fundsChanged();
293  }
294  }
295 
296  protected void fireMapOverlayDataChanged(MapState overlayDataType) {
297  for(MapListener l : mapListeners) {
298  l.mapOverlayDataChanged(overlayDataType);
299  }
300  }
301 
302  void fireOptionsChanged() {
303  for(Listener l : listeners) {
304  l.optionsChanged();
305  }
306  }
307 
308  void fireSpriteMoved(Sprite sprite) {
309  for(MapListener l : mapListeners) {
310  l.spriteMoved(sprite);
311  }
312  }
313 
314  protected void fireTileChanged(int xpos, int ypos) {
315  for(MapListener l : mapListeners) {
316  l.tileChanged(xpos, ypos);
317  }
318  }
319 
320  protected void fireWholeMapChanged() {
321  for(MapListener l : mapListeners) {
322  l.wholeMapChanged();
323  }
324  }
325 
326  ArrayList<Listener> listeners = new ArrayList<Listener>();
327  ArrayList<MapListener> mapListeners = new ArrayList<MapListener>();
328  ArrayList<EarthquakeListener> earthquakeListeners = new ArrayList<EarthquakeListener>();
329 
330  public void addListener(Listener l) {
331  this.listeners.add(l);
332  }
333 
334  public void removeListener(Listener l) {
335  this.listeners.remove(l);
336  }
337 
339  this.earthquakeListeners.add(l);
340  }
341 
343  this.earthquakeListeners.remove(l);
344  }
345 
346  public void addMapListener(MapListener l) {
347  this.mapListeners.add(l);
348  }
349 
351  this.mapListeners.remove(l);
352  }
353 
354 
355 
356  int tempelListenerId = 0;
357  TempelListener [] tempelListeners = new TempelListener[50];
358  public void addTempelListener(TempelListener listener){
359  tempelListeners[tempelListenerId] = listener;
360  tempelListenerId++;
361  }
362  public void notifyCountdown(int countdown){
363  for(int id=0;id<tempelListenerId;id++){
364  tempelListeners[id].onCountdown(countdown);
365  }
366  }
367  public void notifyEnd(){
368  for(int id=0;id<tempelListenerId;id++){
369  tempelListeners[id].onEnd();
370  }
371  }
372 
373 
379  public interface Listener {
380  void cityMessage(MicropolisMessage message, CityLocation loc);
381 
382  void citySound(Sound sound, CityLocation loc);
383 
388  void censusChanged();
389 
394  void demandChanged();
395 
400  void evaluationChanged();
401 
405  void fundsChanged();
406 
411  void optionsChanged();
412  }
413 
414  public int getWidth() {
415  return map[0].length;
416  }
417 
418  public int getHeight() {
419  return map.length;
420  }
421 
422  public char getTile(int xpos, int ypos) {
423  return (char) (map[ypos][xpos] & LOMASK);
424  }
425 
426  public char getTileRaw(int xpos, int ypos) {
427  return map[ypos][xpos];
428  }
429 
430  boolean isTileDozeable(ToolEffectIfc eff) {
431  int myTile = eff.getTile(0, 0);
432  TileSpec ts = Tiles.get(myTile & TileConstants.LOMASK);
433  if(ts.canBulldoze) {
434  return true;
435  }
436 
437  if(ts.owner != null) {
438  // part of a zone; only bulldozeable if the owner tile is
439  // no longer intact.
440 
441  int baseTile = eff.getTile(-ts.ownerOffsetX, -ts.ownerOffsetY);
442  return !(ts.owner.tileNumber == baseTile);
443  }
444 
445  return false;
446  }
447 
448  boolean isTileDozeable(int xpos, int ypos) {
449  return isTileDozeable(new ToolEffect(this, xpos, ypos, getPlayerID()));
450  }
451 
452  public boolean isTilePowered(int xpos, int ypos) {
453  return (getTileRaw(xpos, ypos) & PWRBIT) == PWRBIT;
454  }
455 
456  public void setTile(int xpos, int ypos, char newTile) {
457  if(map[ypos][xpos] != newTile) {
458 // System.out.println("setting tile");
459 // System.out.println((int)newTile);
460 // System.out.println(Utilities.getPlayerID(newTile));
461  map[ypos][xpos] = newTile;
462  fireTileChanged(xpos, ypos);
463  }
464  }
465 
466  public void setTile(int xpos, int ypos, char newTile, int playerID) {
467  setTile(xpos, ypos, (char)Utilities.codePlayerID(newTile, playerID));
468  }
469 
470  final public boolean testBounds(int xpos, int ypos) {
471  return xpos >= 0 && xpos < getWidth() && ypos >= 0 && ypos < getHeight();
472  }
473 
474  final boolean hasPower(int x, int y) {
475  return powerMap[y][x];
476  }
477 
482  public boolean isBudgetTime() {
483  return (cityTime != 0 && (cityTime % TAXFREQ) == 0 && ((fcycle + 1) % 16) == 10 && ((acycle + 1) % 2) == 0);
484  }
485 
486  void step() {
487  fcycle = (fcycle + 1) % 1024;
488  simulate(fcycle % 16);
489  }
490 
491  void clearCensus() {
494  playerInfo.firePop = 0;
495  playerInfo.roadTotal = 0;
496  playerInfo.railTotal = 0;
497  playerInfo.resPop = 0;
498  playerInfo.comPop = 0;
499  playerInfo.indPop = 0;
509  playerInfo.coalCount = 0;
513  powerPlants.clear();
514 
515  for(int y = 0; y < fireStMap.length; y++) {
516  for(int x = 0; x < fireStMap[y].length; x++) {
517  fireStMap[y][x] = 0;
518  policeMap[y][x] = 0;
519  }
520  }
521  }
522 
523  void simulate(int mod16) {
524  final int band = getWidth() / 8;
525 
526  switch(mod16) {
527  case 0:
528  scycle = (scycle + 1) % 1024;
529  cityTime++;
530  if(scycle % 2 == 0) {
531  setValves();
532  }
533  clearCensus();
534  break;
535 
536  case 1:
537  mapScan(0 * band, 1 * band);
538  break;
539 
540  case 2:
541  mapScan(1 * band, 2 * band);
542  break;
543 
544  case 3:
545  mapScan(2 * band, 3 * band);
546  break;
547 
548  case 4:
549  mapScan(3 * band, 4 * band);
550  break;
551 
552  case 5:
553  mapScan(4 * band, 5 * band);
554  break;
555 
556  case 6:
557  mapScan(5 * band, 6 * band);
558  break;
559 
560  case 7:
561  mapScan(6 * band, 7 * band);
562  break;
563 
564  case 8:
565  mapScan(7 * band, getWidth());
566  break;
567 
568  case 9:
569  // add data to history object
570  if(cityTime % CENSUSRATE == 0) {
571  // every 4 weeks = 1 month
572  takeCensus();
573 
574  // once a year
575  if(cityTime % (CENSUSRATE * 12) == 0) {
576  takeCensus2();
577  }
578 
580  }
581 
582  // collect taxes every 16 steps (tare down at the end of the
583  // year won't make you have no taxes!)
584  collectTaxPartial();
585 
586  if(cityTime % TAXFREQ == 0) {
587  collectTax();
588  playerInfo.evaluation.cityEvaluation();
589  }
590  break;
591 
592  case 10:
593  if(scycle % 5 == 0) { // every ~10 weeks
594  // tends to empty self.rateOGMem
595  decROGMem();
596  }
597  decTrafficMem(); // tends to empty self.trfDensity (traffic)
598  fireMapOverlayDataChanged(MapState.TRAFFIC_OVERLAY); // TDMAP
599  fireMapOverlayDataChanged(MapState.TRANSPORT); // RDMAP
600  fireMapOverlayDataChanged(MapState.ALL); // ALMAP
601  fireMapOverlayDataChanged(MapState.RESIDENTIAL); // REMAP
602  fireMapOverlayDataChanged(MapState.COMMERCIAL); // COMAP
603  fireMapOverlayDataChanged(MapState.INDUSTRIAL); // INMAP
604  doMessages();
605  break;
606 
607  case 11:
608 // System.out.println("starting powerscan");
609  powerScan();
610 // System.out.println("dont with powerscan");
611  fireMapOverlayDataChanged(MapState.POWER_OVERLAY);
612  playerInfo.newPower = true;
613  break;
614 
615  case 12:
616  ptlScan();
617  break;
618 
619  case 13:
620  crimeScan();
621  break;
622 
623  case 14:
624  popDenScan();
625  break;
626 
627  case 15:
628  fireAnalysis();
629  doDisasters();
630  break;
631 
632  default:
633  throw new Error("unreachable");
634  }
635  }
636 
637  private int computePopDen(int x, int y, char tile) {
638  if(tile == RESCLR)
639  return doFreePop(x, y);
640 
641  if(tile < COMBASE)
642  return residentialZonePop(tile);
643 
644  if(tile < INDBASE)
645  return commercialZonePop(tile) * 8;
646 
647  if(tile < PORTBASE)
648  return industrialZonePop(tile) * 8;
649 
650  return 0;
651  }
652 
653  private static int[][] doSmooth(int[][] tem) {
654  final int h = tem.length;
655  final int w = tem[0].length;
656  int[][] tem2 = new int[h][w];
657 
658  for(int y = 0; y < h; y++) {
659  for(int x = 0; x < w; x++) {
660  int z = tem[y][x];
661  if(x > 0)
662  z += tem[y][x - 1];
663  if(x + 1 < w)
664  z += tem[y][x + 1];
665  if(y > 0)
666  z += tem[y - 1][x];
667  if(y + 1 < h)
668  z += tem[y + 1][x];
669  z /= 4;
670  if(z > 255)
671  z = 255;
672  tem2[y][x] = z;
673  }
674  }
675 
676  return tem2;
677  }
678 
679  public void calculateCenterMass() {
680  popDenScan();
681  }
682 
683  private void popDenScan() {
684  int xtot = 0;
685  int ytot = 0;
686  int zoneCount = 0;
687  int width = getWidth();
688  int height = getHeight();
689  int[][] tem = new int[(height + 1) / 2][(width + 1) / 2];
690 
691  for(int x = 0; x < width; x++) {
692  for(int y = 0; y < height; y++) {
693  char tile = getTile(x, y);
694  if(isZoneCenter(tile)) {
695  int den = computePopDen(x, y, tile) * 8;
696  if(den > 254)
697  den = 254;
698  tem[y / 2][x / 2] = den;
699  xtot += x;
700  ytot += y;
701  zoneCount++;
702  }
703  }
704  }
705 
706  // wtf?!
707  tem = doSmooth(tem);
708  tem = doSmooth(tem);
709  tem = doSmooth(tem);
710 
711  for(int x = 0; x < (width + 1) / 2; x++) {
712  for(int y = 0; y < (height + 1) / 2; y++) {
713  popDensity[y][x] = 2 * tem[y][x];
714  }
715  }
716 
717  distIntMarket(); // set ComRate
718 
719  // find center of mass for city
720  if(zoneCount != 0) {
721  playerInfo.centerMassX = xtot / zoneCount;
722  playerInfo.centerMassY = ytot / zoneCount;
723  }
724  else {
725  playerInfo.centerMassX = (width + 1) / 2;
726  playerInfo.centerMassY = (height + 1) / 2;
727  }
728 
731  }
732 
733  private void distIntMarket() {
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);
737  z /= 4;
738  z = 64 - z;
739  comRate[y][x] = z;
740  }
741  }
742  }
743 
744  // tends to empty RateOGMem[][]
745  private void decROGMem() {
746  for(int y = 0; y < rateOGMem.length; y++) {
747  for(int x = 0; x < rateOGMem[y].length; x++) {
748  int z = rateOGMem[y][x];
749  if(z == 0)
750  continue;
751 
752  if(z > 0) {
753  rateOGMem[y][x]--;
754  if(z > 200) {
755  rateOGMem[y][x] = 200; // prevent overflow?
756  }
757  continue;
758  }
759 
760  if(z < 0) {
761  rateOGMem[y][x]++;
762  if(z < -200) {
763  rateOGMem[y][x] = -200;
764  }
765  continue;
766  }
767  }
768  }
769  }
770 
771  // tends to empty trfDensity
772  private void decTrafficMem() {
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];
776  if(z != 0) {
777  if(z > 200)
778  trfDensity[y][x] = z - 34;
779  else if(z > 24)
780  trfDensity[y][x] = z - 24;
781  else
782  trfDensity[y][x] = 0;
783  }
784  }
785  }
786  }
787 
788  void crimeScan() {
789  policeMap = smoothFirePoliceMap(policeMap);
790  policeMap = smoothFirePoliceMap(policeMap);
791  policeMap = smoothFirePoliceMap(policeMap);
792 
793  for(int sy = 0; sy < policeMap.length; sy++) {
794  for(int sx = 0; sx < policeMap[sy].length; sx++) {
795  policeMapEffect[sy][sx] = policeMap[sy][sx];
796  }
797  }
798 
799  int count = 0;
800  int sum = 0;
801  int cmax = 0;
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];
805  if(val != 0) {
806  count++;
807  int z = 128 - val + popDensity[hy][hx];
808  z = Math.min(300, z);
809  z -= policeMap[hy / 4][hx / 4];
810  z = Math.min(250, z);
811  z = Math.max(0, z);
812  crimeMem[hy][hx] = z;
813 
814  sum += z;
815  if(z > cmax || (z == cmax && PRNG.nextInt(4) == 0)) {
816  cmax = z;
817  playerInfo.crimeMaxLocationX = hx * 2;
818  playerInfo.crimeMaxLocationY = hy * 2;
819  }
820  }
821  else {
822  crimeMem[hy][hx] = 0;
823  }
824  }
825  }
826 
827  if(count != 0)
828  playerInfo.crimeAverage = sum / count;
829  else
831 
832  fireMapOverlayDataChanged(MapState.POLICE_OVERLAY);
833  }
834 
835  void doDisasters() {
836  if(floodCnt > 0) {
837  floodCnt--;
838  }
839 
840  final int[] DisChance = {
841  480, 240, 60
842  };
843  if(noDisasters)
844  return;
845 
846  if(PRNG.nextInt(DisChance[gameLevel] + 1) != 0)
847  return;
848 
849  switch(PRNG.nextInt(9)) {
850  case 0:
851  case 1:
852  setFire();
853  break;
854  case 2:
855  case 3:
856  makeFlood();
857  break;
858  case 4:
859  break;
860  case 5:
861  makeTornado();
862  break;
863  case 6:
864  makeEarthquake();
865  break;
866  case 7:
867  case 8:
868  if(playerInfo.pollutionAverage > 60) {
869  makeMonster();
870  }
871  break;
872  }
873  }
874 
875  private int[][] smoothFirePoliceMap(int[][] omap) {
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++) {
881  int edge = 0;
882  if(sx > 0) {
883  edge += omap[sy][sx - 1];
884  }
885  if(sx + 1 < smX) {
886  edge += omap[sy][sx + 1];
887  }
888  if(sy > 0) {
889  edge += omap[sy - 1][sx];
890  }
891  if(sy + 1 < smY) {
892  edge += omap[sy + 1][sx];
893  }
894  edge = edge / 4 + omap[sy][sx];
895  nmap[sy][sx] = edge / 2;
896  }
897  }
898  return nmap;
899  }
900 
901  void fireAnalysis() {
902  fireStMap = smoothFirePoliceMap(fireStMap);
903  fireStMap = smoothFirePoliceMap(fireStMap);
904  fireStMap = smoothFirePoliceMap(fireStMap);
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];
908  }
909  }
910 
911  fireMapOverlayDataChanged(MapState.FIRE_OVERLAY);
912  }
913 
914  private boolean testForCond(CityLocation loc, int dir) {
915  int xsave = loc.x;
916  int ysave = loc.y;
917 
918  boolean rv = false;
919  if(movePowerLocation(loc, dir)) {
920  char t = getTile(loc.x, loc.y);
921  rv = (isConductive(t) && t != NUCLEAR && t != POWERPLANT && !hasPower(loc.x, loc.y));
922  }
923 
924  loc.x = xsave;
925  loc.y = ysave;
926  return rv;
927  }
928 
929  private boolean movePowerLocation(CityLocation loc, int dir) {
930  switch(dir) {
931  case 0:
932  if(loc.y > 0) {
933  loc.y--;
934  return true;
935  }
936  else
937  return false;
938  case 1:
939  if(loc.x + 1 < getWidth()) {
940  loc.x++;
941  return true;
942  }
943  else
944  return false;
945  case 2:
946  if(loc.y + 1 < getHeight()) {
947  loc.y++;
948  return true;
949  }
950  else
951  return false;
952  case 3:
953  if(loc.x > 0) {
954  loc.x--;
955  return true;
956  }
957  else
958  return false;
959  case 4:
960  return true;
961  }
962  return false;
963  }
964 
965  private int powerPlayer = 0;
966 
967  void powerScan() {
968  if(powerPlayer % getNumberOfPlayers() == 0) {
969  for(boolean[] bb : powerMap) {
970  Arrays.fill(bb, false);
971  }
972  }
973  powerPlayer++;
974  // clear powerMap
975 
976  //
977  // Note: brownouts are based on total number of power plants, not the number of powerplants connected to your city.
978  //
979 
980  int maxPower = playerInfo.coalCount * 700 + playerInfo.nuclearCount * 2000;
981  int numPower = 0;
982 
983  // This is kind of odd algorithm, but I haven't the heart to rewrite it
984  // at
985  // this time.
986 
987  while(!powerPlants.isEmpty()) {
988  CityLocation loc = powerPlants.pop();
989 
990  int aDir = 4;
991  int conNum;
992  do {
993  if(++numPower > maxPower) {
994  // trigger notification
995  sendMessage(MicropolisMessage.BROWNOUTS_REPORT);
996  return;
997  }
998  movePowerLocation(loc, aDir);
999  powerMap[loc.y][loc.x] = true;
1000 
1001  conNum = 0;
1002  int dir = 0;
1003  while(dir < 4 && conNum < 2) {
1004  if(testForCond(loc, dir)) {
1005  conNum++;
1006  aDir = dir;
1007  }
1008  else {
1009  }
1010  dir++;
1011  }
1012  if(conNum > 1) {
1013  powerPlants.add(new CityLocation(loc.x, loc.y));
1014  }
1015  }
1016  while(conNum != 0);
1017  }
1018  }
1019 
1026  void addTraffic(int mapX, int mapY, int traffic) {
1027  int z = trfDensity[mapY / 2][mapX / 2];
1028  z += traffic;
1029 
1030  // FIXME- why is this only capped to 240
1031  // by random chance. why is there no cap
1032  // the rest of the time?
1033 
1034  if(z > 240 && PRNG.nextInt(6) == 0) {
1035  z = 240;
1038 
1039  HelicopterSprite copter = (HelicopterSprite) getSprite(SpriteKind.COP);
1040  if(copter != null) {
1041  copter.destX = mapX;
1042  copter.destY = mapY;
1043  }
1044  }
1045 
1046  trfDensity[mapY / 2][mapX / 2] = z;
1047  }
1048 
1050  public int getFireStationCoverage(int xpos, int ypos) {
1051  return fireRate[ypos / 8][xpos / 8];
1052  }
1053 
1055  public int getLandValue(int xpos, int ypos) {
1056  if(testBounds(xpos, ypos)) {
1057  return landValueMem[ypos / 2][xpos / 2];
1058  }
1059  else {
1060  return 0;
1061  }
1062  }
1063 
1064  public int getTrafficDensity(int xpos, int ypos) {
1065  if(testBounds(xpos, ypos)) {
1066  return trfDensity[ypos / 2][xpos / 2];
1067  }
1068  else {
1069  return 0;
1070  }
1071  }
1072 
1073  // power, terrain, land value
1074  void ptlScan() {
1075  final int qX = (getWidth() + 3) / 4;
1076  final int qY = (getHeight() + 3) / 4;
1077  int[][] qtem = new int[qY][qX];
1078 
1079  int landValueTotal = 0;
1080  int landValueCount = 0;
1081 
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++) {
1087  int plevel = 0;
1088  int lvflag = 0;
1089  int zx = 2 * x;
1090  int zy = 2 * y;
1091 
1092  for(int mx = zx; mx <= zx + 1; mx++) {
1093  for(int my = zy; my <= zy + 1; my++) {
1094  int tile = getTile(mx, my);
1095  if(tile != DIRT) {
1096  if(tile < RUBBLE) // natural land features
1097  {
1098  // inc terrainMem
1099  qtem[y / 2][x / 2] += 15;
1100  continue;
1101  }
1102  plevel += getPollutionValue(tile, this);
1103  if(isConstructed(tile))
1104  lvflag++;
1105  }
1106  }
1107  }
1108 
1109  if(plevel < 0)
1110  plevel = 250; // ?
1111 
1112  if(plevel > 255)
1113  plevel = 255;
1114 
1115  tem[y][x] = plevel;
1116 
1117  if(lvflag != 0) {
1118  // land value equation
1119 
1120  int dis = 34 - getDisCC(x, y);
1121  dis *= 4;
1122  dis += terrainMem[y / 2][x / 2];
1123  dis -= pollutionMem[y][x];
1124  if(crimeMem[y][x] > 190) {
1125  dis -= 20;
1126  }
1127  if(dis > 250)
1128  dis = 250;
1129  if(dis < 1)
1130  dis = 1;
1131  landValueMem[y][x] = dis;
1132  landValueTotal += dis;
1133  landValueCount++;
1134  }
1135  else {
1136  landValueMem[y][x] = 0;
1137  }
1138  }
1139  }
1140 
1141  playerInfo.landValueAverage = landValueCount != 0 ? (landValueTotal / landValueCount) : 0;
1142 
1143  tem = doSmooth(tem);
1144  tem = doSmooth(tem);
1145 
1146  int pcount = 0;
1147  int ptotal = 0;
1148  int pmax = 0;
1149  for(int x = 0; x < HWLDX; x++) {
1150  for(int y = 0; y < HWLDY; y++) {
1151  int z = tem[y][x];
1152  pollutionMem[y][x] = z;
1153 
1154  if(z != 0) {
1155  pcount++;
1156  ptotal += z;
1157 
1158  if(z > pmax || (z == pmax && PRNG.nextInt(4) == 0)) {
1159  pmax = z;
1162  }
1163  }
1164  }
1165  }
1166 
1167  playerInfo.pollutionAverage = pcount != 0 ? (ptotal / pcount) : 0;
1168 
1169  terrainMem = smoothTerrain(qtem);
1170 
1171  fireMapOverlayDataChanged(MapState.POLLUTE_OVERLAY); // PLMAP
1172  fireMapOverlayDataChanged(MapState.LANDVALUE_OVERLAY); // LVMAP
1173  }
1174  //TODO remove pollution from PlayerInfo !?
1177  }
1178 
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
1181  };
1182 
1183  public static class History {
1184  public int cityTime;
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];
1191  int resMax;
1192  int comMax;
1193  int indMax;
1194  }
1195 
1196  public History history = new History();
1197 
1198  void setValves() {
1199  double normResPop = (double) playerInfo.resPop / 8.0;
1200  playerInfo.totalPop = (int) (normResPop + playerInfo.comPop + playerInfo.indPop);
1201 
1202  double employment;
1203  if(normResPop != 0.0) {
1204  employment = (history.com[1] + history.ind[1]) / normResPop;
1205  }
1206  else {
1207  employment = 1;
1208  }
1209 
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;
1214 
1215  double temp = (history.com[1] + history.ind[1]);
1216  double laborBase;
1217  if(temp != 0.0) {
1218  laborBase = history.res[1] / temp;
1219  }
1220  else {
1221  laborBase = 1;
1222  }
1223 
1224  // clamp laborBase to between 0.0 and 1.3
1225  laborBase = Math.max(0.0, Math.min(1.3, laborBase));
1226 
1227  double internalMarket = (double) (normResPop + playerInfo.comPop + playerInfo.indPop) / 3.7;
1228  double projectedComPop = internalMarket * laborBase;
1229 
1230  int z = gameLevel;
1231  temp = 1.0;
1232  switch(z) {
1233  case 0:
1234  temp = 1.2;
1235  break;
1236  case 1:
1237  temp = 1.1;
1238  break;
1239  case 2:
1240  temp = 0.98;
1241  break;
1242  }
1243 
1244  double projectedIndPop = playerInfo.indPop * laborBase * temp;
1245  if(projectedIndPop < 5.0)
1246  projectedIndPop = 5.0;
1247 
1248  double resRatio;
1249  if(normResPop != 0) {
1250  resRatio = (double) projectedResPop / (double) normResPop;
1251  }
1252  else {
1253  resRatio = 1.3;
1254  }
1255 
1256  double comRatio;
1257  if(playerInfo.comPop != 0)
1258  comRatio = (double) projectedComPop / (double) playerInfo.comPop;
1259  else
1260  comRatio = projectedComPop;
1261 
1262  double indRatio;
1263  if(playerInfo.indPop != 0)
1264  indRatio = (double) projectedIndPop / (double) playerInfo.indPop;
1265  else
1266  indRatio = projectedIndPop;
1267 
1268  if(resRatio > 2.0)
1269  resRatio = 2.0;
1270 
1271  if(comRatio > 2.0)
1272  comRatio = 2.0;
1273 
1274  if(indRatio > 2.0)
1275  indRatio = 2.0;
1276 
1277  int z2 = playerInfo.taxEffect + gameLevel;
1278  if(z2 > 20)
1279  z2 = 20;
1280 
1281  resRatio = (resRatio - 1) * 600 + TaxTable[z2];
1282  comRatio = (comRatio - 1) * 600 + TaxTable[z2];
1283  indRatio = (indRatio - 1) * 600 + TaxTable[z2];
1284 
1285  // ratios are velocity changes to valves
1286  playerInfo.resValve += (int) resRatio;
1287  playerInfo.comValve += (int) comRatio;
1288  playerInfo.indValve += (int) indRatio;
1289 
1290  if(playerInfo.resValve > 2000)
1291  playerInfo.resValve = 2000;
1292  else if(playerInfo.resValve < -2000)
1293  playerInfo.resValve = -2000;
1294 
1295  if(playerInfo.comValve > 1500)
1296  playerInfo.comValve = 1500;
1297  else if(playerInfo.comValve < -1500)
1298  playerInfo.comValve = -1500;
1299 
1300  if(playerInfo.indValve > 1500)
1301  playerInfo.indValve = 1500;
1302  else if(playerInfo.indValve < -1500)
1303  playerInfo.indValve = -1500;
1304 
1305  if(playerInfo.resCap && playerInfo.resValve > 0) {
1306  // residents demand stadium
1307  playerInfo.resValve = 0;
1308  }
1309 
1310  if(playerInfo.comCap && playerInfo.comValve > 0) {
1311  // commerce demands airport
1312  playerInfo.comValve = 0;
1313  }
1314 
1315  if(playerInfo.indCap && playerInfo.indValve > 0) {
1316  // industry demands sea port
1317  playerInfo.indValve = 0;
1318  }
1319 
1321  }
1322 
1323  int[][] smoothTerrain(int[][] qtem) {
1324  final int QWX = qtem[0].length;
1325  final int QWY = qtem.length;
1326 
1327  int[][] mem = new int[QWY][QWX];
1328  for(int y = 0; y < QWY; y++) {
1329  for(int x = 0; x < QWX; x++) {
1330  int z = 0;
1331  if(x > 0)
1332  z += qtem[y][x - 1];
1333  if(x + 1 < QWX)
1334  z += qtem[y][x + 1];
1335  if(y > 0)
1336  z += qtem[y - 1][x];
1337  if(y + 1 < QWY)
1338  z += qtem[y + 1][x];
1339  mem[y][x] = z / 4 + qtem[y][x] / 2;
1340  }
1341  }
1342  return mem;
1343  }
1344 
1345  // calculate manhatten distance (in 2-units) from center of city
1346  // capped at 32
1347  int getDisCC(int x, int y) {
1348  assert x >= 0 && x <= getWidth() / 2;
1349  assert y >= 0 && y <= getHeight() / 2;
1350 
1351  int xdis = Math.abs(x - playerInfo.centerMassX / 2);
1352  int ydis = Math.abs(y - playerInfo.centerMassY / 2);
1353 
1354  int z = (xdis + ydis);
1355  if(z > 32)
1356  return 32;
1357  else
1358  return z;
1359  }
1360 
1361  Map<String, TileBehavior> tileBehaviors;
1362 
1363  void initTileBehaviors() {
1364  HashMap<String, TileBehavior> bb;
1365  bb = new HashMap<String, TileBehavior>();
1366 
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));
1386 
1387  MapScanner tempelScanner = new MapScanner(this, MapScanner.B.TEMPEL);
1388  //tempelScanner.addTempelListener(listener);
1389  bb.put("TEMPEL", new MapScanner(this, MapScanner.B.TEMPEL));
1390 
1391 
1392  this.tileBehaviors = bb;
1393  }
1394 
1395  protected void mapScan(int x0, int x1) {
1396  for(int x = x0; x < x1; x++) {
1397  for(int y = 0; y < getHeight(); y++) {
1398  mapScanTile(x, y);
1399  }
1400  }
1401  }
1402 
1403  void mapScanTile(int xpos, int ypos) {
1404  int tile = getTile(xpos, ypos);
1405  String behaviorStr = getTileBehavior(tile);
1406  if(behaviorStr == null) {
1407  return; // nothing to do
1408  }
1409  else {
1410  TileBehavior b = tileBehaviors.get(behaviorStr);
1411  if(b != null) {
1412  b.processTile(xpos, ypos);
1413  }
1414  else {
1415  throw new Error("Unknown behavior: " + behaviorStr);
1416  }
1417  }
1418  }
1419 
1420  void generateShip() {
1421  int edge = PRNG.nextInt(4);
1422 
1423  if(edge == 0) {
1424  for(int x = 4; x < getWidth() - 2; x++) {
1425  if(getTile(x, 0) == CHANNEL) {
1426  makeShipAt(x, 0, ShipSprite.NORTH_EDGE);
1427  return;
1428  }
1429  }
1430  }
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);
1435  return;
1436  }
1437  }
1438  }
1439  else if(edge == 2) {
1440  for(int x = 4; x < getWidth() - 2; x++) {
1441  if(getTile(x, getHeight() - 1) == CHANNEL) {
1442  makeShipAt(x, getHeight() - 1, ShipSprite.SOUTH_EDGE);
1443  return;
1444  }
1445  }
1446  }
1447  else {
1448  for(int y = 1; y < getHeight() - 2; y++) {
1449  if(getTile(getWidth() - 1, y) == CHANNEL) {
1450  makeShipAt(getWidth() - 1, y, ShipSprite.EAST_EDGE);
1451  return;
1452  }
1453  }
1454  }
1455  }
1456 
1457  Sprite getSprite(SpriteKind kind) {
1458  for(Sprite s : sprites) {
1459  if(s.kind == kind)
1460  return s;
1461  }
1462  return null;
1463  }
1464 
1465  boolean hasSprite(SpriteKind kind) {
1466  return getSprite(kind) != null;
1467  }
1468 
1469  void makeShipAt(int xpos, int ypos, int edge) {
1470  assert !hasSprite(SpriteKind.SHI);
1471 
1472  sprites.add(new ShipSprite(this, xpos, ypos, edge));
1473  }
1474 
1475  void generateCopter(int xpos, int ypos) {
1476  if(!hasSprite(SpriteKind.COP)) {
1477  sprites.add(new HelicopterSprite(this, xpos, ypos));
1478  }
1479  }
1480 
1481  void generatePlane(int xpos, int ypos) {
1482  if(!hasSprite(SpriteKind.AIR)) {
1483  sprites.add(new AirplaneSprite(this, xpos, ypos));
1484  }
1485  }
1486 
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));
1490  }
1491  }
1492 
1493  // counts the population in a certain type of residential zone
1494  int doFreePop(int xpos, int ypos) {
1495  int count = 0;
1496 
1497  for(int x = xpos - 1; x <= xpos + 1; x++) {
1498  for(int y = ypos - 1; y <= ypos + 1; y++) {
1499  if(testBounds(x, y)) {
1500  char loc = getTile(x, y);
1501  if(loc >= LHTHR && loc <= HHTHR)
1502  count++;
1503  }
1504  }
1505  }
1506 
1507  return count;
1508  }
1509 
1510  // TODO: This duplicate of all the above methods is definitely necessary!!
1511  void generateRocket(int xpos, int ypos, int xDest, int yDest, int ownerID) {
1512  // if(!hasSprite(SpriteKind.ROC)) {
1513  RocketSprite rocket = new RocketSprite(this, xpos, ypos, xDest, yDest, ownerID);
1514  sprites.add(rocket);
1515  // }
1516  }
1517 
1518  // called every several cycles; this takes the census data collected in this
1519  // cycle and records it to the history
1520  //
1521  protected void takeCensus() {
1522  int resMax = 0; // residential
1523  int comMax = 0; // commercial
1524  int indMax = 0; // industrial
1525 
1526  for(int i = 118; i >= 0; i--) {
1527  // get max values
1528  if(history.res[i] > resMax)
1529  resMax = history.res[i];
1530  if(history.com[i] > comMax)
1531  comMax = history.res[i];
1532  if(history.ind[i] > indMax)
1533  indMax = history.ind[i];
1534 
1535  // shift every element right
1536  history.res[i + 1] = history.res[i];
1537  history.com[i + 1] = history.com[i];
1538  history.ind[i + 1] = history.ind[i];
1539  history.crime[i + 1] = history.crime[i];
1540  history.pollution[i + 1] = history.pollution[i];
1541  history.money[i + 1] = history.money[i];
1542  }
1543 
1544  // set max values
1545  history.resMax = resMax;
1546  history.comMax = comMax;
1547  history.indMax = indMax;
1548 
1549  // graph10max = Math.max(resMax, Math.max(comMax, indMax));
1550 
1551  // set newest value (front)
1552  history.res[0] = playerInfo.resPop / 8;
1553  history.com[0] = playerInfo.comPop;
1554  history.ind[0] = playerInfo.indPop;
1555 
1556  // apply quarter of change (-> smoothing?)
1558  history.crime[0] = Math.min(255, playerInfo.crimeRamp);
1559 
1561  history.pollution[0] = Math.min(255, playerInfo.polluteRamp);
1562 
1563  int moneyScaled = playerInfo.cashFlow / 20 + 128; // - 3
1564 
1565  if(moneyScaled < 0)
1566  moneyScaled = 0;
1567  if(moneyScaled > 255)
1568  moneyScaled = 255;
1569  history.money[0] = moneyScaled;
1570 
1571  history.cityTime = cityTime;
1572 
1573  // need of buildings?
1576  }
1577  else if(playerInfo.hospitalCount > playerInfo.resPop / 256) {
1578  playerInfo.needHospital = -1;
1579  }
1580  else {
1582  }
1583 
1584  if(playerInfo.churchCount < playerInfo.resPop / 256) {
1585  playerInfo.needChurch = 1;
1586  }
1587  else if(playerInfo.churchCount > playerInfo.resPop / 256) {
1588  playerInfo.needChurch = -1;
1589  }
1590  else {
1591  playerInfo.needChurch = 0;
1592  }
1593  }
1594 
1595  // record data for the whole year
1596  protected void takeCensus2() {
1597  // update long term graphs
1598  int resMax = 0;
1599  int comMax = 0;
1600  int indMax = 0;
1601 
1602  for(int i = 238; i >= 120; i--) {
1603  if(history.res[i] > resMax)
1604  resMax = history.res[i];
1605  if(history.com[i] > comMax)
1606  comMax = history.res[i];
1607  if(history.ind[i] > indMax)
1608  indMax = history.ind[i];
1609 
1610  history.res[i + 1] = history.res[i];
1611  history.com[i + 1] = history.com[i];
1612  history.ind[i + 1] = history.ind[i];
1613  history.crime[i + 1] = history.crime[i];
1614  history.pollution[i + 1] = history.pollution[i];
1615  history.money[i + 1] = history.money[i];
1616  }
1617 
1618  history.res[120] = playerInfo.resPop / 8;
1619  history.com[120] = playerInfo.comPop;
1620  history.ind[120] = playerInfo.indPop;
1621  history.crime[120] = history.crime[0];
1622  history.pollution[120] = history.pollution[0];
1623  history.money[120] = history.money[0];
1624  }
1625 
1629  static final double[] RLevels = {
1630  0.7, 0.9, 1.2
1631  };
1632 
1636  static final double[] FLevels = {
1637  1.4, 1.2, 0.8
1638  };
1639 
1640  public void addResearchPoints() {
1641  addResearchPoints(0);
1642  }
1643 
1644  public void addResearchPoints(int playerID) {
1645  // used in collectTaxPartial()
1646  // Charger accumulates to Delay.
1647  // playerInfo.researchEffect needs a divison as it is 1000 base.
1648  // div 100 => you need at least 1 research station at 10% fund to get a
1649  // point
1650  if (researchDelayCharger >= researchDelay) {
1651  getPlayerInfo(playerID).researchState.researchPoints += (getPlayerInfo(playerID).researchEffect * this.getCityPopulation(playerID)) / (100 * 3000);
1653  researchDelayCharger = 0;
1654  } else {
1655  researchDelayCharger++;
1656  }
1657  }
1658 
1659  void collectTaxPartial() {
1666 
1668 
1669  // update instance variables
1670  playerInfo.budget.taxFund += b.taxIncome;
1671  playerInfo.budget.roadFundEscrow -= b.roadFunded;
1672  playerInfo.budget.fireFundEscrow -= b.fireFunded;
1673  playerInfo.budget.policeFundEscrow -= b.policeFunded;
1674  playerInfo.budget.researchFundEscrow -= b.researchFunded;
1675 
1677  playerInfo.roadEffect = b.roadRequest != 0 ? (int) Math.floor(32.0 * (double) b.roadFunded / (double) b.roadRequest) : 32;
1678  playerInfo.policeEffect = b.policeRequest != 0 ? (int) Math.floor(1000.0 * (double) b.policeFunded
1679  / (double) b.policeRequest) : 1000;
1680  playerInfo.fireEffect = b.fireRequest != 0 ? (int) Math.floor(1000.0 * (double) b.fireFunded / (double) b.fireRequest)
1681  : 1000;
1682  playerInfo.researchEffect = b.researchRequest != 0 ? (int) Math.floor(1000.0 * (double) b.researchFunded
1683  / (double) b.researchRequest) : 1000;
1684  }
1685 
1686  public static class FinancialHistory {
1687  public int cityTime;
1688  public int totalFunds;
1689  public int taxIncome;
1690  public int operatingExpenses;
1691  }
1692 
1693  public ArrayList<FinancialHistory> financialHistory = new ArrayList<FinancialHistory>();
1694 
1695  void collectTax() {
1696  int revenue = playerInfo.budget.taxFund / TAXFREQ;
1697  int expenses = -(playerInfo.budget.roadFundEscrow + playerInfo.budget.fireFundEscrow + playerInfo.budget.policeFundEscrow + playerInfo.budget.researchFundEscrow)
1698  / TAXFREQ;
1699 
1700  FinancialHistory hist = new FinancialHistory();
1701  hist.cityTime = cityTime;
1702  hist.taxIncome = revenue;
1703  hist.operatingExpenses = expenses;
1704 
1705  playerInfo.cashFlow = revenue - expenses;
1707 
1708  hist.totalFunds = playerInfo.budget.totalFunds;
1709  financialHistory.add(0, hist);
1710 
1711  playerInfo.budget.taxFund = 0;
1712  playerInfo.budget.roadFundEscrow = 0;
1713  playerInfo.budget.fireFundEscrow = 0;
1714  playerInfo.budget.policeFundEscrow = 0;
1715  playerInfo.budget.researchFundEscrow = 0;
1716 
1717  }
1718 
1720  static final int POLICE_STATION_MAINTENANCE = 100;
1721 
1723  static final int FIRE_STATION_MAINTENANCE = 100;
1724 
1726  static final int RESEARCH_STATION_MAINTENANCE = 500;
1727 
1729  return generateBudget(playerInfo);
1730  }
1731 
1736  BudgetNumbers b = new BudgetNumbers();
1737  b.taxRate = Math.max(0, playerInfo.cityTax);
1738  b.roadPercent = Math.max(0.0, playerInfo.roadPercent);
1739  b.firePercent = Math.max(0.0, playerInfo.firePercent);
1740  b.policePercent = Math.max(0.0, playerInfo.policePercent);
1741  b.researchPercent = Math.max(0.0, playerInfo.researchPercent);
1742 
1743  b.previousBalance = playerInfo.budget.totalFunds;
1744  b.taxIncome = (int) Math.round(playerInfo.lastTotalPop * factorIncome * playerInfo.landValueAverage / 120 * b.taxRate * FLevels[gameLevel]); // custom
1745  assert b.taxIncome >= 0;
1746 
1747  b.roadRequest = (int) Math.round(((playerInfo.lastRoadTotal + playerInfo.lastRailTotal * 2) * RLevels[gameLevel])/factorRoadFund); // custom
1748  b.fireRequest = FIRE_STATION_MAINTENANCE * playerInfo.lastFireStationCount;
1749  b.policeRequest = POLICE_STATION_MAINTENANCE * playerInfo.lastPoliceCount;
1750  b.researchRequest = RESEARCH_STATION_MAINTENANCE * playerInfo.lastResearchCount;
1751 
1752  b.roadFunded = (int) Math.round(b.roadRequest * b.roadPercent);
1753  b.fireFunded = (int) Math.round(b.fireRequest * b.firePercent);
1754  b.policeFunded = (int) Math.round(b.policeRequest * b.policePercent);
1755  b.researchFunded = (int) Math.round(b.researchRequest * b.researchPercent);
1756 
1757  int yumDuckets = playerInfo.budget.totalFunds + b.taxIncome;
1758  assert yumDuckets >= 0;
1759 
1760  // custom: 'IF' Inserted HEAVY CHANGES
1761  if(yumDuckets >= b.roadFunded) {
1762  yumDuckets -= b.roadFunded;
1763  if(yumDuckets >= b.fireFunded) {
1764  yumDuckets -= b.fireFunded;
1765  if(yumDuckets >= b.policeFunded) {
1766  yumDuckets -= b.policeFunded;
1767 
1768  if(yumDuckets >= b.researchFunded) {
1769  yumDuckets -= b.researchFunded;
1770  }
1771  else {
1772  assert b.researchRequest != 0;
1773 
1774  b.researchFunded = yumDuckets;
1775  b.researchPercent = (double) b.researchFunded / (double) b.researchRequest;
1776  yumDuckets = 0;
1777  }
1778 
1779  }
1780  else {
1781  assert b.policeRequest != 0;
1782 
1783  b.policeFunded = yumDuckets;
1784  b.policePercent = (double) b.policeFunded / (double) b.policeRequest;
1785  b.researchFunded = 0;
1786  b.researchPercent = 0.0;
1787  yumDuckets = 0;
1788  }
1789 
1790  }
1791  else {
1792  assert b.fireRequest != 0;
1793 
1794  b.fireFunded = yumDuckets;
1795  b.firePercent = (double) b.fireFunded / (double) b.fireRequest;
1796  b.policeFunded = 0;
1797  b.policePercent = 0.0;
1798  b.researchFunded = 0;
1799  b.researchPercent = 0.0;
1800  yumDuckets = 0;
1801  }
1802  }
1803  else {
1804  assert b.roadRequest != 0;
1805 
1806  b.roadFunded = yumDuckets;
1807  b.roadPercent = (double) b.roadFunded / (double) b.roadRequest;
1808  b.fireFunded = 0;
1809  b.firePercent = 0.0;
1810  b.policeFunded = 0;
1811  b.policePercent = 0.0;
1812  b.researchFunded = 0;
1813  b.researchPercent = 0.0;
1814  }
1815 
1818 
1819  return b;
1820  }
1821 
1822  int getPopulationDensity(int xpos, int ypos) {
1823  return popDensity[ypos / 2][xpos / 2];
1824  }
1825 
1826  void doMeltdown(int xpos, int ypos) {
1827  meltdownLocation = new CityLocation(xpos, ypos);
1828 
1829  makeExplosion(xpos - 1, ypos - 1);
1830  makeExplosion(xpos - 1, ypos + 2);
1831  makeExplosion(xpos + 2, ypos - 1);
1832  makeExplosion(xpos + 2, ypos + 2);
1833 
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)));
1837  }
1838  }
1839 
1840  for(int z = 0; z < 200; z++) {
1841  int x = xpos - 20 + PRNG.nextInt(41);
1842  int y = ypos - 15 + PRNG.nextInt(31);
1843  if(!testBounds(x, y))
1844  continue;
1845 
1846  int t = map[y][x];
1847  if(isZoneCenter(t)) {
1848  continue;
1849  }
1850  if(isCombustible(t) || t == DIRT) {
1851  setTile(x, y, RADTILE);
1852  }
1853  }
1854 
1855  clearMes();
1856  sendMessageAt(MicropolisMessage.MELTDOWN_REPORT, xpos, ypos);
1857  }
1858 
1859  static final int[] MltdwnTab = {
1860  30000, 20000, 10000
1861  };
1862 
1863  void loadHistoryArray(int[] array, DataInputStream dis) throws IOException {
1864  for(int i = 0; i < 240; i++) {
1865  array[i] = dis.readShort();
1866  }
1867  }
1868 
1869  void writeHistoryArray(int[] array, DataOutputStream out) throws IOException {
1870  for(int i = 0; i < 240; i++) {
1871  out.writeShort(array[i]);
1872  }
1873  }
1874 
1875  // TODO Fix
1876  void loadMisc(DataInputStream dis) throws IOException {
1877  PlayerInfo playerInfo = getPlayerInfo();
1878  dis.readShort(); // [0]... ignored?
1879  dis.readShort(); // [1] externalMarket, ignored
1880  playerInfo.resPop = dis.readShort(); // [2-4] populations
1881  playerInfo.comPop = dis.readShort();
1882  playerInfo.indPop = dis.readShort();
1883  playerInfo.resValve = dis.readShort(); // [5-7] valves
1884  playerInfo.comValve = dis.readShort();
1885  playerInfo.indValve = dis.readShort();
1886  cityTime = dis.readInt(); // [8-9] city time
1887  playerInfo.crimeRamp = dis.readShort(); // [10]
1888  playerInfo.polluteRamp = dis.readShort();
1889  playerInfo.landValueAverage = dis.readShort(); // [12]
1890  playerInfo.crimeAverage = dis.readShort();
1891  playerInfo.pollutionAverage = dis.readShort(); // [14]
1892  gameLevel = dis.readShort();
1893  playerInfo.evaluation.cityClass = dis.readShort(); // [16]
1894  playerInfo.evaluation.cityScore = dis.readShort();
1895 
1896  for(int i = 18; i < 50; i++) {
1897  dis.readShort();
1898  }
1899 
1900  playerInfo.budget.totalFunds = dis.readInt(); // [50-51] total funds
1901  autoBulldoze = dis.readShort() != 0; // 52
1902  autoBudget = dis.readShort() != 0;
1903  autoGo = dis.readShort() != 0; // 54
1904  dis.readShort(); // userSoundOn (this setting not saved to game file in this edition of the game)
1905  playerInfo.cityTax = dis.readShort(); // 56
1906  playerInfo.taxEffect = playerInfo.cityTax;
1907  int simSpeedAsInt = dis.readShort();
1908  if(simSpeedAsInt >= 0 && simSpeedAsInt <= 4)
1909  simSpeed = Speed.values()[simSpeedAsInt];
1910  else
1911  simSpeed = Speed.NORMAL;
1912 
1913  // read playerInfo.budget numbers, convert them to percentages
1914  //
1915  int n = dis.readInt(); // 58,59... police percent
1916  playerInfo.policePercent = n / 65536.0;
1917  n = dis.readInt(); // 60,61... fire percent
1918  playerInfo.firePercent = n / 65536.0;
1919  n = dis.readInt(); // 62,63... road percent
1920  playerInfo.roadPercent = n / 65536.0;
1921 
1922  n = dis.readInt(); // custom 49... research percent
1923  playerInfo.researchPercent = n / 65536.0;
1924 
1925  // was 64 but 1 int was added so we skip that
1926  for(int i = 66; i < 120; i++) {
1927  dis.readShort();
1928  }
1929 
1930  if(cityTime < 0) {
1931  cityTime = 0;
1932  }
1933  if(playerInfo.cityTax < 0 || playerInfo.cityTax > 20) {
1934  playerInfo.cityTax = 7;
1935  }
1936  if(gameLevel < 0 || gameLevel > 2) {
1937  gameLevel = 0;
1938  }
1939  if(playerInfo.evaluation.cityClass < 0 || playerInfo.evaluation.cityClass > 5) {
1940  playerInfo.evaluation.cityClass = 0;
1941  }
1942  if(playerInfo.evaluation.cityScore < 1 || playerInfo.evaluation.cityScore > 999) {
1943  playerInfo.evaluation.cityScore = 500;
1944  }
1945 
1946  playerInfo.resCap = false;
1947  playerInfo.comCap = false;
1948  playerInfo.indCap = false;
1949  }
1950 
1951  // TODO Fix
1952  // save everything but the history and the map
1953  // THIS MUST BE LESS THAN 240 BYTES!
1954  void writeMisc(DataOutputStream out) throws IOException {
1955  PlayerInfo playerInfo = getPlayerInfo();
1956  out.writeShort(0);
1957  out.writeShort(0);
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);
1964  // 8 => 16 bytes
1965  out.writeInt(cityTime);
1966  out.writeShort(playerInfo.crimeRamp);
1967  out.writeShort(playerInfo.polluteRamp);
1968  // 12 => 24 bytes
1969  out.writeShort(playerInfo.landValueAverage);
1970  out.writeShort(playerInfo.crimeAverage);
1971  out.writeShort(playerInfo.pollutionAverage);
1972  out.writeShort(gameLevel);
1973  // 16 => 32 bytes
1974  out.writeShort(playerInfo.evaluation.cityClass);
1975  out.writeShort(playerInfo.evaluation.cityScore);
1976 
1977  // 18
1978  for(int i = 18; i < 50; i++) {
1979  out.writeShort(0);
1980  }
1981 
1982  // 48 => 96 bytes
1983 
1984  // 50 => 100 bytes
1985  out.writeInt(playerInfo.budget.totalFunds);
1986  out.writeShort(autoBulldoze ? 1 : 0);
1987  out.writeShort(autoBudget ? 1 : 0);
1988  // 54
1989  out.writeShort(autoGo ? 1 : 0);
1990  out.writeShort(1); // userSoundOn
1991  out.writeShort(playerInfo.cityTax);
1992  out.writeShort(simSpeed.ordinal());
1993 
1994  // 58/59, 60/62, 63/64, 65/66
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)); // custom: ACHTUNG HEAVY
1999 
2000  // 66 => 132 bytes
2001  for(int i = 66; i < 120; i++) {
2002  out.writeShort(0);
2003  }
2004 
2005  // 119 => 238 bytes
2006  }
2007 
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();
2012 // z &= ~(1024 | 2048 | 4096 | 8192 | 16384); // clear ZONEBIT,ANIMBIT,BULLBIT,BURNBIT,CONDBIT on import
2013  map[y][x] = (char) z;
2014  }
2015  }
2016  }
2017 
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++) {
2021  int z = map[y][x];
2022 // if(isConductive(z & LOMASK)) {
2023 // z |= 16384; // synthesize CONDBIT on export
2024 // }
2025 // if(isCombustible(z & LOMASK)) {
2026 // z |= 8192; // synthesize BURNBIT on export
2027 // }
2028 // if(isTileDozeable(x, y)) {
2029 // z |= 4096; // synthesize BULLBIT on export
2030 // }
2031 // if(isAnimated(z & LOMASK)) {
2032 // z |= 2048; // synthesize ANIMBIT on export
2033 // }
2034 // if(isZoneCenter(z & LOMASK)) {
2035 // z |= 1024; // synthesize ZONEBIT
2036 // }
2037  out.writeShort(z);
2038  }
2039  }
2040  }
2041 
2042  public void load(File filename) throws IOException {
2043  FileInputStream fis = new FileInputStream(filename);
2044  if(fis.getChannel().size() > 27120) {
2045  // some editions of the classic Simcity game
2046  // start the file off with a 128-byte header,
2047  // but otherwise use the same format as us,
2048  // so read in that 128-byte header and continue
2049  // as before.
2050  byte[] bbHeader = new byte[128];
2051  fis.read(bbHeader);
2052  }
2053  load(fis);
2054  }
2055 
2056  void checkPowerMap() {
2057  playerInfo.coalCount = 0;
2058  playerInfo.nuclearCount = 0;
2059 
2060  powerPlants.clear();
2061  for(int y = 0; y < map.length; y++) {
2062  for(int x = 0; x < map[y].length; x++) {
2063  int tile = getTile(x, y);
2064  if(tile == NUCLEAR) {
2065  playerInfo.nuclearCount++;
2066  powerPlants.add(new CityLocation(x, y));
2067  }
2068  else if(tile == POWERPLANT) {
2069  playerInfo.coalCount++;
2070  powerPlants.add(new CityLocation(x, y));
2071  }
2072  }
2073  }
2074 
2075  powerScan();
2076  playerInfo.newPower = true;
2077  }
2078 
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);
2087  loadMisc(dis);
2088  loadMap(dis);
2089  dis.close();
2090 
2091  checkPowerMap();
2092 
2095  fireFundsChanged();
2096  }
2097 
2098  // TODO save game
2099  public void save(File filename) throws IOException {
2100  save(new FileOutputStream(filename));
2101  }
2102 
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);
2111  writeMisc(out);
2112  writeMap(out);
2113  out.close();
2114  }
2115 
2116  public void toggleAutoBudget() {
2118  fireOptionsChanged();
2119  }
2120 
2121  public void toggleAutoBulldoze() {
2123  fireOptionsChanged();
2124  }
2125 
2126  public void toggleDisasters() {
2128  fireOptionsChanged();
2129  }
2130 
2131  public void setSpeed(Speed newSpeed) {
2132  simSpeed = newSpeed;
2133  fireOptionsChanged();
2134  }
2135 
2136  public void animate() {
2137  this.acycle = (this.acycle + 1) % 960;
2138  if(this.acycle % 2 == 0) {
2139  step();
2140  }
2141  if(this.acycle % getNumberOfPlayers() == 0) {
2142  moveObjects();
2143  animateTiles();
2144  }
2145  }
2146 
2147 
2148 
2149  public Sprite[] allSprites() {
2150  return sprites.toArray(new Sprite[0]);
2151  }
2152 
2153  protected void moveObjects() {
2154  for(Sprite sprite : allSprites()) {
2155  sprite.move();
2156  if(sprite.frame == 0) {
2157  sprites.remove(sprite);
2158  }
2159  }
2160  }
2161 
2162  protected void animateTiles() {
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];
2166  TileSpec spec = Tiles.get(tilevalue & LOMASK);
2167  if(spec != null && spec.animNext != null) {
2168  int flags = tilevalue & ALLBITS;
2169  setTile(x, y, (char) (spec.animNext.tileNumber | flags));
2170  }
2171  }
2172  }
2173  }
2174 
2175  public int getCityPopulation() {
2176  return getCityPopulation(getPlayerID());
2177  }
2178 
2179  public int getCityPopulation(int playerID) {
2180  return getPlayerInfo(playerID).lastCityPop;
2181  }
2182 
2183  void makeSound(int x, int y, Sound sound) {
2184  fireCitySound(sound, new CityLocation(x, y));
2185  }
2186 
2187  // TODO check if depends on playerInfo
2188  public void makeEarthquake() {
2189  makeSound(playerInfo.centerMassX, playerInfo.centerMassY, Sound.EXPLOSION_LOW);
2190  fireEarthquakeStarted();
2191 
2192  sendMessageAt(MicropolisMessage.EARTHQUAKE_REPORT, playerInfo.centerMassX, playerInfo.centerMassY);
2193  int time = PRNG.nextInt(701) + 300;
2194  for(int z = 0; z < time; z++) {
2195  int x = PRNG.nextInt(getWidth());
2196  int y = PRNG.nextInt(getHeight());
2197  assert testBounds(x, y);
2198 
2199  if(isVulnerable(getTile(x, y))) {
2200  if(PRNG.nextInt(4) != 0) {
2201  setTile(x, y, (char) (RUBBLE + PRNG.nextInt(4)));
2202  }
2203  else {
2204  setTile(x, y, (char) (FIRE + PRNG.nextInt(8)));
2205  }
2206  }
2207  }
2208  }
2209 
2210  void setFire() {
2211  int x = PRNG.nextInt(getWidth());
2212  int y = PRNG.nextInt(getHeight());
2213  int t = getTile(x, y);
2214 
2215  if(isArsonable(t)) {
2216  setTile(x, y, (char) (FIRE + PRNG.nextInt(8)));
2217  crashLocation = new CityLocation(x, y);
2218  sendMessageAt(MicropolisMessage.FIRE_REPORT, x, y);
2219  }
2220  }
2221 
2222  public void makeFire() {
2223  // forty attempts at finding place to start fire
2224  for(int t = 0; t < 40; t++) {
2225  int x = PRNG.nextInt(getWidth());
2226  int y = PRNG.nextInt(getHeight());
2227  int tile = getTile(x, y);
2228  if(!isZoneCenter(tile) && isCombustible(tile)) {
2229  if(tile > 21 && tile < LASTZONE) {
2230  setTile(x, y, (char) (FIRE + PRNG.nextInt(8)));
2231  sendMessageAt(MicropolisMessage.FIRE_REPORT, x, y);
2232  return;
2233  }
2234  }
2235  }
2236  }
2237 
2243  public boolean makeMeltdown() {
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) {
2248  candidates.add(new CityLocation(x, y));
2249  }
2250  }
2251  }
2252 
2253  if(candidates.isEmpty()) {
2254  // tell caller that no nuclear plants were found
2255  return false;
2256  }
2257 
2258  int i = PRNG.nextInt(candidates.size());
2259  CityLocation p = candidates.get(i);
2260  doMeltdown(p.x, p.y);
2261  return true;
2262  }
2263 
2264  public void makeMonster() {
2265  MonsterSprite monster = (MonsterSprite) getSprite(SpriteKind.GOD);
2266  if(monster != null) {
2267  // already have a monster in town
2268  monster.soundCount = 1;
2269  monster.count = 1000;
2270  monster.flag = false;
2271  monster.destX = playerInfo.pollutionMaxLocationX;
2272  monster.destY = playerInfo.pollutionMaxLocationY;
2273  return;
2274  }
2275 
2276  // try to find a suitable starting spot for monster
2277 
2278  for(int i = 0; i < 300; i++) {
2279  int x = PRNG.nextInt(getWidth() - 19) + 10;
2280  int y = PRNG.nextInt(getHeight() - 9) + 5;
2281  int t = getTile(x, y);
2282  if(t == RIVER) {
2283  makeMonsterAt(x, y);
2284  return;
2285  }
2286  }
2287 
2288  // no "nice" location found, just start in center of map then
2289  makeMonsterAt(getWidth() / 2, getHeight() / 2);
2290  }
2291 
2292  void makeMonsterAt(int xpos, int ypos) {
2293  assert !hasSprite(SpriteKind.GOD);
2294  sprites.add(new MonsterSprite(this, xpos, ypos));
2295  }
2296 
2297  public void makeTornado() {
2298  TornadoSprite tornado = (TornadoSprite) getSprite(SpriteKind.TOR);
2299  if(tornado != null) {
2300  // already have a tornado, so extend the length of the
2301  // existing tornado
2302  tornado.count = 200;
2303  return;
2304  }
2305 
2306  // FIXME- this is not exactly like the original code
2307  int xpos = PRNG.nextInt(getWidth() - 19) + 10;
2308  int ypos = PRNG.nextInt(getHeight() - 19) + 10;
2309  sprites.add(new TornadoSprite(this, xpos, ypos));
2310  sendMessageAt(MicropolisMessage.TORNADO_REPORT, xpos, ypos);
2311  }
2312 
2313  public void makeFlood() {
2314  final int[] DX = {
2315  0, 1, 0, -1
2316  };
2317  final int[] DY = {
2318  -1, 0, 1, 0
2319  };
2320 
2321  for(int z = 0; z < 300; z++) {
2322  int x = PRNG.nextInt(getWidth());
2323  int y = PRNG.nextInt(getHeight());
2324  int tile = getTile(x, y);
2325  if(isRiverEdge(tile)) {
2326  for(int t = 0; t < 4; t++) {
2327  int xx = x + DX[t];
2328  int yy = y + DY[t];
2329  if(testBounds(xx, yy)) {
2330  int c = map[yy][xx];
2331  if(isFloodable(c)) {
2332  setTile(xx, yy, FLOOD);
2333  floodCnt = 30;
2334  sendMessageAt(MicropolisMessage.FLOOD_REPORT, xx, yy);
2335  floodX = xx;
2336  floodY = yy;
2337  return;
2338  }
2339  }
2340  }
2341  }
2342  }
2343  }
2344 
2352  void killZone(int xpos, int ypos, int zoneTile) {
2353  rateOGMem[ypos / 8][xpos / 8] -= 20;
2354 
2355  assert isZoneCenter(zoneTile);
2356  CityDimension dim = getZoneSizeFor(zoneTile);
2357  assert dim != null;
2358  assert dim.width >= 3;
2359  assert dim.height >= 3;
2360 
2361  int zoneBase = (zoneTile & LOMASK) - 1 - dim.width;
2362 
2363  // this will take care of stopping smoke animations
2364  shutdownZone(xpos, ypos, dim);
2365  }
2366 
2374  void powerZone(int xpos, int ypos, CityDimension zoneSize) {
2375  assert zoneSize.width >= 3;
2376  assert zoneSize.height >= 3;
2377 
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;
2382  int tile = getTileRaw(x, y);
2383  TileSpec ts = Tiles.get(tile & LOMASK);
2384  if(ts != null && ts.onPower != null) {
2385  setTile(x, y, (char) (ts.onPower.tileNumber | (tile & ALLBITS)));
2386  }
2387  }
2388  }
2389  }
2390 
2398  void shutdownZone(int xpos, int ypos, CityDimension zoneSize) {
2399  assert zoneSize.width >= 3;
2400  assert zoneSize.height >= 3;
2401 
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;
2406  int tile = getTileRaw(x, y);
2407  TileSpec ts = Tiles.get(tile & LOMASK);
2408  if(ts != null && ts.onShutdown != null) {
2409  setTile(x, y, (char) (ts.onShutdown.tileNumber | (tile & ALLBITS)));
2410  }
2411  }
2412  }
2413  }
2414 
2415  void makeExplosion(int xpos, int ypos) {
2416  makeExplosionAt(xpos * 16 + 8, ypos * 16 + 8);
2417  }
2418 
2419  void makeExplosionAt(int x, int y) {
2420  sprites.add(new ExplosionSprite(this, x, y));
2421  }
2422 
2423  void makeGiantExplosionAt(int x, int y) {
2424  makeGiantExplosionAt(x, y, 2);
2425  }
2426 
2427  void makeGiantExplosionAt(int x, int y, int radius) {
2428  int off = 10;
2429  for(int dx = -radius; dx <= radius; dx++) {
2430  int nradius = radius - Math.abs(dx);
2431  for(int dy = -nradius; dy <= nradius; dy++) {
2432  // if not 1st explosion then make no sound for those
2433  if(dx == 0 || dy == 0) {
2434  sprites.add(new ExplosionSprite(this, x + dx * off, y + dy * off, false, true));
2435  }
2436  // 1st explosion => play sound
2437  else {
2438  sprites.add(new ExplosionSprite(this, x + dx * off, y + dy * off, false, false));
2439  }
2440  }
2441  }
2442  }
2443 
2448  void checkGrowth() {
2449  if(cityTime % 4 == 0) {
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;
2455  }
2456  else if(playerInfo.lastCityPop < 100000 && newPop >= 100000) {
2457  z = MicropolisMessage.POP_100K_REACHED;
2458  }
2459  else if(playerInfo.lastCityPop < 50000 && newPop >= 50000) {
2460  z = MicropolisMessage.POP_50K_REACHED;
2461  }
2462  else if(playerInfo.lastCityPop < 10000 && newPop >= 10000) {
2463  z = MicropolisMessage.POP_10K_REACHED;
2464  }
2465  else if(playerInfo.lastCityPop < 2000 && newPop >= 2000) {
2466  z = MicropolisMessage.POP_2K_REACHED;
2467  }
2468  if(z != null) {
2469  sendMessage(z);
2470  }
2471  }
2472  playerInfo.lastCityPop = newPop;
2473  }
2474  }
2475 
2476  void doMessages() {
2477  // MORE (scenario stuff)
2478 
2479  checkGrowth();
2480 
2481  int totalZoneCount = playerInfo.resZoneCount + playerInfo.comZoneCount + playerInfo.indZoneCount;
2482  int powerCount = playerInfo.nuclearCount + playerInfo.coalCount;
2483 
2484  int z = cityTime % 64;
2485  switch(z) {
2486  case 1:
2487  if(totalZoneCount / 4 >= playerInfo.resZoneCount) {
2488  sendMessage(MicropolisMessage.NEED_RES);
2489  }
2490  break;
2491  case 5:
2492  if(totalZoneCount / 8 >= playerInfo.comZoneCount) {
2493  sendMessage(MicropolisMessage.NEED_COM);
2494  }
2495  break;
2496  case 10:
2497  if(totalZoneCount / 8 >= playerInfo.indZoneCount) {
2498  sendMessage(MicropolisMessage.NEED_IND);
2499  }
2500  break;
2501  case 14:
2502  if(totalZoneCount > 10 && totalZoneCount * 2 > playerInfo.roadTotal) {
2503  sendMessage(MicropolisMessage.NEED_ROADS);
2504  }
2505  break;
2506  case 18:
2507  if(totalZoneCount > 50 && totalZoneCount > playerInfo.railTotal) {
2508  sendMessage(MicropolisMessage.NEED_RAILS);
2509  }
2510  break;
2511  case 22:
2512  if(totalZoneCount > 10 && powerCount == 0) {
2513  sendMessage(MicropolisMessage.NEED_POWER);
2514  }
2515  break;
2516  case 26:
2517  playerInfo.resCap = (playerInfo.resPop > 500 && playerInfo.stadiumCount == 0);
2518  if(playerInfo.resCap) {
2519  sendMessage(MicropolisMessage.NEED_STADIUM);
2520  }
2521  break;
2522  case 28:
2523  playerInfo.indCap = (playerInfo.indPop > 70 && playerInfo.seaportCount == 0);
2524  if(playerInfo.indCap) {
2525  sendMessage(MicropolisMessage.NEED_SEAPORT);
2526  }
2527  break;
2528  case 30:
2529  playerInfo.comCap = (playerInfo.comPop > 100 && playerInfo.airportCount == 0);
2530  if(playerInfo.comCap) {
2531  sendMessage(MicropolisMessage.NEED_AIRPORT);
2532  }
2533  break;
2534  case 32:
2535  int TM = playerInfo.poweredZoneCount + playerInfo.poweredZoneCount;
2536  if(TM != 0) {
2537  if((double) playerInfo.poweredZoneCount / (double) TM < 0.7) {
2538  sendMessage(MicropolisMessage.BLACKOUTS);
2539  }
2540  }
2541  break;
2542  case 35:
2543  if(playerInfo.pollutionAverage > 60) { // FIXME, consider
2544  // changing
2545  // threshold to 80
2546  sendMessage(MicropolisMessage.HIGH_POLLUTION);
2547  }
2548  break;
2549  case 42:
2550  if(playerInfo.crimeAverage > 100) {
2551  sendMessage(MicropolisMessage.HIGH_CRIME);
2552  }
2553  break;
2554  case 45:
2555  if(playerInfo.totalPop > 60 && playerInfo.fireStationCount == 0) {
2556  sendMessage(MicropolisMessage.NEED_FIRESTATION);
2557  }
2558  break;
2559  case 48:
2560  if(playerInfo.totalPop > 60 && playerInfo.policeCount == 0) {
2561  sendMessage(MicropolisMessage.NEED_POLICE);
2562  }
2563  break;
2564  case 49:
2565  if(playerInfo.totalPop > 150 && playerInfo.researchCount == 0) {
2566  sendMessage(MicropolisMessage.NEED_RESEARCH);
2567  }
2568  break;
2569  case 51:
2570  if(playerInfo.cityTax > 12) {
2571  sendMessage(MicropolisMessage.HIGH_TAXES);
2572  }
2573  break;
2574  case 54:
2575  if(playerInfo.roadEffect < 20 && playerInfo.roadTotal > 30) {
2576  sendMessage(MicropolisMessage.ROADS_NEED_FUNDING);
2577  }
2578  break;
2579  case 57:
2580  if(playerInfo.fireEffect < 700 && playerInfo.totalPop > 20) {
2581  sendMessage(MicropolisMessage.FIRE_NEED_FUNDING);
2582  }
2583  break;
2584  case 60:
2585  if(playerInfo.policeEffect < 700 && playerInfo.totalPop > 20) {
2586  sendMessage(MicropolisMessage.POLICE_NEED_FUNDING);
2587  }
2588  break;
2589  case 63:
2590  if(playerInfo.trafficAverage > 60) {
2591  sendMessage(MicropolisMessage.HIGH_TRAFFIC);
2592  }
2593  break;
2594  default:
2595  // nothing
2596  }
2597  }
2598 
2599  void clearMes() {
2600  // TODO.
2601  // What this does in the original code is clears the 'last message'
2602  // properties, ensuring that the next message will be delivered even
2603  // if it is a repeat.
2604  }
2605 
2606  void sendMessage(MicropolisMessage message) {
2607  fireCityMessage(message, null);
2608  }
2609 
2610  void sendMessageAt(MicropolisMessage message, int x, int y) {
2611  fireCityMessage(message, new CityLocation(x, y));
2612  }
2613 
2614  public ZoneStatus queryZoneStatus(int xpos, int ypos) {
2615  ZoneStatus zs = new ZoneStatus();
2616  zs.building = getDescriptionNumber(getTile(xpos, ypos));
2617 
2618  int z;
2619  z = (popDensity[ypos / 2][xpos / 2] / 64) % 4;
2620  zs.popDensity = z + 1;
2621 
2622  z = landValueMem[ypos / 2][xpos / 2];
2623  z = z < 30 ? 4 : z < 80 ? 5 : z < 150 ? 6 : 7;
2624  zs.landValue = z + 1;
2625 
2626  z = ((crimeMem[ypos / 2][xpos / 2] / 64) % 4) + 8;
2627  zs.crimeLevel = z + 1;
2628 
2629  z = Math.max(13, ((pollutionMem[ypos / 2][xpos / 2] / 64) % 4) + 12);
2630  zs.pollution = z + 1;
2631 
2632  z = rateOGMem[ypos / 8][xpos / 8];
2633  z = z < 0 ? 16 : z == 0 ? 17 : z <= 100 ? 18 : 19;
2634  zs.growthRate = z + 1;
2635 
2636  return zs;
2637  }
2638 
2639  public int getResValve() {
2640  return getPlayerInfo().resValve;
2641  }
2642 
2643  public int getComValve() {
2644  return getPlayerInfo().comValve;
2645  }
2646 
2647  public int getIndValve() {
2648  return getPlayerInfo().indValve;
2649  }
2650 
2651  public void setGameLevel(int newLevel) {
2652  assert GameLevel.isValid(newLevel);
2653 
2654  gameLevel = newLevel;
2655  fireOptionsChanged();
2656  }
2657 
2658  public void setFunds(int totalFunds) {
2659  getPlayerInfo().budget.totalFunds = totalFunds;
2660  }
2661 
2662  public int getPlayerID() {
2663  return 0;
2664  }
2665 
2666  public void setBudgetNumbers(int newTaxRate, double roadPct, double newRoadPct, double newPolicePct, double newFirePct, double newResearchPct) {
2667  getPlayerInfo().cityTax = newTaxRate;
2668  getPlayerInfo().roadPercent = newRoadPct;
2669  getPlayerInfo().policePercent = newPolicePct;
2670  getPlayerInfo().firePercent = newFirePct;
2671  getPlayerInfo().researchPercent = newResearchPct;
2672  }
2673 
2674  public int getNumberOfPlayers() {
2675  return 1;
2676  }
2677 
2679  return playerInfo;
2680  }
2681 
2682  public PlayerInfo getPlayerInfo(int playerID) {
2683  if(playerID == getPlayerID()) {
2684  return playerInfo;
2685  } else return null;
2686  }
2687 }