citylimits/core/message.cpp
2024-05-03 22:50:34 -04:00

477 lines
14 KiB
C++

/* message.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 message.cpp
* @brief Handles messaging and notifications within the Micropolis
* game.
*
* This file includes functions for sending messages to the player,
* based on various game events and scenario progress. It handles
* displaying messages related to city growth, requirements,
* disasters, and other significant events. It also deals with
* triggering sound effects associated with specific messages.
*/
////////////////////////////////////////////////////////////////////////
#include "micropolis.h"
#include "text.h"
////////////////////////////////////////////////////////////////////////
/** Check progress of the user, and send him messages about it. */
void Micropolis::sendMessages()
{
short PowerPop;
float TM;
// Running a scenario, and waiting it to 'end' so we can give a score
if (scenario > SC_NONE && scoreType > SC_NONE && scoreWait > 0) {
scoreWait--;
if (scoreWait == 0) {
doScenarioScore(scoreType);
}
}
checkGrowth();
totalZonePop = resZonePop + comZonePop + indZonePop;
PowerPop = nuclearPowerPop + coalPowerPop;
switch (cityTime & 63) {
case 1:
if (totalZonePop / 4 >= resZonePop) {
sendMessage(MESSAGE_NEED_MORE_RESIDENTIAL);
}
break;
case 5:
if (totalZonePop / 8 >= comZonePop) {
sendMessage(MESSAGE_NEED_MORE_COMMERCIAL);
}
break;
case 10:
if (totalZonePop / 8 >= indZonePop) {
sendMessage(MESSAGE_NEED_MORE_INDUSTRIAL);
}
break;
case 14:
if (totalZonePop > 10 && totalZonePop * 2 > roadTotal) {
sendMessage(MESSAGE_NEED_MORE_ROADS);
}
break;
case 18:
if (totalZonePop > 50 && totalZonePop > railTotal) {
sendMessage(MESSAGE_NEED_MORE_RAILS);
}
break;
case 22:
if (totalZonePop > 10 && PowerPop == 0) {
sendMessage(MESSAGE_NEED_ELECTRICITY);
}
break;
case 26:
if (resPop > 500 && stadiumPop == 0) {
sendMessage(MESSAGE_NEED_STADIUM);
resCap = true;
} else {
resCap = false;
}
break;
case 28:
if (indPop > 70 && seaportPop == 0) {
sendMessage(MESSAGE_NEED_SEAPORT);
indCap = true;
} else {
indCap = false;
}
break;
case 30:
if (comPop > 100 && airportPop == 0) {
sendMessage(MESSAGE_NEED_AIRPORT);
comCap = true;
} else {
comCap = false;
}
break;
case 32:
TM = (float)(unpoweredZoneCount + poweredZoneCount); /* dec score for unpowered zones */
if (TM > 0) {
if (poweredZoneCount / TM < 0.7) {
sendMessage(MESSAGE_BLACKOUTS_REPORTED);
}
}
break;
case 35:
if (pollutionAverage > /* 80 */ 60) {
sendMessage(MESSAGE_HIGH_POLLUTION, -1, -1, true);
}
break;
case 42:
if (crimeAverage > 100) {
sendMessage(MESSAGE_HIGH_CRIME, -1, -1, true);
}
break;
case 45:
if (totalPop > 60 && fireStationPop == 0) {
sendMessage(MESSAGE_NEED_FIRE_STATION);
}
break;
case 48:
if (totalPop > 60 && policeStationPop == 0) {
sendMessage(MESSAGE_NEED_POLICE_STATION);
}
break;
case 51:
if (cityTax > 12) {
sendMessage(MESSAGE_TAX_TOO_HIGH);
}
break;
case 54:
// If roadEffect < 5/8 of max effect
if (roadEffect < (5 * MAX_ROAD_EFFECT / 8) && roadTotal > 30) {
sendMessage(MESSAGE_ROAD_NEEDS_FUNDING);
}
break;
case 57:
// If fireEffect < 0.7 of max effect
if (fireEffect < (7 * MAX_FIRE_STATION_EFFECT / 10) && totalPop > 20) {
sendMessage(MESSAGE_FIRE_STATION_NEEDS_FUNDING);
}
break;
case 60:
// If policeEffect < 0.7 of max effect
if (policeEffect < (7 * MAX_POLICE_STATION_EFFECT / 10)
&& totalPop > 20) {
sendMessage(MESSAGE_POLICE_NEEDS_FUNDING);
}
break;
case 63:
if (trafficAverage > 60) {
sendMessage(MESSAGE_TRAFFIC_JAMS, -1, -1, true);
}
break;
}
}
/**
* Detect a change in city class, and produce a message if the player has
* reached the next class.
* @todo This code is very closely related to Micropolis::doPopNum().
* Maybe merge both in some way?
* (This function gets called much more often however then doPopNum().
* Also, at the first call, the difference between thisCityPop and
* cityPop is huge.)
*/
void Micropolis::checkGrowth()
{
if ((cityTime & 3) == 0) {
short category = 0;
Quad thisCityPop = getPopulation();
if (cityPopLast > 0) {
CityClass lastClass = getCityClass(cityPopLast);
CityClass newClass = getCityClass(thisCityPop);
if (lastClass != newClass) {
// Switched class, find appropiate message.
switch (newClass) {
case CC_VILLAGE:
// Don't mention it.
break;
case CC_TOWN:
category = MESSAGE_REACHED_TOWN;
break;
case CC_CITY:
category = MESSAGE_REACHED_CITY;
break;
case CC_CAPITAL:
category = MESSAGE_REACHED_CAPITAL;
break;
case CC_METROPOLIS:
category = MESSAGE_REACHED_METROPOLIS;
break;
case CC_MEGALOPOLIS:
category = MESSAGE_REACHED_MEGALOPOLIS;
break;
default:
NOT_REACHED();
break;
}
}
}
if (category > 0 && category != categoryLast) {
sendMessage(category, NOWHERE, NOWHERE, true);
categoryLast = category;
}
cityPopLast = thisCityPop;
}
}
/**
* Compute score for each scenario
* @param type Scenario used
* @note Parameter \a type may not be \c SC_NONE
*/
void Micropolis::doScenarioScore(Scenario type)
{
short z = MESSAGE_SCENARIO_LOST; /* you lose */
switch (type) {
case SC_DULLSVILLE:
if (cityClass >= CC_METROPOLIS) {
z = MESSAGE_SCENARIO_WON;
}
break;
case SC_SAN_FRANCISCO:
if (cityClass >= CC_METROPOLIS) {
z = MESSAGE_SCENARIO_WON;
}
break;
case SC_HAMBURG:
if (cityClass >= CC_METROPOLIS) {
z = MESSAGE_SCENARIO_WON;
}
break;
case SC_BERN:
if (trafficAverage < 80) {
z = MESSAGE_SCENARIO_WON;
}
break;
case SC_TOKYO:
if (cityScore > 500) {
z = MESSAGE_SCENARIO_WON;
}
break;
case SC_DETROIT:
if (crimeAverage < 60) {
z = MESSAGE_SCENARIO_WON;
}
break;
case SC_BOSTON:
if (cityScore > 500) {
z = MESSAGE_SCENARIO_WON;
}
break;
case SC_RIO:
if (cityScore > 500) {
z = MESSAGE_SCENARIO_WON;
}
break;
default:
NOT_REACHED();
break;
}
sendMessage(z, NOWHERE, NOWHERE, true, true);
if (z == MESSAGE_SCENARIO_LOST) {
doLoseGame();
}
}
/**
* Send the user a message of an event that happens at a particular position
* in the city.
* @param messageIndex Message number of the message to display.
* @param x X coordinate of the position of the event.
* @param y Y coordinate of the position of the event.
* @param picture Flag that is true if a picture should be shown.
* @param important Flag that is true if the message is important.
*/
void Micropolis::sendMessage(short messageIndex, short x, short y, bool picture, bool important)
{
callback->sendMessage(this, callbackVal, messageIndex, x, y, picture, important);
}
/**
* Make a sound for message \a mesgNum if appropriate.
* @param mesgNum Message number displayed.
* @param x Horizontal coordinate in the city of the sound.
* @param y Vertical coordinate in the city of the sound.
*/
void Micropolis::doMakeSound(int mesgNum, int x, int y)
{
assert(mesgNum >= 0);
switch (mesgNum) {
case MESSAGE_TRAFFIC_JAMS:
if (getRandom(5) == 1) {
makeSound("city", "HonkHonkMed", x, y);
} else if (getRandom(5) == 1) {
makeSound("city", "HonkHonkLow", x, y);
} else if (getRandom(5) == 1) {
makeSound("city", "HonkHonkHigh", x, y);
}
break;
case MESSAGE_HIGH_CRIME:
case MESSAGE_FIRE_REPORTED:
case MESSAGE_TORNADO_SIGHTED:
case MESSAGE_EARTHQUAKE:
case MESSAGE_PLANE_CRASHED:
case MESSAGE_SHIP_CRASHED:
case MESSAGE_TRAIN_CRASHED:
case MESSAGE_HELICOPTER_CRASHED:
makeSound("city", "Siren", x, y);
break;
case MESSAGE_MONSTER_SIGHTED:
makeSound("city", "Monster", x, y);
break;
case MESSAGE_FIREBOMBING:
makeSound("city", "ExplosionLow", x, y);
makeSound("city", "Siren", x, y);
break;
case MESSAGE_NUCLEAR_MELTDOWN:
makeSound("city", "ExplosionHigh", x, y);
makeSound("city", "ExplosionLow", x, y);
makeSound("city", "Siren", x, y);
break;
case MESSAGE_RIOTS_REPORTED:
makeSound("city", "Siren", x, y);
break;
}
}
/**
* Tell the front-end that it should perform an auto-goto
* @param x X position at the map
* @param y Y position at the map
* @param message Message
*/
void Micropolis::doAutoGoto(short x, short y, const std::string &message)
{
callback->autoGoto(this, callbackVal, x, y, message);
}
/** Tell the front-end that the player has lost the game */
void Micropolis::doLoseGame()
{
callback->didLoseGame(this, callbackVal);
}
/** Tell the front-end that the player has won the game */
void Micropolis::doWinGame()
{
callback->didWinGame(this, callbackVal);
}
////////////////////////////////////////////////////////////////////////