mirror of
https://github.com/tonytins/citylimits.git
synced 2025-03-15 12:21:22 +00:00
1723 lines
45 KiB
C++
1723 lines
45 KiB
C++
/* simulate.cpp
|
|
*
|
|
* Micropolis, Unix Version. This game was released for the Unix platform
|
|
* in or about 1990 and has been modified for inclusion in the One Laptop
|
|
* Per Child program. Copyright (C) 1989 - 2007 Electronic Arts Inc. If
|
|
* you need assistance with this program, you may contact:
|
|
* http://wiki.laptop.org/go/Micropolis or email micropolis@laptop.org.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details. You should have received a
|
|
* copy of the GNU General Public License along with this program. If
|
|
* not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* ADDITIONAL TERMS per GNU GPL Section 7
|
|
*
|
|
* No trademark or publicity rights are granted. This license does NOT
|
|
* give you any right, title or interest in the trademark SimCity or any
|
|
* other Electronic Arts trademark. You may not distribute any
|
|
* modification of this program using the trademark SimCity or claim any
|
|
* affliation or association with Electronic Arts Inc. or its employees.
|
|
*
|
|
* Any propagation or conveyance of this program must include this
|
|
* copyright notice and these terms.
|
|
*
|
|
* If you convey this program (or any modifications of it) and assume
|
|
* contractual liability for the program to recipients of it, you agree
|
|
* to indemnify Electronic Arts for any liability that those contractual
|
|
* assumptions impose on Electronic Arts.
|
|
*
|
|
* You may not misrepresent the origins of this program; modified
|
|
* versions of the program must be marked as such and not identified as
|
|
* the original program.
|
|
*
|
|
* This disclaimer supplements the one included in the General Public
|
|
* License. TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
|
|
* PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
|
|
* OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK. THE ENTIRE RISK OF
|
|
* SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU. ELECTRONIC ARTS
|
|
* DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
|
|
* INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
|
|
* RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
|
|
* USAGE, OR TRADE PRACTICE. ELECTRONIC ARTS DOES NOT WARRANT AGAINST
|
|
* INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
|
|
* MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
|
|
* UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
|
|
* WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
|
|
* CORRECTED. NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
|
|
* ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SOME
|
|
* JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
|
|
* WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
|
|
* CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
|
|
* NOT APPLY TO YOU.
|
|
*/
|
|
|
|
/**
|
|
* @file simulate.cpp
|
|
* @brief Handles the main simulation logic for Micropolis.
|
|
*
|
|
* This file contains the primary functions responsible for advancing
|
|
* the simulation state in the Micropolis game. It includes methods
|
|
* for handling various aspects of the game world, such as power
|
|
* generation, population growth, resource management, and disaster
|
|
* events. The simulation is divided into multiple phases, with each
|
|
* phase focusing on specific components of the game world.
|
|
*/
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
#include "micropolis.h"
|
|
#include "text.h"
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Constants
|
|
|
|
/**
|
|
* How often to perform the short term census.
|
|
* @todo Rename to CENSUS_MONTHLY_FREQUENCY or so?
|
|
*/
|
|
static const int CENSUS_FREQUENCY_10 = 4;
|
|
|
|
/**
|
|
* How often to perform the long term census.
|
|
* @todo Rename to CENSUS_YEARLY_FREQUENCY or so?
|
|
*/
|
|
static const int CENSUS_FREQUENCY_120 = CENSUS_FREQUENCY_10 * 12;
|
|
|
|
/**
|
|
* How often to collect taxes.
|
|
*/
|
|
static const int TAX_FREQUENCY = 48;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/* comefrom: doEditWindow scoreDoer doMapInFront graphDoer doNilEvent */
|
|
void Micropolis::simFrame()
|
|
{
|
|
|
|
if (simSpeed == 0) {
|
|
return;
|
|
}
|
|
|
|
if (++speedCycle > 1023) {
|
|
speedCycle = 0;
|
|
}
|
|
|
|
if (simSpeed == 1 && (speedCycle % 5) != 0) {
|
|
return;
|
|
}
|
|
|
|
if (simSpeed == 2 && (speedCycle % 3) != 0) {
|
|
return;
|
|
}
|
|
|
|
simulate();
|
|
}
|
|
|
|
|
|
/* comefrom: simFrame */
|
|
void Micropolis::simulate()
|
|
{
|
|
static const short speedPowerScan[3] =
|
|
{ 2, 4, 5 };
|
|
static const short SpeedPollutionTerrainLandValueScan[3] =
|
|
{ 2, 7, 17 };
|
|
static const short speedCrimeScan[3] =
|
|
{ 1, 8, 18 };
|
|
static const short speedPopulationDensityScan[3] =
|
|
{ 1, 9, 19 };
|
|
static const short speedFireAnalysis[3] =
|
|
{ 1, 10, 20 };
|
|
|
|
short speedIndex = clamp((short)(simSpeed - 1), (short)0, (short)2);
|
|
|
|
// The simulator has 16 different phases, which we cycle through
|
|
// according to phaseCycle, which is incremented and wrapped at
|
|
// the end of this switch.
|
|
|
|
if (initSimLoad) {
|
|
phaseCycle = 0;
|
|
} else {
|
|
phaseCycle &= 15;
|
|
}
|
|
|
|
switch (phaseCycle) {
|
|
|
|
case 0:
|
|
|
|
if (++simCycle > 1023) {
|
|
simCycle = 0; // This is cosmic!
|
|
}
|
|
|
|
if (doInitialEval) {
|
|
doInitialEval = false;
|
|
cityEvaluation();
|
|
}
|
|
|
|
cityTime++;
|
|
cityTaxAverage += cityTax;
|
|
|
|
if (!(simCycle & 1)) {
|
|
setValves();
|
|
}
|
|
|
|
clearCensus();
|
|
|
|
break;
|
|
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
|
|
// Scan 1/8 of the map for each of the 8 phases 1..8:
|
|
mapScan((phaseCycle - 1) * WORLD_W / 8, phaseCycle * WORLD_W / 8);
|
|
|
|
break;
|
|
|
|
case 9:
|
|
if (cityTime % CENSUS_FREQUENCY_10 == 0) {
|
|
take10Census();
|
|
}
|
|
|
|
if (cityTime % CENSUS_FREQUENCY_120 == 0) {
|
|
take120Census();
|
|
}
|
|
|
|
if (cityTime % TAX_FREQUENCY == 0) {
|
|
collectTax();
|
|
cityEvaluation();
|
|
}
|
|
|
|
break;
|
|
|
|
case 10:
|
|
|
|
if (!(simCycle % 5)) {
|
|
decRateOfGrowthMap();
|
|
}
|
|
|
|
decTrafficMap();
|
|
|
|
newMapFlags[MAP_TYPE_TRAFFIC_DENSITY] = 1;
|
|
newMapFlags[MAP_TYPE_ROAD] = 1;
|
|
newMapFlags[MAP_TYPE_ALL] = 1;
|
|
newMapFlags[MAP_TYPE_RES] = 1;
|
|
newMapFlags[MAP_TYPE_COM] = 1;
|
|
newMapFlags[MAP_TYPE_IND] = 1;
|
|
newMapFlags[MAP_TYPE_DYNAMIC] = 1;
|
|
|
|
sendMessages();
|
|
|
|
break;
|
|
|
|
case 11:
|
|
|
|
if ((simCycle % speedPowerScan[speedIndex]) == 0) {
|
|
doPowerScan();
|
|
newMapFlags[MAP_TYPE_POWER] = 1;
|
|
newPower = true; /* post-release change */
|
|
}
|
|
|
|
break;
|
|
|
|
case 12:
|
|
|
|
if ((simCycle % SpeedPollutionTerrainLandValueScan[speedIndex]) == 0) {
|
|
pollutionTerrainLandValueScan();
|
|
}
|
|
|
|
break;
|
|
|
|
case 13:
|
|
|
|
if ((simCycle % speedCrimeScan[speedIndex]) == 0) {
|
|
crimeScan();
|
|
}
|
|
|
|
break;
|
|
|
|
case 14:
|
|
|
|
if ((simCycle % speedPopulationDensityScan[speedIndex]) == 0) {
|
|
populationDensityScan();
|
|
}
|
|
|
|
break;
|
|
|
|
case 15:
|
|
|
|
if ((simCycle % speedFireAnalysis[speedIndex]) == 0) {
|
|
fireAnalysis();
|
|
}
|
|
|
|
doDisasters();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Go on the the next phase.
|
|
phaseCycle = (phaseCycle + 1) & 15;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialize simulation.
|
|
* @todo Create constants for initSimLoad.
|
|
*/
|
|
void Micropolis::doSimInit()
|
|
{
|
|
phaseCycle = 0;
|
|
simCycle = 0;
|
|
|
|
if (initSimLoad == 2) {
|
|
/* if new city */
|
|
initSimMemory();
|
|
}
|
|
|
|
if (initSimLoad == 1) {
|
|
/* if city just loaded */
|
|
simLoadInit();
|
|
}
|
|
|
|
setValves();
|
|
clearCensus();
|
|
mapScan(0, WORLD_W);
|
|
doPowerScan();
|
|
newPower = true; /* post rel */
|
|
pollutionTerrainLandValueScan();
|
|
crimeScan();
|
|
populationDensityScan();
|
|
fireAnalysis();
|
|
newMap = 1;
|
|
censusChanged = true;
|
|
totalPop = 1;
|
|
doInitialEval = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Copy bits from powerGridMap to the #PWRBIT in the map for all zones in the
|
|
* world.
|
|
*/
|
|
void Micropolis::doNilPower()
|
|
{
|
|
short x, y;
|
|
|
|
for (x = 0; x < WORLD_W; x++) {
|
|
for (y = 0; y < WORLD_H; y++) {
|
|
MapValue z = map[x][y];
|
|
if (z & ZONEBIT) {
|
|
setZonePower(Position(x, y));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** Decrease traffic memory */
|
|
void Micropolis::decTrafficMap()
|
|
{
|
|
/* tends to empty trafficDensityMap */
|
|
short x, y, z;
|
|
|
|
for (x = 0; x < WORLD_W; x += trafficDensityMap.MAP_BLOCKSIZE) {
|
|
for (y = 0; y < WORLD_H; y += trafficDensityMap.MAP_BLOCKSIZE) {
|
|
z = trafficDensityMap.worldGet(x, y);
|
|
if (z == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (z <= 24) {
|
|
trafficDensityMap.worldSet(x, y, 0);
|
|
continue;
|
|
}
|
|
|
|
if (z > 200) {
|
|
trafficDensityMap.worldSet(x, y, z - 34);
|
|
} else {
|
|
trafficDensityMap.worldSet(x, y, z - 24);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Decrease rate of grow.
|
|
* @todo Limiting rate should not be done here, but when we add a new value to
|
|
* it.
|
|
*/
|
|
void Micropolis::decRateOfGrowthMap()
|
|
{
|
|
/* tends to empty rateOfGrowthMap */
|
|
short x, y, z;
|
|
|
|
for (x = 0; x < rateOfGrowthMap.MAP_W; x++) {
|
|
for (y = 0; y < rateOfGrowthMap.MAP_H; y++) {
|
|
z = rateOfGrowthMap.get(x, y);
|
|
if (z == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (z > 0) {
|
|
z--;
|
|
z = clamp(z, (short)-200, (short)200);
|
|
rateOfGrowthMap.set(x, y, z);
|
|
continue;
|
|
}
|
|
|
|
if (z < 0) {
|
|
z++;
|
|
z = clamp(z, (short)-200, (short)200);
|
|
rateOfGrowthMap.set(x, y, z);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* comefrom: doSimInit */
|
|
void Micropolis::initSimMemory()
|
|
{
|
|
setCommonInits();
|
|
|
|
for (short x = 0; x < 240; x++) {
|
|
resHist[x] = 0;
|
|
comHist[x] = 0;
|
|
indHist[x] = 0;
|
|
moneyHist[x] = 128;
|
|
crimeHist[x] = 0;
|
|
pollutionHist[x] = 0;
|
|
}
|
|
|
|
crimeRamp = 0;
|
|
pollutionRamp = 0;
|
|
totalPop = 0;
|
|
resValve = 0;
|
|
comValve = 0;
|
|
indValve = 0;
|
|
resCap = false; // Do not block residential growth
|
|
comCap = false; // Do not block commercial growth
|
|
indCap = false; // Do not block industrial growth
|
|
|
|
externalMarket = 6.0;
|
|
disasterEvent = SC_NONE;
|
|
scoreType = SC_NONE;
|
|
|
|
/* This clears powermem */
|
|
powerStackPointer = 0;
|
|
doPowerScan();
|
|
newPower = true; /* post rel */
|
|
|
|
initSimLoad = 0;
|
|
}
|
|
|
|
|
|
/* comefrom: doSimInit */
|
|
void Micropolis::simLoadInit()
|
|
{
|
|
// Disaster delay table for each scenario
|
|
static const short disasterWaitTable[SC_COUNT] = {
|
|
0, // No scenario (free playing)
|
|
2, // Dullsville (boredom)
|
|
10, // San francisco (earth quake)
|
|
4 * 10, // Hamburg (fire bombs)
|
|
20, // Bern (traffic)
|
|
3, // Tokyo (scary monster)
|
|
5, // Detroit (crime)
|
|
5, // Boston (nuclear meltdown)
|
|
2 * 48, // Rio (flooding)
|
|
};
|
|
|
|
// Time to wait before score calculation for each scenario
|
|
static const short scoreWaitTable[SC_COUNT] = {
|
|
0, // No scenario (free playing)
|
|
30 * 48, // Dullsville (boredom)
|
|
5 * 48, // San francisco (earth quake)
|
|
5 * 48, // Hamburg (fire bombs)
|
|
10 * 48, // Bern (traffic)
|
|
5 * 48, // Tokyo (scary monster)
|
|
10 * 48, // Detroit (crime)
|
|
5 * 48, // Boston (nuclear meltdown)
|
|
10 * 48, // Rio (flooding)
|
|
};
|
|
|
|
externalMarket = (float)miscHist[1];
|
|
resPop = miscHist[2];
|
|
comPop = miscHist[3];
|
|
indPop = miscHist[4];
|
|
resValve = miscHist[5];
|
|
comValve = miscHist[6];
|
|
indValve = miscHist[7];
|
|
crimeRamp = miscHist[10];
|
|
pollutionRamp = miscHist[11];
|
|
landValueAverage = miscHist[12];
|
|
crimeAverage = miscHist[13];
|
|
pollutionAverage = miscHist[14];
|
|
gameLevel = (GameLevel)miscHist[15];
|
|
|
|
if (cityTime < 0) {
|
|
cityTime = 0;
|
|
}
|
|
|
|
if (!externalMarket) {
|
|
externalMarket = 4.0;
|
|
}
|
|
|
|
// Set game level
|
|
if (gameLevel > LEVEL_LAST || gameLevel < LEVEL_FIRST) {
|
|
gameLevel = LEVEL_FIRST;
|
|
}
|
|
setGameLevel(gameLevel);
|
|
|
|
setCommonInits();
|
|
|
|
// Load cityClass
|
|
cityClass = (CityClass)(miscHist[16]);
|
|
if (cityClass > CC_MEGALOPOLIS || cityClass < CC_VILLAGE) {
|
|
cityClass = CC_VILLAGE;
|
|
}
|
|
|
|
cityScore = miscHist[17];
|
|
if (cityScore > 999 || cityScore < 1) {
|
|
cityScore = 500;
|
|
}
|
|
|
|
resCap = false;
|
|
comCap = false;
|
|
indCap = false;
|
|
|
|
cityTaxAverage = (cityTime % 48) * 7; /* post */
|
|
|
|
// Set power map.
|
|
/// @todo What purpose does this serve? Weird...
|
|
powerGridMap.fill(1);
|
|
|
|
doNilPower();
|
|
|
|
roadEffect = MAX_ROAD_EFFECT;
|
|
policeEffect = MAX_POLICE_STATION_EFFECT;
|
|
fireEffect = MAX_FIRE_STATION_EFFECT;
|
|
initSimLoad = 0;
|
|
|
|
if (scenario >= SC_COUNT) {
|
|
scenario = SC_NONE;
|
|
}
|
|
|
|
if (scenario != SC_NONE) {
|
|
assert(LENGTH_OF(disasterWaitTable) == SC_COUNT);
|
|
assert(LENGTH_OF(scoreWaitTable) == SC_COUNT);
|
|
|
|
disasterEvent = scenario;
|
|
disasterWait = disasterWaitTable[disasterEvent];
|
|
scoreType = disasterEvent;
|
|
scoreWait = scoreWaitTable[disasterEvent];
|
|
|
|
doStartScenario(scenario);
|
|
} else {
|
|
disasterEvent = SC_NONE;
|
|
disasterWait = 0;
|
|
scoreType = SC_NONE;
|
|
scoreWait = 0;
|
|
}
|
|
|
|
doStartGame();
|
|
}
|
|
|
|
|
|
/* comefrom: initSimMemory simLoadInit */
|
|
void Micropolis::setCommonInits()
|
|
{
|
|
evalInit();
|
|
roadEffect = MAX_ROAD_EFFECT;
|
|
policeEffect = MAX_POLICE_STATION_EFFECT;
|
|
fireEffect = MAX_FIRE_STATION_EFFECT;
|
|
taxFlag = false;
|
|
taxFund = 0;
|
|
}
|
|
|
|
|
|
/* comefrom: simulate doSimInit */
|
|
void Micropolis::setValves()
|
|
{
|
|
/// @todo Break the tax table out into configurable parameters.
|
|
static const short taxTable[21] = {
|
|
200, 150, 120, 100, 80, 50, 30, 0, -10, -40, -100,
|
|
-150, -200, -250, -300, -350, -400, -450, -500, -550, -600,
|
|
};
|
|
static const float extMarketParamTable[3] = {
|
|
1.2f, 1.1f, 0.98f,
|
|
};
|
|
assert(LEVEL_COUNT == LENGTH_OF(extMarketParamTable));
|
|
|
|
/// @todo Make configurable parameters.
|
|
short resPopDenom = 8;
|
|
float birthRate = 0.02;
|
|
float laborBaseMax = 1.3;
|
|
float internalMarketDenom = 3.7;
|
|
float projectedIndPopMin = 5.0;
|
|
float resRatioDefault = 1.3;
|
|
float resRatioMax = 2;
|
|
float comRatioMax = 2;
|
|
float indRatioMax = 2;
|
|
short taxMax = 20;
|
|
float taxTableScale = 600;
|
|
|
|
/// @todo Break the interesting values out into public member
|
|
/// variables so the user interface can display them.
|
|
float employment, migration, births, laborBase, internalMarket;
|
|
float resRatio, comRatio, indRatio;
|
|
float normalizedResPop, projectedResPop, projectedComPop, projectedIndPop;
|
|
|
|
miscHist[1] = (short)externalMarket;
|
|
miscHist[2] = resPop;
|
|
miscHist[3] = comPop;
|
|
miscHist[4] = indPop;
|
|
miscHist[5] = resValve;
|
|
miscHist[6] = comValve;
|
|
miscHist[7] = indValve;
|
|
miscHist[10] = crimeRamp;
|
|
miscHist[11] = pollutionRamp;
|
|
miscHist[12] = landValueAverage;
|
|
miscHist[13] = crimeAverage;
|
|
miscHist[14] = pollutionAverage;
|
|
miscHist[15] = gameLevel;
|
|
miscHist[16] = (short)cityClass;
|
|
miscHist[17] = cityScore;
|
|
|
|
normalizedResPop = (float)resPop / (float)resPopDenom;
|
|
totalPopLast = totalPop;
|
|
totalPop = (short)(normalizedResPop + comPop + indPop);
|
|
|
|
if (resPop > 0) {
|
|
employment = (comHist[1] + indHist[1]) / normalizedResPop;
|
|
} else {
|
|
employment = 1;
|
|
}
|
|
|
|
migration = normalizedResPop * (employment - 1);
|
|
births = normalizedResPop * birthRate;
|
|
projectedResPop = normalizedResPop + migration + births; // Projected res pop.
|
|
|
|
// Compute laborBase
|
|
float temp = comHist[1] + indHist[1];
|
|
if (temp > 0.0) {
|
|
laborBase = (resHist[1] / temp);
|
|
} else {
|
|
laborBase = 1;
|
|
}
|
|
laborBase = clamp(laborBase, 0.0f, laborBaseMax);
|
|
|
|
internalMarket = (float)(normalizedResPop + comPop + indPop) / internalMarketDenom;
|
|
|
|
projectedComPop = internalMarket * laborBase;
|
|
|
|
assert(gameLevel >= LEVEL_FIRST && gameLevel <= LEVEL_LAST);
|
|
projectedIndPop = indPop * laborBase * extMarketParamTable[gameLevel];
|
|
projectedIndPop = max(projectedIndPop, projectedIndPopMin);
|
|
|
|
if (normalizedResPop > 0) {
|
|
resRatio = (float)projectedResPop / (float)normalizedResPop; // projected -vs- actual.
|
|
} else {
|
|
resRatio = resRatioDefault;
|
|
}
|
|
|
|
if (comPop > 0) {
|
|
comRatio = (float)projectedComPop / (float)comPop;
|
|
} else {
|
|
comRatio = (float)projectedComPop;
|
|
}
|
|
|
|
if (indPop > 0) {
|
|
indRatio = (float)projectedIndPop / (float)indPop;
|
|
} else {
|
|
indRatio = (float)projectedIndPop;
|
|
}
|
|
|
|
resRatio = min(resRatio, resRatioMax);
|
|
comRatio = min(comRatio, comRatioMax);
|
|
resRatio = min(indRatio, indRatioMax);
|
|
|
|
// Global tax and game level effects.
|
|
short z = min((short)(cityTax + gameLevel), taxMax);
|
|
resRatio = (resRatio - 1) * taxTableScale + taxTable[z];
|
|
comRatio = (comRatio - 1) * taxTableScale + taxTable[z];
|
|
indRatio = (indRatio - 1) * taxTableScale + taxTable[z];
|
|
|
|
// Ratios are velocity changes to valves.
|
|
resValve = clamp(resValve + (short)resRatio, -RES_VALVE_RANGE, RES_VALVE_RANGE);
|
|
comValve = clamp(comValve + (short)comRatio, -COM_VALVE_RANGE, COM_VALVE_RANGE);
|
|
indValve = clamp(indValve + (short)indRatio, -IND_VALVE_RANGE, IND_VALVE_RANGE);
|
|
|
|
if (resCap && resValve > 0) {
|
|
resValve = 0; // Need a stadium, so cap resValve.
|
|
}
|
|
|
|
if (comCap && comValve > 0) {
|
|
comValve = 0; // Need a airport, so cap comValve.
|
|
}
|
|
|
|
if (indCap && indValve > 0) {
|
|
indValve = 0; // Need an seaport, so cap indValve.
|
|
}
|
|
|
|
valveFlag = true;
|
|
}
|
|
|
|
|
|
/* comefrom: simulate doSimInit */
|
|
void Micropolis::clearCensus()
|
|
{
|
|
poweredZoneCount = 0;
|
|
unpoweredZoneCount = 0;
|
|
firePop = 0;
|
|
roadTotal = 0;
|
|
railTotal = 0;
|
|
resPop = 0;
|
|
comPop = 0;
|
|
indPop = 0;
|
|
resZonePop = 0;
|
|
comZonePop = 0;
|
|
indZonePop = 0;
|
|
hospitalPop = 0;
|
|
churchPop = 0;
|
|
policeStationPop = 0;
|
|
fireStationPop = 0;
|
|
stadiumPop = 0;
|
|
coalPowerPop = 0;
|
|
nuclearPowerPop = 0;
|
|
seaportPop = 0;
|
|
airportPop = 0;
|
|
powerStackPointer = 0; /* Reset before Mapscan */
|
|
|
|
fireStationMap.clear();
|
|
//fireStationEffectMap.clear(); // Added in rev293
|
|
policeStationMap.clear();
|
|
//policeStationEffectMap.clear(); // Added in rev293
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Take monthly snaphsot of all relevant data for the historic graphs.
|
|
* Also update variables that control building new churches and hospitals.
|
|
* @todo Rename to takeMonthlyCensus (or takeMonthlySnaphshot?).
|
|
* @todo A lot of this max stuff is also done in graph.cpp
|
|
*/
|
|
void Micropolis::take10Census()
|
|
{
|
|
// TODO: Make configurable parameters.
|
|
int resPopDenom = 8;
|
|
|
|
short x;
|
|
|
|
/* put census#s in Historical Graphs and scroll data */
|
|
resHist10Max = 0;
|
|
comHist10Max = 0;
|
|
indHist10Max = 0;
|
|
|
|
for (x = 118; x >= 0; x--) {
|
|
|
|
resHist10Max = max(resHist10Max, resHist[x]);
|
|
comHist10Max = max(comHist10Max, comHist[x]);
|
|
indHist10Max = max(indHist10Max, indHist[x]);
|
|
|
|
resHist[x + 1] = resHist[x];
|
|
comHist[x + 1] = comHist[x];
|
|
indHist[x + 1] = indHist[x];
|
|
crimeHist[x + 1] = crimeHist[x];
|
|
pollutionHist[x + 1] = pollutionHist[x];
|
|
moneyHist[x + 1] = moneyHist[x];
|
|
|
|
}
|
|
|
|
graph10Max = resHist10Max;
|
|
graph10Max = max(graph10Max, comHist10Max);
|
|
graph10Max = max(graph10Max, indHist10Max);
|
|
|
|
resHist[0] = resPop / resPopDenom;
|
|
comHist[0] = comPop;
|
|
indHist[0] = indPop;
|
|
|
|
crimeRamp += (crimeAverage - crimeRamp) / 4;
|
|
crimeHist[0] = min(crimeRamp, (short)255);
|
|
|
|
pollutionRamp += (pollutionAverage - pollutionRamp) / 4;
|
|
pollutionHist[0] = min(pollutionRamp, (short)255);
|
|
|
|
x = (cashFlow / 20) + 128; /* scale to 0..255 */
|
|
moneyHist[0] = clamp(x, (short)0, (short)255);
|
|
|
|
changeCensus();
|
|
|
|
short resPopScaled = resPop >> 8;
|
|
|
|
if (hospitalPop < resPopScaled) {
|
|
needHospital = 1;
|
|
}
|
|
|
|
if (hospitalPop > resPopScaled) {
|
|
needHospital = -1;
|
|
}
|
|
|
|
if (hospitalPop == resPopScaled) {
|
|
needHospital = 0;
|
|
}
|
|
|
|
int faithfulPop = resPopScaled + faith;
|
|
|
|
if (churchPop < faithfulPop) {
|
|
needChurch = 1;
|
|
}
|
|
|
|
if (churchPop > faithfulPop) {
|
|
needChurch = -1;
|
|
}
|
|
|
|
if (churchPop == faithfulPop) {
|
|
needChurch = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* comefrom: simulate */
|
|
void Micropolis::take120Census()
|
|
{
|
|
// TODO: Make configurable parameters.
|
|
int resPopDenom = 8;
|
|
|
|
/* Long Term Graphs */
|
|
short x;
|
|
|
|
resHist120Max = 0;
|
|
comHist120Max = 0;
|
|
indHist120Max = 0;
|
|
|
|
for (x = 238; x >= 120; x--) {
|
|
|
|
resHist120Max = max(resHist120Max, resHist[x]);
|
|
comHist120Max = max(comHist120Max, comHist[x]);
|
|
indHist120Max = max(indHist120Max, indHist[x]);
|
|
|
|
resHist[x + 1] = resHist[x];
|
|
comHist[x + 1] = comHist[x];
|
|
indHist[x + 1] = indHist[x];
|
|
crimeHist[x + 1] = crimeHist[x];
|
|
pollutionHist[x + 1] = pollutionHist[x];
|
|
moneyHist[x + 1] = moneyHist[x];
|
|
|
|
}
|
|
|
|
graph120Max = resHist120Max;
|
|
graph120Max = max(graph120Max, comHist120Max);
|
|
graph120Max = max(graph120Max, indHist120Max);
|
|
|
|
resHist[120] = resPop / resPopDenom;
|
|
comHist[120] = comPop;
|
|
indHist[120] = indPop;
|
|
crimeHist[120] = crimeHist[0] ;
|
|
pollutionHist[120] = pollutionHist[0];
|
|
moneyHist[120] = moneyHist[0];
|
|
changeCensus();
|
|
}
|
|
|
|
|
|
/** Collect taxes
|
|
* @bug Function seems to be doing different things depending on
|
|
* Micropolis::totalPop value. With an non-empty city it does fund
|
|
* calculations. For an empty city, it immediately sets effects of
|
|
* funding, which seems inconsistent at least, and may be wrong
|
|
* @bug If Micropolis::taxFlag is set, no variable is touched which seems
|
|
* non-robust at least
|
|
*/
|
|
void Micropolis::collectTax()
|
|
{
|
|
short z;
|
|
|
|
/**
|
|
* @todo Break out so the user interface can configure this.
|
|
*/
|
|
static const float RLevels[3] = { 0.7, 0.9, 1.2 };
|
|
static const float FLevels[3] = { 1.4, 1.2, 0.8 };
|
|
|
|
assert(LEVEL_COUNT == LENGTH_OF(RLevels));
|
|
assert(LEVEL_COUNT == LENGTH_OF(FLevels));
|
|
|
|
cashFlow = 0;
|
|
|
|
/**
|
|
* @todo Apparently taxFlag is never set to true in MicropolisEngine
|
|
* or the TCL code, so this always runs.
|
|
* @todo Check old Mac code to see if it's ever set, and why.
|
|
*/
|
|
|
|
if (!taxFlag) { // If the Tax Port is clear
|
|
|
|
/// @todo Do something with z? Check old Mac code to see if it's used.
|
|
z = cityTaxAverage / 48; // post release
|
|
|
|
cityTaxAverage = 0;
|
|
|
|
policeFund = (long)policeStationPop * 100;
|
|
fireFund = (long)fireStationPop * 100;
|
|
roadFund = (long)((roadTotal + (railTotal * 2)) * RLevels[gameLevel]);
|
|
taxFund = (long)((((Quad)totalPop * landValueAverage) / 120) * cityTax * FLevels[gameLevel]);
|
|
|
|
if (totalPop > 0) {
|
|
/* There are people to tax. */
|
|
cashFlow = (short)(taxFund - (policeFund + fireFund + roadFund));
|
|
doBudget();
|
|
} else {
|
|
/* Nobody lives here. */
|
|
roadEffect = MAX_ROAD_EFFECT;
|
|
policeEffect = MAX_POLICE_STATION_EFFECT;
|
|
fireEffect = MAX_FIRE_STATION_EFFECT;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Update effects of (possibly reduced) funding
|
|
*
|
|
* It updates effects with respect to roads, police, and fire.
|
|
* @note This function should probably not be used when #totalPop is
|
|
* clear (ie with an empty) city. See also bugs of #collectTax()
|
|
* @bug I think this should be called after loading a city, or any
|
|
* time anything it depends on changes.
|
|
*/
|
|
void Micropolis::updateFundEffects()
|
|
{
|
|
// Compute road effects of funding
|
|
roadEffect = MAX_ROAD_EFFECT;
|
|
if (roadFund > 0) {
|
|
// Multiply with funding fraction
|
|
roadEffect = (short)((float)roadEffect * (float)roadSpend / (float)roadFund);
|
|
}
|
|
|
|
// Compute police station effects of funding
|
|
policeEffect = MAX_POLICE_STATION_EFFECT;
|
|
if (policeFund > 0) {
|
|
// Multiply with funding fraction
|
|
policeEffect = (short)((float)policeEffect * (float)policeSpend / (float)policeFund);
|
|
}
|
|
|
|
// Compute fire station effects of funding
|
|
fireEffect = MAX_FIRE_STATION_EFFECT;
|
|
if (fireFund > 0) {
|
|
// Multiply with funding fraction
|
|
fireEffect = (short)((float)fireEffect * (float)fireSpend / (float)fireFund);
|
|
}
|
|
|
|
#if 0
|
|
printf("========== updateFundEffects road %d %d %d fire %d %d %d police %d %d %d\n",
|
|
(int)roadEffect, (int)roadSpend, (int)roadFund,
|
|
(int)fireEffect, (int)fireSpend, (int)fireFund,
|
|
(int)policeEffect, (int)policeSpend, (int)policeFund);
|
|
#endif
|
|
|
|
mustDrawBudget = 1;
|
|
}
|
|
|
|
|
|
/* comefrom: simulate doSimInit */
|
|
void Micropolis::mapScan(int x1, int x2)
|
|
{
|
|
short x, y;
|
|
|
|
for (x = x1; x < x2; x++) {
|
|
for (y = 0; y < WORLD_H; y++) {
|
|
|
|
MapValue mapVal = map[x][y];
|
|
if (mapVal == DIRT) {
|
|
continue;
|
|
}
|
|
|
|
MapTile tile = mapVal & LOMASK; /* Mask off status bits */
|
|
|
|
if (tile < FLOOD) {
|
|
continue;
|
|
}
|
|
|
|
// tile >= FLOOD
|
|
|
|
Position pos(x, y);
|
|
|
|
|
|
if (tile < ROADBASE) {
|
|
|
|
if (tile >= FIREBASE) {
|
|
firePop++;
|
|
if (!(getRandom16() & 3)) {
|
|
doFire(pos); /* 1 in 4 times */
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (tile < RADTILE) {
|
|
doFlood(pos);
|
|
} else {
|
|
doRadTile(pos);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (newPower && (mapVal & CONDBIT)) {
|
|
// Copy PWRBIT from powerGridMap
|
|
setZonePower(pos);
|
|
}
|
|
|
|
if (tile >= ROADBASE && tile < POWERBASE) {
|
|
doRoad(pos);
|
|
continue;
|
|
}
|
|
|
|
if (mapVal & ZONEBIT) { /* process Zones */
|
|
doZone(pos);
|
|
continue;
|
|
}
|
|
|
|
if (tile >= RAILBASE && tile < RESBASE) {
|
|
doRail(pos);
|
|
continue;
|
|
}
|
|
|
|
if (tile >= SOMETINYEXP && tile <= LASTTINYEXP) {
|
|
/* clear AniRubble */
|
|
map[x][y] = randomRubble();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle rail track.
|
|
* Generate a train, and handle road deteriorating effects.
|
|
* @param pos Position of the rail.
|
|
*/
|
|
void Micropolis::doRail(const Position &pos)
|
|
{
|
|
railTotal++;
|
|
|
|
generateTrain(pos.posX, pos.posY);
|
|
|
|
if (roadEffect < (15 * MAX_ROAD_EFFECT / 16)) {
|
|
|
|
// roadEffect < 15/16 of max road, enable deteriorating rail
|
|
if (!(getRandom16() & 511)) {
|
|
|
|
MapValue curValue = map[pos.posX][pos.posY];
|
|
if (!(curValue & CONDBIT)) {
|
|
|
|
// Otherwise the '(getRandom16() & 31)' makes no sense
|
|
assert(MAX_ROAD_EFFECT == 32);
|
|
if (roadEffect < (getRandom16() & 31)) {
|
|
MapTile tile = curValue & LOMASK;
|
|
if (tile < RAILBASE + 2) {
|
|
map[pos.posX][pos.posY] = RIVER;
|
|
} else {
|
|
map[pos.posX][pos.posY] = randomRubble();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle decay of radio-active tile
|
|
* @param pos Position of the radio-active tile.
|
|
*/
|
|
void Micropolis::doRadTile(const Position &pos)
|
|
{
|
|
if ((getRandom16() & 4095) == 0) {
|
|
map[pos.posX][pos.posY] = DIRT; /* Radioactive decay */
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle road tile.
|
|
* @param pos Position of the road.
|
|
*/
|
|
void Micropolis::doRoad(const Position &pos)
|
|
{
|
|
short tden, z;
|
|
static const short densityTable[3] = { ROADBASE, LTRFBASE, HTRFBASE };
|
|
|
|
roadTotal++;
|
|
|
|
MapValue mapValue = map[pos.posX][pos.posY];
|
|
MapTile tile = mapValue & LOMASK;
|
|
|
|
/* generateBus(pos.posX, pos.posY); */
|
|
|
|
if (roadEffect < (15 * MAX_ROAD_EFFECT / 16)) {
|
|
// roadEffect < 15/16 of max road, enable deteriorating road
|
|
if ((getRandom16() & 511) == 0) {
|
|
if (!(mapValue & CONDBIT)) {
|
|
assert(MAX_ROAD_EFFECT == 32); // Otherwise the '(getRandom16() & 31)' makes no sense
|
|
if (roadEffect < (getRandom16() & 31)) {
|
|
if ((tile & 15) < 2 || (tile & 15) == 15) {
|
|
map[pos.posX][pos.posY] = RIVER;
|
|
} else {
|
|
map[pos.posX][pos.posY] = randomRubble();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((mapValue & BURNBIT) == 0) { /* If Bridge */
|
|
roadTotal += 4; // Bridge counts as 4 road tiles
|
|
if (doBridge(Position(pos.posX, pos.posY), tile)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (tile < LTRFBASE) {
|
|
tden = 0;
|
|
} else if (tile < HTRFBASE) {
|
|
tden = 1;
|
|
} else {
|
|
roadTotal++; // Heavy traffic counts as 2 roads.
|
|
tden = 2;
|
|
}
|
|
|
|
short trafficDensity = trafficDensityMap.worldGet(pos.posX, pos.posY) >>6;
|
|
|
|
if (trafficDensity > 1) {
|
|
trafficDensity--;
|
|
}
|
|
|
|
if (tden != trafficDensity) { /* tden 0..2 */
|
|
z = ((tile - ROADBASE) & 15) + densityTable[trafficDensity];
|
|
z |= mapValue & (ALLBITS - ANIMBIT);
|
|
|
|
if (trafficDensity > 0) {
|
|
z |= ANIMBIT;
|
|
}
|
|
|
|
map[pos.posX][pos.posY] = z;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle a bridge.
|
|
* @param pos Position of the bridge.
|
|
* @param tile Tile value of the bridge.
|
|
* @return ???
|
|
*
|
|
* @todo What does this function return?
|
|
* @todo Discover the structure of all the magic constants.
|
|
*/
|
|
bool Micropolis::doBridge(const Position &pos, MapTile tile)
|
|
{
|
|
static short HDx[7] = { -2, 2, -2, -1, 0, 1, 2 };
|
|
static short HDy[7] = { -1, -1, 0, 0, 0, 0, 0 };
|
|
static short HBRTAB[7] = {
|
|
HBRDG1 | BULLBIT, HBRDG3 | BULLBIT, HBRDG0 | BULLBIT,
|
|
RIVER, BRWH | BULLBIT, RIVER, HBRDG2 | BULLBIT,
|
|
};
|
|
static short HBRTAB2[7] = {
|
|
RIVER, RIVER, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT,
|
|
HBRIDGE | BULLBIT, HBRIDGE | BULLBIT,
|
|
};
|
|
static short VDx[7] = { 0, 1, 0, 0, 0, 0, 1 };
|
|
static short VDy[7] = { -2, -2, -1, 0, 1, 2, 2 };
|
|
static short VBRTAB[7] = {
|
|
VBRDG0 | BULLBIT, VBRDG1 | BULLBIT, RIVER, BRWV | BULLBIT,
|
|
RIVER, VBRDG2 | BULLBIT, VBRDG3 | BULLBIT,
|
|
};
|
|
static short VBRTAB2[7] = {
|
|
VBRIDGE | BULLBIT, RIVER, VBRIDGE | BULLBIT, VBRIDGE | BULLBIT,
|
|
VBRIDGE | BULLBIT, VBRIDGE | BULLBIT, RIVER,
|
|
};
|
|
int z, x, y, MPtem;
|
|
|
|
if (tile == BRWV) { /* Vertical bridge close */
|
|
|
|
if ((!(getRandom16() & 3)) && getBoatDistance(pos) > 340) {
|
|
|
|
for (z = 0; z < 7; z++) { /* Close */
|
|
|
|
x = pos.posX + VDx[z];
|
|
y = pos.posY + VDy[z];
|
|
|
|
if (testBounds(x, y)) {
|
|
|
|
if ((map[x][y] & LOMASK) == (VBRTAB[z] & LOMASK)) {
|
|
map[x][y] = VBRTAB2[z];
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (tile == BRWH) { /* Horizontal bridge close */
|
|
|
|
if ((!(getRandom16() & 3)) && getBoatDistance(pos) > 340) {
|
|
|
|
for (z = 0; z < 7; z++) { /* Close */
|
|
|
|
x = pos.posX + HDx[z];
|
|
y = pos.posY + HDy[z];
|
|
|
|
if (testBounds(x, y)) {
|
|
|
|
if ((map[x][y] & LOMASK) == (HBRTAB[z] & LOMASK)) {
|
|
|
|
map[x][y] = HBRTAB2[z];
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (getBoatDistance(pos) < 300 || (!(getRandom16() & 7))) {
|
|
if (tile & 1) {
|
|
if (pos.posX < WORLD_W - 1) {
|
|
if (map[pos.posX + 1][pos.posY] == CHANNEL) { /* Vertical open */
|
|
|
|
for (z = 0; z < 7; z++) {
|
|
|
|
x = pos.posX + VDx[z];
|
|
y = pos.posY + VDy[z];
|
|
|
|
if (testBounds(x, y)) {
|
|
|
|
MPtem = map[x][y];
|
|
if (MPtem == CHANNEL || ((MPtem & 15) == (VBRTAB2[z] & 15))) {
|
|
map[x][y] = VBRTAB[z];
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
} else {
|
|
|
|
if (pos.posY > 0) {
|
|
if (map[pos.posX][pos.posY - 1] == CHANNEL) {
|
|
|
|
/* Horizontal open */
|
|
for (z = 0; z < 7; z++) {
|
|
|
|
x = pos.posX + HDx[z];
|
|
y = pos.posY + HDy[z];
|
|
|
|
if (testBounds(x, y)) {
|
|
|
|
MPtem = map[x][y];
|
|
if (((MPtem & 15) == (HBRTAB2[z] & 15)) || MPtem == CHANNEL) {
|
|
map[x][y] = HBRTAB[z];
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Compute distance to nearest boat from a given bridge.
|
|
* @param pos Position of bridge.
|
|
* @return Distance to nearest boat.
|
|
*/
|
|
int Micropolis::getBoatDistance(const Position &pos)
|
|
{
|
|
int sprDist;
|
|
SimSprite *sprite;
|
|
|
|
int dist = 99999;
|
|
int mx = pos.posX * 16 + 8;
|
|
int my = pos.posY * 16 + 8;
|
|
|
|
for (sprite = spriteList; sprite != NULL; sprite = sprite->next) {
|
|
if (sprite->type == SPRITE_SHIP && sprite->frame != 0) {
|
|
|
|
sprDist = absoluteValue(sprite->x + sprite->xHot - mx)
|
|
+ absoluteValue(sprite->y + sprite->yHot - my);
|
|
|
|
dist = min(dist, sprDist);
|
|
}
|
|
}
|
|
return dist;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle tile being on fire.
|
|
* @param pos Position of the fire.
|
|
*
|
|
* @todo Needs a notion of iterative neighbour tiles computing.
|
|
* @todo Use a getFromMap()-like function here.
|
|
* @todo Extract constants of fire station effectiveness from here.
|
|
*/
|
|
void Micropolis::doFire(const Position &pos)
|
|
{
|
|
static const short DX[4] = { -1, 0, 1, 0 };
|
|
static const short DY[4] = { 0, -1, 0, 1 };
|
|
|
|
// Try to set neighbouring tiles on fire as well
|
|
for (short z = 0; z < 4; z++) {
|
|
|
|
if ((getRandom16() & 7) == 0) {
|
|
|
|
short xTem = pos.posX + DX[z];
|
|
short yTem = pos.posY + DY[z];
|
|
|
|
if (testBounds(xTem, yTem)) {
|
|
|
|
MapValue c = map[xTem][yTem];
|
|
if (!(c & BURNBIT)) {
|
|
continue;
|
|
}
|
|
|
|
if (c & ZONEBIT) {
|
|
// Neighbour is a zone and burnable
|
|
fireZone(Position(xTem, yTem), c);
|
|
|
|
if ((c & LOMASK) > IZB) { /* Explode */
|
|
makeExplosionAt(xTem *16 + 8, yTem * 16 + 8);
|
|
}
|
|
}
|
|
|
|
map[xTem][yTem] = randomFire();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute likelyhood of fire running out of fuel
|
|
short rate = 10; // Likelyhood of extinguishing (bigger means less chance)
|
|
short z = fireStationEffectMap.worldGet(pos.posX, pos.posY);
|
|
|
|
if (z > 0) {
|
|
rate = 3;
|
|
if (z > 20) {
|
|
rate = 2;
|
|
}
|
|
if (z > 100) {
|
|
rate = 1;
|
|
}
|
|
}
|
|
|
|
// Decide whether to put out the fire.
|
|
if (getRandom(rate) == 0) {
|
|
map[pos.posX][pos.posY] = randomRubble();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle a zone on fire.
|
|
*
|
|
* Decreases rate of growth of the zone, and makes remaining tiles bulldozable.
|
|
*
|
|
* @param pos Position of the zone on fire.
|
|
* @param ch Character of the zone.
|
|
*/
|
|
void Micropolis::fireZone(const Position &pos, MapValue ch)
|
|
{
|
|
short XYmax;
|
|
|
|
int value = rateOfGrowthMap.worldGet(pos.posX, pos.posY);
|
|
value = clamp(value - 20, -200, 200);
|
|
rateOfGrowthMap.worldSet(pos.posX, pos.posY, value);
|
|
|
|
ch = ch & LOMASK;
|
|
|
|
if (ch < PORTBASE) {
|
|
XYmax = 2;
|
|
} else {
|
|
if (ch == AIRPORT) {
|
|
XYmax = 5;
|
|
} else {
|
|
XYmax = 4;
|
|
}
|
|
}
|
|
|
|
// Make remaining tiles of the zone bulldozable
|
|
for (short x = -1; x < XYmax; x++) {
|
|
for (short y = -1; y < XYmax; y++) {
|
|
|
|
short xTem = pos.posX + x;
|
|
short yTem = pos.posY + y;
|
|
|
|
if (!testBounds(xTem, yTem)) {
|
|
continue;
|
|
}
|
|
|
|
if ((MapTile)(map[xTem][yTem] & LOMASK) >= ROADBASE) {
|
|
/* post release */
|
|
map[xTem][yTem] |= BULLBIT;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Repair a zone at \a pos.
|
|
* @param pos Center-tile position of the zone.
|
|
* @param zCent Value of the center tile.
|
|
* @param zSize Size of the zone (in both directions).
|
|
*/
|
|
void Micropolis::repairZone(const Position &pos, MapTile zCent, short zSize)
|
|
{
|
|
MapTile tile = zCent - 2 - zSize;
|
|
|
|
// y and x loops one position shifted to compensate for the center-tile position.
|
|
for (short y = -1; y < zSize - 1; y++) {
|
|
for (short x = -1; x < zSize - 1; x++) {
|
|
|
|
int xx = pos.posX + x;
|
|
int yy = pos.posY + y;
|
|
|
|
tile++;
|
|
|
|
if (testBounds(xx, yy)) {
|
|
|
|
MapValue mapValue = map[xx][yy];
|
|
|
|
if (mapValue & ZONEBIT) {
|
|
continue;
|
|
}
|
|
|
|
if (mapValue & ANIMBIT) {
|
|
continue;
|
|
}
|
|
|
|
MapTile mapTile = mapValue & LOMASK;
|
|
|
|
if (mapTile < RUBBLE || mapTile >= ROADBASE) {
|
|
map[xx][yy] = tile | CONDBIT | BURNBIT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Update special zones.
|
|
* @param pos Position of the zone.
|
|
* @param powerOn Zone is powered.
|
|
*/
|
|
void Micropolis::doSpecialZone(const Position &pos, bool powerOn)
|
|
{
|
|
// Bigger numbers reduce chance of nuclear melt down
|
|
static const short meltdownTable[3] = { 30000, 20000, 10000 };
|
|
|
|
MapTile tile = map[pos.posX][pos.posY] & LOMASK;
|
|
|
|
switch (tile) {
|
|
|
|
case POWERPLANT:
|
|
|
|
coalPowerPop++;
|
|
|
|
if ((cityTime & 7) == 0) {
|
|
repairZone(pos, POWERPLANT, 4); /* post */
|
|
}
|
|
|
|
pushPowerStack(pos);
|
|
coalSmoke(pos);
|
|
|
|
return;
|
|
|
|
case NUCLEAR:
|
|
|
|
assert(LEVEL_COUNT == LENGTH_OF(meltdownTable));
|
|
|
|
if (enableDisasters && !getRandom(meltdownTable[gameLevel])) {
|
|
doMeltdown(pos);
|
|
return;
|
|
}
|
|
|
|
nuclearPowerPop++;
|
|
|
|
if ((cityTime & 7) == 0) {
|
|
repairZone(pos, NUCLEAR, 4); /* post */
|
|
}
|
|
|
|
pushPowerStack(pos);
|
|
|
|
return;
|
|
|
|
case FIRESTATION: {
|
|
|
|
int z;
|
|
|
|
fireStationPop++;
|
|
|
|
if (!(cityTime & 7)) {
|
|
repairZone(pos, FIRESTATION, 3); /* post */
|
|
}
|
|
|
|
if (powerOn) {
|
|
z = fireEffect; /* if powered get effect */
|
|
} else {
|
|
z = fireEffect / 2; /* from the funding ratio */
|
|
}
|
|
|
|
Position pos2(pos);
|
|
bool foundRoad = findPerimeterRoad(&pos2);
|
|
|
|
if (!foundRoad) {
|
|
z = z / 2; /* post FD's need roads */
|
|
}
|
|
|
|
int value = fireStationMap.worldGet(pos2.posX, pos2.posY);
|
|
value += z;
|
|
fireStationMap.worldSet(pos2.posX, pos2.posY, value);
|
|
|
|
return;
|
|
}
|
|
|
|
case POLICESTATION: {
|
|
|
|
int z;
|
|
|
|
policeStationPop++;
|
|
|
|
if (!(cityTime & 7)) {
|
|
repairZone(pos, POLICESTATION, 3); /* post */
|
|
}
|
|
|
|
if (powerOn) {
|
|
z = policeEffect;
|
|
} else {
|
|
z = policeEffect / 2;
|
|
}
|
|
|
|
Position pos2(pos);
|
|
bool foundRoad = findPerimeterRoad(&pos2);
|
|
|
|
if (!foundRoad) {
|
|
z = z / 2; /* post PD's need roads */
|
|
}
|
|
|
|
int value = policeStationMap.worldGet(pos2.posX, pos2.posY);
|
|
value += z;
|
|
policeStationMap.worldSet(pos2.posX, pos2.posY, value);
|
|
|
|
return;
|
|
}
|
|
|
|
case STADIUM: // Empty stadium
|
|
|
|
stadiumPop++;
|
|
|
|
if (!(cityTime & 15)) {
|
|
repairZone(pos, STADIUM, 4);
|
|
}
|
|
|
|
if (powerOn) {
|
|
// Every now and then, display a match
|
|
if (((cityTime + pos.posX + pos.posY) & 31) == 0) {
|
|
drawStadium(pos, FULLSTADIUM);
|
|
map[pos.posX + 1][pos.posY] = FOOTBALLGAME1 + ANIMBIT;
|
|
map[pos.posX + 1][pos.posY + 1] = FOOTBALLGAME2 + ANIMBIT;
|
|
}
|
|
}
|
|
|
|
return;
|
|
|
|
case FULLSTADIUM: // Full stadium
|
|
|
|
stadiumPop++;
|
|
|
|
if (((cityTime + pos.posX + pos.posY) & 7) == 0) {
|
|
// Stop the match
|
|
drawStadium(pos, STADIUM);
|
|
}
|
|
|
|
return;
|
|
|
|
case AIRPORT:
|
|
|
|
airportPop++;
|
|
|
|
if ((cityTime & 7) == 0) {
|
|
repairZone(pos, AIRPORT, 6);
|
|
}
|
|
|
|
// If powered, display a rotating radar
|
|
if (powerOn) {
|
|
if ((map[pos.posX + 1][pos.posY - 1] & LOMASK) == RADAR) {
|
|
map[pos.posX + 1][pos.posY - 1] = RADAR0 + ANIMBIT + CONDBIT + BURNBIT;
|
|
}
|
|
} else {
|
|
map[pos.posX + 1][pos.posY - 1] = RADAR + CONDBIT + BURNBIT;
|
|
}
|
|
|
|
if (powerOn) { // Handle the airport only if there is power
|
|
doAirport(pos);
|
|
}
|
|
|
|
return;
|
|
|
|
case PORT:
|
|
|
|
seaportPop++;
|
|
|
|
if ((cityTime & 15) == 0) {
|
|
repairZone(pos, PORT, 4);
|
|
}
|
|
|
|
// If port has power and there is no ship, generate one
|
|
if (powerOn && getSprite(SPRITE_SHIP) == NULL) {
|
|
generateShip();
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Draw a stadium (either full or empty).
|
|
* @param center Center tile position of the stadium.
|
|
* @param z Base tile value.
|
|
*
|
|
* @todo Merge with zonePlop()-like function.
|
|
*/
|
|
void Micropolis::drawStadium(const Position ¢er, MapTile z)
|
|
{
|
|
int x, y;
|
|
|
|
z = z - 5;
|
|
|
|
for (y = center.posY - 1; y < center.posY + 3; y++) {
|
|
for (x = center.posX - 1; x < center.posX + 3; x++) {
|
|
map[x][y] = z | BNCNBIT;
|
|
z++;
|
|
}
|
|
}
|
|
|
|
map[center.posX][center.posY] |= ZONEBIT | PWRBIT;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate a airplane or helicopter every now and then.
|
|
* @param pos Position of the airport to start from.
|
|
*/
|
|
void Micropolis::doAirport(const Position &pos)
|
|
{
|
|
if (getRandom(5) == 0) {
|
|
generatePlane(pos);
|
|
return;
|
|
}
|
|
|
|
if (getRandom(12) == 0) {
|
|
generateCopter(pos);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Draw coal smoke tiles around given position (of a coal power plant).
|
|
* @param pos Center tile of the coal power plant
|
|
*/
|
|
void Micropolis::coalSmoke(const Position &pos)
|
|
{
|
|
static const short SmTb[4] = {
|
|
COALSMOKE1, COALSMOKE2,
|
|
COALSMOKE3, COALSMOKE4,
|
|
};
|
|
static const short dx[4] = { 1, 2, 1, 2 };
|
|
static const short dy[4] = { -1, -1, 0, 0 };
|
|
|
|
for (short x = 0; x < 4; x++) {
|
|
map[pos.posX + dx[x]][pos.posY + dy[x]] =
|
|
SmTb[x] | ANIMBIT | CONDBIT | PWRBIT | BURNBIT;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Perform a nuclear melt-down disaster
|
|
* @param pos Position of the nuclear power plant that melts.
|
|
*/
|
|
void Micropolis::doMeltdown(const Position &pos)
|
|
{
|
|
makeExplosion(pos.posX - 1, pos.posY - 1);
|
|
makeExplosion(pos.posX - 1, pos.posY + 2);
|
|
makeExplosion(pos.posX + 2, pos.posY - 1);
|
|
makeExplosion(pos.posX + 2, pos.posY + 2);
|
|
|
|
// Whole power plant is at fire
|
|
for (int x = pos.posX - 1; x < pos.posX + 3; x++) {
|
|
for (int y = pos.posY - 1; y < pos.posY + 3; y++) {
|
|
map[x][y] = randomFire();
|
|
}
|
|
}
|
|
|
|
// Add lots of radiation tiles around the plant
|
|
for (int z = 0; z < 200; z++) {
|
|
|
|
int x = pos.posX - 20 + getRandom(40);
|
|
int y = pos.posY - 15 + getRandom(30);
|
|
|
|
if (!testBounds(x, y)) { // Ignore off-map positions
|
|
continue;
|
|
}
|
|
|
|
MapValue t = map[x][y];
|
|
|
|
if (t & ZONEBIT) {
|
|
continue; // Ignore zones
|
|
}
|
|
|
|
if ((t & BURNBIT) || t == DIRT) {
|
|
map[x][y] = RADTILE; // Make tile radio-active
|
|
}
|
|
|
|
}
|
|
|
|
// Report disaster to the user
|
|
sendMessage(MESSAGE_NUCLEAR_MELTDOWN, pos.posX, pos.posY, true, true);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|